/// <summary>
 /// Initializes a new instance of the <see cref="ObservedPeak"/> class with peak and its calculated statistics.
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <param name="peak">
 /// The peak.
 /// </param>
 /// <param name="statistics">
 /// The Statistics.
 /// </param>
 public ObservedPeak(VoltageGroup group, StandardImsPeak peak, PeakScores statistics)
 {
     this.VoltageGroup = group;
     this.Peak = peak;
     this.Statistics = statistics;
     this.ObservationType = ObservationType.Peak;
     this.mobilityPoint = null;
 }
        public bool ExportMzML(string sourceUIMFPath, string outputPath, VoltageGroup voltageGroup, DataReader originalUIMF, bool averageNotSum)
        {
            FrameParams frameParam = originalUIMF.GetFrameParams(voltageGroup.FirstFrameNumber);
            GlobalParams globalParams = originalUIMF.GetGlobalParams();

            this.scans = (int)frameParam.GetValueDouble(FrameParamKeyType.Scans);
            this.bins = (int)globalParams.GetValueDouble(GlobalParamKeyType.Bins);

            this.calibrationSlope = frameParam.GetValueDouble(FrameParamKeyType.CalibrationSlope);
            this.calibrationIntercept = frameParam.GetValueDouble(FrameParamKeyType.CalibrationIntercept);
            this.binWidth = globalParams.GetValueDouble(GlobalParamKeyType.BinWidth);
            this.tofCorrectionTime = globalParams.GetValueDouble(GlobalParamKeyType.TOFCorrectionTime);

            if (File.Exists(outputPath))
            {
                File.Delete(outputPath);
            }

            this.dateTime = globalParams.GetValue(GlobalParamKeyType.DateStarted);
            string datasetName = Path.GetFileNameWithoutExtension(outputPath);

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(outputPath, settings))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("mzML", "http://psi.hupo.org/ms/mzml");
                writer.WriteAttributeString("id", datasetName);
                writer.WriteAttributeString("version", "1.1.0");
                writer.WriteAttributeString("xmlns", "xls", string.Empty, "http://www.w3.org/2001/XMLSchema-instance");
                writer.WriteAttributeString("xmlns", "schemaLocation", string.Empty, "http://psi.hupo.org/ms/mzml http://psidev.info/files/ms/mzML/xsd/mzML1.1.0.xsd");
                this.WriteCVList(writer);
                this.WriteFileDescription(writer, sourceUIMFPath);
                this.WriteSoftwareList(writer);
                this.WriteInstrumentConfigurationList(writer);
                this.WriteDataProcessingList(writer);
                this.WriteRun(writer, outputPath, originalUIMF, voltageGroup);
                writer.WriteEndElement();
                writer.WriteEndDocument();
                return true;
            }
        }
        /// <summary>
        /// Binary increment the selected peaks 
        /// </summary>
        /// <param name="groups">
        /// The groups.
        /// </param>
        /// <param name="selectedPeaks">
        /// The selected peaks.
        /// </param>
        /// <param name="onIndex">
        /// The on index.
        /// </param>
        /// <param name="graph">
        /// The base peak map.
        /// </param>
        /// <param name="minFitPoints">
        /// The min Fit Points.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        /// If incrementing overflows occurs/ all combinations were gone through.
        /// <exception cref="Exception">
        /// </exception>
        private bool IncrementCombination(VoltageGroup[] groups, ref ObservedPeak[] selectedPeaks, int onIndex, ObservationTransitionGraph<IonTransition> graph)
        {
            int peakIndex;

            if (groups.Length != selectedPeaks.Length)
            {
                throw new ArgumentException("Votlage group does not match selected peaks");
            }

            int length = selectedPeaks.Length;

            // Base case
            if (onIndex >= length)
            {
                return true;
            }

            ObservedPeak incrementPeak = selectedPeaks[onIndex];
            VoltageGroup group = groups[onIndex];

            IList<ObservedPeak> candidates = graph.FindPeaksInVoltageGroup(group).ToList();

            // Empty peak
            if (incrementPeak == null)
            {
                peakIndex = -1;
            }
            else
            {
                peakIndex = candidates.IndexOf(incrementPeak);
                if (peakIndex < 0)
                {
                    throw new Exception("The combination to be incremented is not in the base peak map space.");
                }
            }

            if (peakIndex + 1 >= candidates.Count)
            {
                if (selectedPeaks[onIndex] != null)
                {
                    this.trackPointsCounter--;
                }
                selectedPeaks[onIndex] = null;

                return this.IncrementCombination(groups, ref selectedPeaks, onIndex + 1, graph);
            }
            else
            {
                if (peakIndex + 1 == 0)
                {
                    this.trackPointsCounter++;
                }

                selectedPeaks[onIndex] = candidates[peakIndex + 1];
                return false;
            }
        }
        /// <summary>
        /// The increment combination.
        /// </summary>
        /// <param name="groups">
        /// The groups.
        /// </param>
        /// <param name="selectedPeaks">
        /// The selected peaks.
        /// </param>
        /// <param name="basePeakMap">
        /// The base peak map.
        /// </param>
        /// <param name="minFitPoints">
        /// The min Fit Points.
        /// </param>
        /// <returns>
        /// If the increment overflows the "counter"<see cref="bool"/>.
        /// </returns>
        private bool IncrementCombination(VoltageGroup[] groups, ref ObservedPeak[] selectedPeaks, ObservationTransitionGraph<IonTransition> graph, int minFitPoints)
        {
            bool overflow = false;

            // So-called do while loop
            overflow = this.IncrementCombination(groups, ref selectedPeaks, 0, graph);

            while (!overflow && this.trackPointsCounter < minFitPoints)
            {
                overflow = this.IncrementCombination(groups, ref selectedPeaks, 0, graph);
            }

            return overflow;
        }
        public static double MaxGlobalIntensities(VoltageGroup group, DataReader reader)
        {
            GlobalParams global = reader.GetGlobalParams();
            FrameParams param = reader.GetFrameParams(@group.FirstFrameNumber);
            int firstFrame = group.FirstFrameNumber;
            int lastFrame = group.LastFrameNumber;
            int firstScan = 1;
            int lastScan = param.Scans;
            int firstBin = 0;
            int lastBin = global.Bins;
            int windowOfBins = 1000;
            int windowOfScans = 20;
            double maxIntensities = 0;
            for (int scan = firstScan; scan <= lastScan; scan += windowOfScans)
            {
                int endScan = (scan + windowOfScans > lastScan) ? lastScan : scan + windowOfScans;
                for (int bin = firstBin; bin <= lastBin; bin += windowOfBins)
                {
                    int endBin = (bin + windowOfBins > lastBin) ? lastBin : bin + windowOfBins;

                    double localMaxIntensities = MaxGlobalIntensities(reader, scan, endScan, bin, endBin, firstFrame, lastFrame);
                    maxIntensities = (localMaxIntensities > maxIntensities) ? localMaxIntensities : maxIntensities;
                }
            }
            return maxIntensities;
        }
 /// <summary>
 /// The is last voltage group.
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <param name="totalFrames">
 /// The total frames.
 /// </param>
 /// <returns>
 /// The <see cref="bool"/>.
 /// </returns>
 public static bool IsLastVoltageGroup(VoltageGroup group, int totalFrames)
 {
     return group.LastFrameNumber == totalFrames;
 }
 public static double DeNormalizeDriftTime(double driftTime, VoltageGroup group)
 {
     double normalizedPressure = Metrics.Nondimensionalized2Torr(group.MeanPressureNondimensionalized) / Metrics.StandardImsPressureInTorr;
     double normalizedTemperature = Metrics.Nondimensionalized2Kelvin(group.MeanTemperatureNondimensionalized) / Metrics.RoomTemperatureInKelvin;
     return driftTime * normalizedPressure;
 }
 /// <summary>
 /// return the maximum intensity value possible for a given voltage group
 /// Note currently supports 8-bit digitizers, proceeds with caution when
 /// dealing with 12-bit digitizers
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <param name="reader">
 /// The reader.
 /// </param>
 /// <returns>
 /// The <see cref="double"/>.
 /// </returns>
 public static double MaxIntensityAfterFrameAccumulation(VoltageGroup group, DataReader reader)
 {
     return 255 * group.FrameAccumulationCount * reader.GetFrameParams(group.FirstFrameNumber).GetValueInt32(FrameParamKeyType.Accumulations);
 }
        /// <summary>
        /// The score feature.
        /// </summary>
        /// <param name="peak">
        /// The peak.
        /// </param>
        /// <param name="globalMaxIntensity">
        /// The global max intensity.
        /// </param>
        /// <param name="uimfReader">
        /// The uimf reader.
        /// </param>
        /// <param name="massToleranceInPpm">
        /// The mass tolerance in ppm.
        /// </param>
        /// <param name="driftTimeToleranceInMs">
        /// The drift time tolerance in ms.
        /// </param>
        /// <param name="voltageGroup">
        /// The voltage group.
        /// </param>
        /// <param name="voltageGroupScans">
        /// The voltage group scans.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <param name="isotopicScoreMethod">
        /// The isotopic score method.
        /// </param>
        /// <param name="theoreticalIsotopicProfile">
        /// The theoretical isotopic profile.
        /// </param>
        /// <returns>
        /// The <see cref="PeakScores"/>.
        /// </returns>
        public static PeakScores ScoreFeature(this StandardImsPeak peak, double globalMaxIntensity, DataReader uimfReader, double massToleranceInPpm, double driftTimeToleranceInMs, VoltageGroup voltageGroup, int voltageGroupScans, IImsTarget target, IsotopicScoreMethod isotopicScoreMethod, List<Peak> theoreticalIsotopicProfile)
        {
            double intensityScore = IntensityScore(peak, globalMaxIntensity);

            double peakShapeScore = PeakShapeScore(
                peak,
                uimfReader,
                massToleranceInPpm,
                driftTimeToleranceInMs,
                voltageGroup,
                globalMaxIntensity,
                voltageGroupScans);

            double isotopicScore = 0;
            if (target.HasCompositionInfo)
            {
                isotopicScore = IsotopicProfileScore(
                    peak,
                    uimfReader,
                    target,
                    theoreticalIsotopicProfile,
                    voltageGroup,
                    IsotopicScoreMethod.Angle,
                    globalMaxIntensity,
                    voltageGroupScans);
            }

            return new PeakScores(intensityScore, isotopicScore, peakShapeScore);
        }
        /// <summary>
        /// The Peak shape score. Evaluating how "good" the peak looks. A good
        /// peak shape score indicates that the peak is not a result of noise
        /// or instrument errors. Mostly the feature intensity along is sufficient
        /// to exclude noise but a good shape score helps evaluating the experiment
        /// and thus the reliability of the data analysis result.
        /// </summary>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="massToleranceInPpm">
        /// The mass Tolerance In Ppm.
        /// </param>
        /// <param name="driftTimeToleranceInMs">
        /// The drift Time Tolerance In Scans.
        /// </param>
        /// <param name="imsPeak">
        /// The imsPeak.
        /// </param>
        /// <param name="voltageGroup">
        /// The voltage group.
        /// </param>
        /// <param name="targetMz">
        /// The Target MZ.
        /// </param>
        /// <param name="globalMaxIntensities">
        /// The global Max Intensities.
        /// </param>
        /// <param name="numberOfScans">
        /// The number Of Scans.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        public static double PeakShapeScore(StandardImsPeak imsPeak, DataReader reader, double massToleranceInPpm, double driftTimeToleranceInMs, VoltageGroup voltageGroup, double globalMaxIntensities, int numberOfScans)
        {
            int scanRep = imsPeak.PeakApex.DriftTimeCenterInScanNumber;
            double toleranceInMz = massToleranceInPpm / 1e6 * imsPeak.PeakApex.MzCenterInDalton;
            int scanWidth = (int)Math.Ceiling(driftTimeToleranceInMs / 1000 / voltageGroup.AverageTofWidthInSeconds);
            int scanWindowSize = scanWidth * 2 + 1;

            int scanNumberMin = scanRep - scanWidth;
            int scanNumberMax = scanRep + scanWidth;
            if ((scanNumberMin < 0) || (scanNumberMax > numberOfScans - 1))
            {
                return 0;
            }

            int[][] intensityWindow = reader.GetFramesAndScanIntensitiesForAGivenMz(
                voltageGroup.FirstFrameNumber,
                voltageGroup.LastFrameNumber,
                DataReader.FrameType.MS1,
                scanNumberMin,
                scanNumberMax,
                imsPeak.PeakApex.MzCenterInDalton,
                toleranceInMz);

            // Average the intensity window across frames
            int frames = intensityWindow.GetLength(0);
            double[] averagedPeak = new double[scanWindowSize];
            double highestPeak = 0;
            for (int i = 0; i < scanWindowSize; i++)
            {
                for (int j = 0; j < frames; j++)
                {
                    averagedPeak[i] += intensityWindow[j][i];
                }

                averagedPeak[i] /= frames;
                highestPeak = (averagedPeak[i] > highestPeak) ? averagedPeak[i] : highestPeak;
            }

            // For peaks with peak width lower than 3, return a peak score of 0

            // TODO get the intensity threshold here from the noise level instead.
            if (scanWindowSize >= 3)
            {
                if (averagedPeak[scanRep - scanNumberMin] < globalMaxIntensities * 0.0001
                    || averagedPeak[scanRep - scanNumberMin - 1] < globalMaxIntensities * 0.0001
                    || averagedPeak[scanRep - scanNumberMin + 1] < globalMaxIntensities * 0.0001)
                {
                    return 0;
                }
            }

            // Perform a statistical normality test
            double normalityScore = NormalityTest.PeakNormalityTest(averagedPeak, NormalityTest.JaqueBeraTest, 100, globalMaxIntensities);
            return normalityScore;
        }
        /// <summary>
        /// The score feature using isotopic profile.
        /// </summary>
        /// <param name="imsPeak">
        /// The ims Peak.
        /// </param>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="target">
        /// The Target.
        /// </param>
        /// <param name="isotopicPeakList">
        /// The isotopic peak list.
        /// </param>
        /// <param name="voltageGroup">
        /// The voltage Group.
        /// </param>
        /// <param name="selectedMethod">
        /// The selected Method.
        /// </param>
        /// <param name="globalMaxIntensities">
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// </exception>
        public static double IsotopicProfileScore(StandardImsPeak imsPeak, DataReader reader, IImsTarget target, List<Peak> isotopicPeakList, VoltageGroup voltageGroup, IsotopicScoreMethod selectedMethod, double globalMaxIntensities, double totalScans)
        {
            // No need to move on if the isotopic profile is not found
            // if (observedIsotopicProfile == null || observedIsotopicProfile.MonoIsotopicMass < 1)
            // {
            // result.AnalysisStatus = AnalysisStatus.IsotopicProfileNotFound;
            // continue;
            // }

            // Find Isotopic Profile
            // List<Peak> massSpectrumPeaks;
            // IsotopicProfile observedIsotopicProfile = _msFeatureFinder.IterativelyFindMSFeature(massSpectrum, theoreticalIsotopicProfile, out massSpectrumPeaks);
            if (target.CompositionWithoutAdduct == null)
            {
                throw new InvalidOperationException("Cannot score feature using isotopic profile for Ims Target without CompositionWithoutAdduct provided.");
            }

            // Bad Feature, so get out
            if (imsPeak == null)
            {
                return 0;
            }

            // Get the scanWindow size
            int scanNumberMax = imsPeak.PeakApex.DriftTimeFullWidthHalfMaxHigherBondInScanNumber;
            int scanNumberMin = imsPeak.PeakApex.DriftTimeFullWidthHalfMaxLowerBondInScanNumber;
            if ((scanNumberMin < 0) || (scanNumberMax > totalScans - 1))
            {
                return 0;
            }

            // Get the mass error from the observed feature peak from the Target theoretical peak
            double mzOffset = imsPeak.PeakApex.MzCenterInDalton - target.MassWithAdduct;

            List<double> observedIsotopicPeakList = new List<double>();

            int totalIsotopicIndex = isotopicPeakList.Count;
            int[] isotopicIndexMask = new int[totalIsotopicIndex];

            // Find an unsaturated peak in the isotopic profile
            for (int i = 0; i < totalIsotopicIndex; i++)
            {
                // Isotopic centerMz
                double Mz = isotopicPeakList[i].XValue;

                var peakList = reader.GetXic(Mz + mzOffset,
                    imsPeak.PeakApex.MzWindowToleranceInPpm,
                    voltageGroup.FirstFrameNumber,
                    voltageGroup.LastFrameNumber,
                    scanNumberMin,
                    scanNumberMax,
                    DataReader.FrameType.MS1,
                    DataReader.ToleranceType.PPM);

                // Sum the intensities
                double sumIntensities = 0;
                foreach (var point in peakList)
                {
                    sumIntensities += point.Intensity;
                    if (point.IsSaturated)
                    {
                        isotopicIndexMask[i] = 1;
                    }
                }

                sumIntensities /= voltageGroup.FrameAccumulationCount;
                observedIsotopicPeakList.Add(sumIntensities);
            }

            // Return 0 if the intensity sum is really small
            if (observedIsotopicPeakList.Sum() < globalMaxIntensities * 0.0003)
            {
                return 0;
            }

            // If the unsaturated isotopes are below a certain threshold
            if (totalIsotopicIndex - isotopicIndexMask.Sum() <= 1)
            {
                return 0;
            }

            if (selectedMethod == IsotopicScoreMethod.Angle)
            {
                return IsotopicProfileScoreAngle(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.EuclideanDistance)
            {
                return IsotopicProfileScoreEuclidean(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.PearsonCorrelation)
            {
                return PearsonCorrelation(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.Bhattacharyya)
            {
                return BhattacharyyaDistance(observedIsotopicPeakList, isotopicPeakList);
            }
            else if (selectedMethod == IsotopicScoreMethod.EuclideanDistanceAlternative)
            {
                return EuclideanAlternative(observedIsotopicPeakList, isotopicPeakList);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
 /// <summary>
 /// The max global intensities 2.
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <param name="reader">
 /// The reader.
 /// </param>
 /// <returns>
 /// The <see cref="double"/>.
 /// </returns>
 public static double MaxGlobalIntensities2(VoltageGroup group, DataReader reader)
 {
     Stack<int[]> data = reader.GetFrameAndScanListByDescendingIntensity();
     return data.Pop()[0];
 }
        /// <summary>
        /// The find peaks based on xic.
        /// </summary>
        /// <param name="group">
        /// The group.
        /// </param>
        /// <param name="chromatogram">
        /// The chromatogram.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <returns>
        /// The <see cref="IList"/>.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        private List<StandardImsPeak> FindPeaksBasedOnXIC(VoltageGroup voltageGroup, ExtractedIonChromatogram chromatogram, IImsTarget target)
        {
            if (this.Parameters.PeakDetectorSelection == PeakDetectorEnum.WaterShed)
            {
                // Find peaks using multidimensional peak finder.
                List<IntensityPoint> intensityPoints = chromatogram.IntensityPoints;
                List<FeatureBlob> featureBlobs = PeakFinding.FindPeakUsingWatershed(intensityPoints, this.smoother, this.Parameters.FeatureFilterLevel);

                // Recapture the 2D peak using the 1D feature blob from multidimensional peak finder.
                return featureBlobs.Select(featureBlob => new StandardImsPeak(featureBlob, this.uimfReader, voltageGroup, target.MassWithAdduct, this.Parameters.MzWindowHalfWidthInPpm)).ToList();
            }
            else if (this.Parameters.PeakDetectorSelection == PeakDetectorEnum.MASICPeakFinder)
            {
                // Find peaks using MASIC peak finder
                List<IntensityPoint> intensityPoints = chromatogram.IntensityPoints;
                IList<clsPeak> masicPeaks = PeakFinding.FindPeakUsingMasic(intensityPoints, this.NumberOfScans);

                // Recapture the 2D peak using the 1D feature blob from multidimensional peak finder.
                return masicPeaks.Select(peak => new StandardImsPeak(peak)).ToList();
            }
            else
            {
                throw new NotImplementedException(string.Format("{0} not supported", this.Parameters.PeakDetectorSelection));
            }
        }
        private void WriteRun(XmlWriter writer, string outputPath, DataReader reader, VoltageGroup voltageGroup)
        {
            writer.WriteStartElement("run");
            string dataset = Path.GetFileNameWithoutExtension(outputPath);
            writer.WriteAttributeString("id", dataset);
            writer.WriteAttributeString("defaultInstrumentConfigurationRef", "IC");
            writer.WriteAttributeString("startTimeStamp", this.dateTime);

            writer.WriteStartElement("spectrumList");
            writer.WriteAttributeString("count", this.scans.ToString(CultureInfo.InvariantCulture));

            int startingFrame = voltageGroup.FirstFrameNumber;
            int endingFrame = voltageGroup.LastFrameNumber;
            Console.Write("Summing frame[{0} - {1}]...    ", startingFrame, endingFrame);

            double[,] summedIntensities = reader.AccumulateFrameData(startingFrame, endingFrame, false, 1, this.scans, 1, this.bins, -1, -1);

            // Use dirft time scan as LC scan to massage skyline
            for (int lcScan = 1; lcScan <= this.scans; lcScan++)
            {
                float[] mzArray;
                float[] intensityArray;

                this.GetMzIntensityArrayAtScan(summedIntensities, lcScan, out mzArray, out intensityArray);

                double mzLow = mzArray[0];
                double mzHigh = mzArray[mzArray.Count()-1];

                // Write the bins as mass spectrum
                writer.WriteStartElement("spectrum");
                writer.WriteAttributeString("index", String.Format("{0}", lcScan - 1));
                writer.WriteAttributeString("id", String.Format("frame={0} scan={1} frameType={2}", 1, lcScan, 1));
                writer.WriteAttributeString("defaultArrayLength", mzArray.Count().ToString(CultureInfo.InvariantCulture));

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000511");
                writer.WriteAttributeString("name", "ms level");
                writer.WriteAttributeString("value", "1");
                writer.WriteEndElement();

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000579");
                writer.WriteAttributeString("name", "MS1 spectrum");
                writer.WriteAttributeString("value", "");
                writer.WriteEndElement();

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000128");
                writer.WriteAttributeString("name", "profile spectrum");
                writer.WriteAttributeString("value", "");
                writer.WriteEndElement();

                writer.WriteStartElement("scanList");
                writer.WriteAttributeString("count", "1");

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000795");
                writer.WriteAttributeString("name", "no combination");
                writer.WriteAttributeString("value", "");
                writer.WriteEndElement();

                writer.WriteStartElement("scan");

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000016");
                writer.WriteAttributeString("name", "scan start time");
                writer.WriteAttributeString("value", reader.GetDriftTime(voltageGroup.FirstFrameNumber, lcScan, true).ToString(CultureInfo.InvariantCulture));
                writer.WriteAttributeString("unitCvRef", "UO");
                writer.WriteAttributeString("unitAccession", "UO:0000031");
                writer.WriteAttributeString("unitName", "minute");

                writer.WriteEndElement();

                writer.WriteStartElement("scanWindowList");
                writer.WriteAttributeString("count", "1");

                writer.WriteStartElement("scanWindow");

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000501");
                writer.WriteAttributeString("name", "scan window lower limit");
                writer.WriteAttributeString("value", mzLow.ToString(CultureInfo.InvariantCulture));
                writer.WriteAttributeString("unitCvRef", "MS");
                writer.WriteAttributeString("unitAccession", "MS:1000040");
                writer.WriteAttributeString("unitName", "m/z");
                writer.WriteEndElement();

                writer.WriteStartElement("cvParam");
                writer.WriteAttributeString("cvRef", "MS");
                writer.WriteAttributeString("accession", "MS:1000500");
                writer.WriteAttributeString("name", "scan window upper limit");
                writer.WriteAttributeString("value", mzHigh.ToString(CultureInfo.InvariantCulture));
                writer.WriteAttributeString("unitCvRef", "MS");
                writer.WriteAttributeString("unitAccession", "MS:1000040");
                writer.WriteAttributeString("unitName", "m/z");
                writer.WriteEndElement();

                // scan window
                writer.WriteEndElement();

                // scan window list
                writer.WriteEndElement();

                // scan
                writer.WriteEndElement();

                // scan list
                writer.WriteEndElement();

                this.WriteBinaryDataArrays(mzArray, intensityArray, writer);

                // spectrum
                writer.WriteEndElement();
            }

            // spectrum list
            writer.WriteEndElement();

            // run
            writer.WriteEndElement();
        }
 /// <summary>
 /// A voltage group has stability higher score if the voltage group has more accumulations, less variations
 /// on voltage, temperature and pressure.
 /// </summary>
 /// <param name="group">
 /// The group.
 /// </param>
 /// <returns>
 /// The <see cref="double"/>.
 /// </returns>
 public static double ComputeVoltageGroupStabilityScore(VoltageGroup group)
 {
     double stability = group.VariancePressureNondimensionalized * group.VarianceTemperature * group.VarianceVoltage;
     return ScoreUtil.MapToZeroOneTrignometry(stability, true, 0.00000000000000001);
 }