Esempio n. 1
0
        /// <summary>
        /// Apply the filter to a list of spectra.  In "normal" operation
        /// this list has a length of one. For ion mobility data it
        /// may be a list of spectra with the same retention time but
        /// different ion mobility values. For Agilent Mse data it may be
        /// a list of MS2 spectra that need averaging (or even a list
        /// of MS2 spectra with mixed retention and ion mobility values).  Averaging
        /// is done by unique retention time count, rather than by spectrum
        /// count, so that ion mobility data ion counts are additive (we're
        /// trying to measure ions per injection, basically).
        /// </summary>
        private ExtractedSpectrum FilterSpectrumList(MsDataSpectrum[] spectra,
                                                     SpectrumProductFilter[] productFilters, bool highAcc, bool useIonMobilityHighEnergyOffset)
        {
            int targetCount = 1;

            if (Q1 == 0)
            {
                highAcc = false;    // No mass error for all-ions extraction
            }
            else
            {
                if (productFilters.Length == 0)
                {
                    return(null);
                }
                targetCount = productFilters.Length;
            }

            float[]  extractedIntensities = new float[targetCount];
            float[]  massErrors           = highAcc ? new float[targetCount] : null;
            double[] meanErrors           = highAcc ? new double[targetCount] : null;

            double minIonMobilityHighEnergyOffset = useIonMobilityHighEnergyOffset ? productFilters.Select(f => f.HighEnergyIonMobilityValueOffset).Min() : 0;
            double maxIonMobilityHighEnergyOffset = useIonMobilityHighEnergyOffset ? productFilters.Select(f => f.HighEnergyIonMobilityValueOffset).Max() : 0;

            int    spectrumCount  = 0;
            int    rtCount        = 0;
            double lastRT         = 0;
            var    specIndexFirst = 0;
            var    specIndexLast  = spectra.Length;

            if (specIndexLast > 1 && MinIonMobilityValue.HasValue)
            {
                // Only inspect the range of spectra that match our ion mobility window
                var im0 = spectra[0].IonMobility.Mobility;
                var im1 = spectra[1].IonMobility.Mobility;
                if (im0.HasValue && im1.HasValue)
                {
                    if (im0 < im1)
                    {
                        // Binary search for first spectrum in ascending ion mobility range (as in drift time)
                        var im = MinIonMobilityValue.Value + minIonMobilityHighEnergyOffset;
                        specIndexFirst = CollectionUtil.BinarySearch(spectra, s => (s.IonMobility.Mobility ?? 0).CompareTo(im), true);
                    }
                    else if (im0 > im1)
                    {
                        // Binary search for first spectrum in descending ion mobility range (as in TIMS)
                        var im = MaxIonMobilityValue.Value + maxIonMobilityHighEnergyOffset;
                        specIndexFirst = CollectionUtil.BinarySearch(spectra, s => im.CompareTo(s.IonMobility.Mobility ?? 0), true);
                    }

                    if (specIndexFirst < 0)
                    {
                        specIndexFirst = ~specIndexFirst;
                    }
                }
            }
            for (var specIndex = specIndexFirst; specIndex < specIndexLast; specIndex++)
            {
                var spectrum = spectra[specIndex];
                // If these are spectra from distinct retention times, average them.
                // Note that for ion mobility data we will see fewer retention time changes
                // than the total spectra count - ascending DT (or descending 1/K0) within each RT.  Within a
                // single retention time the ions are additive.
                var rt = spectrum.RetentionTime ?? 0;
                if (lastRT != rt)
                {
                    rtCount++;
                    lastRT = rt;
                }

                // Filter on scan polarity
                if (Q1.IsNegative != spectrum.NegativeCharge)
                {
                    continue;
                }
                spectrumCount++;

                // Filter on ion mobility, if any - gross check before we look at individual fragment high energy offsets
                if (spectrum.IonMobilities == null &&   // Not for 3D spectra
                    !ContainsIonMobilityValue(spectrum.IonMobility, maxIonMobilityHighEnergyOffset) &&
                    !ContainsIonMobilityValue(spectrum.IonMobility, minIonMobilityHighEnergyOffset))
                {
                    if (specIndex > specIndexFirst && specIndexFirst > 0)
                    {
                        break; // We have left the range of interesting ion mobilities
                    }
                    continue;
                }
                var mzArray = spectrum.Mzs;
                if ((mzArray == null) || (mzArray.Length == 0))
                {
                    continue;
                }

                // It's not unusual for mzarray and centerArray to have no overlap, esp. with ion mobility data
                if (Q1 != 0)
                {
                    var lastProductFilter = productFilters[targetCount - 1];
                    if ((lastProductFilter.TargetMz.Value + lastProductFilter.FilterWidth / 2) < mzArray[0])
                    {
                        continue;
                    }
                }

                var intensityArray = spectrum.Intensities;
                var imsArray       = spectrum.IonMobilities;

                // Search for matching peaks for each Q3 filter
                // Use binary search to get to the first m/z value to be considered more quickly
                // This should help MS1 where isotope distributions will be very close in m/z
                // It should also help MS/MS when more selective, larger fragment ions are used,
                // since then a lot of less selective, smaller peaks must be skipped
                int iPeak = 0;
                for (int targetIndex = 0; targetIndex < targetCount; targetIndex++)
                {
                    var productFilter = productFilters[targetIndex];
                    // If fragments have individual high energy ion mobility offsets, recheck
                    // but only if there is no IonMobilities array. Otherwise IMS filtering is
                    // performed during extraction
                    if (spectrum.IonMobilities == null &&
                        productFilter.HighEnergyIonMobilityValueOffset != 0 &&
                        !ContainsIonMobilityValue(spectrum.IonMobility, productFilter.HighEnergyIonMobilityValueOffset))
                    {
                        continue;
                    }


                    // Look for the first peak that is greater than the start of the filter
                    double targetMz = 0, endFilter = double.MaxValue;
                    if (Q1 != 0)
                    {
                        targetMz = productFilter.TargetMz;
                        double filterWindow = productFilter.FilterWidth;
                        double startFilter  = targetMz - filterWindow / 2;
                        endFilter = startFilter + filterWindow;

                        if (iPeak < mzArray.Length)
                        {
                            iPeak = Array.BinarySearch(mzArray, iPeak, mzArray.Length - iPeak, startFilter);
                            if (iPeak < 0)
                            {
                                iPeak = ~iPeak;
                            }
                        }
                        if (iPeak >= mzArray.Length)
                        {
                            break; // No further overlap
                        }
                    }

                    // Add the intensity values of all peaks that pass the filter
                    double totalIntensity = extractedIntensities[targetIndex]; // Start with the value from the previous spectrum, if any
                    double meanError      = highAcc ? meanErrors[targetIndex] : 0;
                    for (int iNext = iPeak; iNext < mzArray.Length && mzArray[iNext] < endFilter; iNext++)
                    {
                        double mz        = mzArray[iNext];
                        double intensity = intensityArray[iNext];

                        // Avoid adding points that are not within the allowed ion mobility range
                        if (imsArray != null && !ContainsIonMobilityValue(imsArray[iNext], productFilter.HighEnergyIonMobilityValueOffset))
                        {
                            continue;
                        }

                        if (Extractor == ChromExtractor.summed)
                        {
                            totalIntensity += intensity;
                        }
                        else if (intensity > totalIntensity)
                        {
                            totalIntensity = intensity;
                            meanError      = 0;
                        }

                        // Accumulate weighted mean mass error for summed, or take a single
                        // mass error of the most intense peak for base peak.
                        if (highAcc && (Extractor == ChromExtractor.summed || meanError == 0))
                        {
                            if (totalIntensity > 0.0)
                            {
                                double deltaPeak = mz - targetMz;
                                meanError += (deltaPeak - meanError) * intensity / totalIntensity;
                            }
                        }
                    }
                    extractedIntensities[targetIndex] = (float)totalIntensity;
                    if (meanErrors != null)
                    {
                        meanErrors[targetIndex] = meanError;
                    }
                }
            }
            if (spectrumCount == 0)
            {
                return(null);
            }
            if (meanErrors != null)
            {
                for (int i = 0; i < targetCount; i++)
                {
                    massErrors[i] = (float)SequenceMassCalc.GetPpm(productFilters[i].TargetMz, meanErrors[i]);
                }
            }

            // If we summed across spectra of different retention times, scale per
            // unique retention time (but not per ion mobility value)
            if ((Extractor == ChromExtractor.summed) && (rtCount > 1))
            {
                float scale = (float)(1.0 / rtCount);
                for (int i = 0; i < targetCount; i++)
                {
                    extractedIntensities[i] *= scale;
                }
            }
            var dtFilter = GetIonMobilityWindow();

            return(new ExtractedSpectrum(ModifiedSequence,
                                         PeptideColor,
                                         Q1,
                                         dtFilter,
                                         Extractor,
                                         Id,
                                         productFilters,
                                         extractedIntensities,
                                         massErrors));
        }
Esempio n. 2
0
        /// <summary>
        /// Apply the filter to a list of spectra.  In "normal" operation
        /// this list has a length of one. For ion mobility data it
        /// may be a list of spectra with the same retention time but
        /// different ion mobility values. For Agilent Mse data it may be
        /// a list of MS2 spectra that need averaging (or even a list
        /// of MS2 spectra with mixed retention and ion mobility values).  Averaging
        /// is done by unique retention time count, rather than by spectrum
        /// count, so that ion mobility data ion counts are additive (we're
        /// trying to measure ions per injection, basically).
        /// </summary>
        private ExtractedSpectrum FilterSpectrumList(IEnumerable <MsDataSpectrum> spectra,
                                                     SpectrumProductFilter[] productFilters, bool highAcc, bool useDriftTimeHighEnergyOffset)
        {
            int targetCount = 1;

            if (Q1 == 0)
            {
                highAcc = false;    // No mass error for all-ions extraction
            }
            else
            {
                if (productFilters.Length == 0)
                {
                    return(null);
                }
                targetCount = productFilters.Length;
            }

            float[]  extractedIntensities = new float[targetCount];
            float[]  massErrors           = highAcc ? new float[targetCount] : null;
            double[] meanErrors           = highAcc ? new double[targetCount] : null;

            int    spectrumCount = 0;
            int    rtCount       = 0;
            double lastRT        = 0;

            foreach (var spectrum in spectra)
            {
                // If these are spectra from distinct retention times, average them.
                // Note that for ion mobility data we will see fewer retention time changes
                // than the total spectra count - ascending DT within each RT.  Within a
                // single retention time the ions are additive.
                var rt = spectrum.RetentionTime ?? 0;
                if (lastRT != rt)
                {
                    rtCount++;
                    lastRT = rt;
                }

                // Filter on scan polarity
                if (Q1.IsNegative != spectrum.NegativeCharge)
                {
                    continue;
                }
                spectrumCount++;

                // Filter on ion mobility, if any
                if (!ContainsIonMobilityValue(spectrum.IonMobility, useDriftTimeHighEnergyOffset))
                {
                    continue;
                }

                var mzArray = spectrum.Mzs;
                if ((mzArray == null) || (mzArray.Length == 0))
                {
                    continue;
                }

                // It's not unusual for mzarray and centerArray to have no overlap, esp. with ion mobility data
                if (Q1 != 0)
                {
                    var lastProductFilter = productFilters[targetCount - 1];
                    if ((lastProductFilter.TargetMz.Value + lastProductFilter.FilterWidth / 2) < mzArray[0])
                    {
                        continue;
                    }
                }

                var intensityArray = spectrum.Intensities;

                // Search for matching peaks for each Q3 filter
                // Use binary search to get to the first m/z value to be considered more quickly
                // This should help MS1 where isotope distributions will be very close in m/z
                // It should also help MS/MS when more selective, larger fragment ions are used,
                // since then a lot of less selective, smaller peaks must be skipped
                int iPeak = 0;
                for (int targetIndex = 0; targetIndex < targetCount; targetIndex++)
                {
                    // Look for the first peak that is greater than the start of the filter
                    double targetMz = 0, endFilter = double.MaxValue;
                    if (Q1 != 0)
                    {
                        var productFilter = productFilters[targetIndex];
                        targetMz = productFilter.TargetMz;
                        double filterWindow = productFilter.FilterWidth;
                        double startFilter  = targetMz - filterWindow / 2;
                        endFilter = startFilter + filterWindow;

                        if (iPeak < mzArray.Length)
                        {
                            iPeak = Array.BinarySearch(mzArray, iPeak, mzArray.Length - iPeak, startFilter);
                            if (iPeak < 0)
                            {
                                iPeak = ~iPeak;
                            }
                        }
                        if (iPeak >= mzArray.Length)
                        {
                            break; // No further overlap
                        }
                    }

                    // Add the intensity values of all peaks that pass the filter
                    double totalIntensity = extractedIntensities[targetIndex]; // Start with the value from the previous spectrum, if any
                    double meanError      = highAcc ? meanErrors[targetIndex] : 0;
                    for (int iNext = iPeak; iNext < mzArray.Length && mzArray[iNext] < endFilter; iNext++)
                    {
                        double mz        = mzArray[iNext];
                        double intensity = intensityArray[iNext];

                        if (Extractor == ChromExtractor.summed)
                        {
                            totalIntensity += intensity;
                        }
                        else if (intensity > totalIntensity)
                        {
                            totalIntensity = intensity;
                            meanError      = 0;
                        }

                        // Accumulate weighted mean mass error for summed, or take a single
                        // mass error of the most intense peak for base peak.
                        if (highAcc && (Extractor == ChromExtractor.summed || meanError == 0))
                        {
                            if (totalIntensity > 0.0)
                            {
                                double deltaPeak = mz - targetMz;
                                meanError += (deltaPeak - meanError) * intensity / totalIntensity;
                            }
                        }
                    }
                    extractedIntensities[targetIndex] = (float)totalIntensity;
                    if (meanErrors != null)
                    {
                        meanErrors[targetIndex] = meanError;
                    }
                }
            }
            if (spectrumCount == 0)
            {
                return(null);
            }
            if (meanErrors != null)
            {
                for (int i = 0; i < targetCount; i++)
                {
                    massErrors[i] = (float)SequenceMassCalc.GetPpm(productFilters[i].TargetMz, meanErrors[i]);
                }
            }

            // If we summed across spectra of different retention times, scale per
            // unique retention time (but not per ion mobility value)
            if ((Extractor == ChromExtractor.summed) && (rtCount > 1))
            {
                float scale = (float)(1.0 / rtCount);
                for (int i = 0; i < targetCount; i++)
                {
                    extractedIntensities[i] *= scale;
                }
            }
            var dtFilter = GetIonMobilityWindow(useDriftTimeHighEnergyOffset);

            return(new ExtractedSpectrum(ModifiedSequence,
                                         PeptideColor,
                                         Q1,
                                         dtFilter,
                                         Extractor,
                                         Id,
                                         productFilters,
                                         extractedIntensities,
                                         massErrors));
        }
Esempio n. 3
0
        /// <summary>
        /// Apply the filter to a list of spectra.  In "normal" operation
        /// this list has a length of one. For ion mobility data it
        /// may be a list of spectra with the same retention time but
        /// different ion mobility values. For Agilent Mse data it may be
        /// a list of MS2 spectra that need averaging (or even a list
        /// of MS2 spectra with mixed retention and ion mobility values).  Averaging
        /// is done by unique retention time count, rather than by spectrum
        /// count, so that ion mobility data ion counts are additive (we're
        /// trying to measure ions per injection, basically).
        /// </summary>
        private ExtractedSpectrum FilterSpectrumList(MsDataSpectrum[] spectra,
                                                     SpectrumProductFilter[] productFilters, bool highAcc, bool useIonMobilityHighEnergyOffset)
        {
            int targetCount = 1;

            if (Q1 == 0)
            {
                highAcc = false;    // No mass error for all-ions extraction
            }
            else
            {
                if (productFilters.Length == 0)
                {
                    return(null);
                }
                targetCount = productFilters.Length;
            }

            float[]  extractedIntensities = new float[targetCount];
            float[]  massErrors           = highAcc ? new float[targetCount] : null;
            double[] meanErrors           = highAcc ? new double[targetCount] : null;

            int    spectrumCount = 0;
            int    rtCount       = 0;
            double lastRT        = 0;

            var imRangeHelper = new IonMobilityRangeHelper(spectra, useIonMobilityHighEnergyOffset ? productFilters : null,
                                                           MinIonMobilityValue, MaxIonMobilityValue);

            if (imRangeHelper.IndexFirst >= spectra.Length)
            {
                // No ion mobility match - record a zero intensity unless IM value is outside the
                // machine's measured range, or if this is a polarity mismatch
                if (!IsOutsideSpectraRangeIM(spectra, MinIonMobilityValue, MaxIonMobilityValue) &&
                    spectra.Any(s => Equals(s.NegativeCharge, Q1.IsNegative)))
                {
                    spectrumCount++; // Our flag to process this as zero rather than null
                }
            }
//            if (spectra.Length > 1)
//                Console.Write(string.Empty);
            for (int specIndex = imRangeHelper.IndexFirst; specIndex < spectra.Length; specIndex++)
            {
                var spectrum = spectra[specIndex];

                if (imRangeHelper.IsBeyondRange(spectrum))
                {
                    break;
                }

                // If these are spectra from distinct retention times, average them.
                // Note that for ion mobility data we will see fewer retention time changes
                // than the total spectra count - ascending DT (or descending 1/K0) within each RT.  Within a
                // single retention time the ions are additive.
                var rt = spectrum.RetentionTime ?? 0;
                if (lastRT != rt)
                {
                    rtCount++;
                    lastRT = rt;
                }

                // Filter on scan polarity
                if (Q1.IsNegative != spectrum.NegativeCharge)
                {
                    continue;
                }

                spectrumCount++;

                var mzArray = spectrum.Mzs;
                if (mzArray == null || mzArray.Length == 0)
                {
                    continue;
                }

                // It's not unusual for mzarray and centerArray to have no overlap, esp. with ion mobility data
                if (Q1 != 0)
                {
                    var lastProductFilter = productFilters[targetCount - 1];
                    if (lastProductFilter.TargetMz.Value + lastProductFilter.FilterWidth / 2 < mzArray[0])
                    {
                        continue;
                    }
                }

                var intensityArray = spectrum.Intensities;
                var imsArray       = spectrum.IonMobilities;

                // Search for matching peaks for each Q3 filter
                // Use binary search to get to the first m/z value to be considered more quickly
                // This should help MS1 where isotope distributions will be very close in m/z
                // It should also help MS/MS when more selective, larger fragment ions are used,
                // since then a lot of less selective, smaller peaks must be skipped
                int iPeak = 0;
                for (int targetIndex = 0; targetIndex < targetCount; targetIndex++)
                {
                    var productFilter = productFilters[targetIndex];
                    // Ensure uncombined IM spectra are within range
                    if (spectrum.IonMobilities == null &&
                        !ContainsIonMobilityValue(spectrum.IonMobility, useIonMobilityHighEnergyOffset
                            ? productFilter.HighEnergyIonMobilityValueOffset : 0))
                    {
                        continue;
                    }

                    // Look for the first peak that is greater than the start of the filter
                    double targetMz = 0, endFilter = double.MaxValue;
                    if (Q1 != 0)
                    {
                        targetMz = productFilter.TargetMz;
                        double filterWindow = productFilter.FilterWidth;
                        double startFilter  = targetMz - filterWindow / 2;
                        endFilter = startFilter + filterWindow;

                        if (iPeak < mzArray.Length)
                        {
                            iPeak = Array.BinarySearch(mzArray, iPeak, mzArray.Length - iPeak, startFilter);
                            if (iPeak < 0)
                            {
                                iPeak = ~iPeak;
                            }
                        }
                        if (iPeak >= mzArray.Length)
                        {
                            break; // No further overlap
                        }
                    }

                    // Add the intensity values of all peaks that pass the filter
                    double totalIntensity = extractedIntensities[targetIndex]; // Start with the value from the previous spectrum, if any
                    double meanError      = highAcc ? meanErrors[targetIndex] : 0;
                    for (int iNext = iPeak; iNext < mzArray.Length && mzArray[iNext] < endFilter; iNext++)
                    {
                        double mz        = mzArray[iNext];
                        double intensity = intensityArray[iNext];

                        // Avoid adding points that are not within the allowed ion mobility range
                        if (imsArray != null && !ContainsIonMobilityValue(imsArray[iNext], useIonMobilityHighEnergyOffset
                                ? productFilter.HighEnergyIonMobilityValueOffset : 0))
                        {
                            continue;
                        }

                        if (Extractor == ChromExtractor.summed)
                        {
                            totalIntensity += intensity;
                        }
                        else if (intensity > totalIntensity)
                        {
                            totalIntensity = intensity;
                            meanError      = 0;
                        }

                        // Accumulate weighted mean mass error for summed, or take a single
                        // mass error of the most intense peak for base peak.
                        if (highAcc && (Extractor == ChromExtractor.summed || meanError == 0))
                        {
                            if (totalIntensity > 0.0)
                            {
                                double deltaPeak = mz - targetMz;
                                meanError += (deltaPeak - meanError) * intensity / totalIntensity;
                            }
                        }
                    }
                    extractedIntensities[targetIndex] = (float)totalIntensity;
                    if (meanErrors != null)
                    {
                        meanErrors[targetIndex] = meanError;
                    }
                }
            }
            if (spectrumCount == 0)
            {
                return(null);
            }
            if (meanErrors != null)
            {
                for (int i = 0; i < targetCount; i++)
                {
                    massErrors[i] = (float)SequenceMassCalc.GetPpm(productFilters[i].TargetMz, meanErrors[i]);
                }
            }

            // If we summed across spectra of different retention times, scale per
            // unique retention time (but not per ion mobility value)
            if (Extractor == ChromExtractor.summed && rtCount > 1)
            {
                float scale = (float)(1.0 / rtCount);
                for (int i = 0; i < targetCount; i++)
                {
                    extractedIntensities[i] *= scale;
                }
            }
            var dtFilter = GetIonMobilityWindow();

            return(new ExtractedSpectrum(ModifiedSequence,
                                         PeptideColor,
                                         Q1,
                                         dtFilter,
                                         Extractor,
                                         Id,
                                         productFilters,
                                         extractedIntensities,
                                         massErrors));
        }