/// <summary>
        /// The input UIMF file needs to be a UIMF file created by direct-injection
        /// IMS, with different drift tube voltages at different frames.
        /// This constructor intelligently group voltages together by observing
        /// sharp changes in running voltage standard deviation. The entire mobility
        /// and frame range would be accumulated.
        /// </summary>
        /// <param name="startScan">
        /// The start scan.
        /// </param>
        /// <param name="endScan">
        /// The end scan.
        /// </param>
        /// <param name="startBin">
        /// The start bin.
        /// </param>
        /// <param name="endBin">
        /// The end bin.
        /// </param>
        /// <param name="xCompression">
        /// The x compression.
        /// </param>
        /// <param name="yCompression">
        /// The y compression.
        /// </param>
        /// <param name="fullScan">
        /// The full Scan.
        /// </param>
        /// <param name="exportFormat">
        /// The export Format.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private bool RunVoltageAccumulationWorkflow(int startScan, int endScan, int startBin, int endBin, double xCompression, double yCompression, bool fullScan, FileFormatEnum exportFormat)
        {
            CrossSectionSearchParameters defaultParams = new CrossSectionSearchParameters(88);
            VoltageSeparatedAccumulatedXiCs accumulatedXiCs = new VoltageSeparatedAccumulatedXiCs(this.UimfReader, 100, defaultParams.MzWindowHalfWidthInPpm, defaultParams.DriftTubeLengthInCm);
            IEnumerable<VoltageGroup> voltageGroups = accumulatedXiCs.Keys;
            bool success = true;
            foreach (var voltageGroup in voltageGroups)
            {
                // convert to MzML or UIMFs
                if (exportFormat == FileFormatEnum.UIMF)
                {
                    string outputPath = Path.Combine(this.OutputDir, this.datasetName + "_" + Math.Round(voltageGroup.MeanVoltageInVolts) + "V.uimf");
                    UimfExporter uimfExporter = new UimfExporter();
                    success = success && uimfExporter.ExportVoltageGroupAsSingleFrameUimf(outputPath, voltageGroup, this.UimfReader, this.averageNotSum, startScan, endScan, startBin, endBin, xCompression, yCompression, fullScan);
                    Console.WriteLine("Writing UIMF files to {0}", outputPath);
                }
                else if (exportFormat == FileFormatEnum.MzML)
                {
                    string outputPath = Path.Combine(this.OutputDir, this.datasetName + "_" + Math.Round(voltageGroup.MeanVoltageInVolts) + "V.mzML");
                    RetentionMobilitySwappedMzMLExporter mzMLExporter = new RetentionMobilitySwappedMzMLExporter();
                    success = success && mzMLExporter.ExportMzML(this.inputPath, outputPath, voltageGroup, this.UimfReader, this.averageNotSum);
                    Console.WriteLine("Writing MzML files to {0}", outputPath);
                }
            }

            return success;
        }
        /// <summary>
        /// The run libray match workflow.
        /// </summary>
        /// <param name="target">
        /// The Target.
        /// </param>
        /// <returns>
        /// The <see cref="LibraryMatchResult"/>.
        /// </returns>
        private LibraryMatchResult RunLibrayMatchWorkflow(DriftTimeTarget target)
        {
            Trace.WriteLine("    Target: " + target.CorrespondingChemical);
            Trace.WriteLine("    Target Info: " + target.TargetDescriptor);

            // Generate Theoretical Isotopic Profile
            List<Peak> theoreticalIsotopicProfilePeakList = null;
            string empiricalFormula = target.CompositionWithAdduct.ToPlainString();
            ITheorFeatureGenerator featureGenerator = new JoshTheorFeatureGenerator();
            IsotopicProfile theoreticalIsotopicProfile = featureGenerator.GenerateTheorProfile(empiricalFormula, 1);
            theoreticalIsotopicProfilePeakList = theoreticalIsotopicProfile.Peaklist.Cast<Peak>().ToList();

            // Voltage grouping
            VoltageSeparatedAccumulatedXiCs accumulatedXiCs = new VoltageSeparatedAccumulatedXiCs(this.uimfReader, target.MassWithAdduct, this.Parameters.InitialSearchMassToleranceInPpm, target.NormalizedDriftTimeInMs, this.Parameters.DriftTimeToleranceInMs, this.Parameters.DriftTubeLengthInCm);

            foreach (VoltageGroup voltageGroup in accumulatedXiCs.Keys)
            {
                // TODO Verify the temperature, pressure and drift tube voltage of the voltage group
                // Because we don't record TVP info in the AMT library. we can't verify it yet, so stick with last voltage group.
                if (IMSUtil.IsLastVoltageGroup(voltageGroup, this.NumberOfFrames))
                {
                    double globalMaxIntensity = IMSUtil.MaxIntensityAfterFrameAccumulation(voltageGroup, this.uimfReader);

                    Trace.WriteLine(string.Format("    Temperature/Pressure/Voltage Adjusted Drift time: {0:F4} ms", IMSUtil.DeNormalizeDriftTime(target.NormalizedDriftTimeInMs, voltageGroup)));

                    // Find peaks using multidimensional peak finder.
                    List<IntensityPoint> intensityPoints = accumulatedXiCs[voltageGroup].IntensityPoints;
                    List<FeatureBlob> featureBlobs = PeakFinding.FindPeakUsingWatershed(intensityPoints, this.smoother, this.Parameters.FeatureFilterLevel);
                    List<StandardImsPeak> standardPeaks = featureBlobs.Select(featureBlob => new StandardImsPeak(featureBlob, this.uimfReader, voltageGroup, target.MassWithAdduct, this.Parameters.InitialSearchMassToleranceInPpm)).ToList();

                    // Score features
                    IDictionary<StandardImsPeak, PeakScores> scoresTable = new Dictionary<StandardImsPeak, PeakScores>();
                    Trace.WriteLine(string.Format("    Voltage Group: {0:F4} V, [{1}-{2}]", voltageGroup.MeanVoltageInVolts, voltageGroup.FirstFrameNumber, voltageGroup.LastFrameNumber));

                    foreach (StandardImsPeak peak in standardPeaks)
                    {
                        PeakScores currentStatistics = FeatureScoreUtilities.ScoreFeature(
                            peak,
                            globalMaxIntensity,
                            this.uimfReader,
                            this.Parameters.InitialSearchMassToleranceInPpm,
                            this.Parameters.DriftTimeToleranceInMs,
                            voltageGroup,
                            this.NumberOfScans,
                            target,
                            IsotopicScoreMethod.Angle,
                            theoreticalIsotopicProfilePeakList);
                        scoresTable.Add(peak, currentStatistics);
                    }

                    // filter out features with Ims scans at 1% left or right.
                    Predicate<StandardImsPeak> scanPredicate = blob => FeatureFilters.FilterExtremeDriftTime(blob, (int)this.NumberOfScans);
                    Predicate<StandardImsPeak> shapeThreshold = blob => FeatureFilters.FilterBadPeakShape(blob, scoresTable[blob].PeakShapeScore, this.Parameters.PeakShapeThreshold);
                    Predicate<StandardImsPeak> isotopeThreshold = blob => FeatureFilters.FilterBadIsotopicProfile(blob, scoresTable[blob].IsotopicScore, this.Parameters.IsotopicThreshold);
                    Predicate<StandardImsPeak> massDistanceThreshold = blob => FeatureFilters.FilterHighMzDistance(blob, target, this.Parameters.MatchingMassToleranceInPpm);

                    // Print out candidate features that pass the intensity threshold.
                    foreach (StandardImsPeak peak in standardPeaks)
                    {
                        DriftTimeFeatureDistance distance = new DriftTimeFeatureDistance(target, peak, voltageGroup);

                        bool badScanRange = scanPredicate(peak);
                        bool badPeakShape = shapeThreshold(peak);
                        bool lowIsotopicAffinity = isotopeThreshold(peak);
                        bool lowMzAffinity = massDistanceThreshold(peak);

                        PeakScores currentStatistics = scoresTable[peak];
                        Trace.WriteLine(string.Empty);
                        Trace.WriteLine(string.Format("        Candidate feature found at drift time {0:F2} ms (scan number {1})",
                            peak.PeakApex.DriftTimeCenterInMs,
                            peak.PeakApex.DriftTimeCenterInScanNumber));
                        Trace.WriteLine(
                            string.Format(
                                "            M/Z: {0:F2} Dalton", peak.PeakApex.MzCenterInDalton));
                        Trace.WriteLine(
                            string.Format(
                                "            Drift time: {0:F2} ms (scan number {1})",
                                peak.PeakApex.DriftTimeCenterInMs,
                                peak.PeakApex.DriftTimeCenterInScanNumber));
                        Trace.WriteLine(
                            string.Format("            MzInDalton difference: {0:F2} ppm", distance.MassDifferenceInPpm));

                        Trace.WriteLine(
                            string.Format("            Drift time difference: {0:F4} ms", distance.DriftTimeDifferenceInMs));

                        Trace.WriteLine(string.Format("            Intensity Score: {0:F4}", currentStatistics.IntensityScore));
                        Trace.WriteLine(string.Format("            Peak Shape Score: {0:F4}", currentStatistics.PeakShapeScore));
                        Trace.WriteLine(string.Format("            Isotopic Score:  {0:F4}", currentStatistics.IsotopicScore));
                        Trace.WriteLine(string.Format("            AveragedPeakIntensities:  {0:F4}", peak.SummedIntensities));

                        string rejectionReason = badScanRange ? "        [Bad scan range] " : "        ";
                        rejectionReason += badPeakShape ? "[Bad Peak Shape] " : string.Empty;
                        rejectionReason += lowMzAffinity ? "[Inaccurate Mass] " : string.Empty;
                        rejectionReason += lowIsotopicAffinity ? "[Different Isotopic Profile] " : string.Empty;

                        if (badScanRange || lowIsotopicAffinity || badPeakShape)
                        {
                            Trace.WriteLine(rejectionReason);
                        }
                        else
                        {
                            Trace.WriteLine("        [Pass]");
                        }
                    }

                    standardPeaks.RemoveAll(scanPredicate);

                    standardPeaks.RemoveAll(massDistanceThreshold);

                    standardPeaks.RemoveAll(shapeThreshold);

                    standardPeaks.RemoveAll(isotopeThreshold);

                    if (standardPeaks.Count == 0)
                    {
                        Trace.WriteLine(string.Format("    [No Match]"));
                        Trace.WriteLine(string.Empty);
                        return new LibraryMatchResult(null, AnalysisStatus.Negative, null);
                    }

                    StandardImsPeak closestPeak = standardPeaks.First();
                    DriftTimeFeatureDistance shortestDistance = new DriftTimeFeatureDistance(target, standardPeaks.First(), voltageGroup);
                    foreach (var peak in standardPeaks)
                    {
                        DriftTimeFeatureDistance distance = new DriftTimeFeatureDistance(target, peak, voltageGroup);
                        if (distance.CompareTo(shortestDistance) < 0)
                        {
                            closestPeak = peak;
                        }
                    }

                    Trace.WriteLine(string.Format("    [Match]"));
                    Trace.WriteLine(string.Empty);
                    return new LibraryMatchResult(closestPeak, AnalysisStatus.Positive, shortestDistance);
                }
            }

            throw new Exception("No voltage groups in the Dataset match temerature, pressure, or drift tube voltage setting from the library. Matching failed.");
        }
        public void TestScoring()
        {
            string formula = "C9H13ClN6";
             string fileLocation = Cae;
             MolecularTarget target = new MolecularTarget(formula, IonizationMethod.Protonated, "CAE");

             Console.WriteLine("CompositionWithoutAdduct: " + target.CompositionWithoutAdduct);
             Console.WriteLine("Monoisotopic ViperCompatibleMass: " + target.MonoisotopicMass);

             CrossSectionSearchParameters parameters = new CrossSectionSearchParameters(driftTubeLength);

             var smoother = new SavitzkyGolaySmoother(parameters.NumPointForSmoothing, 2);

             CrossSectionWorkfow workflow = new CrossSectionWorkfow(fileLocation, "output", parameters);

             Console.WriteLine("Ionization method: " + target.Adduct);
             Console.WriteLine("Targeting centerMz: " + target.MassWithAdduct);

             // Generate Theoretical Isotopic Profile
             List<Peak> theoreticalIsotopicProfilePeakList = null;
             if (target.CompositionWithAdduct != null)
             {
                 string empiricalFormula = target.CompositionWithAdduct.ToPlainString();
                 var theoreticalFeatureGenerator = new JoshTheorFeatureGenerator();
                 IsotopicProfile theoreticalIsotopicProfile = theoreticalFeatureGenerator.GenerateTheorProfile(empiricalFormula, 1);
                 theoreticalIsotopicProfilePeakList = theoreticalIsotopicProfile.Peaklist.Cast<Peak>().ToList();
             }

             // Generate VoltageSeparatedAccumulatedXICs
             var uimfReader = new DataReader(fileLocation);
             Console.WriteLine("Input file: {0}", fileLocation);
             VoltageSeparatedAccumulatedXiCs accumulatedXiCs = new VoltageSeparatedAccumulatedXiCs(uimfReader, target.MassWithAdduct, parameters.MzWindowHalfWidthInPpm, driftTubeLength);

             Console.WriteLine();

             // For each voltage, find 2D XIC features
             foreach (VoltageGroup voltageGroup in accumulatedXiCs.Keys)
             {
                 Console.WriteLine("Voltage group: {0} V, Frame {1}-{2}, {3:F2}K, {4:F2}Torr",
                     voltageGroup.MeanVoltageInVolts,
                     voltageGroup.FirstFrameNumber,
                     voltageGroup.LastFrameNumber,
                     voltageGroup.MeanTemperatureInKelvin,
                     voltageGroup.MeanPressureInTorr);

                 List<IntensityPoint> intensityPoints = accumulatedXiCs[voltageGroup].IntensityPoints;
                 List<FeatureBlob> featureBlobs = PeakFinding.FindPeakUsingWatershed(intensityPoints, smoother, parameters.FeatureFilterLevel);
                 List<StandardImsPeak> standardPeaks = featureBlobs.Select(featureBlob => new StandardImsPeak(featureBlob, uimfReader, voltageGroup,  target.MassWithAdduct, parameters.MzWindowHalfWidthInPpm)).ToList();

                 // feature scorings and Target selection.
                 double globalMaxIntensity = IMSUtil.MaxIntensityAfterFrameAccumulation(voltageGroup, uimfReader);

                 // Check each XIC Peak found
                 foreach (var featurePeak in standardPeaks)
                 {
                     // Evaluate feature scores.
                    double intensityScore = FeatureScoreUtilities.IntensityScore(featurePeak, globalMaxIntensity);

                    double isotopicScoreAngle = FeatureScoreUtilities.IsotopicProfileScore(
                         featurePeak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.Angle,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreDistance = FeatureScoreUtilities.IsotopicProfileScore(
                         featurePeak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.EuclideanDistance,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScorePerson = FeatureScoreUtilities.IsotopicProfileScore(
                         featurePeak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.PearsonCorrelation,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreBhattacharyya = FeatureScoreUtilities.IsotopicProfileScore(
                         featurePeak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.Bhattacharyya,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreDistanceAlternative = FeatureScoreUtilities.IsotopicProfileScore(
                         featurePeak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.EuclideanDistanceAlternative,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double peakShapeScore = FeatureScoreUtilities.PeakShapeScore(featurePeak, workflow.uimfReader, workflow.Parameters.MzWindowHalfWidthInPpm, workflow.Parameters.DriftTimeToleranceInMs, voltageGroup, globalMaxIntensity, workflow.NumberOfScans);

                     // Report all features.
                     Console.WriteLine(" feature found at scan number {0}", featurePeak.PeakApex.DriftTimeCenterInScanNumber);
                     Console.WriteLine("     IntensityScore: {0}", intensityScore);
                     Console.WriteLine("     peakShapeScore: {0}", peakShapeScore);
                     Console.WriteLine("     isotopicScore - Angle:    {0}", isotopicScoreAngle);
                     Console.WriteLine("     isotopicScore - Distance: {0}", isotopicScoreDistance);
                     Console.WriteLine("     isotopicScore - Distance2:{0}", isotopicScoreDistanceAlternative);
                     Console.WriteLine("     isotopicScore - Pearson:  {0}", isotopicScorePerson);
                     Console.WriteLine("     isotopicScore - Bhattacharyya: {0}", isotopicScoreBhattacharyya);

                     Console.WriteLine();
                 }

                 Console.WriteLine();
             }

             workflow.Dispose();
        }
        /// <summary>
        /// The run molecule informed work flow.
        /// </summary>
        /// <param name="target">
        /// The Target.
        /// </param>
        /// <param name="detailedVerbose">
        /// </param>
        /// <returns>
        /// The <see cref="CrossSectionWorkflowResult"/>.
        /// </returns>
        public CrossSectionWorkflowResult RunCrossSectionWorkFlow(IImsTarget target, bool detailedVerbose = true)
        {
            // Reassign trace listener to print to console as well as the target directory.
            using (this.ResetTraceListenerToTarget(target, this.DatasetName))
            {
                try
                {
                    // Get the monoisotopic mass for viper, which is different from anything else.
                    double viperFriendlyMass = target.MassWithAdduct;
                    if (target.ChargeState < 0)
                    {
                        viperFriendlyMass = viperFriendlyMass + new Composition(0, target.ChargeState, 0, 0, 0).Mass;
                    }
                    else
                    {
                        viperFriendlyMass = viperFriendlyMass - new Composition(0, Math.Abs(target.ChargeState), 0, 0, 0).Mass;
                    }

                    // Generate Theoretical Isotopic Profile
                    List<Peak> theoreticalIsotopicProfilePeakList = null;
                    if (target.HasCompositionInfo)
                    {
                        int chargeStateAbs = Math.Abs(target.ChargeState);

                        // Again this isotopic profile generator auto adds hydrogen for you depending on charge states. So here take it out.
                        Composition compensatedComposition = target.CompositionWithAdduct - new Composition(0, chargeStateAbs, 0, 0, 0);
                        string empiricalFormula = compensatedComposition.ToPlainString();

                        IsotopicProfile theoreticalIsotopicProfile = this.theoreticalFeatureGenerator.GenerateTheorProfile(empiricalFormula, chargeStateAbs);

                        theoreticalIsotopicProfilePeakList = theoreticalIsotopicProfile.Peaklist.Cast<Peak>().ToList();
                    }

                    Trace.WriteLine(string.Format("Dataset: {0}", this.uimfReader.UimfFilePath));
                    ReportTargetInfo(target, detailedVerbose);

                    double targetMz = Math.Abs(target.MassWithAdduct / target.ChargeState);

                    // Voltage grouping. Note that we only accumulate frames as needed. Accumulate frames globally is too costly.
                    // Here we accumulate the XICs around target MZ.
                    VoltageSeparatedAccumulatedXiCs accumulatedXiCs = new VoltageSeparatedAccumulatedXiCs(this.uimfReader, targetMz, this.Parameters.MzWindowHalfWidthInPpm, this.Parameters.DriftTubeLengthInCm);

                    // Remove voltage groups that don't have sufficient frame accumulations
                    IEnumerable<VoltageGroup> remainingVGs = accumulatedXiCs.Keys;
                    IEnumerable<VoltageGroup> toBeRemoved = VoltageGroupFilters.RemoveVoltageGroupsWithInsufficentFrames(remainingVGs, this.Parameters.InsufficientFramesFraction);
                    foreach (VoltageGroup voltageGroup in toBeRemoved)
                    {
                        accumulatedXiCs.Remove(voltageGroup);
                    }

                    // Perform feature detection and scoring and the given MzInDalton range on the accumulated XICs to get the base peaks.
                    if (detailedVerbose)
                    {
                        Trace.WriteLine("Feature detection and scoring: ");
                    }

                    IList<VoltageGroup> rejectedVoltageGroups = new List<VoltageGroup>();
                    IList<ObservedPeak> filteredObservations = new List<ObservedPeak>();
                    IList<ObservedPeak> allObservations = new List<ObservedPeak>();
                    IDictionary<string, IList<ObservedPeak>> rejectedObservations = new Dictionary<string, IList<ObservedPeak>>();

                    var numberOfParticipatingVGs = accumulatedXiCs.Keys.Count;

                    // Iterate through the features and perform filtering on isotopic affinity, intensity, drift time and peak shape.
                    foreach (VoltageGroup voltageGroup in accumulatedXiCs.Keys)
                    {
                        double globalMaxIntensity = IMSUtil.MaxIntensityAfterFrameAccumulation(voltageGroup, this.uimfReader);

                        List<StandardImsPeak> standardPeaks = this.FindPeaksBasedOnXIC(voltageGroup, accumulatedXiCs[voltageGroup], target);

                        // Score features
                        IDictionary<StandardImsPeak, PeakScores> scoresTable = new Dictionary<StandardImsPeak, PeakScores>();
                        if (detailedVerbose)
                        {
                            Trace.WriteLine(
                                string.Format(
                                    "    Voltage group: {0:F2} V, Frame {1}-{2}, {3:F2}K, {4:F2}Torr",
                                    voltageGroup.MeanVoltageInVolts,
                                    voltageGroup.FirstFrameNumber,
                                    voltageGroup.LastFrameNumber,
                                    voltageGroup.MeanTemperatureInKelvin,
                                    voltageGroup.MeanPressureInTorr));

                        }

                        foreach (StandardImsPeak peak in standardPeaks)
                        {
                            PeakScores currentStatistics = FeatureScoreUtilities.ScoreFeature(
                                peak,
                                globalMaxIntensity,
                                this.uimfReader,
                                this.Parameters.MzWindowHalfWidthInPpm,
                                this.Parameters.DriftTimeToleranceInMs,
                                voltageGroup,
                                this.NumberOfScans,
                                target,
                                IsotopicScoreMethod.Angle,
                                theoreticalIsotopicProfilePeakList);

                            scoresTable.Add(peak, currentStatistics);
                        }

                        double maxIntensity = standardPeaks.Max(x => x.SummedIntensities);

                        Predicate<StandardImsPeak> relativeIntensityThreshold = imsPeak => FeatureFilters.FilterOnRelativeIntesity(imsPeak, maxIntensity, this.Parameters.RelativeIntensityPercentageThreshold);

                        // filter out non Target peaks and noise.
                        Predicate<StandardImsPeak> absoluteIntensityThreshold = imsPeak => FeatureFilters.FilterOnAbsoluteIntensity(imsPeak, scoresTable[imsPeak].IntensityScore, this.Parameters.AbsoluteIntensityThreshold);

                        // filter out features with Ims scans at 1% left or right.
                        Predicate<StandardImsPeak> scanPredicate = imsPeak => FeatureFilters.FilterExtremeDriftTime(imsPeak, this.NumberOfScans);

                        // filter out features with bad peak shapes.
                        Predicate<StandardImsPeak> shapeThreshold = imsPeak => FeatureFilters.FilterBadPeakShape(imsPeak, scoresTable[imsPeak].PeakShapeScore, this.Parameters.PeakShapeThreshold);

                        // filter out features with distant isotopic profile.
                        Predicate<StandardImsPeak> isotopeThreshold = imsPeak => FeatureFilters.FilterBadIsotopicProfile(imsPeak, scoresTable[imsPeak].IsotopicScore, this.Parameters.IsotopicThreshold);

                        // Print out candidate features and how they were rejected.
                        foreach (StandardImsPeak peak in standardPeaks)
                        {
                            PeakScores currentStatistics = scoresTable[peak];
                            string rejectionReason = ReportFeatureEvaluation(
                                peak,
                                currentStatistics,
                                detailedVerbose,
                                target,
                                scanPredicate(peak),
                                absoluteIntensityThreshold(peak),
                                relativeIntensityThreshold(peak),
                                shapeThreshold(peak),
                                isotopeThreshold(peak));
                            bool pass = string.IsNullOrEmpty(rejectionReason);

                            ObservedPeak analyzedPeak = new ObservedPeak(voltageGroup, peak, currentStatistics);
                            if (pass)
                            {
                                filteredObservations.Add(analyzedPeak);
                            }
                            else
                            {
                                if (rejectedObservations.ContainsKey(rejectionReason))
                                {
                                    rejectedObservations[rejectionReason].Add(analyzedPeak);
                                }

                                else
                                {
                                    rejectedObservations.Add(rejectionReason, new List<ObservedPeak>(){analyzedPeak});
                                }
                            }

                            allObservations.Add(analyzedPeak);
                        }

                        standardPeaks.RemoveAll(scanPredicate);

                        standardPeaks.RemoveAll(absoluteIntensityThreshold);

                        standardPeaks.RemoveAll(relativeIntensityThreshold);

                        standardPeaks.RemoveAll(shapeThreshold);

                        if (target.HasCompositionInfo)
                        {
                            standardPeaks.RemoveAll(isotopeThreshold);
                        }

                        if (standardPeaks.Count == 0)
                        {
                            if (detailedVerbose)
                            {
                                Trace.WriteLine(string.Format("    (All features were rejected in voltage group {0:F4} V)",     voltageGroup.MeanVoltageInVolts));
                                Trace.WriteLine(string.Empty);
                                Trace.WriteLine(string.Empty);
                            }

                            rejectedVoltageGroups.Add(voltageGroup);
                        }

                        // Rate the feature's VoltageGroupScoring score. VoltageGroupScoring score measures how likely the voltage group contains and detected the Target ion.
                        voltageGroup.VoltageGroupScore = VoltageGroupScoring.ComputeVoltageGroupStabilityScore(voltageGroup);
                    }

                    // Remove voltage groups that were rejected
                    foreach (VoltageGroup voltageGroup in rejectedVoltageGroups)
                    {
                        accumulatedXiCs.Remove(voltageGroup);
                    }

                    IEnumerable<ObservedPeak> rejectedPeaks = rejectedObservations.Values.SelectMany(x => x);

                    // Report analysis as negative
                    if (accumulatedXiCs.Keys.Count == 0)
                    {
                        CrossSectionWorkflowResult informedResult = CrossSectionWorkflowResult.CreateNegativeResult(rejectedPeaks, rejectedVoltageGroups, target, this.DatasetPath, this.OutputPath, this.SampleCollectionDate);
                        ReportAnslysisResultAndMetrics(informedResult, detailedVerbose);
                        return informedResult;
                    }
                    else
                    {
                    // Perform the data association algorithm.
                    IIonTracker tracker = new CombinatorialIonTracker(3000);

                    // Because for somereason we are not keeping track of drift tube length in UIMF...so we kind of have to go ask the instrument operator..
                    double driftTubeLength = this.Parameters.DriftTubeLengthInCm;
                    AssociationHypothesis optimalAssociationHypothesis = tracker.FindOptimumHypothesis(filteredObservations, driftTubeLength, target, this.Parameters, numberOfParticipatingVGs);

                    if (optimalAssociationHypothesis == null)
                    {
                        CrossSectionWorkflowResult negResult = CrossSectionWorkflowResult.CreateNegativeResult(rejectedPeaks, rejectedVoltageGroups, target, this.DatasetPath, this.OutputPath, this.SampleCollectionDate);
                        ReportAnslysisResultAndMetrics(negResult, detailedVerbose);
                        return negResult;
                    }

                    if (detailedVerbose)
                    {
                        Console.WriteLine("Writes QC plot of fitline to " + this.OutputPath);
                        Trace.WriteLine(string.Empty);
                    }

                    string extension = (this.Parameters.GraphicsExtension.StartsWith(".")) ?
                        this.Parameters.GraphicsExtension : "." + this.Parameters.GraphicsExtension;
                    string outputPath = string.Format("{0}target_{1}_in_{2}_QA{3}", this.OutputPath, target.TargetDescriptor, this.DatasetName, extension);
                    ImsInformedPlotter plotter = new ImsInformedPlotter();
                    plotter.PlotAssociationHypothesis(optimalAssociationHypothesis, outputPath, this.DatasetName, target, rejectedObservations);

                    // Printout results
                    if (detailedVerbose)
                    {
                        Trace.WriteLine("Target Data Association");
                        int count = 0;
                        foreach (IsomerTrack track in optimalAssociationHypothesis.Tracks)
                        {
                            Trace.WriteLine(string.Format(" T{0}:   ", count));
                            foreach (ObservedPeak peak in track.ObservedPeaks)
                            {
                                Trace.WriteLine(string.Format("       [td: {0:F4}ms, V: {1:F4}V, T: {2:F4}K, P: {3:F4}Torr]",
                                    peak.Peak.PeakApex.DriftTimeCenterInMs,
                                    peak.VoltageGroup.MeanVoltageInVolts,
                                    peak.VoltageGroup.MeanTemperatureInKelvin,
                                    peak.VoltageGroup.MeanPressureInTorr));
                            }
                            count++;
                            Trace.WriteLine("");
                        }

                        Trace.WriteLine("");
                    }

                    // Remove outliers with high influence.
                    // FitLine.RemoveOutliersAboveThreshold(3, minFitPoints);

                    // Remove outliers until min fit point is reached or good R2 is achieved.
                    // while (TrackFilter.IsLowR2(FitLine.RSquared) && FitLine.FitPointCollection.Count > minFitPoints)
                    // {
                    //     FitLine.RemoveOutlierWithHighestCookDistance(minFitPoints);
                    // }

                    // Remove the voltage considered outliers
                    // foreach (VoltageGroup voltageGroup in accumulatedXiCs.Keys.Where(p => FitLine.OutlierCollection.Contains(p.FitPoint)).ToList())
                    // {
                    //     accumulatedXiCs.Remove(voltageGroup);
                    // }

                    CrossSectionWorkflowResult informedResult = CrossSectionWorkflowResult.CreateResultFromAssociationHypothesis(
                        this.Parameters,
                        optimalAssociationHypothesis,
                        target,
                        accumulatedXiCs.Keys,
                        allObservations,
                        this.DatasetPath,
                        this.OutputPath,
                        this.SampleCollectionDate,
                        viperFriendlyMass
                        );
                        ReportAnslysisResultAndMetrics(informedResult, detailedVerbose);

                        Trace.Listeners.Clear();
                    return informedResult;
                    }
                }
                catch (Exception e)
                {
                    // Print result
                    Trace.WriteLine(e.Message);
                    Trace.WriteLine(e.StackTrace);
                    Trace.Listeners.Clear();
                    Trace.Close();
                        Trace.Listeners.Clear();

                    // create the error result
                    return CrossSectionWorkflowResult.CreateErrorResult(target, this.DatasetName, this.DatasetPath, this.OutputPath, this.SampleCollectionDate);
                }
            }
        }
        public void TestFormulaPerturbance()
        {
            List<Tuple<string, string>> formulas = new List<Tuple<string, string>>();

             // truth
             formulas.Add(new Tuple<string, string>("True formula", "C12H10O4S"));
             formulas.Add(new Tuple<string, string>("1 extra H", "C12H11O4S"));
             formulas.Add(new Tuple<string, string>("2 extra H", "C12H12O4S"));
             formulas.Add(new Tuple<string, string>("3 extra H", "C12H13O4S"));
             formulas.Add(new Tuple<string, string>("3 extra H", "C12H14O4S"));
             formulas.Add(new Tuple<string, string>("4 extra H", "C12H15O4S"));
             formulas.Add(new Tuple<string, string>("5 extra H", "C12H16O4S"));
             formulas.Add(new Tuple<string, string>("1 less H", "C12H9O4S"));
             formulas.Add(new Tuple<string, string>("2 less H", "C12H8O4S"));
             formulas.Add(new Tuple<string, string>("3 less H", "C12H7O4S"));
             formulas.Add(new Tuple<string, string>("4 less H", "C12H6O4S"));
             Console.WriteLine("[Intensity], [Distance1], [Distance2], [Angle], [Pearson], [Bucha]");

             string fileLocation = BPSNegative;
             CrossSectionSearchParameters parameters = new CrossSectionSearchParameters(driftTubeLength);
             CrossSectionWorkfow workflow = new CrossSectionWorkfow(fileLocation, "output", parameters);

             foreach (var form in formulas)
             {
                 bool found = false;

                 MolecularTarget target = new MolecularTarget(form.Item2, new IonizationAdduct(IonizationMethod.Deprotonated), form.Item1);
                 Console.Write(form.Item1 + ": ");
                 var smoother = new SavitzkyGolaySmoother(parameters.NumPointForSmoothing, 2);

                 // Generate Theoretical Isotopic Profile
                 List<Peak> theoreticalIsotopicProfilePeakList = null;
                 if (target.CompositionWithAdduct != null)
                 {
                     string empiricalFormula = target.CompositionWithAdduct.ToPlainString();
                     var theoreticalFeatureGenerator = new JoshTheorFeatureGenerator();
                     IsotopicProfile theoreticalIsotopicProfile = theoreticalFeatureGenerator.GenerateTheorProfile(empiricalFormula, 1);
                     theoreticalIsotopicProfilePeakList = theoreticalIsotopicProfile.Peaklist.Cast<Peak>().ToList();
                 }

                 // Generate VoltageSeparatedAccumulatedXICs
                 var uimfReader = new DataReader(fileLocation);
                 VoltageSeparatedAccumulatedXiCs accumulatedXiCs = new VoltageSeparatedAccumulatedXiCs(uimfReader, target.MassWithAdduct, parameters.MzWindowHalfWidthInPpm, parameters.DriftTubeLengthInCm);

                 var voltageGroup = accumulatedXiCs.Keys.First();

                 // Find peaks using multidimensional peak finder.
                 List<IntensityPoint> intensityPoints = accumulatedXiCs[voltageGroup].IntensityPoints;
                 List<FeatureBlob> featureBlobs = PeakFinding.FindPeakUsingWatershed(intensityPoints, smoother, parameters.FeatureFilterLevel);
                 List<StandardImsPeak> standardPeaks = featureBlobs.Select(featureBlob => new StandardImsPeak(featureBlob, uimfReader, voltageGroup,  target.MassWithAdduct, parameters.MzWindowHalfWidthInPpm)).ToList();

                 // feature scorings and Target selection.
                 double globalMaxIntensity = IMSUtil.MaxIntensityAfterFrameAccumulation(voltageGroup, uimfReader);

                 // Check each XIC Peak found
                 foreach (var peak in standardPeaks)
                 {
                     // Evaluate feature scores.
                     double intensityScore = FeatureScoreUtilities.IntensityScore(peak, globalMaxIntensity);

                     double isotopicScoreAngle = FeatureScoreUtilities.IsotopicProfileScore(
                         peak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.Angle,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreDistance = FeatureScoreUtilities.IsotopicProfileScore(
                         peak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.EuclideanDistance,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScorePerson = FeatureScoreUtilities.IsotopicProfileScore(
                         peak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.PearsonCorrelation,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreBhattacharyya = FeatureScoreUtilities.IsotopicProfileScore(
                         peak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.Bhattacharyya,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double isotopicScoreDistanceAlternative = FeatureScoreUtilities.IsotopicProfileScore(
                         peak,
                         workflow.uimfReader,
                         target,
                         theoreticalIsotopicProfilePeakList,
                         voltageGroup,
                         IsotopicScoreMethod.EuclideanDistanceAlternative,
                         globalMaxIntensity,
                         workflow.NumberOfScans);

                     double peakShapeScore = FeatureScoreUtilities.PeakShapeScore(peak, workflow.uimfReader, workflow.Parameters.MzWindowHalfWidthInPpm, workflow.Parameters.DriftTimeToleranceInMs, voltageGroup, globalMaxIntensity, workflow.NumberOfScans);

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", intensityScore);
                         found = true;
                     }

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", isotopicScoreDistance);
                         found = true;
                     }

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", isotopicScoreDistanceAlternative);
                         found = true;
                     }

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", isotopicScoreAngle);
                         found = true;
                     }

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", isotopicScorePerson);
                         found = true;
                     }

                     // Report all features.
                     if (peak.PeakApex.DriftTimeCenterInScanNumber == 115)
                     {
                         Console.Write("{0:F4} ", isotopicScoreBhattacharyya);
                         found = true;
                     }
                 }

                 if (!found)
                 {
                     Console.Write("No features");
                 }

                 Console.WriteLine();
             }

             // Manually dispose so it doesn't interfere with other tests.
             workflow.Dispose();
        }