Skip to content

Commit 4e3b469

Browse files
committed
No limit to MSn
TODO: Implement correct supplemental activation handling
1 parent cc78fad commit 4e3b469

File tree

3 files changed

+111
-158
lines changed

3 files changed

+111
-158
lines changed

ThermoRawFileParser.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
<Compile Include="Util\LimitedSizeDictionary.cs" />
200200
<Compile Include="Util\MZArray.cs" />
201201
<Compile Include="Util\Peptide.cs" />
202+
<Compile Include="Writer\PrecursorInfo.cs" />
202203
<Compile Include="Writer\ScanTrailer.cs" />
203204
<Compile Include="XIC\JSONInputUnit.cs" />
204205
<Compile Include="XIC\JSONParser.cs" />

Writer/MzMlSpectrumWriter.cs

Lines changed: 83 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

Writer/PrecursorInfo.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Generic;
2+
3+
namespace ThermoRawFileParser.Writer
4+
{
5+
public class PrecursorInfo
6+
{
7+
public int MSLevel { get; set; }
8+
9+
public int Scan { get; set; }
10+
11+
public MzML.PrecursorType[] Precursors { get { return _precursors.ToArray(); } }
12+
13+
private List<MzML.PrecursorType> _precursors;
14+
15+
public PrecursorInfo()
16+
{
17+
Scan = 0;
18+
_precursors = new List<MzML.PrecursorType>();
19+
}
20+
21+
public PrecursorInfo(int scan, MzML.PrecursorType[] precursors)
22+
{
23+
Scan = scan;
24+
_precursors = new List<MzML.PrecursorType>(precursors);
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)