diff --git a/pwiz_tools/Skyline/Controls/Graphs/GraphFullScan.cs b/pwiz_tools/Skyline/Controls/Graphs/GraphFullScan.cs index 711eb6b21e1..97618238a1a 100644 --- a/pwiz_tools/Skyline/Controls/Graphs/GraphFullScan.cs +++ b/pwiz_tools/Skyline/Controls/Graphs/GraphFullScan.cs @@ -2169,6 +2169,29 @@ private void GetMaxMzIntensity(out double maxMz, out double maxIntensity) double intensity = SumIntensities(fullScans, minMz, indices, minIonMobilityVal, maxIonMobilityVal); maxIntensity = Math.Max(maxIntensity, intensity); } + + if (maxMz <= 0) + { + // None of the scans have any peaks in them because they measured no ions. Show the + // m/z range that the instrument was scanning so that the axes still get drawn. + maxMz = GetMaxScanWindow(fullScans); + } + } + + /// + /// The highest scan window upper limit declared by any of the spectra, or zero if none of + /// them says what m/z range it was measuring. + /// + private static double GetMaxScanWindow(IEnumerable spectra) + { + double maxScanWindow = 0; + foreach (var spectrum in spectra) + { + var upperLimit = spectrum.Metadata?.ScanWindowUpperLimit; + if (upperLimit.HasValue) + maxScanWindow = Math.Max(maxScanWindow, upperLimit.Value); + } + return maxScanWindow; } private void GetIonMobilityRange(out double minIonMobility, out double maxIonMobility) @@ -2271,21 +2294,28 @@ private void ZoomXAxis() private void ApplyXZoomToPane(GraphPane pane) { var xScale = pane.XAxis.Scale; - xScale.MinAuto = xScale.MaxAuto = false; if (magnifyBtn.Checked) { double mz = _msDataFileScanHelper.Source == ChromSource.ms1 ? _msDataFileScanHelper.ScanProvider.Transitions[_msDataFileScanHelper.TransitionIndex].PrecursorMz : _msDataFileScanHelper.ScanProvider.Transitions[_msDataFileScanHelper.TransitionIndex].ProductMz; + xScale.MinAuto = xScale.MaxAuto = false; xScale.Min = mz - 1.5; xScale.Max = mz + 3.5; } - else if (_requestedRange != null) + else if (_requestedRange != null && _requestedRange.Max > _requestedRange.Min) { + xScale.MinAuto = xScale.MaxAuto = false; xScale.Min = _requestedRange.Min; xScale.Max = _requestedRange.Max; } + else + { + // There is no m/z range to show. Leave the axis auto-scaled, since collapsing it to + // a single value makes ZedGraph skip drawing the axis entirely. + xScale.MinAuto = xScale.MaxAuto = true; + } } public void SetMzScale(MzRange range) @@ -2458,6 +2488,13 @@ public bool ShowPropertiesSheet public bool HasChromatogramData => false; + /// + /// The maximum for an intensity axis, which is never zero. ZedGraph draws nothing at all -- + /// not even the axes -- when the minimum of any of a pane's axis scales equals its maximum, + /// so a scan which measured no ions would otherwise produce a completely blank graph. + /// + private double IntensityAxisMax => _maxIntensity > 0 ? _maxIntensity * 1.1 : 1; + private void ZoomYAxis() { if (_msDataFileScanHelper.ScanProvider == null || _msDataFileScanHelper.ScanProvider.Transitions.Length == 0) @@ -2479,7 +2516,7 @@ private void ZoomYAxis() if (isSpectrum) { yScale.Min = 0; - yScale.Max = _maxIntensity * 1.1; + yScale.Max = IntensityAxisMax; if (magnifyBtn.Checked) { yScale.MaxAuto = true; @@ -2540,11 +2577,11 @@ private void ZoomStickYAxis() maxY = pt.Y; } } - yScale.Max = maxY > 0 ? maxY * 1.1 : _maxIntensity * 1.1; + yScale.Max = maxY > 0 ? maxY * 1.1 : IntensityAxisMax; } else { - yScale.Max = _maxIntensity * 1.1; + yScale.Max = IntensityAxisMax; } _stickSpectrumPane.AxisChange(); } @@ -2566,7 +2603,7 @@ private void ResetStickYAxis() yScale.MinAuto = false; _heatMapPane.LockYAxisMinAtZero = true; yScale.Min = 0; - yScale.Max = _maxIntensity * 1.1; + yScale.Max = IntensityAxisMax; // Magnify on → auto-fit Y to data in the zoomed X range during next paint. yScale.MaxAuto = magnifyBtn.Checked; } diff --git a/pwiz_tools/Skyline/Model/Results/SpectraChromDataProvider.cs b/pwiz_tools/Skyline/Model/Results/SpectraChromDataProvider.cs index fbe1eaa4f0d..85d67bc99f3 100644 --- a/pwiz_tools/Skyline/Model/Results/SpectraChromDataProvider.cs +++ b/pwiz_tools/Skyline/Model/Results/SpectraChromDataProvider.cs @@ -839,6 +839,20 @@ public static bool HasSpectrumData(MsDataFileImpl dataFile) return dataFile.SpectrumCount > 0; } + /// + /// Returns true if a spectrum has no m/z values and should be ignored. + /// A spectrum which measured no ions is a valid measurement of zero intensity, but only if + /// we know which m/z values it was measuring. That range comes from the scan window limits, + /// so a spectrum which declares them is not considered empty. + /// + public static bool IsEmptySpectrum(MsDataSpectrum spectrum) + { + if (spectrum.Mzs != null && spectrum.Mzs.Length != 0) + return false; + return spectrum.Metadata?.ScanWindowLowerLimit == null && + spectrum.Metadata?.ScanWindowUpperLimit == null; + } + private class Spectra : IDisposable { private bool _runningAsync; @@ -1218,7 +1232,7 @@ private SpectrumInfo ReadSpectrum(ref int i) // Assertion for testing ID to spectrum index support // int iFromId = dataFile.GetSpectrumIndex(dataSpectrum.Id); // Assume.IsTrue(i == iFromId); - if (nextSpectrum.Mzs.Length == 0) + if (IsEmptySpectrum(nextSpectrum)) continue; double? rt = nextSpectrum.RetentionTime; @@ -1574,7 +1588,7 @@ public MsDataSpectrum[] Lookahead(MsDataSpectrum dataSpectrum, out double? rt) while (_lookAheadIndex++ < _lenSpectra) { _rt = dataSpectrum.RetentionTime; - if (_rt.HasValue && dataSpectrum.Mzs.Length != 0) + if (_rt.HasValue && !IsEmptySpectrum(dataSpectrum)) { spectrumList.Add(dataSpectrum); if (!rtReported.HasValue) @@ -1630,7 +1644,7 @@ public MsDataSpectrum[] Lookahead(MsDataSpectrum dataSpectrum, out double? rt) while (_lookAheadIndex++ < _lenSpectra) { _rt = dataSpectrum.RetentionTime; - if (_rt.HasValue && dataSpectrum.Mzs.Length != 0) + if (_rt.HasValue && !IsEmptySpectrum(dataSpectrum)) { spectrumList.Add(dataSpectrum); rtTotal += dataSpectrum.RetentionTime.Value; @@ -1649,7 +1663,7 @@ public MsDataSpectrum[] Lookahead(MsDataSpectrum dataSpectrum, out double? rt) { // No need to search forward, this isn't IMS or Agilent ramped-CE data rtReported = dataSpectrum.RetentionTime; - if (rtReported.HasValue && dataSpectrum.Mzs.Length != 0) + if (rtReported.HasValue && !IsEmptySpectrum(dataSpectrum)) { spectrumList.Add(dataSpectrum); } diff --git a/pwiz_tools/Skyline/Model/Results/SpectrumFilterPair.cs b/pwiz_tools/Skyline/Model/Results/SpectrumFilterPair.cs index dcb573390ce..1c2ffc1a961 100644 --- a/pwiz_tools/Skyline/Model/Results/SpectrumFilterPair.cs +++ b/pwiz_tools/Skyline/Model/Results/SpectrumFilterPair.cs @@ -344,7 +344,11 @@ private ExtractedSpectrum FilterSpectrumList(MsDataSpectrum[] spectra, iPeak = ~iPeak; } if (iPeak >= mzArray.Length) + { + // The extracted intensities for the remaining targets will be zero so we can stop extracting + // Consider: we probably still need to keep checking "hasScanWindowCoverage" break; // No further overlap + } } // TODO:(bspratt) for full frame diaPASEF MS2, try not sorting - make IM the initial binary search range (and deal with mz that rolls over) diff --git a/pwiz_tools/Skyline/TestFunctional/SettingsChangeReimportTest.cs b/pwiz_tools/Skyline/TestFunctional/SettingsChangeReimportTest.cs index f2a235bb598..07d1388d65c 100644 --- a/pwiz_tools/Skyline/TestFunctional/SettingsChangeReimportTest.cs +++ b/pwiz_tools/Skyline/TestFunctional/SettingsChangeReimportTest.cs @@ -57,7 +57,7 @@ protected override void DoTest() Assert.AreEqual(1, transitionGroup.Results.Count); var transitionGroupChromInfo = transitionGroup.Results[0].First(); // Verify the peak area is what we expect - Assert.AreEqual(1.460189E+08f, transitionGroupChromInfo.Area.Value); + Assert.AreEqual(147639168f, transitionGroupChromInfo.Area.Value); // Reimport the file with "UseSelectiveExtraction" set to "true" RunUI(() => SkylineWindow.ModifyDocument("Change selective extraction", @@ -87,7 +87,7 @@ protected override void DoTest() Assert.AreEqual(1, transitionGroup.Results.Count); transitionGroupChromInfo = transitionGroup.Results[0].First(); // Verify that the peak area is a smaller number because the chromatogram extraction was more selective - Assert.AreEqual(119880880f, transitionGroupChromInfo.Area.Value); + Assert.AreEqual(121269248f, transitionGroupChromInfo.Area.Value); } } } diff --git a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj index ec32a4ff956..cfb6afae9af 100644 --- a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj +++ b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj @@ -560,6 +560,7 @@ + diff --git a/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.cs b/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.cs new file mode 100644 index 00000000000..28f394486ec --- /dev/null +++ b/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.cs @@ -0,0 +1,100 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2026 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.CommonMsData; +using pwiz.Skyline.Controls.Graphs; +using pwiz.Skyline.Model; +using pwiz.Skyline.Model.Results; +using pwiz.SkylineTestUtil; +using System.Linq; +using ZedGraph; + +namespace pwiz.SkylineTestFunctional +{ + /// + /// Verifies that spectra with no m/z's and intensities are still included in the extracted chromatogram. + /// + [TestClass] + public class ZeroLengthSpectraTest : AbstractFunctionalTestEx + { + [TestMethod] + public void TestZeroLengthSpectra() + { + TestFilesZip = @"TestFunctional\ZeroLengthSpectraTest.zip"; + RunFunctionalTest(); + } + + protected override void DoTest() + { + RunUI(()=>SkylineWindow.OpenFile(TestFilesDir.GetTestPath("ZeroLengthSpectraTest.sky"))); + var msDataFilePath = new MsDataFilePath(TestFilesDir.GetTestPath("S_1.mzML")); + ImportResultsFile(msDataFilePath.FilePath); + using var dataFile = msDataFilePath.OpenMsDataFile(new OpenMsDataFileParams()); + var ms1Spectra = Enumerable.Range(0, dataFile.SpectrumCount).Select(dataFile.GetSpectrum) + .Where(spectrum => spectrum.Level == 1).ToList(); + var emptyMs1Spectra = ms1Spectra.Where(spectrum => spectrum.Mzs.Length == 0).ToList(); + Assert.AreNotEqual(0, emptyMs1Spectra.Count); + Assert.AreNotEqual(emptyMs1Spectra.Count, ms1Spectra.Count); + var document = SkylineWindow.Document; + var peptideDocNode = document.Molecules.First(); + Assert.IsTrue(document.MeasuredResults.TryLoadChromatogram(0, peptideDocNode, peptideDocNode.TransitionGroups.First(), (float) document.Settings.TransitionSettings.Instrument.MzMatchTolerance, out var chromatogramGroupInfos)); + Assert.AreEqual(1, chromatogramGroupInfos.Length); + var chromatogramInfo = chromatogramGroupInfos[0].GetRawTransitionInfo(0); + Assert.IsNotNull(chromatogramInfo); + Assert.AreEqual(ms1Spectra.Count, chromatogramInfo.Times.Count); + RunUI(() => + { + SkylineWindow.SelectedPath = + SkylineWindow.Document.GetPathTo((int)SrmDocument.Level.TransitionGroups, 0); + SkylineWindow.SetTransformChrom(TransformChrom.raw); + }); + + // Click on a point in the chromatogram which came from one of the empty spectra + ClickChromatogram(22.3, 1e4); + var graphFullScan = WaitForOpenForm(); + RunUI(() => + { + graphFullScan.SetZoom(false); + AssertAxesNotDegenerate(graphFullScan.ZedGraphControl.GraphPane); + + // The empty spectrum still says which m/z values it was measuring, and the x-axis + // is supposed to show that range instead of collapsing to nothing + var scanWindowUpperLimit = emptyMs1Spectra.Max(spectrum => spectrum.Metadata.ScanWindowUpperLimit); + Assert.IsNotNull(scanWindowUpperLimit); + AssertEx.IsGreaterThanOrEqual(graphFullScan.ZedGraphControl.GraphPane.XAxis.Scale.Max, + scanWindowUpperLimit.Value); + }); + } + + /// + /// Asserts that the graph will actually be drawn. ZedGraph skips the axes, the grid and the + /// curves when the minimum of any of a pane's axis scales is not less than its maximum, + /// which leaves nothing but the title, so a degenerate range means a blank graph. + /// + private static void AssertAxesNotDegenerate(GraphPane graphPane) + { + AssertEx.IsGreaterThan(graphPane.XAxis.Scale.Max, graphPane.XAxis.Scale.Min); + AssertEx.IsGreaterThan(graphPane.X2Axis.Scale.Max, graphPane.X2Axis.Scale.Min); + foreach (var yAxis in graphPane.YAxisList) + AssertEx.IsGreaterThan(yAxis.Scale.Max, yAxis.Scale.Min); + foreach (var y2Axis in graphPane.Y2AxisList) + AssertEx.IsGreaterThan(y2Axis.Scale.Max, y2Axis.Scale.Min); + } + } +} diff --git a/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.zip b/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.zip new file mode 100644 index 00000000000..739aa70a70f Binary files /dev/null and b/pwiz_tools/Skyline/TestFunctional/ZeroLengthSpectraTest.zip differ diff --git a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestQeDataDiaNN.json b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestQeDataDiaNN.json index 617129ceaa7..6f7debcfbc2 100644 --- a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestQeDataDiaNN.json +++ b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestQeDataDiaNN.json @@ -18,24 +18,24 @@ 3.01 ], [ - 1.77, - 3.02 + 1.76, + 2.99 ], [ - 2.05, + 2.04, 3.16 ], [ - 2.06, - 3.66 + 2.08, + 3.68 ], [ - 1.94, + 1.95, 3.17 ], [ - 1.63, - 3.36 + 1.64, + 3.38 ] ], "DiffPeptideCounts": [ diff --git a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData.json b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData.json index 23aade8281a..bbc6ccdb128 100644 --- a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData.json +++ b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData.json @@ -11,30 +11,30 @@ "MassErrorStats": [ [ 3.03, - 4.37 + 4.38 ], [ - 2.78, - 4.1 + 2.79, + 4.12 ], [ - 3.88, + 3.89, 4.25 ], [ 5.87, - 3.58 + 3.55 ], [ 4.69, 4.17 ], [ - -0.07, - 3.42 + -0.08, + 3.43 ], [ - 1.01, + 1.02, 3.63 ] ], @@ -47,13 +47,13 @@ "UnpolishedProteins": 9, "PolishedProteins": 11, "ScoringModelCoefficients": [ - 0.2332, - -0.667, - 3.4044, - -0.0138, - -0.4653, - 0.9273, - 0.112, - -0.0497 + 0.2442, + -0.6843, + 3.403, + 0.0632, + -0.4762, + 0.9162, + 0.0914, + -0.0489 ] } \ No newline at end of file diff --git a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData_full.json b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData_full.json index 7c3efe33043..6139d88ddd2 100644 --- a/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData_full.json +++ b/pwiz_tools/Skyline/TestPerf/DiaSwathTutorialTest.data/TestTtofData_full.json @@ -11,49 +11,49 @@ "MassErrorStats": [ [ 2.78, - 4.56 + 4.57 ], [ - 2.43, - 4.29 + 2.44, + 4.3 ], [ 3.92, - 4.19 + 4.18 ], [ - 5.16, + 5.17, 4.08 ], [ - 4.48, - 4.2 + 4.49, + 4.19 ], [ - -0.35, - 3.93 + -0.36, + 3.94 ], [ 1.03, - 4.06 + 4.07 ] ], "DiffPeptideCounts": [ - 12808, - 7963, + 12811, + 7965, 2692, - 2142 + 2143 ], - "UnpolishedProteins": 2207, + "UnpolishedProteins": 2214, "PolishedProteins": 2394, "ScoringModelCoefficients": [ - -0.0842, - -0.7447, - 4.2691, - -0.2084, - -0.2644, - 0.7594, - 0.3124, - -0.0659 + -0.0923, + -0.745, + 4.2841, + -0.2199, + -0.2654, + 0.7632, + 0.3208, + -0.0656 ] } \ No newline at end of file diff --git a/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorial.json b/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorial.json index ae9eefe99dc..684af7a03bf 100644 --- a/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorial.json +++ b/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorial.json @@ -4,16 +4,16 @@ "IrtIntercept": -67.652, "MassErrorStats": [ [ - 3.3368965539182054, - 3.7476706541186697 + 3.3541379343332913, + 3.7538169894050677 ], [ - 3.1641379264408145, - 3.3541366574610119 + 3.1606896514008787, + 3.3617599596622205 ], [ - 3.5096551813955963, - 4.1081951397141436 + 3.5475862172657044, + 4.1113677622617537 ] ], "FinalTargetCounts": [ @@ -23,13 +23,13 @@ 1673 ], "ScoringModelCoefficients": [ - -0.1511, - -0.5825, - 5.5994, - -0.5757, - -0.45, - 0.7592, - 0.4174, - -0.0851 + -0.1537, + -0.6123, + 5.803, + -0.7115, + -0.4474, + 0.7195, + 0.41, + -0.0804 ] } \ No newline at end of file diff --git a/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorialFullFileset.json b/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorialFullFileset.json index 11d71d13f5a..7c063d36d23 100644 --- a/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorialFullFileset.json +++ b/pwiz_tools/Skyline/TestPerf/DiaUmpireTutorialTest.data/TestDiaTtofDiaUmpireTutorialFullFileset.json @@ -4,32 +4,32 @@ "IrtIntercept": -67.902, "MassErrorStats": [ [ - 2.5616557448280792, - 5.1752202623151184 + 2.5674782416585793, + 5.1743837532072243 ], [ - 2.494663161693043, - 4.8081199020993743 + 2.4993947910688985, + 4.8055985915421049 ], [ - 3.445711318811381, - 5.1170898217156626 + 3.4524790636975107, + 5.1140550552054487 ], [ - 4.7230924447835783, - 4.8368748712095391 + 4.7303558342453975, + 4.8307178400682451 ], [ - 3.8795328064295034, - 5.1547860040369944 + 3.8875810190246205, + 5.1506054517655011 ], [ - -0.17098470950345382, - 4.5474199723221851 + -0.17103541060360802, + 4.5533590952038292 ], [ - 0.99605995570347916, - 4.8624508158506785 + 1.0044169814970718, + 4.8638563788733586 ] ], "FinalTargetCounts": [ @@ -39,13 +39,13 @@ 196278 ], "ScoringModelCoefficients": [ - 0.1985, - -0.6148, - 4.3467, - -0.0062, - -0.1611, - 0.5597, - 0.0893, - -0.0411 + 0.1964, + -0.6123, + 4.3547, + -0.0126, + -0.1618, + 0.5599, + 0.0914, + -0.041 ] } \ No newline at end of file diff --git a/pwiz_tools/Skyline/TestPerf/PerfImportPrmPasefTest.cs b/pwiz_tools/Skyline/TestPerf/PerfImportPrmPasefTest.cs index 417d13bd844..4340651659b 100644 --- a/pwiz_tools/Skyline/TestPerf/PerfImportPrmPasefTest.cs +++ b/pwiz_tools/Skyline/TestPerf/PerfImportPrmPasefTest.cs @@ -115,18 +115,18 @@ private void TestReports(string msg = null) Resources.ReportSpecList_GetDefaults_Peptide_RT_Results, 210); var rts = new double?[] { - 12.45, 21.48, 16.93, 22.93, 13.63, 19.12, 28.97, 14.88, 14.24, 27.25, 14.97, 14.26, 25.7, 15.06, 11.93, 26.37, 12.89, - 15.87, 18.34, 11.16, 10.46, 10.98, 28, 24.01, 11.15, 18.97, 23.33, 26.56, 11.94, 19, 24.2, 23.42, 26.1, 27.86, 27.76, - 20.99, 26.15, 21.16, 25.99, 14.29, 26.04, 15.04, 24.07, 28.15, 20.1, 26.03, 15.07, 19.78, 27.2, 21.26, 25.39, 24.96, 11.71, - 21.1, 20.51, 14.71, 25.24, 24.9, 28.57, 20.98, 17.19, 15.39, 18.08, 16.06, 26.11, 10.26, 13.11, 9.46, 10.78, 14.41, 25.16, - 26.36, 17.21, 24.79, 13.9, 20.63, 19.7, 22.5, 13.97, 19.34, 19.05, 18.45, 13.96, 6.59, 26.12, 17.76, 28.31, 24.27, 29.25, - 27.56, 23.72, 12.09, 8.92, 18.39, 20.23, 26.41, 28.45, 22.17, 15.16, 24.73, 28.22, 17.16, 9.59, 13.84, 22.91, 17.14, 19.92, + 12.45, 21.48, 16.94, 22.93, 13.63, 19.12, 28.97, 14.88, 14.22, 27.25, 14.97, 14.26, 25.7, 15.06, 11.93, 26.37, 12.89, + 15.88, 18.34, 11.16, 10.46, 10.98, 27.9, 24.01, 11.15, 18.96, 23.33, 26.56, 11.94, 19, 24.2, 23.42, 26.1, 27.86, 27.76, + 20.99, 26.15, 21.16, 25.99, 14.29, 26.04, 15.04, 24.07, 28.15, 20.1, 26.03, 15.07, 19.78, 27.2, 21.26, 25.34, 24.96, 11.71, + 21.1, 20.51, 14.71, 25.24, 24.9, 28.57, 20.98, 17.19, 15.39, 18.08, 16.06, 26.11, 10.26, 13.11, 9.46, 10.78, 14.4, 25.16, + 26.35, 17.21, 24.79, 13.9, 20.63, 19.7, 22.5, 13.97, 19.34, 19.05, 18.44, 13.96, 6.59, 26.12, 17.76, 28.31, 24.27, 29.25, + 27.56, 23.72, 12.09, 8.92, 18.39, 20.23, 26.41, 28.45, 22.17, 15.17, 24.73, 28.22, 17.16, 9.59, 13.84, 22.91, 17.14, 19.92, 24.24, 27.82, 24.67, 10.41, 12.27, 21.21, 18.69, 12.68, 21.09, 22.11, 21.43, 14.51, 17.31, 21.92, 22.43, 19.53, 25.24, 25.31, 11.61, 24.37, 10.07, 24.29, 23.28, 15.83, 14.87, 28.53, 14.85, 28.5, 22.49, 19.35, 24.81, 24.57, 24.63, 24.36, 15.46, 22.7, - 19.85, 22.56, 21.91, 15.97, 20.82, 13.62, 19.52, 15.55, 23.71, 30.35, 19.57, 13.57, 29.08, 15.56, 21.73, 16.05, 13.95, 25.39, - 14.3, 25.25, 15.72, 10.48, 9.88, 21.3, 9.92, 26.56, 27.89, 29.85, 22.12, 25.06, 21.53, 15.25, 21.13, 22.81, 20.44, 18.23, + 19.85, 22.56, 21.91, 15.98, 20.82, 13.62, 19.52, 15.55, 23.71, 30.35, 19.57, 13.56, 29.06, 15.56, 21.73, 16.05, 13.95, 25.39, + 14.3, 25.3, 15.72, 10.48, 9.88, 21.3, 9.94, 26.56, 27.89, 29.85, 22.12, 25.06, 21.53, 15.25, 21.13, 22.81, 20.44, 18.23, 11.49, 25.59, 20.13, 16.32, 13.19, 9.63, 12.11, 21.84, 10.83, 12.49, 16.99, 18.83, 11.71, 12.84, 17.31, 11.13, 12.88, 24.35, - 16.83, 15.26, 22.72, 8.45, 11.17, 18.41, 18.85, 22.16, 16.56, 10.02, 10.13, 25.91, 29.07 + 16.83, 15.26, 22.72, 8.45, 11.17, 18.41, 18.85, 22.16, 16.56, 10.02, 10.13, 25.91, 29.06 }; for (row = 0; row < rts.Length; row++) {