diff --git a/pwiz_tools/Skyline/Model/AbstractModificationMatcher.cs b/pwiz_tools/Skyline/Model/AbstractModificationMatcher.cs index 05fae8343e..3da5f15629 100644 --- a/pwiz_tools/Skyline/Model/AbstractModificationMatcher.cs +++ b/pwiz_tools/Skyline/Model/AbstractModificationMatcher.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Text; using pwiz.Common.Collections; +using pwiz.Common.SystemUtil; using pwiz.Skyline.Model.Crosslinking; using pwiz.Skyline.Model.DocSettings; using pwiz.Skyline.Model.Lib; @@ -39,6 +40,8 @@ public abstract class AbstractModificationMatcher protected SrmSettings Settings { get; set; } protected bool Initialized { get; set; } + protected internal string LibraryName { get; set; } + protected MappedList DefSetStatic { get; private set; } protected MappedList DefSetHeavy { get; private set; } protected IsotopeLabelType DocDefHeavyLabelType { get; private set; } @@ -54,13 +57,15 @@ public static double GetDefaultModMass(char aa, StaticMod mod) } internal void InitMatcherSettings(SrmSettings settings, - MappedList defSetStatic, MappedList defSetHeavy) + MappedList defSetStatic, MappedList defSetHeavy, string libraryName) { DefSetStatic = defSetStatic; DefSetHeavy = defSetHeavy; Settings = settings; + LibraryName = libraryName; + var modifications = settings.PeptideSettings.Modifications; UserDefinedTypedMods = new Dictionary(); @@ -474,24 +479,34 @@ public PeptideDocNode CreateDocNodeFromSettings(LibKey key, Peptide peptide, Srm SpectrumHeaderInfo libInfo; if (nodePep != null && Settings.PeptideSettings.Libraries.TryGetLibInfo(key, out libInfo)) { - var isotopeLabelType = key.Adduct.HasIsotopeLabels ? IsotopeLabelType.heavy : IsotopeLabelType.light; - var group = new TransitionGroup(peptide, key.Adduct, isotopeLabelType); - nodeGroupMatched = new TransitionGroupDocNode(group, Annotations.EMPTY, Settings, null, libInfo, ExplicitTransitionGroupValues.EMPTY, null, null, false); - SpectrumPeaksInfo spectrum; - if (Settings.PeptideSettings.Libraries.TryLoadSpectrum(key, out spectrum)) + try { - // Add fragment and precursor transitions as needed - var transitionDocNodes = - Settings.TransitionSettings.Filter.SmallMoleculeIonTypes.Contains(IonType.precursor) - ? nodeGroupMatched.GetPrecursorChoices(Settings, null, true) // Gives list of precursors - : new List(); - - if (Settings.TransitionSettings.Filter.SmallMoleculeIonTypes.Contains(IonType.custom)) + var isotopeLabelType = key.Adduct.HasIsotopeLabels ? IsotopeLabelType.heavy : IsotopeLabelType.light; + var group = new TransitionGroup(peptide, key.Adduct, isotopeLabelType); + nodeGroupMatched = new TransitionGroupDocNode(group, Annotations.EMPTY, Settings, null, libInfo, ExplicitTransitionGroupValues.EMPTY, null, null, false); + SpectrumPeaksInfo spectrum; + if (Settings.PeptideSettings.Libraries.TryLoadSpectrum(key, out spectrum)) { - GetSmallMoleculeFragments(key, nodeGroupMatched, spectrum, transitionDocNodes); + // Add fragment and precursor transitions as needed + var transitionDocNodes = + Settings.TransitionSettings.Filter.SmallMoleculeIonTypes.Contains(IonType.precursor) + ? nodeGroupMatched.GetPrecursorChoices(Settings, null, true) // Gives list of precursors + : new List(); + + if (Settings.TransitionSettings.Filter.SmallMoleculeIonTypes.Contains(IonType.custom)) + { + GetSmallMoleculeFragments(key, nodeGroupMatched, spectrum, transitionDocNodes); + } + nodeGroupMatched = (TransitionGroupDocNode)nodeGroupMatched.ChangeChildren(transitionDocNodes); + return (PeptideDocNode)nodePep.ChangeChildren(new List() { nodeGroupMatched }); } - nodeGroupMatched = (TransitionGroupDocNode)nodeGroupMatched.ChangeChildren(transitionDocNodes); - return (PeptideDocNode)nodePep.ChangeChildren(new List() { nodeGroupMatched }); + } + catch (InvalidChemicalModificationException e) + { + var message = string.IsNullOrEmpty(LibraryName) ? + string.Format(ModelResources.AbstractModificationMatcher_CreateDocNodeFromSettings_In___0_____1_, key.Target.DisplayName, e.Message) : + string.Format(ModelResources.AbstractModificationMatcher_CreateDocNodeFromSettings_In_entry___0___of___1_____2_, key.Target.DisplayName, LibraryName, e.Message); + Messages.WriteAsyncUserMessage(message); // Adduct makes no sense for target formula } } } diff --git a/pwiz_tools/Skyline/Model/Import.cs b/pwiz_tools/Skyline/Model/Import.cs index 05b476acca..427528e6b3 100644 --- a/pwiz_tools/Skyline/Model/Import.cs +++ b/pwiz_tools/Skyline/Model/Import.cs @@ -844,7 +844,7 @@ private PeptideGroupBuilder AddRow(PeptideGroupBuilder seqBuilder, { seqBuilder.AppendTransition(info, irt, explicitRT, libraryIntensity, productMz, note, lineText, lineNum); } - catch (InvalidDataException x) + catch (Exception x) when (!ExceptionUtil.IsProgrammingDefect(x)) { throw new LineColNumberedIoException(x.Message, lineNum, -1, x); } diff --git a/pwiz_tools/Skyline/Model/ImportPeptideSearch.cs b/pwiz_tools/Skyline/Model/ImportPeptideSearch.cs index 9332e2d199..0ada1b9347 100644 --- a/pwiz_tools/Skyline/Model/ImportPeptideSearch.cs +++ b/pwiz_tools/Skyline/Model/ImportPeptideSearch.cs @@ -501,7 +501,7 @@ private void InitializeUserDefinedTypedMods(SrmDocument document) public void UpdateModificationMatches(SrmDocument document) { _matcher.ClearMatches(); - _matcher.CreateMatches(document.Settings, DocLib.Keys, Settings.Default.StaticModList, Settings.Default.HeavyModList); + _matcher.CreateMatches(document.Settings, DocLib.Keys, Settings.Default.StaticModList, Settings.Default.HeavyModList, DocLib.Name); } public IEnumerable GetMatchedMods() diff --git a/pwiz_tools/Skyline/Model/Lib/BiblioSpecLite.cs b/pwiz_tools/Skyline/Model/Lib/BiblioSpecLite.cs index 2f801b681a..d6c6369594 100644 --- a/pwiz_tools/Skyline/Model/Lib/BiblioSpecLite.cs +++ b/pwiz_tools/Skyline/Model/Lib/BiblioSpecLite.cs @@ -615,7 +615,8 @@ private bool ReadFromDatabase(ILoadMonitor loader, IProgressStatus status) var librarySourceFiles = new List(); bool hasRetentionTimesTable = SqliteOperations.TableExists(_sqliteConnection.Connection, @"RetentionTimes"); int segmentCount = hasRetentionTimesTable ? 2 : 1; - status = status.ChangeSegments(0, segmentCount).ChangeMessage(string.Format(LibResources.BiblioSpecLiteLibrary_ReadFromDatabase_Reading_entries_from__0__library, Path.GetFileName(FilePath))); + var blibFilePath = Path.GetFileName(FilePath); + status = status.ChangeSegments(0, segmentCount).ChangeMessage(string.Format(LibResources.BiblioSpecLiteLibrary_ReadFromDatabase_Reading_entries_from__0__library, blibFilePath)); using (SQLiteCommand select = new SQLiteCommand(_sqliteConnection.Connection)) { // First get header information @@ -833,10 +834,13 @@ private bool ReadFromDatabase(ILoadMonitor loader, IProgressStatus status) return false; } + // Remove and report nonsense entries (e.g. adduct removes more H2O than present in molecule) + libraryEntries = FilterInvalidLibraryEntries(ref status, libraryEntries, blibFilePath); + var valueCache = new ValueCache(); if (hasRetentionTimesTable) // Only a filtered library will have this table { - status = status.ChangeSegments(1, segmentCount).ChangeMessage(string.Format(LibResources.BiblioSpecLiteLibrary_ReadFromDatabase_Reading_retention_times_from__0_, Path.GetFileName(FilePath))); + status = status.ChangeSegments(1, segmentCount).ChangeMessage(string.Format(LibResources.BiblioSpecLiteLibrary_ReadFromDatabase_Reading_retention_times_from__0_, blibFilePath)); var retentionTimeReader = new RetentionTimeReader(FilePath, schemaVer); retentionTimeReader.ReadAllRows(loader, ref status, rows); if (loader.IsCanceled) @@ -872,7 +876,7 @@ private bool ReadFromDatabase(ILoadMonitor loader, IProgressStatus status) _librarySourceFiles = librarySourceFiles.ToArray(); _libraryFiles = new LibraryFiles(_librarySourceFiles.Select(file => file.FilePath)); - SetLibraryEntries(FilterInvalidLibraryEntries(ref status, libraryEntries.OrderBy(spec=>spec.Id))); + SetLibraryEntries(FilterInvalidLibraryEntries(ref status, libraryEntries.OrderBy(spec=>spec.Id), blibFilePath)); EnsureConnections(sm); loader.UpdateProgress(status.ChangeSegments(segmentCount - 1, segmentCount).Complete()); return true; diff --git a/pwiz_tools/Skyline/Model/Lib/EncylopeDiaLibrary.cs b/pwiz_tools/Skyline/Model/Lib/EncylopeDiaLibrary.cs index 49fb6e8e3f..184e9088b4 100644 --- a/pwiz_tools/Skyline/Model/Lib/EncylopeDiaLibrary.cs +++ b/pwiz_tools/Skyline/Model/Lib/EncylopeDiaLibrary.cs @@ -341,7 +341,7 @@ private bool LoadLibraryFromDatabase(ILoadMonitor loader) var spectrumInfos = libKeySourceFileDatas .Where(entry => quantPeptides.Contains(entry.Key)) .Select(entry => MakeSpectrumInfo(entry.Key, entry.Value, sourceFileIds)); - SetLibraryEntries(FilterInvalidLibraryEntries(ref status, spectrumInfos)); + SetLibraryEntries(FilterInvalidLibraryEntries(ref status, spectrumInfos, Path.GetFileName(FilePath))); _sourceFiles = new LibraryFiles(sourceFiles); // ReSharper restore PossibleMultipleEnumeration loader.UpdateProgress(status.Complete()); diff --git a/pwiz_tools/Skyline/Model/Lib/Library.cs b/pwiz_tools/Skyline/Model/Lib/Library.cs index 2c84329160..60d575af6e 100644 --- a/pwiz_tools/Skyline/Model/Lib/Library.cs +++ b/pwiz_tools/Skyline/Model/Lib/Library.cs @@ -1142,15 +1142,19 @@ protected virtual void SetLibraryEntries(IEnumerable entries) _libraryEntries = new LibKeyMap(entryList, entryList.Select(entry=>entry.Key.LibraryKey)); } - protected List FilterInvalidLibraryEntries(ref IProgressStatus status, IEnumerable entries) + // Try to build a precursor from the information in each entry of the library. For those that fail, report the issue + // and remove them from the list of entries. + protected List FilterInvalidLibraryEntries(ref IProgressStatus status, IEnumerable entries, string dataSource) { var validEntries = new List(); var invalidKeys = new List(); foreach (var entry in entries) { - if (!IsValidLibKey(entry.Key)) + if (!IsValidLibKey(entry.Key, out var whyNot)) { invalidKeys.Add(entry.Key); + Messages.WriteAsyncUserMessage(ModelResources.AbstractModificationMatcher_CreateDocNodeFromSettings_In_entry___0___of___1_____2_, + entry.Key, dataSource, whyNot); // Report to immediate window } else { @@ -1162,15 +1166,17 @@ protected List FilterInvalidLibraryEntries(ref IProgressStatus status, IE return validEntries; } - protected bool IsValidLibKey(LibKey libKey) + protected bool IsValidLibKey(LibKey libKey, out string errorMessage) { try { + errorMessage = null; var unused = libKey.LibraryKey.CreatePeptideIdentityObj(); return true; } - catch (Exception) + catch (Exception e) { + errorMessage = e.Message; return false; } } diff --git a/pwiz_tools/Skyline/Model/Lib/NistLibSpec.cs b/pwiz_tools/Skyline/Model/Lib/NistLibSpec.cs index 3e01ba5059..c157f3220b 100644 --- a/pwiz_tools/Skyline/Model/Lib/NistLibSpec.cs +++ b/pwiz_tools/Skyline/Model/Lib/NistLibSpec.cs @@ -1210,7 +1210,7 @@ private bool CreateCache(ILoadMonitor loader, IProgressStatus status, int percen libraryEntries.Add(info); } - libraryEntries = FilterInvalidLibraryEntries(ref status, libraryEntries); + libraryEntries = FilterInvalidLibraryEntries(ref status, libraryEntries, Path.GetFileName(FilePath)); long locationHeaders = outStream.Position; foreach (var info in libraryEntries) diff --git a/pwiz_tools/Skyline/Model/Lib/SpectrumRanker.cs b/pwiz_tools/Skyline/Model/Lib/SpectrumRanker.cs index d6bc6ebd76..8c3c634e9d 100644 --- a/pwiz_tools/Skyline/Model/Lib/SpectrumRanker.cs +++ b/pwiz_tools/Skyline/Model/Lib/SpectrumRanker.cs @@ -173,11 +173,19 @@ public SpectrumRanker(TargetInfo targetInfo, SrmSettings settings, var ionMasses = new IonMasses(calcMatch.GetPrecursorFragmentMass(Sequence), IonTable.EMPTY) .ChangeKnownFragments(knownFragments); - moleculeMasses = - new MoleculeMasses( - SequenceMassCalc.GetMZ( - calcMatchPre.GetPrecursorMass(Sequence.Molecule, null, PrecursorAdduct, - out _), PrecursorAdduct), ionMasses); + try + { + moleculeMasses = + new MoleculeMasses( + SequenceMassCalc.GetMZ( + calcMatchPre.GetPrecursorMass(Sequence.Molecule, null, PrecursorAdduct, + out _), PrecursorAdduct), ionMasses); + } + catch (InvalidChemicalModificationException) + { + moleculeMasses = + new MoleculeMasses(double.NaN, ionMasses); // Precursor m/z can't be calculated + } } else { diff --git a/pwiz_tools/Skyline/Model/Lib/ViewLibraryPepInfo.cs b/pwiz_tools/Skyline/Model/Lib/ViewLibraryPepInfo.cs index 2e724ca24b..37a6379b0d 100644 --- a/pwiz_tools/Skyline/Model/Lib/ViewLibraryPepInfo.cs +++ b/pwiz_tools/Skyline/Model/Lib/ViewLibraryPepInfo.cs @@ -236,8 +236,19 @@ public double CalcMz(SrmSettings settings, { TypedMass massH; if (Target != null) - massH = settings.GetPrecursorCalc(transitionGroup.TransitionGroup.LabelType, mods) - .GetPrecursorMass(Target); + { + if (!Target.IsProteomic) + { + massH = SequenceMassCalc.FormulaMass(BioMassCalc.MONOISOTOPIC, + transitionGroup.PrecursorAdduct.ApplyToMolecule(Target.Molecule.ParsedMolecule)); + return SequenceMassCalc.PersistentMZ(SequenceMassCalc.GetMZ(massH, transitionGroup.PrecursorAdduct.AdductCharge)); + } + else + { + massH = settings.GetPrecursorCalc(transitionGroup.TransitionGroup.LabelType, mods) + .GetPrecursorMass(Target); + } + } else massH = new TypedMass(Key.PrecursorMz ?? 0, MassType.Monoisotopic); return SequenceMassCalc.PersistentMZ(SequenceMassCalc.GetMZ(massH, transitionGroup.PrecursorAdduct)); diff --git a/pwiz_tools/Skyline/Model/Lib/XHunterSpec.cs b/pwiz_tools/Skyline/Model/Lib/XHunterSpec.cs index b50b797a72..899e25f350 100644 --- a/pwiz_tools/Skyline/Model/Lib/XHunterSpec.cs +++ b/pwiz_tools/Skyline/Model/Lib/XHunterSpec.cs @@ -485,7 +485,7 @@ private bool CreateCache(ILoadMonitor loader, IProgressStatus status, int percen } } - libraryEntries = FilterInvalidLibraryEntries(ref status, libraryEntries); + libraryEntries = FilterInvalidLibraryEntries(ref status, libraryEntries, Path.GetFileName(FilePath)); using (FileSaver fs = new FileSaver(CachePath, sm)) using (Stream outStream = sm.CreateStream(fs.SafeName, FileMode.Create, true)) diff --git a/pwiz_tools/Skyline/Model/LibKeyModificationMatcher.cs b/pwiz_tools/Skyline/Model/LibKeyModificationMatcher.cs index 8e62c6b2c6..a2d4c73c5b 100644 --- a/pwiz_tools/Skyline/Model/LibKeyModificationMatcher.cs +++ b/pwiz_tools/Skyline/Model/LibKeyModificationMatcher.cs @@ -36,11 +36,13 @@ public class LibKeyModificationMatcher : AbstractModificationMatcher public PeptideModifications MatcherPepMods { get; set; } public void CreateMatches(SrmSettings settings, IEnumerable libKeys, - MappedList defSetStatic, MappedList defSetHeavy) + MappedList defSetStatic, MappedList defSetHeavy, + string libraryName) { _dictAAMassPairs = new Dictionary>(); _libKeys = libKeys.GetEnumerator(); - InitMatcherSettings(settings, defSetStatic, defSetHeavy); + LibraryName = libraryName; + InitMatcherSettings(settings, defSetStatic, defSetHeavy, libraryName); MatcherPepMods = CreateMatcherPeptideSettings(settings); } diff --git a/pwiz_tools/Skyline/Model/ModelResources.designer.cs b/pwiz_tools/Skyline/Model/ModelResources.designer.cs index bdefa7d37f..a2b3c9792d 100644 --- a/pwiz_tools/Skyline/Model/ModelResources.designer.cs +++ b/pwiz_tools/Skyline/Model/ModelResources.designer.cs @@ -202,6 +202,25 @@ public static string AbstractMassListExporter_ExportScheduledBuckets_transitions } } + /// + /// Looks up a localized string similar to In "{0}": {1}. + /// + public static string AbstractModificationMatcher_CreateDocNodeFromSettings_In___0_____1_ { + get { + return ResourceManager.GetString("AbstractModificationMatcher_CreateDocNodeFromSettings_In___0_____1_", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In entry "{0}" of "{1}": {2}. + /// + public static string AbstractModificationMatcher_CreateDocNodeFromSettings_In_entry___0___of___1_____2_ { + get { + return ResourceManager.GetString("AbstractModificationMatcher_CreateDocNodeFromSettings_In_entry___0___of___1_____2" + + "_", resourceCulture); + } + } + /// /// Looks up a localized string similar to The following modifications could not be interpreted.. /// diff --git a/pwiz_tools/Skyline/Model/ModelResources.resx b/pwiz_tools/Skyline/Model/ModelResources.resx index 2e8b14ee77..0432ce911e 100644 --- a/pwiz_tools/Skyline/Model/ModelResources.resx +++ b/pwiz_tools/Skyline/Model/ModelResources.resx @@ -899,4 +899,10 @@ Would you like to continue? Detailed Info + + In "{0}": {1} + + + In entry "{0}" of "{1}": {2} + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Model/ModificationMatcher.cs b/pwiz_tools/Skyline/Model/ModificationMatcher.cs index e70322d004..924ec4ed09 100644 --- a/pwiz_tools/Skyline/Model/ModificationMatcher.cs +++ b/pwiz_tools/Skyline/Model/ModificationMatcher.cs @@ -62,7 +62,7 @@ public void CreateMatches(SrmSettings settings, IEnumerable sequences, _sequences = sequences.GetEnumerator(); - InitMatcherSettings(settings, defSetStatic, defSetHeavy); + InitMatcherSettings(settings, defSetStatic, defSetHeavy, null); if (UnmatchedSequences.Count > 0) { UnmatchedSequences.Sort(); diff --git a/pwiz_tools/Skyline/Model/SmallMoleculeTransitionListReader.cs b/pwiz_tools/Skyline/Model/SmallMoleculeTransitionListReader.cs index 5b29ac7c11..2d12732966 100644 --- a/pwiz_tools/Skyline/Model/SmallMoleculeTransitionListReader.cs +++ b/pwiz_tools/Skyline/Model/SmallMoleculeTransitionListReader.cs @@ -382,6 +382,7 @@ public static bool IsParserException(Exception exception) { return exception is InvalidOperationException || exception is InvalidDataException + || exception is InvalidChemicalModificationException || exception is ArgumentException; } diff --git a/pwiz_tools/Skyline/SettingsUI/AddModificationsDlg.cs b/pwiz_tools/Skyline/SettingsUI/AddModificationsDlg.cs index c49c28e0fe..c2563e79b1 100644 --- a/pwiz_tools/Skyline/SettingsUI/AddModificationsDlg.cs +++ b/pwiz_tools/Skyline/SettingsUI/AddModificationsDlg.cs @@ -33,6 +33,7 @@ public override string ToString() public LibKeyModificationMatcher Matcher { get; private set; } private readonly LibKey[] _libKeys; + private readonly string _libraryName; private readonly HashSet _userDefinedTypedMods; private readonly IsotopeLabelType _defaultHeavyLabelType; @@ -57,6 +58,7 @@ public AddModificationsDlg(SrmSettings settings, Library library) Matcher = new LibKeyModificationMatcher(); _libKeys = library.Keys.ToArray(); + _libraryName = library.Name; _userDefinedTypedMods = new HashSet(); _settings = settings; NewDocumentModsStatic = new StaticMod[0]; @@ -94,7 +96,7 @@ private void UpdateModificationMatches() { // Update matcher Matcher.ClearMatches(); - Matcher.CreateMatches(_settings, _libKeys, DefaultStatic, DefaultHeavy); + Matcher.CreateMatches(_settings, _libKeys, DefaultStatic, DefaultHeavy, _libraryName); // Update UI listMatched.Items.Clear(); diff --git a/pwiz_tools/Skyline/SettingsUI/ViewLibraryDlg.cs b/pwiz_tools/Skyline/SettingsUI/ViewLibraryDlg.cs index 1b574eebb1..6f6fb2b7e4 100644 --- a/pwiz_tools/Skyline/SettingsUI/ViewLibraryDlg.cs +++ b/pwiz_tools/Skyline/SettingsUI/ViewLibraryDlg.cs @@ -482,7 +482,7 @@ public bool MatchModifications() return false; var matcher = new LibKeyModificationMatcher(); - matcher.CreateMatches(Document.Settings, _selectedLibrary.Keys, Settings.Default.StaticModList, Settings.Default.HeavyModList); + matcher.CreateMatches(Document.Settings, _selectedLibrary.Keys, Settings.Default.StaticModList, Settings.Default.HeavyModList, _selectedLibrary.Name); if (string.IsNullOrEmpty(matcher.FoundMatches) && !matcher.UnmatchedSequences.Any()) { _matcher = matcher; @@ -571,6 +571,7 @@ private void UpdateStatusArea() /// private void UpdateListPeptide(int selectPeptideIndex) { + _matcher.LibraryName = _selectedLibName; var pepMatcher = new ViewLibraryPepMatching(Document, _selectedLibrary, _selectedSpec, _matcher, _peptides); listPeptide.BeginUpdate(); @@ -922,6 +923,11 @@ public void UpdateUI(bool selectionChanged = true) SetGraphItem(new NoDataMSGraphItem(SettingsUIResources.ViewLibraryDlg_UpdateUI_Unauthorized_access_attempting_to_read_from_library_)); return; } + catch (InvalidChemicalModificationException e) + { + SetGraphItem(new NoDataMSGraphItem(e.Message)); + return; + } catch (IOException) { SetGraphItem(new NoDataMSGraphItem(SettingsUIResources.ViewLibraryDlg_UpdateUI_Failure_loading_spectrum_Library_may_be_corrupted)); @@ -2675,15 +2681,25 @@ public PeptideTipProvider(ViewLibraryPepInfo pepInfo, LibKeyModificationMatcher _smallMoleculePartsToDraw = smallMolInfo.LocalizedKeyValuePairs; } + _mzRangePartsToDraw = new List(); if (_pepInfo.Target != null) { // build mz range parts to draw - _mz = _pepInfo.CalcMz(_settings, transitionGroup, mods); - _mzRangePartsToDraw = GetMzRangeItemsToDraw(_mz); + try + { + _mz = _pepInfo.CalcMz(_settings, transitionGroup, mods); + _mzRangePartsToDraw = GetMzRangeItemsToDraw(_mz); + } + catch (InvalidChemicalModificationException e) + { + _mz = double.NaN; + // Show the error at the top of the tip + _seqPartsToDraw.Insert(0,new TextColor(e.Message, Brushes.Red)); + _seqPartsToDraw.Insert(1, new TextColor(Environment.NewLine)); + } } else { - _mzRangePartsToDraw = new List(); var precursorKey = _pepInfo.Key.LibraryKey as PrecursorLibraryKey; if (precursorKey != null) { @@ -2926,13 +2942,23 @@ private SizeF DrawTextParts(Graphics g, float startX, float startY, ListTrue True True + True True True True @@ -283,6 +284,8 @@ public $classname$ Change$PROP$($TYPE$ value) { True True True + True + True True True True @@ -291,6 +294,7 @@ public $classname$ Change$PROP$($TYPE$ value) { True True True + True True True True @@ -336,6 +340,7 @@ public $classname$ Change$PROP$($TYPE$ value) { True True True + True True True True @@ -359,11 +364,13 @@ public $classname$ Change$PROP$($TYPE$ value) { True True True + True True True True True True + True True True True @@ -383,6 +390,7 @@ public $classname$ Change$PROP$($TYPE$ value) { True True True + True True True True \ No newline at end of file diff --git a/pwiz_tools/Skyline/Test/AdductTest.cs b/pwiz_tools/Skyline/Test/AdductTest.cs index 3790c5ace4..629f88e781 100644 --- a/pwiz_tools/Skyline/Test/AdductTest.cs +++ b/pwiz_tools/Skyline/Test/AdductTest.cs @@ -105,7 +105,7 @@ private void TestTaxolAdduct(string adductText, double expectedMz, int expectedC coverage.Add(adduct.AsFormula()); } - private void TestException(string formula, string adductText) + private void TestInvalidDataException(string formula, string adductText) { AssertEx.ThrowsException(() => { @@ -114,6 +114,15 @@ private void TestException(string formula, string adductText) }); } + private void TestInvalidChemicalModificationException(string formula, string adductText) + { + AssertEx.ThrowsException(() => + { + var adduct = Adduct.FromStringAssumeProtonated(adductText); + IonInfo.ApplyAdductToFormula(formula, adduct); + }); + } + private string RoundtripFormulaString(string f) { ParsedMolecule.TryParseFormula(f, out var mol, out _); @@ -452,19 +461,19 @@ public void AdductParserTest() mz = BioMassCalc.CalculateIonMass(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy); Assert.AreEqual(2 * (massHectochlorin + 1.23456), mz, .001); - TestException(PENTANE, "zM+2H"); // That "z" doesn't make any sense as a mass multiplier (must be a positive integer) - TestException(PENTANE, "-2M+2H"); // That "-2" doesn't make any sense as a mass multiplier (must be a positive integer) - TestException("", "+M"); // Meaningless, used to cause an exception in our parser - TestException(Hectochlorin, "M3Cl37+H"); // Trying to label more chlorines than exist in the molecule - TestException(Hectochlorin, "M-3Cl+H"); // Trying to remove more chlorines than exist in the molecule - TestException(PENTANE, "M+foo+H"); // Unknown adduct - TestException(PENTANE, "M2Cl37H+H"); // nonsense label ("2Cl37H2" would make sense, but regular H doesn't belong) - TestException(PENTANE, "M+2H+"); // Trailing sign - we now understand this as a charge state declaration, but this one doesn't match described charge - TestException(PENTANE, "[M-2H]3-"); // Declared charge doesn't match described charge - TestException(PENTANE, "[M-]3-"); // Declared charge doesn't match described charge - TestException(PENTANE, "[M+]-"); // Declared charge doesn't match described charge - TestException(PENTANE, "[M+2]-"); // Declared charge doesn't match described charge - TestException(PENTANE, "[M+2]+3"); // Declared charge doesn't match described charge + TestInvalidDataException(PENTANE, "zM+2H"); // That "z" doesn't make any sense as a mass multiplier (must be a positive integer) + TestInvalidDataException(PENTANE, "-2M+2H"); // That "-2" doesn't make any sense as a mass multiplier (must be a positive integer) + TestInvalidDataException("", "+M"); // Meaningless, used to cause an exception in our parser + TestInvalidChemicalModificationException(Hectochlorin, "M3Cl37+H"); // Trying to label more chlorines than exist in the molecule + TestInvalidChemicalModificationException(Hectochlorin, "M-3Cl+H"); // Trying to remove more chlorines than exist in the molecule + TestInvalidDataException(PENTANE, "M+foo+H"); // Unknown adduct + TestInvalidDataException(PENTANE, "M2Cl37H+H"); // nonsense label ("2Cl37H2" would make sense, but regular H doesn't belong) + TestInvalidDataException(PENTANE, "M+2H+"); // Trailing sign - we now understand this as a charge state declaration, but this one doesn't match described charge + TestInvalidDataException(PENTANE, "[M-2H]3-"); // Declared charge doesn't match described charge + TestInvalidDataException(PENTANE, "[M-]3-"); // Declared charge doesn't match described charge + TestInvalidDataException(PENTANE, "[M+]-"); // Declared charge doesn't match described charge + TestInvalidDataException(PENTANE, "[M+2]-"); // Declared charge doesn't match described charge + TestInvalidDataException(PENTANE, "[M+2]+3"); // Declared charge doesn't match described charge // Test label stripping Assert.AreEqual("C5H9NO2S", (new IonInfo("C5H9H'3NO2S[M-3H]")).UnlabeledFormula.ToString()); diff --git a/pwiz_tools/Skyline/Test/CrosslinkModTest.cs b/pwiz_tools/Skyline/Test/CrosslinkModTest.cs index 38fde9e4ac..166e52003d 100644 --- a/pwiz_tools/Skyline/Test/CrosslinkModTest.cs +++ b/pwiz_tools/Skyline/Test/CrosslinkModTest.cs @@ -379,7 +379,7 @@ public void TestLooplinkCrosslinkLibraryKey() ))); var libKeyModificationMatcher = new LibKeyModificationMatcher(); libKeyModificationMatcher.CreateMatches(srmSettings, new[] {new LibKey(crosslinkLibraryKey)}, - new MappedList(), new MappedList()); + new MappedList(), new MappedList(), null); var peptideDocNode = libKeyModificationMatcher.CreateDocNodeFromSettings(new LibKey(crosslinkLibraryKey), new Peptide("AKIQDKEGIPPDQQR"), SrmSettingsDiff.ALL, out _); diff --git a/pwiz_tools/Skyline/Test/ModificationMatcherTest.cs b/pwiz_tools/Skyline/Test/ModificationMatcherTest.cs index abb61e19cd..dc18827fce 100644 --- a/pwiz_tools/Skyline/Test/ModificationMatcherTest.cs +++ b/pwiz_tools/Skyline/Test/ModificationMatcherTest.cs @@ -248,7 +248,7 @@ public void TestModificationMatcher() int yeastLibIndex = docLibraries.IndexOf(library => Equals(library.Name, yeastLibSpec.Name)); libkeyModMatcher.CreateMatches(modMatchDocContainer.Document.Settings, - docLibraries[anlLibIndex].Keys, defSetSetLight, defSetHeavy); + docLibraries[anlLibIndex].Keys, defSetSetLight, defSetHeavy, anlLibSpec.Name); // Test can match 15N Assert.IsTrue(libkeyModMatcher.Matches.Values.Contains(match => @@ -267,7 +267,7 @@ public void TestModificationMatcher() // Test can match Cysteine (Implicit) and Met Ox (variable) libkeyModMatcher.CreateMatches(modMatchDocContainer.Document.Settings, - docLibraries[yeastLibIndex].Keys, defSetSetLight, defSetHeavy); + docLibraries[yeastLibIndex].Keys, defSetSetLight, defSetHeavy, yeastLibSpec.Name); Assert.IsTrue(libkeyModMatcher.MatcherPepMods.StaticModifications.Contains(mod => mod.ParsedMolecule.Equals(UniMod.GetModification(StaticModList.DEFAULT_NAME, true).ParsedMolecule) && !mod.IsVariable)); Assert.IsTrue(libkeyModMatcher.MatcherPepMods.StaticModifications.Contains(mod => diff --git a/pwiz_tools/Skyline/TestFunctional/LibraryBuildTest.cs b/pwiz_tools/Skyline/TestFunctional/LibraryBuildTest.cs index d927359491..29a46bca26 100644 --- a/pwiz_tools/Skyline/TestFunctional/LibraryBuildTest.cs +++ b/pwiz_tools/Skyline/TestFunctional/LibraryBuildTest.cs @@ -177,6 +177,20 @@ private void MainTest() RunUI(() => AssertEx.IsTrue(viewLibUI.GraphItem.IonLabels.Any())); OkDialog(viewLibUI, viewLibUI.CancelDialog); + // Now check for handling when adduct tries to label more atoms than are present in the molecule + var sslLines = File.ReadAllLines(TestFilesDir.GetTestPath("library_valid\\heavy_adduct.ssl")).ToList(); + var badSSL = sslLines[1].Replace("ms2\t2","ms2\t3").Replace(@"M6C13", @"M66C13"); // Molecule is C54H83N15O20 so this makes no sense + sslLines.Add(badSSL); + File.WriteAllLines(TestFilesDir.GetTestPath("library_valid\\heavy_adduct_bad.ssl"), sslLines); + BuildLibraryValid("heavy_adduct_bad.ssl", true, false, false, 2); + // Make sure explorer handles this adduct, which is a bad match for the molecule + viewLibUI = ShowDialog(SkylineWindow.ViewSpectralLibraries); + RunUI(() => AssertEx.IsTrue(viewLibUI.GraphItem.IonLabels.Any())); + // Add All should cause some notifications since one of them is bad + var filterMatchedDlg = ShowDialog(() => viewLibUI.AddAllPeptides()); + OkDialog(filterMatchedDlg, filterMatchedDlg.CancelDialog); + OkDialog(viewLibUI, viewLibUI.CancelDialog); + // Barbara added code to ProteoWizard to rebuild a missing or invalid mzXML index // BuildLibraryError("bad_mzxml.pep.XML", " not found"); BuildLibraryValid(TestFilesDir.GetTestPath("library_errors"), new[] { "bad_mzxml.pep.XML" }, false, false, false, 1, 0, false); diff --git a/pwiz_tools/Skyline/Util/Adduct.cs b/pwiz_tools/Skyline/Util/Adduct.cs index 5950b09e36..f7179076d5 100644 --- a/pwiz_tools/Skyline/Util/Adduct.cs +++ b/pwiz_tools/Skyline/Util/Adduct.cs @@ -66,6 +66,17 @@ public T this[Adduct a] public IEnumerable Keys { get { return _dict.Keys; } } } + /// + /// Thrown when a loss would remove more atoms than are found in a molecular formula e.g. water loss from C2S5H12 (no oxygen), + /// or label would change more atoms than are in the formula e.g. 3C' on C2S5H12 + /// + public class InvalidChemicalModificationException : IOException + { + public InvalidChemicalModificationException(string message) : base(message) + { + } + } + public class Adduct : Immutable, IComparable, IEquatable, IAuditLogObject { private Molecule Composition { get; set; } // The chemical makeup of the adduct - the "2H" part in 4M3Cl37+2H @@ -1448,7 +1459,7 @@ public ParsedMolecule ApplyToMolecule(ParsedMolecule molecule) if (!molecule.IsMassOnly && count < 0 && !Equals(pair.Key, BioMassCalc.H)) // Treat H loss as a general proton loss { - throw new InvalidDataException( + throw new InvalidChemicalModificationException( string.Format(Resources.Adduct_ApplyToMolecule_Adduct___0___calls_for_removing_more__1__atoms_than_are_found_in_the_molecule__2_, this, pair.Key, molecule.ToString())); } @@ -1508,7 +1519,7 @@ private ParsedMolecule ApplyIsotopeValues(ParsedMolecule molecule, int massMulti } else // Can't remove that which is not there { - throw new InvalidDataException( + throw new InvalidChemicalModificationException( string.Format( UtilResources .Adduct_ApplyToMolecule_Adduct___0___calls_for_labeling_more__1__atoms_than_are_found_in_the_molecule__2_, diff --git a/pwiz_tools/Skyline/Util/TraceWarningListener.cs b/pwiz_tools/Skyline/Util/TraceWarningListener.cs index ebcf938742..7e9ca35860 100644 --- a/pwiz_tools/Skyline/Util/TraceWarningListener.cs +++ b/pwiz_tools/Skyline/Util/TraceWarningListener.cs @@ -16,19 +16,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; namespace pwiz.Skyline.Util { public class TraceWarningListener : TraceListener { private TextWriter _textWriter; + + private Dictionary _recentMessages; + public TraceWarningListener(TextWriter textWriter) { // N.B. if you want to see DebugMessage.AsynchWrite messages too, add .Information to the flags here Filter = new EventTypeFilter(SourceLevels.Warning); _textWriter = textWriter; + _recentMessages = new Dictionary(); } public override void Write(string message) @@ -41,6 +49,31 @@ public override void WriteLine(string message) // Ignore messages which have no TraceEventType } + private bool IsRepeated(string message) + { + var now = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + lock (_recentMessages) + { + // Clear out any older messages + foreach (var staleMessage in _recentMessages.Where(staleMessage => staleMessage.Value + 500 < now).ToArray()) + { + _recentMessages.Remove(staleMessage.Key); + } + + // If this message has been shown recently, don't show it again + if (_recentMessages.ContainsKey(message)) + { + return true; + } + + // Be ready to ignore rapidly repeating messages + _recentMessages.Add(message, now); + + // Not a recent repeat + return false; + } + } + /// /// Override the TraceEvent method to prevent "WriteHeader" and "WriteFooter" from being called. /// @@ -54,14 +87,17 @@ public override void TraceEvent( { if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null)) return; - _textWriter.WriteLine(format, args); + var message = string.Format(format, args); + if (!IsRepeated(message)) + _textWriter.WriteLine(message); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null)) return; - _textWriter.WriteLine(message); + if (!IsRepeated(message)) + _textWriter.WriteLine(message); } } }