Skip to content

Commit 8490139

Browse files
Tims connection fix (#890)
* Divide tims Frames RT by 60 to convert to minutes, added one over K0 field to SpectrumMatchFromTsvHeader * fixed dynamic connection issue in timsTofFileReader * Reverted local-only changes * Added test coverage for timsTOF error handling * Changed method name in TimsDataScan, added project level ExcludeFromCodeCov tag to Test project --------- Co-authored-by: trishorts <mshort@chem.wisc.edu>
1 parent 9c55dd9 commit 8490139

File tree

7 files changed

+214
-49
lines changed

7 files changed

+214
-49
lines changed

mzLib/MassSpectrometry/MsDataFile.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ public abstract class MsDataFile : IEnumerable<MsDataScan>
3030
{
3131
public MsDataScan[] Scans { get; protected set; }
3232
public SourceFile SourceFile { get; set; }
33-
public int NumSpectra => Scans.Length;
33+
public int NumSpectra => Scans?.Length ?? 0;
3434
public string FilePath { get; }
35-
3635
protected MsDataFile(int numSpectra, SourceFile sourceFile)
3736
{
3837
Scans = new MsDataScan[numSpectra];

mzLib/Readers/ExternalResults/IndividualResultRecords/MsFraggerPsm.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,6 @@ public class MsFraggerPsm : IQuantifiableRecord
212212

213213
[Ignore] private List<(string, string, string)> _proteinGroupInfos;
214214

215-
216-
217215
[Ignore] public int ChargeState => Charge;
218216

219217
// decoy reading isn't currently supported for MsFragger psms, this will be revisited later

mzLib/Readers/timsTOF/FrameProxy.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using MassSpectrometry;
2+
using System.Runtime.CompilerServices;
23
using System.Runtime.InteropServices;
34

45
namespace Readers
@@ -32,7 +33,10 @@ internal FrameProxyFactory(FrameTable table, UInt64 fileHandle, Object fileLock,
3233
InitializeLookupTables(fileHandle);
3334
}
3435

35-
internal FrameProxy GetFrameProxy(long frameId)
36+
/// <summary>
37+
/// This class is marked virtual for testing purposes only.
38+
/// </summary>
39+
internal virtual FrameProxy GetFrameProxy(long frameId)
3640
{
3741
return new FrameProxy(FileHandle, frameId, FramesTable.NumScans[frameId - 1], FileLock, Converter);
3842
}
@@ -155,6 +159,24 @@ internal FrameProxy(UInt64 fileHandle, long frameId, int numScans, Object fileLo
155159
_scanOffsets = PartialSum(_rawData, 0, numScans);
156160
}
157161

162+
163+
/// <summary>
164+
/// Sometimes, with corrupted data, the _scanOffsets array will specify a scan range that is
165+
/// greater than the legnth of the _rawData array or is negative. This method checks if the frame is valid
166+
/// </summary>
167+
/// <returns></returns>
168+
internal bool IsFrameValid()
169+
{
170+
// All offsets should be non-negative and smaller than tge _rawData length
171+
for (int i = 0; i < _scanOffsets.Length - 1; i++)
172+
{
173+
if (_scanOffsets[i] < 0 || _scanOffsets[i] > _rawData.Length)
174+
return false;
175+
}
176+
177+
return true;
178+
}
179+
158180
/// <summary>
159181
/// Gets the intensities for the specified scan.
160182
/// </summary>
@@ -259,7 +281,12 @@ private void ThrowIfInvalidScanNumber(int zeroIndexedScanNumber)
259281
private Range GetScanRange(int zeroIndexedScanNumber, int offset)
260282
{
261283
int start = NumberOfScans + 2*_scanOffsets[zeroIndexedScanNumber] + offset;
262-
return new Range(start, start + (int)_rawData[zeroIndexedScanNumber]);
284+
int end = Math.Min(_rawData.Length, start + (int)_rawData[zeroIndexedScanNumber]);
285+
if (start >= _rawData.Length)
286+
{
287+
throw new ArgumentException("Scan data exceeds raw data array length. This indicates that the .tdf_bin file is corrupted");
288+
}
289+
return new Range(start, end);
263290
}
264291

265292
/// <summary>

mzLib/Readers/timsTOF/TimsDataScan.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ public TimsDataScan(MzSpectrum massSpectrum,
6666
ComponentSpectraTotalPeaks = 0;
6767
}
6868

69-
internal void AverageComponentSpectra(FrameProxyFactory proxyFactory, FilteringParams filteringParams = null)
69+
internal void SumComponentSpectra(FrameProxyFactory proxyFactory, FilteringParams filteringParams = null)
7070
{
7171
MassSpectrum = TofSpectraMerger.MergeArraysToMs2Spectrum(mzArrays, intensityArrays, filteringParams);
72+
if (MassSpectrum == null) return;
7273
TotalIonCurrent = MassSpectrum.SumOfAllY;
7374
mzArrays.Clear();
7475
intensityArrays.Clear();

mzLib/Readers/timsTOF/TimsTofFileReader.cs

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@
1717
using System.Diagnostics;
1818
using System.Security.Permissions;
1919
using System.ComponentModel;
20+
using System.Runtime.CompilerServices;
2021

2122
namespace Readers
22-
{
23+
{
2324
public class TimsTofFileReader : MsDataFile, IDisposable
2425
{
2526
// timsTOF instruments collect frames, packets of ions collected by the tims, then analyzed
2627
// over multiple scans with each scan corresponding to the same retention time but different
2728
// ion mobility valuess. When reading the file, multiple scans from the same frame are collapsed into
2829
// a single spectrum
2930

30-
public TimsTofFileReader(string filePath) : base (filePath) { }
31+
public TimsTofFileReader(string filePath) : base (filePath)
32+
{
33+
FaultyFrameIds = new();
34+
}
3135

3236
private UInt64? _fileHandle;
3337
private Object _fileLock;
@@ -36,7 +40,12 @@ public TimsTofFileReader(string filePath) : base (filePath) { }
3640
public int NumberOfFrames { get; private set; }
3741
public List<long> Ms1FrameIds { get; private set; }
3842
internal FrameProxyFactory FrameProxyFactory { get; private set; }
39-
43+
44+
internal List<long> FaultyFrameIds { get; }
45+
46+
public string? Warnings => FaultyFrameIds.Count > 0 ?
47+
"The following frames were not read correctly: " + String.Join(", ", FaultyFrameIds) : null;
48+
4049
// I don't know what the default scan range is, and at this point I'm too afraid to ask...
4150
private MzRange? _scanWindow;
4251
public MzRange ScanWindow => _scanWindow ??= new MzRange(20, 2000);
@@ -49,6 +58,9 @@ public override void InitiateDynamicConnection()
4958
throw new FileNotFoundException("Data file is missing .tdf and/or .tdf_bin file");
5059
}
5160

61+
if (Scans.IsNotNullOrEmpty() && Scans.All(s => s != null)) // If all scans have been loaded, then don't reload
62+
return;
63+
5264
OpenSqlConnection();
5365

5466
if(_fileHandle != null) tims_close((UInt64)_fileHandle);
@@ -57,6 +69,8 @@ public override void InitiateDynamicConnection()
5769

5870
CountFrames();
5971
BuildProxyFactory();
72+
CountMS1Frames();
73+
CountPrecursors();
6074
}
6175

6276
internal void OpenSqlConnection()
@@ -89,6 +103,7 @@ public override void CloseDynamicConnection()
89103
{
90104
if (_sqlConnection?.State == ConnectionState.Open) _sqlConnection.Close();
91105
_sqlConnection?.Dispose();
106+
_sqlConnection = null;
92107
if (_fileHandle != null)
93108
{
94109
tims_close((UInt64)_fileHandle);
@@ -232,9 +247,6 @@ public override MsDataFile LoadAllStaticData(FilteringParams filteringParams = n
232247
{
233248
InitiateDynamicConnection();
234249

235-
CountMS1Frames();
236-
CountPrecursors();
237-
238250
_maxThreads = maxThreads;
239251
Ms1ScansNoPrecursorsBag = new();
240252
Parallel.ForEach(
@@ -315,6 +327,11 @@ internal void AssignOneBasedPrecursorsToPasefScans()
315327
internal void BuildAllScans(long frameId, FilteringParams filteringParams)
316328
{
317329
FrameProxy frame = FrameProxyFactory.GetFrameProxy(frameId);
330+
if (frame == null || !frame.IsFrameValid())
331+
{
332+
FaultyFrameIds.Add(frameId);
333+
return; // If the frame is null, then we can't build any scans for it
334+
}
318335
var records = GetMs1Records(frameId);
319336
foreach(Ms1Record record in records)
320337
{
@@ -453,40 +470,7 @@ internal List<TimsDataScan> BuildPasefScanFromPrecursor(IEnumerable<int> precurs
453470
pasefScans.Add(dataScan);
454471
}
455472

456-
// Grab all fragmentation spectra for each precursor
457-
// Each TimsDataScan in pasefScans corresponds to one precursor.
458-
// A precursor can be isolated and fragmented in multiple pasef frames
459-
// Here, we iterate through each frame, averaging the scans that correspond to each precursor
460-
foreach (long frameId in allFrames)
461-
{
462-
FrameProxy frame = FrameProxyFactory.GetFrameProxy(frameId);
463-
//Iterate through all the datascans created above with this frame
464-
foreach (var scan in pasefScans)
465-
{
466-
if (scan.FrameIds.Contains(frameId))
467-
{
468-
List<uint[]> indexArrays = new();
469-
List<int[]> intensityArrays = new();
470-
for (int mobilityScanIdx = scan.ScanNumberStart; mobilityScanIdx < scan.ScanNumberEnd; mobilityScanIdx++)
471-
{
472-
indexArrays.Add(frame.GetScanIndices(mobilityScanIdx-1));
473-
intensityArrays.Add(frame.GetScanIntensities(mobilityScanIdx-1));
474-
}
475-
// Perform frame level averaging, where all scans from one frame associated with a given precursor are merged and centroided
476-
// Need to convert indexArrays to one uint[] and intensityArrays to one int[]
477-
(double[] Mzs, int[] Intensities) summedArrays = TofSpectraMerger.MergeArraysToMzArray(indexArrays, intensityArrays, FrameProxyFactory);
478-
scan.AddComponentArrays(summedArrays.Mzs, summedArrays.Intensities);
479-
}
480-
}
481-
}
482-
483-
// Now, we average the fragmentation spectra (each spectra originating in a different frame)
484-
// to yield one spectrum per precursor
485-
foreach (TimsDataScan scan in pasefScans)
486-
{
487-
scan.AverageComponentSpectra(FrameProxyFactory, filteringParams);
488-
}
489-
473+
PopulateSpectraForPasefScans(pasefScans, allFrames, filteringParams);
490474
return pasefScans;
491475
}
492476

@@ -530,6 +514,54 @@ internal IEnumerable<PasefRecord> GetPasefRecords(IEnumerable<int> precursorIds)
530514
}
531515
}
532516

517+
/// <summary>
518+
/// Grab all fragmentation spectra for each precursor
519+
/// Each TimsDataScan in pasefScansWithNullSpectra corresponds to one precursor.
520+
/// A precursor can be isolated and fragmented in multiple pasef frames
521+
/// Here, we iterate through each frame, averaging the scans that correspond to each precursor
522+
/// </summary>
523+
/// <param name="pasefScansWithNullSpectra">List of timsDataScans with metadata but no MzSpectrum</param>
524+
/// <param name="relevantFrameIds">Frames that contains scans to average for the given pasefScans</param>
525+
/// <param name="filteringParams">Filtering params that specify MS2 spectrum filtering options</param>
526+
internal void PopulateSpectraForPasefScans(List<TimsDataScan> pasefScansWithNullSpectra, IEnumerable<long> relevantFrameIds, FilteringParams filteringParams)
527+
{
528+
foreach (long frameId in relevantFrameIds)
529+
{
530+
FrameProxy frame = FrameProxyFactory.GetFrameProxy(frameId);
531+
if (frame == null || !frame.IsFrameValid())
532+
{
533+
FaultyFrameIds.Add(frameId);
534+
continue; // If the frame is null, then we can't build any scans for it
535+
}
536+
//Iterate through all the datascans created above with this frame
537+
foreach (var scan in pasefScansWithNullSpectra)
538+
{
539+
if (scan.FrameIds.Contains(frameId))
540+
{
541+
List<uint[]> indexArrays = new();
542+
List<int[]> intensityArrays = new();
543+
for (int mobilityScanIdx = scan.ScanNumberStart; mobilityScanIdx < scan.ScanNumberEnd; mobilityScanIdx++)
544+
{
545+
indexArrays.Add(frame.GetScanIndices(mobilityScanIdx - 1));
546+
intensityArrays.Add(frame.GetScanIntensities(mobilityScanIdx - 1));
547+
}
548+
// Perform frame level averaging, where all scans from one frame associated with a given precursor are merged and centroided
549+
// Need to convert indexArrays to one uint[] and intensityArrays to one int[]
550+
(double[] Mzs, int[] Intensities) summedArrays = TofSpectraMerger.MergeArraysToMzArray(indexArrays, intensityArrays, FrameProxyFactory);
551+
scan.AddComponentArrays(summedArrays.Mzs, summedArrays.Intensities);
552+
}
553+
}
554+
}
555+
556+
// Now, we average the fragmentation spectra (each spectra originating in a different frame)
557+
// to yield one spectrum per precursor
558+
foreach (TimsDataScan scan in pasefScansWithNullSpectra)
559+
{
560+
scan.SumComponentSpectra(FrameProxyFactory, filteringParams);
561+
}
562+
pasefScansWithNullSpectra.RemoveAll(scan => scan.MassSpectrum == null || scan.MassSpectrum.Size < 1);
563+
}
564+
533565
private const string nativeIdFormat = "Frame ID + scan number range format";
534566
private const string massSpecFileFormat = ".D format";
535567
public override SourceFile GetSourceFile()

0 commit comments

Comments
 (0)