public void PlotAssociationHypothesis(AssociationHypothesis hypothesis, string plotLocation, string datasetName, IImsTarget target, IDictionary<string, IList<ObservedPeak>> preFilteredPeaks)
        {
            //int width = 450;
            //int height = 256;

            int width = 675;
            int height = 384;
            PlotModel associationHypothsisPlot = this.AssociationHypothesisPlot(hypothesis, datasetName, target);
            associationHypothsisPlot = this.AnnotateRemovedPeaks(associationHypothsisPlot, preFilteredPeaks);
            this.PlotDiagram(plotLocation, associationHypothsisPlot, width, height);
        }
        /// <summary>
        /// The find all hypothesis.
        /// </summary>
        /// <param name="validTracks">
        /// The valid tracks.
        /// </param>
        /// <param name="allPeaks"></param>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        private IEnumerable<AssociationHypothesis> FindAllHypothesis(IEnumerable<IsomerTrack> validTracks, IEnumerable<ObservedPeak> allPeaks)
        {
            allPeaks = allPeaks.ToArray();
            List<IsomerTrack> tracks = validTracks.ToList();

            const int AlgorithmLimit = 20;
            int size = tracks.Count;

            if (size >= AlgorithmLimit)
            {
                tracks = tracks.GetRange(0, AlgorithmLimit);
                size = tracks.Count;
            }

            int totalCombinations = (int)Math.Pow(2, size) - 1;

            AssociationHypothesis association = new AssociationHypothesis(allPeaks);

            bool hasConflicted = false;
            for (int i = 0; i < totalCombinations - 1; i++)
            {
                long grey = Combinatorics.BinaryToGray(i + 1);
                if (hasConflicted)
                {
                    association = new AssociationHypothesis(allPeaks);
                    hasConflicted = false;
                    int[] indexOfOnes = Combinatorics.GreyCodeToIndexOfOnes(grey).ToArray();

                    foreach (var index in indexOfOnes)
                    {
                        hasConflicted = association.IsConflict(tracks[index]);
                        if (!hasConflicted)
                        {
                            association.AddIsomerTrack(tracks[index]);
                        }
                        else
                        {
                            break;
                        }
                    }

                    if (!hasConflicted)
                    {
                        yield return (AssociationHypothesis)association.Clone();
                    }
                }
                else
                {
                    bool addOrNotRemove;
                    var index = Combinatorics.NextChangeOnGrey(i, out addOrNotRemove);

                    if (addOrNotRemove)
                    {
                        IsomerTrack candiateTrack = tracks[index];
                        if (association.IsConflict(candiateTrack))
                        {
                            hasConflicted = true;
                        }
                        else
                        {
                            association.AddIsomerTrack(tracks[index]);
                            yield return (AssociationHypothesis)association.Clone();
                        }
                    }
                    else
                    {
                        association.RemoveIsomerTrack(tracks[index]);
                        yield return (AssociationHypothesis)association.Clone();
                    }
                }
            }
        }
        internal static CrossSectionWorkflowResult CreateResultFromAssociationHypothesis(CrossSectionSearchParameters parameters, AssociationHypothesis optimalHypothesis, IImsTarget target, IEnumerable<VoltageGroup> allVoltageGroups, IEnumerable<ObservedPeak> allPeaks, string datasetPath, string analysisPath, string  sampleCollectionDate, double viperCompatibleMass = 0)
        {
            // Initialize the result struct.
            AssociationHypothesisInfo associationHypothesisInfo = new AssociationHypothesisInfo(optimalHypothesis.ProbabilityOfDataGivenHypothesis, optimalHypothesis.ProbabilityOfHypothesisGivenData);

            double averageVoltageGroupScore = VoltageGroupScoring.ComputeAverageVoltageGroupStabilityScore(allVoltageGroups);
            IEnumerable<PeakScores> allFeatureStatistics = allPeaks.Select(x => x.Statistics);
            PeakScores averageObservedPeakStatistics = FeatureScoreUtilities.AverageFeatureStatistics(allFeatureStatistics);

            IEnumerable<IsomerTrack> tracks = optimalHypothesis.Tracks.ToList();

            // Find the conformer with the closest m/z
            IsomerTrack trackWithBestMz = tracks.OrderBy(x => Math.Abs(Metrics.DaltonToPpm(x.AverageMzInDalton - target.MassWithAdduct, target.MassWithAdduct))).First();
            double bestMzInPpm = Metrics.DaltonToPpm(trackWithBestMz.AverageMzInDalton - target.MassWithAdduct, target.MassWithAdduct);
            IList<IdentifiedIsomerInfo> isomersInfo = tracks.Select(x => x.ExportIdentifiedIsomerInfo(viperCompatibleMass, allVoltageGroups.Count() - parameters.MaxOutliers, parameters.MinR2,  target, bestMzInPpm)).ToList();

            AnalysisStatus finalStatus = TrackToHypothesisConclusionLogic(isomersInfo.Select(info => info.AnalysisStatus));

            CrossSectionWorkflowResult informedResult = new CrossSectionWorkflowResult(
            datasetPath,
            target,
            finalStatus,
            associationHypothesisInfo,
            isomersInfo,
            averageObservedPeakStatistics,
            averageVoltageGroupScore,
            analysisPath,
            sampleCollectionDate
            );

            return informedResult;
        }
        /// <summary>
        /// The association hypothesis plot.
        /// </summary>
        /// <param name="hypothesis">
        /// The hypothesis.
        /// </param>
        /// <param name="datasetName">
        /// The dataset name.
        /// </param>
        /// <param name="targetDescriptor">
        /// The target descriptor.
        /// </param>
        /// <returns>
        /// The <see cref="PlotModel"/>.
        /// </returns>
        private PlotModel AssociationHypothesisPlot(AssociationHypothesis hypothesis, string datasetName, IImsTarget target, bool plotXAxisFromZero = false)
        {
            PlotModel model = new PlotModel();

            model.LegendBorderThickness = 0;
            model.LegendOrientation = LegendOrientation.Vertical;
            model.LegendPlacement = LegendPlacement.Inside;
            model.LegendPosition = LegendPosition.LeftTop;

            model.TitlePadding = 0;
            model.Title = "Optimal Association Hypothesis Plot";
            model.Subtitle = string.Format("Target: {0}, dataset: {1}", target.TargetDescriptor, datasetName) ;

            model.Axes.Add(
                new LinearAxis
                    {
                        Title = "IMS arrival time (milliseconds)",
                        MajorGridlineStyle = LineStyle.Solid,
                        Position = AxisPosition.Left,
                    });

            model.Axes.Add(
                new LinearAxis
                    {
                        Title = "P/(T*V) with P and T nondimensionalized (1/V)",
                        Position = AxisPosition.Bottom,
                        MajorGridlineStyle = LineStyle.Solid,
                    });

            // Add all the points
            IEnumerable<ObservedPeak> onTrackPeaks = hypothesis.OnTrackObservations;
            IEnumerable<ObservedPeak> offTrackPeaks = hypothesis.AllObservations.Where(x => !hypothesis.IsOnTrack(x));

            Func<ObservedPeak, ScatterPoint> fitPointMap = obj =>
            {
                ObservedPeak observation = obj;
                ContinuousXYPoint xyPoint = observation.ToContinuousXyPoint(false, 0);
                double size = MapToPointSize(observation);
                ScatterPoint sp = new ScatterPoint(xyPoint.X, xyPoint.Y, size);
                return sp;
            };

            var ontrackSeries= new ScatterSeries
            {
                Title = "[Peaks On Tracks]",
                MarkerFill = OxyColors.BlueViolet,
                MarkerType = MarkerType.Circle
            };

            var offtrackSeries= new ScatterSeries
            {
                Title = "[Peaks Off Tracks]",
                MarkerFill = OxyColors.Red,
                MarkerType = MarkerType.Circle
            };

            ontrackSeries.Points.AddRange(onTrackPeaks.Select(x => fitPointMap(x)));

            offtrackSeries.Points.AddRange(offTrackPeaks.Select(x => fitPointMap(x)));

            model.Series.Add(ontrackSeries);

            model.Series.Add(offtrackSeries);

            var allTracks = hypothesis.Tracks;

            // Add the tracks as linear axes
            int count = 1;
            foreach (var track in allTracks)
            {
                FitLine fitline = track.FitLine;
                LineAnnotation annotation = new LineAnnotation();
                annotation.Slope = fitline.Slope;
                annotation.Intercept = fitline.Intercept;
                annotation.TextPadding = 3;
                annotation.TextMargin = 2;
                annotation.Text = string.Format("Conformer {0} - mz: {1:F2}; ccs: {2:F2}, Isotopic Score: {3:F3}; Track Probability: {4:F2}; R2: {5:F5};",
                    count, track.AverageMzInDalton, track.GetMobilityInfoForTarget(target).CollisionCrossSectionArea, track.TrackStatistics.IsotopicScore, track.TrackProbability, track.FitLine.RSquared);
                count++;
                model.Annotations.Add(annotation);
                //Func<object, DataPoint> lineMap = obj =>
                //{
                //    ObservedPeak observation = (ObservedPeak)obj;
                //    ContinuousXYPoint xyPoint = observation.ToContinuousXyPoint();
                //    double x = xyPoint.X;
                //    double y = fitline.ModelPredictX2Y(x);
                //    DataPoint sp = new DataPoint(x, y);
                //    return sp;
                //};

                //model.Series.Add(new LineSeries()
                //{
                //    Mapping = lineMap,
                //    ItemsSource = track.ObservedPeaks,
                //    Color = OxyColors.Purple
                //});
            }

            return model;
        }
        /// <summary>
        /// The report track information.
        /// </summary>
        /// <param name="hypothesis">
        /// The hypothesis.
        /// </param>
        /// <param name="hasCompositionInfo">
        /// The has Composition Info.
        /// </param>
        /// <param name="verbose">
        /// The verbose.
        /// </param>
        private static void ReportTrackInformation(AssociationHypothesis hypothesis, bool hasCompositionInfo, bool verbose)
        {
            foreach (IsomerTrack track in hypothesis.Tracks)
            {
                foreach (var observation in track.ObservedPeaks)
                {
                    VoltageGroup voltageGroup = observation.VoltageGroup;
                    StandardImsPeak peak = observation.Peak;
                    double driftTimeInMs = peak.PeakApex.DriftTimeCenterInMs;

                    // Normalize the drift time to be displayed.
                    double normalizedDriftTimeInMs = IMSUtil.NormalizeDriftTime(driftTimeInMs, voltageGroup);

                    if (verbose)
                    {
                        Trace.WriteLine(
                            string.Format(
                                "    Target presence confirmed at {0:F2} ± {1:F2} V.",
                                voltageGroup.MeanVoltageInVolts,
                                Math.Sqrt(voltageGroup.VarianceVoltage)));

                        Trace.WriteLine(
                            string.Format(
                                "        Frame range: [{0}, {1}]",
                                voltageGroup.FirstFrameNumber,
                                voltageGroup.LastFrameNumber));

                        Trace.WriteLine(
                            string.Format(
                                "        Normalized Drift Time: {0:F4} ms (Scan# = {1})",
                                normalizedDriftTimeInMs,
                                peak.PeakApex.DriftTimeCenterInScanNumber));

                        Trace.WriteLine(string.Format("        VoltageGroupScoring: {0:F4}", voltageGroup.VoltageGroupScore));
                        Trace.WriteLine(string.Format("        IntensityScore: {0:F4}", observation.Statistics.IntensityScore));
                        if (hasCompositionInfo)
                        {
                            Trace.WriteLine(string.Format("        IsotopicScore: {0:F4}", observation.Statistics.IsotopicScore));
                        }

                        Trace.WriteLine(string.Format("        PeakShapeScore: {0:F4}", observation.Statistics.PeakShapeScore));
                        Trace.WriteLine(string.Empty);

                    }
                }
            }
        }