diff --git a/pom.xml b/pom.xml index e314a338..0843f8d8 100644 --- a/pom.xml +++ b/pom.xml @@ -95,72 +95,9 @@ META-INF/*.RSA - - it.unimi.dsi:fastutil - - - - it/unimi/dsi/fastutil/booleans/BooleanArrays** - it/unimi/dsi/fastutil/booleans/BooleanComparator** - - it/unimi/dsi/fastutil/chars/AbstractCharBidirectionalIterator** - it/unimi/dsi/fastutil/chars/AbstractCharCollection** - it/unimi/dsi/fastutil/chars/AbstractCharIterator** - it/unimi/dsi/fastutil/chars/AbstractCharList** - it/unimi/dsi/fastutil/chars/AbstractCharListIterator** - it/unimi/dsi/fastutil/chars/AbstractCharSet** - it/unimi/dsi/fastutil/chars/CharArrayList** - it/unimi/dsi/fastutil/chars/CharArrays** - it/unimi/dsi/fastutil/chars/CharBidirectionalIterator** - it/unimi/dsi/fastutil/chars/CharCollection** - it/unimi/dsi/fastutil/chars/CharComparator** - it/unimi/dsi/fastutil/chars/CharIterable** - it/unimi/dsi/fastutil/chars/CharIterator** - it/unimi/dsi/fastutil/chars/CharIterators** - it/unimi/dsi/fastutil/chars/CharList** - it/unimi/dsi/fastutil/chars/CharListIterator** - it/unimi/dsi/fastutil/chars/CharOpenHashSet** - it/unimi/dsi/fastutil/chars/CharSet** - it/unimi/dsi/fastutil/chars/CharStack** - - it/unimi/dsi/fastutil/ints/IntArrays** - it/unimi/dsi/fastutil/ints/IntComparator** - - it/unimi/dsi/fastutil/objects/AbstractObjectBidirectionalIterator** - it/unimi/dsi/fastutil/objects/AbstractObjectCollection** - it/unimi/dsi/fastutil/objects/AbstractObjectIterator** - it/unimi/dsi/fastutil/objects/AbstractObjectList** - it/unimi/dsi/fastutil/objects/AbstractObjectListIterator** - it/unimi/dsi/fastutil/objects/ObjectArrayList** - it/unimi/dsi/fastutil/objects/ObjectArrays** - it/unimi/dsi/fastutil/objects/ObjectBidirectionalIterator** - it/unimi/dsi/fastutil/objects/ObjectCollection** - it/unimi/dsi/fastutil/objects/ObjectIterable** - it/unimi/dsi/fastutil/objects/ObjectIterator** - it/unimi/dsi/fastutil/objects/ObjectIterators** - it/unimi/dsi/fastutil/objects/ObjectList** - it/unimi/dsi/fastutil/objects/ObjectListIterator** - - it/unimi/dsi/fastutil/Arrays** - it/unimi/dsi/fastutil/BidirectionalIterator** - it/unimi/dsi/fastutil/Hash** - it/unimi/dsi/fastutil/HashCommon** - it/unimi/dsi/fastutil/Stack** - it/unimi/dsi/fastutil/Swapper** - - - it/unimi/dsi/fastutil/chars/AbstractChar2** - it/unimi/dsi/fastutil/chars/Char2** - it/unimi/dsi/fastutil/ints/IntComparators** - it/unimi/dsi/fastutil/objects/ObjectCollections** - it/unimi/dsi/fastutil/objects/ObjectLists** - it/unimi/dsi/fastutil/chars/CharCollections** - it/unimi/dsi/fastutil/chars/CharComparators** - it/unimi/dsi/fastutil/chars/CharLists** - it/unimi/dsi/fastutil/chars/CharSets** - - + *:* diff --git a/src/main/java/edu/ucsd/msjava/msdbsearch/SearchParams.java b/src/main/java/edu/ucsd/msjava/msdbsearch/SearchParams.java index 55982a06..5b9febbe 100644 --- a/src/main/java/edu/ucsd/msjava/msdbsearch/SearchParams.java +++ b/src/main/java/edu/ucsd/msjava/msdbsearch/SearchParams.java @@ -51,6 +51,8 @@ public class SearchParams { private int maxMissedCleavages; private int maxNumMods; private boolean allowDenseCentroidedPeaks; + private int minMSLevel; + private int maxMSLevel; public SearchParams() { } @@ -220,6 +222,16 @@ public boolean getAllowDenseCentroidedPeaks() { return allowDenseCentroidedPeaks; } + // Used by MS-GF+ + public int getMinMSLevel() { + return minMSLevel; + } + + // Used by MS-GF+ + public int getMaxMSLevel() { + return maxMSLevel; + } + /** * Look for # in dataLine * If present, remove that character and any comment after it @@ -410,6 +422,10 @@ public String parse(ParamManager paramManager) { allowDenseCentroidedPeaks = paramManager.getAllowDenseCentroidedPeaks() == 1; + IntRangeParameter msLevelParam = paramManager.getMSLevelParameter(); + minMSLevel = msLevelParam.getMin(); + maxMSLevel = msLevelParam.getMax(); + maxNumMods = paramManager.getMaxNumModsPerPeptide(); int maxNumModsCompare = aaSet.getMaxNumberOfVariableModificationsPerPeptide(); @@ -594,6 +610,7 @@ public String toString() { buf.append(" (custom)\n"); } + buf.append("\tMSLevel: " + this.minMSLevel + "," + this.maxMSLevel + "\n"); buf.append("\tMinNumPeaksPerSpectrum: " + this.minNumPeaksPerSpectrum + "\n"); buf.append("\tNumIsoforms: " + this.maxNumVariantsPerPeptide + "\n"); diff --git a/src/main/java/edu/ucsd/msjava/msutil/SpecKey.java b/src/main/java/edu/ucsd/msjava/msutil/SpecKey.java index 796b3af9..308759eb 100644 --- a/src/main/java/edu/ucsd/msjava/msutil/SpecKey.java +++ b/src/main/java/edu/ucsd/msjava/msutil/SpecKey.java @@ -68,7 +68,9 @@ public static ArrayList getSpecKeyList( int maxCharge, ActivationMethod activationMethod, int minNumPeaksPerSpectrum, - boolean allowDenseCentroidedData) { + boolean allowDenseCentroidedData, + int minMSLevel, + int maxMSLevel) { Iterator itr = specAcc.getSpecItr(); @@ -80,7 +82,9 @@ public static ArrayList getSpecKeyList( maxCharge, activationMethod, minNumPeaksPerSpectrum, - allowDenseCentroidedData); + allowDenseCentroidedData, + minMSLevel, + maxMSLevel); SpectrumParser parser = specAcc.getSpectrumParser(); @@ -104,7 +108,9 @@ public static ArrayList getSpecKeyList( int maxCharge, ActivationMethod activationMethod, int minNumPeaksPerSpectrum, - boolean allowDenseCentroidedData) { + boolean allowDenseCentroidedData, + int minMSLevel, + int maxMSLevel) { if (activationMethod == ActivationMethod.FUSION) return getFusedSpecKeyList(itr, startSpecIndex, endSpecIndex, minCharge, maxCharge); @@ -114,6 +120,7 @@ public static ArrayList getSpecKeyList( int numProfileSpectra = 0; int numDenseCentroidedSpectra = 0; int numSpectraWithTooFewPeaks = 0; + int numFilteredByMSLevel = 0; final int MAX_INFORMATIVE_MESSAGES = 10; int informativeMessageCount = 0; @@ -126,6 +133,11 @@ public static ArrayList getSpecKeyList( if (specIndex >= endSpecIndex) continue; + if (spec.getMSLevel() < minMSLevel || spec.getMSLevel() > maxMSLevel) { + numFilteredByMSLevel++; + continue; + } + spec.setChargeIfSinglyCharged(); int charge = spec.getCharge(); ActivationMethod specActivationMethod = spec.getActivationMethod(); @@ -217,6 +229,9 @@ public static ArrayList getSpecKeyList( } System.out.println("Ignoring " + numProfileSpectra + " profile spectra."); + if (numFilteredByMSLevel > 0) { + System.out.println("Ignoring " + numFilteredByMSLevel + " spectra with MS level outside range [" + minMSLevel + "," + maxMSLevel + "]."); + } System.out.println("Ignoring " + numSpectraWithTooFewPeaks + " spectra having less than " + minNumPeaksPerSpectrum + " peaks."); if (numDenseCentroidedSpectra > 0) { System.out.println("Ignoring " + numDenseCentroidedSpectra + " spectra marked as centroid with dense peaks (<50ppm median distance).\n" + diff --git a/src/main/java/edu/ucsd/msjava/msutil/SpectraAccessor.java b/src/main/java/edu/ucsd/msjava/msutil/SpectraAccessor.java index 846e121b..1854b6a2 100644 --- a/src/main/java/edu/ucsd/msjava/msutil/SpectraAccessor.java +++ b/src/main/java/edu/ucsd/msjava/msutil/SpectraAccessor.java @@ -20,6 +20,9 @@ public class SpectraAccessor { private MzMLAdapter mzmlAdapter = null; + private int minMSLevel = 2; + private int maxMSLevel = 2; + SpectrumAccessorBySpecIndex specMap = null; Iterator specItr = null; @@ -45,13 +48,25 @@ public SpectraAccessor(File specFile, SpecFileFormat specFormat) { this.spectrumParser = null; } + /** + * Set the MS level range for spectrum filtering (both inclusive). + * + * @param minMSLevel minimum MS level to consider (inclusive). + * @param maxMSLevel maximum MS level to consider (inclusive). + */ + public void setMSLevelRange(int minMSLevel, int maxMSLevel) { + this.minMSLevel = minMSLevel; + this.maxMSLevel = maxMSLevel; + } + public SpectrumAccessorBySpecIndex getSpecMap() { if (specMap == null) { if (specFormat == SpecFileFormat.MZXML) - specMap = new MzXMLSpectraMap(specFile.getPath()); + specMap = new MzXMLSpectraMap(specFile.getPath()).msLevel(minMSLevel, maxMSLevel); else if (specFormat == SpecFileFormat.MZML) { if (mzmlAdapter == null) mzmlAdapter = new MzMLAdapter(specFile); + mzmlAdapter.msLevel(minMSLevel, maxMSLevel); specMap = new MzMLSpectraMap(mzmlAdapter); } else if (specFormat == SpecFileFormat.DTA_TXT) specMap = new PNNLSpectraMap(specFile.getPath()); @@ -82,10 +97,11 @@ else if (specFormat == SpecFileFormat.PKL) public Iterator getSpecItr() { if (specItr == null) { if (specFormat == SpecFileFormat.MZXML) - specItr = new MzXMLSpectraIterator(specFile.getPath()); + specItr = new MzXMLSpectraIterator(specFile.getPath(), minMSLevel, maxMSLevel); else if (specFormat == SpecFileFormat.MZML) { if (mzmlAdapter == null) mzmlAdapter = new MzMLAdapter(specFile); + mzmlAdapter.msLevel(minMSLevel, maxMSLevel); specItr = new MzMLSpectraIterator(mzmlAdapter); } else if (specFormat == SpecFileFormat.DTA_TXT) try { diff --git a/src/main/java/edu/ucsd/msjava/mzid/MZIdentMLGen.java b/src/main/java/edu/ucsd/msjava/mzid/MZIdentMLGen.java index 1cc5352d..56728cbc 100644 --- a/src/main/java/edu/ucsd/msjava/mzid/MZIdentMLGen.java +++ b/src/main/java/edu/ucsd/msjava/mzid/MZIdentMLGen.java @@ -260,6 +260,10 @@ public synchronized void addSpectrumIdentificationResults(List re continue; edu.ucsd.msjava.msutil.Spectrum spec = specAcc.getSpecMap().getSpectrumBySpecIndex(specIndex); + if (spec == null) { + System.err.println("Warning: spectrum index " + specIndex + " not found in spectrum file; skipping"); + continue; + } String specID = spec.getID(); float precursorMz = spec.getPrecursorPeak().getMz(); @@ -322,7 +326,7 @@ public synchronized void addSpectrumIdentificationResults(List re DatabaseMatch match = matchList.get(i); if (match.getDeNovoScore() < params.getMinDeNovoScore()) - break; + continue; // int pepIndex = match.getIndex(); // Position of preAA int length = match.getLength(); // Peptide length + 2 diff --git a/src/main/java/edu/ucsd/msjava/mzml/MzMLAdapter.java b/src/main/java/edu/ucsd/msjava/mzml/MzMLAdapter.java index 808c24fd..61f37af7 100644 --- a/src/main/java/edu/ucsd/msjava/mzml/MzMLAdapter.java +++ b/src/main/java/edu/ucsd/msjava/mzml/MzMLAdapter.java @@ -18,7 +18,7 @@ public class MzMLAdapter { private final File specFile; private MzMLUnmarshaller unmarshaller; private int minMSLevel = 2; // inclusive - private int maxMSLevel = Integer.MAX_VALUE; // exclusive + private int maxMSLevel = Integer.MAX_VALUE; // inclusive private CvParam spectrumIDFormatCvParam = null; public MzMLAdapter(File specFile) { diff --git a/src/main/java/edu/ucsd/msjava/params/IntRangeParameter.java b/src/main/java/edu/ucsd/msjava/params/IntRangeParameter.java index 8f772c89..309ae0ca 100644 --- a/src/main/java/edu/ucsd/msjava/params/IntRangeParameter.java +++ b/src/main/java/edu/ucsd/msjava/params/IntRangeParameter.java @@ -19,16 +19,14 @@ public IntRangeParameter(String key, String name, String description) { public String parse(String value) { String[] token = value.split(","); try { -// if(token.length == 1) -// { -// min = Integer.parseInt(token[0]); -// max = min; -// } - if (token.length == 2) { + if (token.length == 1) { + min = Integer.parseInt(token[0]); + max = min; + } else if (token.length == 2) { min = Integer.parseInt(token[0]); max = Integer.parseInt(token[1]); } else { - return "illegar syntax"; + return "illegal syntax"; } } catch (NumberFormatException e) { return "not a valid integer or integer range"; diff --git a/src/main/java/edu/ucsd/msjava/params/ParamManager.java b/src/main/java/edu/ucsd/msjava/params/ParamManager.java index 2bb29ce9..89cae434 100644 --- a/src/main/java/edu/ucsd/msjava/params/ParamManager.java +++ b/src/main/java/edu/ucsd/msjava/params/ParamManager.java @@ -122,6 +122,11 @@ public enum ParamNameEnum { SPEC_INDEX("index", "SpecIndex", "Range of spectrum indices to be considered", "For example, to analyze the first 1000 spectra use -index 1,1000"), + MS_LEVEL("msLevel", "MSLevel", "MS level or range of MS levels to consider; Default: 2", + "Accepts a single value or a comma-separated range.\n" + + "\t For example, -msLevel 2 to search only MS2 spectra\n" + + "\t Or -msLevel 2,3 to search both MS2 and MS3 spectra"), + MAX_MISSED_CLEAVAGES("maxMissedCleavages", "MaxMissedCleavages", "Exclude peptides with more than this number of missed cleavages from the search; Default: -1 (no limit)", null), TDA_STRATEGY("tda", "TDA", "Target decoy strategy", @@ -695,6 +700,14 @@ private void addSpecIndexRangeParam(boolean isHidden) { addParameter(specIndexParam); } + private void addMSLevelParam() { + IntRangeParameter msLevelParam = new IntRangeParameter(ParamNameEnum.MS_LEVEL); + msLevelParam.minValue(1); + msLevelParam.setMaxInclusive(); + msLevelParam.defaultValue("2,2"); + addParameter(msLevelParam); + } + private void addEdgeScoreParam(boolean isHidden) { EnumParameter edgeScoreParam = new EnumParameter(ParamNameEnum.EDGE_SCORE.key); edgeScoreParam.registerEntry("Use edge scoring").setDefault(); @@ -792,6 +805,7 @@ public void addMSGFPlusParams() { addMaxNumModsParam(); addAllowDenseCentroidedPeaksParam(); + addMSLevelParam(); addExample("Example (high-precision): java -Xmx3500M -jar MSGFPlus.jar -s test.mzML -d IPI_human_3.79.fasta -inst 1 -t 20ppm -ti -1,2 -ntt 2 -tda 1 -o testMSGFPlus.mzid -mod Mods.txt"); addExample("Example (low-precision): java -Xmx3500M -jar MSGFPlus.jar -s test.mzML -d IPI_human_3.79.fasta -inst 0 -t 0.5Da,2.5Da -ntt 2 -tda 1 -o testMSGFPlus.mzid -mod Mods.txt"); @@ -1103,6 +1117,10 @@ public IntRangeParameter getSpecIndexParameter() { return ((IntRangeParameter) getParameter(ParamNameEnum.SPEC_INDEX.key)); } + public IntRangeParameter getMSLevelParameter() { + return ((IntRangeParameter) getParameter(ParamNameEnum.MS_LEVEL.key)); + } + public int getTDA() { return getIntValue(ParamNameEnum.TDA_STRATEGY.key); } diff --git a/src/main/java/edu/ucsd/msjava/parser/MzXMLSpectraMap.java b/src/main/java/edu/ucsd/msjava/parser/MzXMLSpectraMap.java index a8ca4d8f..3b48c2ae 100644 --- a/src/main/java/edu/ucsd/msjava/parser/MzXMLSpectraMap.java +++ b/src/main/java/edu/ucsd/msjava/parser/MzXMLSpectraMap.java @@ -26,7 +26,7 @@ public class MzXMLSpectraMap implements SpectrumAccessorBySpecIndex { // if(maxMSLevel >= minMSLevel > 0) only spectra within [minMSLevel, maxMSLevel] will be returned private int minMSLevel = 2; // inclusive - private int maxMSLevel = Integer.MAX_VALUE; // exclusive + private int maxMSLevel = Integer.MAX_VALUE; // inclusive /***** CONSTRUCTORS *****/ /** @@ -64,7 +64,7 @@ public Spectrum getSpectrumByScanNum(int scanNumber) { if (scanObj == null) return null; int msLevel = scanObj.getHeader().getMsLevel(); - if (msLevel < minMSLevel || msLevel >= maxMSLevel) + if (msLevel < minMSLevel || msLevel > maxMSLevel) return null; // get peak list array (mass, intensities) pairs double[][] peakList = scanObj.getMassIntensityList(); @@ -168,7 +168,7 @@ public String getID(int specIndex) { if (scanObj == null) return null; int msLevel = scanObj.getHeader().getMsLevel(); - if (msLevel < minMSLevel || msLevel >= maxMSLevel) + if (msLevel < minMSLevel || msLevel > maxMSLevel) return null; return String.valueOf(specIndex); } @@ -187,7 +187,7 @@ public Float getPrecursorMz(int specIndex) { if (scanObj == null) return null; int msLevel = scanObj.getHeader().getMsLevel(); - if (msLevel < minMSLevel || msLevel >= maxMSLevel) + if (msLevel < minMSLevel || msLevel > maxMSLevel) return null; ScanHeader header = scanObj.getHeader(); diff --git a/src/main/java/edu/ucsd/msjava/ui/MSGFDB.java b/src/main/java/edu/ucsd/msjava/ui/MSGFDB.java index 1fe86467..2f7c07c9 100644 --- a/src/main/java/edu/ucsd/msjava/ui/MSGFDB.java +++ b/src/main/java/edu/ucsd/msjava/ui/MSGFDB.java @@ -278,7 +278,7 @@ private static String runMSGFDB(File specFile, SpecFileFormat specFormat, File o int avgPeptideMass = 2000; int numBytesPerMass = 12; int numSpecScannedTogether = (int) ((float) maxMemory / avgPeptideMass / numBytesPerMass); - ArrayList specKeyList = SpecKey.getSpecKeyList(specAcc.getSpecItr(), startSpecIndex, endSpecIndex, minCharge, maxCharge, activationMethod, Constants.MIN_NUM_PEAKS_PER_SPECTRUM, allowDenseCentroidedPeaks); + ArrayList specKeyList = SpecKey.getSpecKeyList(specAcc.getSpecItr(), startSpecIndex, endSpecIndex, minCharge, maxCharge, activationMethod, Constants.MIN_NUM_PEAKS_PER_SPECTRUM, allowDenseCentroidedPeaks, 2, Integer.MAX_VALUE); int specSize = specKeyList.size(); System.out.print("Reading spectra finished "); diff --git a/src/main/java/edu/ucsd/msjava/ui/MSGFDBLib.java b/src/main/java/edu/ucsd/msjava/ui/MSGFDBLib.java index b84dcf0f..f2eaf055 100644 --- a/src/main/java/edu/ucsd/msjava/ui/MSGFDBLib.java +++ b/src/main/java/edu/ucsd/msjava/ui/MSGFDBLib.java @@ -104,7 +104,7 @@ public static String runMSGFLib(ParamManager paramManager) { int avgPeptideMass = 2000; int numBytesPerMass = 12; int numSpecScannedTogether = (int) ((float) maxMemory / avgPeptideMass / numBytesPerMass); - ArrayList specKeyList = SpecKey.getSpecKeyList(specAcc.getSpecItr(), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, activationMethod, Constants.MIN_NUM_PEAKS_PER_SPECTRUM, false); + ArrayList specKeyList = SpecKey.getSpecKeyList(specAcc.getSpecItr(), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, activationMethod, Constants.MIN_NUM_PEAKS_PER_SPECTRUM, false, 2, Integer.MAX_VALUE); int specSize = specKeyList.size(); System.out.print("Reading spectra finished "); diff --git a/src/main/java/edu/ucsd/msjava/ui/MSGFPlus.java b/src/main/java/edu/ucsd/msjava/ui/MSGFPlus.java index 37f344a3..92914210 100644 --- a/src/main/java/edu/ucsd/msjava/ui/MSGFPlus.java +++ b/src/main/java/edu/ucsd/msjava/ui/MSGFPlus.java @@ -263,12 +263,16 @@ private static String runMSGFPlus(int ioIndex, SpecFileFormat specFormat, File o System.out.printf("Opening %s %s\n", specFormat.getPSIName(), specFile.getName()); SpectraAccessor specAcc = new SpectraAccessor(specFile, specFormat); + int minMSLevel = params.getMinMSLevel(); + int maxMSLevel = params.getMaxMSLevel(); + specAcc.setMSLevelRange(minMSLevel, maxMSLevel); if (specAcc.getSpecMap() == null || specAcc.getSpecItr() == null) return "Error while parsing spectrum file: " + specFile.getPath(); ArrayList specKeyList = SpecKey.getSpecKeyList(specAcc, - startSpecIndex, endSpecIndex, minCharge, maxCharge, activationMethod, minNumPeaksPerSpectrum, allowDenseCentroidedPeaks); + startSpecIndex, endSpecIndex, minCharge, maxCharge, activationMethod, minNumPeaksPerSpectrum, allowDenseCentroidedPeaks, + minMSLevel, maxMSLevel); int specSize = specKeyList.size(); if (specSize == 0) diff --git a/src/test/java/msgfplus/TestIntRangeParameter.java b/src/test/java/msgfplus/TestIntRangeParameter.java new file mode 100644 index 00000000..67f3cacc --- /dev/null +++ b/src/test/java/msgfplus/TestIntRangeParameter.java @@ -0,0 +1,94 @@ +package msgfplus; + +import static org.junit.Assert.*; + +import edu.ucsd.msjava.params.IntRangeParameter; +import org.junit.Test; + +/** + * Tests for IntRangeParameter, which supports single values and ranges. + * Part of issue #159: the -msLevel parameter uses IntRangeParameter. + */ +public class TestIntRangeParameter { + + private IntRangeParameter createInclusiveParam() { + IntRangeParameter p = new IntRangeParameter("test", "Test", "desc"); + p.setMaxInclusive(); + return p; + } + + @Test + public void testSingleValue() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("2"); + assertNull("Single value should parse successfully", err); + assertEquals(2, (int) p.getMin()); + assertEquals(2, (int) p.getMax()); + } + + @Test + public void testRange() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("2,3"); + assertNull("Range should parse successfully", err); + assertEquals(2, (int) p.getMin()); + assertEquals(3, (int) p.getMax()); + } + + @Test + public void testWideRange() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("1,5"); + assertNull(err); + assertEquals(1, (int) p.getMin()); + assertEquals(5, (int) p.getMax()); + } + + @Test + public void testSameMinMax() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("3,3"); + assertNull(err); + assertEquals(3, (int) p.getMin()); + assertEquals(3, (int) p.getMax()); + } + + @Test + public void testSingleValueExclusiveMaxRejects() { + // Default constructor has isMaxInclusive=false, so single value "2" + // produces min=2,max=2 but effective maxNumber=1 < minNumber=2 -> invalid + IntRangeParameter p = new IntRangeParameter("test", "Test", "desc"); + String err = p.parse("2"); + assertNotNull("Single value with exclusive max should fail", err); + } + + @Test + public void testInvalidReversedRange() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("5,2"); + assertNotNull("Reversed range should fail", err); + } + + @Test + public void testInvalidTooManyValues() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("1,2,3"); + assertNotNull("Three values should fail", err); + assertEquals("illegal syntax", err); + } + + @Test + public void testInvalidNonNumeric() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse("abc"); + assertNotNull("Non-numeric should fail", err); + assertEquals("not a valid integer or integer range", err); + } + + @Test + public void testInvalidEmpty() { + IntRangeParameter p = createInclusiveParam(); + String err = p.parse(""); + assertNotNull("Empty string should fail", err); + } +} diff --git a/src/test/java/msgfplus/TestMSLevelFiltering.java b/src/test/java/msgfplus/TestMSLevelFiltering.java new file mode 100644 index 00000000..361c574d --- /dev/null +++ b/src/test/java/msgfplus/TestMSLevelFiltering.java @@ -0,0 +1,76 @@ +package msgfplus; + +import static org.junit.Assert.*; + +import edu.ucsd.msjava.params.IntRangeParameter; +import edu.ucsd.msjava.params.ParamManager; +import org.junit.Test; + +/** + * Tests for the -msLevel parameter (issue #159). + * Verifies that MS level filtering is properly wired through ParamManager. + */ +public class TestMSLevelFiltering { + + private ParamManager createParamManager() { + ParamManager pm = new ParamManager("MS-GF+", "test", "2024.01.01", "test"); + pm.addMSGFPlusParams(); + return pm; + } + + @Test + public void testMSLevelParameterExists() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + assertNotNull("MS_LEVEL parameter should exist", msLevel); + } + + @Test + public void testMSLevelDefaultIsMS2() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + // Default should be MS2 only (2,2) + assertEquals("Default min MS level should be 2", 2, (int) msLevel.getMin()); + assertEquals("Default max MS level should be 2", 2, (int) msLevel.getMax()); + } + + @Test + public void testMSLevelParseSingleValue() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + String err = msLevel.parse("2"); + assertNull("Parsing '2' should succeed", err); + assertEquals(2, (int) msLevel.getMin()); + assertEquals(2, (int) msLevel.getMax()); + } + + @Test + public void testMSLevelParseRange() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + String err = msLevel.parse("2,3"); + assertNull("Parsing '2,3' should succeed", err); + assertEquals(2, (int) msLevel.getMin()); + assertEquals(3, (int) msLevel.getMax()); + } + + @Test + public void testMSLevelParseMS3Only() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + String err = msLevel.parse("3"); + assertNull("Parsing '3' should succeed", err); + assertEquals(3, (int) msLevel.getMin()); + assertEquals(3, (int) msLevel.getMax()); + } + + @Test + public void testMSLevelParseWideRange() { + ParamManager pm = createParamManager(); + IntRangeParameter msLevel = pm.getMSLevelParameter(); + String err = msLevel.parse("1,5"); + assertNull("Parsing '1,5' should succeed", err); + assertEquals(1, (int) msLevel.getMin()); + assertEquals(5, (int) msLevel.getMax()); + } +} diff --git a/src/test/java/msgfplus/TestMZIdentMLGen.java b/src/test/java/msgfplus/TestMZIdentMLGen.java new file mode 100644 index 00000000..46c2e0cc --- /dev/null +++ b/src/test/java/msgfplus/TestMZIdentMLGen.java @@ -0,0 +1,142 @@ +package msgfplus; + +import static org.junit.Assert.*; + +import java.io.*; +import java.nio.file.Files; + +import org.junit.Test; + +import edu.ucsd.msjava.ui.MSGFPlus; + +/** + * Tests for issue #157: verify mzid export completeness. + * Ensures that all SpectrumIdentificationItems have complete score CVParams + * and that the break->continue fix in MZIdentMLGen preserves all valid PSMs. + */ +public class TestMZIdentMLGen { + + /** + * Run a small MSGF+ search and verify that the output mzid has + * complete scores for every SpectrumIdentificationItem. + * + * This catches the issue #157 bug where a 'break' on low DeNovoScore + * would silently drop all subsequent matches for that spectrum. + * With the fix (continue instead of break), every valid match gets + * its full set of score CVParams. + */ + @Test + public void testMzidScoreCompleteness() throws Exception { + File specFile = new File(getClass().getClassLoader().getResource("test.mgf").toURI()); + File dbFile = new File(getClass().getClassLoader().getResource("Tryp_Pig_Bov.fasta").toURI()); + File outputFile = File.createTempFile("test_157_", ".mzid"); + outputFile.deleteOnExit(); + + // Use Tryp_Pig_Bov.fasta (tiny DB) for fast execution. + // Even with few or no PSMs, validates the code path does not crash. + String[] argv = { + "-s", specFile.getPath(), + "-d", dbFile.getPath(), + "-o", outputFile.getPath(), + "-t", "20ppm", + "-tda", "0", + "-ntt", "2", + "-thread", "2", + "-minLength", "6", + "-maxLength", "40", + "-minCharge", "2", + "-maxCharge", "4", + "-n", "1" + }; + + MSGFPlus.main(argv); + + assertTrue("Output mzid file should exist", outputFile.exists()); + assertTrue("Output mzid file should not be empty", outputFile.length() > 0); + + // Parse the output and verify score completeness + String content = new String(Files.readAllBytes(outputFile.toPath())); + + // Count SpectrumIdentificationItem elements (opening tags only) + int siiCount = countOccurrences(content, " 0) { + // Every SII must have all 4 score CVParams + assertEquals("Every SII should have a RawScore", siiCount, rawScoreCount); + assertEquals("Every SII should have a DeNovoScore", siiCount, deNovoScoreCount); + assertEquals("Every SII should have a SpecEValue", siiCount, specEValueCount); + assertEquals("Every SII should have an EValue", siiCount, eValueCount); + } + + // Verify no empty SpectrumIdentificationResult (SIR without SII children) + // This would indicate silently dropped PSMs + assertFalse("Should not have empty SpectrumIdentificationResult elements", + content.contains("") && + content.contains("")); + + outputFile.delete(); + } + + /** + * Verify that the mzid output file is well-formed XML and contains + * the required mzIdentML structure elements. + */ + @Test + public void testMzidStructuralValidity() throws Exception { + File specFile = new File(getClass().getClassLoader().getResource("test.mgf").toURI()); + File dbFile = new File(getClass().getClassLoader().getResource("BSA.fasta").toURI()); + File outputFile = File.createTempFile("test_157_struct_", ".mzid"); + outputFile.deleteOnExit(); + + String[] argv = { + "-s", specFile.getPath(), + "-d", dbFile.getPath(), + "-o", outputFile.getPath(), + "-t", "20ppm", + "-tda", "0", + "-ntt", "2", + "-thread", "2", + "-n", "1" + }; + + MSGFPlus.main(argv); + + assertTrue("Output mzid file should exist", outputFile.exists()); + String content = new String(Files.readAllBytes(outputFile.toPath())); + + // Verify required mzIdentML sections exist + assertTrue("Should contain MzIdentML root element", + content.contains("