-
Notifications
You must be signed in to change notification settings - Fork 61
Average profile #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Average profile #313
Changes from all commits
f82eadb
aea8375
fc97675
2c01f57
b8fdb79
91c4d4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; | ||
gr5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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(); | ||
| } | ||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ | |
| #include "zernikeprocess.h" | ||
| #include <QAbstractTableModel> | ||
| #include "plotcolor.h" | ||
| #include <QtGlobal> | ||
|
|
||
| extern double outputLambda; | ||
|
|
||
|
|
@@ -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.; | ||
|
|
@@ -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) { | ||
atsju marked this conversation as resolved.
Show resolved
Hide resolved
atsju marked this conversation as resolved.
Show resolved
Hide resolved
atsju marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clang-tidy diagnosticprofileplot.cpp:431:5: warning: [clang-analyzer-security.FloatLoopCounter]
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) {
| ^ ~~~ ~~~
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| 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 | ||
|
|
@@ -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") | ||
|
|
@@ -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(); | ||
|
|
@@ -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(); | ||
|
|
@@ -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){ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.