From dc20e44f80801fb2e5a06cc02e2c4f0c5359c527 Mon Sep 17 00:00:00 2001 From: Nic Bollis Date: Tue, 29 Oct 2024 10:44:26 -0500 Subject: [PATCH] Neutral Mass Spectrum (#806) * began neutral mz spectrum * Refactor visibility and clean up deconvolution code Changed `ClassicDeconvolutionAlgorithm`, `DeconvolutionAlgorithm`, and `ExampleNewDeconvolutionAlgorithmTemplate` classes and their members from `public` to `internal` to restrict visibility within the assembly. Added summary comment to `DeconvolutionAlgorithm` class. Refactored `Deconvoluter` class to remove unnecessary `using` directives and simplify the `Deconvolute` method by removing switch-case logic. Updated `IsotopicEnvelope` class by removing `MassIndex` and `StDev` properties, and modified constructor and `ScoreIsotopeEnvelope` method accordingly. Updated `MzSpectrum` class to use `StandardDeviation` extension method from `Easy.Common.Extensions`. Removed various unnecessary `using` directives from multiple files. * Finish NeutralMassSpectrum - Added `InternalsVisibleTo` entries for "Development" and "Test" in `MassSpectrometry.csproj`. - Changed `MostAbundantObservedIsotopicMass` to `internal` in `IsotopicEnvelope.cs`. - Added a new constructor to `IsotopicEnvelope` with monoisotopic mass, intensity, and charge. - Added XML documentation and changed `GeneratePeak` to `protected virtual` in `MzSpectrum.cs`. - Removed unused `using` directives in `MzSpectrum.cs` and `NeutralMzSpectrum.cs`. - Modified `NeutralMzSpectrum` constructor to validate array lengths. - Added `Charges` property to `NeutralMzSpectrum` and initialized it in the constructor. - Overrode `GeneratePeak` in `NeutralMzSpectrum` to convert to a charged spectrum using `Charges`. * Refactor Deconvoluter and rename NeutralMzSpectrum Added necessary using directives in Deconvoluter.cs. Modified Deconvoluter class for short-circuit deconvolution. Removed redundant lines in Deconvoluter.cs. Renamed NeutralMzSpectrum to NeutralMassSpectrum. Updated constructor and references accordingly. * added neutral mass file bool * Adjsuted and tested neutral mass spectra * Refactor Deconvoluter and add new tests Refactored Deconvoluter.cs to use a foreach loop for yielding IsotopicEnvelopes. Reformatted multiple test methods in TestDeconvolution.cs for better readability. Added new test methods to validate Deconvolute with NeutralMassSpectrum, ensuring correct processing of spectra with various charge states and ranges. * Make FirstX and LastX properties virtual; update tests - Changed FirstX and LastX properties in MzSpectrum to virtual. - Included MzLibUtil namespace in NeutralMassSpectrum class. - Updated NeutralMassSpectrum constructor to set FirstX and LastX. - Overrode FirstX and LastX in NeutralMassSpectrum class. - Added test NeutralMassSpectrum_MzRange to validate m/z range. * fixed nuspec * Update mzLib.nuspec --------- Co-authored-by: Nic Bollis --- .../ClassicDeconvolutionAlgorithm.cs | 11 +- .../Algorithms/DeconvolutionAlgorithm.cs | 5 +- ...xampleNewDeconvolutionAlgorithmTemplate.cs | 9 +- .../Deconvolution/Deconvoluter.cs | 48 ++-- .../MassSpectrometry/MassSpectrometry.csproj | 4 + mzLib/MassSpectrometry/MsDataFile.cs | 3 - .../MzSpectra/IsotopicEnvelope.cs | 32 +-- .../MassSpectrometry/MzSpectra/MzSpectrum.cs | 15 +- .../MzSpectra/NeutralMassSpectrum.cs | 65 ++++++ mzLib/Test/TestDeconvolution.cs | 216 +++++++++++++++--- mzLib/Test/TestSpectra.cs | 107 +++++++++ 11 files changed, 417 insertions(+), 98 deletions(-) create mode 100644 mzLib/MassSpectrometry/MzSpectra/NeutralMassSpectrum.cs diff --git a/mzLib/MassSpectrometry/Deconvolution/Algorithms/ClassicDeconvolutionAlgorithm.cs b/mzLib/MassSpectrometry/Deconvolution/Algorithms/ClassicDeconvolutionAlgorithm.cs index 4a919a7e5..8f7bb320b 100644 --- a/mzLib/MassSpectrometry/Deconvolution/Algorithms/ClassicDeconvolutionAlgorithm.cs +++ b/mzLib/MassSpectrometry/Deconvolution/Algorithms/ClassicDeconvolutionAlgorithm.cs @@ -1,20 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Chemistry; -using Easy.Common.Extensions; using MathNet.Numerics.Statistics; using MzLibUtil; namespace MassSpectrometry { - public class ClassicDeconvolutionAlgorithm : DeconvolutionAlgorithm + internal class ClassicDeconvolutionAlgorithm : DeconvolutionAlgorithm { private MzSpectrum spectrum; - public ClassicDeconvolutionAlgorithm(DeconvolutionParameters deconParameters) : base(deconParameters) + internal ClassicDeconvolutionAlgorithm(DeconvolutionParameters deconParameters) : base(deconParameters) { } @@ -25,7 +22,7 @@ public ClassicDeconvolutionAlgorithm(DeconvolutionParameters deconParameters) : /// spectrum to deconvolute /// Range of peaks to deconvolute /// - public override IEnumerable Deconvolute(MzSpectrum spectrumToDeconvolute, MzRange range) + internal override IEnumerable Deconvolute(MzSpectrum spectrumToDeconvolute, MzRange range) { var deconParams = DeconvolutionParameters as ClassicDeconvolutionParameters ?? throw new MzLibException("Deconvolution params and algorithm do not match"); spectrum = spectrumToDeconvolute; @@ -205,7 +202,7 @@ private IsotopicEnvelope FindIsotopicEnvelope(int massIndex, double candidateFor } } - return new IsotopicEnvelope(listOfObservedPeaks, monoisotopicMass, chargeState, totalIntensity, Statistics.StandardDeviation(listOfRatios), massIndex); + return new IsotopicEnvelope(listOfObservedPeaks, monoisotopicMass, chargeState, totalIntensity, listOfRatios.StandardDeviation()); } private int ObserveAdjacentChargeStates(IsotopicEnvelope originalEnvelope, double mostIntensePeakMz, int massIndex, double deconvolutionTolerancePpm, double intensityRatioLimit, double minChargeToLookFor, double maxChargeToLookFor, List monoisotopicMassPredictions) diff --git a/mzLib/MassSpectrometry/Deconvolution/Algorithms/DeconvolutionAlgorithm.cs b/mzLib/MassSpectrometry/Deconvolution/Algorithms/DeconvolutionAlgorithm.cs index e8a052e39..1bb6bf523 100644 --- a/mzLib/MassSpectrometry/Deconvolution/Algorithms/DeconvolutionAlgorithm.cs +++ b/mzLib/MassSpectrometry/Deconvolution/Algorithms/DeconvolutionAlgorithm.cs @@ -8,6 +8,9 @@ namespace MassSpectrometry { + /// + /// Parent class defining minimum requirement to be used + /// public abstract class DeconvolutionAlgorithm { // For ClassicDeconv. If not used elsewhere, move to that class @@ -79,6 +82,6 @@ protected DeconvolutionAlgorithm(DeconvolutionParameters deconParameters) /// spectrum to be deconvoluted /// Range of peaks to deconvolute /// - public abstract IEnumerable Deconvolute(MzSpectrum spectrum, MzRange range); + internal abstract IEnumerable Deconvolute(MzSpectrum spectrum, MzRange range); } } diff --git a/mzLib/MassSpectrometry/Deconvolution/Algorithms/ExampleNewDeconvolutionAlgorithmTemplate.cs b/mzLib/MassSpectrometry/Deconvolution/Algorithms/ExampleNewDeconvolutionAlgorithmTemplate.cs index 18957d8d0..c70c10b63 100644 --- a/mzLib/MassSpectrometry/Deconvolution/Algorithms/ExampleNewDeconvolutionAlgorithmTemplate.cs +++ b/mzLib/MassSpectrometry/Deconvolution/Algorithms/ExampleNewDeconvolutionAlgorithmTemplate.cs @@ -1,22 +1,19 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using MzLibUtil; namespace MassSpectrometry { [ExcludeFromCodeCoverage] - public class ExampleNewDeconvolutionAlgorithmTemplate : DeconvolutionAlgorithm + internal class ExampleNewDeconvolutionAlgorithmTemplate : DeconvolutionAlgorithm { - public ExampleNewDeconvolutionAlgorithmTemplate(DeconvolutionParameters deconParameters) : base(deconParameters) + internal ExampleNewDeconvolutionAlgorithmTemplate(DeconvolutionParameters deconParameters) : base(deconParameters) { } - public override IEnumerable Deconvolute(MzSpectrum spectrum, MzRange range = null) + internal override IEnumerable Deconvolute(MzSpectrum spectrum, MzRange range = null) { var deconParams = DeconvolutionParameters as ExampleNewDeconvolutionParametersTemplate ?? throw new MzLibException("Deconvolution params and algorithm do not match"); range ??= spectrum.Range; diff --git a/mzLib/MassSpectrometry/Deconvolution/Deconvoluter.cs b/mzLib/MassSpectrometry/Deconvolution/Deconvoluter.cs index d419561f2..773422862 100644 --- a/mzLib/MassSpectrometry/Deconvolution/Deconvoluter.cs +++ b/mzLib/MassSpectrometry/Deconvolution/Deconvoluter.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Easy.Common.Extensions; -using Easy.Common.Interfaces; +using System.Collections.Generic; +using Chemistry; using MzLibUtil; namespace MassSpectrometry @@ -30,27 +25,11 @@ public static class Deconvoluter public static IEnumerable Deconvolute(MsDataScan scan, DeconvolutionParameters deconvolutionParameters, MzRange rangeToGetPeaksFrom = null) { - rangeToGetPeaksFrom ??= scan.MassSpectrum.Range; + // set any specific deconvolution parameters found only in the MsDataScan - // set deconvolution algorithm and any specific deconvolution parameters found in the MsDataScan - DeconvolutionAlgorithm deconAlgorithm; - switch (deconvolutionParameters.DeconvolutionType) - { - case DeconvolutionType.ClassicDeconvolution: - deconAlgorithm = new ClassicDeconvolutionAlgorithm(deconvolutionParameters); - break; - - case DeconvolutionType.ExampleNewDeconvolutionTemplate: - deconAlgorithm = new ExampleNewDeconvolutionAlgorithmTemplate(deconvolutionParameters); - break; - - default: throw new MzLibException("DeconvolutionType not yet supported"); - } - - return deconAlgorithm.Deconvolute(scan.MassSpectrum, rangeToGetPeaksFrom); + foreach (var isotopicEnvelope in Deconvolute(scan.MassSpectrum, deconvolutionParameters, rangeToGetPeaksFrom)) + yield return isotopicEnvelope; } - - /// /// Static deconvolution of an MzSpectrum that does not require Deconvoluter construction @@ -79,7 +58,22 @@ public static IEnumerable Deconvolute(MzSpectrum spectrum, default: throw new MzLibException("DeconvolutionType not yet supported"); } - return deconAlgorithm.Deconvolute(spectrum, rangeToGetPeaksFrom); + // Short circuit deconvolution if it is called on a neutral mass spectrum + if (spectrum is NeutralMassSpectrum newt) + { + for (int i = 0; i < newt.XArray.Length; i++) + { + // skip this peak if it's outside the range of interest (e.g. if we're only interested in deconvoluting a small m/z range) + if (!rangeToGetPeaksFrom.Contains(newt.XArray[i].ToMz(newt.Charges[i]))) + continue; + yield return new IsotopicEnvelope(newt.XArray[i], newt.YArray[i], newt.Charges[i]); + } + } + else + { + foreach (var isotopicEnvelope in deconAlgorithm.Deconvolute(spectrum, rangeToGetPeaksFrom)) + yield return isotopicEnvelope; + } } } } diff --git a/mzLib/MassSpectrometry/MassSpectrometry.csproj b/mzLib/MassSpectrometry/MassSpectrometry.csproj index 6af63b9e4..9d8e74edc 100644 --- a/mzLib/MassSpectrometry/MassSpectrometry.csproj +++ b/mzLib/MassSpectrometry/MassSpectrometry.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/mzLib/MassSpectrometry/MsDataFile.cs b/mzLib/MassSpectrometry/MsDataFile.cs index 7242f2cf7..3e88fcfef 100644 --- a/mzLib/MassSpectrometry/MsDataFile.cs +++ b/mzLib/MassSpectrometry/MsDataFile.cs @@ -23,9 +23,6 @@ namespace MassSpectrometry { - // TODO: Define scope of class - // Class scope is to provide to the data loaded from the DataFile. - /// /// A class for interacting with data collected from a Mass Spectrometer, and stored in a file /// diff --git a/mzLib/MassSpectrometry/MzSpectra/IsotopicEnvelope.cs b/mzLib/MassSpectrometry/MzSpectra/IsotopicEnvelope.cs index 7e42426b1..3b2ab3d1b 100644 --- a/mzLib/MassSpectrometry/MzSpectra/IsotopicEnvelope.cs +++ b/mzLib/MassSpectrometry/MzSpectra/IsotopicEnvelope.cs @@ -14,29 +14,36 @@ public class IsotopicEnvelope : IHasMass /// /// Mass of most abundant observed isotopic peak, not accounting for addition or subtraction or protons due to ESI charge state induction /// - public double MostAbundantObservedIsotopicMass { get; private set; } + internal double MostAbundantObservedIsotopicMass { get; private set; } public readonly int Charge; public readonly double TotalIntensity; - public readonly double StDev; - public readonly int MassIndex; public double Score { get; private set; } - public IsotopicEnvelope(List<(double mz, double intensity)> bestListOfPeaks, double bestMonoisotopicMass, int bestChargeState, double bestTotalIntensity, double bestStDev, int bestMassIndex) + /// + /// Used for an isotopic envelope that mzLib deconvoluted (e.g., from a mass spectrum) + /// + public IsotopicEnvelope(List<(double mz, double intensity)> bestListOfPeaks, double bestMonoisotopicMass, int bestChargeState, double bestTotalIntensity, double bestStDev) { Peaks = bestListOfPeaks; MonoisotopicMass = bestMonoisotopicMass; - MostAbundantObservedIsotopicMass = GetMostAbundantObservedIsotopicMass(bestListOfPeaks, bestChargeState); + MostAbundantObservedIsotopicMass = bestListOfPeaks.MaxBy(p => p.intensity).mz * Math.Abs(bestChargeState); Charge = bestChargeState; TotalIntensity = bestTotalIntensity; - StDev = bestStDev; - MassIndex = bestMassIndex; - Score = ScoreIsotopeEnvelope(); + Score = ScoreIsotopeEnvelope(bestStDev); } - public double GetMostAbundantObservedIsotopicMass(List<(double mz, double intensity)> peaks, int charge) + /// + /// Used for a neutral mass read in from a deconvoluted file + /// Assumes the mass is correct: score is max value + /// + public IsotopicEnvelope(double monoisotopicMass, double intensity, int charge) { - return peaks.MaxBy(p => p.intensity).mz * Math.Abs(charge); + MonoisotopicMass = monoisotopicMass; + Charge = charge; + TotalIntensity = intensity; + Score = double.MaxValue; + Peaks = [(monoisotopicMass.ToMz(charge), intensity)]; } public override string ToString() @@ -44,10 +51,10 @@ public override string ToString() return Charge + "\t" + Peaks[0].mz.ToString("G8") + "\t" + Peaks.Count + "\t" + TotalIntensity; } - private double ScoreIsotopeEnvelope() //likely created by Stefan Solntsev using peptide data + private double ScoreIsotopeEnvelope(double stDev) //likely created by Stefan Solntsev using peptide data { return Peaks.Count >= 2 ? - TotalIntensity / Math.Pow(StDev, 0.13) * Math.Pow(Peaks.Count, 0.4) / Math.Pow(Math.Abs(Charge), 0.06) : + TotalIntensity / Math.Pow(stDev, 0.13) * Math.Pow(Peaks.Count, 0.4) / Math.Pow(Math.Abs(Charge), 0.06) : 0; } @@ -60,6 +67,5 @@ public void SetMedianMonoisotopicMass(List monoisotopicMassPredictions) { MonoisotopicMass = monoisotopicMassPredictions.Median(); } - } } \ No newline at end of file diff --git a/mzLib/MassSpectrometry/MzSpectra/MzSpectrum.cs b/mzLib/MassSpectrometry/MzSpectra/MzSpectrum.cs index 2e9fcc7a4..88a97d1ac 100644 --- a/mzLib/MassSpectrometry/MzSpectra/MzSpectrum.cs +++ b/mzLib/MassSpectrometry/MzSpectra/MzSpectrum.cs @@ -22,10 +22,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text.Json; namespace MassSpectrometry { @@ -126,7 +124,7 @@ public MzRange Range } } - public double? FirstX + public virtual double? FirstX { get { @@ -138,7 +136,7 @@ public double? FirstX } } - public double? LastX + public virtual double? LastX { get { @@ -373,7 +371,7 @@ public IsotopicEnvelope FindIsotopicEnvelope(int massIndex, double candidateForM } } - return new IsotopicEnvelope(listOfObservedPeaks, monoisotopicMass, chargeState, totalIntensity, Statistics.StandardDeviation(listOfRatios), massIndex); + return new IsotopicEnvelope(listOfObservedPeaks, monoisotopicMass, chargeState, totalIntensity, listOfRatios.StandardDeviation()); } [Obsolete("Deconvolution Has been moved to the Deconvoluter Object")] @@ -796,7 +794,12 @@ private MzPeak GetPeak(int index) return peakList[index]; } - private MzPeak GeneratePeak(int index) + /// + /// The source of all peaks which populate the peakList + /// + /// + /// + protected virtual MzPeak GeneratePeak(int index) { return new MzPeak(XArray[index], YArray[index]); } diff --git a/mzLib/MassSpectrometry/MzSpectra/NeutralMassSpectrum.cs b/mzLib/MassSpectrometry/MzSpectra/NeutralMassSpectrum.cs new file mode 100644 index 000000000..dcb5d7d2b --- /dev/null +++ b/mzLib/MassSpectrometry/MzSpectra/NeutralMassSpectrum.cs @@ -0,0 +1,65 @@ +using System; +using Chemistry; + +namespace MassSpectrometry +{ + public class NeutralMassSpectrum : MzSpectrum + { + public int[] Charges { get; init; } + public NeutralMassSpectrum(double[,] monoisotopicMassesIntensities, int[] charges) : base(monoisotopicMassesIntensities) + { + if (monoisotopicMassesIntensities.GetLength(0) != charges.Length) + throw new ArgumentException("The lengths of monoisotopicMasses, intensities, and charges must be the same."); + + Charges = charges; + + double minMz = double.MaxValue; + double maxMz = double.MinValue; + for (int i = 0; i < monoisotopicMassesIntensities.GetLength(0); i++) + { + var mz = monoisotopicMassesIntensities[i,0].ToMz(charges[i]); + if (mz < minMz) + minMz = mz; + if (mz > maxMz) + maxMz = mz; + } + + FirstX = minMz; + LastX = maxMz; + } + + public NeutralMassSpectrum(double[] monoisotopicMasses, double[] intensities, int[] charges, bool shouldCopy) + : base(monoisotopicMasses, intensities, shouldCopy) + { + if (monoisotopicMasses.GetLength(0) != intensities.Length || monoisotopicMasses.Length != charges.Length) + throw new ArgumentException("The lengths of monoisotopicMasses, intensities, and charges must be the same."); + + Charges = charges; + + double minMz = double.MaxValue; + double maxMz = double.MinValue; + for (int i = 0; i < monoisotopicMasses.Length; i++) + { + var mz = monoisotopicMasses[i].ToMz(charges[i]); + if (mz < minMz) + minMz = mz; + if (mz > maxMz) + maxMz = mz; + } + + FirstX = minMz; + LastX = maxMz; + } + + public override double? FirstX { get; } // in m/z + public override double? LastX { get; } // in m/z + + /// + /// Converts to a charged spectrum + /// + protected override MzPeak GeneratePeak(int index) + { + return new MzPeak(XArray[index].ToMz(Charges[index]), YArray[index]); + } + } +} diff --git a/mzLib/Test/TestDeconvolution.cs b/mzLib/Test/TestDeconvolution.cs index 8f6cbb9ee..7ee3f59a2 100644 --- a/mzLib/Test/TestDeconvolution.cs +++ b/mzLib/Test/TestDeconvolution.cs @@ -26,15 +26,18 @@ public sealed class TestDeconvolution #region Old Deconvolution [Test] - [TestCase(586.2143122, 24, 41983672, 586.2)]//This is a lesser abundant charge state envelope at the low mz end - [TestCase(740.372202090153, 19, 108419280, 740.37)]//This is the most abundant charge state envelope - [TestCase(1081.385183, 13, 35454636, 1081.385)]//This is a lesser abundant charge state envelope at the high mz end - public void TestDeconvolutionProteoformMultiChargeState(double selectedIonMz, int selectedIonChargeStateGuess, double selectedIonIntensity, double isolationMz) + [TestCase(586.2143122, 24, 41983672, 586.2)] //This is a lesser abundant charge state envelope at the low mz end + [TestCase(740.372202090153, 19, 108419280, 740.37)] //This is the most abundant charge state envelope + [TestCase(1081.385183, 13, 35454636, + 1081.385)] //This is a lesser abundant charge state envelope at the high mz end + public void TestDeconvolutionProteoformMultiChargeState(double selectedIonMz, int selectedIonChargeStateGuess, + double selectedIonIntensity, double isolationMz) { MsDataScan[] Scans = new MsDataScan[1]; //txt file, not mgf, because it's an MS1. Most intense proteoform has mass of ~14037.9 Da - string Ms1SpectrumPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"DataFiles\14kDaProteoformMzIntensityMs1.txt"); + string Ms1SpectrumPath = Path.Combine(TestContext.CurrentContext.TestDirectory, + @"DataFiles\14kDaProteoformMzIntensityMs1.txt"); string[] spectrumLines = File.ReadAllLines(Ms1SpectrumPath); @@ -51,7 +54,9 @@ public void TestDeconvolutionProteoformMultiChargeState(double selectedIonMz, in MzSpectrum spectrum = new MzSpectrum(ms1mzs, ms1intensities, false); - Scans[0] = new MsDataScan(spectrum, 1, 1, false, Polarity.Positive, 1.0, new MzRange(495, 1617), "first spectrum", MZAnalyzerType.Unknown, spectrum.SumOfAllY, null, null, null, selectedIonMz, selectedIonChargeStateGuess, selectedIonIntensity, isolationMz, 4); + Scans[0] = new MsDataScan(spectrum, 1, 1, false, Polarity.Positive, 1.0, new MzRange(495, 1617), + "first spectrum", MZAnalyzerType.Unknown, spectrum.SumOfAllY, null, null, null, selectedIonMz, + selectedIonChargeStateGuess, selectedIonIntensity, isolationMz, 4); var myMsDataFile = new FakeMsDataFile(Scans); @@ -68,21 +73,24 @@ public void TestDeconvolutionProteoformMultiChargeState(double selectedIonMz, in [Test] [TestCase("APSGGKK", "12-18-17_frac7_calib_ms1_663_665.mzML", 2)] - [TestCase("PKRKAEGDAKGDKAKVKDEPQRRSARLSAKPAPPKPEPKPKKAPAKKGEKVPKGKKGKADAGKEGNNPAENGDAKTDQAQKAEGAGDAK", "FXN11_tr1_032017-calib_ms1_scans716_718.mzML", 8)] - [TestCase("PKRKVSSAEGAAKEEPKRRSARLSAKPPAKVEAKPKKAAAKDKSSDKKVQTKGKRGAKGKQAEVANQETKEDLPAENGETKTEESPASDEAGEKEAKSD", "FXN11_tr1_032017-calib_ms1_scans781_783.mzML", 16)] + [TestCase("PKRKAEGDAKGDKAKVKDEPQRRSARLSAKPAPPKPEPKPKKAPAKKGEKVPKGKKGKADAGKEGNNPAENGDAKTDQAQKAEGAGDAK", + "FXN11_tr1_032017-calib_ms1_scans716_718.mzML", 8)] + [TestCase("PKRKVSSAEGAAKEEPKRRSARLSAKPPAKVEAKPKKAAAKDKSSDKKVQTKGKRGAKGKQAEVANQETKEDLPAENGETKTEESPASDEAGEKEAKSD", + "FXN11_tr1_032017-calib_ms1_scans781_783.mzML", 16)] public static void CheckGetMostAbundantObservedIsotopicMass(string peptide, string file, int charge) { Protein test1 = new Protein(peptide, "Accession"); DigestionParams d = new DigestionParams(); - PeptideWithSetModifications pw = new PeptideWithSetModifications(test1, d, 1, test1.Length, CleavageSpecificity.None, "", 0, new Dictionary(), 0); + PeptideWithSetModifications pw = new PeptideWithSetModifications(test1, d, 1, test1.Length, + CleavageSpecificity.None, "", 0, new Dictionary(), 0); double m = pw.MostAbundantMonoisotopicMass.ToMz(charge); string singleScan = Path.Combine(TestContext.CurrentContext.TestDirectory, "DataFiles", file); - var reader = MsDataFileReader.GetDataFile(singleScan); + var reader = MsDataFileReader.GetDataFile(singleScan); reader.LoadAllStaticData(); List singlescan = reader.GetAllScansList(); - + MzSpectrum singlespec = singlescan[0].MassSpectrum; MzRange singleRange = new MzRange(singlespec.XArray.Min(), singlespec.XArray.Max()); int minAssumedChargeState = 1; @@ -91,13 +99,16 @@ public static void CheckGetMostAbundantObservedIsotopicMass(string peptide, stri double intensityRatioLimit = 3; //check assigned correctly - List lie2 = singlespec.Deconvolute(singleRange, minAssumedChargeState, maxAssumedChargeState, deconvolutionTolerancePpm, intensityRatioLimit).ToList(); + List lie2 = singlespec.Deconvolute(singleRange, minAssumedChargeState, + maxAssumedChargeState, deconvolutionTolerancePpm, intensityRatioLimit).ToList(); List lie2_charge = lie2.Where(p => p.Charge == charge).ToList(); Assert.That(lie2_charge[0].MostAbundantObservedIsotopicMass / charge, Is.EqualTo(m).Within(0.1)); //check that if already assigned, skips assignment and just recalls same value - List lie3 = singlespec.Deconvolute(singleRange, minAssumedChargeState, maxAssumedChargeState, deconvolutionTolerancePpm, intensityRatioLimit).ToList(); - Assert.AreEqual(lie2.Select(p => p.MostAbundantObservedIsotopicMass), lie3.Select(p => p.MostAbundantObservedIsotopicMass)); + List lie3 = singlespec.Deconvolute(singleRange, minAssumedChargeState, + maxAssumedChargeState, deconvolutionTolerancePpm, intensityRatioLimit).ToList(); + Assert.AreEqual(lie2.Select(p => p.MostAbundantObservedIsotopicMass), + lie3.Select(p => p.MostAbundantObservedIsotopicMass)); } #endregion @@ -105,15 +116,18 @@ public static void CheckGetMostAbundantObservedIsotopicMass(string peptide, stri #region Classic Deconvolution [Test] - [TestCase(586.2143122, 24, 41983672, 586.2)]//This is a lesser abundant charge state envelope at the low mz end - [TestCase(740.372202090153, 19, 108419280, 740.37)]//This is the most abundant charge state envelope - [TestCase(1081.385183, 13, 35454636, 1081.385)]//This is a lesser abundant charge state envelope at the high mz end - public void TestClassicDeconvolutionProteoformMultiChargeState(double selectedIonMz, int selectedIonChargeStateGuess, double selectedIonIntensity, double isolationMz) + [TestCase(586.2143122, 24, 41983672, 586.2)] //This is a lesser abundant charge state envelope at the low mz end + [TestCase(740.372202090153, 19, 108419280, 740.37)] //This is the most abundant charge state envelope + [TestCase(1081.385183, 13, 35454636, + 1081.385)] //This is a lesser abundant charge state envelope at the high mz end + public void TestClassicDeconvolutionProteoformMultiChargeState(double selectedIonMz, + int selectedIonChargeStateGuess, double selectedIonIntensity, double isolationMz) { MsDataScan[] Scans = new MsDataScan[1]; //txt file, not mgf, because it's an MS1. Most intense proteoform has mass of ~14037.9 Da - string Ms1SpectrumPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"DataFiles\14kDaProteoformMzIntensityMs1.txt"); + string Ms1SpectrumPath = Path.Combine(TestContext.CurrentContext.TestDirectory, + @"DataFiles\14kDaProteoformMzIntensityMs1.txt"); string[] spectrumLines = File.ReadAllLines(Ms1SpectrumPath); @@ -130,7 +144,9 @@ public void TestClassicDeconvolutionProteoformMultiChargeState(double selectedIo MzSpectrum spectrum = new MzSpectrum(ms1mzs, ms1intensities, false); - Scans[0] = new MsDataScan(spectrum, 1, 1, false, Polarity.Positive, 1.0, new MzRange(495, 1617), "first spectrum", MZAnalyzerType.Unknown, spectrum.SumOfAllY, null, null, null, selectedIonMz, selectedIonChargeStateGuess, selectedIonIntensity, isolationMz, 4); + Scans[0] = new MsDataScan(spectrum, 1, 1, false, Polarity.Positive, 1.0, new MzRange(495, 1617), + "first spectrum", MZAnalyzerType.Unknown, spectrum.SumOfAllY, null, null, null, selectedIonMz, + selectedIonChargeStateGuess, selectedIonIntensity, isolationMz, 4); var myMsDataFile = new FakeMsDataFile(Scans); @@ -141,7 +157,8 @@ public void TestClassicDeconvolutionProteoformMultiChargeState(double selectedIo DeconvolutionParameters deconParameters = new ClassicDeconvolutionParameters(1, 60, 4, 3); List isolatedMasses = scan.GetIsolatedMassesAndCharges(scan, deconParameters).ToList(); - List isolatedMasses2 = scan.GetIsolatedMassesAndCharges(scan.MassSpectrum, deconParameters).ToList(); + List isolatedMasses2 = + scan.GetIsolatedMassesAndCharges(scan.MassSpectrum, deconParameters).ToList(); List monoIsotopicMasses = isolatedMasses.Select(m => m.MonoisotopicMass).ToList(); List monoIsotopicMasses2 = isolatedMasses2.Select(m => m.MonoisotopicMass).ToList(); @@ -154,13 +171,16 @@ public void TestClassicDeconvolutionProteoformMultiChargeState(double selectedIo [Test] [TestCase("APSGGKK", "12-18-17_frac7_calib_ms1_663_665.mzML", 2)] - [TestCase("PKRKAEGDAKGDKAKVKDEPQRRSARLSAKPAPPKPEPKPKKAPAKKGEKVPKGKKGKADAGKEGNNPAENGDAKTDQAQKAEGAGDAK", "FXN11_tr1_032017-calib_ms1_scans716_718.mzML", 8)] - [TestCase("PKRKVSSAEGAAKEEPKRRSARLSAKPPAKVEAKPKKAAAKDKSSDKKVQTKGKRGAKGKQAEVANQETKEDLPAENGETKTEESPASDEAGEKEAKSD", "FXN11_tr1_032017-calib_ms1_scans781_783.mzML", 16)] + [TestCase("PKRKAEGDAKGDKAKVKDEPQRRSARLSAKPAPPKPEPKPKKAPAKKGEKVPKGKKGKADAGKEGNNPAENGDAKTDQAQKAEGAGDAK", + "FXN11_tr1_032017-calib_ms1_scans716_718.mzML", 8)] + [TestCase("PKRKVSSAEGAAKEEPKRRSARLSAKPPAKVEAKPKKAAAKDKSSDKKVQTKGKRGAKGKQAEVANQETKEDLPAENGETKTEESPASDEAGEKEAKSD", + "FXN11_tr1_032017-calib_ms1_scans781_783.mzML", 16)] public static void CheckClassicGetMostAbundantObservedIsotopicMass(string peptide, string file, int charge) { Protein test1 = new Protein(peptide, "Accession"); DigestionParams d = new DigestionParams(); - PeptideWithSetModifications pw = new PeptideWithSetModifications(test1, d, 1, test1.Length, CleavageSpecificity.None, "", 0, new Dictionary(), 0); + PeptideWithSetModifications pw = new PeptideWithSetModifications(test1, d, 1, test1.Length, + CleavageSpecificity.None, "", 0, new Dictionary(), 0); double m = pw.MostAbundantMonoisotopicMass.ToMz(charge); string singleScan = Path.Combine(TestContext.CurrentContext.TestDirectory, "DataFiles", file); @@ -176,7 +196,8 @@ public static void CheckClassicGetMostAbundantObservedIsotopicMass(string peptid double intensityRatioLimit = 3; DeconvolutionParameters deconParameters = - new ClassicDeconvolutionParameters(minAssumedChargeState, maxAssumedChargeState, deconvolutionTolerancePpm, + new ClassicDeconvolutionParameters(minAssumedChargeState, maxAssumedChargeState, + deconvolutionTolerancePpm, intensityRatioLimit); //check assigned correctly @@ -187,7 +208,8 @@ public static void CheckClassicGetMostAbundantObservedIsotopicMass(string peptid //check that if already assigned, skips assignment and just recalls same value List lie3 = Deconvoluter.Deconvolute(singlespec, deconParameters, singleRange).ToList(); - Assert.AreEqual(lie2.Select(p => p.MostAbundantObservedIsotopicMass), lie3.Select(p => p.MostAbundantObservedIsotopicMass)); + Assert.AreEqual(lie2.Select(p => p.MostAbundantObservedIsotopicMass), + lie3.Select(p => p.MostAbundantObservedIsotopicMass)); } #endregion @@ -225,21 +247,22 @@ public void TestNegativeModeClassicDeconvolution(double expectedMz, int expected public static void TestExampleNewDeconvolutionInDeconvoluter() { DeconvolutionParameters deconParams = new ExampleNewDeconvolutionParametersTemplate(1, 60); - var dataFile = MsDataFileReader.GetDataFile(Path.Combine(TestContext.CurrentContext.TestDirectory, "DataFiles", "GUACUG_NegativeMode_Sliced.mzML")); + var dataFile = MsDataFileReader.GetDataFile(Path.Combine(TestContext.CurrentContext.TestDirectory, + "DataFiles", "GUACUG_NegativeMode_Sliced.mzML")); dataFile.InitiateDynamicConnection(); var scan = dataFile.GetOneBasedScanFromDynamicConnection(726); var spectrum = scan.MassSpectrum; dataFile.CloseDynamicConnection(); // test switch statements in Deconvoluter - Assert.Throws(() => Deconvoluter.Deconvolute(spectrum, deconParams)); - Assert.Throws(() => Deconvoluter.Deconvolute(scan, deconParams)); + NUnit.Framework.Assert.Throws(() => _ = Deconvoluter.Deconvolute(spectrum, deconParams).ToList()); + NUnit.Framework.Assert.Throws(() => _ =Deconvoluter.Deconvolute(scan, deconParams).ToList()); // test default exceptions in deconvoluter var badEnumValue = (DeconvolutionType)Int32.MaxValue; deconParams.GetType().GetProperty("DeconvolutionType")!.SetValue(deconParams, badEnumValue); - Assert.Throws(() => Deconvoluter.Deconvolute(spectrum, deconParams)); - Assert.Throws(() => Deconvoluter.Deconvolute(scan, deconParams)); + NUnit.Framework.Assert.Throws(() => _ = Deconvoluter.Deconvolute(spectrum, deconParams).ToList()); + NUnit.Framework.Assert.Throws(() => _ = Deconvoluter.Deconvolute(scan, deconParams).ToList()); } @@ -247,14 +270,15 @@ public static void TestExampleNewDeconvolutionInDeconvoluter() public static void Test_MsDataScan_GetIsolatedMassesAndCharges() { // get scan - string filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "DataFiles", "GUACUG_NegativeMode_Sliced.mzML"); + string filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "DataFiles", + "GUACUG_NegativeMode_Sliced.mzML"); var dataFile = MsDataFileReader.GetDataFile(filePath); var precursorScan = dataFile.GetOneBasedScan(1); var fragmentationScan = dataFile.GetOneBasedScan(2); // set up deconvolution DeconvolutionParameters deconParams = new ClassicDeconvolutionParameters(-10, -1, 20, 3, Polarity.Negative); - + // get isolated masses and charges on an MS1 scan. This means the isolation window is null. var ms1Result = precursorScan.GetIsolatedMassesAndCharges(precursorScan.MassSpectrum, deconParams).ToList(); Assert.That(ms1Result.Count, Is.EqualTo(0)); @@ -262,10 +286,132 @@ public static void Test_MsDataScan_GetIsolatedMassesAndCharges() Assert.That(ms1Result.Count, Is.EqualTo(0)); // get isolated masses and charges on an MS2 scan. This should work correctly - var ms2Result = fragmentationScan.GetIsolatedMassesAndCharges(precursorScan.MassSpectrum, deconParams).ToList(); + var ms2Result = fragmentationScan.GetIsolatedMassesAndCharges(precursorScan.MassSpectrum, deconParams) + .ToList(); Assert.That(ms2Result.Count, Is.EqualTo(1)); ms2Result = fragmentationScan.GetIsolatedMassesAndCharges(precursorScan, deconParams).ToList(); Assert.That(ms2Result.Count, Is.EqualTo(1)); } + + [Test] + public void NeutralMassSpectrum_Deconvolute_AllInRange() + { + // Arrange + var xArray = new[] { 260.774188159546, 391.660998843979 }; + var yArray = new[] { 1000.0, 1.0 }; + var charges = new[] { 1, 1 }; + var spectrum = new NeutralMassSpectrum(xArray, yArray, charges, false); + var deconvolutionParameters = new ClassicDeconvolutionParameters(1, 60, 20, 2); + var rangeToGetPeaksFrom = new MzRange(260.0, 400.0); + + // Act + var result = Deconvoluter.Deconvolute(spectrum, deconvolutionParameters, rangeToGetPeaksFrom).ToList(); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOf>(result); + Assert.AreEqual(2, result.Count()); + + for (int i = 0; i < result.Count(); i++) + { + Assert.That(result[i].MonoisotopicMass, Is.EqualTo(xArray[i])); + Assert.That(result[i].TotalIntensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Peaks.Count, Is.EqualTo(1)); + Assert.That(result[i].Peaks.First().mz, Is.EqualTo(xArray[i].ToMz(charges[i]))); + Assert.That(result[i].Peaks.First().intensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Charge, Is.EqualTo(charges[i])); + } + } + + + [Test] + public void NeutralMassSpectrum_Deconvolute_AllInRange_Charged() + { + // Arrange + var xArray = new[] { 260.774188159546, 391.660998843979 }; + var yArray = new[] { 1000.0, 1.0 }; + var charges = new[] { 3, 3 }; + var spectrum = new NeutralMassSpectrum(xArray, yArray, charges, false); + var deconvolutionParameters = new ClassicDeconvolutionParameters(1, 60, 20, 2); + var rangeToGetPeaksFrom = new MzRange(00, 200.0); + + // Act + var result = Deconvoluter.Deconvolute(spectrum, deconvolutionParameters, rangeToGetPeaksFrom).ToList(); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOf>(result); + Assert.AreEqual(2, result.Count()); + + for (int i = 0; i < result.Count(); i++) + { + Assert.That(result[i].MonoisotopicMass, Is.EqualTo(xArray[i])); + Assert.That(result[i].TotalIntensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Peaks.Count, Is.EqualTo(1)); + Assert.That(result[i].Peaks.First().mz, Is.EqualTo(xArray[i].ToMz(charges[i]))); + Assert.That(result[i].Peaks.First().intensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Charge, Is.EqualTo(charges[i])); + } + } + + [Test] + public void NeutralMassSpectrum_Deconvolute_SomeInRange() + { + // Arrange + var xArray = new[] { 260.774188159546, 391.660998843979 }; + var yArray = new[] { 1000.0, 1.0 }; + var charges = new[] { 1, 1 }; + var spectrum = new NeutralMassSpectrum(xArray, yArray, charges, false); + var deconvolutionParameters = new ClassicDeconvolutionParameters(1, 60, 20, 2); + var rangeToGetPeaksFrom = new MzRange(260.0, 300.0); + + // Act + var result = Deconvoluter.Deconvolute(spectrum, deconvolutionParameters, rangeToGetPeaksFrom).ToList(); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOf>(result); + Assert.AreEqual(1, result.Count()); + + for (int i = 0; i < result.Count(); i++) + { + Assert.That(result[i].MonoisotopicMass, Is.EqualTo(xArray[i])); + Assert.That(result[i].TotalIntensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Peaks.Count, Is.EqualTo(1)); + Assert.That(result[i].Peaks.First().mz, Is.EqualTo(xArray[i].ToMz(charges[i]))); + Assert.That(result[i].Peaks.First().intensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Charge, Is.EqualTo(charges[i])); + } + } + + [Test] + public void NeutralMassSpectrum_Deconvolute_SomeInRange_Charged() + { + // Arrange + var xArray = new[] { 260.774188159546, 391.660998843979 }; + var yArray = new[] { 1000.0, 1.0 }; + var charges = new[] { 1, 20 }; + var spectrum = new NeutralMassSpectrum(xArray, yArray, charges, false); + var deconvolutionParameters = new ClassicDeconvolutionParameters(1, 60, 20, 2); + var rangeToGetPeaksFrom = new MzRange(260.0, 300.0); + + // Act + var result = Deconvoluter.Deconvolute(spectrum, deconvolutionParameters, rangeToGetPeaksFrom).ToList(); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOf>(result); + Assert.AreEqual(1, result.Count()); + + for (int i = 0; i < result.Count(); i++) + { + Assert.That(result[i].MonoisotopicMass, Is.EqualTo(xArray[i])); + Assert.That(result[i].TotalIntensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Peaks.Count, Is.EqualTo(1)); + Assert.That(result[i].Peaks.First().mz, Is.EqualTo(xArray[i].ToMz(charges[i]))); + Assert.That(result[i].Peaks.First().intensity, Is.EqualTo(yArray[i])); + Assert.That(result[i].Charge, Is.EqualTo(charges[i])); + } + } } -} \ No newline at end of file +} diff --git a/mzLib/Test/TestSpectra.cs b/mzLib/Test/TestSpectra.cs index e33d9adb1..fec83ba8b 100644 --- a/mzLib/Test/TestSpectra.cs +++ b/mzLib/Test/TestSpectra.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Chemistry; using Assert = NUnit.Framework.Legacy.ClassicAssert; using Stopwatch = System.Diagnostics.Stopwatch; @@ -342,5 +343,111 @@ public void TestEqualsAndHashCode() Assert.That(!_mzSpectrumA.Equals(2)); Assert.That(!_mzSpectrumA.Equals((object)2)); } + + + [Test] + public void NeutralMassSpectrum_Constructor_ValidArguments_InitializesProperties() + { + double[] monoisotopicMasses = { 100.0, 200.0, 300.0 }; + double[] intensities = { 0.5, 0.8, 1.0 }; + int[] charges = { 1, 2, 3 }; + + var spectrum = new NeutralMassSpectrum(monoisotopicMasses, intensities, charges, true); + + Assert.That(monoisotopicMasses.Length, Is.EqualTo(spectrum.XArray.Length)); + Assert.That(intensities.Length, Is.EqualTo(spectrum.YArray.Length)); + Assert.That(charges.Length, Is.EqualTo(spectrum.Charges.Length)); + } + + [Test] + public void NeutralMassSpectrum_Constructor_InvalidArguments_ThrowsArgumentException() + { + double[] monoisotopicMasses = { 100.0, 200.0, 300.0 }; + double[] intensities = { 0.5, 0.8 }; + int[] charges = { 1, 2, 3 }; + bool shouldCopy = true; + + Assert.Throws(() => new NeutralMassSpectrum(monoisotopicMasses, intensities, charges, shouldCopy)); + } + + [Test] + public void NeutralMassSpectrum_MzPeak() + { + double[] monoisotopicMasses = { 100.0, 200.0, 300.0 }; + double[] intensities = { 0.5, 0.8, 1.0 }; + int[] charges = { 1, 2, 3 }; + var spectrum = new NeutralMassSpectrum(monoisotopicMasses, intensities, charges, true); + + + var peak = spectrum.Extract(50, 210).ToArray(); + Assert.That(peak.Length, Is.EqualTo(2)); + + for (int i = 0; i < peak.Length; i++) + { + double mono = monoisotopicMasses[i]; + int charge = charges[i]; + double intensity = intensities[i]; + double mz = mono.ToMz(charge); + + Assert.That(peak[i].Mz, Is.EqualTo(mz)); + Assert.That(peak[i].Intensity, Is.EqualTo(intensity)); + } + } + + [Test] + public void NeutralMassSpectrum_MzRange() + { + double[] monoisotopicMasses = { 100.0, 200.0, 300.0 }; + double[] intensities = { 0.5, 0.8, 1.0 }; + int[] charges = { 1, 2, 3 }; + var spectrum = new NeutralMassSpectrum(monoisotopicMasses, intensities, charges, true); + + + var peak = spectrum.Extract(50, 2100).ToArray(); + Assert.That(peak.Length, Is.EqualTo(3)); + var minPeak = peak.MinBy(p => p.Mz); + var maxPeak = peak.MaxBy(p => p.Mz); + + Assert.That(minPeak.Mz, Is.EqualTo(spectrum.Range.Minimum)); + Assert.That(minPeak.Mz, Is.EqualTo(spectrum.FirstX)); + Assert.That(maxPeak.Mz, Is.EqualTo(spectrum.Range.Maximum)); + Assert.That(maxPeak.Mz, Is.EqualTo(spectrum.LastX)); + + for (int i = 0; i < peak.Length; i++) + { + double mono = monoisotopicMasses[i]; + int charge = charges[i]; + double intensity = intensities[i]; + double mz = mono.ToMz(charge); + + Assert.That(peak[i].Mz, Is.EqualTo(mz)); + Assert.That(peak[i].Intensity, Is.EqualTo(intensity)); + } + } + + [Test] + public void NeutralMassSpectrum_Constructor_ValidArguments_InitializesCharges() + { + // Arrange + double[,] monoisotopicMassesIntensities = new double[,] { { 100.0, 200.0 }, { 300.0, 400.0 } }; + int[] charges = new int[] { 1, 2 }; + + // Act + var spectrum = new NeutralMassSpectrum(monoisotopicMassesIntensities, charges); + + // Assert + Assert.AreEqual(charges, spectrum.Charges); + } + + [Test] + public void NeutralMassSpectrum_Constructor2_InvalidArguments_ThrowsArgumentException() + { + // Arrange + double[,] monoisotopicMassesIntensities = new double[,] { { 100.0, 200.0 }, { 300.0, 400.0 } }; + int[] charges = new int[] { 1, 2, 3 }; + + // Act & Assert + Assert.Throws(() => new NeutralMassSpectrum(monoisotopicMassesIntensities, charges)); + } } } \ No newline at end of file