From f82eadb722fd0a048f408f305ee80799262e79e3 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Sun, 4 Jan 2026 21:26:37 -0600 Subject: [PATCH 1/5] reworked profile slope highlight and ploting obstruction --- DFTFringe.pro | 3 + DFTFringe_Dale.pro | 2 + profilecurve.cpp | 85 ++++++++++++++++++++++++++ profilecurve.h | 42 +++++++++++++ profileplot.cpp | 135 ++++++++++++++++++++++-------------------- profileplotpicker.cpp | 2 +- 6 files changed, 203 insertions(+), 66 deletions(-) create mode 100644 profilecurve.cpp create mode 100644 profilecurve.h diff --git a/DFTFringe.pro b/DFTFringe.pro index f722d1bd..f9b935fe 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -208,6 +208,8 @@ SOURCES += SingleApplication/singleapplication.cpp \ outlinestatsdlg.cpp \ pdfcalibrationdlg.cpp \ percentcorrectiondlg.cpp \ + profilecurve.cpp \ + profileplot.cpp \ pixelstats.cpp \ plotcolor.cpp \ profileplot.cpp \ @@ -330,6 +332,7 @@ HEADERS += bezier/bezier.h \ percentCorrectionSurface.h \ pixelstats.h \ plotcolor.h \ + profilecurve.h \ profileplot.h \ profileplotpicker.h \ ronchicomparedialog.h \ diff --git a/DFTFringe_Dale.pro b/DFTFringe_Dale.pro index e0c6cb34..24b5d0e7 100644 --- a/DFTFringe_Dale.pro +++ b/DFTFringe_Dale.pro @@ -45,6 +45,7 @@ SOURCES += main.cpp \ oglrendered.cpp \ pdfcalibrationdlg.cpp \ percentcorrectiondlg.cpp \ + profilecurve.cpp \ profileplot.cpp \ profileplotpicker.cpp \ ronchicomparedialog.cpp \ @@ -163,6 +164,7 @@ HEADERS += mainwindow.h \ pdfcalibrationdlg.h \ percentCorrectionSurface.h \ percentcorrectiondlg.h \ + profilecurve.h \ profileplot.h \ profileplotpicker.h \ ronchicomparedialog.h \ diff --git a/profilecurve.cpp b/profilecurve.cpp new file mode 100644 index 00000000..862e3612 --- /dev/null +++ b/profilecurve.cpp @@ -0,0 +1,85 @@ +#include "profilecurve.h" +#include +#include "profilecurve.h" +#include +#include +#include + +// creates a curve of the profile and will highlight segments where the slope is violated if the user has that feature enabled. +// uses the base class for most of the work. Overrides the actual painting of the line. +ProfileCurve::ProfileCurve(const QString &title) + : QwtPlotCurve(title) + , m_showSlopeError(false) + , m_slopeLimit(0.0) + , m_highlightWidth(2) +{ + // Ensure we are using basic line style for the base profile + setStyle(QwtPlotCurve::Lines); +} + +void ProfileCurve::setSlopeSettings(bool show, double limit, int highlightWidth) +{ + m_showSlopeError = show; + m_slopeLimit = limit; + m_highlightWidth = highlightWidth; +} + +#include // Ensure this is at the top of profilecurve.cpp + +#include +#include + +void ProfileCurve::drawLines(QPainter *painter, + const QwtScaleMap &xMap, + const QwtScaleMap &yMap, + const QRectF &canvasRect, + int from, int to) const +{ + if (to <= from) return; + + painter->save(); + + // 1. MANUALLY DRAW THE BASE BLACK LINE (Replacing Qwt's default) + // This prevents Qwt from drawing its own spider lines. + QPen basePen = pen(); + painter->setPen(basePen); + + for (int i = from; i < to; ++i) { + QPointF p1 = sample(i); + QPointF p2 = sample(i + 1); + + // If either point is NaN, skip this segment entirely (lifts the pen) + if (std::isnan(p1.y()) || std::isnan(p2.y())) continue; + + // Draw the segment only between two valid points + double x1 = xMap.transform(p1.x()); + double y1 = yMap.transform(p1.y()); + double x2 = xMap.transform(p2.x()); + double y2 = yMap.transform(p2.y()); + + painter->drawLine(QPointF(x1, y1), QPointF(x2, y2)); + } + + // 2. DRAW THE ORANGE HIGHLIGHTS + if (m_showSlopeError && m_slopeLimit > 0.0) { + QPen orangePen(QColor("orange"), m_highlightWidth); + orangePen.setCapStyle(Qt::RoundCap); + painter->setPen(orangePen); + + for (int i = from; i < to; ++i) { + QPointF p1 = sample(i); + QPointF p2 = sample(i + 1); + + if (std::isnan(p1.y()) || std::isnan(p2.y())) continue; + + if (std::abs(p1.y() - p2.y()) > m_slopeLimit) { + painter->drawLine( + QPointF(xMap.transform(p1.x()), yMap.transform(p1.y())), + QPointF(xMap.transform(p2.x()), yMap.transform(p2.y())) + ); + } + } + } + + painter->restore(); +} diff --git a/profilecurve.h b/profilecurve.h new file mode 100644 index 00000000..50848682 --- /dev/null +++ b/profilecurve.h @@ -0,0 +1,42 @@ +#ifndef PROFILECURVE_H +#define PROFILECURVE_H + +#include +#include +#include + +/** + * @brief The ProfileCurve class + * A custom QwtPlotCurve that automatically highlights segments where the + * vertical delta between adjacent points exceeds a specified threshold. + */ +class ProfileCurve : public QwtPlotCurve +{ +public: + explicit ProfileCurve(const QString &title = QString()); + + /** + * @brief Configure the slope highlighting behavior + * @param show - Toggle the orange highlight on/off + * @param limit - The vertical delta threshold (hDelLimit) + * @param highlightWidth - Width of the orange pen + */ + void setSlopeSettings(bool show, double limit, int highlightWidth = 2); + +protected: + /** + * @brief Overrides the standard line drawing to add the highlight pass + */ + virtual void drawLines(QPainter *painter, + const QwtScaleMap &xMap, + const QwtScaleMap &yMap, + const QRectF &canvasRect, + int from, int to) const override; + +private: + bool m_showSlopeError; + double m_slopeLimit; + int m_highlightWidth; +}; + +#endif // PROFILECURVE_H diff --git a/profileplot.cpp b/profileplot.cpp index ad6a5acd..d3f6f991 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -50,6 +50,7 @@ #include "zernikeprocess.h" #include #include "plotcolor.h" +#include extern double outputLambda; @@ -62,6 +63,8 @@ extern double outputLambda; #include #include #include "percentcorrectiondlg.h" +#include "profilecurve.h" + #define DEGTORAD M_PI/180.; double g_angle = 270. * DEGTORAD; //start at 90 deg (pointing east) double y_offset = 0.; @@ -406,95 +409,82 @@ QPolygonF ProfilePlot::createAverageProfile(double /*umnits*/, wavefront * /*wf* return avg; } -QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffset){ + + +// creates set of points for a profile at a given angle. Adds Nan values to points in the obstruction or outside the mirror +// THe angle for the diameter is given in a global. +QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffset) { QPolygonF points; mirrorDlg &md = *mirrorDlg::get_Instance(); - double steps = 1./wf->m_outside.m_radius; - double offset = y_offset; - if (!allowOffset) offset = 0.; - double radius = md.m_clearAperature/2.; - double obs_radius = md.obs/2.; - if (m_displayInches){ + // 1. Setup constants + double steps = 1.0 / wf->m_outside.m_radius; + double offset = allowOffset ? y_offset : 0.0; + double radius = md.m_clearAperature / 2.0; + double obs_radius = md.obs / 2.0; + + if (m_displayInches) { obs_radius /= 25.4; } - for (double rad = -1.; rad < 1.; rad += steps){ - int dx, dy; + // Main Sampling Loop + for (double rad = -1.0; rad < 1.0; rad += steps) { double radn = rad * wf->m_outside.m_radius; double radx = rad * radius; - if (m_displayInches){ - radx /= 25.4; - } - double e = 1.; - if (m_displayPercent){ - radx = 100. * radx/radius; - obs_radius = 100 *( md.obs/2)/radius; - } + if (m_displayInches) radx /= 25.4; - if (md.isEllipse()){ - e = md.m_verticalAxis/md.diameter; + if (m_displayPercent) { + radx = 100.0 * radx / radius; + obs_radius = 100.0 * (md.obs / 2.0) / radius; } - dx = radn * cos(g_angle + M_PI_2) + wf->m_outside.m_center.x(); - dy = -radn * e * sin(g_angle + M_PI_2) + wf->m_outside.m_center.y(); - if (dy >= wf->data.rows || dx >= wf->data.cols || dy < 0 || dx < 0){ - continue; - + double e = 1.0; + if (md.isEllipse()) { + e = md.m_verticalAxis / md.diameter; } + // Calculate matrix coordinates + int dx = radn * cos(g_angle + M_PI_2) + wf->m_outside.m_center.x(); + int dy = -radn * e * sin(g_angle + M_PI_2) + wf->m_outside.m_center.y(); - if (abs(radx) < obs_radius){ - points << QPointF(radx,0.0); + // Boundary Check: Ignore points outside the matrix + if (dy >= wf->data.rows || dx >= wf->data.cols || dy < 0 || dx < 0) { continue; } - if (wf->workMask.at(dy,dx)){ - double defocus = 0.; - - if (m_defocus_mode){ - defocus = (m_defocusValue)* (-1. + 2. * rad * rad); - points << QPointF(radx,(units * (wf->workData((int)dy,(int)dx) + defocus ) * - wf->lambda/outputLambda) +offset * units); - } - else { - - points << QPointF(radx,(units * (wf->workData((int)dy,(int)dx) ) * - wf->lambda/outputLambda) +offset * units); - - } + // Handle the obstruction (The Hole) + if (std::abs(radx) < obs_radius) { + // Only add a NaN if the very last point was a real number. + // This "lifts the pen" once and then skips the rest of the hole. + if (!points.isEmpty() && !std::isnan(points.last().y())) { + points << QPointF(radx, qQNaN()); } - //else points << QPointF(radx,0.0); - } - - if (m_showSlopeError){ - - double arcsecLimit = (slopeLimitArcSec/3600) * M_PI/180; - double xDel = points[0].x() - points[1].x(); - double hDelLimit =m_showNm * m_showSurface * ((outputLambda/m_wf->lambda)*fabs(xDel * tan(arcsecLimit)) /(outputLambda * 1.e-6)); + continue; + } - for (int i = 0; i < points.size() - 1; ++i){ - double hdel = (points[i].y()- points[i+1].y()); - if (fabs(points[i].x()) < obs_radius || fabs(points[i+1].x()) < obs_radius) - continue; - if (fabs(hdel) > hDelLimit){ + // Mask and Data Processing + if (wf->workMask.at(dy, dx)) { + double val = (units * (wf->workData(dy, dx)) * wf->lambda / outputLambda) + (offset * units); - QVector pts; - QwtPlotCurve *limitCurve = new QwtPlotCurve; - pts<< points[i] << points[i+1]; - limitCurve->setSamples(pts); + if (m_defocus_mode) { + double defocus = (m_defocusValue) * (-1.0 + 2.0 * rad * rad); + val += (units * defocus * wf->lambda / outputLambda); + } - limitCurve->setPen(QPen(QColor("orange"),Settings2::m_profile->slopeErrorWidth())); - limitCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol,false ); - limitCurve->setLegendAttribute(QwtPlotCurve::LegendShowLine,false ); - limitCurve->setItemAttribute(QwtPlotCurve::Legend,false); - limitCurve->attach( m_plot); + points << QPointF(radx, val); + } else { + // Lift the pen if we hit a mask gap outside the center hole + if (!points.isEmpty() && !std::isnan(points.last().y())) { + points << QPointF(radx, qQNaN()); } } } + return points; } + + // create a smoothed wave front with only spherical terms. // Use that to get zernike values to send to percent completion feature // display the profile and then send the zerns to percent completion @@ -660,6 +650,8 @@ qDebug() << "Populate"; double smoothing = settings.value("GBValue", 20).toInt(); m_plot->detachItems(QwtPlotItem::Rtti_PlotTextLabel); + + if (m_wf->m_outside.m_radius > 0 && settings.value("GBlur", false).toBool()){ double val = .01 * (m_wf->diameter) * smoothing; QString t = QString("Surface Smoothing diameter %1% of surface diameter %2 mm") @@ -694,6 +686,7 @@ qDebug() << "Populate"; m_plot->detachItems( QwtPlotItem::Rtti_PlotCurve); m_plot->detachItems( QwtPlotItem::Rtti_PlotMarker); + double arcsecLimit = (slopeLimitArcSec/3600) * M_PI/180; m_plot->insertLegend( new QwtLegend() , QwtPlot::BottomLegend); surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); @@ -704,7 +697,7 @@ qDebug() << "Populate"; wavefront* wf = wfs->at(list[i]); QStringList path = wf->name.split("/"); QString name = path.last().replace(".wft",""); - QwtPlotCurve *cprofile = new QwtPlotCurve(name ); + ProfileCurve *cprofile = new ProfileCurve(name ); int width = Settings2::m_profile->lineWidth(); if (name == m_wf->name.split("/").last().replace(".wft","")) width = Settings2::m_profile->selectedWidth(); @@ -719,11 +712,23 @@ qDebug() << "Populate"; y_offset = m_waveFrontyOffsets[name + " avg"]; qDebug() << "using avg"; } -qDebug() << "offsets" << m_waveFrontyOffsets<< y_offset; + // if show one angle if (m_show_oneAngle or (!m_showAvg and !m_show_16_diameters and !m_show_oneAngle)){ - cprofile->setSamples( createProfile( units,wf, true)); + QPolygonF points = createProfile(units, wf, true); + if (points.size() >= 2) { + // Distance between two samples + double xDel = fabs(points[0].x() - points[1].x()); + + // Recalculate hDelLimit using this specific xDel + double hDelLimit = m_showNm * m_showSurface * ((outputLambda/m_wf->lambda) * fabs(xDel * tan(arcsecLimit)) / (outputLambda * 1.e-6)); + + cprofile->setSlopeSettings(m_showSlopeError, hDelLimit, Settings2::m_profile->slopeErrorWidth()); + } + cprofile->setSamples(points); + + cprofile->attach( m_plot ); } if (m_show_16_diameters){ diff --git a/profileplotpicker.cpp b/profileplotpicker.cpp index e09ebccb..f37ef83d 100644 --- a/profileplotpicker.cpp +++ b/profileplotpicker.cpp @@ -111,7 +111,7 @@ void profilePlotPicker::select( QPoint pos ) if ( ( *it )->rtti() == QwtPlotItem::Rtti_PlotCurve ) { QwtPlotCurve *c = static_cast( *it ); - + if (c->title().text() == "slope") continue; double d; int idx = c->closestPoint( pos, &d ); if ( d < dist ) From fc9767576eac2b31aa6d0fdc187535f7f74a1b54 Mon Sep 17 00:00:00 2001 From: gr5 Date: Mon, 5 Jan 2026 11:00:28 -0500 Subject: [PATCH 2/5] Update DFTFringe.pro Co-authored-by: Julien Staub --- DFTFringe.pro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DFTFringe.pro b/DFTFringe.pro index f9b935fe..4adcc4e2 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -208,10 +208,9 @@ SOURCES += SingleApplication/singleapplication.cpp \ outlinestatsdlg.cpp \ pdfcalibrationdlg.cpp \ percentcorrectiondlg.cpp \ - profilecurve.cpp \ - profileplot.cpp \ pixelstats.cpp \ plotcolor.cpp \ + profilecurve.cpp \ profileplot.cpp \ profileplotpicker.cpp \ ronchicomparedialog.cpp \ From 2c01f571183aa25b58334d31516014dcc05dc13d Mon Sep 17 00:00:00 2001 From: gr5 Date: Mon, 5 Jan 2026 11:00:41 -0500 Subject: [PATCH 3/5] Update profilecurve.cpp Co-authored-by: Julien Staub --- profilecurve.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/profilecurve.cpp b/profilecurve.cpp index 862e3612..46bd726d 100644 --- a/profilecurve.cpp +++ b/profilecurve.cpp @@ -35,6 +35,7 @@ void ProfileCurve::drawLines(QPainter *painter, const QRectF &canvasRect, int from, int to) const { + Q_UNUSED(canvasRect); // drawLines is an override function, we shall not change args if (to <= from) return; painter->save(); From b8fdb79d228c3588ae059499d7fd3af3721387c0 Mon Sep 17 00:00:00 2001 From: gr5 Date: Mon, 5 Jan 2026 11:00:47 -0500 Subject: [PATCH 4/5] Update profileplot.cpp Co-authored-by: Julien Staub --- profileplot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/profileplot.cpp b/profileplot.cpp index d3f6f991..47bcd5a4 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -411,8 +411,8 @@ QPolygonF ProfilePlot::createAverageProfile(double /*umnits*/, wavefront * /*wf* } -// creates set of points for a profile at a given angle. Adds Nan values to points in the obstruction or outside the mirror -// THe angle for the diameter is given in a global. +// creates set of points for a profile at a given angle. Adds NaN values to points in the obstruction or outside the mirror +// Tee angle for the diameter is given in a global. QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffset) { QPolygonF points; mirrorDlg &md = *mirrorDlg::get_Instance(); From 91c4d4bcc7631e4341250c944b092b9ceeac3bcb Mon Sep 17 00:00:00 2001 From: Julien Staub Date: Mon, 5 Jan 2026 18:03:58 +0100 Subject: [PATCH 5/5] fix QT5 build and add const --- DFTFringe_QT5.pro | 2 ++ profileplot.cpp | 2 +- profileplot.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DFTFringe_QT5.pro b/DFTFringe_QT5.pro index e7c3fbdf..79b3336e 100644 --- a/DFTFringe_QT5.pro +++ b/DFTFringe_QT5.pro @@ -209,6 +209,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ percentcorrectiondlg.cpp \ pixelstats.cpp \ plotcolor.cpp \ + profilecurve.cpp \ profileplot.cpp \ profileplotpicker.cpp \ ronchicomparedialog.cpp \ @@ -329,6 +330,7 @@ HEADERS += bezier/bezier.h \ percentCorrectionSurface.h \ pixelstats.h \ plotcolor.h \ + profilecurve.h \ profileplot.h \ profileplotpicker.h \ ronchicomparedialog.h \ diff --git a/profileplot.cpp b/profileplot.cpp index 47bcd5a4..ce1a7e57 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -413,7 +413,7 @@ QPolygonF ProfilePlot::createAverageProfile(double /*umnits*/, wavefront * /*wf* // creates set of points for a profile at a given angle. Adds NaN values to points in the obstruction or outside the mirror // Tee angle for the diameter is given in a global. -QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffset) { +QPolygonF ProfilePlot::createProfile(double units, const wavefront *wf, bool allowOffset) { QPolygonF points; mirrorDlg &md = *mirrorDlg::get_Instance(); diff --git a/profileplot.h b/profileplot.h index e797b68a..26da92d4 100644 --- a/profileplot.h +++ b/profileplot.h @@ -49,7 +49,7 @@ class ProfilePlot : public QWidget QVector *wfs; void setSurface(wavefront * wf); virtual void resizeEvent( QResizeEvent * ); - QPolygonF createProfile(double units, wavefront *wf, bool allowOffset = true); + QPolygonF createProfile(double units, const wavefront *wf, bool allowOffset = true); QPolygonF createAverageProfile(double umnits, wavefront *wf, bool removeNull); ContourTools *m_tools; double m_waveRange;