Skip to content

Commit b4c28a1

Browse files
committed
Refactor PRM precursor lists
Added virtual precursor at index -1 that is employed for PRM scans; When precursor scan cannot be determined, trying to report at least the minimal information
1 parent 3dd4ec7 commit b4c28a1

File tree

2 files changed

+90
-193
lines changed

2 files changed

+90
-193
lines changed

Writer/MzMlSpectrumWriter.cs

Lines changed: 89 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public MzMlSpectrumWriter(ParseInput parseInput) : base(parseInput)
6868
_mzMlNamespace.Add(string.Empty, "http://psi.hupo.org/ms/mzml");
6969
_doIndexing = ParseInput.OutputFormat == OutputFormat.IndexMzML;
7070
_osOffset = Environment.NewLine == "\n" ? 0 : 1;
71+
_precursorScanNumbers[""] = -1;
72+
_precursorTree[-1] = new PrecursorInfo();
7173
}
7274

7375
/// <inheritdoc />
@@ -1275,7 +1277,7 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
12751277
});
12761278

12771279
// Keep track of scan number for precursor reference
1278-
_precursorScanNumbers[""] = scanNumber;
1280+
_precursorScanNumbers[""] = -1;
12791281
_precursorTree[scanNumber] = new PrecursorInfo();
12801282

12811283
}
@@ -1312,54 +1314,52 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
13121314
_precursorScanNumber = GetParentFromScanString(result.Groups[1].Value);
13131315
}
13141316

1315-
if (_precursorScanNumber > 0)
1317+
//finding precursor scan failed
1318+
if (_precursorScanNumber == -2)
13161319
{
1320+
Log.Warn($"Cannot find precursor scan for scan# {scanNumber}");
1321+
_precursorTree[-2] = new PrecursorInfo(0, msLevel, FindLastReaction(scanEvent, msLevel), new PrecursorType[0]);
1322+
}
13171323

1318-
try
1324+
try
1325+
{
1326+
try //since there is no direct way to get the number of reactions available, it is necessary to try and fail
13191327
{
1320-
try //since there is no direct way to get the number of reactions available, it is necessary to try and fail
1328+
scanEvent.GetReaction(_precursorTree[_precursorScanNumber].ReactionCount);
1329+
}
1330+
catch (ArgumentOutOfRangeException ex)
1331+
{
1332+
Log.Debug($"Using Tribrid decision tree fix for scan# {scanNumber}");
1333+
//Is it a decision tree scheduled scan on tribrid?
1334+
if (msLevel == _precursorTree[_precursorScanNumber].MSLevel)
13211335
{
1322-
scanEvent.GetReaction(_precursorTree[_precursorScanNumber].ReactionCount);
1336+
_precursorScanNumber = GetParentFromScanString(result.Groups[1].Value);
13231337
}
1324-
catch (ArgumentOutOfRangeException ex)
1338+
else
13251339
{
1326-
Log.Debug($"Using Tribrid decision tree fix for scan# {scanNumber}");
1327-
//Is it a decision tree scheduled scan on tribrid?
1328-
if (msLevel == _precursorTree[_precursorScanNumber].MSLevel)
1329-
{
1330-
_precursorScanNumber = GetParentFromScanString(result.Groups[1].Value);
1331-
}
1332-
else
1333-
{
1334-
throw new RawFileParserException(
1335-
$"Tribrid decision tree fix failed - cannot get reaction# {_precursorTree[_precursorScanNumber].ReactionCount} from {scanEvent.ToString()}",
1336-
ex);
1337-
}
1340+
throw new RawFileParserException(
1341+
$"Tribrid decision tree fix failed - cannot get reaction# {_precursorTree[_precursorScanNumber].ReactionCount} from {scanEvent.ToString()}",
1342+
ex);
13381343
}
1339-
1340-
// Construct and set the precursor list element of the spectrum
1341-
spectrum.precursorList =
1342-
ConstructPrecursorList(_precursorScanNumber, scanEvent, charge, monoisotopicMz, isolationWidth,
1343-
SPSMasses, out var reactionCount);
1344-
1345-
//save precursor information for later reference
1346-
_precursorTree[scanNumber] = new PrecursorInfo(_precursorScanNumber, msLevel, reactionCount, spectrum.precursorList.precursor);
13471344
}
1348-
catch (Exception e)
1349-
{
1350-
var extra = (e.InnerException is null) ? "" : $"\n{e.InnerException.StackTrace}";
1351-
1352-
Log.Warn($"Failed creating precursor list for scan# {scanNumber} - precursor information for this and dependent scans will be empty\nException details:{e.Message}\n{e.StackTrace}\n{extra}");
1353-
ParseInput.NewWarn();
13541345

1355-
_precursorTree[scanNumber] = new PrecursorInfo(_precursorScanNumber, 1, 0, new PrecursorType[0]);
1346+
// Construct and set the precursor list element of the spectrum
1347+
spectrum.precursorList =
1348+
ConstructPrecursorList(_precursorScanNumber, scanEvent, charge, monoisotopicMz, isolationWidth,
1349+
SPSMasses, out var reactionCount);
13561350

1357-
}
1358-
1351+
//save precursor information for later reference
1352+
_precursorTree[scanNumber] = new PrecursorInfo(_precursorScanNumber, msLevel, reactionCount, spectrum.precursorList.precursor);
13591353
}
1360-
else
1354+
catch (Exception e)
13611355
{
1362-
spectrum.precursorList = ConstructPRMPrecursorList(scanEvent, charge, isolationWidth);
1356+
var extra = (e.InnerException is null) ? "" : $"\n{e.InnerException.StackTrace}";
1357+
1358+
Log.Warn($"Failed creating precursor list for scan# {scanNumber} - precursor information for this and dependent scans will be empty\nException details:{e.Message}\n{e.StackTrace}\n{extra}");
1359+
ParseInput.NewWarn();
1360+
1361+
_precursorTree[scanNumber] = new PrecursorInfo(_precursorScanNumber, 1, 0, new PrecursorType[0]);
1362+
13631363
}
13641364
}
13651365
else
@@ -1884,6 +1884,45 @@ private SpectrumType ConstructMSSpectrum(int scanNumber)
18841884
return spectrum;
18851885
}
18861886

1887+
private int FindLastReaction(IScanEvent scanEvent, int msLevel)
1888+
{
1889+
int lastReactionIndex = msLevel - 2;
1890+
1891+
//iteratively trying find the last available index for reaction
1892+
while(true)
1893+
{
1894+
try
1895+
{
1896+
scanEvent.GetReaction(lastReactionIndex + 1);
1897+
}
1898+
catch (ArgumentOutOfRangeException)
1899+
{
1900+
//stop trying
1901+
break;
1902+
}
1903+
1904+
lastReactionIndex++;
1905+
}
1906+
1907+
//supplemental activation flag is on -> one of the levels (not necissirily the last one) used supplemental activation
1908+
//check last two activations
1909+
if (scanEvent.SupplementalActivation == TriState.On)
1910+
{
1911+
var lastActivation = scanEvent.GetReaction(lastReactionIndex).ActivationType;
1912+
var beforeLastActivation = scanEvent.GetReaction(lastReactionIndex - 1).ActivationType;
1913+
1914+
if ((beforeLastActivation == ActivationType.ElectronTransferDissociation || beforeLastActivation == ActivationType.ElectronCaptureDissociation) &&
1915+
(lastActivation == ActivationType.CollisionInducedDissociation || lastActivation == ActivationType.HigherEnergyCollisionalDissociation))
1916+
return lastReactionIndex - 1; //ETD or ECD followed by HCD or CID -> supplemental activation in the last level (move the last reaction one step back)
1917+
else
1918+
return lastReactionIndex;
1919+
}
1920+
else //just use the last one
1921+
{
1922+
return lastReactionIndex;
1923+
}
1924+
}
1925+
18871926
private SpectrumType ConstructPDASpectrum(int scanNumber, int instrumentNumber)
18881927
{
18891928
// Get each scan from the RAW file
@@ -2146,20 +2185,24 @@ private PrecursorListType ConstructPrecursorList(int precursorScanNumber, IScanE
21462185
// Get precursors from earlier levels
21472186
var prevPrecursors = _precursorTree[precursorScanNumber];
21482187

2149-
var spectrumRef = "";
2188+
string spectrumRef = null;
21502189
int msLevel = (int)scanEvent.MSOrder;
21512190
IReaction reaction = null;
21522191
var precursorMz = 0.0;
21532192
reactionCount = prevPrecursors.ReactionCount;
21542193

2155-
spectrumRef = ConstructSpectrumTitle((int)Device.MS, 1, precursorScanNumber);
21562194
reaction = scanEvent.GetReaction(reactionCount);
2157-
2158-
precursorMz = reaction.PrecursorMass;
21592195

21602196
//if isolation width was not found in the trailer, try to get one from the reaction
21612197
if (isolationWidth == null) isolationWidth = reaction.IsolationWidth;
2162-
2198+
2199+
precursorMz = reaction.PrecursorMass;
2200+
2201+
if (precursorScanNumber > 0)
2202+
{
2203+
spectrumRef = ConstructSpectrumTitle((int)Device.MS, 1, precursorScanNumber);
2204+
}
2205+
21632206
var precursor = new PrecursorType
21642207
{
21652208
selectedIonList =
@@ -2196,7 +2239,7 @@ private PrecursorListType ConstructPrecursorList(int precursorScanNumber, IScanE
21962239
});
21972240
}
21982241

2199-
if (selectedIonMz > ZeroDelta)
2242+
if (selectedIonMz > ZeroDelta && precursorScanNumber > 0)
22002243
{
22012244
var selectedIonIntensity = CalculatePrecursorPeakIntensity(_rawFile, precursorScanNumber, reaction.PrecursorMass, isolationWidth,
22022245
ParseInput.NoPeakPicking.Contains(msLevel - 1));
@@ -2417,156 +2460,10 @@ private PrecursorListType ConstructPrecursorList(int precursorScanNumber, IScanE
24172460

24182461
}
24192462

2420-
/// <summary>
2421-
/// Populate the precursor list element for PRM/MS2-only datasets (No MS1 scans)
2422-
/// </summary>
2423-
/// <param name="scanEvent">the scan event</param>
2424-
/// <param name="charge">the charge from trailer</param>
2425-
/// <param name="isolationWidth">the isolation width value from trailer</param>
2426-
/// <returns>the precursor list</returns>
2427-
private PrecursorListType ConstructPRMPrecursorList(IScanEventBase scanEvent, int? charge, double? isolationWidth)
2428-
{
2429-
List<PrecursorType> precursors = new List<PrecursorType>();
2430-
2431-
2432-
int msLevel = (int)scanEvent.MSOrder;
2433-
IReaction reaction = scanEvent.GetReaction(0);
2434-
double precursorMz = reaction.PrecursorMass;
2435-
2436-
//if isolation width was not found in the trailer, try to get one from the reaction
2437-
if (isolationWidth == null) isolationWidth = reaction.IsolationWidth;
2438-
2439-
var precursor = new PrecursorType
2440-
{
2441-
selectedIonList =
2442-
new SelectedIonListType { count = "1", selectedIon = new ParamGroupType[1] },
2443-
};
2444-
2445-
precursor.selectedIonList.selectedIon[0] = new ParamGroupType();
2446-
2447-
var ionCvParams = new List<CVParamType>
2448-
{
2449-
new CVParamType
2450-
{
2451-
name = "selected ion m/z",
2452-
value = precursorMz.ToString(CultureInfo.InvariantCulture),
2453-
accession = "MS:1000744",
2454-
cvRef = "MS",
2455-
unitCvRef = "MS",
2456-
unitAccession = "MS:1000040",
2457-
unitName = "m/z"
2458-
}
2459-
};
2460-
2461-
if (charge != null)
2462-
{
2463-
ionCvParams.Add(new CVParamType
2464-
{
2465-
name = "charge state",
2466-
value = charge.ToString(),
2467-
accession = "MS:1000041",
2468-
cvRef = "MS"
2469-
});
2470-
}
2471-
precursor.selectedIonList.selectedIon[0].cvParam = ionCvParams.ToArray();
2472-
2473-
precursor.isolationWindow =
2474-
new ParamGroupType
2475-
{
2476-
cvParam = new CVParamType[3]
2477-
};
2478-
2479-
precursor.isolationWindow.cvParam[0] =
2480-
new CVParamType
2481-
{
2482-
accession = "MS:1000827",
2483-
name = "isolation window target m/z",
2484-
value = precursorMz.ToString(CultureInfo.InvariantCulture),
2485-
cvRef = "MS",
2486-
unitCvRef = "MS",
2487-
unitAccession = "MS:1000040",
2488-
unitName = "m/z"
2489-
};
2490-
if (isolationWidth != null)
2491-
{
2492-
var offset = isolationWidth.Value / 2 + reaction.IsolationWidthOffset;
2493-
precursor.isolationWindow.cvParam[1] =
2494-
new CVParamType
2495-
{
2496-
accession = "MS:1000828",
2497-
name = "isolation window lower offset",
2498-
value = (isolationWidth.Value - offset).ToString(CultureInfo.InvariantCulture),
2499-
cvRef = "MS",
2500-
unitCvRef = "MS",
2501-
unitAccession = "MS:1000040",
2502-
unitName = "m/z"
2503-
};
2504-
precursor.isolationWindow.cvParam[2] =
2505-
new CVParamType
2506-
{
2507-
accession = "MS:1000829",
2508-
name = "isolation window upper offset",
2509-
value = offset.ToString(CultureInfo.InvariantCulture),
2510-
cvRef = "MS",
2511-
unitCvRef = "MS",
2512-
unitAccession = "MS:1000040",
2513-
unitName = "m/z"
2514-
};
2515-
}
2516-
2517-
// Activation
2518-
var activationCvParams = new List<CVParamType>();
2519-
if (reaction != null)
2520-
{
2521-
if (reaction.CollisionEnergyValid)
2522-
{
2523-
activationCvParams.Add(
2524-
new CVParamType
2525-
{
2526-
accession = "MS:1000045",
2527-
name = "collision energy",
2528-
cvRef = "MS",
2529-
value = reaction.CollisionEnergy.ToString(CultureInfo.InvariantCulture),
2530-
unitCvRef = "UO",
2531-
unitAccession = "UO:0000266",
2532-
unitName = "electronvolt"
2533-
});
2534-
}
2535-
2536-
if (!OntologyMapping.DissociationTypes.TryGetValue(reaction.ActivationType, out var activation))
2537-
{
2538-
activation = new CVParamType
2539-
{
2540-
accession = "MS:1000044",
2541-
name = "Activation Method",
2542-
cvRef = "MS",
2543-
value = ""
2544-
};
2545-
}
2546-
2547-
activationCvParams.Add(activation);
2548-
}
2549-
2550-
precursor.activation =
2551-
new ParamGroupType
2552-
{
2553-
cvParam = activationCvParams.ToArray()
2554-
};
2555-
2556-
precursors.Add(precursor);
2557-
2558-
return new PrecursorListType
2559-
{
2560-
count = precursors.Count.ToString(),
2561-
precursor = precursors.ToArray()
2562-
};
2563-
2564-
}
2565-
25662463
private int GetParentFromScanString(string scanString)
25672464
{
2568-
var result = _filterStringIsolationMzPattern.Match(scanString);
2569-
var parts = Regex.Split(result.Groups[1].Value, " ");
2465+
//var result = _filterStringIsolationMzPattern.Match(scanString);
2466+
var parts = Regex.Split(scanString, " ");
25702467

25712468
//find the position of the first (from the end) precursor with a different mass
25722469
//to account for possible supplementary activations written in the filter
@@ -2584,7 +2481,7 @@ private int GetParentFromScanString(string scanString)
25842481
return _precursorScanNumbers[parentFilter];
25852482
}
25862483

2587-
return -1; //unsuccessful parsing
2484+
return -2; //unsuccessful parsing
25882485
}
25892486

25902487
/// <summary>

Writer/PrecursorInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/// </summary>
66
public class PrecursorInfo
77
{
8-
//for future use
8+
//Current MSLevel
99
public int MSLevel { get; }
1010

1111
//precursor scan number, 0 - means not a precursor

0 commit comments

Comments
 (0)