diff --git a/README.md b/README.md index 6f6d9498..f80d0a3b 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`. 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/resources/conf/searchGUI_configuration.txt b/resources/conf/searchGUI_configuration.txt index 3faea0ec..01796ddf 100644 --- a/resources/conf/searchGUI_configuration.txt +++ b/resources/conf/searchGUI_configuration.txt @@ -31,6 +31,13 @@ false Novor Location: Not Selected false +InstaNovo Location: +Not Selected +false +InstaNovo+ Enabled: +false +InstaNovo Refinement Enabled: +false DirecTag Location: Not Selected false @@ -38,4 +45,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..91649f9d 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; @@ -141,6 +143,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 +244,10 @@ public class SearchHandler { * The Novor location. */ private File novorLocation = null; + /** + * The InstaNovo location. + */ + private File instaNovoLocation = null; /** * The DirecTag location. */ @@ -310,6 +328,10 @@ public class SearchHandler { * The Novor process. */ private NovorProcessBuilder novorProcessBuilder = null; + /** + * The InstaNovo process. + */ + private InstaNovoProcessBuilder instaNovoProcessBuilder = null; /** * The DirecTag process. */ @@ -557,6 +579,19 @@ public SearchHandler( false ); + enableInstaNovo = loadSearchEngineLocation( + Advocate.instanovo, + true, + true, + true, + true, + false, + false, + false + ); + enableInstaNovoPlus = loadBooleanConfigurationValue("InstaNovo+ Enabled:", false); + enableInstaNovoRefine = loadBooleanConfigurationValue("InstaNovo Refinement Enabled:", false); + enableDirecTag = loadSearchEngineLocation( Advocate.direcTag, false, @@ -651,6 +686,9 @@ public SearchHandler( boolean runMetaMorpheus, boolean runSage, boolean runNovor, + boolean runInstaNovo, + boolean runInstaNovoPlus, + boolean runInstaNovoRefine, boolean runDirecTag, File omssaFolder, File xTandemFolder, @@ -664,6 +702,7 @@ public SearchHandler( File metaMorpheusFolder, File sageFolder, File novorFolder, + File instaNovoFolder, File direcTagFolder, File makeblastdbFolder, ProcessingParameters processingParameters @@ -691,6 +730,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 +906,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 +1440,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; } @@ -1395,6 +1454,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. * @@ -1523,6 +1621,63 @@ 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 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. * @@ -1871,6 +2026,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 +2044,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 +2224,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 +2350,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 +2737,15 @@ protected Object doInBackground() { if (enableNovor) { nProgress += nFilesToSearch; } + if (enableInstaNovo) { + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; + } + if (enableInstaNovoPlus) { + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; + } + if (enableInstaNovoRefine) { + nProgress += nFilesToSearch * InstaNovoProcessBuilder.PRIMARY_PROGRESS_UNITS; + } if (enableDirecTag) { nProgress += nFilesToSearch; } @@ -2725,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. @@ -3794,6 +4032,114 @@ 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, + getInstaNovoParameters(), + 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 + ); + completeProcessPrimaryProgress(instaNovoProcessBuilder); + } + } + + // Run standalone InstaNovo+ + if (enableInstaNovoPlus && !waitingHandler.isRunCanceled()) { + + File instaNovoPlusOutputFile = new File(outputTempFolder, getInstaNovoPlusFileName(spectrumFileName)); + + instaNovoProcessBuilder = new InstaNovoProcessBuilder( + instaNovoLocation, + spectrumFile, + instaNovoPlusOutputFile, + InstaNovoProcessBuilder.Mode.diffusion, + getInstaNovoPlusParameters(), + 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 + ); + completeProcessPrimaryProgress(instaNovoProcessBuilder); + } + } + + // 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, + getInstaNovoParameters(), + 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.instanovoRefined, + instaNovoRefinedOutputFile, + spectrumFile + ); + completeProcessPrimaryProgress(instaNovoProcessBuilder); + } + } + // Run DirecTag if (enableDirecTag && !waitingHandler.isRunCanceled()) { @@ -4207,6 +4553,78 @@ 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) { + + 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 + ); + + } + + } + + 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()) { @@ -4440,6 +4858,59 @@ 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 + ); + } + } + + /** + * 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. * @@ -4452,7 +4923,9 @@ 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")); + if (fastaFile != null) { + bw.write(fastaFile.getAbsolutePath() + System.getProperty("line.separator")); + } // add the ms files for (File spectrumFile : msFiles) { @@ -4978,7 +5451,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()) { @@ -5046,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()) { @@ -5326,7 +5803,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/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..d35c9c60 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. @@ -226,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); @@ -299,13 +317,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 +383,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 +645,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 +789,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. * @@ -848,16 +918,20 @@ public static boolean isValidStartup(CommandLine aLine) throws IOException { return false; } } - } - - // 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; } } @@ -1005,21 +1079,44 @@ 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)) { - return false; - } - } - - // check the search engine folders - if (aLine.hasOption(SearchCLIParams.OMSSA_LOCATION.id)) { + 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; + } + } + + 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()) { @@ -1102,17 +1199,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; @@ -1165,12 +1270,65 @@ 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 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. + * + * @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 d2802ead..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), @@ -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), @@ -123,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"; @@ -138,9 +144,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 +161,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..ebe64233 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.*; @@ -75,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; @@ -88,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; @@ -103,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; @@ -214,6 +221,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. @@ -393,6 +404,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? @@ -714,6 +728,21 @@ 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(); + 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(); @@ -1750,6 +1779,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + initInstaNovoGuiComponents(); + javax.swing.GroupLayout searchEnginesPanelLayout = new javax.swing.GroupLayout(searchEnginesPanel); searchEnginesPanel.setLayout(searchEnginesPanelLayout); searchEnginesPanelLayout.setHorizontalGroup( @@ -1828,31 +1859,46 @@ 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)) .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, sageButton, 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) @@ -1938,12 +1984,33 @@ 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) + .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(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(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); @@ -2635,6 +2702,642 @@ 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 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) { + 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); + + 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); + } + }); + + } + + /** + * 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 description label. + * + * @param label the label + * @param description the description text + */ + private void configureInstaNovoDescriptionLabel(javax.swing.JLabel label, String description) { + label.setFont(new java.awt.Font("Segoe UI", 0, 12)); // NOI18N + 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)); + } + }); + } + + /** + * 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)); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + } + }); + } + + /** + * 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()); + + 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)) { + + 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"}); + 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(instaNovoModelCmb); + } + + if (showInstaNovoPlusModel) { + panel.add(new JLabel("InstaNovo+ model")); + panel.add(instaNovoPlusModelCmb); + } + + 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")); + 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 && instaNovoModelCmb.getSelectedItem() == null) { + JOptionPane.showMessageDialog(this, "The InstaNovo model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); + return; + } + + if (showInstaNovoPlusModel && instaNovoPlusModelCmb.getSelectedItem() == null) { + JOptionPane.showMessageDialog(this, "The InstaNovo+ model cannot be empty.", "Input Error", JOptionPane.WARNING_MESSAGE); + return; + } + + if (showInstaNovoModel) { + instaNovoParameters.setInstaNovoModel(instaNovoModelCmb.getSelectedItem().toString()); + } + + if (showInstaNovoPlusModel) { + instaNovoParameters.setInstaNovoPlusModel(instaNovoPlusModelCmb.getSelectedItem().toString()); + } + + 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); + + } + } + + /** + * Creates a model combo box populated from the InstaNovo models file. + * + * @param models the available models + * @param selectedModel the selected model + * + * @return the model combo box + */ + private JComboBox createInstaNovoModelComboBox(ArrayList models, String selectedModel) { + + if (models == null) { + models = new ArrayList<>(); + } + + 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 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) { + + HashMap> models = getInstaNovoModels(instaNovoLocation); + ArrayList result = models.get(modelType); + + return result == null ? new ArrayList<>() : result; + } + + /** + * Returns the available InstaNovo models. + * + * @param instaNovoLocation the InstaNovo installation folder + * + * @return the available model ids indexed by model type + */ + static HashMap> getInstaNovoModels(File instaNovoLocation) { + + HashMap> result = new HashMap<>(); + + if (instaNovoLocation == null) { + return result; + } + + File modelsFile = InstaNovoSetup.getModelsFile(instaNovoLocation); + + if (modelsFile == null || !modelsFile.exists()) { + return result; + } + + try (FileReader reader = new FileReader(modelsFile)) { + + JsonObject modelsJson = JsonParser.parseReader(reader).getAsJsonObject(); + + 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()); + } + + result.put(modelTypeEntry.getKey(), models); + } + } + + } catch (Exception e) { + result.clear(); + } + + return result; + } + + /** + * 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. + * + * @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; + } + + 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); + + int result = folderChooser.showOpenDialog(this); + + if (result == JFileChooser.APPROVE_OPTION) { + + searchHandler.setInstaNovoLocation(folderChooser.getSelectedFile()); + return validateSearchEngineInstallation(advocate, searchHandler.getInstaNovoLocation(), true); + + } + + return false; + + } + + /** + * 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. * @@ -6753,6 +7456,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; @@ -6767,6 +7473,18 @@ 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 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; private javax.swing.JPopupMenu.Separator jSeparator16; @@ -7065,6 +7783,7 @@ public boolean validateSearchEngines(boolean showMessage) { boolean metaMorpheusValid = true; boolean sageValid = true; boolean novorValid = true; + boolean instaNovoValid = true; boolean direcTagValid = true; if (enableOmssaJCheckBox.isSelected()) { @@ -7165,6 +7884,17 @@ public boolean validateSearchEngines(boolean showMessage) { showMessage ); + } + if (enableInstaNovoJCheckBox.isSelected() + || enableInstaNovoPlusJCheckBox.isSelected() + || enableInstaNovoRefineJCheckBox.isSelected()) { + + instaNovoValid = validateSearchEngineInstallation( + Advocate.instanovo, + searchHandler.getInstaNovoLocation(), + false + ); + } if (enableDirecTagJCheckBox.isSelected()) { @@ -7176,17 +7906,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; } /** @@ -7209,6 +7945,9 @@ private boolean validateInput(boolean showMessage) { && !enableMetaMorpheusJCheckBox.isSelected() && !enableSageJCheckBox.isSelected() && !enableNovorJCheckBox.isSelected() + && !enableInstaNovoJCheckBox.isSelected() + && !enableInstaNovoPlusJCheckBox.isSelected() + && !enableInstaNovoRefineJCheckBox.isSelected() && !enableDirecTagJCheckBox.isSelected()) { if (showMessage && valid) { @@ -7254,7 +7993,12 @@ private boolean validateInput(boolean showMessage) { } - if (databaseFileTxt.getText() == null || databaseFileTxt.getText().trim().equals("")) { + // 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) { @@ -7272,7 +8016,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()); @@ -7294,6 +8038,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 @@ -7361,6 +8109,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. * @@ -7908,6 +8675,12 @@ 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("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")); @@ -7969,6 +8742,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); @@ -7980,6 +8810,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); @@ -7993,6 +8826,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); @@ -8271,6 +9107,28 @@ public static boolean validateSearchEngineInstallation( feedBackInDialog ); + } else if (advocate == Advocate.instanovo || advocate == Advocate.instanovoPlus || advocate == Advocate.instanovoRefined) { + + if (searchEngineLocation != null) { + + File executable = InstaNovoProcessBuilder.getExecutable(searchEngineLocation); + + if (executable.exists()) { + return true; + } + } + + return validateSearchEngineInstallation( + advocate, + InstaNovoProcessBuilder.EXECUTABLE_FILE_NAME, + "version", + null, + searchEngineLocation, + null, + false, + feedBackInDialog + ); + } else if (advocate == Advocate.direcTag) { return validateSearchEngineInstallation( @@ -8872,6 +9730,21 @@ 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); + instaNovoSettingsButton.setEnabled(enable); + instaNovoPlusSettingsButton.setEnabled(enable); + instaNovoRefineSettingsButton.setEnabled(enable); + instaNovoLinkLabel.setEnabled(enable); + instaNovoPlusLinkLabel.setEnabled(enable); + instaNovoRefineLinkLabel.setEnabled(enable); // peptideshaker peptideShakerSettingsButton.setEnabled(enable); 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..b087004e --- /dev/null +++ b/src/main/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilder.java @@ -0,0 +1,210 @@ +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"; + /** + * The number of primary progress units allocated to one InstaNovo + * prediction run. + */ + public static final int PRIMARY_PROGRESS_UNITS = 200; + + /** + * 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 instaNovoParameters the InstaNovo parameters + * @param waitingHandler the waiting handler + * @param exceptionHandler the exception handler + */ + public InstaNovoProcessBuilder( + File instaNovoFolder, + File spectrumFile, + File outputFile, + Mode mode, + InstaNovoParameters instaNovoParameters, + WaitingHandler waitingHandler, + ExceptionHandler exceptionHandler + ) { + + this.spectrumFile = spectrumFile; + this.outputFile = outputFile; + this.mode = mode; + this.waitingHandler = waitingHandler; + this.exceptionHandler = exceptionHandler; + this.primaryProgressUnits = PRIMARY_PROGRESS_UNITS; + + if (instaNovoParameters == null) { + instaNovoParameters = new InstaNovoParameters(); + } + + 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.getInstaNovoModel()); + } + + if (mode == Mode.diffusion || mode == Mode.refined) { + process_name_array.add("--instanovo-plus-model"); + process_name_array.add(instaNovoParameters.getInstaNovoPlusModel()); + } + + if (mode == Mode.diffusion) { + process_name_array.add("--no-refinement"); + } else if (mode == Mode.refined) { + 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.add("log_interval=1"); + + 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 + */ + public static File getExecutable(File instaNovoFolder) { + + if (instaNovoFolder != null) { + + 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; + } + } + } + + 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/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"); diff --git a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java index 888dc220..47bccf9c 100644 --- a/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java +++ b/src/main/java/eu/isas/searchgui/processbuilders/SearchGUIProcessBuilder.java @@ -9,6 +9,9 @@ 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; /** * A simple ancestor class to reduce code duplication in formatdb, omssacl and @@ -40,6 +43,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; } } 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()) { 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; + } + } +} diff --git a/src/main/resources/helpFiles/SearchGUI.html b/src/main/resources/helpFiles/SearchGUI.html index 2f868696..440cba4a 100644 --- a/src/main/resources/helpFiles/SearchGUI.html +++ b/src/main/resources/helpFiles/SearchGUI.html @@ -11,7 +11,24 @@

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. + +

+ 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. + SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; + 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. +

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..853a2adc --- /dev/null +++ b/src/test/java/eu/isas/searchgui/cmd/SearchCLIInstaNovoTest.java @@ -0,0 +1,183 @@ +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; +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()); + + 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()); + } + + /** + * 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)); + + CommandLine databaseSearchWithoutFasta = parse( + "-spectrum_files", createFile("input", ".mgf").getAbsolutePath(), + "-output_folder", createFolder("searchgui-output").getAbsolutePath(), + "-msgf", "1" + ); + 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")); + 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")); + } + + /** + * 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. + * + * @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/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/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java new file mode 100644 index 00000000..077dc156 --- /dev/null +++ b/src/test/java/eu/isas/searchgui/processbuilders/InstaNovoProcessBuilderTest.java @@ -0,0 +1,378 @@ +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; +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"); + + InstaNovoParameters defaultParameters = new InstaNovoParameters(); + + assertCommand( + new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, InstaNovoProcessBuilder.Mode.transformer, defaultParameters, 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, defaultParameters, 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, defaultParameters, null, null).getCommand(), + instaNovoFolder, + spectrumFile, + outputFile, + "InstaNovo with InstaNovo+ refinement", + "predict", + null, + "--instanovo-model", + InstaNovoParameters.DEFAULT_INSTANOVO_MODEL, + "--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")); + 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, + outputFile, + InstaNovoProcessBuilder.Mode.transformer, + defaultParameters, + null, + 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() + ); + } + + /** + * 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...")); + } + + /** + * 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. + * + * @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.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")); + + Assert.assertEquals(type, new InstaNovoProcessBuilder(instaNovoFolder, spectrumFile, outputFile, mode, new InstaNovoParameters(), 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; + } + + /** + * 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 2f868696..440cba4a 100644 --- a/src/test/resources/helpFiles/SearchGUI.html +++ b/src/test/resources/helpFiles/SearchGUI.html @@ -11,7 +11,24 @@

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. + +

+ 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. + SearchGUI uses a desktop-oriented InstaNovo batch size of 16 by default; + 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. +

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