From 55539bdecd243e3f9d82b2043a52bbedebc42914 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 12:17:37 +0200 Subject: [PATCH 01/18] Add InstaNovo search integration --- README.md | 4 +- resources/conf/searchGUI_configuration.txt | 5 +- .../java/eu/isas/searchgui/SearchHandler.java | 361 ++++++++++++++++++ .../java/eu/isas/searchgui/cmd/SearchCLI.java | 18 +- .../searchgui/cmd/SearchCLIInputBean.java | 206 +++++++--- .../isas/searchgui/cmd/SearchCLIParams.java | 32 +- .../java/eu/isas/searchgui/gui/SearchGUI.java | 26 ++ .../InstaNovoProcessBuilder.java | 181 +++++++++ src/main/resources/helpFiles/SearchGUI.html | 3 +- .../searchgui/cmd/SearchCLIInstaNovoTest.java | 126 ++++++ .../InstaNovoProcessBuilderTest.java | 189 +++++++++ src/test/resources/helpFiles/SearchGUI.html | 3 +- 12 files changed, 1075 insertions(+), 79 deletions(-) create mode 100644 src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java create mode 100644 src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java create mode 100644 src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java diff --git a/README.md b/README.md index 6f6d9498..7b1b4371 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ ## Introduction -SearchGUI is a a highly adaptable open-source common interface for configuring and running proteomics search and de novo engines, currently supporting [X! Tandem](http://www.thegpm.org/tandem), [MyriMatch](http://forge.fenchurch.mc.vanderbilt.edu/scm/viewvc.php/*checkout*/trunk/doc/index.html?root=myrimatch), [MS Amanda](http://ms.imp.ac.at/?goto=msamanda), [MS-GF+](https://github.com/MSGFPlus/msgfplus), [OMSSA](http://www.ncbi.nlm.nih.gov/pubmed/15473683), [Comet](https://uwpr.github.io/Comet), [Tide](http://cruxtoolkit.sourceforge.net), [Andromeda](http://www.andromeda-search.org), [MetaMorpheus](https://github.com/smith-chem-wisc/MetaMorpheus), [Sage](https://github.com/lazear/sage), [Novor](http://rapidnovor.com) and [DirecTag](http://fenchurch.mc.vanderbilt.edu/bumbershoot/directag/). +SearchGUI is a a highly adaptable open-source common interface for configuring and running proteomics search and de novo engines, currently supporting [X! Tandem](http://www.thegpm.org/tandem), [MyriMatch](http://forge.fenchurch.mc.vanderbilt.edu/scm/viewvc.php/*checkout*/trunk/doc/index.html?root=myrimatch), [MS Amanda](http://ms.imp.ac.at/?goto=msamanda), [MS-GF+](https://github.com/MSGFPlus/msgfplus), [OMSSA](http://www.ncbi.nlm.nih.gov/pubmed/15473683), [Comet](https://uwpr.github.io/Comet), [Tide](http://cruxtoolkit.sourceforge.net), [Andromeda](http://www.andromeda-search.org), [MetaMorpheus](https://github.com/smith-chem-wisc/MetaMorpheus), [Sage](https://github.com/lazear/sage), [Novor](http://rapidnovor.com), [DirecTag](http://fenchurch.mc.vanderbilt.edu/bumbershoot/directag/), [InstaNovo](https://github.com/instadeepai/instanovo) and [InstaNovo+](https://github.com/instadeepai/instanovo). To start using SearchGUI, unzip the downloaded file, and double-click the `SearchGUI-X.Y.Z.jar file`. No additional installation required! @@ -63,6 +63,8 @@ The main purpose of SearchGUI is to make it simpler to use multiple search engin For details about the command line see: [SearchCLI](https://github.com/compomics/searchgui/wiki/SearchCLI). +InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. + [Go to top of page](#searchgui) ---- diff --git a/resources/conf/searchGUI_configuration.txt b/resources/conf/searchGUI_configuration.txt index 3faea0ec..ccf527a2 100644 --- a/resources/conf/searchGUI_configuration.txt +++ b/resources/conf/searchGUI_configuration.txt @@ -31,6 +31,9 @@ false Novor Location: Not Selected false +InstaNovo Location: +Not Selected +false DirecTag Location: Not Selected false @@ -38,4 +41,4 @@ makeblastdb Location: Not Selected Modification use: -Acetylation of K//Acetylation of protein N-term//Carbamidomethylation of C//Oxidation of M//Phosphorylation of S//Phosphorylation of T//Phosphorylation of Y//iTRAQ 4-plex of peptide N-term//iTRAQ 4-plex of K//iTRAQ 4-plex of Y//iTRAQ 8-plex of peptide N-term//iTRAQ 8-plex of K//iTRAQ 8-plex of Y//TMT 6-plex of peptide N-term//TMT 6-plex of K//TMT 10-plex of peptide N-term//TMT 10-plex of K//Pyrolidone from E//Pyrolidone from Q//Pyrolidone from carbamidomethylated C//Deamidation of N//Deamidation of Q// \ No newline at end of file +Acetylation of K//Acetylation of protein N-term//Carbamidomethylation of C//Oxidation of M//Phosphorylation of S//Phosphorylation of T//Phosphorylation of Y//iTRAQ 4-plex of peptide N-term//iTRAQ 4-plex of K//iTRAQ 4-plex of Y//iTRAQ 8-plex of peptide N-term//iTRAQ 8-plex of K//iTRAQ 8-plex of Y//TMT 6-plex of peptide N-term//TMT 6-plex of K//TMT 10-plex of peptide N-term//TMT 10-plex of K//Pyrolidone from E//Pyrolidone from Q//Pyrolidone from carbamidomethylated C//Deamidation of N//Deamidation of Q// diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index 5562df06..32d8a443 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -141,6 +141,18 @@ public class SearchHandler { * If true, Novor will be used. */ private boolean enableNovor = false; + /** + * If true, InstaNovo will be used. + */ + private boolean enableInstaNovo = false; + /** + * If true, standalone InstaNovo+ will be used. + */ + private boolean enableInstaNovoPlus = false; + /** + * If true, InstaNovo with InstaNovo+ refinement will be used. + */ + private boolean enableInstaNovoRefine = false; /** * If true, DirecTag will be used. */ @@ -230,6 +242,10 @@ public class SearchHandler { * The Novor location. */ private File novorLocation = null; + /** + * The InstaNovo location. + */ + private File instaNovoLocation = null; /** * The DirecTag location. */ @@ -310,6 +326,10 @@ public class SearchHandler { * The Novor process. */ private NovorProcessBuilder novorProcessBuilder = null; + /** + * The InstaNovo process. + */ + private InstaNovoProcessBuilder instaNovoProcessBuilder = null; /** * The DirecTag process. */ @@ -557,6 +577,17 @@ public SearchHandler( false ); + enableInstaNovo = loadSearchEngineLocation( + Advocate.instanovo, + true, + true, + true, + true, + false, + false, + false + ); + enableDirecTag = loadSearchEngineLocation( Advocate.direcTag, false, @@ -651,6 +682,9 @@ public SearchHandler( boolean runMetaMorpheus, boolean runSage, boolean runNovor, + boolean runInstaNovo, + boolean runInstaNovoPlus, + boolean runInstaNovoRefine, boolean runDirecTag, File omssaFolder, File xTandemFolder, @@ -664,6 +698,7 @@ public SearchHandler( File metaMorpheusFolder, File sageFolder, File novorFolder, + File instaNovoFolder, File direcTagFolder, File makeblastdbFolder, ProcessingParameters processingParameters @@ -691,6 +726,9 @@ public SearchHandler( this.enableMetaMorpheus = runMetaMorpheus; this.enableSage = runSage; this.enableNovor = runNovor; + this.enableInstaNovo = runInstaNovo; + this.enableInstaNovoPlus = runInstaNovoPlus; + this.enableInstaNovoRefine = runInstaNovoRefine; this.enableDirecTag = runDirecTag; this.identificationParameters = identificationParameters; @@ -864,6 +902,21 @@ public SearchHandler( ); // try to use the default } + if (instaNovoFolder != null) { + this.instaNovoLocation = instaNovoFolder; + } else { + loadSearchEngineLocation( + Advocate.instanovo, + true, + true, + true, + true, + false, + false, + false + ); // try to use the default + } + if (direcTagFolder != null) { this.direcTagLocation = direcTagFolder; } else { @@ -1383,6 +1436,8 @@ private boolean loadSearchEngineLocation( sageLocation = searchEngineLoation; } else if (searchEngineAdvocate == Advocate.novor) { novorLocation = searchEngineLoation; + } else if (searchEngineAdvocate == Advocate.instanovo) { + instaNovoLocation = searchEngineLoation; } else if (searchEngineAdvocate == Advocate.direcTag) { direcTagLocation = searchEngineLoation; } @@ -1523,6 +1578,39 @@ public static String getNovorFileName(String spectrumFileName) { return IoUtil.removeExtension(spectrumFileName) + ".novor.csv"; } + /** + * Returns the name of the InstaNovo transformer-only result file. + * + * @param spectrumFileName the name of the spectrum file searched + * + * @return the name of the InstaNovo result file + */ + public static String getInstaNovoFileName(String spectrumFileName) { + return IoUtil.removeExtension(spectrumFileName) + ".instanovo.csv"; + } + + /** + * Returns the name of the standalone InstaNovo+ result file. + * + * @param spectrumFileName the name of the spectrum file searched + * + * @return the name of the InstaNovo+ result file + */ + public static String getInstaNovoPlusFileName(String spectrumFileName) { + return IoUtil.removeExtension(spectrumFileName) + ".instanovoplus.csv"; + } + + /** + * Returns the name of the InstaNovo with InstaNovo+ refinement result file. + * + * @param spectrumFileName the name of the spectrum file searched + * + * @return the name of the refined InstaNovo result file + */ + public static String getInstaNovoRefinedFileName(String spectrumFileName) { + return IoUtil.removeExtension(spectrumFileName) + ".instanovo.refined.csv"; + } + /** * Returns the name of the DirecTag result file. * @@ -1871,6 +1959,15 @@ public File getNovorLocation() { return novorLocation; } + /** + * Returns the InstaNovo location. + * + * @return the InstaNovo location + */ + public File getInstaNovoLocation() { + return instaNovoLocation; + } + /** * Set the Novor location. * @@ -1880,6 +1977,15 @@ public void setNovorLocation(File novorLocation) { this.novorLocation = novorLocation; } + /** + * Set the InstaNovo location. + * + * @param instaNovoLocation the InstaNovo location to set + */ + public void setInstaNovoLocation(File instaNovoLocation) { + this.instaNovoLocation = instaNovoLocation; + } + /** * Returns the DirecTag location. * @@ -2051,6 +2157,33 @@ public boolean isNovorEnabled() { return enableNovor; } + /** + * Returns true if InstaNovo is to be used. + * + * @return if InstaNovo is to be used + */ + public boolean isInstaNovoEnabled() { + return enableInstaNovo; + } + + /** + * Returns true if standalone InstaNovo+ is to be used. + * + * @return if standalone InstaNovo+ is to be used + */ + public boolean isInstaNovoPlusEnabled() { + return enableInstaNovoPlus; + } + + /** + * Returns true if InstaNovo with InstaNovo+ refinement is to be used. + * + * @return if InstaNovo with InstaNovo+ refinement is to be used + */ + public boolean isInstaNovoRefineEnabled() { + return enableInstaNovoRefine; + } + /** * Returns true if DirecTag is to be used. * @@ -2150,6 +2283,33 @@ public void setNovorEnabled(boolean runNovor) { this.enableNovor = runNovor; } + /** + * Set if InstaNovo is to be used. + * + * @param runInstaNovo run InstaNovo? + */ + public void setInstaNovoEnabled(boolean runInstaNovo) { + this.enableInstaNovo = runInstaNovo; + } + + /** + * Set if standalone InstaNovo+ is to be used. + * + * @param runInstaNovoPlus run standalone InstaNovo+? + */ + public void setInstaNovoPlusEnabled(boolean runInstaNovoPlus) { + this.enableInstaNovoPlus = runInstaNovoPlus; + } + + /** + * Set if InstaNovo with InstaNovo+ refinement is to be used. + * + * @param runInstaNovoRefine run InstaNovo with InstaNovo+ refinement? + */ + public void setInstaNovoRefineEnabled(boolean runInstaNovoRefine) { + this.enableInstaNovoRefine = runInstaNovoRefine; + } + /** * Set if DirecTag is to be used. * @@ -2510,6 +2670,15 @@ protected Object doInBackground() { if (enableNovor) { nProgress += nFilesToSearch; } + if (enableInstaNovo) { + nProgress += nFilesToSearch; + } + if (enableInstaNovoPlus) { + nProgress += nFilesToSearch; + } + if (enableInstaNovoRefine) { + nProgress += nFilesToSearch; + } if (enableDirecTag) { nProgress += nFilesToSearch; } @@ -3794,6 +3963,111 @@ protected Object doInBackground() { } + // Run InstaNovo + if (enableInstaNovo && !waitingHandler.isRunCanceled()) { + + File instaNovoOutputFile = new File(outputTempFolder, getInstaNovoFileName(spectrumFileName)); + + instaNovoProcessBuilder = new InstaNovoProcessBuilder( + instaNovoLocation, + spectrumFile, + instaNovoOutputFile, + InstaNovoProcessBuilder.Mode.transformer, + waitingHandler, + exceptionHandler + ); + + waitingHandler.appendReport( + "Processing " + spectrumFile.getName() + " with " + Advocate.instanovo.getName() + ".", + true, + true + ); + + waitingHandler.appendReportEndLine(); + instaNovoProcessBuilder.startProcess(); + + if (!waitingHandler.isRunCanceled()) { + registerIdentificationFile( + identificationFiles, + spectrumFileName, + Advocate.instanovo, + instaNovoOutputFile, + spectrumFile + ); + waitingHandler.increasePrimaryProgressCounter(); + } + } + + // Run standalone InstaNovo+ + if (enableInstaNovoPlus && !waitingHandler.isRunCanceled()) { + + File instaNovoPlusOutputFile = new File(outputTempFolder, getInstaNovoPlusFileName(spectrumFileName)); + + instaNovoProcessBuilder = new InstaNovoProcessBuilder( + instaNovoLocation, + spectrumFile, + instaNovoPlusOutputFile, + InstaNovoProcessBuilder.Mode.diffusion, + waitingHandler, + exceptionHandler + ); + + waitingHandler.appendReport( + "Processing " + spectrumFile.getName() + " with " + Advocate.instanovoPlus.getName() + ".", + true, + true + ); + + waitingHandler.appendReportEndLine(); + instaNovoProcessBuilder.startProcess(); + + if (!waitingHandler.isRunCanceled()) { + registerIdentificationFile( + identificationFiles, + spectrumFileName, + Advocate.instanovoPlus, + instaNovoPlusOutputFile, + spectrumFile + ); + waitingHandler.increasePrimaryProgressCounter(); + } + } + + // Run InstaNovo with InstaNovo+ refinement + if (enableInstaNovoRefine && !waitingHandler.isRunCanceled()) { + + File instaNovoRefinedOutputFile = new File(outputTempFolder, getInstaNovoRefinedFileName(spectrumFileName)); + + instaNovoProcessBuilder = new InstaNovoProcessBuilder( + instaNovoLocation, + spectrumFile, + instaNovoRefinedOutputFile, + InstaNovoProcessBuilder.Mode.refined, + waitingHandler, + exceptionHandler + ); + + waitingHandler.appendReport( + "Processing " + spectrumFile.getName() + " with InstaNovo and InstaNovo+ refinement.", + true, + true + ); + + waitingHandler.appendReportEndLine(); + instaNovoProcessBuilder.startProcess(); + + if (!waitingHandler.isRunCanceled()) { + registerIdentificationFile( + identificationFiles, + spectrumFileName, + Advocate.instanovoPlus, + instaNovoRefinedOutputFile, + spectrumFile + ); + waitingHandler.increasePrimaryProgressCounter(); + } + } + // Run DirecTag if (enableDirecTag && !waitingHandler.isRunCanceled()) { @@ -4207,6 +4481,54 @@ protected Object doInBackground() { } + if (enableInstaNovo) { + + File outputFile = getDefaultOutputFile( + outputFolder, + Advocate.instanovo.getName(), + utilitiesUserParameters.isIncludeDateInOutputName() + ); + + if (outputFile.exists()) { + + identificationFilesList.add(outputFile); + + } else { + + waitingHandler.appendReport( + "Could not find " + Advocate.instanovo.getName() + " results.", + true, + true + ); + + } + + } + + if (enableInstaNovoPlus || enableInstaNovoRefine) { + + File outputFile = getDefaultOutputFile( + outputFolder, + Advocate.instanovoPlus.getName(), + utilitiesUserParameters.isIncludeDateInOutputName() + ); + + if (outputFile.exists()) { + + identificationFilesList.add(outputFile); + + } else { + + waitingHandler.appendReport( + "Could not find " + Advocate.instanovoPlus.getName() + " results.", + true, + true + ); + + } + + } + } else if (utilitiesUserParameters.getSearchGuiOutputParameters() == OutputParameters.run) { for (String run : identificationFiles.keySet()) { @@ -4440,6 +4762,45 @@ public boolean isFinished() { } + /** + * Registers a search engine identification file. + * + * @param identificationFiles the identification files map + * @param spectrumFileName the spectrum file name + * @param advocate the advocate + * @param identificationFile the identification file + * @param spectrumFile the spectrum file + */ + private void registerIdentificationFile( + HashMap> identificationFiles, + String spectrumFileName, + Advocate advocate, + File identificationFile, + File spectrumFile + ) { + + HashMap runIdentificationFiles = identificationFiles.get(spectrumFileName); + + if (runIdentificationFiles == null) { + runIdentificationFiles = new HashMap<>(); + identificationFiles.put(spectrumFileName, runIdentificationFiles); + } + + if (identificationFile.exists()) { + + runIdentificationFiles.put(advocate.getIndex(), identificationFile); + idFileToSpectrumFileMap.put(identificationFile.getName(), spectrumFile); + + } else { + + waitingHandler.appendReport( + "Could not find " + advocate.getName() + " result file for " + spectrumFileName + ".", + true, + true + ); + } + } + /** * Save the input file. * diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLI.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLI.java index 09545005..586c1525 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLI.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLI.java @@ -208,10 +208,13 @@ public Object call() { searchCLIInputBean.isCometEnabled(), searchCLIInputBean.isTideEnabled(), searchCLIInputBean.isAndromedaEnabled(), - searchCLIInputBean.isMetaMorpheusEnabled(), - searchCLIInputBean.isSageEnabled(), - searchCLIInputBean.isNovorEnabled(), - searchCLIInputBean.isDirecTagEnabled(), + searchCLIInputBean.isMetaMorpheusEnabled(), + searchCLIInputBean.isSageEnabled(), + searchCLIInputBean.isNovorEnabled(), + searchCLIInputBean.isInstaNovoEnabled(), + searchCLIInputBean.isInstaNovoPlusEnabled(), + searchCLIInputBean.isInstaNovoRefineEnabled(), + searchCLIInputBean.isDirecTagEnabled(), searchCLIInputBean.getOmssaLocation(), searchCLIInputBean.getXtandemLocation(), searchCLIInputBean.getMsgfLocation(), @@ -222,9 +225,10 @@ public Object call() { searchCLIInputBean.getTideIndexLocation(), searchCLIInputBean.getAndromedaLocation(), searchCLIInputBean.getMetaMorpheusLocation(), - searchCLIInputBean.getSageLocation(), - searchCLIInputBean.getNovorLocation(), - searchCLIInputBean.getDirecTagLocation(), + searchCLIInputBean.getSageLocation(), + searchCLIInputBean.getNovorLocation(), + searchCLIInputBean.getInstaNovoLocation(), + searchCLIInputBean.getDirecTagLocation(), searchCLIInputBean.getMakeblastdbLocation(), processingParameters ); diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java index debe7f93..8a3e9264 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java @@ -84,13 +84,25 @@ public class SearchCLIInputBean { * If true, Sage is enabled. */ private boolean sageEnabled = false; - /** - * If true, Novor is enabled. - */ - private boolean novorEnabled = false; - /** - * If true, DirecTag is enabled. - */ + /** + * If true, Novor is enabled. + */ + private boolean novorEnabled = false; + /** + * If true, InstaNovo is enabled. + */ + private boolean instaNovoEnabled = false; + /** + * If true, standalone InstaNovo+ is enabled. + */ + private boolean instaNovoPlusEnabled = false; + /** + * If true, InstaNovo with InstaNovo+ refinement is enabled. + */ + private boolean instaNovoRefineEnabled = false; + /** + * If true, DirecTag is enabled. + */ private boolean direcTagEnabled = false; /** * The folder where OMSSA is installed. @@ -136,13 +148,17 @@ public class SearchCLIInputBean { * The folder where Sage is installed. */ private File sageLocation = null; - /** - * The folder where Novor is installed. - */ - private File novorLocation = null; - /** - * The folder where DirecTag is installed. - */ + /** + * The folder where Novor is installed. + */ + private File novorLocation = null; + /** + * The folder where InstaNovo is installed. + */ + private File instaNovoLocation = null; + /** + * The folder where DirecTag is installed. + */ private File direcTagLocation = null; /** * The folder where makeblastdb is installed. @@ -299,13 +315,25 @@ public SearchCLIInputBean(CommandLine aLine) throws IOException, ClassNotFoundEx String sageOption = aLine.getOptionValue(SearchCLIParams.SAGE.id); sageEnabled = sageOption.trim().equals("1"); } - if (aLine.hasOption(SearchCLIParams.NOVOR.id)) { - String novorOption = aLine.getOptionValue(SearchCLIParams.NOVOR.id); - novorEnabled = novorOption.trim().equals("1"); - } - if (aLine.hasOption(SearchCLIParams.DIRECTAG.id)) { - String direcTagOption = aLine.getOptionValue(SearchCLIParams.DIRECTAG.id); - direcTagEnabled = direcTagOption.trim().equals("1"); + if (aLine.hasOption(SearchCLIParams.NOVOR.id)) { + String novorOption = aLine.getOptionValue(SearchCLIParams.NOVOR.id); + novorEnabled = novorOption.trim().equals("1"); + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO.id)) { + String instaNovoOption = aLine.getOptionValue(SearchCLIParams.INSTANOVO.id); + instaNovoEnabled = instaNovoOption.trim().equals("1"); + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_PLUS.id)) { + String instaNovoPlusOption = aLine.getOptionValue(SearchCLIParams.INSTANOVO_PLUS.id); + instaNovoPlusEnabled = instaNovoPlusOption.trim().equals("1"); + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_REFINE.id)) { + String instaNovoRefineOption = aLine.getOptionValue(SearchCLIParams.INSTANOVO_REFINE.id); + instaNovoRefineEnabled = instaNovoRefineOption.trim().equals("1"); + } + if (aLine.hasOption(SearchCLIParams.DIRECTAG.id)) { + String direcTagOption = aLine.getOptionValue(SearchCLIParams.DIRECTAG.id); + direcTagEnabled = direcTagOption.trim().equals("1"); } // search engine folders @@ -353,13 +381,17 @@ public SearchCLIInputBean(CommandLine aLine) throws IOException, ClassNotFoundEx String sageFolder = aLine.getOptionValue(SearchCLIParams.SAGE_LOCATION.id); sageLocation = new File(sageFolder); } - if (aLine.hasOption(SearchCLIParams.NOVOR_LOCATION.id)) { - String novorFolder = aLine.getOptionValue(SearchCLIParams.NOVOR_LOCATION.id); - novorLocation = new File(novorFolder); - } - if (aLine.hasOption(SearchCLIParams.DIRECTAG_LOCATION.id)) { - String direcTagFolder = aLine.getOptionValue(SearchCLIParams.DIRECTAG_LOCATION.id); - direcTagLocation = new File(direcTagFolder); + if (aLine.hasOption(SearchCLIParams.NOVOR_LOCATION.id)) { + String novorFolder = aLine.getOptionValue(SearchCLIParams.NOVOR_LOCATION.id); + novorLocation = new File(novorFolder); + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_LOCATION.id)) { + String instaNovoFolder = aLine.getOptionValue(SearchCLIParams.INSTANOVO_LOCATION.id); + instaNovoLocation = new File(instaNovoFolder); + } + if (aLine.hasOption(SearchCLIParams.DIRECTAG_LOCATION.id)) { + String direcTagFolder = aLine.getOptionValue(SearchCLIParams.DIRECTAG_LOCATION.id); + direcTagLocation = new File(direcTagFolder); } // makeblastdb folder @@ -611,10 +643,37 @@ public boolean isSageEnabled() { * * @return if Novor is to be used */ - public boolean isNovorEnabled() { - return novorEnabled; - } - + public boolean isNovorEnabled() { + return novorEnabled; + } + + /** + * Returns true if InstaNovo is to be used. + * + * @return if InstaNovo is to be used + */ + public boolean isInstaNovoEnabled() { + return instaNovoEnabled; + } + + /** + * Returns true if standalone InstaNovo+ is to be used. + * + * @return if standalone InstaNovo+ is to be used + */ + public boolean isInstaNovoPlusEnabled() { + return instaNovoPlusEnabled; + } + + /** + * Returns true if InstaNovo with InstaNovo+ refinement is to be used. + * + * @return if InstaNovo with InstaNovo+ refinement is to be used + */ + public boolean isInstaNovoRefineEnabled() { + return instaNovoRefineEnabled; + } + /** * Returns true if DirecTag is to be used. * @@ -728,10 +787,19 @@ public File getSageLocation() { * * @return the Novor location */ - public File getNovorLocation() { - return novorLocation; - } - + public File getNovorLocation() { + return novorLocation; + } + + /** + * Returns the InstaNovo location. + * + * @return the InstaNovo location + */ + public File getInstaNovoLocation() { + return instaNovoLocation; + } + /** * Returns the DirecTag location. * @@ -1005,15 +1073,33 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { return false; } } - if (aLine.hasOption(SearchCLIParams.NOVOR.id)) { - String input = aLine.getOptionValue(SearchCLIParams.NOVOR.id); - if (!CommandParameter.isBooleanInput(SearchCLIParams.NOVOR.id, input)) { - return false; - } - } - if (aLine.hasOption(SearchCLIParams.DIRECTAG.id)) { - String input = aLine.getOptionValue(SearchCLIParams.DIRECTAG.id); - if (!CommandParameter.isBooleanInput(SearchCLIParams.DIRECTAG.id, input)) { + if (aLine.hasOption(SearchCLIParams.NOVOR.id)) { + String input = aLine.getOptionValue(SearchCLIParams.NOVOR.id); + if (!CommandParameter.isBooleanInput(SearchCLIParams.NOVOR.id, input)) { + return false; + } + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO.id)) { + String input = aLine.getOptionValue(SearchCLIParams.INSTANOVO.id); + if (!CommandParameter.isBooleanInput(SearchCLIParams.INSTANOVO.id, input)) { + return false; + } + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_PLUS.id)) { + String input = aLine.getOptionValue(SearchCLIParams.INSTANOVO_PLUS.id); + if (!CommandParameter.isBooleanInput(SearchCLIParams.INSTANOVO_PLUS.id, input)) { + return false; + } + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_REFINE.id)) { + String input = aLine.getOptionValue(SearchCLIParams.INSTANOVO_REFINE.id); + if (!CommandParameter.isBooleanInput(SearchCLIParams.INSTANOVO_REFINE.id, input)) { + return false; + } + } + if (aLine.hasOption(SearchCLIParams.DIRECTAG.id)) { + String input = aLine.getOptionValue(SearchCLIParams.DIRECTAG.id); + if (!CommandParameter.isBooleanInput(SearchCLIParams.DIRECTAG.id, input)) { return false; } } @@ -1102,17 +1188,25 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { return false; } } - if (aLine.hasOption(SearchCLIParams.NOVOR_LOCATION.id)) { - String input = aLine.getOptionValue(SearchCLIParams.NOVOR_LOCATION.id); - File file = new File(input); - if (!file.exists()) { - System.out.println(System.getProperty("line.separator") + "The " + SearchCLIParams.NOVOR_LOCATION.id + " \'" + input + "\' does not exist." + System.getProperty("line.separator")); - return false; - } - } - if (aLine.hasOption(SearchCLIParams.DIRECTAG_LOCATION.id)) { - String input = aLine.getOptionValue(SearchCLIParams.DIRECTAG_LOCATION.id); - File file = new File(input); + if (aLine.hasOption(SearchCLIParams.NOVOR_LOCATION.id)) { + String input = aLine.getOptionValue(SearchCLIParams.NOVOR_LOCATION.id); + File file = new File(input); + if (!file.exists()) { + System.out.println(System.getProperty("line.separator") + "The " + SearchCLIParams.NOVOR_LOCATION.id + " \'" + input + "\' does not exist." + System.getProperty("line.separator")); + return false; + } + } + if (aLine.hasOption(SearchCLIParams.INSTANOVO_LOCATION.id)) { + String input = aLine.getOptionValue(SearchCLIParams.INSTANOVO_LOCATION.id); + File file = new File(input); + if (!file.exists()) { + System.out.println(System.getProperty("line.separator") + "The " + SearchCLIParams.INSTANOVO_LOCATION.id + " \'" + input + "\' does not exist." + System.getProperty("line.separator")); + return false; + } + } + if (aLine.hasOption(SearchCLIParams.DIRECTAG_LOCATION.id)) { + String input = aLine.getOptionValue(SearchCLIParams.DIRECTAG_LOCATION.id); + File file = new File(input); if (!file.exists()) { System.out.println(System.getProperty("line.separator") + "The " + SearchCLIParams.DIRECTAG_LOCATION.id + " \'" + input + "\' does not exist." + System.getProperty("line.separator")); return false; diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java index d2802ead..4f663dfa 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java @@ -30,9 +30,12 @@ public enum SearchCLIParams { TIDE("tide", "Turn the Tide search on or off (0: off, 1: on, default is '0').", false), ANDROMEDA("andromeda", "Turn the Andromeda search on or off (0: off, 1: on, default is '0').", false), META_MORPHEUS("meta_morpheus", "Turn the MetaMorpheus search on or off (0: off, 1: on, default is '0').", false), - SAGE("sage", "Turn the Sage search on or off (0: off, 1: on, default is '0').", false), - NOVOR("novor", "Turn the Novor sequencing on or off (0: off, 1: on, default is '0').", false), - DIRECTAG("directag", "Turn the DirecTag sequencing on or off (0: off, 1: on, default is '0').", false), + SAGE("sage", "Turn the Sage search on or off (0: off, 1: on, default is '0').", false), + NOVOR("novor", "Turn the Novor sequencing on or off (0: off, 1: on, default is '0').", false), + INSTANOVO("instanovo", "Turn the InstaNovo sequencing on or off (0: off, 1: on, default is '0').", false), + INSTANOVO_PLUS("instanovo_plus", "Turn the standalone InstaNovo+ sequencing on or off (0: off, 1: on, default is '0').", false), + INSTANOVO_REFINE("instanovo_refine", "Turn InstaNovo with InstaNovo+ refinement on or off (0: off, 1: on, default is '0').", false), + DIRECTAG("directag", "Turn the DirecTag sequencing on or off (0: off, 1: on, default is '0').", false), OMSSA_LOCATION("omssa_folder", "The folder where OMSSA is installed, defaults to the provided version for the given OS.", false), MAKEBLASTDB_LOCATION("makeblastdb_folder", "The folder where makeblastdb is installed, defaults to the provided version for the given OS.", false), XTANDEM_LOCATION("xtandem_folder", "The folder where X!Tandem is installed, defaults to the provided version for the given OS.", false), @@ -44,9 +47,10 @@ public enum SearchCLIParams { TIDE_INDEX_LOCATION("tide_index_file", "The folder where the Tide index should be stored. If this option is provided and the folder is not found, an index will be created in this folder and not deleted so that the index can be reused in subsequent runs. If this option is provided and the index is found, the creation of the index will be skipped. If this option is not provided, the index will always be created and stored either in the Tide or the temp folder and handled according to the Tide advanced parameters.", false), ANDROMEDA_LOCATION("andromeda_folder", "The folder where Andromeda is installed, defaults to the included version.", false), META_MORPHEUS_LOCATION("meta_morpheus_folder", "The folder where MetaMorpheus is installed, defaults to the included version.", false), - SAGE_LOCATION("sage_folder", "The folder where Sage is installed, defaults to the included version.", false), - NOVOR_LOCATION("novor_folder", "The folder where Novor is installed, defaults to the included version.", false), - DIRECTAG_LOCATION("directag_folder", "The folder where DirecTag is installed, defaults to the included version.", false), + SAGE_LOCATION("sage_folder", "The folder where Sage is installed, defaults to the included version.", false), + NOVOR_LOCATION("novor_folder", "The folder where Novor is installed, defaults to the included version.", false), + INSTANOVO_LOCATION("instanovo_folder", "The folder where InstaNovo is installed, defaults to the included launcher.", false), + DIRECTAG_LOCATION("directag_folder", "The folder where DirecTag is installed, defaults to the included version.", false), MGF_CHECK_SIZE("mgf_check_size", "Turn the mgf size check on or off (0: off, 1: on, default is '0').", false), MGF_SPLITTING_LIMIT("mgf_splitting", "The maximum mgf file size in MB before splitting the mgf. Default is '1000'.", false), MGF_MAX_SPECTRA("mgf_spectrum_count", "The maximum number of spectra per mgf file when splitting. Default is '25000'.", false), @@ -138,9 +142,12 @@ public static String getOptionsAsString() { output += "-" + String.format(formatter, COMET.id) + " " + COMET.description + "\n"; output += "-" + String.format(formatter, TIDE.id) + " " + TIDE.description + "\n"; output += "-" + String.format(formatter, ANDROMEDA.id) + " " + ANDROMEDA.description + "\n"; - output += "-" + String.format(formatter, META_MORPHEUS.id) + " " + META_MORPHEUS.description + "\n"; - output += "-" + String.format(formatter, NOVOR.id) + " " + NOVOR.description + "\n"; - output += "-" + String.format(formatter, DIRECTAG.id) + " " + DIRECTAG.description + "\n"; + output += "-" + String.format(formatter, META_MORPHEUS.id) + " " + META_MORPHEUS.description + "\n"; + output += "-" + String.format(formatter, NOVOR.id) + " " + NOVOR.description + "\n"; + output += "-" + String.format(formatter, INSTANOVO.id) + " " + INSTANOVO.description + "\n"; + output += "-" + String.format(formatter, INSTANOVO_PLUS.id) + " " + INSTANOVO_PLUS.description + "\n"; + output += "-" + String.format(formatter, INSTANOVO_REFINE.id) + " " + INSTANOVO_REFINE.description + "\n"; + output += "-" + String.format(formatter, DIRECTAG.id) + " " + DIRECTAG.description + "\n"; output += "\n\nTools Location:\n\n"; output += "-" + String.format(formatter, OMSSA_LOCATION.id) + " " + OMSSA_LOCATION.description + "\n"; @@ -152,9 +159,10 @@ public static String getOptionsAsString() { output += "-" + String.format(formatter, TIDE_LOCATION.id) + " " + TIDE_LOCATION.description + "\n"; output += "-" + String.format(formatter, TIDE_INDEX_LOCATION.id) + " " + TIDE_INDEX_LOCATION.description + "\n"; output += "-" + String.format(formatter, ANDROMEDA_LOCATION.id) + " " + ANDROMEDA_LOCATION.description + "\n"; - output += "-" + String.format(formatter, META_MORPHEUS_LOCATION.id) + " " + META_MORPHEUS_LOCATION.description + "\n"; - output += "-" + String.format(formatter, NOVOR_LOCATION.id) + " " + NOVOR_LOCATION.description + "\n"; - output += "-" + String.format(formatter, DIRECTAG_LOCATION.id) + " " + DIRECTAG_LOCATION.description + "\n"; + output += "-" + String.format(formatter, META_MORPHEUS_LOCATION.id) + " " + META_MORPHEUS_LOCATION.description + "\n"; + output += "-" + String.format(formatter, NOVOR_LOCATION.id) + " " + NOVOR_LOCATION.description + "\n"; + output += "-" + String.format(formatter, INSTANOVO_LOCATION.id) + " " + INSTANOVO_LOCATION.description + "\n"; + output += "-" + String.format(formatter, DIRECTAG_LOCATION.id) + " " + DIRECTAG_LOCATION.description + "\n"; output += "-" + String.format(formatter, MAKEBLASTDB_LOCATION.id) + " " + MAKEBLASTDB_LOCATION.description + "\n"; output += "\n\nInput Files Handling:\n\n"; diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 476094e0..3ae6905f 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -35,6 +35,7 @@ import com.compomics.util.gui.error_handlers.BugReport; import com.compomics.util.gui.waiting.waitinghandlers.ProgressDialogX; import eu.isas.searchgui.SearchHandler; +import eu.isas.searchgui.processbuilders.InstaNovoProcessBuilder; import java.awt.Color; import java.awt.Toolkit; import java.io.*; @@ -7908,6 +7909,8 @@ private void saveConfigurationFile() { bw.write(searchHandler.getSageLocation() + System.getProperty("line.separator") + searchHandler.isSageEnabled() + System.getProperty("line.separator")); bw.write("Novor Location:" + System.getProperty("line.separator")); bw.write(searchHandler.getNovorLocation() + System.getProperty("line.separator") + searchHandler.isNovorEnabled() + System.getProperty("line.separator")); + bw.write("InstaNovo Location:" + System.getProperty("line.separator")); + bw.write(searchHandler.getInstaNovoLocation() + System.getProperty("line.separator") + searchHandler.isInstaNovoEnabled() + System.getProperty("line.separator")); bw.write("DirecTag Location:" + System.getProperty("line.separator")); bw.write(searchHandler.getDirecTagLocation() + System.getProperty("line.separator") + searchHandler.isDirecTagEnabled() + System.getProperty("line.separator")); bw.write("makeblastdb Location:" + System.getProperty("line.separator")); @@ -8271,6 +8274,29 @@ public static boolean validateSearchEngineInstallation( feedBackInDialog ); + } else if (advocate == Advocate.instanovo || advocate == Advocate.instanovoPlus) { + + if (searchEngineLocation != null) { + + File virtualEnvironmentExecutable = new File(searchEngineLocation, ".venv" + File.separator + "bin" + File.separator + InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME); + File executable = new File(searchEngineLocation, InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME); + + if (virtualEnvironmentExecutable.exists() || executable.exists()) { + return true; + } + } + + return validateSearchEngineInstallation( + advocate, + InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME, + "version", + null, + searchEngineLocation, + null, + false, + feedBackInDialog + ); + } else if (advocate == Advocate.direcTag) { return validateSearchEngineInstallation( diff --git a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java new file mode 100644 index 00000000..138ebebf --- /dev/null +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -0,0 +1,181 @@ +package eu.isas.searchgui.processbuilders; + +import com.compomics.util.exceptions.ExceptionHandler; +import com.compomics.util.parameters.identification.tool_specific.InstaNovoParameters; +import com.compomics.util.waiting.WaitingHandler; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Process builder for InstaNovo and InstaNovo+. + * + * @author CompOmics + */ +public class InstaNovoProcessBuilder extends SearchGUIProcessBuilder { + + /** + * The InstaNovo executable name. + */ + public static final String EXECUTABLE_FILE_NAME = "instanovo"; + + /** + * InstaNovo execution modes. + */ + public enum Mode { + transformer, + diffusion, + refined + } + + /** + * The input spectrum file. + */ + private final File spectrumFile; + /** + * The output file. + */ + private final File outputFile; + /** + * The mode. + */ + private final Mode mode; + + /** + * Constructor. + * + * @param instaNovoFolder the InstaNovo folder + * @param spectrumFile the spectrum file + * @param outputFile the output file + * @param mode the mode + * @param waitingHandler the waiting handler + * @param exceptionHandler the exception handler + */ + public InstaNovoProcessBuilder( + File instaNovoFolder, + File spectrumFile, + File outputFile, + Mode mode, + WaitingHandler waitingHandler, + ExceptionHandler exceptionHandler + ) { + + this.spectrumFile = spectrumFile; + this.outputFile = outputFile; + this.mode = mode; + this.waitingHandler = waitingHandler; + this.exceptionHandler = exceptionHandler; + + File executableFile = getExecutable(instaNovoFolder); + process_name_array.add(executableFile.getPath()); + + if (mode == Mode.transformer) { + process_name_array.add("transformer"); + process_name_array.add("predict"); + } else if (mode == Mode.diffusion) { + process_name_array.add("diffusion"); + process_name_array.add("predict"); + } else { + process_name_array.add("predict"); + } + + process_name_array.add("--data-path"); + process_name_array.add(spectrumFile.getAbsolutePath()); + process_name_array.add("--output-path"); + process_name_array.add(outputFile.getAbsolutePath()); + process_name_array.add("--denovo"); + + if (mode == Mode.transformer || mode == Mode.refined) { + process_name_array.add("--instanovo-model"); + process_name_array.add(InstaNovoParameters.DEFAULT_INSTANOVO_MODEL); + } + + if (mode == Mode.diffusion || mode == Mode.refined) { + process_name_array.add("--instanovo-plus-model"); + process_name_array.add(InstaNovoParameters.DEFAULT_INSTANOVO_PLUS_MODEL); + } + + if (mode == Mode.diffusion) { + process_name_array.add("--no-refinement"); + } else if (mode == Mode.refined) { + process_name_array.add("--with-refinement"); + } + + process_name_array.trimToSize(); + + System.out.println(System.getProperty("line.separator") + System.getProperty("line.separator") + "instanovo command: "); + for (Object element : process_name_array) { + System.out.print(element + " "); + } + System.out.println(System.getProperty("line.separator")); + + pb = new ProcessBuilder(process_name_array); + if (instaNovoFolder != null && instaNovoFolder.exists()) { + pb.directory(instaNovoFolder); + } + pb.redirectErrorStream(true); + } + + /** + * Returns the executable. + * + * @param instaNovoFolder the InstaNovo folder + * + * @return the executable + */ + private File getExecutable(File instaNovoFolder) { + + if (instaNovoFolder != null) { + + File virtualEnvironmentExecutable = new File( + instaNovoFolder, + ".venv" + File.separator + "bin" + File.separator + EXECUTABLE_FILE_NAME + ); + + if (virtualEnvironmentExecutable.exists()) { + virtualEnvironmentExecutable.setExecutable(true); + return virtualEnvironmentExecutable; + } + + File executable = new File(instaNovoFolder, EXECUTABLE_FILE_NAME); + + if (executable.exists()) { + executable.setExecutable(true); + return executable; + } + } + + return new File(EXECUTABLE_FILE_NAME); + } + + /** + * Returns the command line used by the process builder. + * + * @return the command line used by the process builder + */ + public List getCommand() { + + ArrayList result = new ArrayList<>(); + + for (Object argument : process_name_array) { + result.add(argument.toString()); + } + + return result; + } + + @Override + public String getType() { + if (mode == Mode.diffusion) { + return "InstaNovo+"; + } else if (mode == Mode.refined) { + return "InstaNovo with InstaNovo+ refinement"; + } + return "InstaNovo"; + } + + @Override + public String getCurrentlyProcessedFileName() { + return spectrumFile.getName(); + } +} diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index 2f868696..0ed7574f 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -11,7 +11,8 @@

SearchGUI



SearchGUI is a user-friendly interface for configuring and running proteomics - identification search engines and de novo sequencing algorithms. + identification search engines and de novo sequencing algorithms, including + InstaNovo and InstaNovo+ from the command line.

For more details and help see the SearchGUI home page: http://compomics.github.io/projects/searchgui.html. diff --git a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java new file mode 100644 index 00000000..0df69e57 --- /dev/null +++ b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java @@ -0,0 +1,126 @@ +package eu.isas.searchgui.cmd; + +import java.io.File; +import junit.framework.TestCase; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.junit.Assert; + +/** + * Tests the InstaNovo SearchCLI integration. + * + * @author CompOmics + */ +public class SearchCLIInstaNovoTest extends TestCase { + + /** + * Tests parsing of the InstaNovo command line options. + * + * @throws Exception if an exception occurs + */ + public void testInstaNovoCliParsing() throws Exception { + + File instaNovoFolder = createFolder("instanovo-cli"); + CommandLine commandLine = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-fasta_file", createFile("database", ".fasta").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-instanovo", "1", + "-instanovo_plus", "1", + "-instanovo_refine", "1", + "-instanovo_folder", instaNovoFolder.getAbsolutePath() + ); + + SearchCLIInputBean inputBean = new SearchCLIInputBean(commandLine); + + Assert.assertTrue(inputBean.isInstaNovoEnabled()); + Assert.assertTrue(inputBean.isInstaNovoPlusEnabled()); + Assert.assertTrue(inputBean.isInstaNovoRefineEnabled()); + Assert.assertEquals(instaNovoFolder.getAbsoluteFile(), inputBean.getInstaNovoLocation().getAbsoluteFile()); + } + + /** + * Tests validation and help listing of the InstaNovo command line options. + * + * @throws Exception if an exception occurs + */ + public void testInstaNovoCliValidationAndHelp() throws Exception { + + CommandLine invalidBoolean = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-fasta_file", createFile("database", ".fasta").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-instanovo", "2" + ); + Assert.assertFalse(SearchCLIInputBean.isValidStartup(invalidBoolean)); + + CommandLine missingFolder = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-fasta_file", createFile("database", ".fasta").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-instanovo_folder", new File(createFolder("missing-parent"), "missing").getAbsolutePath() + ); + Assert.assertFalse(SearchCLIInputBean.isValidStartup(missingFolder)); + + String help = SearchCLIParams.getOptionsAsString(); + Assert.assertTrue(help.contains("-instanovo")); + Assert.assertTrue(help.contains("-instanovo_plus")); + Assert.assertTrue(help.contains("-instanovo_refine")); + Assert.assertTrue(help.contains("-instanovo_folder")); + } + + /** + * Parses command line arguments. + * + * @param args the command line arguments + * + * @return the parsed command line + * + * @throws Exception if an exception occurs + */ + private CommandLine parse(String... args) throws Exception { + + Options options = new Options(); + SearchCLIParams.createOptionsCLI(options); + + return new DefaultParser().parse(options, args); + } + + /** + * Creates a temporary file. + * + * @param prefix the prefix + * @param suffix the suffix + * + * @return the file + * + * @throws Exception if an exception occurs + */ + private File createFile(String prefix, String suffix) throws Exception { + + File file = File.createTempFile(prefix, suffix); + file.deleteOnExit(); + + return file; + } + + /** + * Creates a temporary folder. + * + * @param prefix the prefix + * + * @return the folder + * + * @throws Exception if an exception occurs + */ + private File createFolder(String prefix) throws Exception { + + File folder = File.createTempFile(prefix, ""); + folder.delete(); + folder.mkdirs(); + folder.deleteOnExit(); + + return folder; + } +} diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java new file mode 100644 index 00000000..0b771a05 --- /dev/null +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -0,0 +1,189 @@ +package eu.isas.searchgui.processbuilders; + +import com.compomics.util.parameters.identification.tool_specific.InstaNovoParameters; +import java.io.File; +import java.util.List; +import junit.framework.TestCase; +import org.junit.Assert; + +/** + * Tests the InstaNovo process builder command lines. + * + * @author CompOmics + */ +public class InstaNovoProcessBuilderTest extends TestCase { + + /** + * Tests the three supported InstaNovo execution modes. + * + * @throws Exception if an exception occurs + */ + public void testInstaNovoCommandLines() throws Exception { + + File instaNovoFolder = createInstaNovoFolder(); + File spectrumFile = createFile("input", ".mgf"); + File outputFile = createFile("output", ".csv"); + + assertCommand( + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.transformer, null, null).getCommand(), + instaNovoFolder, + spectrumFile, + outputFile, + "InstaNovo", + "transformer", + "predict", + "--instanovo-model", + InstaNovoParameters.DEFAULT_INSTANOVO_MODEL, + null, + null + ); + + assertCommand( + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.diffusion, null, null).getCommand(), + instaNovoFolder, + spectrumFile, + outputFile, + "InstaNovo+", + "diffusion", + "predict", + "--instanovo-plus-model", + InstaNovoParameters.DEFAULT_INSTANOVO_PLUS_MODEL, + "--no-refinement", + null + ); + + assertCommand( + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.refined, null, null).getCommand(), + instaNovoFolder, + spectrumFile, + outputFile, + "InstaNovo with InstaNovo+ refinement", + "predict", + null, + "--instanovo-model", + InstaNovoParameters.DEFAULT_INSTANOVO_MODEL, + "--with-refinement", + "--instanovo-plus-model" + ); + } + + /** + * Asserts a command line. + * + * @param command the command + * @param instaNovoFolder the InstaNovo folder + * @param spectrumFile the spectrum file + * @param outputFile the output file + * @param type the expected type + * @param firstCommand the first command + * @param secondCommand the second command + * @param modelFlag the model flag + * @param modelName the model name + * @param modeFlag the mode flag + * @param extraFlag the extra flag + */ + private void assertCommand( + List command, + File instaNovoFolder, + File spectrumFile, + File outputFile, + String type, + String firstCommand, + String secondCommand, + String modelFlag, + String modelName, + String modeFlag, + String extraFlag + ) { + + Assert.assertEquals(new File(instaNovoFolder, ".venv/bin/instanovo").getAbsolutePath(), command.get(0)); + Assert.assertTrue(command.contains(firstCommand)); + + if (secondCommand != null) { + Assert.assertTrue(command.contains(secondCommand)); + } + + Assert.assertTrue(command.contains("--data-path")); + Assert.assertTrue(command.contains(spectrumFile.getAbsolutePath())); + Assert.assertTrue(command.contains("--output-path")); + Assert.assertTrue(command.contains(outputFile.getAbsolutePath())); + Assert.assertTrue(command.contains("--denovo")); + Assert.assertTrue(command.contains(modelFlag)); + Assert.assertTrue(command.contains(modelName)); + + if (modeFlag != null) { + Assert.assertTrue(command.contains(modeFlag)); + } + + if (extraFlag != null) { + Assert.assertTrue(command.contains(extraFlag)); + } + + InstaNovoProcessBuilder.Mode mode; + if (type.equals("InstaNovo+")) { + mode = InstaNovoProcessBuilder.Mode.diffusion; + } else if (type.equals("InstaNovo with InstaNovo+ refinement")) { + mode = InstaNovoProcessBuilder.Mode.refined; + } else { + mode = InstaNovoProcessBuilder.Mode.transformer; + } + + Assert.assertEquals(type, new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, mode, null, null).getType()); + } + + /** + * Creates a temporary InstaNovo folder with a virtual environment + * executable. + * + * @return the InstaNovo folder + * + * @throws Exception if an exception occurs + */ + private File createInstaNovoFolder() throws Exception { + + File folder = createFolder("instanovo-process"); + File executable = new File(folder, ".venv/bin/instanovo"); + executable.getParentFile().mkdirs(); + executable.createNewFile(); + executable.deleteOnExit(); + + return folder; + } + + /** + * Creates a temporary file. + * + * @param prefix the prefix + * @param suffix the suffix + * + * @return the file + * + * @throws Exception if an exception occurs + */ + private File createFile(String prefix, String suffix) throws Exception { + + File file = File.createTempFile(prefix, suffix); + file.deleteOnExit(); + + return file; + } + + /** + * Creates a temporary folder. + * + * @param prefix the prefix + * + * @return the folder + * + * @throws Exception if an exception occurs + */ + private File createFolder(String prefix) throws Exception { + + File folder = File.createTempFile(prefix, ""); + folder.delete(); + folder.mkdirs(); + folder.deleteOnExit(); + + return folder; + } +} diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index 2f868696..0ed7574f 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -11,7 +11,8 @@

SearchGUI



SearchGUI is a user-friendly interface for configuring and running proteomics - identification search engines and de novo sequencing algorithms. + identification search engines and de novo sequencing algorithms, including + InstaNovo and InstaNovo+ from the command line.

For more details and help see the SearchGUI home page: http://compomics.github.io/projects/searchgui.html. From 0566cbc793e8b41e124ec7ee6c3ce6b59a1bcd0c Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 13:13:38 +0200 Subject: [PATCH 02/18] Add InstaNovo GUI controls --- resources/conf/searchGUI_configuration.txt | 4 + .../java/eu/isas/searchgui/SearchHandler.java | 41 ++ .../java/eu/isas/searchgui/gui/SearchGUI.java | 439 +++++++++++++++++- 3 files changed, 472 insertions(+), 12 deletions(-) diff --git a/resources/conf/searchGUI_configuration.txt b/resources/conf/searchGUI_configuration.txt index ccf527a2..01796ddf 100644 --- a/resources/conf/searchGUI_configuration.txt +++ b/resources/conf/searchGUI_configuration.txt @@ -34,6 +34,10 @@ false InstaNovo Location: Not Selected false +InstaNovo+ Enabled: +false +InstaNovo Refinement Enabled: +false DirecTag Location: Not Selected false diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index 32d8a443..b9abff27 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -587,6 +587,8 @@ public SearchHandler( false, false ); + enableInstaNovoPlus = loadBooleanConfigurationValue("InstaNovo+ Enabled:", false); + enableInstaNovoRefine = loadBooleanConfigurationValue("InstaNovo Refinement Enabled:", false); enableDirecTag = loadSearchEngineLocation( Advocate.direcTag, @@ -1450,6 +1452,45 @@ private boolean loadSearchEngineLocation( } + /** + * Loads an optional boolean value from the SearchGUI configuration file. + * + * @param key the configuration key + * @param defaultValue the default value + * + * @return the configured boolean value + */ + private boolean loadBooleanConfigurationValue(String key, boolean defaultValue) { + + File folder = new File(getJarFilePath() + File.separator + "resources" + File.separator + "conf" + File.separator); + File input = new File(folder, SEARCHGUI_CONFIGURATION_FILE); + + if (!input.exists()) { + return defaultValue; + } + + try (BufferedReader br = new BufferedReader(new FileReader(input))) { + + String line; + + while ((line = br.readLine()) != null) { + + if (line.trim().equals(key)) { + + String value = br.readLine(); + return value == null ? defaultValue : Boolean.parseBoolean(value.trim()); + + } + } + + } catch (IOException e) { + return defaultValue; + } + + return defaultValue; + + } + /** * Returns the name of the X!Tandem result file if renamed. * diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 3ae6905f..b9d85708 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -394,6 +394,9 @@ public SearchGUI( enableMetaMorpheusJCheckBox.setSelected(searchHandler.isMetaMorpheusEnabled()); enableSageJCheckBox.setSelected(searchHandler.isSageEnabled()); enableNovorJCheckBox.setSelected(searchHandler.isNovorEnabled()); + enableInstaNovoJCheckBox.setSelected(searchHandler.isInstaNovoEnabled()); + enableInstaNovoPlusJCheckBox.setSelected(searchHandler.isInstaNovoPlusEnabled()); + enableInstaNovoRefineJCheckBox.setSelected(searchHandler.isInstaNovoRefineEnabled()); enableDirecTagJCheckBox.setSelected(searchHandler.isDirecTagEnabled()); // add desktop shortcut? @@ -715,6 +718,18 @@ private void initComponents() { direcTagLinkLabel = new javax.swing.JLabel(); novorSettingsButton = new javax.swing.JButton(); direcTagSettingsButton = new javax.swing.JButton(); + enableInstaNovoJCheckBox = new javax.swing.JCheckBox(); + enableInstaNovoPlusJCheckBox = new javax.swing.JCheckBox(); + enableInstaNovoRefineJCheckBox = new javax.swing.JCheckBox(); + instaNovoButton = new javax.swing.JButton(); + instaNovoPlusButton = new javax.swing.JButton(); + instaNovoRefineButton = new javax.swing.JButton(); + instaNovoSupportButton = new javax.swing.JButton(); + instaNovoPlusSupportButton = new javax.swing.JButton(); + instaNovoRefineSupportButton = new javax.swing.JButton(); + instaNovoLinkLabel = new javax.swing.JLabel(); + instaNovoPlusLinkLabel = new javax.swing.JLabel(); + instaNovoRefineLinkLabel = new javax.swing.JLabel(); enableMetaMorpheusJCheckBox = new javax.swing.JCheckBox(); metaMorpheusButton = new javax.swing.JButton(); metaMorpheusSupportButton = new javax.swing.JButton(); @@ -1751,6 +1766,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + initInstaNovoGuiComponents(); + javax.swing.GroupLayout searchEnginesPanelLayout = new javax.swing.GroupLayout(searchEnginesPanel); searchEnginesPanel.setLayout(searchEnginesPanelLayout); searchEnginesPanelLayout.setHorizontalGroup( @@ -1835,23 +1852,35 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, searchEnginesPanelLayout.createSequentialGroup() .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(enableNovorJCheckBox) - .addComponent(enableDirecTagJCheckBox)) + .addComponent(enableDirecTagJCheckBox) + .addComponent(enableInstaNovoJCheckBox) + .addComponent(enableInstaNovoPlusJCheckBox) + .addComponent(enableInstaNovoRefineJCheckBox)) .addGap(61, 61, 61) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 105, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 105, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(novorSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(direcTagSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(34, 34, 34) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(novorLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(direcTagLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) ); - searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaButton, cometButton, direcTagButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, novorButton, omssaButton, tideButton, xtandemButton}); + searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaButton, cometButton, direcTagButton, instaNovoButton, instaNovoPlusButton, instaNovoRefineButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, novorButton, omssaButton, tideButton, xtandemButton}); searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, direcTagSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, novorSettingsButton, omssaSettingsButton, sageSettingsButton, tideSettingsButton, xtandemSettingsButton}); @@ -1939,7 +1968,25 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(direcTagLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(direcTagSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(direcTagSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(enableInstaNovoJCheckBox) + .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(enableInstaNovoPlusJCheckBox) + .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(enableInstaNovoRefineJCheckBox) + .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) ); searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {andromedaButton, cometButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, omssaButton, tideButton, xtandemButton}); @@ -2636,6 +2683,262 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { pack(); }// //GEN-END:initComponents + /** + * Initializes the InstaNovo search engine controls. + */ + private void initInstaNovoGuiComponents() { + + configureInstaNovoCheckBox(enableInstaNovoJCheckBox, "Enable InstaNovo"); + configureInstaNovoCheckBox(enableInstaNovoPlusJCheckBox, "Enable InstaNovo+"); + configureInstaNovoCheckBox(enableInstaNovoRefineJCheckBox, "Enable InstaNovo with InstaNovo+ refinement"); + + enableInstaNovoJCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoJCheckBoxActionPerformed(evt); + } + }); + enableInstaNovoPlusJCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoPlusJCheckBoxActionPerformed(evt); + } + }); + enableInstaNovoRefineJCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoRefineJCheckBoxActionPerformed(evt); + } + }); + + configureInstaNovoButton(instaNovoButton, "InstaNovo", "Enable InstaNovo"); + configureInstaNovoButton(instaNovoPlusButton, "InstaNovo+", "Enable InstaNovo+"); + configureInstaNovoButton(instaNovoRefineButton, "InstaNovo + refine", "Enable InstaNovo with InstaNovo+ refinement"); + + instaNovoButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + instaNovoButtonActionPerformed(evt); + } + }); + instaNovoPlusButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + instaNovoPlusButtonActionPerformed(evt); + } + }); + instaNovoRefineButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + instaNovoRefineButtonActionPerformed(evt); + } + }); + + configureInstaNovoSupportButton(instaNovoSupportButton); + configureInstaNovoSupportButton(instaNovoPlusSupportButton); + configureInstaNovoSupportButton(instaNovoRefineSupportButton); + + configureInstaNovoLinkLabel(instaNovoLinkLabel); + configureInstaNovoLinkLabel(instaNovoPlusLinkLabel); + configureInstaNovoLinkLabel(instaNovoRefineLinkLabel); + + } + + /** + * Configures an InstaNovo checkbox. + * + * @param checkBox the checkbox + * @param toolTip the tooltip + */ + private void configureInstaNovoCheckBox(javax.swing.JCheckBox checkBox, String toolTip) { + checkBox.setToolTipText(toolTip); + checkBox.setEnabled(false); + } + + /** + * Configures an InstaNovo text button. + * + * @param button the button + * @param text the button text + * @param toolTip the tooltip + */ + private void configureInstaNovoButton(javax.swing.JButton button, String text, String toolTip) { + button.setFont(new java.awt.Font("Segoe UI", 1, 15)); // NOI18N + button.setText(text); + button.setToolTipText(toolTip); + button.setBorder(null); + button.setBorderPainted(false); + button.setContentAreaFilled(false); + button.setEnabled(false); + button.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + button.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseEntered(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + }); + } + + /** + * Configures an InstaNovo platform support button. + * + * @param button the button + */ + private void configureInstaNovoSupportButton(javax.swing.JButton button) { + button.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/all_platforms_gray.png"))); // NOI18N + button.setToolTipText("Supported on Windows, macOS and Linux"); + button.setBorder(null); + button.setBorderPainted(false); + button.setContentAreaFilled(false); + button.setEnabled(false); + button.setPreferredSize(new java.awt.Dimension(85, 25)); + } + + /** + * Configures an InstaNovo web link label. + * + * @param label the label + */ + private void configureInstaNovoLinkLabel(javax.swing.JLabel label) { + label.setFont(new java.awt.Font("Segoe UI", 0, 12)); // NOI18N + label.setForeground(new java.awt.Color(0, 0, 255)); + label.setText("Website"); + label.setEnabled(false); + label.setPreferredSize(new java.awt.Dimension(68, 25)); + label.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.WAIT_CURSOR)); + BareBonesBrowserLaunch.openURL("https://github.com/instadeepai/instanovo"); + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + + public void mouseEntered(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + }); + } + + /** + * Enable/disable InstaNovo. + * + * @param evt the action event + */ + private void enableInstaNovoJCheckBoxActionPerformed(java.awt.event.ActionEvent evt) { + + boolean selected = enableInstaNovoJCheckBox.isSelected(); + + if (selected && !ensureInstaNovoLocation(Advocate.instanovo)) { + enableInstaNovoJCheckBox.setSelected(false); + selected = false; + } + + searchHandler.setInstaNovoEnabled(selected); + validateInput(false); + + } + + /** + * Enable/disable InstaNovo+. + * + * @param evt the action event + */ + private void enableInstaNovoPlusJCheckBoxActionPerformed(java.awt.event.ActionEvent evt) { + + boolean selected = enableInstaNovoPlusJCheckBox.isSelected(); + + if (selected && !ensureInstaNovoLocation(Advocate.instanovoPlus)) { + enableInstaNovoPlusJCheckBox.setSelected(false); + selected = false; + } + + searchHandler.setInstaNovoPlusEnabled(selected); + validateInput(false); + + } + + /** + * Enable/disable InstaNovo with InstaNovo+ refinement. + * + * @param evt the action event + */ + private void enableInstaNovoRefineJCheckBoxActionPerformed(java.awt.event.ActionEvent evt) { + + boolean selected = enableInstaNovoRefineJCheckBox.isSelected(); + + if (selected && !ensureInstaNovoLocation(Advocate.instanovoPlus)) { + enableInstaNovoRefineJCheckBox.setSelected(false); + selected = false; + } + + searchHandler.setInstaNovoRefineEnabled(selected); + validateInput(false); + + } + + /** + * Enable/disable InstaNovo via the text button. + * + * @param evt the action event + */ + private void instaNovoButtonActionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoJCheckBox.setSelected(!enableInstaNovoJCheckBox.isSelected()); + enableInstaNovoJCheckBoxActionPerformed(null); + } + + /** + * Enable/disable InstaNovo+ via the text button. + * + * @param evt the action event + */ + private void instaNovoPlusButtonActionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoPlusJCheckBox.setSelected(!enableInstaNovoPlusJCheckBox.isSelected()); + enableInstaNovoPlusJCheckBoxActionPerformed(null); + } + + /** + * Enable/disable InstaNovo refinement via the text button. + * + * @param evt the action event + */ + private void instaNovoRefineButtonActionPerformed(java.awt.event.ActionEvent evt) { + enableInstaNovoRefineJCheckBox.setSelected(!enableInstaNovoRefineJCheckBox.isSelected()); + enableInstaNovoRefineJCheckBoxActionPerformed(null); + } + + /** + * Makes sure SearchGUI has a valid InstaNovo installation folder. + * + * @param advocate the InstaNovo advocate variant + * + * @return true if the installation folder is valid + */ + private boolean ensureInstaNovoLocation(Advocate advocate) { + + File instaNovoLocation = searchHandler.getInstaNovoLocation(); + + if (validateSearchEngineInstallation(advocate, instaNovoLocation, false)) { + return true; + } + + JFileChooser folderChooser = new JFileChooser(instaNovoLocation); + folderChooser.setDialogTitle("Select the InstaNovo Installation Folder"); + folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + folderChooser.setMultiSelectionEnabled(false); + + int result = folderChooser.showOpenDialog(this); + + if (result == JFileChooser.APPROVE_OPTION) { + + searchHandler.setInstaNovoLocation(folderChooser.getSelectedFile()); + return validateSearchEngineInstallation(advocate, searchHandler.getInstaNovoLocation(), true); + + } + + return false; + + } + /** * Clear the list of spectra. * @@ -6754,6 +7057,9 @@ private void sageButtonMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST: private javax.swing.JCheckBox enableAndromedaJCheckBox; private javax.swing.JCheckBox enableCometJCheckBox; private javax.swing.JCheckBox enableDirecTagJCheckBox; + private javax.swing.JCheckBox enableInstaNovoJCheckBox; + private javax.swing.JCheckBox enableInstaNovoPlusJCheckBox; + private javax.swing.JCheckBox enableInstaNovoRefineJCheckBox; private javax.swing.JCheckBox enableMetaMorpheusJCheckBox; private javax.swing.JCheckBox enableMsAmandaJCheckBox; private javax.swing.JCheckBox enableMsgfJCheckBox; @@ -6768,6 +7074,15 @@ private void sageButtonMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST: private javax.swing.JMenu helpMenu; private javax.swing.JMenuItem helpMenuItem; private javax.swing.JPanel inputFilesPanel; + private javax.swing.JButton instaNovoButton; + private javax.swing.JLabel instaNovoLinkLabel; + private javax.swing.JButton instaNovoPlusButton; + private javax.swing.JLabel instaNovoPlusLinkLabel; + private javax.swing.JButton instaNovoPlusSupportButton; + private javax.swing.JButton instaNovoRefineButton; + private javax.swing.JLabel instaNovoRefineLinkLabel; + private javax.swing.JButton instaNovoRefineSupportButton; + private javax.swing.JButton instaNovoSupportButton; private javax.swing.JMenuItem jMenuItem1; private javax.swing.JPopupMenu.Separator jSeparator1; private javax.swing.JPopupMenu.Separator jSeparator16; @@ -7066,6 +7381,7 @@ public boolean validateSearchEngines(boolean showMessage) { boolean metaMorpheusValid = true; boolean sageValid = true; boolean novorValid = true; + boolean instaNovoValid = true; boolean direcTagValid = true; if (enableOmssaJCheckBox.isSelected()) { @@ -7166,6 +7482,17 @@ public boolean validateSearchEngines(boolean showMessage) { showMessage ); + } + if (enableInstaNovoJCheckBox.isSelected() + || enableInstaNovoPlusJCheckBox.isSelected() + || enableInstaNovoRefineJCheckBox.isSelected()) { + + instaNovoValid = validateSearchEngineInstallation( + Advocate.instanovo, + searchHandler.getInstaNovoLocation(), + showMessage + ); + } if (enableDirecTagJCheckBox.isSelected()) { @@ -7177,17 +7504,23 @@ public boolean validateSearchEngines(boolean showMessage) { } - if (!omssaValid || !xtandemValid || !msgfValid || !msAmandaValid || !myriMatchValid - || !cometValid || !tideValid || !andromedaValid || !metaMorpheusValid - || !sageValid || !novorValid || !direcTagValid) { + if (!instaNovoValid) { - new SoftwareLocationDialog(this, true); + instaNovoValid = ensureInstaNovoLocation(Advocate.instanovo); } - return omssaValid && xtandemValid && msgfValid && msAmandaValid && myriMatchValid + boolean otherSearchEnginesValid = omssaValid && xtandemValid && msgfValid && msAmandaValid && myriMatchValid && cometValid && tideValid && andromedaValid && metaMorpheusValid && sageValid && novorValid && direcTagValid; + + if (!otherSearchEnginesValid) { + + new SoftwareLocationDialog(this, true); + + } + + return otherSearchEnginesValid && instaNovoValid; } /** @@ -7210,6 +7543,9 @@ private boolean validateInput(boolean showMessage) { && !enableMetaMorpheusJCheckBox.isSelected() && !enableSageJCheckBox.isSelected() && !enableNovorJCheckBox.isSelected() + && !enableInstaNovoJCheckBox.isSelected() + && !enableInstaNovoPlusJCheckBox.isSelected() + && !enableInstaNovoRefineJCheckBox.isSelected() && !enableDirecTagJCheckBox.isSelected()) { if (showMessage && valid) { @@ -7911,6 +8247,10 @@ private void saveConfigurationFile() { bw.write(searchHandler.getNovorLocation() + System.getProperty("line.separator") + searchHandler.isNovorEnabled() + System.getProperty("line.separator")); bw.write("InstaNovo Location:" + System.getProperty("line.separator")); bw.write(searchHandler.getInstaNovoLocation() + System.getProperty("line.separator") + searchHandler.isInstaNovoEnabled() + System.getProperty("line.separator")); + bw.write("InstaNovo+ Enabled:" + System.getProperty("line.separator")); + bw.write(searchHandler.isInstaNovoPlusEnabled() + System.getProperty("line.separator")); + bw.write("InstaNovo Refinement Enabled:" + System.getProperty("line.separator")); + bw.write(searchHandler.isInstaNovoRefineEnabled() + System.getProperty("line.separator")); bw.write("DirecTag Location:" + System.getProperty("line.separator")); bw.write(searchHandler.getDirecTagLocation() + System.getProperty("line.separator") + searchHandler.isDirecTagEnabled() + System.getProperty("line.separator")); bw.write("makeblastdb Location:" + System.getProperty("line.separator")); @@ -7972,6 +8312,63 @@ public void enableSearchEngines( boolean enableDirecTag ) { + enableSearchEngines( + enableOmssa, + enbleXTandem, + enableMsgf, + enableMsAmanda, + enableMyriMatch, + enableComet, + enableTide, + enableAndromeda, + enableMetaMorpheus, + enableSage, + enableNovor, + searchHandler != null && searchHandler.isInstaNovoEnabled(), + searchHandler != null && searchHandler.isInstaNovoPlusEnabled(), + searchHandler != null && searchHandler.isInstaNovoRefineEnabled(), + enableDirecTag + ); + + } + + /** + * Enables/disables the search engines. + * + * @param enableOmssa enable OMSSA + * @param enbleXTandem enable X! Tandem + * @param enableMsgf enable MS-GF+ + * @param enableMsAmanda enable MS Amanda + * @param enableMyriMatch enable MyriMatch + * @param enableComet enable Comet + * @param enableTide enable Tide + * @param enableAndromeda enable Andromeda + * @param enableMetaMorpheus enable MetaMorpheus + * @param enableSage enable Sage + * @param enableNovor enable Novor + * @param enableInstaNovo enable InstaNovo + * @param enableInstaNovoPlus enable InstaNovo+ + * @param enableInstaNovoRefine enable InstaNovo with InstaNovo+ refinement + * @param enableDirecTag enable DirecTag + */ + public void enableSearchEngines( + boolean enableOmssa, + boolean enbleXTandem, + boolean enableMsgf, + boolean enableMsAmanda, + boolean enableMyriMatch, + boolean enableComet, + boolean enableTide, + boolean enableAndromeda, + boolean enableMetaMorpheus, + boolean enableSage, + boolean enableNovor, + boolean enableInstaNovo, + boolean enableInstaNovoPlus, + boolean enableInstaNovoRefine, + boolean enableDirecTag + ) { + enableOmssaJCheckBox.setSelected(enableOmssa); enableXTandemJCheckBox.setSelected(enbleXTandem); enableMsgfJCheckBox.setSelected(enableMsgf); @@ -7983,6 +8380,9 @@ public void enableSearchEngines( enableMetaMorpheusJCheckBox.setSelected(enableMetaMorpheus); enableSageJCheckBox.setSelected(enableSage); enableNovorJCheckBox.setSelected(enableNovor); + enableInstaNovoJCheckBox.setSelected(enableInstaNovo); + enableInstaNovoPlusJCheckBox.setSelected(enableInstaNovoPlus); + enableInstaNovoRefineJCheckBox.setSelected(enableInstaNovoRefine); enableDirecTagJCheckBox.setSelected(enableDirecTag); searchHandler.setOmssaEnabled(enableOmssa); @@ -7996,6 +8396,9 @@ public void enableSearchEngines( searchHandler.setMetaMorpheusEnabled(enableMetaMorpheus); searchHandler.setSageEnabled(enableSage); searchHandler.setNovorEnabled(enableNovor); + searchHandler.setInstaNovoEnabled(enableInstaNovo); + searchHandler.setInstaNovoPlusEnabled(enableInstaNovoPlus); + searchHandler.setInstaNovoRefineEnabled(enableInstaNovoRefine); searchHandler.setDirecTagEnabled(enableDirecTag); validateInput(false); @@ -8898,6 +9301,18 @@ private void enableSearchSettingsDependentFeatures(boolean enable) { enableNovorJCheckBox.setEnabled(enable); novorButton.setEnabled(enable); novorLinkLabel.setEnabled(enable); + enableInstaNovoJCheckBox.setEnabled(enable); + enableInstaNovoPlusJCheckBox.setEnabled(enable); + enableInstaNovoRefineJCheckBox.setEnabled(enable); + instaNovoButton.setEnabled(enable); + instaNovoPlusButton.setEnabled(enable); + instaNovoRefineButton.setEnabled(enable); + instaNovoSupportButton.setEnabled(enable); + instaNovoPlusSupportButton.setEnabled(enable); + instaNovoRefineSupportButton.setEnabled(enable); + instaNovoLinkLabel.setEnabled(enable); + instaNovoPlusLinkLabel.setEnabled(enable); + instaNovoRefineLinkLabel.setEnabled(enable); // peptideshaker peptideShakerSettingsButton.setEnabled(enable); From 4b406504f7e3393507486033855fd917e0234d47 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 13:56:34 +0200 Subject: [PATCH 03/18] Add InstaNovo advanced settings controls --- .../java/eu/isas/searchgui/SearchHandler.java | 29 +++ .../java/eu/isas/searchgui/gui/SearchGUI.java | 215 ++++++++++++++++-- .../InstaNovoProcessBuilder.java | 25 +- .../InstaNovoProcessBuilderTest.java | 46 +++- 4 files changed, 284 insertions(+), 31 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index b9abff27..f9ddb49e 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -23,6 +23,8 @@ import com.compomics.util.parameters.identification.IdentificationParameters; import com.compomics.util.parameters.identification.search.SearchParameters; import com.compomics.util.parameters.identification.tool_specific.CometParameters; +import com.compomics.util.parameters.identification.tool_specific.InstaNovoParameters; +import com.compomics.util.parameters.identification.tool_specific.InstaNovoPlusParameters; import com.compomics.util.parameters.identification.tool_specific.MsAmandaParameters; import com.compomics.util.parameters.identification.tool_specific.MyriMatchParameters; import com.compomics.util.parameters.identification.tool_specific.OmssaParameters; @@ -1652,6 +1654,30 @@ public static String getInstaNovoRefinedFileName(String spectrumFileName) { return IoUtil.removeExtension(spectrumFileName) + ".instanovo.refined.csv"; } + /** + * Returns the InstaNovo parameters. + * + * @return the InstaNovo parameters + */ + private InstaNovoParameters getInstaNovoParameters() { + + Object parameters = identificationParameters.getSearchParameters().getIdentificationAlgorithmParameter(Advocate.instanovo.getIndex()); + return parameters instanceof InstaNovoParameters ? (InstaNovoParameters) parameters : new InstaNovoParameters(); + + } + + /** + * Returns the InstaNovo+ parameters. + * + * @return the InstaNovo+ parameters + */ + private InstaNovoPlusParameters getInstaNovoPlusParameters() { + + Object parameters = identificationParameters.getSearchParameters().getIdentificationAlgorithmParameter(Advocate.instanovoPlus.getIndex()); + return parameters instanceof InstaNovoPlusParameters ? (InstaNovoPlusParameters) parameters : new InstaNovoPlusParameters(); + + } + /** * Returns the name of the DirecTag result file. * @@ -4014,6 +4040,7 @@ protected Object doInBackground() { spectrumFile, instaNovoOutputFile, InstaNovoProcessBuilder.Mode.transformer, + getInstaNovoParameters(), waitingHandler, exceptionHandler ); @@ -4049,6 +4076,7 @@ protected Object doInBackground() { spectrumFile, instaNovoPlusOutputFile, InstaNovoProcessBuilder.Mode.diffusion, + getInstaNovoPlusParameters(), waitingHandler, exceptionHandler ); @@ -4084,6 +4112,7 @@ protected Object doInBackground() { spectrumFile, instaNovoRefinedOutputFile, InstaNovoProcessBuilder.Mode.refined, + getInstaNovoParameters(), waitingHandler, exceptionHandler ); diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index b9d85708..b0f64526 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -76,6 +76,8 @@ import com.compomics.util.parameters.identification.tool_specific.AndromedaParameters; import com.compomics.util.parameters.identification.tool_specific.CometParameters; import com.compomics.util.parameters.identification.tool_specific.DirecTagParameters; +import com.compomics.util.parameters.identification.tool_specific.InstaNovoParameters; +import com.compomics.util.parameters.identification.tool_specific.InstaNovoPlusParameters; import com.compomics.util.parameters.identification.tool_specific.MsAmandaParameters; import com.compomics.util.parameters.identification.tool_specific.MsgfParameters; import com.compomics.util.parameters.identification.tool_specific.MyriMatchParameters; @@ -730,6 +732,9 @@ private void initComponents() { instaNovoLinkLabel = new javax.swing.JLabel(); instaNovoPlusLinkLabel = new javax.swing.JLabel(); instaNovoRefineLinkLabel = new javax.swing.JLabel(); + instaNovoSettingsButton = new javax.swing.JButton(); + instaNovoPlusSettingsButton = new javax.swing.JButton(); + instaNovoRefineSettingsButton = new javax.swing.JButton(); enableMetaMorpheusJCheckBox = new javax.swing.JCheckBox(); metaMorpheusButton = new javax.swing.JButton(); metaMorpheusSupportButton = new javax.swing.JButton(); @@ -1846,6 +1851,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(andromedaSettingsButton) .addComponent(novorSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(direcTagSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(metaMorpheusSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(sageSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(10, 10, 10)) @@ -1858,11 +1866,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(enableInstaNovoRefineJCheckBox)) .addGap(61, 61, 61) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(novorSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1882,7 +1890,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaButton, cometButton, direcTagButton, instaNovoButton, instaNovoPlusButton, instaNovoRefineButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, novorButton, omssaButton, tideButton, xtandemButton}); - searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, direcTagSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, novorSettingsButton, omssaSettingsButton, sageSettingsButton, tideSettingsButton, xtandemSettingsButton}); + searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, direcTagSettingsButton, instaNovoSettingsButton, instaNovoPlusSettingsButton, instaNovoRefineSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, novorSettingsButton, omssaSettingsButton, sageSettingsButton, tideSettingsButton, xtandemSettingsButton}); searchEnginesPanelLayout.setVerticalGroup( searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1974,24 +1982,27 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(enableInstaNovoJCheckBox) .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(instaNovoLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(instaNovoSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(enableInstaNovoPlusJCheckBox) .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(instaNovoPlusLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoPlusSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(instaNovoPlusSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) .addComponent(enableInstaNovoRefineJCheckBox) .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(instaNovoRefineLinkLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoRefineSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(instaNovoRefineSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))) ); searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {andromedaButton, cometButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, omssaButton, tideButton, xtandemButton}); - searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, omssaSettingsButton, tideSettingsButton, xtandemSettingsButton}); + searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, instaNovoSettingsButton, instaNovoPlusSettingsButton, instaNovoRefineSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, omssaSettingsButton, tideSettingsButton, xtandemSettingsButton}); searchEnginesScrollPane.setViewportView(searchEnginesPanel); @@ -2710,7 +2721,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { configureInstaNovoButton(instaNovoButton, "InstaNovo", "Enable InstaNovo"); configureInstaNovoButton(instaNovoPlusButton, "InstaNovo+", "Enable InstaNovo+"); - configureInstaNovoButton(instaNovoRefineButton, "InstaNovo + refine", "Enable InstaNovo with InstaNovo+ refinement"); + configureInstaNovoButton(instaNovoRefineButton, "InstaNovo with refinement", "Enable InstaNovo with InstaNovo+ refinement"); instaNovoButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -2732,9 +2743,29 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { configureInstaNovoSupportButton(instaNovoPlusSupportButton); configureInstaNovoSupportButton(instaNovoRefineSupportButton); - configureInstaNovoLinkLabel(instaNovoLinkLabel); - configureInstaNovoLinkLabel(instaNovoPlusLinkLabel); - configureInstaNovoLinkLabel(instaNovoRefineLinkLabel); + configureInstaNovoDescriptionLabel(instaNovoLinkLabel, "De novo peptide sequencing with transformer based InstaNovo model"); + configureInstaNovoDescriptionLabel(instaNovoPlusLinkLabel, "De novo peptide sequencing with diffusion based InstaNovo+ model"); + configureInstaNovoDescriptionLabel(instaNovoRefineLinkLabel, "Refine InstaNovo predictions with InstaNovo+"); + + configureInstaNovoSettingsButton(instaNovoSettingsButton, "Edit InstaNovo Advanced Settings"); + configureInstaNovoSettingsButton(instaNovoPlusSettingsButton, "Edit InstaNovo+ Advanced Settings"); + configureInstaNovoSettingsButton(instaNovoRefineSettingsButton, "Edit InstaNovo Refinement Advanced Settings"); + + instaNovoSettingsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editInstaNovoSettings(false, true, false); + } + }); + instaNovoPlusSettingsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editInstaNovoSettings(true, false, true); + } + }); + instaNovoRefineSettingsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editInstaNovoSettings(false, true, true); + } + }); } @@ -2792,23 +2823,32 @@ private void configureInstaNovoSupportButton(javax.swing.JButton button) { } /** - * Configures an InstaNovo web link label. + * Configures an InstaNovo description label. * * @param label the label + * @param description the description text */ - private void configureInstaNovoLinkLabel(javax.swing.JLabel label) { + private void configureInstaNovoDescriptionLabel(javax.swing.JLabel label, String description) { label.setFont(new java.awt.Font("Segoe UI", 0, 12)); // NOI18N - label.setForeground(new java.awt.Color(0, 0, 255)); - label.setText("Website"); + label.setText(description); label.setEnabled(false); - label.setPreferredSize(new java.awt.Dimension(68, 25)); - label.addMouseListener(new java.awt.event.MouseAdapter() { - public void mouseClicked(java.awt.event.MouseEvent evt) { - setCursor(new java.awt.Cursor(java.awt.Cursor.WAIT_CURSOR)); - BareBonesBrowserLaunch.openURL("https://github.com/instadeepai/instanovo"); - setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - } + } + /** + * Configures an InstaNovo settings button. + * + * @param button the button + * @param toolTip the tooltip + */ + private void configureInstaNovoSettingsButton(javax.swing.JButton button, String toolTip) { + button.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/edit_gray.png"))); // NOI18N + button.setToolTipText(toolTip); + button.setBorder(null); + button.setBorderPainted(false); + button.setContentAreaFilled(false); + button.setEnabled(false); + button.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/edit.png"))); // NOI18N + button.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseEntered(java.awt.event.MouseEvent evt) { setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); } @@ -2819,6 +2859,125 @@ public void mouseExited(java.awt.event.MouseEvent evt) { }); } + /** + * Edit the InstaNovo advanced settings. + * + * @param plusParameters if true, edits the standalone InstaNovo+ parameter object + * @param showInstaNovoModel show the InstaNovo model field + * @param showInstaNovoPlusModel show the InstaNovo+ model field + */ + private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovoModel, boolean showInstaNovoPlusModel) { + + SearchParameters searchParameters = identificationParameters.getSearchParameters(); + int advocateIndex = plusParameters ? Advocate.instanovoPlus.getIndex() : Advocate.instanovo.getIndex(); + Object oldParameters = searchParameters.getIdentificationAlgorithmParameter(advocateIndex); + InstaNovoParameters instaNovoParameters = oldParameters instanceof InstaNovoParameters + ? copyInstaNovoParameters((InstaNovoParameters) oldParameters, plusParameters) + : (plusParameters ? new InstaNovoPlusParameters() : new InstaNovoParameters()); + + JTextField instaNovoModelTxt = new JTextField(instaNovoParameters.getInstaNovoModel()); + JTextField instaNovoPlusModelTxt = new JTextField(instaNovoParameters.getInstaNovoPlusModel()); + JTextField configPathTxt = new JTextField(instaNovoParameters.getConfigFile() == null ? "" : instaNovoParameters.getConfigFile()); + JSpinner numberOfBeamsSpinner = new JSpinner(new SpinnerNumberModel(instaNovoParameters.getNumberOfBeams(), 1, 1000, 1)); + JComboBox beamSearchCombo = new JComboBox<>(new String[]{"Standard beam search", "Knapsack beam search"}); + beamSearchCombo.setSelectedIndex(instaNovoParameters.isUseKnapsack() ? 1 : 0); + JCheckBox saveAllPredictionsCheckBox = new JCheckBox("Save all beam predictions", instaNovoParameters.isSaveAllPredictions()); + JSpinner batchSizeSpinner = new JSpinner(new SpinnerNumberModel(instaNovoParameters.getBatchSize(), -1, 100000, 1)); + JCheckBox forceCpuCheckBox = new JCheckBox("Force CPU execution", instaNovoParameters.isForceCpu()); + + JPanel panel = new JPanel(new java.awt.GridLayout(0, 2, 5, 5)); + + if (showInstaNovoModel) { + panel.add(new JLabel("InstaNovo model")); + panel.add(instaNovoModelTxt); + } + + if (showInstaNovoPlusModel) { + panel.add(new JLabel("InstaNovo+ model")); + panel.add(instaNovoPlusModelTxt); + } + + panel.add(new JLabel("Number of beams")); + panel.add(numberOfBeamsSpinner); + panel.add(new JLabel("Beam search")); + panel.add(beamSearchCombo); + panel.add(new JLabel("Predictions")); + panel.add(saveAllPredictionsCheckBox); + panel.add(new JLabel("Batch size (-1 uses default)")); + panel.add(batchSizeSpinner); + panel.add(new JLabel("Config path")); + panel.add(configPathTxt); + panel.add(new JLabel("Device")); + panel.add(forceCpuCheckBox); + + int option = JOptionPane.showConfirmDialog( + this, + panel, + showInstaNovoModel && showInstaNovoPlusModel + ? "InstaNovo Refinement Advanced Settings" + : (plusParameters ? "InstaNovo+ Advanced Settings" : "InstaNovo Advanced Settings"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE + ); + + if (option == JOptionPane.OK_OPTION) { + + if (showInstaNovoModel && instaNovoModelTxt.getText().trim().isEmpty()) { + JOptionPane.showMessageDialog(this, "The InstaNovo model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); + return; + } + + if (showInstaNovoPlusModel && instaNovoPlusModelTxt.getText().trim().isEmpty()) { + JOptionPane.showMessageDialog(this, "The InstaNovo+ model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); + return; + } + + if (showInstaNovoModel) { + instaNovoParameters.setInstaNovoModel(instaNovoModelTxt.getText().trim()); + } + + if (showInstaNovoPlusModel) { + instaNovoParameters.setInstaNovoPlusModel(instaNovoPlusModelTxt.getText().trim()); + } + + String configPath = configPathTxt.getText().trim(); + instaNovoParameters.setConfigFile(configPath.isEmpty() ? null : configPath); + instaNovoParameters.setNumberOfBeams((Integer) numberOfBeamsSpinner.getValue()); + instaNovoParameters.setUseKnapsack(beamSearchCombo.getSelectedIndex() == 1); + instaNovoParameters.setSaveAllPredictions(saveAllPredictionsCheckBox.isSelected()); + instaNovoParameters.setBatchSize((Integer) batchSizeSpinner.getValue()); + instaNovoParameters.setForceCpu(forceCpuCheckBox.isSelected()); + + searchParameters.setIdentificationAlgorithmParameter(advocateIndex, instaNovoParameters); + validateInput(false); + + } + } + + /** + * Copies InstaNovo parameters. + * + * @param oldParameters the old parameters + * @param plusParameters whether to create InstaNovo+ parameters + * + * @return a copy of the parameters + */ + private InstaNovoParameters copyInstaNovoParameters(InstaNovoParameters oldParameters, boolean plusParameters) { + + InstaNovoParameters result = plusParameters ? new InstaNovoPlusParameters() : new InstaNovoParameters(); + result.setInstaNovoModel(oldParameters.getInstaNovoModel()); + result.setInstaNovoPlusModel(oldParameters.getInstaNovoPlusModel()); + result.setConfigFile(oldParameters.getConfigFile()); + result.setNumberOfBeams(oldParameters.getNumberOfBeams()); + result.setUseKnapsack(oldParameters.isUseKnapsack()); + result.setSaveAllPredictions(oldParameters.isSaveAllPredictions()); + result.setBatchSize(oldParameters.getBatchSize()); + result.setForceCpu(oldParameters.isForceCpu()); + + return result; + + } + /** * Enable/disable InstaNovo. * @@ -7078,10 +7237,13 @@ private void sageButtonMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST: private javax.swing.JLabel instaNovoLinkLabel; private javax.swing.JButton instaNovoPlusButton; private javax.swing.JLabel instaNovoPlusLinkLabel; + private javax.swing.JButton instaNovoPlusSettingsButton; private javax.swing.JButton instaNovoPlusSupportButton; private javax.swing.JButton instaNovoRefineButton; private javax.swing.JLabel instaNovoRefineLinkLabel; + private javax.swing.JButton instaNovoRefineSettingsButton; private javax.swing.JButton instaNovoRefineSupportButton; + private javax.swing.JButton instaNovoSettingsButton; private javax.swing.JButton instaNovoSupportButton; private javax.swing.JMenuItem jMenuItem1; private javax.swing.JPopupMenu.Separator jSeparator1; @@ -9310,6 +9472,9 @@ private void enableSearchSettingsDependentFeatures(boolean enable) { instaNovoSupportButton.setEnabled(enable); instaNovoPlusSupportButton.setEnabled(enable); instaNovoRefineSupportButton.setEnabled(enable); + instaNovoSettingsButton.setEnabled(enable); + instaNovoPlusSettingsButton.setEnabled(enable); + instaNovoRefineSettingsButton.setEnabled(enable); instaNovoLinkLabel.setEnabled(enable); instaNovoPlusLinkLabel.setEnabled(enable); instaNovoRefineLinkLabel.setEnabled(enable); diff --git a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java index 138ebebf..ed05d244 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -48,6 +48,7 @@ public enum Mode { * @param spectrumFile the spectrum file * @param outputFile the output file * @param mode the mode + * @param instaNovoParameters the InstaNovo parameters * @param waitingHandler the waiting handler * @param exceptionHandler the exception handler */ @@ -56,6 +57,7 @@ public InstaNovoProcessBuilder( File spectrumFile, File outputFile, Mode mode, + InstaNovoParameters instaNovoParameters, WaitingHandler waitingHandler, ExceptionHandler exceptionHandler ) { @@ -66,6 +68,10 @@ public InstaNovoProcessBuilder( this.waitingHandler = waitingHandler; this.exceptionHandler = exceptionHandler; + if (instaNovoParameters == null) { + instaNovoParameters = new InstaNovoParameters(); + } + File executableFile = getExecutable(instaNovoFolder); process_name_array.add(executableFile.getPath()); @@ -87,12 +93,12 @@ public InstaNovoProcessBuilder( if (mode == Mode.transformer || mode == Mode.refined) { process_name_array.add("--instanovo-model"); - process_name_array.add(InstaNovoParameters.DEFAULT_INSTANOVO_MODEL); + process_name_array.add(instaNovoParameters.getInstaNovoModel()); } if (mode == Mode.diffusion || mode == Mode.refined) { process_name_array.add("--instanovo-plus-model"); - process_name_array.add(InstaNovoParameters.DEFAULT_INSTANOVO_PLUS_MODEL); + process_name_array.add(instaNovoParameters.getInstaNovoPlusModel()); } if (mode == Mode.diffusion) { @@ -101,6 +107,21 @@ public InstaNovoProcessBuilder( process_name_array.add("--with-refinement"); } + if (instaNovoParameters.getConfigFile() != null && !instaNovoParameters.getConfigFile().trim().isEmpty()) { + process_name_array.add("--config-path"); + process_name_array.add(instaNovoParameters.getConfigFile()); + } + + process_name_array.add("num_beams=" + instaNovoParameters.getNumberOfBeams()); + process_name_array.add("use_knapsack=" + Boolean.toString(instaNovoParameters.isUseKnapsack())); + process_name_array.add("save_all_predictions=" + Boolean.toString(instaNovoParameters.isSaveAllPredictions())); + + if (instaNovoParameters.getBatchSize() > 0) { + process_name_array.add("batch_size=" + instaNovoParameters.getBatchSize()); + } + + process_name_array.add("force_cpu=" + Boolean.toString(instaNovoParameters.isForceCpu())); + process_name_array.trimToSize(); System.out.println(System.getProperty("line.separator") + System.getProperty("line.separator") + "instanovo command: "); diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java index 0b771a05..fa4421e1 100644 --- a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -24,8 +24,10 @@ public void testInstaNovoCommandLines() throws Exception { File spectrumFile = createFile("input", ".mgf"); File outputFile = createFile("output", ".csv"); + InstaNovoParameters defaultParameters = new InstaNovoParameters(); + assertCommand( - new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.transformer, null, null).getCommand(), + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.transformer, defaultParameters, null, null).getCommand(), instaNovoFolder, spectrumFile, outputFile, @@ -39,7 +41,7 @@ public void testInstaNovoCommandLines() throws Exception { ); assertCommand( - new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.diffusion, null, null).getCommand(), + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.diffusion, defaultParameters, null, null).getCommand(), instaNovoFolder, spectrumFile, outputFile, @@ -53,7 +55,7 @@ public void testInstaNovoCommandLines() throws Exception { ); assertCommand( - new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.refined, null, null).getCommand(), + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.refined, defaultParameters, null, null).getCommand(), instaNovoFolder, spectrumFile, outputFile, @@ -65,6 +67,36 @@ public void testInstaNovoCommandLines() throws Exception { "--with-refinement", "--instanovo-plus-model" ); + + InstaNovoParameters advancedParameters = new InstaNovoParameters(); + advancedParameters.setInstaNovoModel("instanovo-custom"); + advancedParameters.setInstaNovoPlusModel("instanovoplus-custom"); + advancedParameters.setConfigFile("custom-config"); + advancedParameters.setNumberOfBeams(17); + advancedParameters.setUseKnapsack(true); + advancedParameters.setSaveAllPredictions(false); + advancedParameters.setBatchSize(64); + advancedParameters.setForceCpu(true); + + List advancedCommand = new InstaNovoProcessBuilder( + instaNovoFolder, + spectrumFile, + outputFile, + InstaNovoProcessBuilder.Mode.refined, + advancedParameters, + null, + null + ).getCommand(); + + Assert.assertTrue(advancedCommand.contains("--config-path")); + Assert.assertTrue(advancedCommand.contains("custom-config")); + Assert.assertTrue(advancedCommand.contains("instanovo-custom")); + Assert.assertTrue(advancedCommand.contains("instanovoplus-custom")); + Assert.assertTrue(advancedCommand.contains("num_beams=17")); + Assert.assertTrue(advancedCommand.contains("use_knapsack=true")); + Assert.assertTrue(advancedCommand.contains("save_all_predictions=false")); + Assert.assertTrue(advancedCommand.contains("batch_size=64")); + Assert.assertTrue(advancedCommand.contains("force_cpu=true")); } /** @@ -128,7 +160,13 @@ private void assertCommand( mode = InstaNovoProcessBuilder.Mode.transformer; } - Assert.assertEquals(type, new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, mode, null, null).getType()); + Assert.assertTrue(command.contains("num_beams=5")); + Assert.assertTrue(command.contains("use_knapsack=false")); + Assert.assertTrue(command.contains("save_all_predictions=true")); + Assert.assertTrue(command.contains("force_cpu=false")); + Assert.assertFalse(command.contains("batch_size=-1")); + + Assert.assertEquals(type, new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, mode, new InstaNovoParameters(), null, null).getType()); } /** From 6c7327902891d99b8f1e1fe305e31b1c37571296 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:08:50 +0200 Subject: [PATCH 04/18] Fix InstaNovo engine list layout --- src/main/java/eu/isas/searchgui/gui/SearchGUI.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index b0f64526..16887ae2 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -1866,11 +1866,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(enableInstaNovoRefineJCheckBox)) .addGap(61, 61, 61) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(novorButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(direcTagButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoPlusButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(instaNovoRefineButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchEnginesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(novorSupportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1888,7 +1888,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) ); - searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaButton, cometButton, direcTagButton, instaNovoButton, instaNovoPlusButton, instaNovoRefineButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, novorButton, omssaButton, tideButton, xtandemButton}); + searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaButton, cometButton, direcTagButton, instaNovoButton, instaNovoPlusButton, instaNovoRefineButton, metaMorpheusButton, msAmandaButton, msgfButton, myriMatchButton, novorButton, omssaButton, sageButton, tideButton, xtandemButton}); searchEnginesPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {andromedaSettingsButton, cometSettingsButton, direcTagSettingsButton, instaNovoSettingsButton, instaNovoPlusSettingsButton, instaNovoRefineSettingsButton, metaMorpheusSettingsButton, msAmandaSettingsButton, msgfSettingsButton, myriMatchSettingsButton, novorSettingsButton, omssaSettingsButton, sageSettingsButton, tideSettingsButton, xtandemSettingsButton}); @@ -2722,6 +2722,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { configureInstaNovoButton(instaNovoButton, "InstaNovo", "Enable InstaNovo"); configureInstaNovoButton(instaNovoPlusButton, "InstaNovo+", "Enable InstaNovo+"); configureInstaNovoButton(instaNovoRefineButton, "InstaNovo with refinement", "Enable InstaNovo with InstaNovo+ refinement"); + instaNovoRefineButton.setFont(new java.awt.Font("Segoe UI", 1, 13)); // NOI18N instaNovoButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { From c8b12c2c22a81df5f0b036515f5ecbd849131d87 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:14:48 +0200 Subject: [PATCH 05/18] Link InstaNovo descriptions to documentation --- .../java/eu/isas/searchgui/gui/SearchGUI.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 16887ae2..cfa163b8 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -217,6 +217,10 @@ public class SearchGUI extends javax.swing.JFrame implements JavaHomeOrMemoryDia * Reference for the separation of modification and its frequency. */ public static final String MODIFICATION_USE_SEPARATOR = "_"; + /** + * The InstaNovo documentation URL. + */ + private static final String INSTANOVO_DOCUMENTATION_URL = "https://instadeepai.github.io/InstaNovo/"; /** * If true, then one of the currently processed spectra has duplicate * titles. @@ -2831,8 +2835,24 @@ private void configureInstaNovoSupportButton(javax.swing.JButton button) { */ private void configureInstaNovoDescriptionLabel(javax.swing.JLabel label, String description) { label.setFont(new java.awt.Font("Segoe UI", 0, 12)); // NOI18N - label.setText(description); + label.setText("" + description + " "); + label.setToolTipText("Open the InstaNovo documentation"); label.setEnabled(false); + label.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.WAIT_CURSOR)); + BareBonesBrowserLaunch.openURL(INSTANOVO_DOCUMENTATION_URL); + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + + public void mouseEntered(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + }); } /** From 998e4209c1b9309355e1a2398e30614e966aee28 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:30:53 +0200 Subject: [PATCH 06/18] Load InstaNovo model choices from models json --- README.md | 2 +- .../java/eu/isas/searchgui/gui/SearchGUI.java | 107 ++++++++++++++++-- src/main/resources/helpFiles/SearchGUI.html | 10 ++ .../gui/SearchGUIInstaNovoModelsTest.java | 72 ++++++++++++ src/test/resources/helpFiles/SearchGUI.html | 10 ++ 5 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 src/test/java/eu/isas/searchgui/gui/SearchGUIInstaNovoModelsTest.java diff --git a/README.md b/README.md index 7b1b4371..d1af78bc 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The main purpose of SearchGUI is to make it simpler to use multiple search engin For details about the command line see: [SearchCLI](https://github.com/compomics/searchgui/wiki/SearchCLI). -InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. +InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. To use InstaNovo from SearchGUI or SearchCLI, clone the InstaNovo repository locally, install `uv`, and run `uv sync` in the InstaNovo checkout so that `/.venv/bin/instanovo` is created. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; `` is the InstaNovo checkout root, not the `.venv` folder. SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. The advanced settings model selectors are populated from `/instanovo/models.json`. [Go to top of page](#searchgui) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index cfa163b8..26a7e519 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -91,6 +91,9 @@ import com.compomics.util.parameters.identification.search.DigestionParameters; import com.compomics.util.parameters.identification.tool_specific.MetaMorpheusParameters; import com.compomics.util.parameters.identification.tool_specific.SageParameters; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.common.collect.Sets; import eu.isas.searchgui.SearchGUIWrapper; import eu.isas.searchgui.parameters.SearchGUIPathParameters; @@ -2896,8 +2899,21 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo ? copyInstaNovoParameters((InstaNovoParameters) oldParameters, plusParameters) : (plusParameters ? new InstaNovoPlusParameters() : new InstaNovoParameters()); - JTextField instaNovoModelTxt = new JTextField(instaNovoParameters.getInstaNovoModel()); - JTextField instaNovoPlusModelTxt = new JTextField(instaNovoParameters.getInstaNovoPlusModel()); + JComboBox instaNovoModelCmb = createInstaNovoModelComboBox("transformer", instaNovoParameters.getInstaNovoModel()); + JComboBox instaNovoPlusModelCmb = createInstaNovoModelComboBox("diffusion", instaNovoParameters.getInstaNovoPlusModel()); + + if ((showInstaNovoModel && instaNovoModelCmb.getItemCount() == 0) + || (showInstaNovoPlusModel && instaNovoPlusModelCmb.getItemCount() == 0)) { + + JOptionPane.showMessageDialog( + this, + "Could not read the InstaNovo model list from instanovo/models.json. Select the InstaNovo checkout root as the installation folder before editing advanced settings.", + "InstaNovo Model List", + JOptionPane.WARNING_MESSAGE + ); + return; + } + JTextField configPathTxt = new JTextField(instaNovoParameters.getConfigFile() == null ? "" : instaNovoParameters.getConfigFile()); JSpinner numberOfBeamsSpinner = new JSpinner(new SpinnerNumberModel(instaNovoParameters.getNumberOfBeams(), 1, 1000, 1)); JComboBox beamSearchCombo = new JComboBox<>(new String[]{"Standard beam search", "Knapsack beam search"}); @@ -2910,12 +2926,12 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo if (showInstaNovoModel) { panel.add(new JLabel("InstaNovo model")); - panel.add(instaNovoModelTxt); + panel.add(instaNovoModelCmb); } if (showInstaNovoPlusModel) { panel.add(new JLabel("InstaNovo+ model")); - panel.add(instaNovoPlusModelTxt); + panel.add(instaNovoPlusModelCmb); } panel.add(new JLabel("Number of beams")); @@ -2943,22 +2959,22 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo if (option == JOptionPane.OK_OPTION) { - if (showInstaNovoModel && instaNovoModelTxt.getText().trim().isEmpty()) { + if (showInstaNovoModel && instaNovoModelCmb.getSelectedItem() == null) { JOptionPane.showMessageDialog(this, "The InstaNovo model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); return; } - if (showInstaNovoPlusModel && instaNovoPlusModelTxt.getText().trim().isEmpty()) { + if (showInstaNovoPlusModel && instaNovoPlusModelCmb.getSelectedItem() == null) { JOptionPane.showMessageDialog(this, "The InstaNovo+ model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); return; } if (showInstaNovoModel) { - instaNovoParameters.setInstaNovoModel(instaNovoModelTxt.getText().trim()); + instaNovoParameters.setInstaNovoModel(instaNovoModelCmb.getSelectedItem().toString()); } if (showInstaNovoPlusModel) { - instaNovoParameters.setInstaNovoPlusModel(instaNovoPlusModelTxt.getText().trim()); + instaNovoParameters.setInstaNovoPlusModel(instaNovoPlusModelCmb.getSelectedItem().toString()); } String configPath = configPathTxt.getText().trim(); @@ -2975,6 +2991,81 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo } } + /** + * Creates a model combo box populated from the InstaNovo models file. + * + * @param modelType the model type in models.json + * @param selectedModel the selected model + * + * @return the model combo box + */ + private JComboBox createInstaNovoModelComboBox(String modelType, String selectedModel) { + + ArrayList models = getInstaNovoModels(modelType); + + JComboBox comboBox = new JComboBox<>(models.toArray(new String[0])); + comboBox.setEditable(false); + + if (selectedModel != null && models.contains(selectedModel)) { + comboBox.setSelectedItem(selectedModel); + } + + return comboBox; + } + + /** + * Returns the available InstaNovo models for the given type. + * + * @param modelType the model type in models.json + * + * @return the available model ids + */ + private ArrayList getInstaNovoModels(String modelType) { + + return getInstaNovoModels(searchHandler.getInstaNovoLocation(), modelType); + } + + /** + * Returns the available InstaNovo models for the given type. + * + * @param instaNovoLocation the InstaNovo installation folder + * @param modelType the model type in models.json + * + * @return the available model ids + */ + static ArrayList getInstaNovoModels(File instaNovoLocation, String modelType) { + + ArrayList result = new ArrayList<>(); + + if (instaNovoLocation == null) { + return result; + } + + File modelsFile = new File(instaNovoLocation, "instanovo" + File.separator + "models.json"); + + if (!modelsFile.exists()) { + return result; + } + + try (FileReader reader = new FileReader(modelsFile)) { + + JsonObject modelsJson = JsonParser.parseReader(reader).getAsJsonObject(); + JsonObject modelSection = modelsJson.getAsJsonObject(modelType); + + if (modelSection != null) { + + for (Map.Entry entry : modelSection.entrySet()) { + result.add(entry.getKey()); + } + } + + } catch (Exception e) { + result.clear(); + } + + return result; + } + /** * Copies InstaNovo parameters. * diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index 0ed7574f..1912dc65 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -14,6 +14,16 @@

SearchGUI

identification search engines and de novo sequencing algorithms, including InstaNovo and InstaNovo+ from the command line. +

+ To use InstaNovo, clone the InstaNovo repository locally, install uv, and + run uv sync in the InstaNovo checkout. When SearchGUI asks + for the InstaNovo installation folder, select the InstaNovo checkout root, + not the .venv folder. SearchGUI looks for + <folder>/.venv/bin/instanovo before falling back to + <folder>/instanovo, and the advanced settings model + selectors are populated from <folder>/instanovo/models.json. +

+

For more details and help see the SearchGUI home page: http://compomics.github.io/projects/searchgui.html.

diff --git a/src/test/java/eu/isas/searchgui/gui/SearchGUIInstaNovoModelsTest.java b/src/test/java/eu/isas/searchgui/gui/SearchGUIInstaNovoModelsTest.java new file mode 100644 index 00000000..cc7624f7 --- /dev/null +++ b/src/test/java/eu/isas/searchgui/gui/SearchGUIInstaNovoModelsTest.java @@ -0,0 +1,72 @@ +package eu.isas.searchgui.gui; + +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import junit.framework.TestCase; +import org.junit.Assert; + +/** + * Tests loading InstaNovo model ids. + * + * @author CompOmics + */ +public class SearchGUIInstaNovoModelsTest extends TestCase { + + /** + * Tests loading transformer and diffusion model ids from models.json. + * + * @throws Exception if an exception occurs + */ + public void testGetInstaNovoModels() throws Exception { + + File folder = createFolder("instanovo-models"); + File instaNovoFolder = new File(folder, "instanovo"); + Assert.assertTrue(instaNovoFolder.mkdirs()); + + File modelsFile = new File(instaNovoFolder, "models.json"); + + try (FileWriter writer = new FileWriter(modelsFile)) { + writer.write("{\n" + + " \"transformer\": {\n" + + " \"instanovo-v1.2.0\": {\"remote\": \"transformer-1\"},\n" + + " \"instanovo-v1.1.0\": {\"remote\": \"transformer-2\"}\n" + + " },\n" + + " \"diffusion\": {\n" + + " \"instanovoplus-v1.1.0\": {\"remote\": \"diffusion-1\"}\n" + + " }\n" + + "}\n"); + } + + ArrayList transformerModels = SearchGUI.getInstaNovoModels(folder, "transformer"); + Assert.assertEquals(2, transformerModels.size()); + Assert.assertEquals("instanovo-v1.2.0", transformerModels.get(0)); + Assert.assertEquals("instanovo-v1.1.0", transformerModels.get(1)); + + ArrayList diffusionModels = SearchGUI.getInstaNovoModels(folder, "diffusion"); + Assert.assertEquals(1, diffusionModels.size()); + Assert.assertEquals("instanovoplus-v1.1.0", diffusionModels.get(0)); + + Assert.assertTrue(SearchGUI.getInstaNovoModels(folder, "missing").isEmpty()); + Assert.assertTrue(SearchGUI.getInstaNovoModels(null, "transformer").isEmpty()); + } + + /** + * Creates a temporary folder. + * + * @param prefix the prefix + * + * @return the folder + * + * @throws Exception if an exception occurs + */ + private File createFolder(String prefix) throws Exception { + + File file = File.createTempFile(prefix, ""); + Assert.assertTrue(file.delete()); + Assert.assertTrue(file.mkdirs()); + file.deleteOnExit(); + + return file; + } +} diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index 0ed7574f..1912dc65 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -14,6 +14,16 @@

SearchGUI

identification search engines and de novo sequencing algorithms, including InstaNovo and InstaNovo+ from the command line. +

+ To use InstaNovo, clone the InstaNovo repository locally, install uv, and + run uv sync in the InstaNovo checkout. When SearchGUI asks + for the InstaNovo installation folder, select the InstaNovo checkout root, + not the .venv folder. SearchGUI looks for + <folder>/.venv/bin/instanovo before falling back to + <folder>/instanovo, and the advanced settings model + selectors are populated from <folder>/instanovo/models.json. +

+

For more details and help see the SearchGUI home page: http://compomics.github.io/projects/searchgui.html.

From 6bd9d403a95dc4908fbc748ca130c8e20f8ce920 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:37:12 +0200 Subject: [PATCH 07/18] Allow InstaNovo de novo runs without fasta --- README.md | 2 +- .../java/eu/isas/searchgui/SearchHandler.java | 8 +- .../searchgui/cmd/SearchCLIInputBean.java | 75 ++++++++++++++----- .../isas/searchgui/cmd/SearchCLIParams.java | 18 +++-- .../java/eu/isas/searchgui/gui/SearchGUI.java | 37 ++++++++- src/main/resources/helpFiles/SearchGUI.html | 3 + .../searchgui/cmd/SearchCLIInstaNovoTest.java | 21 ++++++ src/test/resources/helpFiles/SearchGUI.html | 3 + 8 files changed, 135 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d1af78bc..a6a3e20f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The main purpose of SearchGUI is to make it simpler to use multiple search engin For details about the command line see: [SearchCLI](https://github.com/compomics/searchgui/wiki/SearchCLI). -InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. To use InstaNovo from SearchGUI or SearchCLI, clone the InstaNovo repository locally, install `uv`, and run `uv sync` in the InstaNovo checkout so that `/.venv/bin/instanovo` is created. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; `` is the InstaNovo checkout root, not the `.venv` folder. SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. The advanced settings model selectors are populated from `/instanovo/models.json`. +InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. To use InstaNovo from SearchGUI or SearchCLI, clone the InstaNovo repository locally, install `uv`, and run `uv sync` in the InstaNovo checkout so that `/.venv/bin/instanovo` is created. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; `` is the InstaNovo checkout root, not the `.venv` folder. SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. The advanced settings model selectors are populated from `/instanovo/models.json`. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. [Go to top of page](#searchgui) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index f9ddb49e..c08ac585 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -4883,7 +4883,7 @@ public void saveInputFile(File folder) { try ( BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) { // add the fasta file - bw.write(fastaFile.getAbsolutePath() + System.getProperty("line.separator")); + bw.write((fastaFile == null ? "" : fastaFile.getAbsolutePath()) + System.getProperty("line.separator")); // add the ms files for (File spectrumFile : msFiles) { @@ -5409,7 +5409,9 @@ public void organizeOutput( dataFolder.mkdir(); // copy fasta file - IoUtil.copyFile(fastaFile, new File(dataFolder, fastaFile.getName())); + if (fastaFile != null) { + IoUtil.copyFile(fastaFile, new File(dataFolder, fastaFile.getName())); + } // copy the spectrum files for (File spectrumFile : getSpectrumFiles()) { @@ -5757,7 +5759,7 @@ private long getTotalUncompressedSizeOfData() { */ private long getTotalUncompressedSizeOfData(File spectrumFile, File cmsFile) { - long totalUncompressedSize = fastaFile.length(); + long totalUncompressedSize = fastaFile == null ? 0 : fastaFile.length(); if (spectrumFile != null) { totalUncompressedSize += spectrumFile.length(); diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java index 8a3e9264..5fda7b55 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java @@ -242,9 +242,11 @@ public SearchCLIInputBean(CommandLine aLine) throws IOException, ClassNotFoundEx String spectrumFilesTxt = aLine.getOptionValue(SearchCLIParams.SPECTRUM_FILES.id); spectrumFiles = getSpectrumFiles(spectrumFilesTxt); - // get the FASTA file - String arg = aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id); - fastaFile = new File(arg); + // get the FASTA file + String arg = aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id); + if (arg != null && !arg.trim().isEmpty()) { + fastaFile = new File(arg); + } // output folder arg = aLine.getOptionValue(SearchCLIParams.OUTPUT_FOLDER.id); @@ -918,14 +920,18 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { } } - // check the FASTA file - if (!aLine.hasOption(SearchCLIParams.FASTA_FILE.id) || ((String) aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id)).equals("")) { - System.out.println(System.getProperty("line.separator") + "FASTA file not specified." + System.getProperty("line.separator")); - return false; - } else { - File file = new File(((String) aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id))); - if (!file.exists()) { - System.out.println(System.getProperty("line.separator") + "FASTA file \'" + file.getName() + "\' not found." + System.getProperty("line.separator")); + // check the FASTA file + boolean fastaRequired = isDatabaseSearchSelected(aLine); + boolean fastaProvided = aLine.hasOption(SearchCLIParams.FASTA_FILE.id) + && !((String) aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id)).equals(""); + + if (fastaRequired && !fastaProvided) { + System.out.println(System.getProperty("line.separator") + "FASTA file not specified." + System.getProperty("line.separator")); + return false; + } else if (fastaProvided) { + File file = new File(((String) aLine.getOptionValue(SearchCLIParams.FASTA_FILE.id))); + if (!file.exists()) { + System.out.println(System.getProperty("line.separator") + "FASTA file \'" + file.getName() + "\' not found." + System.getProperty("line.separator")); return false; } } @@ -1259,12 +1265,47 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { if (!IdentificationParametersInputBean.isValidStartup(aLine, false)) { return false; } - - return true; - } - - /** - * Returns the path settings provided by the user. + + return true; + } + + /** + * Returns true if a selected command line engine requires a FASTA database. + * + * @param aLine the command line + * + * @return true if a FASTA database is required + */ + private static boolean isDatabaseSearchSelected(CommandLine aLine) { + + return isEnabled(aLine, SearchCLIParams.OMSSA) + || isEnabled(aLine, SearchCLIParams.XTANDEM) + || isEnabled(aLine, SearchCLIParams.MSGF) + || isEnabled(aLine, SearchCLIParams.MS_AMANDA) + || isEnabled(aLine, SearchCLIParams.MYRIMATCH) + || isEnabled(aLine, SearchCLIParams.COMET) + || isEnabled(aLine, SearchCLIParams.TIDE) + || isEnabled(aLine, SearchCLIParams.ANDROMEDA) + || isEnabled(aLine, SearchCLIParams.META_MORPHEUS) + || isEnabled(aLine, SearchCLIParams.SAGE); + } + + /** + * Returns true if the option is enabled on the command line. + * + * @param aLine the command line + * @param option the option + * + * @return true if the option is enabled + */ + private static boolean isEnabled(CommandLine aLine, SearchCLIParams option) { + + return aLine.hasOption(option.id) + && "1".equals(aLine.getOptionValue(option.id).trim()); + } + + /** + * Returns the path settings provided by the user. * * @return the path settings provided by the user */ diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java index 4f663dfa..d7694f09 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLIParams.java @@ -17,7 +17,7 @@ public enum SearchCLIParams { // https://github.com/compomics/searchgui/wiki/SearchCLI. /////////////////////////////////////////////////////////////////////////////////////////////////////////////// SPECTRUM_FILES("spectrum_files", "Spectrum files (mgf or mzml), comma separated list or an entire folder.", true), - FASTA_FILE("fasta_file", "The complete path to the FASTA file.", true), + FASTA_FILE("fasta_file", "The complete path to the FASTA file. Required when running database search engines, optional for de novo-only searches.", false), OUTPUT_FOLDER("output_folder", "The output folder.", true), CONFIG_FOLDER("config_folder", "The config folder.", true), THREADS("threads", "Number of threads to use for the processing, default: the number of cores.", false), @@ -127,13 +127,15 @@ public static String getOptionsAsString() { String output = ""; String formatter = "%-25s"; - output += "Mandatory Parameters:\n\n"; - output += "-" + String.format(formatter, SPECTRUM_FILES.id) + " " + SPECTRUM_FILES.description + "\n"; - output += "-" + String.format(formatter, FASTA_FILE.id) + " " + FASTA_FILE.description + "\n"; - output += "-" + String.format(formatter, OUTPUT_FOLDER.id) + " " + OUTPUT_FOLDER.description + "\n"; - output += "-" + String.format(formatter, IdentificationParametersCLIParams.IDENTIFICATION_PARAMETERS.id) + " " + IdentificationParametersCLIParams.IDENTIFICATION_PARAMETERS.description + "\n"; - - output += "\n\nSearch Engine Selection:\n\n"; + output += "Mandatory Parameters:\n\n"; + output += "-" + String.format(formatter, SPECTRUM_FILES.id) + " " + SPECTRUM_FILES.description + "\n"; + output += "-" + String.format(formatter, OUTPUT_FOLDER.id) + " " + OUTPUT_FOLDER.description + "\n"; + output += "-" + String.format(formatter, IdentificationParametersCLIParams.IDENTIFICATION_PARAMETERS.id) + " " + IdentificationParametersCLIParams.IDENTIFICATION_PARAMETERS.description + "\n"; + + output += "\n\nConditional Parameters:\n\n"; + output += "-" + String.format(formatter, FASTA_FILE.id) + " " + FASTA_FILE.description + "\n"; + + output += "\n\nSearch Engine Selection:\n\n"; output += "-" + String.format(formatter, OMSSA.id) + " " + OMSSA.description + "\n"; output += "-" + String.format(formatter, XTANDEM.id) + " " + XTANDEM.description + "\n"; output += "-" + String.format(formatter, MSGF.id) + " " + MSGF.description + "\n"; diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 26a7e519..ff4a4368 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -7865,13 +7865,21 @@ private boolean validateInput(boolean showMessage) { } - if (databaseFileTxt.getText() == null || databaseFileTxt.getText().trim().equals("")) { + boolean databaseRequired = isDatabaseSearchSelected() + || peptideShakerCheckBox.isSelected(); + + if (databaseRequired + && (databaseFileTxt.getText() == null || databaseFileTxt.getText().trim().equals(""))) { if (showMessage && valid) { + String message = peptideShakerCheckBox.isSelected() && !isDatabaseSearchSelected() + ? "You need to specify a search database when PeptideShaker post-processing is enabled." + : "You need to specify a search database."; + JOptionPane.showMessageDialog( this, - "You need to specify a search database.", + message, "Search Database Not Found", JOptionPane.WARNING_MESSAGE ); @@ -7883,7 +7891,7 @@ private boolean validateInput(boolean showMessage) { databaseFileTxt.setToolTipText(null); valid = false; - } else { + } else if (databaseFileTxt.getText() != null && !databaseFileTxt.getText().trim().equals("")) { File test = new File(databaseFileTxt.getText().trim()); @@ -7905,6 +7913,10 @@ private boolean validateInput(boolean showMessage) { valid = false; } + } else { + + databaseSettingsLbl.setToolTipText("A database file is optional for de novo-only searches"); + databaseFileTxt.setToolTipText(null); } // validate the search parameters @@ -7972,6 +7984,25 @@ private boolean validateInput(boolean showMessage) { } + /** + * Returns true if a selected engine requires a FASTA database. + * + * @return true if a FASTA database is required + */ + private boolean isDatabaseSearchSelected() { + + return enableOmssaJCheckBox.isSelected() + || enableXTandemJCheckBox.isSelected() + || enableMsgfJCheckBox.isSelected() + || enableMsAmandaJCheckBox.isSelected() + || enableMyriMatchJCheckBox.isSelected() + || enableCometJCheckBox.isSelected() + || enableTideJCheckBox.isSelected() + || enableAndromedaJCheckBox.isSelected() + || enableMetaMorpheusJCheckBox.isSelected() + || enableSageJCheckBox.isSelected(); + } + /** * Inspects the parameter validity. * diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index 1912dc65..da1d2104 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -22,6 +22,9 @@

SearchGUI

<folder>/.venv/bin/instanovo before falling back to <folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. + A FASTA database is required for database search engines and PeptideShaker + post-processing, but not for de novo-only InstaNovo, InstaNovo+, or + InstaNovo with refinement runs.

diff --git a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java index 0df69e57..94a06000 100644 --- a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java +++ b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java @@ -38,6 +38,18 @@ public void testInstaNovoCliParsing() throws Exception { Assert.assertTrue(inputBean.isInstaNovoPlusEnabled()); Assert.assertTrue(inputBean.isInstaNovoRefineEnabled()); Assert.assertEquals(instaNovoFolder.getAbsoluteFile(), inputBean.getInstaNovoLocation().getAbsoluteFile()); + + CommandLine deNovoOnlyCommandLine = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-instanovo", "1", + "-instanovo_folder", instaNovoFolder.getAbsolutePath() + ); + + SearchCLIInputBean deNovoOnlyInputBean = new SearchCLIInputBean(deNovoOnlyCommandLine); + + Assert.assertTrue(deNovoOnlyInputBean.isInstaNovoEnabled()); + Assert.assertNull(deNovoOnlyInputBean.getFastaFile()); } /** @@ -63,11 +75,20 @@ public void testInstaNovoCliValidationAndHelp() throws Exception { ); Assert.assertFalse(SearchCLIInputBean.isValidStartup(missingFolder)); + CommandLine databaseSearchWithoutFasta = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-msgf", "1" + ); + Assert.assertFalse(SearchCLIInputBean.isValidStartup(databaseSearchWithoutFasta)); + String help = SearchCLIParams.getOptionsAsString(); Assert.assertTrue(help.contains("-instanovo")); Assert.assertTrue(help.contains("-instanovo_plus")); Assert.assertTrue(help.contains("-instanovo_refine")); Assert.assertTrue(help.contains("-instanovo_folder")); + Assert.assertTrue(help.contains("Conditional Parameters")); + Assert.assertTrue(help.contains("optional for de novo-only searches")); } /** diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index 1912dc65..da1d2104 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -22,6 +22,9 @@

SearchGUI

<folder>/.venv/bin/instanovo before falling back to <folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. + A FASTA database is required for database search engines and PeptideShaker + post-processing, but not for de novo-only InstaNovo, InstaNovo+, or + InstaNovo with refinement runs.

From a7356f135f01f31dfdc87d07d2fe9c1c817e311a Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:38:19 +0200 Subject: [PATCH 08/18] Link uv installation docs for InstaNovo setup --- README.md | 2 +- src/main/resources/helpFiles/SearchGUI.html | 5 +++-- src/test/resources/helpFiles/SearchGUI.html | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6a3e20f..f80d0a3b 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The main purpose of SearchGUI is to make it simpler to use multiple search engin For details about the command line see: [SearchCLI](https://github.com/compomics/searchgui/wiki/SearchCLI). -InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. To use InstaNovo from SearchGUI or SearchCLI, clone the InstaNovo repository locally, install `uv`, and run `uv sync` in the InstaNovo checkout so that `/.venv/bin/instanovo` is created. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; `` is the InstaNovo checkout root, not the `.venv` folder. SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. The advanced settings model selectors are populated from `/instanovo/models.json`. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. +InstaNovo v1.2.2 can be run from SearchCLI with `-instanovo 1`, standalone InstaNovo+ with `-instanovo_plus 1`, and InstaNovo with InstaNovo+ refinement with `-instanovo_refine 1`. To use InstaNovo from SearchGUI or SearchCLI, clone the InstaNovo repository locally, [install `uv`](https://docs.astral.sh/uv/getting-started/installation/), and run `uv sync` in the InstaNovo checkout so that `/.venv/bin/instanovo` is created. Use `-instanovo_folder ` when the `instanovo` executable is not available on `PATH` or when a specific local InstaNovo installation should be used; `` is the InstaNovo checkout root, not the `.venv` folder. SearchGUI will look for `/.venv/bin/instanovo` before falling back to `/instanovo`. The advanced settings model selectors are populated from `/instanovo/models.json`. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. [Go to top of page](#searchgui) diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index da1d2104..b1879f6b 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -15,8 +15,9 @@

SearchGUI

InstaNovo and InstaNovo+ from the command line.

- To use InstaNovo, clone the InstaNovo repository locally, install uv, and - run uv sync in the InstaNovo checkout. When SearchGUI asks + To use InstaNovo, clone the InstaNovo repository locally, install + uv, + and run uv sync in the InstaNovo checkout. When SearchGUI asks for the InstaNovo installation folder, select the InstaNovo checkout root, not the .venv folder. SearchGUI looks for <folder>/.venv/bin/instanovo before falling back to diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index da1d2104..b1879f6b 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -15,8 +15,9 @@

SearchGUI

InstaNovo and InstaNovo+ from the command line.

- To use InstaNovo, clone the InstaNovo repository locally, install uv, and - run uv sync in the InstaNovo checkout. When SearchGUI asks + To use InstaNovo, clone the InstaNovo repository locally, install + uv, + and run uv sync in the InstaNovo checkout. When SearchGUI asks for the InstaNovo installation folder, select the InstaNovo checkout root, not the .venv folder. SearchGUI looks for <folder>/.venv/bin/instanovo before falling back to From 1e27ca3fded4b008f572b3a25105d664a03ccafe Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 14:53:32 +0200 Subject: [PATCH 09/18] Track InstaNovo prediction progress in GUI --- .../java/eu/isas/searchgui/SearchHandler.java | 26 +- .../InstaNovoProcessBuilder.java | 7 + .../SearchGUIProcessBuilder.java | 312 +++++++++++++++++- .../InstaNovoProcessBuilderTest.java | 37 +++ 4 files changed, 375 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index c08ac585..47fded42 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -2738,13 +2738,13 @@ protected Object doInBackground() { nProgress += nFilesToSearch; } if (enableInstaNovo) { - nProgress += nFilesToSearch; + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; } if (enableInstaNovoPlus) { - nProgress += nFilesToSearch; + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; } if (enableInstaNovoRefine) { - nProgress += nFilesToSearch; + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; } if (enableDirecTag) { nProgress += nFilesToSearch; @@ -4062,7 +4062,7 @@ protected Object doInBackground() { instaNovoOutputFile, spectrumFile ); - waitingHandler.increasePrimaryProgressCounter(); + completeProcessPrimaryProgress(instaNovoProcessBuilder); } } @@ -4098,7 +4098,7 @@ protected Object doInBackground() { instaNovoPlusOutputFile, spectrumFile ); - waitingHandler.increasePrimaryProgressCounter(); + completeProcessPrimaryProgress(instaNovoProcessBuilder); } } @@ -4134,7 +4134,7 @@ protected Object doInBackground() { instaNovoRefinedOutputFile, spectrumFile ); - waitingHandler.increasePrimaryProgressCounter(); + completeProcessPrimaryProgress(instaNovoProcessBuilder); } } @@ -4871,6 +4871,20 @@ private void registerIdentificationFile( } } + /** + * Completes the primary progress units allocated to a process. + * + * @param processBuilder the process builder + */ + private void completeProcessPrimaryProgress(SearchGUIProcessBuilder processBuilder) { + + int remainingProgress = processBuilder.getPrimaryProgressUnits() - processBuilder.getPrimaryProgressUnitsCompleted(); + + if (remainingProgress > 0) { + waitingHandler.increasePrimaryProgressCounter(remainingProgress); + } + } + /** * Save the input file. * diff --git a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java index ed05d244..19ba6093 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -18,6 +18,11 @@ public class InstaNovoProcessBuilder extends SearchGUIProcessBuilder { * The InstaNovo executable name. */ public static final String EXECUTABLE_FILE_NAME = "instanovo"; + /** + * The number of primary progress units allocated to one InstaNovo + * prediction run. + */ + public static final int PRIMARY_PROGRESS_UNITS = 200; /** * InstaNovo execution modes. @@ -67,6 +72,7 @@ public InstaNovoProcessBuilder( this.mode = mode; this.waitingHandler = waitingHandler; this.exceptionHandler = exceptionHandler; + this.primaryProgressUnits = PRIMARY_PROGRESS_UNITS; if (instaNovoParameters == null) { instaNovoParameters = new InstaNovoParameters(); @@ -121,6 +127,7 @@ public InstaNovoProcessBuilder( } process_name_array.add("force_cpu=" + Boolean.toString(instaNovoParameters.isForceCpu())); + process_name_array.add("log_interval=1"); process_name_array.trimToSize(); diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 888dc220..77816fda 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -9,6 +9,8 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A simple ancestor class to reduce code duplication in formatdb, omssacl and @@ -40,6 +42,30 @@ public abstract class SearchGUIProcessBuilder implements Runnable { * The exception handler to manage exception. */ protected ExceptionHandler exceptionHandler; + /** + * The number of primary progress units covered by this process. + */ + protected int primaryProgressUnits = 1; + /** + * The number of primary progress units already reported by this process. + */ + private int primaryProgressUnitsCompleted = 0; + /** + * Pattern used to remove ANSI escape codes from external tool output. + */ + private static final Pattern ANSI_PATTERN = Pattern.compile("\\x1B\\[[;\\d]*[ -/]*[@-~]"); + /** + * Pattern used to parse InstaNovo batch progress. + */ + private static final Pattern INSTANOVO_BATCH_PROGRESS_PATTERN = Pattern.compile("\\[Batch\\s+([0-9,]+)\\s*/\\s*([0-9,]+)\\]"); + /** + * Pattern used to parse generic percentage progress. + */ + private static final Pattern PERCENTAGE_PATTERN = Pattern.compile("(? 0 && !waitingHandler.isRunCanceled()) { + processInstaNovoOutputLine( + buffer.toString(), + predictionStarted, + secondaryProgressStarted, + secondaryProgress + ); + } + } + + /** + * Processes one InstaNovo output line. + * + * @param line the output line + * @param predictionStarted whether prediction progress has started + * @param secondaryProgressStarted whether the secondary progress bar is + * initialized + * @param secondaryProgress the current secondary progress + * + * @return the updated output status + */ + private InstaNovoOutputStatus processInstaNovoOutputLine( + String line, + boolean predictionStarted, + boolean secondaryProgressStarted, + int secondaryProgress + ) { + + String cleanLine = stripAnsi(line).trim(); + + if (cleanLine.isEmpty()) { + return new InstaNovoOutputStatus(predictionStarted, secondaryProgressStarted, secondaryProgress); + } + + if (cleanLine.lastIndexOf("") != -1) { + waitingHandler.appendReportEndLine(); + cleanLine = cleanLine.substring("".length(), cleanLine.length() - "".length()); + waitingHandler.appendReport(cleanLine, true, true); + waitingHandler.setRunCanceled(); + return new InstaNovoOutputStatus(predictionStarted, secondaryProgressStarted, secondaryProgress); + } + + if (isInstaNovoPredictionStart(cleanLine)) { + + predictionStarted = true; + + if (!secondaryProgressStarted) { + waitingHandler.setSecondaryProgressCounterIndeterminate(false); + waitingHandler.resetSecondaryProgressCounter(); + waitingHandler.setMaxSecondaryProgressCounter(100); + secondaryProgressStarted = true; + } + } + + Integer progressPercentage = predictionStarted ? parseInstaNovoProgressPercentage(cleanLine) : null; + + if (progressPercentage != null) { + + int boundedProgress = Math.max(0, Math.min(100, progressPercentage)); + int primaryProgress = (int) Math.floor(((double) boundedProgress * primaryProgressUnits) / 100.0); + increaseProcessPrimaryProgress(primaryProgress - primaryProgressUnitsCompleted); + + if (secondaryProgressStarted && boundedProgress > secondaryProgress) { + waitingHandler.increaseSecondaryProgressCounter(boundedProgress - secondaryProgress); + secondaryProgress = boundedProgress; + } + + if (boundedProgress > 0) { + waitingHandler.appendReport(cleanLine, false, true); + } + + } else { + waitingHandler.appendReport(cleanLine, false, true); + } + + return new InstaNovoOutputStatus(predictionStarted, secondaryProgressStarted, secondaryProgress); + } + + /** + * Returns true if this process is an InstaNovo process. + * + * @return true if this process is an InstaNovo process + */ + private boolean isInstaNovoProcess() { + return getType().equalsIgnoreCase("InstaNovo") + || getType().equalsIgnoreCase("InstaNovo+") + || getType().equalsIgnoreCase("InstaNovo with InstaNovo+ refinement"); + } + + /** + * Returns true if the line marks the start of InstaNovo prediction progress. + * + * @param line the output line + * + * @return true if prediction has started + */ + private static boolean isInstaNovoPredictionStart(String line) { + return line.contains("Predicting...") + || INSTANOVO_BATCH_PROGRESS_PATTERN.matcher(line).find(); + } + + /** + * Parses an InstaNovo progress percentage from an output line. + * + * @param line the output line + * + * @return the progress percentage, or null if no progress could be parsed + */ + static Integer parseInstaNovoProgressPercentage(String line) { + + String cleanLine = stripAnsi(line); + Matcher batchMatcher = INSTANOVO_BATCH_PROGRESS_PATTERN.matcher(cleanLine); + + if (batchMatcher.find()) { + return getProgressPercentage(batchMatcher.group(1), batchMatcher.group(2)); + } + + Matcher percentageMatcher = PERCENTAGE_PATTERN.matcher(cleanLine); + + if (percentageMatcher.find()) { + + try { + return (int) Math.floor(Double.parseDouble(percentageMatcher.group(1))); + } catch (NumberFormatException e) { + return null; + } + } + + Matcher currentTotalMatcher = CURRENT_TOTAL_PATTERN.matcher(cleanLine); + + if (currentTotalMatcher.find()) { + return getProgressPercentage(currentTotalMatcher.group(1), currentTotalMatcher.group(2)); + } + + return null; + } + + /** + * Converts current/total strings to a progress percentage. + * + * @param current the current value + * @param total the total value + * + * @return the progress percentage, or null if parsing fails + */ + private static Integer getProgressPercentage(String current, String total) { + + try { + + int currentValue = Integer.parseInt(current.replace(",", "")); + int totalValue = Integer.parseInt(total.replace(",", "")); + + if (totalValue <= 0) { + return null; + } + + return (int) Math.floor(((double) currentValue * 100.0) / totalValue); + + } catch (NumberFormatException e) { + return null; + } + } + + /** + * Removes ANSI escape codes. + * + * @param line the line + * + * @return the line without ANSI escape codes + */ + private static String stripAnsi(String line) { + return ANSI_PATTERN.matcher(line).replaceAll(""); + } + + /** + * Increases the primary progress counter for this process. + * + * @param increment the increment + */ + protected void increaseProcessPrimaryProgress(int increment) { + + if (increment > 0 && waitingHandler != null && !waitingHandler.isRunCanceled()) { + int cappedIncrement = Math.min(increment, primaryProgressUnits - primaryProgressUnitsCompleted); + waitingHandler.increasePrimaryProgressCounter(cappedIncrement); + primaryProgressUnitsCompleted += cappedIncrement; + } + } + + /** + * Returns the number of primary progress units covered by this process. + * + * @return the number of primary progress units + */ + public int getPrimaryProgressUnits() { + return primaryProgressUnits; + } + + /** + * Returns the number of primary progress units already reported by this + * process. + * + * @return the number of completed primary progress units + */ + public int getPrimaryProgressUnitsCompleted() { + return primaryProgressUnitsCompleted; + } + + /** + * The InstaNovo output progress status. + */ + private static class InstaNovoOutputStatus { + + /** + * Whether prediction progress has started. + */ + private final boolean predictionStarted; + /** + * Whether the secondary progress bar is initialized. + */ + private final boolean secondaryProgressStarted; + /** + * The current secondary progress. + */ + private final int secondaryProgress; + + /** + * Constructor. + * + * @param predictionStarted whether prediction progress has started + * @param secondaryProgressStarted whether the secondary progress bar is + * initialized + * @param secondaryProgress the current secondary progress + */ + private InstaNovoOutputStatus( + boolean predictionStarted, + boolean secondaryProgressStarted, + int secondaryProgress + ) { + this.predictionStarted = predictionStarted; + this.secondaryProgressStarted = secondaryProgressStarted; + this.secondaryProgress = secondaryProgress; + } + } + /** * Returns the type of the process. * diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java index fa4421e1..5fb1cad7 100644 --- a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -97,6 +97,42 @@ public void testInstaNovoCommandLines() throws Exception { Assert.assertTrue(advancedCommand.contains("save_all_predictions=false")); Assert.assertTrue(advancedCommand.contains("batch_size=64")); Assert.assertTrue(advancedCommand.contains("force_cpu=true")); + Assert.assertTrue(advancedCommand.contains("log_interval=1")); + + InstaNovoProcessBuilder processBuilder = new InstaNovoProcessBuilder( + instaNovoFolder, + spectrumFile, + outputFile, + InstaNovoProcessBuilder.Mode.transformer, + defaultParameters, + null, + null + ); + Assert.assertEquals(InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS, processBuilder.getPrimaryProgressUnits()); + } + + /** + * Tests parsing InstaNovo progress output. + */ + public void testInstaNovoProgressParsing() { + + Assert.assertEquals( + Integer.valueOf(50), + SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("[Batch 00050/00100] [00:10/00:20, 5.0it/s]:") + ); + Assert.assertEquals( + Integer.valueOf(42), + SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("Predicting: 42%|####2 | 42/100 [00:04<00:06, 7.5it/s]") + ); + Assert.assertEquals( + Integer.valueOf(25), + SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("25/100 [00:01<00:03]") + ); + Assert.assertEquals( + Integer.valueOf(12), + SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("\u001B[32mINFO\u001B[0m Rows filtered: 12.50%") + ); + Assert.assertNull(SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("Loading model...")); } /** @@ -164,6 +200,7 @@ private void assertCommand( Assert.assertTrue(command.contains("use_knapsack=false")); Assert.assertTrue(command.contains("save_all_predictions=true")); Assert.assertTrue(command.contains("force_cpu=false")); + Assert.assertTrue(command.contains("log_interval=1")); Assert.assertFalse(command.contains("batch_size=-1")); Assert.assertEquals(type, new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, mode, new InstaNovoParameters(), null, null).getType()); From 2cc0779d2ca2d2e5446da89b31e632213e8dc91e Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 15:00:20 +0200 Subject: [PATCH 10/18] Document InstaNovo desktop batch size --- src/main/java/eu/isas/searchgui/gui/SearchGUI.java | 2 +- src/main/resources/helpFiles/SearchGUI.html | 3 +++ .../searchgui/processbuilders/InstaNovoProcessBuilderTest.java | 1 + src/test/resources/helpFiles/SearchGUI.html | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index ff4a4368..e8aac26d 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -2940,7 +2940,7 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo panel.add(beamSearchCombo); panel.add(new JLabel("Predictions")); panel.add(saveAllPredictionsCheckBox); - panel.add(new JLabel("Batch size (-1 uses default)")); + panel.add(new JLabel("Batch size (-1 uses InstaNovo default)")); panel.add(batchSizeSpinner); panel.add(new JLabel("Config path")); panel.add(configPathTxt); diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index b1879f6b..3db9728d 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -23,6 +23,9 @@

SearchGUI

<folder>/.venv/bin/instanovo before falling back to <folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. + SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; + increase it in the advanced settings for larger GPUs, or set it to -1 to + use the default from the InstaNovo inference configuration. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java index 5fb1cad7..97c7fc7e 100644 --- a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -199,6 +199,7 @@ private void assertCommand( Assert.assertTrue(command.contains("num_beams=5")); Assert.assertTrue(command.contains("use_knapsack=false")); Assert.assertTrue(command.contains("save_all_predictions=true")); + Assert.assertTrue(command.contains("batch_size=" + InstaNovoParameters.DEFAULT_BATCH_SIZE)); Assert.assertTrue(command.contains("force_cpu=false")); Assert.assertTrue(command.contains("log_interval=1")); Assert.assertFalse(command.contains("batch_size=-1")); diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index b1879f6b..3db9728d 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -23,6 +23,9 @@

SearchGUI

<folder>/.venv/bin/instanovo before falling back to <folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. + SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; + increase it in the advanced settings for larger GPUs, or set it to -1 to + use the default from the InstaNovo inference configuration. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. From c7ccc06b781621425f16d0270d632682e5ea8309 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 15:05:41 +0200 Subject: [PATCH 11/18] Handle failed external process exits --- .../java/eu/isas/searchgui/gui/SearchGUI.java | 4 +- .../SearchGUIProcessBuilder.java | 27 +++-- src/main/resources/helpFiles/SearchGUI.html | 3 +- .../InstaNovoProcessBuilderTest.java | 102 ++++++++++++++++++ src/test/resources/helpFiles/SearchGUI.html | 3 +- 5 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index e8aac26d..aed83075 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -2919,7 +2919,7 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo JComboBox beamSearchCombo = new JComboBox<>(new String[]{"Standard beam search", "Knapsack beam search"}); beamSearchCombo.setSelectedIndex(instaNovoParameters.isUseKnapsack() ? 1 : 0); JCheckBox saveAllPredictionsCheckBox = new JCheckBox("Save all beam predictions", instaNovoParameters.isSaveAllPredictions()); - JSpinner batchSizeSpinner = new JSpinner(new SpinnerNumberModel(instaNovoParameters.getBatchSize(), -1, 100000, 1)); + JSpinner batchSizeSpinner = new JSpinner(new SpinnerNumberModel(instaNovoParameters.getBatchSize(), 1, 100000, 1)); JCheckBox forceCpuCheckBox = new JCheckBox("Force CPU execution", instaNovoParameters.isForceCpu()); JPanel panel = new JPanel(new java.awt.GridLayout(0, 2, 5, 5)); @@ -2940,7 +2940,7 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo panel.add(beamSearchCombo); panel.add(new JLabel("Predictions")); panel.add(saveAllPredictionsCheckBox); - panel.add(new JLabel("Batch size (-1 uses InstaNovo default)")); + panel.add(new JLabel("Batch size")); panel.add(batchSizeSpinner); panel.add(new JLabel("Config path")); panel.add(configPathTxt); diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 77816fda..0323656b 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -314,18 +314,33 @@ public void startProcess() throws IOException { } else { processDuration.end(); - waitingHandler.appendReportEndLine(); - waitingHandler.appendReportEndLine(); - waitingHandler.appendReport(getType() + " finished for " + getCurrentlyProcessedFileName() + " (" + processDuration.toString() + ").", true, true); - waitingHandler.appendReportEndLine(); - // wait for process to terminate before exiting + // wait for process to terminate before reporting success try { - p.waitFor(); + + int exitCode = p.waitFor(); + + if (exitCode == 0) { + waitingHandler.appendReportEndLine(); + waitingHandler.appendReportEndLine(); + waitingHandler.appendReport(getType() + " finished for " + getCurrentlyProcessedFileName() + " (" + processDuration.toString() + ").", true, true); + waitingHandler.appendReportEndLine(); + } else { + waitingHandler.appendReportEndLine(); + waitingHandler.appendReport(getType() + " failed for " + getCurrentlyProcessedFileName() + " with exit code " + exitCode + ".", true, true); + waitingHandler.setRunCanceled(); + } + } catch (InterruptedException e) { + if (p != null) { p.destroy(); } + + waitingHandler.appendReportEndLine(); + waitingHandler.appendReport(getType() + " was interrupted for " + getCurrentlyProcessedFileName() + ".", true, true); + waitingHandler.setRunCanceled(); + Thread.currentThread().interrupt(); } } } diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index 3db9728d..440cba4a 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -24,8 +24,7 @@

SearchGUI

<folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; - increase it in the advanced settings for larger GPUs, or set it to -1 to - use the default from the InstaNovo inference configuration. + increase it in the advanced settings for larger GPUs. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java index 97c7fc7e..24611f9f 100644 --- a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -1,6 +1,8 @@ package eu.isas.searchgui.processbuilders; +import com.compomics.util.gui.waiting.waitinghandlers.WaitingHandlerDummy; import com.compomics.util.parameters.identification.tool_specific.InstaNovoParameters; +import com.compomics.util.waiting.WaitingHandler; import java.io.File; import java.util.List; import junit.framework.TestCase; @@ -99,6 +101,18 @@ public void testInstaNovoCommandLines() throws Exception { Assert.assertTrue(advancedCommand.contains("force_cpu=true")); Assert.assertTrue(advancedCommand.contains("log_interval=1")); + InstaNovoParameters legacyParameters = new InstaNovoParameters(); + legacyParameters.setBatchSize(-1); + Assert.assertTrue(new InstaNovoProcessBuilder( + instaNovoFolder, + spectrumFile, + outputFile, + InstaNovoProcessBuilder.Mode.transformer, + legacyParameters, + null, + null + ).getCommand().contains("batch_size=" + InstaNovoParameters.DEFAULT_BATCH_SIZE)); + InstaNovoProcessBuilder processBuilder = new InstaNovoProcessBuilder( instaNovoFolder, spectrumFile, @@ -135,6 +149,23 @@ public void testInstaNovoProgressParsing() { Assert.assertNull(SearchGUIProcessBuilder.parseInstaNovoProgressPercentage("Loading model...")); } + /** + * Tests that non-zero external process exits cancel the run. + * + * @throws Exception if an exception occurs + */ + public void testFailedProcessCancelsRun() throws Exception { + + TestWaitingHandler waitingHandler = new TestWaitingHandler(); + TestProcessBuilder processBuilder = new TestProcessBuilder(waitingHandler, "echo failing; exit 7"); + + processBuilder.startProcess(); + + Assert.assertTrue(waitingHandler.isRunCanceled()); + Assert.assertTrue(waitingHandler.report.toString().contains("failed for input.mgf with exit code 7")); + Assert.assertFalse(waitingHandler.report.toString().contains("finished for input.mgf")); + } + /** * Asserts a command line. * @@ -262,4 +293,75 @@ private File createFolder(String prefix) throws Exception { return folder; } + + /** + * Test process builder. + */ + private static class TestProcessBuilder extends SearchGUIProcessBuilder { + + /** + * Constructor. + * + * @param waitingHandler the waiting handler + * @param command the shell command + */ + private TestProcessBuilder(WaitingHandler waitingHandler, String command) { + + this.waitingHandler = waitingHandler; + process_name_array.add("sh"); + process_name_array.add("-c"); + process_name_array.add(command); + pb = new ProcessBuilder(process_name_array); + pb.redirectErrorStream(true); + } + + @Override + public String getType() { + return "TestTool"; + } + + @Override + public String getCurrentlyProcessedFileName() { + return "input.mgf"; + } + } + + /** + * Test waiting handler. + */ + private static class TestWaitingHandler extends WaitingHandlerDummy { + + /** + * Whether the run was canceled. + */ + private boolean runCanceled = false; + /** + * The report. + */ + private final StringBuilder report = new StringBuilder(); + + @Override + public void setRunCanceled() { + runCanceled = true; + } + + @Override + public void appendReport(String report, boolean includeDate, boolean addNewLine) { + this.report.append(report); + if (addNewLine) { + this.report.append(System.getProperty("line.separator")); + } + } + + @Override + public void appendReportEndLine() { + report.append(System.getProperty("line.separator")); + } + + @Override + public boolean isRunCanceled() { + return runCanceled; + } + + } } diff --git a/src/test/resources/helpFiles/SearchGUI.html b/src/test/resources/helpFiles/SearchGUI.html index 3db9728d..440cba4a 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -24,8 +24,7 @@

SearchGUI

<folder>/instanovo, and the advanced settings model selectors are populated from <folder>/instanovo/models.json. SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; - increase it in the advanced settings for larger GPUs, or set it to -1 to - use the default from the InstaNovo inference configuration. + increase it in the advanced settings for larger GPUs. A FASTA database is required for database search engines and PeptideShaker post-processing, but not for de novo-only InstaNovo, InstaNovo+, or InstaNovo with refinement runs. From ee7bd6e3edd03df54d109624817acb6156b85eda Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 16:08:05 +0200 Subject: [PATCH 12/18] Separate InstaNovo refined outputs --- .../java/eu/isas/searchgui/SearchHandler.java | 28 +++++++++- .../searchgui/cmd/SearchCLIInputBean.java | 39 +++++++++++--- .../java/eu/isas/searchgui/gui/SearchGUI.java | 53 ++++++++++++------- .../InstaNovoProcessBuilder.java | 2 +- .../SearchGUIProcessBuilder.java | 30 ++++++++--- .../searchgui/cmd/SearchCLIInstaNovoTest.java | 7 +++ 6 files changed, 122 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index 47fded42..4d8e9f73 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -4130,7 +4130,7 @@ protected Object doInBackground() { registerIdentificationFile( identificationFiles, spectrumFileName, - Advocate.instanovoPlus, + Advocate.instanovoRefined, instaNovoRefinedOutputFile, spectrumFile ); @@ -4575,7 +4575,7 @@ protected Object doInBackground() { } - if (enableInstaNovoPlus || enableInstaNovoRefine) { + if (enableInstaNovoPlus) { File outputFile = getDefaultOutputFile( outputFolder, @@ -4599,6 +4599,30 @@ protected Object doInBackground() { } + if (enableInstaNovoRefine) { + + File outputFile = getDefaultOutputFile( + outputFolder, + Advocate.instanovoRefined.getName(), + utilitiesUserParameters.isIncludeDateInOutputName() + ); + + if (outputFile.exists()) { + + identificationFilesList.add(outputFile); + + } else { + + waitingHandler.appendReport( + "Could not find " + Advocate.instanovoRefined.getName() + " results.", + true, + true + ); + + } + + } + } else if (utilitiesUserParameters.getSearchGuiOutputParameters() == OutputParameters.run) { for (String run : identificationFiles.keySet()) { diff --git a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java index 5fda7b55..d35c9c60 100644 --- a/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java +++ b/src/main/java/eu/isas/searchgui/cmd/SearchCLIInputBean.java @@ -918,8 +918,8 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { return false; } } - } - + } + // check the FASTA file boolean fastaRequired = isDatabaseSearchSelected(aLine); boolean fastaProvided = aLine.hasOption(SearchCLIParams.FASTA_FILE.id) @@ -1106,12 +1106,17 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { if (aLine.hasOption(SearchCLIParams.DIRECTAG.id)) { String input = aLine.getOptionValue(SearchCLIParams.DIRECTAG.id); if (!CommandParameter.isBooleanInput(SearchCLIParams.DIRECTAG.id, input)) { - return false; - } - } - - // check the search engine folders - if (aLine.hasOption(SearchCLIParams.OMSSA_LOCATION.id)) { + return false; + } + } + + if (!isSearchEngineOrDeNovoAlgorithmSelected(aLine)) { + System.out.println(System.getProperty("line.separator") + "No search engine or de novo algorithm selected." + System.getProperty("line.separator")); + return false; + } + + // check the search engine folders + if (aLine.hasOption(SearchCLIParams.OMSSA_LOCATION.id)) { String input = aLine.getOptionValue(SearchCLIParams.OMSSA_LOCATION.id); File file = new File(input); if (!file.exists()) { @@ -1290,6 +1295,24 @@ private static boolean isDatabaseSearchSelected(CommandLine aLine) { || isEnabled(aLine, SearchCLIParams.SAGE); } + /** + * Returns true if any command line search engine or de novo algorithm is + * selected. + * + * @param aLine the command line + * + * @return true if an engine or de novo algorithm is selected + */ + private static boolean isSearchEngineOrDeNovoAlgorithmSelected(CommandLine aLine) { + + return isDatabaseSearchSelected(aLine) + || isEnabled(aLine, SearchCLIParams.NOVOR) + || isEnabled(aLine, SearchCLIParams.INSTANOVO) + || isEnabled(aLine, SearchCLIParams.INSTANOVO_PLUS) + || isEnabled(aLine, SearchCLIParams.INSTANOVO_REFINE) + || isEnabled(aLine, SearchCLIParams.DIRECTAG); + } + /** * Returns true if the option is enabled on the command line. * diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index aed83075..1f58adbc 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -2899,8 +2899,9 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo ? copyInstaNovoParameters((InstaNovoParameters) oldParameters, plusParameters) : (plusParameters ? new InstaNovoPlusParameters() : new InstaNovoParameters()); - JComboBox instaNovoModelCmb = createInstaNovoModelComboBox("transformer", instaNovoParameters.getInstaNovoModel()); - JComboBox instaNovoPlusModelCmb = createInstaNovoModelComboBox("diffusion", instaNovoParameters.getInstaNovoPlusModel()); + HashMap> instaNovoModels = getInstaNovoModels(searchHandler.getInstaNovoLocation()); + JComboBox instaNovoModelCmb = createInstaNovoModelComboBox(instaNovoModels.get("transformer"), instaNovoParameters.getInstaNovoModel()); + JComboBox instaNovoPlusModelCmb = createInstaNovoModelComboBox(instaNovoModels.get("diffusion"), instaNovoParameters.getInstaNovoPlusModel()); if ((showInstaNovoModel && instaNovoModelCmb.getItemCount() == 0) || (showInstaNovoPlusModel && instaNovoPlusModelCmb.getItemCount() == 0)) { @@ -2994,14 +2995,16 @@ private void editInstaNovoSettings(boolean plusParameters, boolean showInstaNovo /** * Creates a model combo box populated from the InstaNovo models file. * - * @param modelType the model type in models.json + * @param models the available models * @param selectedModel the selected model * * @return the model combo box */ - private JComboBox createInstaNovoModelComboBox(String modelType, String selectedModel) { + private JComboBox createInstaNovoModelComboBox(ArrayList models, String selectedModel) { - ArrayList models = getInstaNovoModels(modelType); + if (models == null) { + models = new ArrayList<>(); + } JComboBox comboBox = new JComboBox<>(models.toArray(new String[0])); comboBox.setEditable(false); @@ -3016,26 +3019,29 @@ private JComboBox createInstaNovoModelComboBox(String modelType, String /** * Returns the available InstaNovo models for the given type. * + * @param instaNovoLocation the InstaNovo installation folder * @param modelType the model type in models.json * * @return the available model ids */ - private ArrayList getInstaNovoModels(String modelType) { + static ArrayList getInstaNovoModels(File instaNovoLocation, String modelType) { - return getInstaNovoModels(searchHandler.getInstaNovoLocation(), modelType); + HashMap> models = getInstaNovoModels(instaNovoLocation); + ArrayList result = models.get(modelType); + + return result == null ? new ArrayList<>() : result; } /** - * Returns the available InstaNovo models for the given type. + * Returns the available InstaNovo models. * * @param instaNovoLocation the InstaNovo installation folder - * @param modelType the model type in models.json * - * @return the available model ids + * @return the available model ids indexed by model type */ - static ArrayList getInstaNovoModels(File instaNovoLocation, String modelType) { + static HashMap> getInstaNovoModels(File instaNovoLocation) { - ArrayList result = new ArrayList<>(); + HashMap> result = new HashMap<>(); if (instaNovoLocation == null) { return result; @@ -3050,12 +3056,20 @@ static ArrayList getInstaNovoModels(File instaNovoLocation, String model try (FileReader reader = new FileReader(modelsFile)) { JsonObject modelsJson = JsonParser.parseReader(reader).getAsJsonObject(); - JsonObject modelSection = modelsJson.getAsJsonObject(modelType); - if (modelSection != null) { + for (Map.Entry modelTypeEntry : modelsJson.entrySet()) { + + JsonElement modelTypeElement = modelTypeEntry.getValue(); + + if (modelTypeElement.isJsonObject()) { + + ArrayList models = new ArrayList<>(); + + for (Map.Entry modelEntry : modelTypeElement.getAsJsonObject().entrySet()) { + models.add(modelEntry.getKey()); + } - for (Map.Entry entry : modelSection.entrySet()) { - result.add(entry.getKey()); + result.put(modelTypeEntry.getKey(), models); } } @@ -8982,14 +8996,13 @@ public static boolean validateSearchEngineInstallation( feedBackInDialog ); - } else if (advocate == Advocate.instanovo || advocate == Advocate.instanovoPlus) { + } else if (advocate == Advocate.instanovo || advocate == Advocate.instanovoPlus || advocate == Advocate.instanovoRefined) { if (searchEngineLocation != null) { - File virtualEnvironmentExecutable = new File(searchEngineLocation, ".venv" + File.separator + "bin" + File.separator + InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME); - File executable = new File(searchEngineLocation, InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME); + File executable = InstaNovoProcessBuilder.getExecutable(searchEngineLocation); - if (virtualEnvironmentExecutable.exists() || executable.exists()) { + if (executable.exists()) { return true; } } diff --git a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java index 19ba6093..2cb0f2c9 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -151,7 +151,7 @@ public InstaNovoProcessBuilder( * * @return the executable */ - private File getExecutable(File instaNovoFolder) { + public static File getExecutable(File instaNovoFolder) { if (instaNovoFolder != null) { diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 0323656b..65939e25 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -9,6 +9,7 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Scanner; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -308,9 +309,7 @@ public void startProcess() throws IOException { // check if the user has cancelled the process or not if (waitingHandler.isRunCanceled()) { - if (p != null) { - p.destroy(); - } + terminateProcess(); } else { processDuration.end(); @@ -333,9 +332,7 @@ public void startProcess() throws IOException { } catch (InterruptedException e) { - if (p != null) { - p.destroy(); - } + terminateProcess(); waitingHandler.appendReportEndLine(); waitingHandler.appendReport(getType() + " was interrupted for " + getCurrentlyProcessedFileName() + ".", true, true); @@ -351,8 +348,29 @@ public void startProcess() throws IOException { * Ends the process. */ public void endProcess() { + terminateProcess(); + } + + /** + * Terminates the external process. + */ + private void terminateProcess() { + if (p != null) { + p.destroy(); + + try { + + if (!p.waitFor(5, TimeUnit.SECONDS)) { + p.destroyForcibly(); + } + + } catch (InterruptedException e) { + + p.destroyForcibly(); + Thread.currentThread().interrupt(); + } } } diff --git a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java index 94a06000..4532611d 100644 --- a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java +++ b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java @@ -82,6 +82,13 @@ public void testInstaNovoCliValidationAndHelp() throws Exception { ); Assert.assertFalse(SearchCLIInputBean.isValidStartup(databaseSearchWithoutFasta)); + CommandLine noEngineSelected = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-fasta_file", createFile("database", ".fasta").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath() + ); + Assert.assertFalse(SearchCLIInputBean.isValidStartup(noEngineSelected)); + String help = SearchCLIParams.getOptionsAsString(); Assert.assertTrue(help.contains("-instanovo")); Assert.assertTrue(help.contains("-instanovo_plus")); From bb14ef226ae290db1b29e4f0df74dd1cdfb646da Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 22:36:26 +0200 Subject: [PATCH 13/18] Allow de novo searches and PeptideShaker post-processing without a database A database is only required when a database search engine is selected; de novo only runs (including PeptideShaker post-processing) can now proceed without one. The PeptideShaker launch omits the -fasta_file argument when no database is set. --- src/main/java/eu/isas/searchgui/gui/SearchGUI.java | 11 ++++------- .../processbuilders/PeptideShakerProcessBuilder.java | 7 +++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 1f58adbc..8ebfdd75 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -7879,21 +7879,18 @@ private boolean validateInput(boolean showMessage) { } - boolean databaseRequired = isDatabaseSearchSelected() - || peptideShakerCheckBox.isSelected(); + // a database is only required for database search engines; de novo only runs + // (including PeptideShaker post-processing) can be performed without a database + boolean databaseRequired = isDatabaseSearchSelected(); if (databaseRequired && (databaseFileTxt.getText() == null || databaseFileTxt.getText().trim().equals(""))) { if (showMessage && valid) { - String message = peptideShakerCheckBox.isSelected() && !isDatabaseSearchSelected() - ? "You need to specify a search database when PeptideShaker post-processing is enabled." - : "You need to specify a search database."; - JOptionPane.showMessageDialog( this, - message, + "You need to specify a search database.", "Search Database Not Found", JOptionPane.WARNING_MESSAGE ); diff --git a/src/main/java/eu/isas/searchgui/processbuilders/PeptideShakerProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/PeptideShakerProcessBuilder.java index 9efb3fd6..7afc1de5 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/PeptideShakerProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/PeptideShakerProcessBuilder.java @@ -127,8 +127,11 @@ private void setUpProcessBuilder() throws IOException, ClassNotFoundException { process_name_array.add(CommandLineUtils.getCommandLineArgument(identificationFiles)); process_name_array.add("-spectrum_files"); process_name_array.add(CommandLineUtils.getCommandLineArgument(spectrumFiles)); - process_name_array.add("-fasta_file"); - process_name_array.add(CommandLineUtils.getCommandLineArgument(fastaFile)); + if (fastaFile != null) { + // de novo only runs are processed without a protein sequence database + process_name_array.add("-fasta_file"); + process_name_array.add(CommandLineUtils.getCommandLineArgument(fastaFile)); + } process_name_array.add("-id_params"); process_name_array.add(CommandLineUtils.getCommandLineArgument(identificationParametersFile)); process_name_array.add("-out"); From bac0f0aa0665d7312daef232648d77bc6f72e87c Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 22:36:26 +0200 Subject: [PATCH 14/18] Report a clear error when an external tool fails to start When an external tool (such as ThermoRawFileParser) cannot be started, report the cause and cancel the run instead of failing later with a null process NullPointerException. --- .../SearchGUIProcessBuilder.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 65939e25..62d42391 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -108,6 +108,25 @@ public void startProcess() throws IOException { ioe.printStackTrace(); } + // the external process could not be started (e.g. the executable or a + // required runtime is missing); report the real cause and abort gracefully + // instead of failing later with a null process NPE + if (p == null) { + + if (waitingHandler != null) { + waitingHandler.appendReport( + "Could not start " + getType() + ". Please verify that it is installed and on the path. " + + "Note that .NET based tools such as ThermoRawFileParser require mono on Linux and macOS.", + true, + true + ); + waitingHandler.setRunCanceled(); + } + + return; + + } + // get inputstream from process InputStream inputStream = p.getInputStream(); From a1d918852482147668a854917e0abe5d6af8f0c1 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 22:36:26 +0200 Subject: [PATCH 15/18] Use the .mzML extension for ThermoRawFileParser output ThermoRawFileParser writes .mzML output; request and look for that exact extension so the converted file is found on case-sensitive file systems (otherwise de novo engines reading the spectrum file directly could not find it). --- src/main/java/eu/isas/searchgui/SearchHandler.java | 4 +++- .../ThermoRawFileParserProcessBuilder.java | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index 4d8e9f73..06aa6487 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -2961,7 +2961,9 @@ protected Object doInBackground() { File rawFile = rawFiles.get(i); String rawFileName = rawFile.getName(); File folder = rawFile.getParentFile(); - String msFileName = IoUtil.removeExtension(rawFileName) + thermoRawFileParserParameters.getOutputFormat().fileNameEnding.toLowerCase(); + // use the exact extension produced by ThermoRawFileParser (e.g. .mzML) so the + // converted file is found on case-sensitive file systems + String msFileName = IoUtil.removeExtension(rawFileName) + thermoRawFileParserParameters.getOutputFormat().fileNameEnding; File msFile = new File(folder, msFileName); // Check whether the file exists but with a different case for the extension. diff --git a/src/main/java/eu/isas/searchgui/processbuilders/ThermoRawFileParserProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/ThermoRawFileParserProcessBuilder.java index 028ec4a2..c465f46f 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/ThermoRawFileParserProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/ThermoRawFileParserProcessBuilder.java @@ -107,11 +107,10 @@ private void setUpProcessBuilder() throws IOException, ClassNotFoundException { // add the conversion parameters process_name_array.add("-i=" + rawFile.getAbsolutePath()); - if (thermoRawFileParserParameters.getOutputFormat() == ThermoRawFileParserOutputFormat.mgf) { - process_name_array.add("-b=" + new File(destinationFolder, IoUtil.removeExtension(rawFile.getName()) + ".mgf").getAbsolutePath()); - } else { - process_name_array.add("-b=" + new File(destinationFolder, IoUtil.removeExtension(rawFile.getName()) + ".mzml").getAbsolutePath()); - } + // use the format's own extension (.mgf / .mzML) so the requested output name matches + // what ThermoRawFileParser writes and what SearchGUI looks for afterwards + String outputEnding = thermoRawFileParserParameters.getOutputFormat().fileNameEnding; + process_name_array.add("-b=" + new File(destinationFolder, IoUtil.removeExtension(rawFile.getName()) + outputEnding).getAbsolutePath()); process_name_array.add("-f=" + thermoRawFileParserParameters.getOutputFormat().index); if (!thermoRawFileParserParameters.isPeackPicking()) { From 18bd69ce701c55f7844dd81fea2184e52097b71f Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Mon, 22 Jun 2026 23:24:04 +0200 Subject: [PATCH 16/18] Harden de novo-only InstaNovo output --- .../java/eu/isas/searchgui/SearchHandler.java | 20 +++++++----- .../InstaNovoProcessBuilder.java | 31 ++++++++++--------- .../searchgui/cmd/SearchCLIInstaNovoTest.java | 29 +++++++++++++++++ .../InstaNovoProcessBuilderTest.java | 11 +++++++ 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/SearchHandler.java b/src/main/java/eu/isas/searchgui/SearchHandler.java index 06aa6487..91649f9d 100644 --- a/src/main/java/eu/isas/searchgui/SearchHandler.java +++ b/src/main/java/eu/isas/searchgui/SearchHandler.java @@ -4923,7 +4923,9 @@ public void saveInputFile(File folder) { try ( BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) { // add the fasta file - bw.write((fastaFile == null ? "" : fastaFile.getAbsolutePath()) + System.getProperty("line.separator")); + if (fastaFile != null) { + bw.write(fastaFile.getAbsolutePath() + System.getProperty("line.separator")); + } // add the ms files for (File spectrumFile : msFiles) { @@ -5519,13 +5521,15 @@ private void addDataToZip( ); // add the fasta file - ZipUtils.addFileToZip( - DEFAULT_DATA_FOLDER, - fastaFile, - out, - waitingHandler, - totalUncompressedSize - ); + if (fastaFile != null) { + ZipUtils.addFileToZip( + DEFAULT_DATA_FOLDER, + fastaFile, + out, + waitingHandler, + totalUncompressedSize + ); + } // add the spectrum files for (File spectrumFile : getSpectrumFiles()) { diff --git a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java index 2cb0f2c9..b087004e 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -155,21 +155,22 @@ public static File getExecutable(File instaNovoFolder) { if (instaNovoFolder != null) { - File virtualEnvironmentExecutable = new File( - instaNovoFolder, - ".venv" + File.separator + "bin" + File.separator + EXECUTABLE_FILE_NAME - ); - - if (virtualEnvironmentExecutable.exists()) { - virtualEnvironmentExecutable.setExecutable(true); - return virtualEnvironmentExecutable; - } - - File executable = new File(instaNovoFolder, EXECUTABLE_FILE_NAME); - - if (executable.exists()) { - executable.setExecutable(true); - return executable; + String[] relativeExecutablePaths = { + ".venv" + File.separator + "bin" + File.separator + EXECUTABLE_FILE_NAME, + ".venv" + File.separator + "Scripts" + File.separator + EXECUTABLE_FILE_NAME, + ".venv" + File.separator + "Scripts" + File.separator + EXECUTABLE_FILE_NAME + ".exe", + EXECUTABLE_FILE_NAME, + EXECUTABLE_FILE_NAME + ".exe" + }; + + for (String relativeExecutablePath : relativeExecutablePaths) { + + File executable = new File(instaNovoFolder, relativeExecutablePath); + + if (executable.exists()) { + executable.setExecutable(true); + return executable; + } } } diff --git a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java index 4532611d..853a2adc 100644 --- a/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java +++ b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java @@ -1,6 +1,10 @@ package eu.isas.searchgui.cmd; +import eu.isas.searchgui.SearchHandler; import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; import junit.framework.TestCase; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; @@ -98,6 +102,31 @@ public void testInstaNovoCliValidationAndHelp() throws Exception { Assert.assertTrue(help.contains("optional for de novo-only searches")); } + /** + * Tests that de novo-only input manifests do not contain blank FASTA lines. + * + * @throws Exception if an exception occurs + */ + public void testDeNovoOnlyInputManifestHasNoBlankFastaLine() throws Exception { + + File outputFolder = createFolder("searchgui-input-manifest"); + File spectrumFile = createFile("input", ".mgf"); + + SearchHandler searchHandler = new SearchHandler(); + searchHandler.setFastaFile(null); + + ArrayList spectrumFiles = new ArrayList<>(); + spectrumFiles.add(spectrumFile); + searchHandler.setSpectrumFiles(spectrumFiles); + searchHandler.saveInputFile(outputFolder); + + List lines = Files.readAllLines(SearchHandler.getInputFile(outputFolder).toPath()); + + Assert.assertEquals(1, lines.size()); + Assert.assertEquals(spectrumFile.getAbsolutePath(), lines.get(0)); + Assert.assertFalse(lines.get(0).isEmpty()); + } + /** * Parses command line arguments. * diff --git a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java index 24611f9f..077dc156 100644 --- a/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -123,6 +123,17 @@ public void testInstaNovoCommandLines() throws Exception { null ); Assert.assertEquals(InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS, processBuilder.getPrimaryProgressUnits()); + + File windowsInstaNovoFolder = createFolder("instanovo-windows-process"); + File windowsExecutable = new File(windowsInstaNovoFolder, ".venv/Scripts/instanovo.exe"); + windowsExecutable.getParentFile().mkdirs(); + windowsExecutable.createNewFile(); + windowsExecutable.deleteOnExit(); + + Assert.assertEquals( + windowsExecutable.getAbsolutePath(), + InstaNovoProcessBuilder.getExecutable(windowsInstaNovoFolder).getAbsolutePath() + ); } /** From 1f4d9df04ca11f04c389cf306b1c8f5f2dca2238 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Tue, 23 Jun 2026 04:13:16 +0200 Subject: [PATCH 17/18] Add managed InstaNovo installation support --- .../java/eu/isas/searchgui/gui/SearchGUI.java | 122 ++++- .../isas/searchgui/util/InstaNovoSetup.java | 493 ++++++++++++++++++ 2 files changed, 611 insertions(+), 4 deletions(-) create mode 100644 src/main/java/eu/isas/searchgui/util/InstaNovoSetup.java diff --git a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java index 8ebfdd75..ebe64233 100644 --- a/src/main/java/eu/isas/searchgui/gui/SearchGUI.java +++ b/src/main/java/eu/isas/searchgui/gui/SearchGUI.java @@ -109,6 +109,7 @@ import eu.isas.searchgui.processbuilders.SageProcessBuilder; import eu.isas.searchgui.processbuilders.TandemProcessBuilder; import eu.isas.searchgui.processbuilders.TideSearchProcessBuilder; +import eu.isas.searchgui.util.InstaNovoSetup; import java.awt.Dimension; import java.lang.reflect.Field; import java.net.ConnectException; @@ -3047,9 +3048,9 @@ static HashMap> getInstaNovoModels(File instaNovoLocat return result; } - File modelsFile = new File(instaNovoLocation, "instanovo" + File.separator + "models.json"); + File modelsFile = InstaNovoSetup.getModelsFile(instaNovoLocation); - if (!modelsFile.exists()) { + if (modelsFile == null || !modelsFile.exists()) { return result; } @@ -3206,7 +3207,40 @@ private boolean ensureInstaNovoLocation(Advocate advocate) { return true; } - JFileChooser folderChooser = new JFileChooser(instaNovoLocation); + Object[] options = {"Install InstaNovo", "Select Existing Folder", "Cancel"}; + int option = JOptionPane.showOptionDialog( + this, + "InstaNovo is required for the selected workflow.\n\n" + + "SearchGUI can install InstaNovo " + InstaNovoSetup.INSTANOVO_VERSION + " in a local virtual environment,\n" + + "or you can select an existing InstaNovo checkout or installation folder.", + "Install or Select InstaNovo", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0] + ); + + if (option == 0) { + return installInstaNovo(InstaNovoSetup.getDefaultInstallationFolder(), advocate); + } else if (option != 1) { + return false; + } + + return selectInstaNovoLocation(advocate, instaNovoLocation); + } + + /** + * Selects an existing InstaNovo location. + * + * @param advocate the InstaNovo advocate variant + * @param currentLocation the current location + * + * @return true if a valid location was selected + */ + private boolean selectInstaNovoLocation(Advocate advocate, File currentLocation) { + + JFileChooser folderChooser = new JFileChooser(currentLocation); folderChooser.setDialogTitle("Select the InstaNovo Installation Folder"); folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); folderChooser.setMultiSelectionEnabled(false); @@ -3224,6 +3258,86 @@ private boolean ensureInstaNovoLocation(Advocate advocate) { } + /** + * Installs InstaNovo in a local virtual environment. + * + * @param installationFolder the installation folder + * @param advocate the InstaNovo advocate variant + * + * @return true if the installation succeeded + */ + private boolean installInstaNovo(File installationFolder, Advocate advocate) { + + int confirm = JOptionPane.showConfirmDialog( + this, + "Install InstaNovo " + InstaNovoSetup.INSTANOVO_VERSION + " into:\n" + + installationFolder.getAbsolutePath() + + "\n\n" + InstaNovoSetup.getInstallVariantDescription() + + "\n\nThis downloads uv if needed, a managed Python runtime, and Python dependencies.\n" + + "The InstaNovo model checkpoint will still be downloaded by InstaNovo on first use.", + "Install InstaNovo", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (confirm != JOptionPane.OK_OPTION) { + return false; + } + + final ProgressDialogX installerDialog = new ProgressDialogX( + this, + Toolkit.getDefaultToolkit().getImage(getClass().getResource("/icons/searchgui.gif")), + Toolkit.getDefaultToolkit().getImage(getClass().getResource("/icons/searchgui-orange.gif")), + true + ); + installerDialog.setPrimaryProgressCounterIndeterminate(true); + installerDialog.setTitle("Installing InstaNovo. Please Wait..."); + + final boolean[] success = new boolean[]{false}; + final Exception[] exception = new Exception[]{null}; + + Thread installerThread = new Thread("InstallInstaNovoThread") { + @Override + public void run() { + try { + InstaNovoSetup.install(installationFolder, installerDialog); + success[0] = true; + } catch (Exception e) { + exception[0] = e; + } finally { + installerDialog.setRunFinished(); + } + } + }; + + installerThread.start(); + installerDialog.setVisible(true); + + if (!success[0]) { + JOptionPane.showMessageDialog( + this, + "Could not install InstaNovo.\n\n" + (exception[0] == null ? "The installation was canceled." : exception[0].getMessage()), + "InstaNovo Installation", + JOptionPane.WARNING_MESSAGE + ); + return false; + } + + if (!InstaNovoProcessBuilder.getExecutable(installationFolder).exists()) { + JOptionPane.showMessageDialog( + this, + "InstaNovo was installed, but the executable could not be found in " + installationFolder + ".", + "InstaNovo Installation", + JOptionPane.WARNING_MESSAGE + ); + return false; + } + + searchHandler.setInstaNovoLocation(installationFolder); + + return validateSearchEngineInstallation(advocate, searchHandler.getInstaNovoLocation(), true); + } + /** * Clear the list of spectra. * @@ -7778,7 +7892,7 @@ public boolean validateSearchEngines(boolean showMessage) { instaNovoValid = validateSearchEngineInstallation( Advocate.instanovo, searchHandler.getInstaNovoLocation(), - showMessage + false ); } diff --git a/src/main/java/eu/isas/searchgui/util/InstaNovoSetup.java b/src/main/java/eu/isas/searchgui/util/InstaNovoSetup.java new file mode 100644 index 00000000..0119dd64 --- /dev/null +++ b/src/main/java/eu/isas/searchgui/util/InstaNovoSetup.java @@ -0,0 +1,493 @@ +package eu.isas.searchgui.util; + +import com.compomics.util.waiting.WaitingHandler; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; + +/** + * Helper methods for installing and locating InstaNovo. + * + * @author CompOmics + */ +public class InstaNovoSetup { + + /** + * The supported InstaNovo version. + */ + public static final String INSTANOVO_VERSION = "1.2.2"; + /** + * The CPU package specification. + */ + private static final String INSTANOVO_CPU_PACKAGE = "instanovo[cpu]==" + INSTANOVO_VERSION; + /** + * The CUDA package specification. + */ + private static final String INSTANOVO_CUDA_PACKAGE = "instanovo[cu126]==" + INSTANOVO_VERSION; + /** + * The PyTorch CPU wheel index. + */ + private static final String PYTORCH_CPU_INDEX = "https://download.pytorch.org/whl/cpu"; + /** + * The PyTorch CUDA 12.6 wheel index. + */ + private static final String PYTORCH_CUDA_INDEX = "https://download.pytorch.org/whl/cu126"; + + /** + * Returns the default shared CompOmics InstaNovo installation folder. + * + * @return the default installation folder + */ + public static File getDefaultInstallationFolder() { + + return new File(getCompOmicsDataFolder(), "tools" + File.separator + "instanovo" + File.separator + INSTANOVO_VERSION); + } + + /** + * Returns the platform-specific CompOmics data folder. + * + * @return the CompOmics data folder + */ + private static File getCompOmicsDataFolder() { + + String userHome = System.getProperty("user.home"); + + if (isWindows()) { + + String localAppData = System.getenv("LOCALAPPDATA"); + + if (localAppData != null && !localAppData.trim().isEmpty()) { + return new File(localAppData, "CompOmics"); + } + + return new File(userHome, "AppData" + File.separator + "Local" + File.separator + "CompOmics"); + } + + if (isMacOs()) { + return new File(userHome, "Library" + File.separator + "Application Support" + File.separator + "CompOmics"); + } + + String xdgDataHome = System.getenv("XDG_DATA_HOME"); + + if (xdgDataHome != null && !xdgDataHome.trim().isEmpty() && new File(xdgDataHome).isAbsolute()) { + return new File(xdgDataHome, "compomics"); + } + + return new File(userHome, ".local" + File.separator + "share" + File.separator + "compomics"); + } + + /** + * Installs uv if needed and creates an InstaNovo virtual environment. + * + * @param installationFolder the installation folder + * @param waitingHandler the waiting handler + * + * @throws IOException thrown if a command fails + * @throws InterruptedException thrown if interrupted + */ + public static void install(File installationFolder, WaitingHandler waitingHandler) throws IOException, InterruptedException { + + if (!installationFolder.exists() && !installationFolder.mkdirs()) { + throw new IOException("Could not create " + installationFolder + "."); + } + + File uvExecutable = ensureUv(waitingHandler); + boolean cudaInstall = isCudaGpuDetected(); + String instaNovoPackage = cudaInstall ? INSTANOVO_CUDA_PACKAGE : INSTANOVO_CPU_PACKAGE; + String pytorchIndex = cudaInstall ? PYTORCH_CUDA_INDEX : PYTORCH_CPU_INDEX; + + append(waitingHandler, "Creating the InstaNovo Python environment in " + installationFolder + "."); + runCommand( + Arrays.asList(uvExecutable.getAbsolutePath(), "venv", "--python", "3.12", "--clear"), + installationFolder, + waitingHandler + ); + + append(waitingHandler, "Installing " + instaNovoPackage + (cudaInstall ? " with CUDA PyTorch wheels." : " with CPU PyTorch wheels.")); + runCommand( + Arrays.asList( + uvExecutable.getAbsolutePath(), + "pip", + "install", + instaNovoPackage, + "--extra-index-url", + pytorchIndex, + "--index-strategy", + "unsafe-best-match" + ), + installationFolder, + waitingHandler + ); + } + + /** + * Returns a description of the install variant selected for this machine. + * + * @return the install variant description + */ + public static String getInstallVariantDescription() { + + if (isCudaGpuDetected()) { + return "GPU installation: InstaNovo " + INSTANOVO_VERSION + " with CUDA 12.6 PyTorch wheels."; + } + + if (isAppleSiliconMac()) { + return "macOS installation: InstaNovo " + INSTANOVO_VERSION + " with PyTorch wheels that can use Apple Silicon MPS when available."; + } + + if (isMacOs()) { + return "CPU fallback installation: CUDA is not available on macOS and no Apple Silicon GPU was detected. Prediction will be very slow."; + } + + return "CPU fallback installation: no NVIDIA/CUDA GPU was detected. Prediction will be very slow."; + } + + /** + * Returns true if an NVIDIA/CUDA-capable GPU is detected. + * + * @return true if a CUDA GPU is detected + */ + public static boolean isCudaGpuDetected() { + + if (isMacOs()) { + return false; + } + + String cudaPath = System.getenv("CUDA_PATH"); + String cudaHome = System.getenv("CUDA_HOME"); + + return commandSucceeds("nvidia-smi") + || new File("/proc/driver/nvidia/version").exists() + || findOnPath("nvcc") != null + || new File("/usr/local/cuda/bin/nvcc").exists() + || new File("/usr/local/cuda-12.6/bin/nvcc").exists() + || cudaPath != null && new File(cudaPath, "bin" + File.separator + "nvcc.exe").exists() + || cudaHome != null && new File(cudaHome, "bin" + File.separator + "nvcc.exe").exists() + || new File("C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA").exists() + || new File("C:\\Windows\\System32\\nvidia-smi.exe").exists(); + } + + /** + * Returns the InstaNovo models file from either a checkout root or a virtual + * environment installation. + * + * @param instaNovoLocation the InstaNovo location + * + * @return the models file, or null if not found + */ + public static File getModelsFile(File instaNovoLocation) { + + if (instaNovoLocation == null) { + return null; + } + + ArrayList candidates = new ArrayList(); + candidates.add(new File(instaNovoLocation, "instanovo" + File.separator + "models.json")); + candidates.add(new File(instaNovoLocation, ".venv" + File.separator + "Lib" + File.separator + "site-packages" + File.separator + "instanovo" + File.separator + "models.json")); + + File unixSitePackages = new File(instaNovoLocation, ".venv" + File.separator + "lib"); + + if (unixSitePackages.exists()) { + + File[] pythonFolders = unixSitePackages.listFiles(); + + if (pythonFolders != null) { + for (File pythonFolder : pythonFolders) { + candidates.add(new File(pythonFolder, "site-packages" + File.separator + "instanovo" + File.separator + "models.json")); + } + } + } + + for (File candidate : candidates) { + if (candidate.exists()) { + return candidate; + } + } + + return null; + } + + /** + * Ensures that uv is available. + * + * @param waitingHandler the waiting handler + * + * @return the uv executable + * + * @throws IOException thrown if uv cannot be installed + * @throws InterruptedException thrown if interrupted + */ + private static File ensureUv(WaitingHandler waitingHandler) throws IOException, InterruptedException { + + File uvExecutable = findUvExecutable(); + + if (uvExecutable != null) { + append(waitingHandler, "Using uv at " + uvExecutable + "."); + return uvExecutable; + } + + append(waitingHandler, "uv was not found. Installing uv with the official standalone installer."); + runCommand(getUvInstallCommand(), null, waitingHandler); + + uvExecutable = findUvExecutable(); + + if (uvExecutable == null) { + throw new IOException("uv was installed, but the uv executable could not be found. Restart the application or install uv manually."); + } + + return uvExecutable; + } + + /** + * Finds uv on PATH or in the default standalone installer location. + * + * @return the uv executable, or null if not found + */ + private static File findUvExecutable() { + + String executableName = isWindows() ? "uv.exe" : "uv"; + File pathExecutable = findOnPath(executableName); + + if (pathExecutable != null) { + return pathExecutable; + } + + String userHome = System.getProperty("user.home"); + String localAppData = System.getenv("LOCALAPPDATA"); + + ArrayList candidates = new ArrayList(); + candidates.add(new File(userHome, ".local" + File.separator + "bin" + File.separator + executableName)); + + if (localAppData != null) { + candidates.add(new File(localAppData, "uv" + File.separator + "uv.exe")); + candidates.add(new File(localAppData, "Programs" + File.separator + "uv" + File.separator + "uv.exe")); + } + + for (File candidate : candidates) { + if (candidate.exists()) { + candidate.setExecutable(true); + return candidate; + } + } + + return null; + } + + /** + * Finds an executable on PATH. + * + * @param executableName the executable name + * + * @return the executable, or null if not found + */ + private static File findOnPath(String executableName) { + + String path = System.getenv("PATH"); + + if (path == null) { + return null; + } + + String[] pathEntries = path.split(File.pathSeparator); + + for (String pathEntry : pathEntries) { + File candidate = new File(pathEntry, executableName); + if (candidate.exists()) { + candidate.setExecutable(true); + return candidate; + } + } + + return null; + } + + /** + * Returns the uv standalone installer command. + * + * @return the command + */ + private static List getUvInstallCommand() { + + if (isWindows()) { + return Arrays.asList( + "powershell", + "-ExecutionPolicy", + "ByPass", + "-c", + "$env:UV_NO_MODIFY_PATH='1'; irm https://astral.sh/uv/install.ps1 | iex" + ); + } + + return Arrays.asList( + "sh", + "-c", + "if command -v curl >/dev/null 2>&1; then curl -LsSf https://astral.sh/uv/install.sh | env UV_NO_MODIFY_PATH=1 sh; " + + "elif command -v wget >/dev/null 2>&1; then wget -qO- https://astral.sh/uv/install.sh | env UV_NO_MODIFY_PATH=1 sh; " + + "else echo 'Installing uv requires curl or wget.' >&2; exit 1; fi" + ); + } + + /** + * Runs a command. + * + * @param command the command + * @param workingDirectory the working directory + * @param waitingHandler the waiting handler + * + * @throws IOException thrown if the command fails + * @throws InterruptedException thrown if interrupted + */ + private static void runCommand(List command, File workingDirectory, WaitingHandler waitingHandler) throws IOException, InterruptedException { + + append(waitingHandler, "Running: " + join(command)); + + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.redirectErrorStream(true); + + if (workingDirectory != null) { + processBuilder.directory(workingDirectory); + } + + Process process = processBuilder.start(); + + Scanner scanner = new Scanner(process.getInputStream()); + LinkedList recentOutput = new LinkedList(); + + while (scanner.hasNextLine()) { + + if (waitingHandler != null && waitingHandler.isRunCanceled()) { + process.destroy(); + throw new InterruptedException("InstaNovo installation canceled."); + } + + String line = scanner.nextLine(); + recentOutput.add(line); + + while (recentOutput.size() > 20) { + recentOutput.removeFirst(); + } + + append(waitingHandler, line); + } + + int exitCode = process.waitFor(); + + if (exitCode != 0) { + throw new IOException("Command exited with status " + exitCode + ": " + join(command) + getRecentOutput(recentOutput)); + } + } + + /** + * Returns the recent command output for error reporting. + * + * @param recentOutput the recent output lines + * + * @return the recent command output + */ + private static String getRecentOutput(LinkedList recentOutput) { + + if (recentOutput.isEmpty()) { + return ""; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("\n\nLast command output:"); + + for (String line : recentOutput) { + stringBuilder.append("\n").append(line); + } + + return stringBuilder.toString(); + } + + /** + * Appends text to the waiting handler. + * + * @param waitingHandler the waiting handler + * @param text the text + */ + private static void append(WaitingHandler waitingHandler, String text) { + + if (waitingHandler != null) { + if (waitingHandler.isReport()) { + waitingHandler.appendReport(text, true, true); + } else { + waitingHandler.setWaitingText(text); + } + } + } + + /** + * Joins a command line for display. + * + * @param command the command + * + * @return the display string + */ + private static String join(List command) { + + StringBuilder stringBuilder = new StringBuilder(); + + for (String part : command) { + + if (stringBuilder.length() > 0) { + stringBuilder.append(' '); + } + + stringBuilder.append(part); + } + + return stringBuilder.toString(); + } + + /** + * Returns true on Windows. + * + * @return true on Windows + */ + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + /** + * Returns true on macOS. + * + * @return true on macOS + */ + private static boolean isMacOs() { + return System.getProperty("os.name").toLowerCase().contains("mac"); + } + + /** + * Returns true on Apple Silicon macOS. + * + * @return true on Apple Silicon macOS + */ + private static boolean isAppleSiliconMac() { + + String architecture = System.getProperty("os.arch").toLowerCase(); + + return isMacOs() && (architecture.contains("aarch64") || architecture.contains("arm64")); + } + + /** + * Returns true if running a command succeeds. + * + * @param command the command + * + * @return true if the command succeeds + */ + private static boolean commandSucceeds(String command) { + + try { + Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); + return process.waitFor() == 0; + } catch (Exception e) { + return false; + } + } +} From cc64d4f122688b389961ccf0618d9d623392e3d4 Mon Sep 17 00:00:00 2001 From: Jeroen Van Goey Date: Tue, 23 Jun 2026 04:13:16 +0200 Subject: [PATCH 18/18] Avoid double-reading process output streams --- .../SearchGUIProcessBuilder.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 62d42391..47bccf9c 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -127,20 +127,16 @@ public void startProcess() throws IOException { } - // get inputstream from process - InputStream inputStream = p.getInputStream(); - - try { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { if (isInstaNovoProcess()) { handleInstaNovoOutput(bufferedReader); - } else if (getType().equalsIgnoreCase("Comet")) { - - Scanner scanner = new Scanner(inputStream); - scanner.useDelimiter("\n|\b "); + } else if (getType().equalsIgnoreCase("Comet")) { + + Scanner scanner = new Scanner(bufferedReader); + scanner.useDelimiter("\n|\b "); String lastString = ""; // get input from scanner, send to std out and text box @@ -198,7 +194,7 @@ public void startProcess() throws IOException { } } else if (getType().equalsIgnoreCase("ThermoRawFileParser")) { - Scanner scanner = new Scanner(inputStream); + Scanner scanner = new Scanner(bufferedReader); scanner.useDelimiter("\\s|\\n"); waitingHandler.setSecondaryProgressCounterIndeterminate(false); @@ -226,7 +222,7 @@ public void startProcess() throws IOException { } else if (getType().equalsIgnoreCase("MetaMorpheus")) { - Scanner scanner = new Scanner(inputStream); + Scanner scanner = new Scanner(bufferedReader); scanner.useDelimiter("\\s|\\n"); waitingHandler.setSecondaryProgressCounterIndeterminate(false); @@ -322,9 +318,7 @@ public void startProcess() throws IOException { } } - inputStream.close(); - bufferedReader.close(); - } finally { + } finally { // check if the user has cancelled the process or not if (waitingHandler.isRunCanceled()) {