/// <summary>
        /// Generate the mass error scatter plots and mass error histogram plots, saving as PNG files
        /// </summary>
        /// <param name="psmResults"></param>
        /// <param name="fixedMzMLFileExists"></param>
        /// <param name="haveScanTimes"></param>
        public override bool GeneratePNGPlots(IReadOnlyCollection <IdentData> psmResults, bool fixedMzMLFileExists, bool haveScanTimes)
        {
            if (!ValidateOutputDirectories(BaseOutputFilePath))
            {
                return(false);
            }

            var metadataFileInfo = new MetadataFileInfo(BaseOutputFilePath, Options);

            var scatterPlotSuccess = ErrorScatterPlotsToPng(psmResults, metadataFileInfo.MassErrorPlotFile.FullName, fixedMzMLFileExists, haveScanTimes);

            if (scatterPlotSuccess)
            {
                Console.WriteLine("Generated {0}", metadataFileInfo.MassErrorPlotFile.FullName);
            }
            else
            {
                Console.WriteLine("Error generating {0}", metadataFileInfo.MassErrorPlotFile.FullName);
            }

            var histogramPlotSuccess = ErrorHistogramsToPng(psmResults, metadataFileInfo.HistogramPlotFile.FullName, fixedMzMLFileExists);

            if (histogramPlotSuccess)
            {
                Console.WriteLine("Generated {0}", metadataFileInfo.HistogramPlotFile.FullName);
            }
            else
            {
                Console.WriteLine("Error generating {0}", metadataFileInfo.HistogramPlotFile.FullName);
            }

            return(scatterPlotSuccess && histogramPlotSuccess);
        }
        /// <summary>
        /// Generate the mass error scatter plots and mass error histogram plots, saving as PNG files
        /// </summary>
        /// <param name="scanData"></param>
        /// <param name="fixedMzMLFileExists"></param>
        /// <param name="haveScanTimes"></param>
        public override bool GeneratePNGPlots(IReadOnlyCollection <IdentData> scanData, bool fixedMzMLFileExists, bool haveScanTimes)
        {
            var metadataFileInfo = new MetadataFileInfo(BaseOutputFilePath, Options);

            if (!ValidateOutputDirectories(BaseOutputFilePath))
            {
                return(false);
            }

            var histogramPlotDataExported = ExportHistogramPlotData(
                scanData, metadataFileInfo.BaseOutputFile, fixedMzMLFileExists,
                out var errorHistogramsExportFileName);

            if (!histogramPlotDataExported)
            {
                return(false);
            }

            var scatterPlotDataExported = ExportScatterPlotData(
                scanData, fixedMzMLFileExists, haveScanTimes, metadataFileInfo.BaseOutputFile,
                out var massErrorVsTimeExportFileName,
                out var massErrorVsMassExportFileName);

            if (!scatterPlotDataExported)
            {
                return(false);
            }

            metadataFileInfo.ErrorHistogramsExportFileName = errorHistogramsExportFileName;
            metadataFileInfo.MassErrorVsTimeExportFileName = massErrorVsTimeExportFileName;
            metadataFileInfo.MassErrorVsMassExportFileName = massErrorVsMassExportFileName;

            var success = GeneratePlotsWithPython(metadataFileInfo);

            return(success);
        }
        /// <summary>
        /// Call the Python script to create the plots
        /// </summary>
        /// <returns>True if success, otherwise false</returns>
        /// <remarks>Call ErrorHistogramsToPng and ErrorScatterPlotsToPng prior to calling this method</remarks>
        private bool GeneratePlotsWithPython(MetadataFileInfo metadataFileInfo)
        {
            if (!PythonInstalled)
            {
                NotifyPythonNotFound("Could not find the python executable");
                return(false);
            }

            if (metadataFileInfo.BaseOutputFile.Directory == null)
            {
                OnErrorEvent("Unable to determine the parent directory of the base output file: " + metadataFileInfo.BaseOutputFile.FullName);
                return(false);
            }

            if (!metadataFileInfo.BaseOutputFile.Directory.Exists)
            {
                metadataFileInfo.BaseOutputFile.Directory.Create();
            }

            var workDir = metadataFileInfo.BaseOutputFile.Directory.FullName;

            var exeDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

            if (exeDirectory == null)
            {
                OnErrorEvent("Unable to determine the path to the directory with the PPMErrorCharter executable");
                return(false);
            }

            var pythonScriptFile = new FileInfo(Path.Combine(exeDirectory, "PPMErrorCharter_Plotter.py"));

            if (!pythonScriptFile.Exists)
            {
                OnErrorEvent("Python plotting script not found: " + pythonScriptFile.FullName);
                return(false);
            }

            var baseOutputName = metadataFileInfo.BaseOutputFile.Name;

            var metadataFile = new FileInfo(Path.Combine(workDir, "MZRefinery_Plotting_Metadata.txt"));

            OnDebugEvent("Creating " + metadataFile.FullName);

            using (var writer = new StreamWriter(new FileStream(metadataFile.FullName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)))
            {
                var plotFilesDefined = 0;
                if (metadataFileInfo.HistogramPlotFile != null)
                {
                    writer.WriteLine("HistogramPlotFilePath=" + metadataFileInfo.HistogramPlotFile.FullName);
                    plotFilesDefined++;
                }

                if (metadataFileInfo.MassErrorPlotFile != null)
                {
                    writer.WriteLine("MassErrorPlotFilePath=" + metadataFileInfo.MassErrorPlotFile.FullName);
                    plotFilesDefined++;
                }

                if (plotFilesDefined < 2)
                {
                    writer.WriteLine("BaseOutputName=" + baseOutputName);
                    metadataFileInfo.HistogramPlotFile = new FileInfo(Path.Combine(metadataFileInfo.BaseOutputFile.FullName, baseOutputName + "_Histograms.png"));
                    metadataFileInfo.MassErrorPlotFile = new FileInfo(Path.Combine(metadataFileInfo.BaseOutputFile.FullName, baseOutputName + "_MassErrors.png"));
                }

                writer.WriteLine("HistogramData=" + metadataFileInfo.ErrorHistogramsExportFileName);
                writer.WriteLine("MassErrorVsTimeData=" + metadataFileInfo.MassErrorVsTimeExportFileName);
                writer.WriteLine("MassErrorVsMassData=" + metadataFileInfo.MassErrorVsMassExportFileName);
            }

            var args = PathUtils.PossiblyQuotePath(pythonScriptFile.FullName) + " " + PathUtils.PossiblyQuotePath(metadataFile.FullName);

            OnDebugEvent("{0} {1}", PythonPath, args);

            var progRunner = new ProgRunner
            {
                Arguments          = args,
                CreateNoWindow     = true,
                MonitoringInterval = 2000,
                Name              = "PythonPlotter",
                Program           = PythonPath,
                Repeat            = false,
                RepeatHoldOffTime = 0,
                WorkDir           = workDir
            };

            RegisterEvents(progRunner);

            const int MAX_RUNTIME_SECONDS           = 600;
            const int MONITOR_INTERVAL_MILLISECONDS = 1000;
            var       runtimeExceeded = false;

            try
            {
                // Start the program executing
                progRunner.StartAndMonitorProgram();

                var startTime = DateTime.UtcNow;

                // Loop until program is complete, or until MAX_RUNTIME_SECONDS seconds elapses
                while (progRunner.State != ProgRunner.States.NotMonitoring)
                {
                    ProgRunner.SleepMilliseconds(MONITOR_INTERVAL_MILLISECONDS);

                    if (DateTime.UtcNow.Subtract(startTime).TotalSeconds < MAX_RUNTIME_SECONDS)
                    {
                        continue;
                    }

                    OnErrorEvent("Plot creation with Python has taken more than {0:F0} minutes; aborting", MAX_RUNTIME_SECONDS / 60.0);
                    progRunner.StopMonitoringProgram(kill: true);

                    runtimeExceeded = true;
                    break;
                }
            }
            catch (Exception ex)
            {
                OnErrorEvent("Exception creating plots using Python", ex);
                return(false);
            }

            if (runtimeExceeded)
            {
                return(false);
            }

            // Examine the exit code
            if (progRunner.ExitCode == 0)
            {
                OnStatusEvent("Generated plots; see:\n  " + metadataFileInfo.HistogramPlotFile?.FullName + "\nand\n  " + metadataFileInfo.MassErrorPlotFile?.FullName);

                if (DeleteTempFiles)
                {
                    // Delete the temp export files

                    try
                    {
                        metadataFile.Delete();
                        File.Delete(Path.Combine(workDir, metadataFileInfo.ErrorHistogramsExportFileName));
                        File.Delete(Path.Combine(workDir, metadataFileInfo.MassErrorVsTimeExportFileName));
                        File.Delete(Path.Combine(workDir, metadataFileInfo.MassErrorVsMassExportFileName));
                    }
                    catch (Exception ex)
                    {
                        OnErrorEvent("Error deleting files: " + ex.Message);
                    }
                }
                else
                {
                    ConsoleMsgUtils.ShowDebug("{0}\n    {1}\n    {2}\n    {3}\n    {4}",
                                              "Not deleting the following temporary files since debug mode is enabled",
                                              metadataFile.FullName,
                                              Path.Combine(workDir, metadataFileInfo.ErrorHistogramsExportFileName),
                                              Path.Combine(workDir, metadataFileInfo.MassErrorVsTimeExportFileName),
                                              Path.Combine(workDir, metadataFileInfo.MassErrorVsMassExportFileName));
                }

                return(true);
            }

            OnErrorEvent("Python ExitCode = " + progRunner.ExitCode);
            return(false);
        }