public void TestPWiz(
            string fileOrDirectoryName,
            bool isDirectory,
            int expectedSpectraInFile,
            int expectedSpectraLoaded,
            int expectedTotalDataPoints,
            double expectedMedianTIC,
            double expectedMedianBPI)
        {
            try
            {
                if (!InstrumentDataUtilities.FindInstrumentData(fileOrDirectoryName, isDirectory, out var instrumentDataFileOrDirectory))
                {
                    Assert.Fail("File or directory not found");
                    return;
                }

                using (var reader = new MSDataFileReader(instrumentDataFileOrDirectory.FullName))
                {
                    Console.WriteLine("Chromatogram count: " + reader.ChromatogramCount);
                    Console.WriteLine();

                    var ticIntensities = new Dictionary <int, float>();

                    for (var chromatogramIndex = 0; chromatogramIndex < reader.ChromatogramCount; chromatogramIndex++)
                    {
                        // Note that even for a small .Wiff file (1.5 MB), obtaining the Chromatogram list will take some time (20 to 60 seconds)
                        // The chromatogram at index 0 should be the TIC
                        // The chromatogram at index >=1 will be each SRM

                        reader.GetChromatogram(chromatogramIndex, out var chromatogramID, out var timeArray, out var intensityArray);

                        // Determine the chromatogram type

                        if (chromatogramID == null)
                        {
                            chromatogramID = string.Empty;
                        }

                        var cvParams = reader.GetChromatogramCVParams(chromatogramIndex);

                        if (MSDataFileReader.TryGetCVParamDouble(cvParams, pwiz.CLI.cv.CVID.MS_TIC_chromatogram, out _))
                        {
                            // This chromatogram is the TIC
                            Console.WriteLine("TIC has id {0} and {1} data points", chromatogramID, timeArray.Length);

                            for (var i = 0; i < intensityArray.Length; i++)
                            {
                                ticIntensities.Add(i, intensityArray[i]);
                            }
                        }

                        if (MSDataFileReader.TryGetCVParamDouble(cvParams, pwiz.CLI.cv.CVID.MS_selected_reaction_monitoring_chromatogram, out _))
                        {
                            // This chromatogram is an SRM scan
                            Console.WriteLine("SRM scan has id {0} and {1} data points", chromatogramID, timeArray.Length);
                        }
                    }

                    Console.WriteLine("Spectrum count: " + reader.SpectrumCount);

                    var    spectraLoaded    = 0;
                    long   totalPointsRead  = 0;
                    double ticSumAllSpectra = 0;
                    double bpiSumAllSpectra = 0;

                    var spectrumIndex = 0;
                    while (spectrumIndex < reader.SpectrumCount)
                    {
                        var spectrum = reader.GetSpectrum(spectrumIndex, getBinaryData: true);
                        spectraLoaded += 1;

                        Console.WriteLine();
                        Console.WriteLine("ScanIndex {0}, NativeId {1}, Elution Time {2:F2} minutes, MS Level {3}",
                                          spectrumIndex, spectrum.NativeId, spectrum.RetentionTime, spectrum.Level);

                        // Use the following to get the MZs and Intensities
                        var mzList      = spectrum.Mzs.ToList();
                        var intensities = spectrum.Intensities.ToList();

                        if (mzList.Count > 0)
                        {
                            Console.WriteLine("  Data count: " + mzList.Count);

                            totalPointsRead += mzList.Count;

                            double tic = 0;
                            double bpi = 0;
                            for (var index = 0; index <= mzList.Count - 1; index++)
                            {
                                tic += intensities[index];
                                if (intensities[index] > bpi)
                                {
                                    bpi = intensities[index];
                                }
                            }

                            ticSumAllSpectra += tic;
                            bpiSumAllSpectra += bpi;

                            if (!ticIntensities.TryGetValue(spectrumIndex, out var ticFromChromatogram))
                            {
                                ticFromChromatogram = -1;
                            }

                            var spectrumInfo = reader.GetSpectrumObject(spectrumIndex);

                            if (MSDataFileReader.TryGetCVParamDouble(spectrumInfo.cvParams, pwiz.CLI.cv.CVID.MS_total_ion_current,
                                                                     out var ticFromSpectrumObject))
                            {
                                if (ticFromChromatogram < 0)
                                {
                                    // ticIntensities did not have an entry for spectrumIndex
                                    // This could be the case on a Waters Synapt instrument, where ticIntensities has one TIC per frame,
                                    // while pWiz.GetSpectrum() returns individual mass spectra, of which there could be hundreds of spectra per frame
                                    Console.WriteLine("  TIC from actual data is {0:E2} vs. {1:E2} from the spectrum object",
                                                      tic, ticFromSpectrumObject);
                                }
                                else
                                {
                                    // Note: the TIC value from the CvParams has been seen to be drastically off from the manually computed value
                                    Console.WriteLine(
                                        "  TIC from actual data is {0:E2} vs. {1:E2} from the chromatogram and {2:E2} from the spectrum object",
                                        tic, ticFromChromatogram, ticFromSpectrumObject);
                                }
                            }

                            if (MSDataFileReader.TryGetCVParamDouble(spectrumInfo.cvParams, pwiz.CLI.cv.CVID.MS_base_peak_intensity,
                                                                     out var bpiFromSpectrumObject))
                            {
                                if (MSDataFileReader.TryGetCVParamDouble(spectrumInfo.cvParams, pwiz.CLI.cv.CVID.MS_base_peak_m_z,
                                                                         out var bpiMzFromSpectrumObject))
                                {
                                    // Note: the BPI intensity from the CvParams has been seen to be drastically off from the manually computed value
                                    Console.WriteLine("  BPI from spectrum object is {0:E2} at {1:F3} m/z",
                                                      bpiFromSpectrumObject, bpiMzFromSpectrumObject);
                                }
                            }
                        }

                        if (spectrumIndex < 25)
                        {
                            spectrumIndex += 1;
                        }
                        else if (spectrumIndex < 1250)
                        {
                            spectrumIndex += 50;
                        }
                        else
                        {
                            spectrumIndex += 500;
                        }
                    }

                    if (spectraLoaded > 0)
                    {
                        var medianTIC = ticSumAllSpectra / spectraLoaded;
                        var medianBPI = bpiSumAllSpectra / spectraLoaded;

                        Console.WriteLine();
                        Console.WriteLine("Read {0:N0} data points from {1} spectra in {2}",
                                          totalPointsRead, spectraLoaded, Path.GetFileName(fileOrDirectoryName));

                        Console.WriteLine("Median TIC: {0:E4}", medianTIC);
                        Console.WriteLine("Median BPI: {0:E4}", medianBPI);

                        Assert.AreEqual(expectedSpectraInFile, reader.SpectrumCount, "Total spectrum count mismatch");

                        Assert.AreEqual(expectedSpectraLoaded, spectraLoaded, "Spectra loaded mismatch");

                        Assert.AreEqual(expectedTotalDataPoints, totalPointsRead, "Spectra loaded mismatch");
                        var ticComparisonTolerance = expectedMedianTIC * 0.01;
                        var bpiComparisonTolerance = expectedMedianBPI * 0.01;

                        Assert.AreEqual(expectedMedianTIC, medianTIC, ticComparisonTolerance, "Median TIC mismatch");
                        Assert.AreEqual(expectedMedianBPI, medianBPI, bpiComparisonTolerance, "Median BPI mismatch");
                    }
                }
            }
            catch (Exception ex)
            {
                ConsoleMsgUtils.ShowError("Error using ProteoWizard reader", ex);
            }
        }
        public bool StoreMSSpectraInfo(
            bool ticStored,
            ref double runtimeMinutes,
            bool skipExistingScans,
            bool skipScansWithNoIons,
            int maxScansToTrackInDetail,
            int maxScansForTicAndBpi,
            out int scanCountSuccess,
            out int scanCountError)
        {
            scanCountSuccess = 0;
            scanCountError   = 0;

            try
            {
                Console.WriteLine();
                OnStatusEvent("Obtaining scan times and MSLevels (this could take several minutes)");
                mLastScanLoadingDebugProgressTime  = DateTime.UtcNow;
                mLastScanLoadingStatusProgressTime = DateTime.UtcNow;
                mReportedTotalSpectraToExamine     = false;

                mCancellationToken = new CancellationTokenSource();

                var scanTimes = new double[0];
                var msLevels  = new byte[0];

                mGetScanTimesStartTime          = DateTime.UtcNow;
                mGetScanTimesMaxWaitTimeSeconds = 90;
                mGetScanTimesAutoAborted        = false;

                var minScanIndexWithoutScanTimes = int.MaxValue;

                try
                {
                    mPWiz.GetScanTimesAndMsLevels(mCancellationToken.Token, out scanTimes, out msLevels, MonitorScanTimeLoadingProgress);
                }
                catch (OperationCanceledException)
                {
                    // mCancellationToken.Cancel was called in MonitorScanTimeLoadingProgress

                    // Determine the scan index where GetScanTimesAndMsLevels exited the for loop
                    for (var scanIndex = 0; scanIndex <= scanTimes.Length - 1; scanIndex++)
                    {
                        if (msLevels[scanIndex] > 0)
                        {
                            continue;
                        }

                        minScanIndexWithoutScanTimes = scanIndex;
                        break;
                    }

                    if (!mGetScanTimesAutoAborted && minScanIndexWithoutScanTimes < int.MaxValue)
                    {
                        // Manually aborted; shrink the arrays to reflect the amount of data that was actually loaded
                        Array.Resize(ref scanTimes, minScanIndexWithoutScanTimes);
                        Array.Resize(ref msLevels, minScanIndexWithoutScanTimes);
                    }
                }

                var spectrumCount = scanTimes.Length;

                // The scan times returned by .GetScanTimesAndMsLevels() are the acquisition time in seconds from the start of the analysis
                // Convert these to minutes
                for (var scanIndex = 0; scanIndex <= spectrumCount - 1; scanIndex++)
                {
                    if (scanIndex >= minScanIndexWithoutScanTimes)
                    {
                        break;
                    }

                    var scanTimeMinutes = scanTimes[scanIndex] / 60.0;
                    scanTimes[scanIndex] = scanTimeMinutes;
                }

                Console.WriteLine();
                OnStatusEvent("Reading spectra");
                var lastDebugProgressTime  = DateTime.UtcNow;
                var lastStatusProgressTime = DateTime.UtcNow;
                var skippedEmptyScans      = 0;

                var scanNumber           = 0;
                var scansStored          = 0;
                var ticAndBpiScansStored = 0;

                var scanCountHMS  = 0;
                var scanCountHMSn = 0;
                var scanCountMS   = 0;
                var scanCountMSn  = 0;

                for (var scanIndex = 0; scanIndex <= spectrumCount - 1; scanIndex++)
                {
                    try
                    {
                        var computeTIC = true;
                        var computeBPI = true;

                        // Obtain the raw mass spectrum
                        var msDataSpectrum = mPWiz.GetSpectrum(scanIndex);

                        scanNumber = scanIndex + 1;

                        if (scanIndex >= minScanIndexWithoutScanTimes)
                        {
                            // msDataSpectrum.RetentionTime is already in minutes
                            scanTimes[scanIndex] = msDataSpectrum.RetentionTime ?? 0;

                            if (msDataSpectrum.Level >= byte.MinValue && msDataSpectrum.Level <= byte.MaxValue)
                            {
                                msLevels[scanIndex] = (byte)msDataSpectrum.Level;
                            }
                        }

                        var scanStatsEntry = new ScanStatsEntry
                        {
                            ScanNumber = scanNumber,
                            ScanType   = msDataSpectrum.Level
                        };

                        if (msLevels[scanIndex] > 1)
                        {
                            if (HighResMS2)
                            {
                                scanStatsEntry.ScanTypeName = "HMSn";
                                scanCountHMSn++;
                            }
                            else
                            {
                                scanStatsEntry.ScanTypeName = "MSn";
                                scanCountMSn++;
                            }
                        }
                        else
                        {
                            if (HighResMS1)
                            {
                                scanStatsEntry.ScanTypeName = "HMS";
                                scanCountHMS++;
                            }
                            else
                            {
                                scanStatsEntry.ScanTypeName = "MS";
                                scanCountMS++;
                            }
                        }

                        var driftTimeMsec = msDataSpectrum.DriftTimeMsec ?? 0;

                        scanStatsEntry.ScanFilterText = driftTimeMsec > 0 ? "IMS" : string.Empty;
                        scanStatsEntry.ExtendedScanInfo.ScanFilterText = scanStatsEntry.ScanFilterText;

                        scanStatsEntry.DriftTimeMsec = driftTimeMsec.ToString("0.0###");

                        scanStatsEntry.ElutionTime = scanTimes[scanIndex].ToString("0.0###");

                        // Bump up runtimeMinutes if necessary
                        if (scanTimes[scanIndex] > runtimeMinutes)
                        {
                            runtimeMinutes = scanTimes[scanIndex];
                        }

                        var spectrum = mPWiz.GetSpectrumObject(scanIndex);

                        if (MSDataFileReader.TryGetCVParamDouble(spectrum.cvParams, pwiz.CLI.cv.CVID.MS_total_ion_current, out var tic))
                        {
                            // For timsTOF data, this is the TIC of the entire frame
                            scanStatsEntry.TotalIonIntensity = StringUtilities.ValueToString(tic, 5);
                            computeTIC = false;
                        }

                        if (MSDataFileReader.TryGetCVParamDouble(spectrum.cvParams, pwiz.CLI.cv.CVID.MS_base_peak_intensity, out var bpi))
                        {
                            // For timsTOF data, this is the BPI of the entire frame
                            // Additionally, for timsTOF data, MS_base_peak_m_z is not defined
                            scanStatsEntry.BasePeakIntensity = StringUtilities.ValueToString(bpi, 5);

                            if (MSDataFileReader.TryGetCVParamDouble(spectrum.scanList.scans[0].cvParams, pwiz.CLI.cv.CVID.MS_base_peak_m_z, out var basePeakMzFromCvParams))
                            {
                                scanStatsEntry.BasePeakMZ = StringUtilities.ValueToString(basePeakMzFromCvParams, 5);
                                computeBPI = false;
                            }
                        }

                        if (string.IsNullOrEmpty(scanStatsEntry.ScanFilterText))
                        {
                            if (spectrum?.scanList?.scans.Count > 0)
                            {
                                // Bruker timsTOF datasets will have CVParam "inverse reduced ion mobility" for IMS spectra; check for this
                                foreach (var scanItem in spectrum.scanList.scans)
                                {
                                    if (MSDataFileReader.TryGetCVParam(scanItem.cvParams, pwiz.CLI.cv.CVID.MS_inverse_reduced_ion_mobility, out _))
                                    {
                                        scanStatsEntry.ScanFilterText = "IMS";
                                        break;
                                    }
                                }
                            }
                        }

                        // Base peak signal to noise ratio
                        scanStatsEntry.BasePeakSignalToNoiseRatio = "0";

                        scanStatsEntry.IonCount    = msDataSpectrum.Mzs.Length;
                        scanStatsEntry.IonCountRaw = scanStatsEntry.IonCount;

                        if ((computeBPI || computeTIC) && scanStatsEntry.IonCount > 0)
                        {
                            // Step through the raw data to compute the BPI and TIC

                            var mzList      = msDataSpectrum.Mzs;
                            var intensities = msDataSpectrum.Intensities;

                            tic = 0;
                            bpi = 0;
                            double basePeakMZ = 0;

                            for (var index = 0; index <= mzList.Length - 1; index++)
                            {
                                tic += intensities[index];
                                if (intensities[index] > bpi)
                                {
                                    bpi        = intensities[index];
                                    basePeakMZ = mzList[index];
                                }
                            }

                            scanStatsEntry.TotalIonIntensity = StringUtilities.ValueToString(tic, 5);
                            scanStatsEntry.BasePeakIntensity = StringUtilities.ValueToString(bpi, 5);
                            scanStatsEntry.BasePeakMZ        = StringUtilities.ValueToString(basePeakMZ, 5);
                        }

                        var addScan = !skipExistingScans || skipExistingScans && !mDatasetStatsSummarizer.HasScanNumber(scanNumber);

                        if (addScan)
                        {
                            if (skipScansWithNoIons && scanStatsEntry.IonCount == 0)
                            {
                                skippedEmptyScans++;

                                if (skippedEmptyScans < 25 ||
                                    skippedEmptyScans < 100 && skippedEmptyScans % 10 == 0 ||
                                    skippedEmptyScans < 1000 && skippedEmptyScans % 100 == 0 ||
                                    skippedEmptyScans < 10000 && skippedEmptyScans % 1000 == 0 ||
                                    skippedEmptyScans < 100000 && skippedEmptyScans % 10000 == 0 ||
                                    skippedEmptyScans % 100000 == 0)
                                {
                                    ConsoleMsgUtils.ShowDebug("Skipping scan {0:N0} since no ions; {1:N0} total skipped scans", scanNumber, skippedEmptyScans);
                                }
                            }
                            else
                            {
                                if (maxScansToTrackInDetail < 0 || scansStored < maxScansToTrackInDetail)
                                {
                                    mDatasetStatsSummarizer.AddDatasetScan(scanStatsEntry);
                                    scansStored += 1;
                                }
                            }
                        }

                        if (mSaveTICAndBPI && !ticStored && (maxScansForTicAndBpi < 0 || ticAndBpiScansStored < maxScansForTicAndBpi))
                        {
                            mTICAndBPIPlot.AddData(scanStatsEntry.ScanNumber, msLevels[scanIndex], (float)scanTimes[scanIndex], bpi, tic);
                            ticAndBpiScansStored += 1;
                        }

                        if (mSaveLCMS2DPlots && addScan)
                        {
                            mLCMS2DPlot.AddScan(scanStatsEntry.ScanNumber, msLevels[scanIndex], (float)scanTimes[scanIndex], msDataSpectrum.Mzs.Length, msDataSpectrum.Mzs, msDataSpectrum.Intensities);
                        }

                        if (mCheckCentroidingStatus)
                        {
                            mDatasetStatsSummarizer.ClassifySpectrum(msDataSpectrum.Mzs, msLevels[scanIndex], "Scan " + scanStatsEntry.ScanNumber);
                        }

                        scanCountSuccess += 1;
                    }
                    catch (Exception ex)
                    {
                        OnErrorEvent("Error loading header info for scan " + scanNumber, ex);
                        scanCountError += 1;
                    }

                    if (DateTime.UtcNow.Subtract(lastStatusProgressTime).TotalMinutes > 5)
                    {
                        OnStatusEvent(string.Format("Reading spectra, loaded {0:N0} / {1:N0} spectra; " +
                                                    "{2:N0} HMS spectra; {3:N0} HMSn spectra; " +
                                                    "{4:N0} MS spectra; {5:N0} MSn spectra; " +
                                                    "max elution time is {6:F2} minutes",
                                                    scanNumber, spectrumCount,
                                                    scanCountHMS, scanCountHMSn,
                                                    scanCountMS, scanCountMSn,
                                                    runtimeMinutes));

                        lastStatusProgressTime = DateTime.UtcNow;
                        lastDebugProgressTime  = DateTime.UtcNow;
                        continue;
                    }

                    if (DateTime.UtcNow.Subtract(lastDebugProgressTime).TotalSeconds < 15)
                    {
                        continue;
                    }

                    lastDebugProgressTime = DateTime.UtcNow;
                    var percentComplete = scanNumber / (float)spectrumCount * 100;

                    var percentCompleteOverall = clsMSFileInfoProcessorBaseClass.ComputeIncrementalProgress(
                        PROGRESS_SCAN_TIMES_LOADED,
                        clsMSFileInfoProcessorBaseClass.PROGRESS_SPECTRA_LOADED,
                        percentComplete);

                    OnProgressUpdate(string.Format("Spectra processed: {0:N0}", scanNumber), percentCompleteOverall);
                }

                mDatasetStatsSummarizer.StoreScanTypeTotals(scanCountHMS, scanCountHMSn,
                                                            scanCountMS, scanCountMSn,
                                                            runtimeMinutes);

                var scanCountTotal = scanCountSuccess + scanCountError;
                if (scanCountTotal == 0)
                {
                    return(false);
                }

                // Return True if at least 50% of the spectra were successfully read
                return(scanCountSuccess >= scanCountTotal / 2.0);
            }
            catch (AccessViolationException)
            {
                // Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
                if (!mWarnedAccessViolationException)
                {
                    OnWarningEvent("Error reading instrument data with ProteoWizard: Attempted to read or write protected memory. " +
                                   "The instrument data file is likely corrupt.");
                    mWarnedAccessViolationException = true;
                }
                mDatasetStatsSummarizer.CreateEmptyScanStatsFiles = false;
                return(false);
            }
            catch (Exception ex)
            {
                OnErrorEvent("Error reading instrument data with ProteoWizard: " + ex.Message, ex);
                return(false);
            }
        }