11using System ;
2+ using System . Collections . Generic ;
23using System . Globalization ;
34using System . Linq ;
45using System . Reflection ;
@@ -21,15 +22,13 @@ public class MgfSpectrumWriter : SpectrumWriter
2122 private const string NegativePolarity = "-" ;
2223
2324 // Filter string
24- private const string FilterStringIsolationMzPattern = @"ms2 (.*?)@" ;
25+ private readonly Regex _filterStringIsolationMzPattern = new Regex ( @"ms\d+ (.+?) \[" ) ;
2526
26- // Precursor scan number for MS2 scans
27- private int _precursorMs1ScanNumber ;
27+ // Precursor scan number for MSn scans
28+ private int _precursorScanNumber ;
2829
29- // Dictionary with isolation m/z (key) and precursor scan number (value) entries
30- // for reference in the precursor element of an MS3 spectrum
31- private readonly LimitedSizeDictionary < string , int > _isolationMzToPrecursorScanNumberMapping =
32- new LimitedSizeDictionary < string , int > ( 40 ) ;
30+ // Precursor scan number (value) and isolation m/z (key) for reference in the precursor element of an MSn spectrum
31+ private readonly Dictionary < string , int > _precursorScanNumbers = new Dictionary < string , int > ( ) ;
3332
3433 public MgfSpectrumWriter ( ParseInput parseInput ) : base ( parseInput )
3534 {
@@ -60,6 +59,8 @@ public override void Write(IRawDataPlus rawFile, int firstScanNumber, int lastSc
6059 }
6160 }
6261
62+ _precursorScanNumber = 0 ;
63+
6364 // Get the scan from the RAW file
6465 var scan = Scan . FromFile ( rawFile , scanNumber ) ;
6566
@@ -72,23 +73,81 @@ public override void Write(IRawDataPlus rawFile, int firstScanNumber, int lastSc
7273 // Get the scan event for this scan number
7374 var scanEvent = rawFile . GetScanEventForScanNumber ( scanNumber ) ;
7475
76+ // Trailer extra data list
77+ ScanTrailer trailerData ;
78+
79+ try
80+ {
81+ trailerData = new ScanTrailer ( rawFile . GetTrailerExtraInformation ( scanNumber ) ) ;
82+ }
83+ catch ( Exception ex )
84+ {
85+ Log . WarnFormat ( "Cannot load trailer infromation for scan {0} due to following exception\n {1}" , scanNumber , ex . Message ) ;
86+ trailerData = new ScanTrailer ( ) ;
87+ }
88+
89+ // Get scan ms level
90+ var msLevel = ( int ) scanFilter . MSOrder ;
91+
7592 // Construct the precursor reference string for the title
7693 var precursorReference = "" ;
94+
7795 if ( ParseInput . MgfPrecursor )
7896 {
79- if ( scanFilter . MSOrder == MSOrderType . Ms )
97+ if ( msLevel == 1 )
8098 {
8199 // Keep track of the MS1 scan number for precursor reference
82- _precursorMs1ScanNumber = scanNumber ;
100+ _precursorScanNumbers [ "" ] = scanNumber ;
83101 }
84102 else
85103 {
86- precursorReference = ConstructPrecursorReference ( scanFilter . MSOrder , scanNumber , scanEvent ) ;
104+ // Keep track of scan number and isolation m/z for precursor reference
105+ var result = _filterStringIsolationMzPattern . Match ( scanEvent . ToString ( ) ) ;
106+ if ( result . Success )
107+ {
108+ if ( _precursorScanNumbers . ContainsKey ( result . Groups [ 1 ] . Value ) )
109+ {
110+ _precursorScanNumbers . Remove ( result . Groups [ 1 ] . Value ) ;
111+ }
112+
113+ _precursorScanNumbers . Add ( result . Groups [ 1 ] . Value , scanNumber ) ;
114+ }
115+
116+ //update precursor scan if it is provided in trailer data
117+ var trailerMasterScan = trailerData . AsPositiveInt ( "Master Scan Number:" ) ;
118+ if ( trailerMasterScan . HasValue )
119+ {
120+ _precursorScanNumber = trailerMasterScan . Value ;
121+ }
122+ else //try getting it from the scan filter
123+ {
124+ var parts = Regex . Split ( result . Groups [ 1 ] . Value , " " ) ;
125+
126+ //find the position of the first (from the end) precursor with a different mass
127+ //to account for possible supplementary activations written in the filter
128+ var lastIonMass = parts . Last ( ) . Split ( '@' ) . First ( ) ;
129+ int last = parts . Length ;
130+ while ( last > 0 &&
131+ parts [ last - 1 ] . Split ( '@' ) . First ( ) == lastIonMass )
132+ {
133+ last -- ;
134+ }
135+
136+ string parentFilter = String . Join ( " " , parts . Take ( last ) ) ;
137+ if ( _precursorScanNumbers . ContainsKey ( parentFilter ) )
138+ {
139+ _precursorScanNumber = _precursorScanNumbers [ parentFilter ] ;
140+ }
141+ }
142+
143+ if ( _precursorScanNumber > 0 )
144+ precursorReference = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , _precursorScanNumber ) ;
145+ else
146+ Log . Error ( $ "Failed finding precursor for { scanNumber } ") ;
87147 }
88148 }
89149
90- // Don't include MS1 spectra
91- if ( ParseInput . MsLevel . Contains ( ( int ) scanFilter . MSOrder ) )
150+ if ( ParseInput . MsLevel . Contains ( msLevel ) )
92151 {
93152 var reaction = GetReaction ( scanEvent , scanNumber ) ;
94153
@@ -107,23 +166,10 @@ public override void Write(IRawDataPlus rawFile, int firstScanNumber, int lastSc
107166 Writer . WriteLine (
108167 $ "RTINSECONDS={ ( retentionTime * 60 ) . ToString ( CultureInfo . InvariantCulture ) } ") ;
109168
110- // Trailer extra data list
111- ScanTrailer trailerData ;
112-
113- try
114- {
115- trailerData = new ScanTrailer ( rawFile . GetTrailerExtraInformation ( scanNumber ) ) ;
116- }
117- catch ( Exception ex )
118- {
119- Log . WarnFormat ( "Cannot load trailer infromation for scan {0} due to following exception\n {1}" , scanNumber , ex . Message ) ;
120- trailerData = new ScanTrailer ( ) ;
121- }
122-
123169 int ? charge = trailerData . AsPositiveInt ( "Charge State:" ) ;
124170 double ? monoisotopicMz = trailerData . AsDouble ( "Monoisotopic M/Z:" ) ;
125171 double ? isolationWidth =
126- trailerData . AsDouble ( "MS" + ( int ) scanFilter . MSOrder + " Isolation Width:" ) ;
172+ trailerData . AsDouble ( "MS" + msLevel + " Isolation Width:" ) ;
127173
128174 if ( reaction != null )
129175 {
@@ -153,7 +199,7 @@ public override void Write(IRawDataPlus rawFile, int firstScanNumber, int lastSc
153199 double [ ] masses ;
154200 double [ ] intensities ;
155201
156- if ( ! ParseInput . NoPeakPicking . Contains ( ( int ) scanFilter . MSOrder ) )
202+ if ( ! ParseInput . NoPeakPicking . Contains ( msLevel ) )
157203 {
158204 // Check if the scan has a centroid stream
159205 if ( scan . HasCentroidStream )
@@ -200,48 +246,5 @@ public override void Write(IRawDataPlus rawFile, int firstScanNumber, int lastSc
200246 }
201247 }
202248 }
203-
204- private string ConstructPrecursorReference ( MSOrderType msOrder , int scanNumber , IScanEvent scanEvent )
205- {
206- // Precursor reference
207- var precursorReference = "" ;
208-
209- switch ( msOrder )
210- {
211- case MSOrderType . Ms2 :
212- // Keep track of the MS2 scan number and isolation m/z for precursor reference
213- var result = Regex . Match ( scanEvent . ToString ( ) , FilterStringIsolationMzPattern ) ;
214- if ( result . Success )
215- {
216- if ( _isolationMzToPrecursorScanNumberMapping . ContainsKey ( result . Groups [ 1 ] . Value ) )
217- {
218- _isolationMzToPrecursorScanNumberMapping . Remove ( result . Groups [ 1 ] . Value ) ;
219- }
220-
221- _isolationMzToPrecursorScanNumberMapping . Add ( result . Groups [ 1 ] . Value , scanNumber ) ;
222- }
223-
224- precursorReference = ConstructSpectrumTitle ( ( int ) Device . MS , 1 , _precursorMs1ScanNumber ) ;
225- break ;
226-
227- case MSOrderType . Ms3 :
228- var precursorScanNumber = _isolationMzToPrecursorScanNumberMapping . Keys . FirstOrDefault (
229- isolationMz => scanEvent . ToString ( ) . Contains ( isolationMz ) ) ;
230- if ( ! precursorScanNumber . IsNullOrEmpty ( ) )
231- {
232- precursorReference = ConstructSpectrumTitle ( ( int ) Device . MS , 1 ,
233- _isolationMzToPrecursorScanNumberMapping [ precursorScanNumber ] ) ;
234- }
235- else
236- {
237- throw new InvalidOperationException ( "Couldn't find a MS2 precursor scan for MS3 scan " +
238- scanEvent ) ;
239- }
240-
241- break ;
242- }
243-
244- return precursorReference ;
245- }
246249 }
247250}
0 commit comments