@@ -27,7 +27,7 @@ public class MzMlSpectrumWriter : SpectrumWriter
2727 private static readonly ILog Log =
2828 LogManager . GetLogger ( MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2929
30- private readonly Regex _filterStringIsolationMzPattern = new Regex ( @"ms2 (.*?)@ " ) ;
30+ private readonly Regex _filterStringIsolationMzPattern = new Regex ( @"ms\d+ (.+?) \[ " ) ;
3131
3232 // Tune version < 3 produces multiple trailer entry like "SPS Mass [number]"
3333 private readonly Regex _spSentry = new Regex ( @"SPS Mass\s+\d+:" ) ;
@@ -46,11 +46,12 @@ public class MzMlSpectrumWriter : SpectrumWriter
4646 new Dictionary < IonizationModeType , CVParamType > ( ) ;
4747
4848 // Precursor scan number for reference in the precursor element of an MS2 spectrum
49- private int _precursorMs1ScanNumber ;
49+ private int _precursorScanNumber ;
5050
51- // Precursor scan number (value) and isolation m/z (key) for reference in the precursor element of an MS3 spectrum
52- private readonly LimitedSizeDictionary < string , int > _precursorMs2ScanNumbers =
53- new LimitedSizeDictionary < string , int > ( 40 ) ;
51+ // Precursor scan number (value) and isolation m/z (key) for reference in the precursor element of an MSn spectrum
52+ private readonly Dictionary < string , int > _precursorScanNumbers = new Dictionary < string , int > ( ) ;
53+
54+ private Dictionary < int , PrecursorInfo > _precursorTree = new Dictionary < int , PrecursorInfo > ( ) ;
5455
5556 private const string SourceFileId = "RAW1" ;
5657 private readonly XmlSerializerFactory _factory = new XmlSerializerFactory ( ) ;
@@ -1161,6 +1162,9 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
11611162 // Get the scan filter for this scan number
11621163 var scanFilter = _rawFile . GetFilterForScanNumber ( scanNumber ) ;
11631164
1165+ // Get scan ms level
1166+ var msLevel = ( int ) scanFilter . MSOrder ;
1167+
11641168 // Get the scan event for this scan number
11651169 var scanEvent = _rawFile . GetScanEventForScanNumber ( scanNumber ) ;
11661170 var spectrum = new SpectrumType
@@ -1176,7 +1180,7 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
11761180 {
11771181 name = "ms level" ,
11781182 accession = "MS:1000511" ,
1179- value = ( ( int ) scanFilter . MSOrder ) . ToString ( CultureInfo . InvariantCulture ) ,
1183+ value = msLevel . ToString ( ) ,
11801184 cvRef = "MS"
11811185 }
11821186 } ;
@@ -1216,139 +1220,75 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
12161220 }
12171221 }
12181222
1219- //Older iterative version that works with trailer directly, can be removed if the new object version is better
1220- /*var trailerData = _rawFile.GetTrailerExtraInformation(scanNumber);
1221- int? charge = null;
1222- double? monoisotopicMz = null;
1223- double? ionInjectionTime = null;
1224- double? FAIMSCV = null;
1225- bool FAIMSON = false;
1226- List<double> SPSMasses = new List<double>();
1227- double? isolationWidth = null;
1228-
1229- for (var i = 0; i < trailerData.Length; i++)
1230- {
1231- if (trailerData.Labels[i] == "Charge State:")
1232- {
1233- if (Convert.ToInt32(trailerData.Values[i]) > 0)
1234- {
1235- charge = Convert.ToInt32(trailerData.Values[i]);
1236- }
1237- }
1238-
1239- if (trailerData.Labels[i] == "Monoisotopic M/Z:")
1240- {
1241- monoisotopicMz = double.Parse(trailerData.Values[i], NumberStyles.Any,
1242- CultureInfo.CurrentCulture);
1243- }
1244-
1245- if (trailerData.Labels[i] == "Ion Injection Time (ms):")
1246- {
1247- ionInjectionTime = double.Parse(trailerData.Values[i], NumberStyles.Any,
1248- CultureInfo.CurrentCulture);
1249- }
1250-
1251- if (trailerData.Labels[i] == "MS" + (int) scanFilter.MSOrder + " Isolation Width:")
1252- {
1253- isolationWidth = double.Parse(trailerData.Values[i], NumberStyles.Any,
1254- CultureInfo.CurrentCulture);
1255- }
1256-
1257- if (trailerData.Labels[i] == "FAIMS CV:")
1258- {
1259- FAIMSCV = double.Parse(trailerData.Values[i], NumberStyles.Any,
1260- CultureInfo.CurrentCulture);
1261- }
1262-
1263- // Check if the FAIMS voltage is on
1264- if (trailerData.Labels[i] == "FAIMS Voltage On:" && trailerData.Values[i].Equals("Yes"))
1265- {
1266- FAIMSON = true;
1267- }
1268-
1269- // Tune version < 3 produced trailer entry like "SPS Mass #", one entry per mass
1270- if (_spSentry.IsMatch(trailerData.Labels[i]))
1271- {
1272- var mass = double.Parse(trailerData.Values[i]);
1273- if (mass > 0) SPSMasses.Add(mass); // zero means mass does not exist
1274- }
1275-
1276- // Tune version == 3 produces trailer entry "SPS Masses", comma separated list of masses
1277- if (_spSentry3.IsMatch(trailerData.Labels[i]))
1278- {
1279- foreach (var mass in trailerData.Values[i].Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
1280- {
1281- SPSMasses.Add(double.Parse(mass));
1282- }
1283-
1284- }
1285- }*/
1286-
12871223 // Construct and set the scan list element of the spectrum
12881224 var scanListType = ConstructScanList ( scanNumber , scan , scanFilter , scanEvent , monoisotopicMz ,
12891225 ionInjectionTime ) ;
12901226 spectrum . scanList = scanListType ;
12911227
1292- switch ( scanFilter . MSOrder )
1228+
1229+ if ( msLevel == 1 )
12931230 {
1294- case MSOrderType . Ms :
1295- spectrumCvParams . Add ( new CVParamType
1296- {
1297- accession = "MS:1000579" ,
1298- cvRef = "MS" ,
1299- name = "MS1 spectrum" ,
1300- value = ""
1301- } ) ;
1231+ spectrumCvParams . Add ( new CVParamType
1232+ {
1233+ accession = "MS:1000579" ,
1234+ cvRef = "MS" ,
1235+ name = "MS1 spectrum" ,
1236+ value = ""
1237+ } ) ;
13021238
1303- // Keep track of scan number for precursor reference
1304- _precursorMs1ScanNumber = scanNumber ;
1239+ // Keep track of scan number for precursor reference
1240+ _precursorScanNumbers [ "" ] = scanNumber ;
1241+ _precursorTree [ scanNumber ] = new PrecursorInfo ( ) ;
13051242
1306- break ;
1307- case MSOrderType . Ms2 :
1308- spectrumCvParams . Add ( new CVParamType
1243+ }
1244+ else if ( msLevel > 1 )
1245+ {
1246+ spectrumCvParams . Add ( new CVParamType
13091247 {
13101248 accession = "MS:1000580" ,
13111249 cvRef = "MS" ,
13121250 name = "MSn spectrum" ,
13131251 value = ""
13141252 } ) ;
13151253
1316- //update precursor scan if it is provided in trailer data
1317- var trailerMasterScan = trailerData . AsPositiveInt ( "Master Scan Number:" ) ;
1318- if ( trailerMasterScan . HasValue ) _precursorMs1ScanNumber = trailerMasterScan . Value ;
1319-
1320- // Keep track of scan number and isolation m/z for precursor reference
1321- var result = _filterStringIsolationMzPattern . Match ( scanEvent . ToString ( ) ) ;
1322- if ( result . Success )
1254+ // Keep track of scan number and isolation m/z for precursor reference
1255+ var result = _filterStringIsolationMzPattern . Match ( scanEvent . ToString ( ) ) ;
1256+ if ( result . Success )
1257+ {
1258+ if ( _precursorScanNumbers . ContainsKey ( result . Groups [ 1 ] . Value ) )
13231259 {
1324- if ( _precursorMs2ScanNumbers . ContainsKey ( result . Groups [ 1 ] . Value ) )
1325- {
1326- _precursorMs2ScanNumbers . Remove ( result . Groups [ 1 ] . Value ) ;
1327- }
1328-
1329- _precursorMs2ScanNumbers . Add ( result . Groups [ 1 ] . Value , scanNumber ) ;
1260+ _precursorScanNumbers . Remove ( result . Groups [ 1 ] . Value ) ;
13301261 }
13311262
1332- // Construct and set the precursor list element of the spectrum
1333- var precursorListType =
1334- ConstructPrecursorList ( scanEvent , charge , scanFilter . MSOrder , monoisotopicMz , isolationWidth ,
1335- SPSMasses ) ;
1336- spectrum . precursorList = precursorListType ;
1337- break ;
1338- case MSOrderType . Ms3 :
1339- spectrumCvParams . Add ( new CVParamType
1263+ _precursorScanNumbers . Add ( result . Groups [ 1 ] . Value , scanNumber ) ;
1264+ }
1265+
1266+ //update precursor scan if it is provided in trailer data
1267+ var trailerMasterScan = trailerData . AsPositiveInt ( "Master Scan Number:" ) ;
1268+ if ( trailerMasterScan . HasValue )
1269+ {
1270+ _precursorScanNumber = trailerMasterScan . Value ;
1271+ }
1272+ else //try getting it from the scan filter
1273+ {
1274+ var parts = Regex . Split ( result . Groups [ 1 ] . Value , " " ) ;
1275+ string parentFilter = String . Join ( " " , parts . Take ( parts . Length - 1 ) ) ;
1276+ if ( _precursorScanNumbers . ContainsKey ( parentFilter ) )
13401277 {
1341- accession = "MS:1000580" ,
1342- cvRef = "MS" ,
1343- name = "MSn spectrum" ,
1344- value = ""
1345- } ) ;
1346- precursorListType = ConstructPrecursorList ( scanEvent , charge , scanFilter . MSOrder , monoisotopicMz ,
1347- isolationWidth , SPSMasses ) ;
1348- spectrum . precursorList = precursorListType ;
1349- break ;
1350- default :
1351- throw new ArgumentOutOfRangeException ( ) ;
1278+ _precursorScanNumber = _precursorScanNumbers [ parentFilter ] ;
1279+ }
1280+ }
1281+
1282+ // Construct and set the precursor list element of the spectrum
1283+ spectrum . precursorList =
1284+ ConstructPrecursorList ( scanEvent , charge , scanFilter . MSOrder , monoisotopicMz , isolationWidth ,
1285+ SPSMasses ) ;
1286+
1287+ _precursorTree [ scanNumber ] = new PrecursorInfo ( _precursorScanNumber , spectrum . precursorList . precursor ) ;
1288+ }
1289+ else
1290+ {
1291+ throw new ArgumentOutOfRangeException ( $ "Unknown msLevel: { msLevel } ") ;
13521292 }
13531293
13541294 // Scan polarity
@@ -1943,45 +1883,19 @@ private SpectrumType ConstructPDASpectrum(int scanNumber, int instrumentNumber)
19431883 private PrecursorListType ConstructPrecursorList ( IScanEventBase scanEvent , int ? charge , MSOrderType msLevel ,
19441884 double ? monoisotopicMz , double ? isolationWidth , List < double > SPSMasses )
19451885 {
1946- // Construct the precursor
1947- var precursorList = new PrecursorListType
1948- {
1949- count = ( Math . Max ( SPSMasses . Count , 1 ) ) . ToString ( ) ,
1950- precursor = new PrecursorType [ Math . Max ( SPSMasses . Count , 1 ) ]
1951- } ;
1886+ // Construct the precursors
1887+
1888+ List < PrecursorType > precursors = new List < PrecursorType > ( ) ;
19521889
19531890 var spectrumRef = "" ;
1954- int precursorScanNumber = _precursorMs1ScanNumber ;
1891+ int precursorScanNumber = _precursorScanNumber ;
19551892 IReaction reaction = null ;
19561893 var precursorMz = 0.0 ;
19571894 try
19581895 {
1959- switch ( msLevel )
1960- {
1961- case MSOrderType . Ms2 :
1962- spectrumRef = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , _precursorMs1ScanNumber ) ;
1963- reaction = scanEvent . GetReaction ( 0 ) ;
1964- precursorScanNumber = _precursorMs1ScanNumber ;
1965- break ;
1966- case MSOrderType . Ms3 :
1967- var precursorMs2ScanNumber =
1968- _precursorMs2ScanNumbers . Keys . FirstOrDefault ( isolationMz =>
1969- scanEvent . ToString ( ) . Contains ( isolationMz ) ) ;
1970- if ( ! precursorMs2ScanNumber . IsNullOrEmpty ( ) )
1971- {
1972- spectrumRef = ConstructSpectrumTitle ( ( int ) Device . MS , 1 ,
1973- _precursorMs2ScanNumbers [ precursorMs2ScanNumber ] ) ;
1974- reaction = scanEvent . GetReaction ( 1 ) ;
1975- precursorScanNumber = _precursorMs1ScanNumber ;
1976- }
1977- else
1978- {
1979- throw new InvalidOperationException ( "Couldn't find a MS2 precursor scan for MS3 scan " +
1980- scanEvent ) ;
1981- }
1982-
1983- break ;
1984- }
1896+ spectrumRef = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , _precursorScanNumber ) ;
1897+ reaction = scanEvent . GetReaction ( ( int ) msLevel - 2 ) ;
1898+ precursorScanNumber = _precursorScanNumber ;
19851899
19861900 precursorMz = reaction . PrecursorMass ;
19871901
@@ -2069,7 +1983,7 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
20691983 } ;
20701984 if ( isolationWidth != null )
20711985 {
2072- var offset = isolationWidth . Value / 2 ;
1986+ var offset = isolationWidth . Value / 2 + reaction . IsolationWidthOffset ;
20731987 precursor . isolationWindow . cvParam [ 1 ] =
20741988 new CVParamType
20751989 {
@@ -2127,7 +2041,7 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
21272041 activationCvParams . Add ( activation ) ;
21282042 }
21292043
2130- // Check for supplemental activation
2044+ // TODO: implement supplemental activation
21312045 if ( scanEvent . SupplementalActivation == TriState . On )
21322046 {
21332047 try
@@ -2190,7 +2104,7 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
21902104 cvParam = activationCvParams . ToArray ( )
21912105 } ;
21922106
2193- precursorList . precursor [ 0 ] = precursor ;
2107+ precursors . Add ( precursor ) ;
21942108
21952109 // The first SPS mass seems to be the same as the one from reaction or scan filter
21962110 for ( int n = 1 ; n < SPSMasses . Count ; n ++ )
@@ -2228,10 +2142,21 @@ private PrecursorListType ConstructPrecursorList(IScanEventBase scanEvent, int?
22282142 cvParam = activationCvParams . ToArray ( )
22292143 } ;
22302144
2231- precursorList . precursor [ n ] = SPSPrecursor ;
2145+ precursors . Add ( SPSPrecursor ) ;
22322146 }
22332147
2234- return precursorList ;
2148+ //Add precursors from previous levels
2149+ if ( _precursorTree [ precursorScanNumber ] . Scan != 0 )
2150+ {
2151+ precursors . AddRange ( _precursorTree [ precursorScanNumber ] . Precursors ) ;
2152+ }
2153+
2154+ return new PrecursorListType
2155+ {
2156+ count = precursors . Count . ToString ( ) ,
2157+ precursor = precursors . ToArray ( )
2158+ } ;
2159+
22352160 }
22362161
22372162 /// <summary>
0 commit comments