@@ -45,12 +45,10 @@ public class MzMlSpectrumWriter : SpectrumWriter
4545 private readonly Dictionary < IonizationModeType , CVParamType > _ionizationTypes =
4646 new Dictionary < IonizationModeType , CVParamType > ( ) ;
4747
48- // Precursor scan number for reference in the precursor element of an MS2 spectrum
49- private int _precursorScanNumber ;
50-
5148 // Precursor scan number (value) and isolation m/z (key) for reference in the precursor element of an MSn spectrum
5249 private readonly Dictionary < string , int > _precursorScanNumbers = new Dictionary < string , int > ( ) ;
5350
51+ //Precursor information for scans
5452 private Dictionary < int , PrecursorInfo > _precursorTree = new Dictionary < int , PrecursorInfo > ( ) ;
5553
5654 private const string SourceFileId = "RAW1" ;
@@ -1156,6 +1154,9 @@ private ChromatogramType TraceToChromatogram(ChromatogramSignal trace, string ch
11561154 /// <returns>The SpectrumType object</returns>
11571155 private SpectrumType ConstructMSSpectrum ( int scanNumber )
11581156 {
1157+ // Last precursor scan number for use in MSn spectrum
1158+ int _precursorScanNumber = 0 ;
1159+
11591160 // Get each scan from the RAW file
11601161 var scan = Scan . FromFile ( _rawFile , scanNumber ) ;
11611162
@@ -1272,19 +1273,44 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
12721273 else //try getting it from the scan filter
12731274 {
12741275 var parts = Regex . Split ( result . Groups [ 1 ] . Value , " " ) ;
1275- string parentFilter = String . Join ( " " , parts . Take ( parts . Length - 1 ) ) ;
1276+
1277+ //find the position of the first (from the end) precursor with a different mass
1278+ //to account for possible supplementary activations written in the filter
1279+ var lastIonMass = parts . Last ( ) . Split ( '@' ) . First ( ) ;
1280+ int last = parts . Length ;
1281+ while ( last > 0 &&
1282+ parts [ last - 1 ] . Split ( '@' ) . First ( ) == lastIonMass )
1283+ {
1284+ last -- ;
1285+ }
1286+
1287+ string parentFilter = String . Join ( " " , parts . Take ( last ) ) ;
12761288 if ( _precursorScanNumbers . ContainsKey ( parentFilter ) )
12771289 {
12781290 _precursorScanNumber = _precursorScanNumbers [ parentFilter ] ;
12791291 }
12801292 }
12811293
1282- // Construct and set the precursor list element of the spectrum
1283- spectrum . precursorList =
1284- ConstructPrecursorList ( scanEvent , charge , scanFilter . MSOrder , monoisotopicMz , isolationWidth ,
1285- SPSMasses ) ;
1294+ if ( _precursorScanNumber > 0 )
1295+ {
1296+ // Construct and set the precursor list element of the spectrum
1297+ spectrum . precursorList =
1298+ ConstructPrecursorList ( _precursorScanNumber , scanEvent , charge , monoisotopicMz , isolationWidth ,
1299+ SPSMasses , out var reactionCount ) ;
12861300
1287- _precursorTree [ scanNumber ] = new PrecursorInfo ( _precursorScanNumber , spectrum . precursorList . precursor ) ;
1301+ //save precursor information for later reference
1302+ _precursorTree [ scanNumber ] = new PrecursorInfo ( _precursorScanNumber , reactionCount , spectrum . precursorList . precursor ) ;
1303+ }
1304+ else
1305+ {
1306+ spectrum . precursorList = new PrecursorListType
1307+ {
1308+ count = "0" ,
1309+ precursor = new PrecursorType [ 0 ]
1310+ } ;
1311+
1312+ Log . Error ( $ "Failed finding precursor for { scanNumber } ") ;
1313+ }
12881314 }
12891315 else
12901316 {
@@ -1873,38 +1899,40 @@ private SpectrumType ConstructPDASpectrum(int scanNumber, int instrumentNumber)
18731899 /// <summary>
18741900 /// Populate the precursor list element
18751901 /// </summary>
1902+ /// <param name="precursorScanNumber">scan number for the last precursor</param>
18761903 /// <param name="scanEvent">the scan event</param>
1877- /// <param name="charge">the charge</param>
1878- /// <param name="msLevel">the MS level</param>
1879- /// <param name="monoisotopicMz">the monoisotopic m/z value</param>
1880- /// <param name="isolationWidth">the isolation width</param>
1904+ /// <param name="charge">the charge from trailer</param>
1905+ /// <param name="monoisotopicMz">the monoisotopic m/z value from trailer</param>
1906+ /// <param name="isolationWidth">the isolation width value from trailer</param>
18811907 /// <param name="SPSMasses">List of masses selected for SPS</param>
1908+ /// <param name="reactionCount">Number of activation reactions (see PrecursorInfo for details)</param>
18821909 /// <returns>the precursor list</returns>
1883- private PrecursorListType ConstructPrecursorList ( IScanEventBase scanEvent , int ? charge , MSOrderType msLevel ,
1884- double ? monoisotopicMz , double ? isolationWidth , List < double > SPSMasses )
1910+ private PrecursorListType ConstructPrecursorList ( int precursorScanNumber , IScanEventBase scanEvent , int ? charge ,
1911+ double ? monoisotopicMz , double ? isolationWidth , List < double > SPSMasses , out int reactionCount )
18851912 {
1886- // Construct the precursors
1887-
18881913 List < PrecursorType > precursors = new List < PrecursorType > ( ) ;
18891914
1915+ // Get precursors from earlier levels
1916+ var prevPrecursors = _precursorTree [ precursorScanNumber ] ;
1917+
18901918 var spectrumRef = "" ;
1891- int precursorScanNumber = _precursorScanNumber ;
1919+ int msLevel = ( int ) scanEvent . MSOrder ;
18921920 IReaction reaction = null ;
18931921 var precursorMz = 0.0 ;
1922+ reactionCount = prevPrecursors . ReactionCount ;
18941923 try
18951924 {
1896- spectrumRef = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , _precursorScanNumber ) ;
1897- reaction = scanEvent . GetReaction ( ( int ) msLevel - 2 ) ;
1898- precursorScanNumber = _precursorScanNumber ;
1899-
1925+ spectrumRef = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , precursorScanNumber ) ;
1926+ reaction = scanEvent . GetReaction ( reactionCount ) ;
1927+
19001928 precursorMz = reaction . PrecursorMass ;
19011929
19021930 //if isolation width was not found in the trailer, try to get one from the reaction
19031931 if ( isolationWidth == null ) isolationWidth = reaction . IsolationWidth ;
19041932 }
19051933 catch ( ArgumentOutOfRangeException )
19061934 {
1907- // Do nothing
1935+ Log . Warn ( $ "Failed to get reaction when parsing precursor { precursorScanNumber } " ) ;
19081936 }
19091937
19101938 var precursor = new PrecursorType
@@ -1946,7 +1974,7 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
19461974 if ( selectedIonMz > ZeroDelta )
19471975 {
19481976 var selectedIonIntensity = CalculatePrecursorPeakIntensity ( _rawFile , precursorScanNumber , reaction . PrecursorMass , isolationWidth ,
1949- ParseInput . NoPeakPicking . Contains ( ( int ) msLevel - 1 ) ) ;
1977+ ParseInput . NoPeakPicking . Contains ( msLevel - 1 ) ) ;
19501978 if ( selectedIonIntensity != null )
19511979 {
19521980 ionCvParams . Add ( new CVParamType
@@ -2041,12 +2069,15 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
20412069 activationCvParams . Add ( activation ) ;
20422070 }
20432071
2044- // TODO: implement supplemental activation
2072+ //increase reaction count after successful parsing
2073+ reactionCount ++ ;
2074+
20452075 if ( scanEvent . SupplementalActivation == TriState . On )
2076+ //the property is On if *at least* one of the levels had SA (i.e. not necissirily the last one), thus we need to try (and posibly fail)
20462077 {
20472078 try
20482079 {
2049- reaction = scanEvent . GetReaction ( 1 ) ;
2080+ reaction = scanEvent . GetReaction ( reactionCount ) ;
20502081
20512082 if ( reaction != null )
20522083 {
@@ -2065,36 +2096,40 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
20652096 } ) ;
20662097 }
20672098
2068- // Add this supplemental CV term
2069- // TODO: use a more generic approach
2070- if ( reaction . ActivationType == ActivationType . HigherEnergyCollisionalDissociation )
2099+ // Add the supplemental CV term
2100+ switch ( reaction . ActivationType )
20712101 {
2072- activationCvParams . Add ( new CVParamType
2073- {
2074- accession = "MS:1002678" ,
2075- name = "supplemental beam-type collision-induced dissociation" ,
2076- cvRef = "MS" ,
2077- value = ""
2078- } ) ;
2079- }
2102+ case ActivationType . HigherEnergyCollisionalDissociation :
2103+ activationCvParams . Add ( new CVParamType
2104+ {
2105+ accession = "MS:1002678" ,
2106+ name = "supplemental beam-type collision-induced dissociation" ,
2107+ cvRef = "MS" ,
2108+ value = ""
2109+ } ) ; break ;
2110+
2111+ case ActivationType . CollisionInducedDissociation :
2112+ activationCvParams . Add ( new CVParamType
2113+ {
2114+ accession = "MS:1002679" ,
2115+ name = "supplemental collision-induced dissociation" ,
2116+ cvRef = "MS" ,
2117+ value = ""
2118+ } ) ; break ;
2119+
2120+ default :
2121+ Log . Warn ( $ "Unknown supplemental activation type: { reaction . ActivationType } ") ;
2122+ break ;
20802123
2081- if ( ! OntologyMapping . DissociationTypes . TryGetValue ( reaction . ActivationType , out var activation ) )
2082- {
2083- activation = new CVParamType
2084- {
2085- accession = "MS:1000044" ,
2086- name = "Activation Method" ,
2087- cvRef = "MS" ,
2088- value = ""
2089- } ;
20902124 }
20912125
2092- activationCvParams . Add ( activation ) ;
2126+ //increase reaction count after successful parsing
2127+ reactionCount ++ ;
20932128 }
20942129 }
20952130 catch ( ArgumentOutOfRangeException )
20962131 {
2097- // Do nothing
2132+ // If we failed do nothing
20982133 }
20992134 }
21002135
@@ -2145,11 +2180,8 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
21452180 precursors . Add ( SPSPrecursor ) ;
21462181 }
21472182
2148- //Add precursors from previous levels
2149- if ( _precursorTree [ precursorScanNumber ] . Scan != 0 )
2150- {
2151- precursors . AddRange ( _precursorTree [ precursorScanNumber ] . Precursors ) ;
2152- }
2183+ //Add precursors from previous levels to the end of the list
2184+ precursors . AddRange ( prevPrecursors . Precursors ) ;
21532185
21542186 return new PrecursorListType
21552187 {
0 commit comments