Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DFTFringe.pro
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ SOURCES += SingleApplication/singleapplication.cpp \
percentcorrectiondlg.cpp \
pixelstats.cpp \
plotcolor.cpp \
profilecurve.cpp \
profileplot.cpp \
profileplotpicker.cpp \
ronchicomparedialog.cpp \
Expand Down Expand Up @@ -330,6 +331,7 @@ HEADERS += bezier/bezier.h \
percentCorrectionSurface.h \
pixelstats.h \
plotcolor.h \
profilecurve.h \
profileplot.h \
profileplotpicker.h \
ronchicomparedialog.h \
Expand Down
2 changes: 2 additions & 0 deletions DFTFringe_Dale.pro
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ SOURCES += main.cpp \
oglrendered.cpp \
pdfcalibrationdlg.cpp \
percentcorrectiondlg.cpp \
profilecurve.cpp \
profileplot.cpp \
profileplotpicker.cpp \
ronchicomparedialog.cpp \
Expand Down Expand Up @@ -163,6 +164,7 @@ HEADERS += mainwindow.h \
pdfcalibrationdlg.h \
percentCorrectionSurface.h \
percentcorrectiondlg.h \
profilecurve.h \
profileplot.h \
profileplotpicker.h \
ronchicomparedialog.h \
Expand Down
2 changes: 2 additions & 0 deletions DFTFringe_QT5.pro
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ SOURCES += SingleApplication/singleapplication.cpp \
percentcorrectiondlg.cpp \
pixelstats.cpp \
plotcolor.cpp \
profilecurve.cpp \
profileplot.cpp \
profileplotpicker.cpp \
ronchicomparedialog.cpp \
Expand Down Expand Up @@ -329,6 +330,7 @@ HEADERS += bezier/bezier.h \
percentCorrectionSurface.h \
pixelstats.h \
plotcolor.h \
profilecurve.h \
profileplot.h \
profileplotpicker.h \
ronchicomparedialog.h \
Expand Down
86 changes: 86 additions & 0 deletions profilecurve.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include "profilecurve.h"
#include <QPainter>
#include "profilecurve.h"
#include <qpainter.h>
#include <qpainterpath.h>
#include <qwt_scale_map.h>

// 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 <QPainterPath> // Ensure this is at the top of profilecurve.cpp

#include <qwt_scale_map.h>
#include <qpainter.h>

void ProfileCurve::drawLines(QPainter *painter,
const QwtScaleMap &xMap,
const QwtScaleMap &yMap,
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();

// 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();
}
42 changes: 42 additions & 0 deletions profilecurve.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef PROFILECURVE_H
#define PROFILECURVE_H

#include <qwt_plot_curve.h>
#include <QPen>
#include <cmath>

/**
* @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
135 changes: 70 additions & 65 deletions profileplot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "zernikeprocess.h"
#include <QAbstractTableModel>
#include "plotcolor.h"
#include <QtGlobal>

extern double outputLambda;

Expand All @@ -62,6 +63,8 @@
#include <algorithm>
#include <map>
#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.;
Expand Down Expand Up @@ -406,95 +409,82 @@

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
// Tee angle for the diameter is given in a global.
QPolygonF ProfilePlot::createProfile(double units, const 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) {

Check warning on line 431 in profileplot.cpp

View workflow job for this annotation

GitHub Actions / build-linux (ubuntu-24.04)

profileplot.cpp:431:5 [clang-analyzer-security.FloatLoopCounter]

Variable 'rad' with floating point type 'double' should not be used as a loop counter
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostic

profileplot.cpp:431:5: warning: [clang-analyzer-security.FloatLoopCounter]

Variable 'rad' with floating point type 'double' should not be used as a loop counter

  431 |     for (double rad = -1.0; rad < 1.0; rad += steps) {
      |     ^                       ~~~        ~~~
profileplot.cpp:431:5: note: Variable 'rad' with floating point type 'double' should not be used as a loop counter
  431 |     for (double rad = -1.0; rad < 1.0; rad += steps) {
      |     ^                       ~~~        ~~~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine as is. I don't think the outer radius (wf->m_outside.m_radius) is specified in an integral number of pixels anyway - that's a float that can be for example 300.5 (it can be integral or on the halves).

Yes you could have a fp summation rounding error and get one too few in the profile plot. At the end of the plot. At this time I don't think it's a problem (I don't think you can get one too many as there is also code to see if we go beyond the outer radius).

What I don't love is that we aren't grabbing the nearest wavefront point under the line but rounded down to the nearest integer: wf->workData(dy, dx). Dy could be 100.9 and we get the point from dy position 100.

Ideally do a weighted average of the nearest 4 points, weighted by the distance to each point. And handle edge cases where some of the 4 points are outside the mask. We do all that when we rotate wavefronts if I remember right.

But really it's all fine as is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes 0.5 pixels. This makes it more difficult to "fix" than first reading.
OK for me.

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<bool>(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<bool>(dy, dx)) {
double val = (units * (wf->workData(dy, dx)) * wf->lambda / outputLambda) + (offset * units);

QVector<QPointF> 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
Expand Down Expand Up @@ -660,6 +650,8 @@
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")
Expand Down Expand Up @@ -694,6 +686,7 @@
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();
Expand All @@ -704,7 +697,7 @@
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();
Expand All @@ -719,11 +712,23 @@
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){
Expand Down
Loading
Loading