diff --git a/pwiz_tools/BiblioSpec/src/ProxlXmlReader.cpp b/pwiz_tools/BiblioSpec/src/ProxlXmlReader.cpp index 955d5091f0..fda506cbd3 100644 --- a/pwiz_tools/BiblioSpec/src/ProxlXmlReader.cpp +++ b/pwiz_tools/BiblioSpec/src/ProxlXmlReader.cpp @@ -107,6 +107,8 @@ void ProxlXmlReader::startElement(const XML_Char* name, const XML_Char** attr) { analysisType_ = BYONIC_ANALYSIS; else if (program == "plink") analysisType_ = PLINK_ANALYIS; + else if (program == "merox") + analysisType_ = MEROX_ANALYSIS; } break; case REPORTED_PEPTIDES_STATE: @@ -127,7 +129,7 @@ void ProxlXmlReader::startElement(const XML_Char* name, const XML_Char** attr) { break; case REPORTED_PEPTIDE_STATE: if (analysisType_ == UNKNOWN_ANALYSIS) - throw runtime_error("only Byonic, Percolator, and pLink ProxlXML files are supported; " + throw runtime_error("only Byonic, Percolator, pLink, and MeroX ProxlXML files are supported; " "cannot handle search program: " + bal::join(searchPrograms_, ", ")); if (isScoreLookup_) throw SAXHandler::EndEarlyException(); @@ -194,7 +196,8 @@ void ProxlXmlReader::startElement(const XML_Char* name, const XML_Char** attr) { string score = bal::to_lower_copy(string(getRequiredAttrValue("annotation_name", attr))); if (analysisType_ == PERCOLATOR_ANALYSIS && score == "q-value" || analysisType_ == BYONIC_ANALYSIS && score == "peptide abslogprob2d" || - analysisType_ == PLINK_ANALYIS && score == "score") { + analysisType_ == PLINK_ANALYIS && score == "score" || + analysisType_ == MEROX_ANALYSIS && score == "qvalue") { curProxlPsm_->score = getDoubleRequiredAttrValue("value", attr); } @@ -306,9 +309,12 @@ double ProxlXmlReader::getScoreThreshold() { switch (analysisType_) { - case BYONIC_ANALYSIS: return blibMaker_.getScoreThreshold(BYONIC); - case PERCOLATOR_ANALYSIS: return blibMaker_.getScoreThreshold(GENERIC_QVALUE_INPUT); - case PLINK_ANALYIS: return blibMaker_.getScoreThreshold(GENERIC_QVALUE_INPUT); + case BYONIC_ANALYSIS: + return blibMaker_.getScoreThreshold(BYONIC); + case PERCOLATOR_ANALYSIS: + case PLINK_ANALYIS: + case MEROX_ANALYSIS: + return blibMaker_.getScoreThreshold(GENERIC_QVALUE_INPUT); default: throw runtime_error("no case for analysisType_: " + lexical_cast((int) analysisType_)); diff --git a/pwiz_tools/BiblioSpec/src/ProxlXmlReader.h b/pwiz_tools/BiblioSpec/src/ProxlXmlReader.h index 5881b9a4d5..025ee29e76 100644 --- a/pwiz_tools/BiblioSpec/src/ProxlXmlReader.h +++ b/pwiz_tools/BiblioSpec/src/ProxlXmlReader.h @@ -81,7 +81,8 @@ class ProxlXmlReader : public BuildParser { UNKNOWN_ANALYSIS, PERCOLATOR_ANALYSIS, BYONIC_ANALYSIS, - PLINK_ANALYIS + PLINK_ANALYIS, + MEROX_ANALYSIS }; static double aaMasses_[128]; diff --git a/pwiz_tools/BiblioSpec/tests/Jamfile.jam b/pwiz_tools/BiblioSpec/tests/Jamfile.jam index d42a9c0abc..75d095012d 100644 --- a/pwiz_tools/BiblioSpec/tests/Jamfile.jam +++ b/pwiz_tools/BiblioSpec/tests/Jamfile.jam @@ -249,6 +249,7 @@ blib-test-build pride-mill : -o : output/pride-mill.blib : pride-mill.check zbui blib-test-build tiny-proxl : --unicode -o : output/tiny-proxl.blib : tiny-proxl.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tiny.proxl.xml ; blib-test-build tinyByonic-proxl : -o : output/tinyByonic-proxl.blib : tinyByonic-proxl.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tinyByonic.proxl.xml ; blib-test-build tinyPlink-proxl : -o : output/tinyPlink-proxl.blib : tinyPlink-proxl.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tinyPlink.proxl.xml ; +blib-test-build tinyMerox-proxl : -o : output/tinyMerox-proxl.blib : tinyMerox-proxl.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tinyMerox.proxl.xml ; blib-test-build tiny-msf : --unicode -o : output/tiny-msf.blib : tiny-msf.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tiny.msf ; blib-test-build tiny-v2-msf : -o : output/tiny-v2-msf.blib : tiny-v2-msf.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tiny-v2.msf ; blib-test-build tiny-v2-filtered-pdResult : -o : output/tiny-v2-filtered-pdResult.blib : tiny-v2-filtered-pdResult.check zbuild.skip-lines : $(TEST_INPUTS_PATH)/tiny-v2-filtered.pdResult ; diff --git a/pwiz_tools/BiblioSpec/tests/inputs/tinyMerox.proxl.xml b/pwiz_tools/BiblioSpec/tests/inputs/tinyMerox.proxl.xml new file mode 100644 index 0000000000..2e0ccda948 --- /dev/null +++ b/pwiz_tools/BiblioSpec/tests/inputs/tinyMerox.proxl.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + K + + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pwiz_tools/BiblioSpec/tests/inputs/tinyMeroxProxl.mzML b/pwiz_tools/BiblioSpec/tests/inputs/tinyMeroxProxl.mzML new file mode 100644 index 0000000000..61b82cfa45 --- /dev/null +++ b/pwiz_tools/BiblioSpec/tests/inputs/tinyMeroxProxl.mzML @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + diff --git a/pwiz_tools/BiblioSpec/tests/reference/tinyMerox-proxl.check b/pwiz_tools/BiblioSpec/tests/reference/tinyMerox-proxl.check new file mode 100644 index 0000000000..aff2c63bb8 --- /dev/null +++ b/pwiz_tools/BiblioSpec/tests/reference/tinyMerox-proxl.check @@ -0,0 +1,29 @@ +libLSID numSpecs majorVersion minorVersion +urn:lsid:proteome.gs.washington.edu:spectral_library:bibliospec:redundant:tinyMerox-proxl.blib 1 1 10 +id RefSpectraID position mass +1 1 4 848.413597 +id peptideSeq precursorMZ precursorCharge peptideModSeq prevAA nextAA copies numPeaks ionMobility collisionalCrossSectionSqA ionMobilityHighEnergyOffset ionMobilityType retentionTime startTime endTime totalIonCurrent moleculeName chemicalFormula precursorAdduct inchiKey otherKeys fileID SpecIDinFile score scoreType +1 LWDKETLEK-MAKTIK 503.26185380 4 LWDKETLEK-MAKTIK-[+158.0038@4,3] - - 1 292 0.0 0.0 0.0 0 52.662712 N/A N/A 13494642.46 N/A N/A N/A N/A N/A 1 22237 0.0 1 +id fileName idFileName cutoffScore +1 /BiblioSpec/tests/inputs/tinyMeroxProxl.mzML /BiblioSpec/tests/inputs/tinyMerox.proxl.xml -1.0 +id scoreType probabilityType +0 UNKNOWN NOT_A_PROBABILITY_VALUE +1 PERCOLATOR QVALUE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +2 PEPTIDE PROPHET SOMETHING PROBABILITY_THAT_IDENTIFICATION_IS_CORRECT +3 SPECTRUM MILL NOT_A_PROBABILITY_VALUE +4 IDPICKER FDR PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +5 MASCOT IONS SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +6 TANDEM EXPECTATION VALUE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +7 PROTEIN PILOT CONFIDENCE PROBABILITY_THAT_IDENTIFICATION_IS_CORRECT +8 SCAFFOLD SOMETHING PROBABILITY_THAT_IDENTIFICATION_IS_CORRECT +9 WATERS MSE PEPTIDE SCORE NOT_A_PROBABILITY_VALUE +10 OMSSA EXPECTATION SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +11 PROTEIN PROSPECTOR EXPECTATION SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +12 SEQUEST XCORR PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +13 MAXQUANT SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +14 MORPHEUS SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +15 MSGF+ SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +16 PEAKS CONFIDENCE SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +17 BYONIC SCORE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT +18 PEPTIDE SHAKER CONFIDENCE PROBABILITY_THAT_IDENTIFICATION_IS_CORRECT +19 GENERIC Q-VALUE PROBABILITY_THAT_IDENTIFICATION_IS_INCORRECT diff --git a/pwiz_tools/Skyline/Model/Crosslinking/CrosslinkBuilder.cs b/pwiz_tools/Skyline/Model/Crosslinking/CrosslinkBuilder.cs index 8508cdf575..d3bfae0e1c 100644 --- a/pwiz_tools/Skyline/Model/Crosslinking/CrosslinkBuilder.cs +++ b/pwiz_tools/Skyline/Model/Crosslinking/CrosslinkBuilder.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Linq; using pwiz.Common.Chemistry; +using pwiz.Common.Collections; using pwiz.Skyline.Model.DocSettings; using pwiz.Skyline.Model.Lib; using pwiz.Skyline.Model.Results; @@ -213,7 +214,9 @@ public IEnumerable GetComplexFragmentIons(TransitionGroup tr simpleTransitions.Add(_peptideBuilders[i].GetSingleFragmentIons(peptideTransitionGroup, useFilter).ToList()); } - return PermuteTransitions(simpleTransitions, Settings.PeptideSettings.Modifications.MaxNeutralLosses); + int maxNeutralLosses = Settings.PeptideSettings.Modifications.MaxNeutralLosses; + return PermuteTransitions(simpleTransitions, maxNeutralLosses).SelectMany(neutralFragmentIon => + PermuteCleavedCrosslinks(neutralFragmentIon, maxNeutralLosses)); } public IEnumerable GetTransitionDocNodes(TransitionGroup transitionGroup, @@ -292,6 +295,69 @@ public IEnumerable PermuteTransitions(IList PermuteCleavedCrosslinks(NeutralFragmentIon neutralFragmentIon, int maxFragmentationEventCount) + { + var cleavedCrosslinks = neutralFragmentIon.GetCleavedCrosslinks(PeptideStructure).ToList(); + if (cleavedCrosslinks.Count == 0) + { + yield return neutralFragmentIon; + yield break; + } + + if (cleavedCrosslinks.Count + neutralFragmentIon.CountFragmentationEvents() > maxFragmentationEventCount) + { + yield break; + } + + var massType = Settings.TransitionSettings.Prediction.FragmentMassType; + foreach (var lossList in PermuteTransitionLosses( + cleavedCrosslinks.Select(crosslink => crosslink.Crosslinker), + massType)) + { + var transitionLosses = new TransitionLosses(lossList.ToList(), massType); + yield return neutralFragmentIon.AddLosses(transitionLosses); + } + } + + public static IList> PermuteTransitionLosses(IEnumerable staticMods, MassType massType) + { + var allLosses = new List> { ImmutableList.EMPTY }; + foreach (var group in staticMods.GroupBy(mod => mod)) + { + var staticMod = group.Key; + if (staticMod.Losses == null) + { + return Array.Empty>(); + } + + var transitionLosses = staticMod.Losses.Select(loss => new TransitionLoss(staticMod, loss, massType)).ToList(); + var integerCombinations = IntegerCombinations(transitionLosses.Count, group.Count()); + allLosses = allLosses.SelectMany(entry => + { + return integerCombinations.Select(indexes => + ImmutableList.ValueOf(entry.Concat(indexes.Select(i => transitionLosses[i])))); + }).ToList(); + } + + return allLosses; + } + + private static List> IntegerCombinations(int range, int count) + { + var list = new List> { ImmutableList.EMPTY }; + for (int position = 0; position < count; position++) + { + list = list.SelectMany(entry => Enumerable.Range(0, range).Select(i => + { + var newEntry = entry.Append(i).ToList(); + newEntry.Sort(); + return ImmutableList.ValueOf(newEntry); + })).Distinct().ToList(); + } + + return list; + } + public IEnumerable MakeTransitionDocNodes( TransitionGroup transitionGroup, IsotopeDistInfo isotopeDist, diff --git a/pwiz_tools/Skyline/Model/Crosslinking/NeutralFragmentIon.cs b/pwiz_tools/Skyline/Model/Crosslinking/NeutralFragmentIon.cs index 0eb4091b0f..27a0774b6e 100644 --- a/pwiz_tools/Skyline/Model/Crosslinking/NeutralFragmentIon.cs +++ b/pwiz_tools/Skyline/Model/Crosslinking/NeutralFragmentIon.cs @@ -89,19 +89,47 @@ public bool IsConnected(PeptideStructure peptideStructure) } var containedCrosslinks = new List(); + var cleavedCrosslinks = new List(); foreach (var crosslink in peptideStructure.Crosslinks) { bool? isContained = ContainsCrosslink(peptideStructure, crosslink.Sites); if (!isContained.HasValue) { - return false; + cleavedCrosslinks.Add(crosslink); + } + else if (isContained.Value) + { + containedCrosslinks.Add(crosslink); } + } - if (isContained.Value) + // Verify that the number of cleaved crosslinkers with a particular name is the same as + // the number of neutral losses with that name + if (Losses == null) + { + if (cleavedCrosslinks.Count > 0) { - containedCrosslinks.Add(crosslink); + return false; } } + else + { + var lossCountsByName = Losses.Losses.GroupBy(loss => loss.PrecursorMod.Name) + .ToDictionary(group => group.Key, group => group.Count()); + var cleavedCrosslinksByName = cleavedCrosslinks.GroupBy(crosslink => crosslink.Crosslinker.Name) + .ToDictionary(group => group.Key, group => group.Count()); + foreach (var crosslinkerName in peptideStructure.Crosslinks + .Select(crosslink => crosslink.Crosslinker.Name).Distinct()) + { + lossCountsByName.TryGetValue(crosslinkerName, out int lossCount); + cleavedCrosslinksByName.TryGetValue(crosslinkerName, out int cleavedCrosslinkCount); + if (lossCount != cleavedCrosslinkCount) + { + return false; + } + } + } + var peptideIndexQueue = new Queue(); var visitedPeptideIndexes = new HashSet(); peptideIndexQueue.Enqueue(peptideIndexes.Min()); @@ -127,6 +155,12 @@ public bool IsConnected(PeptideStructure peptideStructure) return visitedPeptideIndexes.SetEquals(peptideIndexes); } + public IEnumerable GetCleavedCrosslinks(PeptideStructure peptideStructure) + { + return peptideStructure.Crosslinks.Where(crosslink => + !ContainsCrosslink(peptideStructure, crosslink.Sites).HasValue); + } + public bool IsOrphan { get @@ -226,6 +260,11 @@ public bool IsAllowed(PeptideStructure peptideStructure) { foreach (var crosslink in peptideStructure.Crosslinks) { + if (crosslink.Crosslinker.HasLoss) + { + // A crosslinker with losses is cleavable to assume it is valid for now + continue; + } if (!ContainsCrosslink(peptideStructure, crosslink.Sites).HasValue) { return false; @@ -324,5 +363,24 @@ public ComplexFragmentIon MakeChargedIon(TransitionGroup group, Adduct adduct, E { return new ComplexFragmentIon(MakeTransition(group, adduct), this, explicitMods); } + + public NeutralFragmentIon AddLosses(TransitionLosses losses) + { + if (losses == null) + { + return this; + } + TransitionLosses newLosses; + if (Losses == null) + { + newLosses = losses; + } + else + { + newLosses = new TransitionLosses(Losses.Losses.Concat(losses.Losses).ToList(), losses.MassType); + } + + return ChangeProp(ImClone(this), im => im.Losses = newLosses); + } } } diff --git a/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.cs b/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.cs new file mode 100644 index 0000000000..a41195068a --- /dev/null +++ b/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.cs @@ -0,0 +1,108 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2022 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.Skyline.Model.DocSettings; +using pwiz.Skyline.SettingsUI; +using pwiz.SkylineTestUtil; + +namespace pwiz.SkylineTestFunctional +{ + [TestClass] + public class CleavableCrosslinkTest : AbstractFunctionalTest + { + [TestMethod] + public void TestCleavableCrosslink() + { + TestFilesZip = @"TestFunctional\CleavableCrosslinkTest.zip"; + RunFunctionalTest(); + } + + protected override void DoTest() + { + var peptideSettingsUi = ShowDialog(SkylineWindow.ShowPeptideSettingsUI); + RunUI(() => + { + peptideSettingsUi.SelectedTab = PeptideSettingsUI.TABS.Digest; + peptideSettingsUi.MaxMissedCleavages = 3; + peptideSettingsUi.SelectedTab = PeptideSettingsUI.TABS.Library; + }); + // Library build + var buildLibrary = ShowDialog(peptideSettingsUi.ShowBuildLibraryDlg); + RunUI(() => + { + buildLibrary.LibraryName = "MyLibrary"; + buildLibrary.LibraryPath = TestFilesDir.GetTestPath("MyLibrary.blib"); + buildLibrary.OkWizardPage(); + + buildLibrary.InputFileNames = new[] { TestFilesDir.GetTestPath("CleavableCrosslinkTest.proxl.xml") }; + }); + WaitForConditionUI(() => buildLibrary.Grid.ScoreTypesLoaded); + OkDialog(buildLibrary, buildLibrary.OkWizardPage); + OkDialog(peptideSettingsUi, peptideSettingsUi.OkDialog); + WaitForDocumentLoaded(); + + // Define a cleavable crosslinker named "DSSO" + peptideSettingsUi = ShowDialog(SkylineWindow.ShowPeptideSettingsUI); + RunUI(() => peptideSettingsUi.SelectedTab = PeptideSettingsUI.TABS.Modifications); + var editModListDlg = ShowEditStaticModsDlg(peptideSettingsUi); + var editStaticModDlg = ShowDialog(editModListDlg.AddItem); + RunUI(() => { + { + editStaticModDlg.Modification = new StaticMod("DSSO", "K", null, "C6O3SH6"); + editStaticModDlg.IsCrosslinker = true; + } + }); + // Add neutral losses for each of the cleavable products + RunDlg(editStaticModDlg.AddLoss, dlg => + { + dlg.Loss = new FragmentLoss("C3O2SH4"); + dlg.OkDialog(); + }); + RunDlg(editStaticModDlg.AddLoss, dlg => + { + dlg.Loss = new FragmentLoss("C3OH2"); + dlg.OkDialog(); + }); + RunDlg(editStaticModDlg.AddLoss, dlg => + { + dlg.Loss = new FragmentLoss("C3O2H4"); + dlg.OkDialog(); + }); + OkDialog(editStaticModDlg, editStaticModDlg.OkDialog); + OkDialog(editModListDlg, editModListDlg.OkDialog); + OkDialog(peptideSettingsUi, peptideSettingsUi.OkDialog); + + // Change the transition settings so that this peptide will be acceptable + RunDlg(SkylineWindow.ShowTransitionSettingsUI, transitionSettingsUi => + { + transitionSettingsUi.SelectedTab = TransitionSettingsUI.TABS.Filter; + transitionSettingsUi.PrecursorCharges = "2,3,4,5"; + transitionSettingsUi.ProductCharges = "1,2,3,4"; + transitionSettingsUi.FragmentTypes = "p,y,b"; + transitionSettingsUi.SelectedTab = TransitionSettingsUI.TABS.Library; + transitionSettingsUi.IonCount = 20; + transitionSettingsUi.OkDialog(); + }); + + var spectralLibraryViewer = ShowDialog(SkylineWindow.ViewSpectralLibraries); + RunUI(()=>spectralLibraryViewer.AddPeptide()); + OkDialog(spectralLibraryViewer, spectralLibraryViewer.Close); + } + } +} diff --git a/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.zip b/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.zip new file mode 100644 index 0000000000..0b10b2504c Binary files /dev/null and b/pwiz_tools/Skyline/TestFunctional/CleavableCrosslinkTest.zip differ diff --git a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj index d613d36c3e..dbfa2e1496 100644 --- a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj +++ b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj @@ -171,6 +171,7 @@ +