diff --git a/Bruker-msAlign-exporter/20220413-DA53-msAlign-QC-exporter.vbs b/Bruker-msAlign-exporter/20220413-DA53-msAlign-QC-exporter.vbs new file mode 100644 index 0000000000000000000000000000000000000000..11da72b6fe7a962423c22971b4597a0d69301b06 --- /dev/null +++ b/Bruker-msAlign-exporter/20220413-DA53-msAlign-QC-exporter.vbs @@ -0,0 +1,284 @@ +'***** Bruker DataAnalysis msAlign Exporter +' Created by Matt Willetts (Bruker), Kyle A. Brown (U-Wisconsin), and David L. Tabb (Institut Pasteur) +' This software makes it possible to conduct TopPIC searches on data from Bruker Q-TOF instruments. +' The SNAP / AutoMSn code at the very top handles "deconvolution." +' Everything else crafts an msalign file from the list of Compound objects. + +'***** To Do: +' How can we detect the precursor charge better? The loop through MS1 peaks seems error-prone. +' How can we specify the settings for SNAP and AutoMSn directly rather than relying on users? +' How do we "rescue" MS/MS scans for which no precursor charge is reported? +' Do we need to write "FEATURE" files for better TopPIC compatibility? + +' Localise for US, using '.' rather than ',' as a decimal separator +SetLocale(1033) +Const Proton = 1.00727647 +Const IntensityMultiplier = 10 +Const MZTolerance = 0.001 +Const ZCeiling = 100 +Const PkCountCeiling = 1000 +Dim ZDistn(100) +Dim PkCountDistn(1000) +Dim PathAndFile, objFSO, msalignFile, qcFile, NoZCount +Dim ZMinimum, ZQuartile1, ZMedian, ZQuartile3, ZMaximum, ZMode +Dim PkCountMinimum, PkCountQuartile1, PkCountMedian, PkCountQuartile3, PkCountMaximum + +'***** Run deconvolution in SNAP / MaxEnt / AutoMSn +'***** Configure for SNAP peak picking +Analysis.Method.MassListParameters.DetectionAlgorithm = 2 +'***** Just select everything for peak picking +Analysis.ClearChromatogramRangeSelections +Analysis.AddChromatogramRangeSelection 0, 1000 +Analysis.FindAutoMSn +On Error Resume Next +Analysis.Save + +'***** Set up our output file for writing +Set objFSO = CreateObject("Scripting.FileSystemObject") +PathAndFile = Left(Analysis.Path, Len(Analysis.Path)-2) & ".msalign" +Set msalignFile = objFSO.CreateTextFile(PathAndFile) +PathAndFile = Left(Analysis.Path, Len(Analysis.Path)-2) & ".qc.tsv" +Set qcFile = objFSO.CreateTextFile(PathAndFile) + +For Looper = 0 to ZCeiling + ZDistn(Looper) = 0 +Next +For Looper = 0 to PkCountCeiling + PkCountDistn(Looper) = 0 +Next + +For Each ThisCompound In Analysis.Compounds + Dim MS1Spec, MS2Spec, MSMSType, MS1ScanNumber, MS2ScanNumber + Dim PrecursorMass, PrecursorCharge, PrecursorMZ, PrecursorIntensity, PrecursorRT + Dim StartPos, StopPos, ThisPeak, PeakCounter + + Set MS1Spec = ThisCompound(1) + Set MS2Spec = ThisCompound(2) + +'***** Determine the dissociation type (based on whether or not component Name contains the string "ETD"). + If InStr(ThisCompound.Name,"ETD") Then + MSMSType = "ETD" + Else + MSMSType = "CID" + End If + +'***** Determine the precursor m/z and mass, since we will need that to determine its charge + If IsNumeric(ThisCompound.Precursor) Then + PrecursorMZ = ThisCompound.Precursor + Else + '***** Otherwise grab the mass from the string naming this component + StartPos = InStr(ThisCompound.Name,"(") + 1 + StopPos = InStr(ThisCompound.Name,")") + PrecursorMZ = Mid(ThisCompound.Name,StartPos,StopPos-StartPos) + End If + + If IsNumeric(ThisCompound.RetentionTime) Then + PrecursorRT = ThisCompound.RetentionTime + Else + PrecursorRT = 0 + End If + + If IsNumeric(ThisCompound.Intensity) Then + PrecursorIntensity = ThisCompound.Intensity + Else + PrecursorIntensity = 0 + End If + +'***** Determine the precursor charge state, keeping track of how many precursors cannot be matched back to the MS scans. + PrecursorCharge = -1 + + For Each ThisPeak in MS1Spec.MSPeakList + If Abs(ThisPeak.m_over_z - PrecursorMZ)< MZTolerance Then + PrecursorCharge = ThisPeak.ChargeState + Exit For + End If + Next + + If PrecursorCharge = -1 Then + ZDistn(0) = ZDistn(0) + 1 + ' If we couldn't determine the precursor charge we call it a +1 in the msalign file. + PrecursorCharge = 1 + PrecursorMass = PrecursorMZ - Proton + Else + ' Record this precursor charge in the array of precursor charge frequences + ZDistn(PrecursorCharge) = ZDistn(PrecursorCharge) + 1 + PrecursorMass = (PrecursorMZ*PrecursorCharge) - (PrecursorCharge*Proton) + End If + +'***** Get the first MS scan number to report for this compound + StartPos = InStr(MS1Spec.Name,"#") + StopPos = InStrRev(MS1Spec.Name,"-") + If StopPos = 0 Then + MS1ScanNumber=Mid(MS1Spec.Name, StartPos+1) + Else + MS1ScanNumber=Mid(MS1Spec.Name, StartPos+1, StopPos-(StartPos+1)) + End If + + +'***** Get the first MS/MS scan number to report for this compound + StartPos = InStr(MS2Spec.Name,"#") + StopPos = InStrRev(MS2Spec.Name,"-") + If StopPos = 0 Then + MS2ScanNumber=Mid(MS2Spec.Name, StartPos+1) + Else + MS2ScanNumber=Mid(MS2Spec.Name, StartPos+1, StopPos-(StartPos+1)) + End If + + ' Add the number of MS/MS peaks for this spectrum to the Peak Count distribution + PkCountDistn(MS2Spec.MSPeakList.Count) = PkCountDistn(MS2Spec.MSPeakList.Count)+1 + +'***** Now write this compound header to the msAlign file + msalignFile.WriteLine("BEGIN IONS") + msalignFile.WriteLine("ID=" & ThisCompound.CompoundNumber) + msalignFile.WriteLine("FRACTION_ID=1") + msalignFile.WriteLine("FILE_NAME=" & Analysis.Name) + msalignFile.WriteLine("SCANS=" & MS2ScanNumber) + msalignFile.WriteLine("RETENTION_TIME=" & PrecursorRT) + ' Naturally the following will require update when something beyond MS/MS is being used. + msalignFile.WriteLine("LEVEL=2") + msalignFile.WriteLine("ACTIVATION=" & MSMSType) + msalignFile.WriteLine("MS_ONE_ID=" & ThisCompound.CompoundNumber) + msalignFile.WriteLine("MS_ONE_SCAN=" & MS1ScanNumber) + msalignFile.WriteLine("PRECURSOR_MZ=" & PrecursorMZ) + msalignFile.WriteLine("PRECURSOR_CHARGE=" & PrecursorCharge) + msalignFile.WriteLine("PRECURSOR_MASS=" & PrecursorMass) + ' TopFD writes floating point precursor intensities, but Bruker gives us integers + msalignFile.WriteLine("PRECURSOR_INTENSITY=" & PrecursorIntensity & ".00") + +'***** Now write the compound MS/MS peaks to the msAlign file + For Each ThisPeak in MS2Spec.MSPeakList + msalignFile.Write(Round(ThisPeak.ChargeState * (ThisPeak.m_over_z - Proton),4)) + msalignFile.Write(" ") + msalignFile.Write(Round(ThisPeak.Intensity * IntensityMultiplier,2)) + msalignFile.Write(" ") + msalignFile.WriteLine(ThisPeak.ChargeState) + Next + + msalignFile.WriteLine("END IONS" & vbCrLf) + +'***** We're done with this Compound. Continue to the next one. +Next +'***** Now complete writing the msalign file +msalignFile.Close + +'***** QC METRIC COMPUTATION + +'***** Compute quartiles for Precursor Z, skipping unknown precursor charges +ZMinimum = -1 +ZQuartile1 = -1 +ZMedian = -1 +ZQuartile3 = -1 +ZMaximum = -1 +ZMode = -1 + +PositiveBinSum = 0 +BiggestBinFreq = 0 +For Looper = 1 to ZCeiling + If ZDistn(Looper) > 0 Then + PositiveBinSum = PositiveBinSum + ZDistn(Looper) + If ZMinimum = -1 Then + ZMinimum = Looper + End If + If ZDistn(Looper) > BiggestBinFreq Then + BiggestBinFreq=ZDistn(Looper) + ZMode = Looper + End If + ZMaximum = Looper + End If +Next + +' What target number of spectra must be taken into account to find the first, second, and third quartiles? +Q1BinSum = PositiveBinSum / 4 +Q2BinSum = PositiveBinSum / 2 +Q3BinSum = Q1BinSum + Q2BinSum +PositiveBinSum = 0 +For Looper = 1 to ZCeiling + PositiveBinSum = PositiveBinSum + ZDistn(Looper) + If ZQuartile1 = -1 AND PositiveBinSum >= Q1BinSum Then + ZQuartile1 = Looper + End IF + If ZMedian = -1 AND PositiveBinSum >= Q2BinSum Then + ZMedian = Looper + End IF + If ZQuartile3 = -1 AND PositiveBinSum >= Q3BinSum Then + ZQuartile3 = Looper + End IF +Next + +'***** Compute quartiles for MS/MS Peak Count +PkCountMinimum = -1 +PkCountQuartile1 = -1 +PkCountMedian = -1 +PkCountQuartile3 = -1 +PkCountMaximum = -1 +PkCountMode = -1 + +PositiveBinSum = 0 +BiggestBinFreq = 0 +For Looper = 0 to PkCountCeiling + If PkCountDistn(Looper) > 0 Then + PositiveBinSum = PositiveBinSum + PkCountDistn(Looper) + If PkCountMinimum = -1 Then + PkCountMinimum = Looper + End If + If PkCountDistn(Looper) > BiggestBinFreq Then + BiggestBinFreq=PkCountDistn(Looper) + PkCountMode = Looper + End If + PkCountMaximum = Looper + End If +Next + +' What target number of spectra must be taken into account to find the first, second, and third quartiles? +Q1BinSum = PositiveBinSum / 4 +Q2BinSum = PositiveBinSum / 2 +Q3BinSum = Q1BinSum + Q2BinSum +PositiveBinSum = 0 +For Looper = 0 to PkCountCeiling + PositiveBinSum = PositiveBinSum + PkCountDistn(Looper) + If PkCountQuartile1 = -1 AND PositiveBinSum >= Q1BinSum Then + PkCountQuartile1 = Looper + End IF + If PkCountMedian = -1 AND PositiveBinSum >= Q2BinSum Then + PkCountMedian = Looper + End IF + If PkCountQuartile3 = -1 AND PositiveBinSum >= Q3BinSum Then + PkCountQuartile3 = Looper + End IF +Next + +qcFile.WriteLine("AllSpectraCount" & vbTab & Analysis.SpectraCount) +qcFile.WriteLine("AutoMSnCompoundCount" & vbTab & Analysis.Compounds.Count) +qcFile.WriteLine("CompoundsLackingZ" & vbTab & ZDistn(0)) +qcFile.WriteLine() + +qcFile.WriteLine("ZMinimum" & vbTab & ZMinimum) +qcFile.WriteLine("ZQuartile1" & vbTab & ZQuartile1) +qcFile.WriteLine("ZMedian" & vbTab & ZMedian) +qcFile.WriteLine("ZQuartile3" & vbTab & ZQuartile3) +qcFile.WriteLine("ZMaximum" & vbTab & ZMaximum) +qcFile.WriteLine("ZMode" & vbTab & ZMode) + +qcFile.WriteLine() +qcFile.WriteLine("MS2PkCountMinimum" & vbTab & PkCountMinimum) +qcFile.WriteLine("MS2PkCountQuartile1" & vbTab & PkCountQuartile1) +qcFile.WriteLine("MS2PkCountMedian" & vbTab & PkCountMedian) +qcFile.WriteLine("MS2PkCountQuartile3" & vbTab & PkCountQuartile3) +qcFile.WriteLine("MS2PkCountMaximum" & vbTab & PkCountMaximum) +qcFile.WriteLine("MS2PkCountMode" & vbTab & PkCountMode) + +qcFile.WriteLine() +qcFile.WriteLine("CompoundZ" & vbTab & "Frequency") +For Looper = 0 to ZMaximum + qcFile.WriteLine(Looper & vbTab & ZDistn(Looper)) +Next + +qcFile.WriteLine() +qcFile.WriteLine("MS2PkCount" & vbTab & "Frequency") +For Looper = 0 to PkCountMaximum + qcFile.WriteLine(Looper & vbTab & PkCountDistn(Looper)) +Next + +qcFile.Close +MsgBox ("Wrote .msalign and .qc.tsv file successfully.") +Form.Close