public void Test4() { // Based on https://onlinecourses.science.psu.edu/stat501/node/397 double[] x_values = new double[] { 16, 14, 22, 10, 14, 17, 10, 13, 19, 12, 18, 11 }; double[] y_values = new double[] { 77, 70, 85, 50, 62, 70, 55, 63, 88, 57, 81, 51 }; var reg_ols = new LinearRegression(); for (int i = 0; i < x_values.Length; i++) { reg_ols.AddDataPoint(x_values[i], y_values[i]); } reg_ols.Solve(); Assert.AreEqual(3.269, reg_ols.A, 0.001); Assert.AreEqual(19.47, reg_ols.B, 0.01); Assert.AreEqual(4.5983, reg_ols.StdDev, 0.0001); Assert.AreEqual(0.365, reg_ols.Uncertainty_A, 0.001); Assert.AreEqual(5.52, reg_ols.Uncertainty_B, 0.01); var residuals = reg_ols.Residuals.ToArray(); var reg2 = new LinearRegression(); for (int i = 0; i < x_values.Length; i++) { reg2.AddDataPoint(x_values[i], Math.Abs(residuals[i])); } reg2.Solve(); var predictedValues = new double[x_values.Length]; for (int i = 0; i < x_values.Length; i++) { predictedValues[i] = reg2.ComputeY(x_values[i]); } var reg_wls = new LinearRegression(); var factor = 1; for (int i = 0; i < x_values.Length; i++) { double weight = factor / (predictedValues[i] * predictedValues[i]); reg_wls.AddDataPoint(x_values[i], y_values[i], weight); } reg_wls.Solve(); Assert.AreEqual(3.421, reg_wls.A, 0.001); Assert.AreEqual(17.30, reg_wls.B, 0.01); Assert.AreEqual(1.15935, reg_wls.StdDevUnscaled, 0.00001); }
public void Test2() { var reg = new LinearRegression(); reg.AddDataPoint(1, 6); reg.AddDataPoint(2, 5); reg.AddDataPoint(3, 7); reg.AddDataPoint(4, 10); reg.Solve(); Assert.AreEqual(1.4, reg.A, 0.001); Assert.AreEqual(3.5, reg.B, 0.001); }
private void DoLinearFit() { m_LinearFitX.Reset(); m_LinearFitY.Reset(); // Look for linear movement: // x = a + i * b // y = a + i * b int firstFrameId = m_PreviousPositionFrameIds[0]; for (int i = 0; i < m_PreviousPositions.Count; i++) { int deltaFrames = m_PreviousPositionFrameIds[i] - firstFrameId; m_LinearFitX.AddDataPoint(deltaFrames, m_PreviousPositions[i].XDouble); m_LinearFitY.AddDataPoint(deltaFrames, m_PreviousPositions[i].YDouble); } m_LinearFitX.Solve(); m_LinearFitY.Solve(); }
public void Test3() { // Based on https://onlinecourses.science.psu.edu/stat501/node/352 double[] x_values = new double[] { 0.21, 0.20, 0.19, 0.18, 0.17, 0.16, 0.15 }; double[] y_values = new double[] { 0.1726, 0.1707, 0.1637, 0.1640, 0.1613, 0.1617, 0.1598 }; double[] sd_values = new double[] { 0.01988, 0.01938, 0.01896, 0.02037, 0.01654, 0.01594, 0.01763 }; var reg_ols = new LinearRegression(); var reg_wls = new LinearRegression(); var reg_wls2 = new LinearRegression(); for (int i = 0; i < x_values.Length; i++) { reg_ols.AddDataPoint(x_values[i], y_values[i]); reg_wls.AddDataPoint(x_values[i], y_values[i], 1 / (sd_values[i] * sd_values[i])); reg_wls2.AddDataPoint(x_values[i], y_values[i], 2 / (sd_values[i] * sd_values[i])); } reg_ols.Solve(); reg_wls.Solve(); reg_wls2.Solve(); Assert.AreEqual(0.2048, reg_wls.A, 0.0001); Assert.AreEqual(0.12796, reg_wls.B, 0.0001); Assert.AreEqual(0.2048, reg_wls2.A, 0.0001); Assert.AreEqual(0.12796, reg_wls2.B, 0.0001); if (Math.Abs(reg_wls.StdDevUnscaled - reg_wls2.StdDevUnscaled) < 0.0001) { Assert.Fail("Proportional weights result in different StdDevs in the weighted population"); } ; Assert.AreEqual(reg_wls.StdDev, reg_wls2.StdDev, 0.0001, "Proportional weights result in the same scaled StdDev"); Assert.AreEqual(0.2100, reg_ols.A, 0.0001); Assert.AreEqual(0.12703, reg_ols.B, 0.0001); }
public int AstroAnalogueVideoNormaliseNtpDataIfNeeded(Action <int> progressCallback, out float oneSigmaError) { int ntpError = -1; oneSigmaError = float.NaN; if (NtpDataAvailable && !OcrDataAvailable && !m_UseNtpTimeAsCentralExposureTime) { if (m_CountFrames > 1 /* No Timestamp for first frame */ + 1 /* No Timestamp for last frame*/ + 3 /* Minimum timestamped frames for a FIT */) { try { double frameDurationMilliseconds = 1000 / m_FrameRate; var lr = new LinearRegression(); long zeroPointTicks = -1; int percentDone = 0; int percentDoneCalled = 0; if (progressCallback != null) { progressCallback(percentDone); } long ntpTimestampErrorSum = 0; int ntpTimestampErrorDatapoints = 0; for (int i = m_FirstFrame; i < m_FirstFrame + m_CountFrames; i++) { FrameStateData stateChannel = GetFrameStatusChannel(i); if (stateChannel.HasValidNtpTimeStamp) { long centralTicks = stateChannel.EndFrameNtpTime.AddMilliseconds(-0.5 * frameDurationMilliseconds).Ticks; if (zeroPointTicks == -1) { zeroPointTicks = centralTicks; } lr.AddDataPoint(i, new TimeSpan(centralTicks - zeroPointTicks).TotalMilliseconds); ntpTimestampErrorSum += stateChannel.NtpTimeStampError; ntpTimestampErrorDatapoints++; } percentDone = 100 * (i - m_FirstFrame) / m_CountFrames; if (progressCallback != null && percentDone - percentDoneCalled > 5) { progressCallback(percentDone); percentDoneCalled = percentDone; } } if (lr.NumberOfDataPoints > 3) { lr.Solve(); m_CalibratedNtpTimeZeroPoint = zeroPointTicks; m_CalibratedNtpTimeSource = lr; m_UseNtpTimeAsCentralExposureTime = true; m_NtpTimeFitSigma = lr.StdDev; m_NtpTimeAverageNetworkError = (ntpTimestampErrorSum * 1.0 / ntpTimestampErrorDatapoints); ntpError = (int)Math.Round(m_NtpTimeFitSigma + m_NtpTimeAverageNetworkError); Trace.WriteLine(string.Format("NTP Timebase Established. 1-Sigma = {0} ms", lr.StdDev.ToString("0.00"))); oneSigmaError = (float)m_NtpTimeFitSigma; } progressCallback(100); } catch (Exception ex) { Trace.WriteLine(ex.GetFullStackTrace()); } } } return(ntpError); }
public ProcessingReturnValues FitAndPlotSlowFlyby( Dictionary <int, SingleMultiFrameMeasurement> measurements, FlybyMeasurementContext meaContext, FittingContext fittingContext, FittingValue fittingValue, GetFrameStateDataCallback getFrameStateDataCallback, Graphics g, FlybyPlottingContext plottingContext, float xScale, float yScale, int imageWidth, int imageHight, out double motionRate) { try { #region Building Test Cases if (m_DumpTestCaseData) { var mvSer = new XmlSerializer(typeof(FlybyMeasurementContext)); var sb = new StringBuilder(); using (var wrt = new StringWriter(sb)) { mvSer.Serialize(wrt, meaContext); } Trace.WriteLine(sb.ToString()); var fcSer = new XmlSerializer(typeof(FittingContext)); sb.Clear(); using (var wrt = new StringWriter(sb)) { fcSer.Serialize(wrt, fittingContext); } Trace.WriteLine(sb.ToString()); var smfmSer = new XmlSerializer(typeof(SingleMultiFrameMeasurement)); foreach (int key in measurements.Keys) { sb.Clear(); using (var wrt2 = new StringWriter(sb)) { smfmSer.Serialize(wrt2, measurements[key]); if (measurements[key].FrameNo != key) { throw new InvalidOperationException(); } Trace.WriteLine(sb.ToString()); } } } #endregion // Do linear regression, use residual based exclusion rules // Report the interpolated position at the middle of the measured interva // Don't forget to add the video normal position flag in the OBS file // Expect elongated images and apply instrumental delay corrections motionRate = double.NaN; var rv = new ProcessingReturnValues(); int numFramesUser = 0; rv.EarliestFrame = int.MaxValue; rv.LatestFrame = int.MinValue; var intervalValues = new Dictionary <int, Tuple <List <double>, List <double> > >(); var intervalMedians = new Dictionary <double, double>(); var intervalWeights = new Dictionary <double, double>(); LinearRegression regression = null; if (measurements.Values.Count > 1) { rv.EarliestFrame = measurements.Values.Select(m => m.FrameNo).Min(); rv.LatestFrame = measurements.Values.Select(m => m.FrameNo).Max(); var minUncertainty = meaContext.MinPositionUncertaintyPixels * meaContext.ArsSecsInPixel; foreach (SingleMultiFrameMeasurement measurement in measurements.Values) { int integrationInterval = (measurement.FrameNo - fittingContext.FirstFrameIdInIntegrationPeroid) / fittingContext.IntegratedFramesCount; Tuple <List <double>, List <double> > intPoints; if (!intervalValues.TryGetValue(integrationInterval, out intPoints)) { intPoints = Tuple.Create(new List <double>(), new List <double>()); intervalValues.Add(integrationInterval, intPoints); } if (fittingValue == FittingValue.RA) { intPoints.Item1.Add(measurement.RADeg); intPoints.Item2.Add(ComputePositionWeight(measurement.SolutionUncertaintyRACosDEArcSec, measurement, minUncertainty, fittingContext.Weighting)); } else { intPoints.Item1.Add(measurement.DEDeg); intPoints.Item2.Add(ComputePositionWeight(measurement.SolutionUncertaintyDEArcSec, measurement, minUncertainty, fittingContext.Weighting)); } } if (intervalValues.Count > 2) { regression = new LinearRegression(); foreach (int integratedFrameNo in intervalValues.Keys) { Tuple <List <double>, List <double> > data = intervalValues[integratedFrameNo]; double median; double medianWeight; WeightedMedian(data, out median, out medianWeight); // Assign the data point to the middle of the integration interval (using frame numbers) // // |--|--|--|--|--|--|--|--| // | | | // // Because the time associated with the first frame is the middle of the frame, but the // time associated with the middle of the interval is the end of the field then the correction // is (N / 2) - 0.5 frames when integration is used or no correction when integration of x1 is used. double dataPointFrameNo = rv.EarliestFrame + fittingContext.IntegratedFramesCount * integratedFrameNo + (fittingContext.IntegratedFramesCount / 2) - (fittingContext.IntegratedFramesCount > 1 ? 0.5 : 0); intervalMedians.Add(dataPointFrameNo, median); intervalWeights.Add(dataPointFrameNo, medianWeight); if (fittingContext.Weighting != WeightingMode.None) { regression.AddDataPoint(dataPointFrameNo, median, medianWeight); } else { regression.AddDataPoint(dataPointFrameNo, median); } } regression.Solve(); var firstPos = measurements[rv.EarliestFrame]; var lastPos = measurements[rv.LatestFrame]; double distanceArcSec = AngleUtility.Elongation(firstPos.RADeg, firstPos.DEDeg, lastPos.RADeg, lastPos.DEDeg) * 3600; var firstTime = GetTimeForFrame(fittingContext, rv.EarliestFrame, meaContext.FirstVideoFrame, getFrameStateDataCallback, firstPos.FrameTimeStamp); var lastTime = GetTimeForFrame(fittingContext, rv.LatestFrame, meaContext.FirstVideoFrame, getFrameStateDataCallback, lastPos.FrameTimeStamp); double elapsedSec = new TimeSpan(lastTime.UT.Ticks - firstTime.UT.Ticks).TotalSeconds; motionRate = distanceArcSec / elapsedSec; } } FrameTime resolvedTime = null; if (int.MinValue != meaContext.UserMidFrame) { // Find the closest video 'normal' MPC time and compute the frame number for it // Now compute the RA/DE for the computed 'normal' frame resolvedTime = GetTimeForFrame(fittingContext, meaContext.UserMidFrame, meaContext.FirstVideoFrame, getFrameStateDataCallback, measurements[meaContext.UserMidFrame].FrameTimeStamp); #region Plotting Code if (g != null) { float xPosBeg = (float)(resolvedTime.ClosestNormalIntervalFirstFrameNo - rv.EarliestFrame) * xScale + 5; float xPosEnd = (float)(resolvedTime.ClosestNormalIntervalLastFrameNo - rv.EarliestFrame) * xScale + 5; g.FillRectangle(s_NormalTimeIntervalHighlightBrush, xPosBeg, 1, (xPosEnd - xPosBeg), imageHight - 2); } #endregion } Dictionary <double, double> secondPassData = new Dictionary <double, double>(); int minFrameId = measurements.Keys.Min(); #region Plotting Code if (g != null) { foreach (SingleMultiFrameMeasurement measurement in measurements.Values) { float x = (measurement.FrameNo - minFrameId) * xScale + 5; ProcessingValues val = new ProcessingValues() { Value = fittingValue == FittingValue.RA ? measurement.RADeg : measurement.DEDeg, StdDev = fittingValue == FittingValue.RA ? measurement.StdDevRAArcSec / 3600.0 : measurement.StdDevDEArcSec / 3600.0 }; double valueFrom = val.Value - val.StdDev; double valueTo = val.Value + val.StdDev; float yFrom = (float)(valueFrom - plottingContext.MinValue) * yScale + 5; float yTo = (float)(valueTo - plottingContext.MinValue) * yScale + 5; g.DrawLine(plottingContext.IncludedPen, x, yFrom, x, yTo); g.DrawLine(plottingContext.IncludedPen, x - 1, yFrom, x + 1, yFrom); g.DrawLine(plottingContext.IncludedPen, x - 1, yTo, x + 1, yTo); } } #endregion foreach (double integrFrameNo in intervalMedians.Keys) { double val = intervalMedians[integrFrameNo]; double fittedValAtFrame = regression != null ? regression.ComputeY(integrFrameNo) : double.NaN; bool included = Math.Abs(fittedValAtFrame - val) < 3 * regression.StdDev; #region Plotting Code if (g != null) { if (fittingContext.IntegratedFramesCount > 1) { Pen mPen = included ? plottingContext.IncludedPen : plottingContext.ExcludedPen; float x = (float)(integrFrameNo - minFrameId) * xScale + 5; float y = (float)(val - plottingContext.MinValue) * yScale + 5; g.DrawEllipse(mPen, x - 3, y - 3, 6, 6); g.DrawLine(mPen, x - 5, y - 5, x + 5, y + 5); g.DrawLine(mPen, x + 5, y - 5, x - 5, y + 5); } } #endregion if (included) { secondPassData.Add(integrFrameNo, val); } } #region Second Pass regression = null; if (secondPassData.Count > 2) { regression = new LinearRegression(); foreach (double frameNo in secondPassData.Keys) { if (fittingContext.Weighting != WeightingMode.None) { regression.AddDataPoint(frameNo, secondPassData[frameNo], intervalWeights[frameNo]); } else { regression.AddDataPoint(frameNo, secondPassData[frameNo]); } } regression.Solve(); } #endregion if (regression != null) { #region Plotting Code if (g != null) { double leftFittedVal = regression.ComputeY(rv.EarliestFrame); double rightFittedVal = regression.ComputeY(rv.LatestFrame); double err = 3 * regression.StdDev; float leftAve = (float)(leftFittedVal - plottingContext.MinValue) * yScale + 5; float rightAve = (float)(rightFittedVal - plottingContext.MinValue) * yScale + 5; float leftX = 5 + (float)(rv.EarliestFrame - rv.EarliestFrame) * xScale; float rightX = 5 + (float)(rv.LatestFrame - rv.EarliestFrame) * xScale; g.DrawLine(plottingContext.AveragePen, leftX, leftAve - 1, rightX, rightAve - 1); g.DrawLine(plottingContext.AveragePen, leftX, leftAve, rightX, rightAve); g.DrawLine(plottingContext.AveragePen, leftX, leftAve + 1, rightX, rightAve + 1); float leftMin = (float)(leftFittedVal - err - plottingContext.MinValue) * yScale + 5; float leftMax = (float)(leftFittedVal + err - plottingContext.MinValue) * yScale + 5; float rightMin = (float)(rightFittedVal - err - plottingContext.MinValue) * yScale + 5; float rightMax = (float)(rightFittedVal + err - plottingContext.MinValue) * yScale + 5; g.DrawLine(plottingContext.AveragePen, leftX, leftMin, rightX, rightMin); g.DrawLine(plottingContext.AveragePen, leftX, leftMax, rightX, rightMax); } #endregion if (int.MinValue != meaContext.UserMidFrame && resolvedTime != null) { // Find the closest video 'normal' MPC time and compute the frame number for it // Now compute the RA/DE for the computed 'normal' frame double fittedValueUncertainty; double fittedValueAtMiddleFrame = regression.ComputeYWithError(resolvedTime.ClosestNormalFrameNo, out fittedValueUncertainty); Trace.WriteLine(string.Format("{0}; Included: {1}; Normal Frame No: {2}; Fitted Val: {3} +/- {4:0.00}", meaContext.UserMidValue.ToString("0.00000"), numFramesUser, resolvedTime.ClosestNormalFrameNo, AstroConvert.ToStringValue(fittedValueAtMiddleFrame, "+HH MM SS.T"), regression.StdDev * 60 * 60)); // Report the interpolated position at the middle of the measured interval // Don't forget to add the video normal position flag in the OBS file // Expect elongated images and apply instrumental delay corrections rv.FittedValue = fittedValueAtMiddleFrame; rv.FittedValueTime = resolvedTime.ClosestNormalFrameTime; rv.IsVideoNormalPosition = true; rv.FittedNormalFrame = resolvedTime.ClosestNormalFrameNo; rv.FittedValueUncertaintyArcSec = fittedValueUncertainty * 60 * 60; #region Plotting Code if (g != null) { // Plot the frame float xPos = (float)(resolvedTime.ClosestNormalFrameNo - rv.EarliestFrame) * xScale + 5; float yPos = (float)(rv.FittedValue - plottingContext.MinValue) * yScale + 5; g.DrawLine(Pens.Yellow, xPos, 1, xPos, imageHight - 2); g.FillEllipse(Brushes.Yellow, xPos - 3, yPos - 3, 6, 6); } #endregion } else { rv.FittedValue = double.NaN; } } else { rv.FittedValue = double.NaN; } return(rv); } catch (Exception ex) { Trace.WriteLine(ex.GetFullStackTrace()); motionRate = 0; return(null); } }
private ImagePixel GetExpectedPosition(int frameNo) { ImagePixel rv = null; var intervalValues = new Dictionary <int, List <ImagePixel> >(); var intervalMedians = new Dictionary <int, ImagePixel>(); int earliestFrame = m_PastFrameNos[0]; for (int i = 0; i < m_PastFrameNos.Count; i++) { int integrationInterval = (m_PastFrameNos[i] - earliestFrame) / m_MeasurementContext.IntegratedFramesCount; List <ImagePixel> intPoints; if (!intervalValues.TryGetValue(integrationInterval, out intPoints)) { intPoints = new List <ImagePixel>(); intervalValues.Add(integrationInterval, intPoints); } intPoints.Add(new ImagePixel(m_PastFramePosX[i], m_PastFramePosY[i])); } var calcBucketX = new List <double>(); var calcBucketY = new List <double>(); foreach (int key in intervalValues.Keys) { calcBucketX.Clear(); calcBucketY.Clear(); intervalValues[key].ForEach(v => { calcBucketX.Add(v.XDouble); calcBucketY.Add(v.YDouble); }); double xMed = calcBucketX.Median(); double yMed = calcBucketY.Median(); intervalMedians.Add(key, new ImagePixel(xMed, yMed)); } var xMotion = new LinearRegression(); var yMotion = new LinearRegression(); foreach (int intInt in intervalMedians.Keys) { long t = intInt; double x = intervalMedians[intInt].XDouble; double y = intervalMedians[intInt].YDouble; if (x > 0 && y > 0) { xMotion.AddDataPoint(t, x); yMotion.AddDataPoint(t, y); } } try { xMotion.Solve(); yMotion.Solve(); int currIntInterval = (frameNo - earliestFrame) / m_MeasurementContext.IntegratedFramesCount; rv = new ImagePixel(xMotion.ComputeY(currIntInterval), yMotion.ComputeY(currIntInterval)); } catch (Exception ex) { Trace.WriteLine(ex.GetFullStackTrace()); } return(rv); }
public void Test1() { // Based on https://www.medcalc.org/manual/weighted-regression-worked-example.php var reg = new LinearRegression(); double[] x_values = new double[] { 27, 21, 22, 24, 25, 23, 20, 20, 29, 24, 25, 28, 26, 38, 32, 33, 31, 34, 37, 38, 33, 35, 30, 31, 37, 39, 46, 49, 40, 42, 43, 46, 43, 44, 46, 47, 45, 49, 48, 40, 42, 55, 54, 57, 52, 53, 56, 52, 50, 59, 50, 52, 58, 57 }; double[] y_values = new double[] { 73, 66, 63, 75, 71, 70, 65, 70, 79, 72, 68, 67, 79, 91, 76, 69, 66, 73, 78, 87, 76, 79, 73, 80, 68, 75, 89, 101, 70, 72, 80, 83, 75, 71, 80, 96, 92, 80, 70, 90, 85, 76, 71, 99, 86, 79, 92, 85, 71, 90, 91, 100, 80, 109 }; for (int i = 0; i < x_values.Length; i++) { reg.AddDataPoint(x_values[i], y_values[i]); } reg.Solve(); Assert.AreEqual(0.5800, reg.A, 0.0001); Assert.AreEqual(56.1569, reg.B, 0.0001); Assert.AreEqual(8.1457, reg.StdDev, 0.0001); Assert.AreEqual(0.09695, reg.Uncertainty_A, 0.0001); Assert.AreEqual(3.9937, reg.Uncertainty_B, 0.0001); var residuals = reg.Residuals.ToArray(); var reg2 = new LinearRegression(); for (int i = 0; i < x_values.Length; i++) { reg2.AddDataPoint(x_values[i], Math.Abs(residuals[i])); } reg2.Solve(); Assert.AreEqual(0.1982, reg2.A, 0.0001); Assert.AreEqual(-1.5495, reg2.B, 0.0001); Assert.AreEqual(4.4606, reg2.StdDev, 0.0001); Assert.AreEqual(0.05309, reg2.Uncertainty_A, 0.0001); Assert.AreEqual(2.1869, reg2.Uncertainty_B, 0.0001); var predictedValues = new double[x_values.Length]; for (int i = 0; i < x_values.Length; i++) { predictedValues[i] = reg2.ComputeY(x_values[i]); } var reg3 = new LinearRegression(); var factor = 1; for (int i = 0; i < x_values.Length; i++) { double weight = factor / (predictedValues[i] * predictedValues[i]); reg3.AddDataPoint(x_values[i], y_values[i], weight); } reg3.Solve(); Assert.AreEqual(0.5963, reg3.A, 0.0001); Assert.AreEqual(55.5658, reg3.B, 0.0001); Assert.AreEqual(1.2130, reg3.StdDevUnscaled, 0.0001); }
public void TestRAUncertainty() { var regressionRA = new LinearRegression(); var regressionRACosDE = new LinearRegression(); var regressionDE = new LinearRegression(); #region DATA var data3 = new double[100, 2] { { 107.739483057629, 23.1727860863356 }, { 107.739758423225, 23.1725858289442 }, { 107.739270316112, 23.1727265176296 }, { 107.740138616241, 23.1726236358575 }, { 107.739635635855, 23.1720172219518 }, { 107.739770516897, 23.1729142307849 }, { 107.73978035157, 23.1724868171702 }, { 107.7402464908, 23.1722211800014 }, { 107.740153319844, 23.17248806658 }, { 107.739638162317, 23.1721297891023 }, { 107.740152592624, 23.1724840894266 }, { 107.73958378293, 23.1723613227751 }, { 107.740253757515, 23.172044928013 }, { 107.739869281082, 23.1718262300426 }, { 107.739789179397, 23.1719229687229 }, { 107.740109562638, 23.1722060231074 }, { 107.740283891099, 23.1718843884705 }, { 107.739908616613, 23.1721244581715 }, { 107.740456941346, 23.171955354053 }, { 107.739775905586, 23.1718647897894 }, { 107.739910899659, 23.1717039889273 }, { 107.740289075343, 23.1716186337573 }, { 107.739949394724, 23.171506240555 }, { 107.739932221015, 23.1716027243754 }, { 107.740295228809, 23.1716475314679 }, { 107.740110388037, 23.1713426960706 }, { 107.740000979798, 23.1716693560547 }, { 107.740683952325, 23.1709681704731 }, { 107.740203119785, 23.1714519510736 }, { 107.740273707513, 23.1713702195282 }, { 107.740715664145, 23.1708006096487 }, { 107.740242143812, 23.1714449185373 }, { 107.740687548443, 23.1714370988362 }, { 107.740393828828, 23.1711679651411 }, { 107.740547330181, 23.1707733940985 }, { 107.740304974691, 23.1709983145174 }, { 107.74034540204, 23.1710469626238 }, { 107.740904365499, 23.1712337543625 }, { 107.740271422439, 23.1707876401664 }, { 107.740473226403, 23.1707728540074 }, { 107.740636055235, 23.1704881107108 }, { 107.740425649522, 23.1706972672815 }, { 107.741075014634, 23.1711886612524 }, { 107.740786717375, 23.1703917179741 }, { 107.740612480474, 23.1707997640991 }, { 107.740513888292, 23.1702033095878 }, { 107.740861057518, 23.1707917436653 }, { 107.740431450664, 23.1704962321596 }, { 107.740894364108, 23.1706460451468 }, { 107.740737653969, 23.1699454114361 }, { 107.741167338868, 23.1704390641158 }, { 107.7413536072, 23.1702117449598 }, { 107.741086391515, 23.1708025027409 }, { 107.740817260861, 23.1707126761071 }, { 107.741221711022, 23.1703529560508 }, { 107.740861537438, 23.1703420196572 }, { 107.74133696574, 23.1699266624604 }, { 107.741008974082, 23.1698332023904 }, { 107.741330374957, 23.1699747581779 }, { 107.740956577025, 23.1699971703065 }, { 107.741383593614, 23.1700614545797 }, { 107.741111473117, 23.1693429455465 }, { 107.741121545844, 23.1698626383014 }, { 107.740820518692, 23.1696821611261 }, { 107.740916798005, 23.1696481452449 }, { 107.741407098196, 23.1697794065368 }, { 107.741287855641, 23.1694896025056 }, { 107.741231209856, 23.1697278715938 }, { 107.741571326879, 23.1696485314836 }, { 107.741256422563, 23.1692796164232 }, { 107.741083941569, 23.1692085591873 }, { 107.742137813992, 23.1692501809322 }, { 107.741681536193, 23.1691250721346 }, { 107.741303539868, 23.1692497992003 }, { 107.741635549928, 23.1696783769589 }, { 107.741659390834, 23.1692018221502 }, { 107.741519314827, 23.1689582724524 }, { 107.741584255568, 23.1690406639767 }, { 107.741457448448, 23.1689571870363 }, { 107.741598496613, 23.1690963575179 }, { 107.741640939783, 23.1689548869089 }, { 107.741369971733, 23.1688034552239 }, { 107.741859029521, 23.1688553893107 }, { 107.741963565581, 23.1683666431612 }, { 107.741666822527, 23.1688987338326 }, { 107.741641792823, 23.1689634685279 }, { 107.742023968239, 23.1690510611327 }, { 107.741962846523, 23.1688076089897 }, { 107.741683243803, 23.1683235937605 }, { 107.742121706882, 23.1689619396987 }, { 107.741538062532, 23.168283426697 }, { 107.742422013432, 23.1687801621234 }, { 107.741896618583, 23.1686362544736 }, { 107.741972513575, 23.1686551496664 }, { 107.74206679923, 23.168500337937 }, { 107.742405073144, 23.1684770104653 }, { 107.742032294448, 23.1683475533958 }, { 107.742230287337, 23.1682284222132 }, { 107.742201384546, 23.1682279605208 }, { 107.741884032454, 23.1682565185304 } }; var data2 = new double[100, 2] { { 107.739477771965, 69.1728347555165 }, { 107.73961196667, 69.1726830767986 }, { 107.739409817125, 69.1725933008645 }, { 107.74019077671, 69.172494940147 }, { 107.740585586026, 69.1726077103564 }, { 107.74059838218, 69.1722819118463 }, { 107.740208274081, 69.1723120729806 }, { 107.740712281929, 69.1720791829606 }, { 107.74033612626, 69.1723780587458 }, { 107.741242715081, 69.1721192003436 }, { 107.741266002392, 69.1722343854433 }, { 107.741105006249, 69.1721003309252 }, { 107.741363419742, 69.172088671089 }, { 107.741690798727, 69.1722568414364 }, { 107.741666861184, 69.17205417308 }, { 107.741361837266, 69.1720675384819 }, { 107.742084712903, 69.1718874998462 }, { 107.742042010619, 69.1719175816475 }, { 107.742280817921, 69.1719094396275 }, { 107.742577304285, 69.1717986068223 }, { 107.741966244595, 69.1718798130166 }, { 107.742684141694, 69.1718510228511 }, { 107.742976630078, 69.17157595174 }, { 107.743352294911, 69.1715857376448 }, { 107.743061556848, 69.1714241326127 }, { 107.743090113926, 69.1714458284253 }, { 107.743275509384, 69.1715522559223 }, { 107.743176399757, 69.1714823812197 }, { 107.743814300524, 69.1713901151672 }, { 107.743767969983, 69.1714428793583 }, { 107.743858065548, 69.1713035534992 }, { 107.743822885639, 69.1712307918348 }, { 107.744334295083, 69.1713055626603 }, { 107.744427875787, 69.1712388751409 }, { 107.744340783728, 69.1710928940912 }, { 107.744805413896, 69.1710055991679 }, { 107.74502108349, 69.1709137441353 }, { 107.744828974649, 69.1709496406011 }, { 107.74512401154, 69.1708573778863 }, { 107.745281281021, 69.1708161629737 }, { 107.745576469803, 69.1710203029433 }, { 107.745740147716, 69.1707075932863 }, { 107.745502577032, 69.1708074622807 }, { 107.745623997879, 69.1706684821245 }, { 107.746509028511, 69.1708362938784 }, { 107.74602549245, 69.1706568551002 }, { 107.745897609677, 69.170596921634 }, { 107.746469521491, 69.1704027875192 }, { 107.746277468908, 69.1703449453588 }, { 107.74672857027, 69.1705498632184 }, { 107.746946720371, 69.1702725215642 }, { 107.746884661238, 69.1704713141467 }, { 107.746860604767, 69.1702156222331 }, { 107.747294089089, 69.1701998357532 }, { 107.747620144745, 69.1702161529364 }, { 107.747858455982, 69.170059426359 }, { 107.747632580795, 69.1700516486805 }, { 107.747755094573, 69.1698765265442 }, { 107.747994751954, 69.1701514424405 }, { 107.748121399878, 69.1700667592208 }, { 107.748172662863, 69.1698849007696 }, { 107.748228803142, 69.1699923304849 }, { 107.749014214424, 69.1697868133965 }, { 107.748792915115, 69.1700161340914 }, { 107.748638559821, 69.1698409328545 }, { 107.748636809009, 69.1697153039492 }, { 107.749136438823, 69.1697074633957 }, { 107.749338377121, 69.1697654496001 }, { 107.749569612206, 69.1697500026688 }, { 107.749595422398, 69.169419529115 }, { 107.750383523525, 69.1696656542994 }, { 107.75048831811, 69.1692035028989 }, { 107.750352623105, 69.1693815488115 }, { 107.750761537512, 69.1692866838914 }, { 107.750685403332, 69.1693024139098 }, { 107.750705748086, 69.1692717484141 }, { 107.750843059166, 69.1692580533525 }, { 107.750716179597, 69.1691529497331 }, { 107.75072202975, 69.1691168107163 }, { 107.75108559747, 69.1691834005496 }, { 107.751493043153, 69.1691522809292 }, { 107.751596829163, 69.169012841963 }, { 107.75131727043, 69.1688012714158 }, { 107.751666922544, 69.1689359714136 }, { 107.751578388338, 69.1687399323656 }, { 107.751905351653, 69.1688376229061 }, { 107.752250104552, 69.1687619831483 }, { 107.752678677296, 69.1687244251449 }, { 107.752525957028, 69.1686536798413 }, { 107.752373714866, 69.1686287601216 }, { 107.753019411842, 69.1686380478501 }, { 107.752741905599, 69.1684961094107 }, { 107.753018243388, 69.1683971436757 }, { 107.752942371889, 69.1684595315802 }, { 107.753259742823, 69.1684806579804 }, { 107.753511849838, 69.168268224757 }, { 107.75342673874, 69.1685388792275 }, { 107.753991832751, 69.1681999371544 }, { 107.753606583523, 69.1682240722345 }, { 107.753806764198, 69.1682937040626 }, }; var data = new double[100, 2] { { 107.73935730677, 69.172711754819 }, { 107.739941719805, 69.1725607327368 }, { 107.739969545763, 69.1728020832412 }, { 107.739725613496, 69.1724948444009 }, { 107.739031353708, 69.1726159366499 }, { 107.739747958109, 69.1721826902977 }, { 107.74074663056, 69.1722130865804 }, { 107.739619292955, 69.1724872676585 }, { 107.739526386824, 69.1723097829692 }, { 107.7410771233, 69.1725703918204 }, { 107.740739660076, 69.1724144742981 }, { 107.740697636644, 69.1721550394849 }, { 107.741880544558, 69.1719738870443 }, { 107.741833247397, 69.1721940884685 }, { 107.742314186337, 69.17193450189 }, { 107.740926254247, 69.1721384298611 }, { 107.741970497859, 69.1721455511604 }, { 107.742119450987, 69.1721636360282 }, { 107.742132805385, 69.1719603779096 }, { 107.742777129314, 69.1714210932301 }, { 107.743075397391, 69.1714527431053 }, { 107.743768675633, 69.1715943552255 }, { 107.742542542626, 69.1716645234002 }, { 107.743524460336, 69.1716088168889 }, { 107.743350115709, 69.1717882604853 }, { 107.743489485345, 69.1721692852173 }, { 107.74401344733, 69.1717430018303 }, { 107.743001358569, 69.171745750306 }, { 107.744101155658, 69.1715995718388 }, { 107.743357827113, 69.1709192593056 }, { 107.744693317033, 69.1716866797303 }, { 107.74508962137, 69.1713006067015 }, { 107.743997983147, 69.1708910615953 }, { 107.743996491802, 69.1715212096561 }, { 107.74373222623, 69.1709626863127 }, { 107.744993875976, 69.1714069871322 }, { 107.744573043011, 69.1710885783853 }, { 107.744610440986, 69.1709524781463 }, { 107.744515880438, 69.171012647055 }, { 107.745021853434, 69.1706795177817 }, { 107.745928469586, 69.1709513201446 }, { 107.746274328526, 69.1709990525173 }, { 107.746003621776, 69.1703636654999 }, { 107.746860479622, 69.1707788370646 }, { 107.746015994963, 69.170532070676 }, { 107.745308266138, 69.1703833282827 }, { 107.747434171025, 69.1707583505672 }, { 107.746380716695, 69.1707477283657 }, { 107.746322773734, 69.1705759472448 }, { 107.747195770778, 69.1704783387962 }, { 107.746726832268, 69.1705445287354 }, { 107.746479807916, 69.170295182694 }, { 107.746858852632, 69.170151842435 }, { 107.748005552167, 69.1701324143026 }, { 107.746440725163, 69.1702997117708 }, { 107.747632752596, 69.1699714950714 }, { 107.746288060495, 69.170247112624 }, { 107.748643753256, 69.1698662806951 }, { 107.74881046717, 69.1699979516342 }, { 107.747535292112, 69.170078645581 }, { 107.747766333879, 69.1699744718271 }, { 107.748809227233, 69.1700193921839 }, { 107.749697498078, 69.1695793718616 }, { 107.74893998829, 69.1698713857366 }, { 107.748878487794, 69.1695228880432 }, { 107.74904534252, 69.1698472549382 }, { 107.749463140037, 69.1696628177742 }, { 107.749830174792, 69.1698519550262 }, { 107.749699649195, 69.1695956041761 }, { 107.750502343619, 69.1693099555417 }, { 107.750326261356, 69.1691415029809 }, { 107.748673873243, 69.1692620977489 }, { 107.75106313072, 69.1690631812159 }, { 107.75052688368, 69.1691938573698 }, { 107.750543324985, 69.1695068538202 }, { 107.751031016666, 69.1692150774484 }, { 107.751096804392, 69.1688797213772 }, { 107.75042386222, 69.1690032179007 }, { 107.750153005185, 69.169176739356 }, { 107.751104742713, 69.1687864249361 }, { 107.751084417858, 69.1690641095538 }, { 107.751285471877, 69.1690996992436 }, { 107.751597211541, 69.169157134543 }, { 107.751909360988, 69.1690350795147 }, { 107.752462310445, 69.1684796654783 }, { 107.751635885797, 69.1688208914861 }, { 107.752428595531, 69.1685312843568 }, { 107.753169064017, 69.1685892661358 }, { 107.752228822227, 69.1685323946141 }, { 107.753263916127, 69.168469632772 }, { 107.753442528401, 69.1684911064022 }, { 107.753490171167, 69.168976339076 }, { 107.753304279553, 69.1685787342008 }, { 107.75333647205, 69.1683355355218 }, { 107.753820078749, 69.1683194672346 }, { 107.753790511111, 69.1683609672304 }, { 107.754442898298, 69.1681319406902 }, { 107.753649542414, 69.1683447688382 }, { 107.754436235939, 69.1684819745314 }, { 107.754808387808, 69.1680718218884 }, }; #endregion double SEC_TO_DAY = 1.0 / (24 * 3600); for (int i = 0; i < 100; i++) { var ra = data[i, 0]; var de = data[i, 1]; regressionRA.AddDataPoint(i * SEC_TO_DAY, ra); regressionDE.AddDataPoint(i * SEC_TO_DAY, de); regressionRACosDE.AddDataPoint(i * SEC_TO_DAY, ra * Math.Cos(de * Math.PI / 180.0)); } regressionRA.Solve(); regressionDE.Solve(); regressionRACosDE.Solve(); double timeOfDay = 50 * SEC_TO_DAY; ErrorMethod errMethod = ErrorMethod.HalfStdDev; double errRAArcSec; double errDEArcSec; var raHours = regressionRA.ComputeYWithError(timeOfDay, out errRAArcSec, errMethod) / 15.0; var deDeg = regressionDE.ComputeYWithError(timeOfDay, out errDEArcSec, errMethod); double cosDEFactor = Math.Cos(deDeg * Math.PI / 180); double errRACosDEArcSec = errRAArcSec * cosDEFactor * 3600; errDEArcSec *= 3600; Trace.WriteLine(string.Format("RA = {0} +/- {1:0.00}\" StdDev={4:0.000}; DE={2} +/- {3:0.00}\"", AstroConvert.ToStringValue(raHours, "HH MM SS.TT"), errRACosDEArcSec, AstroConvert.ToStringValue(deDeg, "+DD MM SS.T"), errDEArcSec, regressionRA.StdDevUnscaled * cosDEFactor * 3600)); double errRA2; var raHoursCosDE = regressionRACosDE.ComputeYWithError(timeOfDay, out errRA2, errMethod) / 15.0; raHours = raHoursCosDE / cosDEFactor; errRA2 *= 3600; Trace.WriteLine(string.Format("RA = {0} +/- {1:0.00}\" StdDev={4:0.000}; DE={2} +/- {3:0.00}\"", AstroConvert.ToStringValue(raHours, "HH MM SS.TT"), errRA2, AstroConvert.ToStringValue(deDeg, "+DD MM SS.T"), errDEArcSec, regressionRACosDE.StdDevUnscaled * 3600)); Assert.IsTrue(regressionRA.StdDevUnscaled * cosDEFactor < regressionRACosDE.StdDevUnscaled); // NOTE: StdDev in the RA fit appears to be smaller in RA.CosDE terms when the fitting is done in RA, compared to when it is done in RA.CosDE. // Therefore we continue to use the RA fitting, rather than RA.CosDE fitting. // TODO: This needs to be looked through and proper mathematical evaluation needs to be done for the uncertainties before the RA.CosDE fitting can be used directly }
public void Calculate( MeasurementPositionEntry[] entries, WeightingMode weighting, bool removeOutliers, double outlierSigmaCoeff, double instDelayTimeOfDay, double minUncertainty, bool includePositionalUncertainties, ErrorMethod errorMethod, double smallestReportedUncertaintyArcSec) { m_InstDelayTimeOfDay = instDelayTimeOfDay; m_Weighting = weighting; m_ErrorMethod = errorMethod; m_SmallestReportedUncertaintyArcSec = smallestReportedUncertaintyArcSec; m_MinSinglePositionUncertainty = minUncertainty; var regRA = new LinearRegression(); var regDE = new LinearRegression(); foreach (var entry in entries) { var midFrameTime = entry.TimeOfDayUTC - instDelayTimeOfDay; if (weighting == WeightingMode.None) { regRA.AddDataPoint(midFrameTime, entry.RADeg); regDE.AddDataPoint(midFrameTime, entry.DEDeg); } else { var weightRA = CalulateWeight(entry, entry.SolutionUncertaintyRACosDEArcSec); var weightDE = CalulateWeight(entry, entry.SolutionUncertaintyDEArcSec); regRA.AddDataPoint(midFrameTime, entry.RADeg, weightRA); regDE.AddDataPoint(midFrameTime, entry.DEDeg, weightDE); } } m_Entries = new List <MeasurementPositionEntry>(); regRA.Solve(); regDE.Solve(); RemovedOutliers = 0; if (removeOutliers) { var outlierLimitRA = regRA.StdDev * outlierSigmaCoeff; var residualsRA = regRA.Residuals.ToArray(); var outlierLimitDE = regDE.StdDev * outlierSigmaCoeff; var residualsDE = regDE.Residuals.ToArray(); for (int i = 0; i < entries.Length; i++) { if (Math.Abs(residualsRA[i]) <= outlierLimitRA && Math.Abs(residualsDE[i]) <= outlierLimitDE) { m_Entries.Add(entries[i]); } else { RemovedOutliers++; } } m_RegressionRA = new LinearRegression(); m_RegressionDE = new LinearRegression(); foreach (var entry in m_Entries) { var midFrameTime = entry.TimeOfDayUTC - instDelayTimeOfDay; if (weighting == WeightingMode.None) { m_RegressionRA.AddDataPoint(midFrameTime, entry.RADeg); m_RegressionDE.AddDataPoint(midFrameTime, entry.DEDeg); } else { var weightRA = CalulateWeight(entry, entry.SolutionUncertaintyRACosDEArcSec); m_RegressionRA.AddDataPoint(midFrameTime, entry.RADeg, weightRA); var weightDE = CalulateWeight(entry, entry.SolutionUncertaintyDEArcSec); m_RegressionDE.AddDataPoint(midFrameTime, entry.DEDeg, weightDE); } } m_RegressionRA.Solve(); m_RegressionDE.Solve(); } else { m_RegressionRA = regRA; m_RegressionDE = regDE; m_Entries = entries.ToList(); } if (includePositionalUncertainties) { var posUncertaintyAveLst = new List <double>(); foreach (var entry in m_Entries) { var posUncertainty = entry.FWHMArcSec / (2.355 * entry.SNR); if (posUncertainty < m_MinSinglePositionUncertainty) { posUncertainty = m_MinSinglePositionUncertainty; } posUncertaintyAveLst.Add(posUncertainty); } var posUncertaintyMedian = posUncertaintyAveLst.Median(); m_PosUncertaintyMedArcSec = posUncertaintyMedian / Math.Sqrt(posUncertaintyAveLst.Count); } else { m_PosUncertaintyMedArcSec = null; } }
internal void Plot(Graphics g, Pen pen, int fullWidth, int fullHeight, double minY, double maxY, string motionName, double aveIncl, Func <CalculatedEntry, double> getVal, Func <CalculatedEntry, double> getWeight, Func <CalculatedEntry, double> getCalcVal, Func <FastMotionChunkPositionExtractor, double> getMidPointPos, Func <CalculatedEntry, double> rateCalcFactor) { g.Clear(SystemColors.ControlDarkDark); if (m_Chunks.Count == 0) { return; } float clientAreaWidth = fullWidth - PADDING_L - PADDING_R - 2 * BORDER; float clientAreaHeight = fullHeight - PADDING_L - PADDING_R - 2 * BORDER; g.DrawRectangle(SystemPens.ControlDark, PADDING_L, PADDING_R, clientAreaWidth + 2 * BORDER, clientAreaHeight + 2 * BORDER); double minX = m_Chunks.Min(x => x.MinTimeOfDayUTCInstrDelayApplied); double maxX = m_Chunks.Max(x => x.MaxTimeOfDayUTCInstrDelayApplied); float scaleX = (float)(clientAreaWidth / (maxX - minX)); float scaleY = (float)(clientAreaHeight / (maxY - minY)); var repX = new List <double>(); var repY = new List <double>(); double?motionRate = null; Pen constraintPen = new Pen(Color.FromArgb(100, pen.Color)); var allFrameNos = m_AllEntries.Select(x => x.FrameNo).ToList(); foreach (var chunk in m_Chunks) { double? startX = null; double? startYCalc = null; CalculatedEntry lastEntry = null; foreach (var entry in chunk.Entries) { if (!entry.IsConstraintPoint) { lastEntry = entry; } float x = (float)Math.Round(PADDING_L + BORDER + (scaleX * (entry.TimeOfDayUTCInstrDelayApplied - minX))); float y = fullHeight - PADDING_L - BORDER - (float)(scaleY * (getVal(entry) - minY)); if (!startX.HasValue && !entry.IsConstraintPoint) { startX = entry.TimeOfDayUTCInstrDelayApplied; startYCalc = getCalcVal(entry); } var entryPen = entry.IsConstraintPoint ? constraintPen : pen; g.DrawEllipse(entryPen, x - 1, y - 1, 2, 2); float yErr = (float)(scaleY * getWeight(entry)); if (yErr > 0) { g.DrawLine(entryPen, x, y - yErr, x, y + yErr); g.DrawLine(entryPen, x - 1, y - yErr, x + 1, y - yErr); g.DrawLine(entryPen, x - 1, y + yErr, x + 1, y + yErr); } if (allFrameNos.IndexOf(entry.FrameNo) == -1) { Trace.WriteLine(string.Format("ERROR: Frame number {0} has been already used in another chunk!", entry.FrameNo)); } else { allFrameNos.Remove(entry.FrameNo); } } // Plot fitted line if (lastEntry != null) { double endX = lastEntry.TimeOfDayUTCInstrDelayApplied; double endYCalc = getCalcVal(lastEntry); float x1 = (float)Math.Round(PADDING_L + BORDER + (scaleX * (startX.Value - minX))); float y1 = fullHeight - PADDING_L - BORDER - (float)(scaleY * (startYCalc.Value - minY)); float x2 = (float)Math.Round(PADDING_L + BORDER + (scaleX * (endX - minX))); float y2 = fullHeight - PADDING_L - BORDER - (float)(scaleY * (endYCalc - minY)); g.DrawLine(Pens.Azure, x1, y1, x2, y2); g.DrawLine(Pens.Azure, x1, y1 + 1, x2, y2 + 1); double fittedY = getMidPointPos(chunk); double midX = chunk.GetMidPointDelayCorrectedTimeOfDay(); float xf = (float)Math.Round(PADDING_L + BORDER + (scaleX * (midX - minX))); float yf = fullHeight - PADDING_L - BORDER - (float)(scaleY * (fittedY - minY)); g.FillEllipse(Brushes.Azure, xf - 3, yf - 3, 7, 7); repX.Add(midX); repY.Add(fittedY); var rate = Math.Abs((endYCalc - startYCalc.Value) * 3600.0 / ((endX - startX.Value) * SECONDS_IN_A_DAY)); if (!motionRate.HasValue || motionRate.Value < rate) { motionRate = rate * rateCalcFactor(lastEntry); } } } int allRemovedOutliers = m_Chunks.Sum(x => x.RemovedOutliers); // Plot total included points and outliers string statsText = m_RemoveOutliers ? string.Format("Included points: {0}, Excluded outliers: {1}", m_AllEntries.Count, allRemovedOutliers) : string.Format("Included points: {0}", m_AllEntries.Count); var szf = g.MeasureString(statsText, s_LegendFont); string rateText = null; SizeF rszf = SizeF.Empty; if (motionRate.HasValue) { rateText = string.Format("Rate({0}): {1:0.00}\"/sec", motionName, motionRate.Value); rszf = g.MeasureString(rateText, s_LegendFont); } bool topLeft = aveIncl > 0; if (topLeft) { g.FillRectangle(SystemBrushes.ControlDarkDark, PADDING_L + BORDER, PADDING_R + BORDER, szf.Width, szf.Height); g.DrawString(statsText, s_LegendFont, Brushes.Azure, PADDING_L + BORDER, PADDING_R + BORDER); g.FillRectangle(SystemBrushes.ControlDarkDark, fullWidth - PADDING_R - BORDER - rszf.Width, fullHeight - PADDING_L - BORDER - rszf.Height - 2, rszf.Width, rszf.Height); g.DrawString(rateText, s_LegendFont, Brushes.Azure, fullWidth - PADDING_R - BORDER - rszf.Width, fullHeight - PADDING_L - BORDER - rszf.Height - 2); } else { g.FillRectangle(SystemBrushes.ControlDarkDark, fullWidth - PADDING_R - BORDER - szf.Width, PADDING_R + BORDER, szf.Width, szf.Height); g.DrawString(statsText, s_LegendFont, Brushes.Azure, fullWidth - PADDING_R - BORDER - szf.Width, PADDING_R + BORDER); g.FillRectangle(SystemBrushes.ControlDarkDark, PADDING_L + BORDER, fullHeight - PADDING_L - BORDER - rszf.Height - 2, rszf.Width, rszf.Height); g.DrawString(rateText, s_LegendFont, Brushes.Azure, PADDING_L + BORDER, fullHeight - PADDING_L - BORDER - rszf.Height - 2); } // Calculate the StdDev of linear motion from all reported points and draw it on the plot if (repX.Count > 2) { var repReg = new LinearRegression(); for (int i = 0; i < repX.Count; i++) { repReg.AddDataPoint(repX[i], repY[i]); } repReg.Solve(); double stdDevArcSec = repReg.StdDev * 3600.0; string text = string.Format("StdDev from {0} measurements: {1:0.00} arcsec", repX.Count, stdDevArcSec); szf = g.MeasureString(text, s_LegendFont); if (topLeft) { g.FillRectangle(SystemBrushes.ControlDarkDark, PADDING_L + BORDER, PADDING_R + BORDER + szf.Height + 2, szf.Width, szf.Height); g.DrawString(text, s_LegendFont, Brushes.Azure, PADDING_L + BORDER, PADDING_R + BORDER + szf.Height + 2); } else { g.FillRectangle(SystemBrushes.ControlDarkDark, fullWidth - PADDING_R - BORDER - szf.Width, PADDING_R + BORDER + szf.Height + 2, szf.Width, szf.Height); g.DrawString(text, s_LegendFont, Brushes.Azure, fullWidth - PADDING_R - BORDER - szf.Width, PADDING_R + BORDER + szf.Height + 2); } } // Plot Axis Marks var minYArcSec = (long)Math.Ceiling(minY * 3600); var maxYArcSec = (long)Math.Floor(maxY * 3600); for (long wholeArcSec = minYArcSec; wholeArcSec <= maxYArcSec; wholeArcSec++) { int len = 1; if (wholeArcSec % 60 == 0) { len = 5; } else if (wholeArcSec % 10 == 0) { len = 3; } float y = fullHeight - PADDING_L - BORDER - (float)(scaleY * (wholeArcSec / 3600.0 - minY)); g.DrawLine(SystemPens.ControlDark, PADDING_L + 1, y, PADDING_L + 1 + len, y); g.DrawLine(SystemPens.ControlDark, fullWidth - PADDING_R - 1, y, fullWidth - PADDING_R - 1 - len, y); } var minXSeconds = (long)Math.Ceiling(minX * SECONDS_IN_A_DAY); var maxXSeconds = (long)Math.Floor(maxX * SECONDS_IN_A_DAY); for (long wholeSeconds = minXSeconds; wholeSeconds <= maxXSeconds; wholeSeconds++) { int len = 1; if (wholeSeconds % 60 == 0) { len = 5; } else if (wholeSeconds % 10 == 0) { len = 3; } float x = (float)Math.Round(PADDING_L + BORDER + (scaleX * (wholeSeconds / SECONDS_IN_A_DAY - minX))); g.DrawLine(SystemPens.ControlDark, x, fullHeight - PADDING_L - 1, x, fullHeight - PADDING_L - 1 - len); g.DrawLine(SystemPens.ControlDark, x, PADDING_R + 1, x, PADDING_R + 1 + len); } string axisText = "Time (sec)"; var axf = g.MeasureString(axisText, s_LegendFont); g.DrawString(axisText, s_LegendFont, Brushes.Azure, new PointF(PADDING_L + (clientAreaWidth - axf.Width) / 2, fullHeight - axf.Height)); axisText = string.Format("Motion {0} (arcsec)", motionName); axf = g.MeasureString(axisText, s_LegendFont); s_VerticalDrawFormat.Alignment = StringAlignment.Far; g.TranslateTransform(fullWidth, fullHeight); g.RotateTransform(180); g.DrawString(axisText, s_LegendFont, Brushes.Azure, new PointF(TITLE_PADDING + clientAreaWidth + axf.Height, (fullHeight + axf.Width) / 2), s_VerticalDrawFormat); }