From 9f092e887792c0f3341699e5a6d2c8159930c97f Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Fri, 12 Dec 2025 04:39:03 -0600 Subject: [PATCH 01/13] Changed how profiles are selected and displayed. Added profile line width in preferences --- profileplot.cpp | 387 +++++++++++++++++++++------------ profileplot.h | 9 +- profileplotpicker.cpp | 4 +- settingsprofile.cpp | 30 ++- settingsprofile.h | 6 + settingsprofile.ui | 487 ++++++++++++++++++++++-------------------- 6 files changed, 543 insertions(+), 380 deletions(-) diff --git a/profileplot.cpp b/profileplot.cpp index 3a75b537..763065c1 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -56,7 +56,11 @@ extern double outputLambda; #include #include #include + +#include #include +#include +#include #include "percentcorrectiondlg.h" #define DEGTORAD M_PI/180.; double g_angle = 270. * DEGTORAD; //start at 90 deg (pointing east) @@ -84,14 +88,7 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): QVBoxLayout *v1 = new QVBoxLayout(); showNmCB = new QCheckBox("Show in Nanometers",this); showSurfaceCB = new QCheckBox("Surface error"); - Show16 = new QRadioButton("16 diameters of current wavefront",this); - OneOnly = new QRadioButton("one diameter of current wavefront",this); - OneOnly->setChecked(true); - ShowAll = new QRadioButton("All wavefronts",this); - - connect(Show16, &QAbstractButton::clicked, this, &ProfilePlot::show16); - connect(OneOnly, &QAbstractButton::clicked, this, &ProfilePlot::showOne); - connect(ShowAll, &QAbstractButton::clicked, this, &ProfilePlot::showAll); + l1->addStretch(); showSlopeError = new QCheckBox("Show Slope: "); @@ -126,16 +123,14 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): // QDoubleSpinBox::valueChanged is overloaded in Qt5 connect(slopeLimitSB, QOverload::of(&QDoubleSpinBox::valueChanged), this, &ProfilePlot::slopeLimit); #endif - // + // connect(showSlopeError,&QAbstractButton::clicked, this, &ProfilePlot::showSlope); l1->addWidget(showSlopeError); l1->addWidget(slopeLimitSB); l1->addWidget(showNmCB); l1->addWidget(showSurfaceCB); - l1->addWidget(OneOnly); - l1->addWidget(Show16); - l1->addWidget(ShowAll); + l1->addStretch(); v1->addLayout(l1); v1->addWidget(m_plot,10); @@ -205,13 +200,13 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): // new QwtCompassMagnetNeedle( QwtCompassMagnetNeedle::ThinStyle ) ); compass->setValue( 270 ); compass->setOrigin( -90 ); - + // Using the old SIGNAL/SLOT syntax because problems with QWT. // Qt is not able to match signal at runtime even if compile time checks all passed. // ChatGPT tells it might be an ABI problem with QWT library but I (JST) have been unable to fix for now (2025-10-20). connect(compass, SIGNAL(valueChanged(double)), this ,SLOT(angleChanged(double))); //connect(compass,&QwtAbstractSlider::valueChanged,this ,&ProfilePlot::angleChanged); - + connect(m_tools, &ContourTools::newDisplayErrorRange, this, &ProfilePlot::newDisplayErrorRange); connect(m_tools, &ContourTools::contourZeroOffsetChanged, this, &ProfilePlot::zeroOffsetChanged); @@ -479,6 +474,7 @@ QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffs } 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)); @@ -494,12 +490,11 @@ QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffs pts<< points[i] << points[i+1]; limitCurve->setSamples(pts); - limitCurve->setPen(QPen(QColor("orange"),4)); + 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); - } } } @@ -547,6 +542,100 @@ void ProfilePlot::make_correction_graph(){ m_pcdlg->setData(surfs); } + + + + + + +// Struct to hold the sum of heights and the count for each distance bin +struct RadialBin { + double sum = 0.0; + int count = 0; +}; +// the following code was generated by AI and may be used in the future to show the average profile +/** + * @brief Computes the average height profile along all radial lines, + * returning a vector where index r is the average height at radius r. + * It assumes the input matrix is of type CV_64FC1 (double). + * * @param matrix The 2D matrix (cv::Mat, expected CV_64FC1). + * @param center The center point of the circular surface (cv::Point). + * @param radius The radius of the circular surface in pixels. + * @return A std::vector where index r contains the average height at integer radius r. + */ +std::vector compute_average_radial_profile( + const cv::Mat& matrix, + const cv::Point& center, + int radius) +{ + // Check matrix validity + if (matrix.empty() || matrix.channels() != 1 || matrix.type() != CV_64FC1) { + qDebug() << "Error: Matrix must be non-empty, single-channel, and of type CV_64FC1."; + return {}; + } + + int rows = matrix.rows; + int cols = matrix.cols; + + double center_x = (double)center.x; + double center_y = (double)center.y; + + // 1. Define the Bounding Box (ROI) for optimized iteration + int min_y = std::max(0, center.y - radius); + int max_y = std::min(rows - 1, center.y + radius); + int min_x = std::max(0, center.x - radius); + int max_x = std::min(cols - 1, center.x + radius); + + // Use a map to consolidate sums and counts for all points grouped by + // their final integer radius (r = round(sqrt(r^2))) + // Key: Integer Radius (r). Value: RadialBin struct. + std::map radial_bins_by_int_radius; + + // 2. Optimized Iteration and Binning + for (int y = min_y; y <= max_y; ++y) { + for (int x = min_x; x <= max_x; ++x) { + + double dx = x - center_x; + double dy = y - center_y; + + double dist_sq_float = dx * dx + dy * dy; + + // Only process points within the defined radius + if (dist_sq_float <= (double)radius * radius) { + + double value = matrix.at(y, x); + double radius_actual = std::sqrt(dist_sq_float); + + // Determine the bin index by rounding the actual radius + int profile_index = static_cast(std::round(radius_actual)); + + // Ensure the index doesn't exceed the intended maximum radius R + if (profile_index <= radius) { + radial_bins_by_int_radius[profile_index].sum += value; + radial_bins_by_int_radius[profile_index].count += 1; + } + } + } + } + + // 3. Compute the Final Profile Vector + // Size is radius + 1 (for indices 0 up to radius R) + std::vector radial_profile(radius + 1, 0.0); + + for (const auto& pair : radial_bins_by_int_radius) { + int r_index = pair.first; + const RadialBin& bin = pair.second; + + // Calculate the average height for this integer radius bin + double average_height = bin.sum / bin.count; + + // Store the result directly at the integer radius index + radial_profile[r_index] = average_height; + } + + return radial_profile; +} + void ProfilePlot::populate() { @@ -611,136 +700,120 @@ void ProfilePlot::populate() m_plot->detachItems( QwtPlotItem::Rtti_PlotMarker); + m_plot->insertLegend( new QwtLegend() , QwtPlot::BottomLegend); + surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); + QList list = saTools->SelectedWaveFronts(); - switch (type) { - case ProfileType::SHOW_ONE:{ - QStringList path = wfs->at(0)->name.split("/"); - QString name; - int l = path.length(); - if (l >= 2){ - name = path[l-2] + "/" + path[l-1]; - } - else - name = wfs->at(0)->name; - QwtPlotCurve *cprofile = new QwtPlotCurve( name ); - cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); - //m_plot->insertLegend( new QwtLegend() , QwtPlot::BottomLegend); - //cprofile->setLegendAttribute( QwtPlotCurve::LegendShowLine, false ); - cprofile->setPen( Qt::black ); - cprofile->attach( m_plot ); - QPolygonF points = createProfile( m_showNm * m_showSurface,m_wf); - - cprofile->setSamples( points ); - - break; - } + for (int i = 0; i < list.size(); ++i){ + wavefront* wf = wfs->at(list[i]); + QStringList path = wf->name.split("/"); + QString name = path.last().replace(".wft",""); + QwtPlotCurve *cprofile = new QwtPlotCurve(name ); + int width = Settings2::m_profile->lineWidth(); + if (name == m_wf->name.split("/").last().replace(".wft","")) + width = Settings2::m_profile->selectedWidth(); + cprofile->setLegendIconSize(QSize(50,20)); + cprofile->setPen(QPen(Settings2::m_profile->getColor(i),width)); + cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); + + double units = m_showNm * m_showSurface; - case ProfileType::SHOW_16: { - surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); - QList list = saTools->SelectedWaveFronts(); + // if not show 16 diam + if (!(m_show_16_diameters or m_showOnlyAvg)){ + cprofile->setSamples( createProfile( units,wf, true)); + cprofile->attach( m_plot ); + } + else { + // compute 16 diameters + // if only one wave front is selected then use blue for the average QColor penColor = QColor("blue"); - for (int indx = 0; indx < list.size(); ++indx){ - if (indx > 0) penColor = QColor(plotColors[indx % 10]); - QPolygonF avg; - QString t = "Average of all 16 diameters"; - QwtText title(t); - title.setRenderFlags( Qt::AlignHCenter | Qt::AlignBottom ); - - QFont font; - font.setPointSize(12); - title.setFont( font ); - title.setColor(Qt::blue); - QwtPlotTextLabel *titleItem = new QwtPlotTextLabel(); - titleItem->setText( title ); - titleItem->attach( m_plot ); - - double startAngle = g_angle; - QPolygonF sum; - QMap count; - for (int i = 0; i < 16; ++i){ - QPolygonF points; - g_angle = startAngle + i * M_PI/ 16; - - QwtPlotCurve *cprofile = new QwtPlotCurve( ); - cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); - cprofile->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); - cprofile->setPen( Qt::black ); - - points = createProfile( m_showNm * m_showSurface,wfs->at(list[indx])); - if (i == 0) { - sum = points; - for (int j = 0; j < sum.length(); ++j) - count[j] = 1; - } - else { - for(int j = 0; j < fmin(sum.length(),points.length());++j){ - sum[j].ry() += points[j].y();; + // if more than one wave front is selected then used color table. + penColor = QColor(plotColors[i % 10]); + + + + QString t = "Average of all 16 diameters"; + QwtText title(t); + title.setRenderFlags( Qt::AlignHCenter | Qt::AlignBottom ); + + QFont font; + font.setPointSize(12); + title.setFont( font ); + title.setColor(Qt::blue); + QwtPlotTextLabel *titleItem = new QwtPlotTextLabel(); + titleItem->setText( title ); + //titleItem->attach( m_plot ); + + double startAngle = g_angle; + QPolygonF sum; + QMap count; + for (int idx = 0; idx < 16; ++idx){ + QPolygonF points; + g_angle = startAngle + idx * M_PI/ 16; + + QwtPlotCurve *cprofile = new QwtPlotCurve( ); + cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); + cprofile->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); + cprofile->setItemAttribute(QwtPlotItem::Legend, false); + cprofile->setPen( QColor(plotColors[i % 10])); + + points = createProfile( m_showNm * m_showSurface,wf); + if (i == 0) { + sum = points; + for (int j = 0; j < sum.length(); ++j) + count[j] = 1; + } + else { + for(int j = 0; j < fmin(sum.length(),points.length());++j){ + sum[j].ry() += points[j].y();; - if (count.contains(j)) count[j] += 1 ; - else count[j] = 1; - } + if (count.contains(j)) count[j] += 1 ; + else count[j] = 1; } - - cprofile->setSamples( points); - cprofile->attach( m_plot ); - } - - // plot the average profile - int i = 0; - foreach(QPointF p, sum){ - avg << QPointF(p.x(),p.y()/(count[i++])); + if (!m_showOnlyAvg){ + cprofile->setSamples( points); + cprofile->attach( m_plot ); } - QString name("average"); - - QwtPlotCurve *cprofileavg = new QwtPlotCurve( name); - cprofileavg->setRenderHint( QwtPlotItem::RenderAntialiased ); - cprofileavg->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); - cprofileavg->setLegendIconSize(QSize(50,20)); - cprofileavg->setPen( QPen(penColor,5) ); - cprofileavg->setSamples( avg); - cprofileavg->attach( m_plot ); - g_angle = startAngle; - } + // plot the average profile + std::vector avgRadius = compute_average_radial_profile(wf->workData, + cv::Point(wf->m_outside.m_center.x(),wf->m_outside.m_center.y()), wf->m_outside.m_radius); - break; - } - case ProfileType::SHOW_ALL:{ + QPolygonF right; //right half of profile - m_plot->insertLegend( new QwtLegend() , QwtPlot::BottomLegend); - surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); - QList list = saTools->SelectedWaveFronts(); - if (list.size() <2){ - list.clear(); - for (int i = 0; i < wfs->size(); ++i){ - list << i; - } - } - for (int i = 0; i < list.size(); ++i){ - QStringList path = wfs->at(list[i])->name.split("/"); - QString name = path.last().replace(".wft",""); -qDebug() << "name " << name; - QwtPlotCurve *cprofile = new QwtPlotCurve(name ); - int width = Settings2::m_profile->lineWidth(); - if (name == m_wf->name.split("/").last().replace(".wft","")) - width = Settings2::m_profile->selectedWidth(); - cprofile->setLegendIconSize(QSize(50,20)); - cprofile->setPen(QPen(Settings2::m_profile->getColor(i),width)); - cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); - cprofile->setSamples( createProfile( m_showNm * m_showSurface,wfs->at(list[i]), i==0)); - cprofile->attach( m_plot ); - } + for (size_t i = 0; i < avgRadius.size(); ++i) { + double rr = (double)(i)/avgRadius.size() * wf->diameter/2.; + double val = (units * avgRadius[i] * wf->lambda/outputLambda) + y_offset * units; - break; + right << QPointF(rr, val); + } + // make left have of profile + QPolygonF left = right; + std::reverse(left.begin(), left.end()); + left.pop_back(); + // Create a transform that scales the X axis by -1 and the Y axis by 1 (no scaling) + QTransform transform; + transform.scale(-1.0, 1.0); + + // Apply the transform to the polygon + left = transform.map(left); + left.append(right); + + QwtPlotCurve *cprofileavg = new QwtPlotCurve( name + " avg"); + cprofileavg->setRenderHint( QwtPlotItem::RenderAntialiased ); + cprofileavg->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); + cprofileavg->setLegendIconSize(QSize(50,20)); + cprofileavg->setPen( QPen(penColor, Settings2::m_profile->avgProfileWidth()) ); + cprofileavg->setSamples( left); + cprofileavg->attach( m_plot ); + g_angle = startAngle; + } } - default: - break; - } // Insert markers @@ -815,16 +888,44 @@ void ProfilePlot::showContextMenu(QPoint pos) // Create menu and insert some actions QMenu myMenu; myMenu.setToolTipsVisible(true); - QString txt = (zoomed)? "Restore to MainWindow" : "FullScreen"; - myMenu.addAction(txt, this, &ProfilePlot::zoom); - myMenu.addAction("Change X axis to show percentage", this, SLOT(showXPercent())); - myMenu.addAction("Change X asix to show inches",this, SLOT(showXInches())); - myMenu.addAction("Change X axis to show mm",this, SLOT(showXMM())); - QAction *correctionAction = myMenu.addAction("show percent of correction",this, SLOT(showCorrection())); - correctionAction->setToolTip("Show % correction of zone areas used in the Zambuto method of mirror figuring."); + QAction* showFull = new QAction("FullScreen", this); + showFull->setCheckable(true); + showFull->setChecked(zoomed); + connect(showFull, &QAction::triggered, this, &ProfilePlot::zoom); + myMenu.addAction(showFull); + + QAction* showPercentage = new QAction("Change X axis to show percentage", this); + connect(showPercentage, &QAction::triggered, this, &ProfilePlot::showXPercent); + myMenu.addAction(showPercentage); + + QAction* showInches = new QAction("Change X asix to show inches",this); + connect(showInches, &QAction::triggered, this, &ProfilePlot::showXInches); + myMenu.addAction(showInches); + + QAction* showMM = new QAction("Change X axis to show mm",this); + connect(showMM, &QAction::triggered, this, &ProfilePlot::showXMM); + myMenu.addAction(showMM); + QAction *show16 = new QAction("show 16 diameters",this); + show16->setCheckable(true); + show16->setChecked(m_show_16_diameters); + connect(show16, &QAction::triggered, this, &ProfilePlot::toggleShow16); + myMenu.addAction(show16); + + QAction *showAvgOnly = new QAction("Show only avg", this); + showAvgOnly->setCheckable(true); + showAvgOnly->setChecked(m_showOnlyAvg); + showAvgOnly->setToolTip("Shows only the average of all diameters"); + connect(showAvgOnly, &QAction::triggered, this, &ProfilePlot::toggleShowAvgOnly); + myMenu.addAction(showAvgOnly); + + QAction* showCorrection = new QAction("show percent of correction",this); + connect(showCorrection, &QAction::trigger, this, &ProfilePlot::showCorrection); + showCorrection->setToolTip("Show % correction of zone areas used in the Zambuto method of mirror figuring."); + myMenu.addAction(showCorrection); + // Show context menu at handling position myMenu.exec(globalPos); } @@ -833,6 +934,18 @@ void ProfilePlot::saveXscaleSettings(){ set.setValue("xScalepercent", m_displayPercent); set.setValue("xScaleInches", m_displayInches); } +void ProfilePlot::toggleShow16(){ + m_show_16_diameters = !m_show_16_diameters; + + populate(); + m_plot->replot(); +} +void ProfilePlot::toggleShowAvgOnly(){ + m_showOnlyAvg = !m_showOnlyAvg; + + populate(); + m_plot->replot(); +} void ProfilePlot::showXPercent(){ m_displayPercent = true; m_displayInches = false; diff --git a/profileplot.h b/profileplot.h index 474181e1..1db94565 100644 --- a/profileplot.h +++ b/profileplot.h @@ -54,13 +54,13 @@ class ProfilePlot : public QWidget double m_waveRange; QCheckBox *showNmCB; QCheckBox *showSurfaceCB; - QRadioButton *OneOnly; - QRadioButton *Show16; - QRadioButton *ShowAll; + double m_showSurface; double m_showNm; bool zoomed; bool m_showSlopeError; + bool m_showOnlyAvg = false; + bool m_show_16_diameters = false; double slopeLimitArcSec; @@ -92,6 +92,9 @@ public slots: void showXPercent(); void showXInches(); void showXMM(); + void toggleShowAvgOnly(); + void toggleShow16(); + //QPolygonF createZernProfile(wavefront *wf); private: enum class ProfileType { diff --git a/profileplotpicker.cpp b/profileplotpicker.cpp index ce68e902..58eaf0a7 100644 --- a/profileplotpicker.cpp +++ b/profileplotpicker.cpp @@ -131,10 +131,10 @@ void profilePlotPicker::select( QPoint pos ) // Move the selected point void profilePlotPicker::move( QPoint pos ) { - qDebug() << "move pos" << pos; + if ( !d_selectedCurve ) return; -qDebug() << "Move" << plot()->invTransform(d_selectedCurve->yAxis(), pos.y()); + double last = plot()->invTransform( d_selectedCurve->yAxis(), m_lastMousePos.y() ); double current = plot()->invTransform( d_selectedCurve->yAxis(), pos.y() ); double delta = current - last; diff --git a/settingsprofile.cpp b/settingsprofile.cpp index 2a2c1758..fa25c9e6 100644 --- a/settingsprofile.cpp +++ b/settingsprofile.cpp @@ -21,7 +21,7 @@ #include #include #include - +#include static inline QString colorButtonStyleSheet(const QColor &bgColor) { if (bgColor.isValid()) { @@ -46,7 +46,15 @@ settingsProfile::settingsProfile(QWidget *parent) : color = QColor(set.value("profile color "+name, color).toString()); btn->setStyleSheet(colorButtonStyleSheet(color)); } + ui->SlopeErrorWidth->blockSignals(true); + ui->SlopeErrorWidth->setValue(set.value("profileSlopeErrorLineWidth", 3).toInt()); + ui->SlopeErrorWidth->blockSignals(false); + + ui->AvgProfileWidth->blockSignals(true); + ui->AvgProfileWidth->setValue(set.value("profileAveLineWidth", 3).toInt()); + ui->AvgProfileWidth->blockSignals(false); } + QColor settingsProfile::getColor(int num){ QString name = QString("pushButton_%1").arg(1 + num%7); QPushButton *btn = findChild(name); @@ -67,7 +75,13 @@ int settingsProfile::lineWidth(){ int settingsProfile::selectedWidth(){ return ui->selectedWidth->value(); } +int settingsProfile::slopeErrorWidth(){ + return ui->SlopeErrorWidth->value(); +} +int settingsProfile::avgProfileWidth(){ + return ui->AvgProfileWidth->value(); +} settingsProfile::~settingsProfile() { spdlog::get("logger")->trace("settingsProfile::~settingsProfile"); @@ -120,3 +134,17 @@ void settingsProfile::on_pushButton_3_pressed() } + +void settingsProfile::on_SlopeErrorWidth_valueChanged(int arg1) +{ + QSettings set; + set.setValue("profileSlopeErrorLineWidth", arg1); +} + + +void settingsProfile::on_AvgProfileWidth_valueChanged(int arg1) +{ + QSettings set; + set.setValue("profileAveLineWidth", arg1); +} + diff --git a/settingsprofile.h b/settingsprofile.h index ea91bb49..5d6a9734 100644 --- a/settingsprofile.h +++ b/settingsprofile.h @@ -35,6 +35,8 @@ class settingsProfile : public QDialog QColor getColor(int num); int lineWidth(); int selectedWidth(); + int slopeErrorWidth(); + int avgProfileWidth(); private slots: void on_pushButton_1_pressed(); @@ -54,6 +56,10 @@ private slots: void on_pushButton_3_pressed(); + void on_SlopeErrorWidth_valueChanged(int arg1); + + void on_AvgProfileWidth_valueChanged(int arg1); + private: Ui::settingsProfile *ui; }; diff --git a/settingsprofile.ui b/settingsprofile.ui index 86e70ad7..88607600 100644 --- a/settingsprofile.ui +++ b/settingsprofile.ui @@ -6,248 +6,261 @@ 0 0 - 400 - 300 + 493 + 506 Profile line colors - - - - 210 - 260 - 111 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::NoButton - - - - - - 30 - 0 - 231 - 16 - - - - These colors are used for the profile line plots - - - - - - 180 - 10 - 191 - 61 - - - - When displaying multiple profiles these colors are cycled through for each wavefront. - - - true - - - - - - 30 - 20 - 121 - 266 - - - - - - - Color 1: - - - - - - - - - - - - - - Color 2: - - - - - - - - - - - - - - Color 3: - - - - - - - - - - - - - - Color 4: - - - - - - - - - - - - - - Color 5: - - - - - - - - - - - - - - Color 6: - - - - - - - Color 7: - - - - - - - Color 8: - - - - - - - Color 9: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 190 - 100 - 95 - 22 - - - - - - - Line Width - - - - - - - 2 - - - - - - - - - 190 - 140 - 150 - 22 - - - - - - - Selected Profile Width - - - - - - - 5 - - - - - + + + + + + + Color 1: + + + + + + + + + + + + + + Color 2: + + + + + + + + + + + + + + Color 3: + + + + + + + + + + + + + + Color 4: + + + + + + + + + + + + + + Color 5: + + + + + + + + + + + + + + Color 6: + + + + + + + Color 7: + + + + + + + Color 8: + + + + + + + Color 9: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + These colors are used for the profile line plots + + + + + + + When displaying multiple profiles these colors are cycled through for each wavefront. + + + true + + + + + + + + + Line Width + + + + + + + 2 + + + + + + + + + + + Selected Profile Width + + + + + + + 5 + + + + + + + + + + + Slope Error Line Width + + + + + + + 1 + + + + + + + + + + + Average Profile Width + + + + + + + 1 + + + + + + + + + Qt::Vertical + + + + 17 + 13 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::NoButton + + + + From 55041ba2725ab2957e6f088d96547b4a2cd3bb41 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Fri, 12 Dec 2025 16:33:32 -0600 Subject: [PATCH 02/13] change autoinvert default value to useConic. Improved profile menus. Corrected coloring of profile lines. --- mainwindow.cpp | 2 +- plotcolor.cpp | 4 +-- profileplot.cpp | 81 +++++++++++++++++++++------------------------- profileplot.h | 19 ++++------- surfacemanager.cpp | 2 +- 5 files changed, 47 insertions(+), 61 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index 208528ab..769a9994 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2139,7 +2139,7 @@ void MainWindow::on_actionastig_in_polar_triggered() void MainWindow::on_actionStop_auto_invert_triggered() { - m_surfaceManager->m_inverseMode = invNOTSET; + m_surfaceManager->m_inverseMode = invCONIC; m_mirrorDlg->updateAutoInvertStatus(); //QMessageBox::information(this, "auto invert", "DFTFringe will now ask if it thinks it needs to invert a wave front."); } diff --git a/plotcolor.cpp b/plotcolor.cpp index ca265b1e..9ff08123 100644 --- a/plotcolor.cpp +++ b/plotcolor.cpp @@ -1,7 +1,7 @@ #include "plotcolor.h" const char *plotColors[]= -{ "Red", - "Black", +{ "BLack", + "Red", "Green", "LightSalmon", "SteelBlue", diff --git a/profileplot.cpp b/profileplot.cpp index 763065c1..82813059 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -83,7 +83,7 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): connect(m_plot, &QwtPlot::customContextMenuRequested, this, &ProfilePlot::showContextMenu); new profilePlotPicker(m_plot); - type = ProfileType::SHOW_ONE; + QHBoxLayout * l1 = new QHBoxLayout(); QVBoxLayout *v1 = new QVBoxLayout(); showNmCB = new QCheckBox("Show in Nanometers",this); @@ -254,23 +254,7 @@ void ProfilePlot::showSurface(bool flag){ setSurface(m_wf); } -void ProfilePlot::showOne(){ - type = ProfileType::SHOW_ONE; - m_pcdlg->close(); - populate(); - m_plot->replot(); -} -void ProfilePlot::show16(){ - type = ProfileType::SHOW_16; - populate(); - m_plot->replot(); -} -void ProfilePlot::showAll(){ - type = ProfileType::SHOW_ALL; - m_pcdlg->close(); - populate(); - m_plot->replot(); -} + void ProfilePlot::zeroOffsetChanged(const QString &s){ if (offsetType == s) return; @@ -302,7 +286,7 @@ void ProfilePlot::angleChanged(double a){ return; g_angle = a * DEGTORAD; - if (type == ProfileType::SHOW_ONE || type == ProfileType::SHOW_ALL) + populate(); m_plot->replot(); emit profileAngleChanged(M_PI_2 - g_angle); @@ -719,20 +703,15 @@ void ProfilePlot::populate() double units = m_showNm * m_showSurface; - // if not show 16 diam - if (!(m_show_16_diameters or m_showOnlyAvg)){ + // if show one angle + if (m_show_oneAngle or (!m_showAvg and !m_show_16_diameters & !m_show_oneAngle)){ + cprofile->setSamples( createProfile( units,wf, true)); cprofile->attach( m_plot ); } - else { + if (m_show_16_diameters){ // compute 16 diameters - // if only one wave front is selected then use blue for the average - QColor penColor = QColor("blue"); - - // if more than one wave front is selected then used color table. - penColor = QColor(plotColors[i % 10]); - - + QColor penColor = QColor(Settings2::m_profile->getColor(i%10)); QString t = "Average of all 16 diameters"; QwtText title(t); @@ -757,7 +736,7 @@ void ProfilePlot::populate() cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); cprofile->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); cprofile->setItemAttribute(QwtPlotItem::Legend, false); - cprofile->setPen( QColor(plotColors[i % 10])); + cprofile->setPen( penColor); points = createProfile( m_showNm * m_showSurface,wf); if (i == 0) { @@ -773,13 +752,16 @@ void ProfilePlot::populate() else count[j] = 1; } } - if (!m_showOnlyAvg){ + if (m_show_16_diameters){ cprofile->setSamples( points); cprofile->attach( m_plot ); } } - + } + if (m_showAvg){ + qDebug() << "pen color" << i << plotColors[i]; + QColor penColor = QColor(Settings2::m_profile->getColor(i%10)); // plot the average profile std::vector avgRadius = compute_average_radial_profile(wf->workData, cv::Point(wf->m_outside.m_center.x(),wf->m_outside.m_center.y()), wf->m_outside.m_radius); @@ -811,10 +793,10 @@ void ProfilePlot::populate() cprofileavg->setPen( QPen(penColor, Settings2::m_profile->avgProfileWidth()) ); cprofileavg->setSamples( left); cprofileavg->attach( m_plot ); - g_angle = startAngle; - } + //g_angle = startAngle; } + } // Insert markers // ...a horizontal line at y = 0... @@ -907,6 +889,11 @@ void ProfilePlot::showContextMenu(QPoint pos) connect(showMM, &QAction::triggered, this, &ProfilePlot::showXMM); myMenu.addAction(showMM); + QAction *showOneAngle = new QAction("show one angle",this); + showOneAngle->setCheckable(true); + showOneAngle->setChecked(m_show_oneAngle); + connect(showOneAngle, &QAction::triggered, this, &ProfilePlot::toggleOneAngle); + myMenu.addAction(showOneAngle); QAction *show16 = new QAction("show 16 diameters",this); show16->setCheckable(true); @@ -914,15 +901,15 @@ void ProfilePlot::showContextMenu(QPoint pos) connect(show16, &QAction::triggered, this, &ProfilePlot::toggleShow16); myMenu.addAction(show16); - QAction *showAvgOnly = new QAction("Show only avg", this); - showAvgOnly->setCheckable(true); - showAvgOnly->setChecked(m_showOnlyAvg); - showAvgOnly->setToolTip("Shows only the average of all diameters"); - connect(showAvgOnly, &QAction::triggered, this, &ProfilePlot::toggleShowAvgOnly); - myMenu.addAction(showAvgOnly); + QAction *showAvg = new QAction("Show avg", this); + showAvg->setCheckable(true); + showAvg->setChecked(m_showAvg); + showAvg->setToolTip("Shows only the average of all diameters"); + connect(showAvg, &QAction::triggered, this, &ProfilePlot::toggleShowAvg); + myMenu.addAction(showAvg); QAction* showCorrection = new QAction("show percent of correction",this); - connect(showCorrection, &QAction::trigger, this, &ProfilePlot::showCorrection); + connect(showCorrection, &QAction::triggered, this, &ProfilePlot::showCorrection); showCorrection->setToolTip("Show % correction of zone areas used in the Zambuto method of mirror figuring."); myMenu.addAction(showCorrection); @@ -936,16 +923,22 @@ void ProfilePlot::saveXscaleSettings(){ } void ProfilePlot::toggleShow16(){ m_show_16_diameters = !m_show_16_diameters; - +qDebug() << "show 16"; populate(); m_plot->replot(); } -void ProfilePlot::toggleShowAvgOnly(){ - m_showOnlyAvg = !m_showOnlyAvg; +void ProfilePlot::toggleShowAvg(){ + m_showAvg = !m_showAvg; populate(); m_plot->replot(); } +void ProfilePlot::toggleOneAngle(){ + m_show_oneAngle = !m_show_oneAngle; +qDebug() << "show one"; + populate(); + m_plot->replot(); +} void ProfilePlot::showXPercent(){ m_displayPercent = true; m_displayInches = false; diff --git a/profileplot.h b/profileplot.h index 1db94565..06b338ff 100644 --- a/profileplot.h +++ b/profileplot.h @@ -59,8 +59,9 @@ class ProfilePlot : public QWidget double m_showNm; bool zoomed; bool m_showSlopeError; - bool m_showOnlyAvg = false; + bool m_showAvg = false; bool m_show_16_diameters = false; + bool m_show_oneAngle = true; double slopeLimitArcSec; @@ -76,9 +77,7 @@ public slots: void angleChanged(double a); void newDisplayErrorRange(double min, double max); void zeroOffsetChanged(const QString&); - void showOne(); - void show16(); - void showAll(); + void showNm(bool); void showSurface(bool); void zoom(); @@ -92,16 +91,13 @@ public slots: void showXPercent(); void showXInches(); void showXMM(); - void toggleShowAvgOnly(); + void toggleShowAvg(); void toggleShow16(); + void toggleOneAngle(); //QPolygonF createZernProfile(wavefront *wf); private: - enum class ProfileType { - SHOW_ONE = 0, - SHOW_16 = 1, - SHOW_ALL = 2 - }; + void updateGradient(); void saveXscaleSettings(); @@ -118,9 +114,6 @@ public slots: bool m_displayInches = false; private: - ProfileType type; - - Ui::ProfilePlot *ui; bool m_defocus_mode; cv::Mat_ m_defocus_wavefront; diff --git a/surfacemanager.cpp b/surfacemanager.cpp index d594de2c..e1bf8ebd 100644 --- a/surfacemanager.cpp +++ b/surfacemanager.cpp @@ -526,7 +526,7 @@ SurfaceManager::SurfaceManager(QObject *parent, surfaceAnalysisTools *tools, m_surfaceTools(tools),m_profilePlot(profilePlot), m_contourView(contourView), m_SurfaceGraph(glPlot), m_metrics(mets),m_gbValue(21), m_GB_enabled(false),m_currentNdx(-1),m_standAvg(0),insideOffset(0),outsideOffset(0), - m_inverseMode(invNOTSET),m_ignoreInverse(false), m_standAstigWizard(nullptr), workToDo(0), m_wftStats(0) + m_inverseMode(invCONIC),m_ignoreInverse(false), m_standAstigWizard(nullptr), workToDo(0), m_wftStats(0) { okToUpdateSurfacesOnGenerateComplete = true; From 6ac13dafeae4046fe1ceed4973d199bc24a862d1 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Sat, 13 Dec 2025 18:20:23 -0600 Subject: [PATCH 03/13] corrected displaying percentage and inches. Fixed bug where first analyzed igram did not display the profile after analysis. --- autoinvertdlg.cpp | 6 ++++++ autoinvertdlg.h | 2 ++ autoinvertdlg.ui | 48 +++++++++++++++++++++++++++++++++++++++++++--- profileplot.cpp | 17 ++++++++++++---- surfacemanager.cpp | 1 + surfacemanager.h | 2 ++ 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/autoinvertdlg.cpp b/autoinvertdlg.cpp index 817d4130..ef36c479 100644 --- a/autoinvertdlg.cpp +++ b/autoinvertdlg.cpp @@ -53,3 +53,9 @@ void autoInvertDlg::setMainLabel(const QString & str) { void autoInvertDlg::enableConic(bool b) { ui->btnUseConic->setEnabled(b); } + +void autoInvertDlg::on_InvertWithoutAskingRb_clicked(bool checked) +{ + +} + diff --git a/autoinvertdlg.h b/autoinvertdlg.h index dd41699f..647af4fd 100644 --- a/autoinvertdlg.h +++ b/autoinvertdlg.h @@ -27,6 +27,8 @@ private slots: void on_btnOutside_clicked(); + void on_InvertWithoutAskingRb_clicked(bool checked); + private: Ui::autoInvertDlg *ui; }; diff --git a/autoinvertdlg.ui b/autoinvertdlg.ui index 75bfbd71..e7fbc1e5 100644 --- a/autoinvertdlg.ui +++ b/autoinvertdlg.ui @@ -6,8 +6,8 @@ 0 0 - 1078 - 464 + 1295 + 565 @@ -25,7 +25,7 @@ true - + @@ -137,6 +137,48 @@ + + + + + + <html><head/><body><p><span style=" font-weight:700;">What to do if invertions may be detected</span></p></body></html> + + + true + + + + + + + + + Don't Ask but Invert + + + + + + + Ask but Don't Invert + + + true + + + + + + + Don't ask or invert + + + + + + + diff --git a/profileplot.cpp b/profileplot.cpp index 82813059..0273058a 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -81,7 +81,7 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): m_plot = new QwtPlot(this); m_plot->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_plot, &QwtPlot::customContextMenuRequested, this, &ProfilePlot::showContextMenu); - + m_plot->setCanvasBackground(Qt::darkGray); new profilePlotPicker(m_plot); QHBoxLayout * l1 = new QHBoxLayout(); @@ -622,7 +622,7 @@ std::vector compute_average_radial_profile( void ProfilePlot::populate() { - +qDebug() << "Populate"; m_plot->detachItems(QwtPlotItem::Rtti_PlotItem); compass->setGeometry(QRect(80,80,70,70)); QString tmp("nanometers"); @@ -687,6 +687,7 @@ void ProfilePlot::populate() m_plot->insertLegend( new QwtLegend() , QwtPlot::BottomLegend); surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); QList list = saTools->SelectedWaveFronts(); + // if no wave front was selected then use the last one for (int i = 0; i < list.size(); ++i){ wavefront* wf = wfs->at(list[i]); @@ -756,7 +757,6 @@ void ProfilePlot::populate() cprofile->setSamples( points); cprofile->attach( m_plot ); } - } } if (m_showAvg){ @@ -769,7 +769,16 @@ void ProfilePlot::populate() QPolygonF right; //right half of profile for (size_t i = 0; i < avgRadius.size(); ++i) { - double rr = (double)(i)/avgRadius.size() * wf->diameter/2.; + double rr = 100. * (double)(i)/(avgRadius.size()-1); + + if(m_displayInches){ + rr *= wf->diameter/2; + rr /=25.4; + } + else if (!m_displayPercent){ // convert rr into mm. + rr *= wf->diameter/2; + + } double val = (units * avgRadius[i] * wf->lambda/outputLambda) + y_offset * units; right << QPointF(rr, val); diff --git a/surfacemanager.cpp b/surfacemanager.cpp index e1bf8ebd..2e79e3ee 100644 --- a/surfacemanager.cpp +++ b/surfacemanager.cpp @@ -1099,6 +1099,7 @@ void SurfaceManager::createSurfaceFromPhaseMap(cv::Mat phase, CircleOutline outs m_surfaceTools->addWaveFront(wf->name); m_currentNdx = m_wavefronts.size()-1; + m_surfaceTools->select(m_currentNdx); } wf->m_outside = outside; wf->m_inside = center; diff --git a/surfacemanager.h b/surfacemanager.h index de8108f3..3f336ac8 100644 --- a/surfacemanager.h +++ b/surfacemanager.h @@ -45,6 +45,7 @@ #include "surfacegraph.h" enum configRESPONSE { YES, NO, ASK}; enum AutoInvertMode {invNOTSET, invMANUAL, invCONIC, invINSIDE, invOUTSIDE}; +enum askAboutInversion {ask, doNotAskDoNotInvert, doNotAskbutInvert}; struct textres { QTextEdit *Edit; QList res; @@ -112,6 +113,7 @@ class SurfaceManager : public QObject void subtractWavefronts(); AutoInvertMode m_inverseMode; + askAboutInversion m_askAboutInvert; bool m_ignoreInverse; bool m_surface_finished; configRESPONSE diamResp; From 434b16696ec915373e8501bf09687bbef31340e7 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Sun, 14 Dec 2025 14:51:09 -0600 Subject: [PATCH 04/13] Corrected bug in average display of inches and mm. --- profileplot.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/profileplot.cpp b/profileplot.cpp index 0273058a..d44662d8 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -769,13 +769,18 @@ qDebug() << "Populate"; QPolygonF right; //right half of profile for (size_t i = 0; i < avgRadius.size(); ++i) { - double rr = 100. * (double)(i)/(avgRadius.size()-1); - - if(m_displayInches){ + double rr = (double)(i)/(avgRadius.size()-1); + if (m_displayPercent){ + rr *= 100.; + } + else if(m_displayInches){ + qDebug() << "percent" << rr << wf->diameter/2; rr *= wf->diameter/2; rr /=25.4; + + qDebug() << "Inches" << rr; } - else if (!m_displayPercent){ // convert rr into mm. + else { // convert rr into mm. rr *= wf->diameter/2; } From e9ef0a9cf3f44ffc8bcfc726d40abf8c8225fc7b Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Mon, 22 Dec 2025 01:08:56 -0600 Subject: [PATCH 05/13] Modified code to shift the 16 diameters. Not perfect but good enough for now. Also added the worker thread class but not using it yet. This can be used as a model for that task. --- DFTFringe_Dale.pro | 2 + profileplot.cpp | 166 ++++++++++++++++++++++++++++++++++---- profileplot.h | 6 +- profileplotpicker.cpp | 2 +- profileplotpicker.h | 3 +- wavefrontloaderworker.cpp | 44 ++++++++++ wavefrontloaderworker.h | 36 +++++++++ 7 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 wavefrontloaderworker.cpp create mode 100644 wavefrontloaderworker.h diff --git a/DFTFringe_Dale.pro b/DFTFringe_Dale.pro index 17d54447..d0ee71ea 100644 --- a/DFTFringe_Dale.pro +++ b/DFTFringe_Dale.pro @@ -59,6 +59,7 @@ SOURCES += main.cpp \ dftcolormap.cpp \ surfaceanalysistools.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ zernikedlg.cpp \ zernikepolar.cpp \ zernikeprocess.cpp \ @@ -175,6 +176,7 @@ HEADERS += mainwindow.h \ dftcolormap.h \ surfaceanalysistools.h \ surfacemanager.h \ + wavefrontloaderworker.h \ zernikedlg.h \ zernikepolar.h \ zernikeprocess.h \ diff --git a/profileplot.cpp b/profileplot.cpp index d44662d8..ab0cc0f1 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -50,7 +50,7 @@ #include "zernikeprocess.h" #include #include "plotcolor.h" -#include "profileplotpicker.h" + extern double outputLambda; #include @@ -82,8 +82,8 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): m_plot->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_plot, &QwtPlot::customContextMenuRequested, this, &ProfilePlot::showContextMenu); m_plot->setCanvasBackground(Qt::darkGray); - new profilePlotPicker(m_plot); - + m_pk = new profilePlotPicker(m_plot); + QObject::connect(m_pk, &profilePlotPicker::offset, this, &ProfilePlot::SetYoffset); QHBoxLayout * l1 = new QHBoxLayout(); QVBoxLayout *v1 = new QVBoxLayout(); showNmCB = new QCheckBox("Show in Nanometers",this); @@ -219,11 +219,20 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): ProfilePlot::~ProfilePlot(){ delete ui; delete m_pcdlg; + delete m_pk; } +void ProfilePlot::SetYoffset(QString name, double value){ + if (m_waveFrontyOffsets.contains(name)){ + m_waveFrontyOffsets[name] += value; + } + else{ + m_waveFrontyOffsets.insert(name,value); + } +} void ProfilePlot::showSlope(bool val){ m_showSlopeError = val; @@ -316,6 +325,7 @@ void ProfilePlot::setSurface(wavefront * wf){ double mul = m_showNm * m_showSurface; m_plot->setAxisScale( m_plot->yLeft, fmin(-.125,m_tools->m_min) * 1.2 * mul , fmax(.125,m_tools->m_max) * 1.2 * mul); + m_waveFrontyOffsets.clear(); populate(); m_plot->replot(); } @@ -401,6 +411,7 @@ QPolygonF ProfilePlot::createProfile(double units, wavefront *wf, bool allowOffs 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.; @@ -702,8 +713,13 @@ qDebug() << "Populate"; cprofile->setRenderHint( QwtPlotItem::RenderAntialiased ); double units = m_showNm * m_showSurface; - - + if (m_waveFrontyOffsets.contains(name)) + y_offset = m_waveFrontyOffsets[name]; + else if (m_waveFrontyOffsets.contains(name + " avg")){ + 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 & !m_show_oneAngle)){ @@ -729,6 +745,7 @@ qDebug() << "Populate"; double startAngle = g_angle; QPolygonF sum; QMap count; + qDebug() << "yoffsets" << m_waveFrontyOffsets << title.text(); for (int idx = 0; idx < 16; ++idx){ QPolygonF points; g_angle = startAngle + idx * M_PI/ 16; @@ -738,8 +755,9 @@ qDebug() << "Populate"; cprofile->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, false ); cprofile->setItemAttribute(QwtPlotItem::Legend, false); cprofile->setPen( penColor); - - points = createProfile( m_showNm * m_showSurface,wf); + if (m_waveFrontyOffsets.contains(name)) + y_offset = m_waveFrontyOffsets[name]; + points = createProfile( m_showNm * m_showSurface,wf,true); if (i == 0) { sum = points; for (int j = 0; j < sum.length(); ++j) @@ -760,25 +778,22 @@ qDebug() << "Populate"; } } if (m_showAvg){ - qDebug() << "pen color" << i << plotColors[i]; + qDebug() << "inside avg" << y_offset; QColor penColor = QColor(Settings2::m_profile->getColor(i%10)); // plot the average profile std::vector avgRadius = compute_average_radial_profile(wf->workData, cv::Point(wf->m_outside.m_center.x(),wf->m_outside.m_center.y()), wf->m_outside.m_radius); QPolygonF right; //right half of profile - - for (size_t i = 0; i < avgRadius.size(); ++i) { - double rr = (double)(i)/(avgRadius.size()-1); + int edgeMaskWidth = 4; + for (size_t i = 0; i < avgRadius.size()-edgeMaskWidth; ++i) { + double rr = (double)(i)/(avgRadius.size()-edgeMaskWidth-1); if (m_displayPercent){ rr *= 100.; } else if(m_displayInches){ - qDebug() << "percent" << rr << wf->diameter/2; rr *= wf->diameter/2; rr /=25.4; - - qDebug() << "Inches" << rr; } else { // convert rr into mm. rr *= wf->diameter/2; @@ -922,6 +937,10 @@ void ProfilePlot::showContextMenu(QPoint pos) connect(showAvg, &QAction::triggered, this, &ProfilePlot::toggleShowAvg); myMenu.addAction(showAvg); + QAction *createAvg = new QAction("make surface from average profile", this); + connect(createAvg, &QAction::triggered, this, &ProfilePlot::CreateWaveFrontFromAverage); + myMenu.addAction(createAvg); + QAction* showCorrection = new QAction("show percent of correction",this); connect(showCorrection, &QAction::triggered, this, &ProfilePlot::showCorrection); showCorrection->setToolTip("Show % correction of zone areas used in the Zambuto method of mirror figuring."); @@ -1020,3 +1039,122 @@ void ProfilePlot::showCorrection(){ } + +/** + * @brief Performs linear interpolation between two values. + * @param a The value at index floor(x). + * @param b The value at index ceil(x). + * @param t The fractional part of x (t = x - floor(x)). + * @return The interpolated value. + */ +double lerp(double a, double b, double t) { + return a + t * (b - a); +} + +/** + * @brief Creates an OpenCV matrix representing a radially symmetric circular surface, + * using linear interpolation for sub-integer radial distances. + * + * @param radialProfile The vector of doubles representing the value from center to edge (index 0 to N-1). + * @return cv::Mat The square matrix representing the circular surface. + */ +cv::Mat createInterpolatedCircularSurface(const std::vector& radialProfile) { + if (radialProfile.empty()) { + std::cerr << "Error: Input radialProfile vector is empty." << std::endl; + return cv::Mat(); + } + + // The radius of the circle is determined by the max index in the profile. + // Max distance is profile size - 1. + const int profileSize = radialProfile.size(); + const double maxRadiusDistance = static_cast(profileSize - 1); + + // The surface size (diameter) will be twice the radius + 1 (for the center pixel). + int surfaceSize = 2 * static_cast(maxRadiusDistance) + 1; + + // 1. Initialize the output matrix + cv::Mat surface = cv::Mat::zeros(surfaceSize, surfaceSize, CV_64FC1); + + // Center coordinates + double centerX = maxRadiusDistance; + double centerY = maxRadiusDistance; + + // 2. Iterate over all pixels in the matrix + for (int y = 0; y < surfaceSize; ++y) { + // Get a pointer to the current row for fast access + double* rowPtr = surface.ptr(y); + + for (int x = 0; x < surfaceSize; ++x) { + // Calculate coordinates relative to the center + double dx = x - centerX; + double dy = y - centerY; + + // Calculate the current pixel's distance from the center (r_pixel) + double r_pixel = std::sqrt(dx * dx + dy * dy); + + // 3. Boundary Check and Interpolation + + // Check if the pixel is within the circular area defined by the profile length + if (r_pixel <= maxRadiusDistance) { + + // --- Perform Linear Interpolation --- + + // Index 1 (Lower bound) + int index1 = static_cast(std::floor(r_pixel)); + + // Index 2 (Upper bound) + int index2 = static_cast(std::ceil(r_pixel)); + + // Fractional part 't' used for interpolation + double t = r_pixel - index1; + + // Value for the pixel + double interpolatedValue = 0.0; + + if (index1 == index2) { + // This happens when r_pixel is an exact integer distance (e.g., 5.0). + // We only need to use the single value. + interpolatedValue = radialProfile[index1]; + } else { + // Get the values at the two surrounding integer indices + double value1 = radialProfile[index1]; + // Ensure index2 is within bounds (only fails if r_pixel > maxRadiusDistance, + // but boundary check above already handles this for the last element). + double value2 = radialProfile[index2]; + + // Apply linear interpolation + interpolatedValue = lerp(value1, value2, t); + } + + rowPtr[x] = interpolatedValue; + + } else { + // The pixel is outside the circular surface area. Value remains 0.0. + } + } + } + + return surface; +} +void ProfilePlot::CreateWaveFrontFromAverage(){ + + std::vector avgRadius = compute_average_radial_profile(m_wf->workData, + cv::Point(m_wf->m_outside.m_center.x(),m_wf->m_outside.m_center.y()), m_wf->m_outside.m_radius); + + //create a matrix from the avgRadius profile. + mirrorDlg *md = mirrorDlg::get_Instance(); + // first add the null back into it. + if (md->doNull){ + for (unsigned int i = 0; i < avgRadius.size(); ++i) { + double R2 = (double(i))/(avgRadius.size() -1); + R2 *= R2; + avgRadius[i] += md->z8 * md->cc * (1. + R2 * (-6 + 6. * R2));; + } + } + cv::Mat result = createInterpolatedCircularSurface(avgRadius); + SurfaceManager *sm = SurfaceManager::get_instance(); + sm->createSurfaceFromPhaseMap(result, + m_wf->m_outside, + m_wf->m_inside, + QString("avg")); +} diff --git a/profileplot.h b/profileplot.h index 06b338ff..2071d999 100644 --- a/profileplot.h +++ b/profileplot.h @@ -31,6 +31,7 @@ #include #include #include "percentcorrectiondlg.h" +#include "profileplotpicker.h" namespace Ui { class ProfilePlot; } @@ -94,7 +95,8 @@ public slots: void toggleShowAvg(); void toggleShow16(); void toggleOneAngle(); - + void CreateWaveFrontFromAverage(); + void SetYoffset(QString name, double value); //QPolygonF createZernProfile(wavefront *wf); private: @@ -117,6 +119,8 @@ public slots: Ui::ProfilePlot *ui; bool m_defocus_mode; cv::Mat_ m_defocus_wavefront; + QMap m_waveFrontyOffsets; + profilePlotPicker *m_pk; }; #endif // PROFILEPLOT_H diff --git a/profileplotpicker.cpp b/profileplotpicker.cpp index 58eaf0a7..7be18500 100644 --- a/profileplotpicker.cpp +++ b/profileplotpicker.cpp @@ -149,7 +149,7 @@ void profilePlotPicker::move( QPoint pos ) yData[i] = d_selectedCurve->sample(i).y() + delta; } d_selectedCurve->setSamples( xData, yData ); - + emit offset(d_selectedCurve->title().text(), delta); /* Enable QwtPlotCanvas::ImmediatePaint, so that the canvas has been updated before we paint the cursor on it. diff --git a/profileplotpicker.h b/profileplotpicker.h index 54bb7aeb..7f295cd4 100644 --- a/profileplotpicker.h +++ b/profileplotpicker.h @@ -17,7 +17,8 @@ class profilePlotPicker: public QObject virtual bool eventFilter( QObject *, QEvent * ); virtual bool event( QEvent * ); - +signals: + void offset(QString wavefrontName, double delta); private: void select( QPoint ); diff --git a/wavefrontloaderworker.cpp b/wavefrontloaderworker.cpp new file mode 100644 index 00000000..f41a38fb --- /dev/null +++ b/wavefrontloaderworker.cpp @@ -0,0 +1,44 @@ +#include "wavefrontloaderworker.h" +#include +#include "surfacemanager.h" +// Ensure your SurfaceManager header is included here +// #include "surfacemanager.h" + +WavefrontLoaderWorker::WavefrontLoaderWorker(QObject *parent) + : QObject(parent), m_isCancelled(false) +{ +} + +void WavefrontLoaderWorker::process(QStringList args, SurfaceManager* manager) { + m_isCancelled = false; // Reset the flag for this run + int cnt = 0; + + for (const QString &arg : args) { + // Safe check for cross-thread cancellation + if (m_isCancelled.load()) { + break; + } + + if (arg.endsWith(".wft", Qt::CaseInsensitive)) { + // Signal the UI thread to update the label + emit progressLabelChanged(arg); + + try { + // The actual heavy task + manager->loadWavefront(arg); + } catch (...) { + // Handle potential exceptions during load + break; + } + + // Signal the UI thread to update the progress bar + emit progressValueChanged(++cnt); + } + } + + emit finished(); +} + +void WavefrontLoaderWorker::cancel() { + m_isCancelled.store(true); +} diff --git a/wavefrontloaderworker.h b/wavefrontloaderworker.h new file mode 100644 index 00000000..ee579c75 --- /dev/null +++ b/wavefrontloaderworker.h @@ -0,0 +1,36 @@ +#ifndef WAVEFRONTLOADERWORKER_H +#define WAVEFRONTLOADERWORKER_H + +#include +#include +#include + +// Forward declaration of SurfaceManager to keep header clean +class SurfaceManager; + +class WavefrontLoaderWorker : public QObject { + Q_OBJECT +public: + explicit WavefrontLoaderWorker(QObject *parent = nullptr); + +public slots: + /** + * @brief The main loop that processes the wavefront files. + */ + void process(QStringList args, SurfaceManager* manager); + + /** + * @brief Slot to trigger a safe exit of the loop. + */ + void cancel(); + +signals: + void progressLabelChanged(const QString &text); + void progressValueChanged(int value); + void finished(); + +private: + std::atomic m_isCancelled; +}; + +#endif // WAVEFRONTLOADERWORKER_H From d4e87ae1efdaaf0b56d4c6829aa53b560b93cb22 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Mon, 22 Dec 2025 21:54:46 -0600 Subject: [PATCH 06/13] added make ronchi image of selected wave fronts. Restructed foucault view ronchi and foucalut generater --- foucaultview.cpp | 341 ++++++++++++++++++++++++++++++++++++++++++++++- foucaultview.h | 63 +++++---- 2 files changed, 375 insertions(+), 29 deletions(-) diff --git a/foucaultview.cpp b/foucaultview.cpp index e31d2bd7..66b0d8df 100644 --- a/foucaultview.cpp +++ b/foucaultview.cpp @@ -74,10 +74,27 @@ void foucaultView::showContextMenu(QPoint pos) QMenu myMenu; myMenu.addAction("Save Ronchi image", this, &foucaultView::saveRonchiImage); myMenu.addAction("Save Foucault Image", this, &foucaultView::saveFoucaultImage); + QAction *showAllRonchi = new QAction("Show all Selected Wave Fronts using Ronchi"); + connect (showAllRonchi, &QAction::triggered,this, &foucaultView::showSelectedRonchiImages); + myMenu.addAction(showAllRonchi); // Show context menu at handling position myMenu.exec(globalPos); } + +void foucaultView::showSelectedRonchiImages(){ + + surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); + QList list = saTools->SelectedWaveFronts(); + + QList wfs; + + for (int i = 0; i < list.size(); ++i){ + wfs << m_sm->m_wavefronts.at(list[i]); + } + generateBatchRonchiImage(wfs); +} + QImage *foucaultView::render(){ on_makePb_clicked(); QSize imsize = ui->foucaultViewLb->size(); @@ -152,6 +169,328 @@ QVector scaleProfile(QPolygonF points, int width, return results; } + + +QImage foucaultView::generateOpticalTestImage(OpticalTestType type, wavefront* wf, const OpticalTestSettings& s) +{ + if (!wf || wf->data.cols == 0) return QImage(); + + // 1. Setup Constants + double pad = 1.1; + int size = (int(wf->data.cols * pad) / 2) * 2; + double actualPad = (double)size / wf->data.cols; + double moving_constant = (s.movingSource) ? 1.0 : 2.0; + + mirrorDlg *md = mirrorDlg::get_Instance(); + double unitMultiplyer = s.useMM ? 1.0 : 25.4; + double coc_offset_mm = s.rocOffset * unitMultiplyer; + + // Physics geometry + double r2 = (md->diameter / 2.0) * (md->diameter / 2.0); + double b = md->roc + coc_offset_mm; + double pv = (sqrt(r2 + (md->roc * md->roc)) - (sqrt(r2 + b * b) - coc_offset_mm)) / (md->lambda * 1.E-6); + double z3 = pv / moving_constant; + double effectiveZ3 = (type == OpticalTestType::Ronchi) ? (s.ronchiX * z3) : z3; + + // 2. Wavefront Prep + std::vector originalZerns = wf->InputZerns; + std::vector tempZerns = originalZerns; + tempZerns[3] -= 3 * tempZerns[8]; + wf->InputZerns = tempZerns; + + SimulationsView *sv = SimulationsView::getInstance(0); + sv->setSurface(wf); + cv::Mat surf_fft = sv->computeStarTest(s.heightMultiply * sv->nulledSurface(effectiveZ3), size, actualPad, true); + wf->InputZerns = originalZerns; // Restore state immediately + + // 3. Mask Generation + cv::Mat mask = cv::Mat::zeros(size, size, CV_64FC1); + cv::Mat sourceSlit = cv::Mat::zeros(size, size, CV_64FC1); + int hx = (size - 1) / 2 + s.lateralOffset; + double pixwidth = s.outputLambda * 1.E-6 * (0.5 * md->roc / md->diameter) * 2. / (25.4 * actualPad); + + if (type == OpticalTestType::Ronchi) { + double lpi_val = s.lpi * (s.useMM ? 25.4 : 1.0); + int ppl = std::max(1, (int)((0.5 / lpi_val) / pixwidth)); + int start = (size / 2) - (ppl / 2); + bool even = ((start / ppl) % 2 == 0) ^ s.clearCenter; + int roffset_start = ppl - (start % ppl); + + for (int y = 0; y < size; ++y) { + int line_no = 0, roffset = roffset_start; + for (int x = 0; x < size; ++x) { + if (((even && (line_no % 2 == 0)) || (!even && (line_no % 2 != 0)))) mask.at(y, x) = 1.0; + if (++roffset >= ppl) { line_no++; roffset = 0; } + if (x > hx - ppl / 2. && x < hx + ppl / 2.) sourceSlit.at(y, x) = 1.0; + } + } + } else { + double slitWidthHalf = (.001 / pixwidth) * s.slitWidth * 1000 * (s.useMM ? 1./25.4 : 1.0); + for (int y = 0; y < size; ++y) { + double ry = double(y - hx) / hx; + for (int x = 0; x < size; ++x) { + if (sqrt(pow(double(x - hx)/hx, 2) + ry*ry) <= 1.0 && std::abs(x - hx) < slitWidthHalf) sourceSlit.at(y, x) = 255.0; + int k_side = s.knifeOnLeft ? (size - x) : x; + if (k_side > hx) mask.at(y, x) = 255.0; + } + } + } + + // 4. DFT Pipeline + cv::Mat FFT1, FFT2, complexMask, complexSlit, combinedFilter, finalResult; + cv::Mat planesM[] = {mask, cv::Mat::zeros(mask.size(), CV_64FC1)}; + cv::Mat planesS[] = {sourceSlit, cv::Mat::zeros(sourceSlit.size(), CV_64FC1)}; + cv::merge(planesM, 2, complexMask); + cv::merge(planesS, 2, complexSlit); + + cv::dft(complexMask, FFT1, cv::DFT_REAL_OUTPUT); + cv::dft(complexSlit, FFT2, cv::DFT_REAL_OUTPUT); + if (type == OpticalTestType::Ronchi) { shiftDFT(FFT1); shiftDFT(FFT2); } + + cv::mulSpectrums(FFT1, FFT2, combinedFilter, 0, true); + cv::idft(combinedFilter, combinedFilter, cv::DFT_SCALE); + if (type == OpticalTestType::Ronchi) shiftDFT(combinedFilter); + + cv::mulSpectrums(combinedFilter, surf_fft, finalResult, 0, true); + cv::idft(finalResult, finalResult, cv::DFT_SCALE); + if (type == OpticalTestType::Ronchi) shiftDFT(finalResult); + + // 5. Output Image + QImage res = showMag(finalResult, false, "", false, s.gamma); + int startx = size - wf->data.cols; + return res.copy(startx, startx, wf->data.cols, wf->data.cols).mirrored(true, false); +} + + +void foucaultView::on_makePb_clicked() +{ + m_guiTimer.stop(); + if (m_wf == nullptr || m_wf->data.cols == 0) + return; + + if (mirrorDlg::get_Instance()->isEllipse()){ + QMessageBox::warning(0,"warning","Foucaualt is not suppported for flat surfaces"); + return; + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + + // 1. Pack the current UI state into the settings struct + OpticalTestSettings settings; + settings.rocOffset = ui->rocOffsetSb->value(); + settings.ronchiX = ui->RonchiX->value(); + settings.lpi = ui->lpiSb->value(); + settings.gamma = ui->gammaSb->value(); + settings.slitWidth = ui->slitWidthSb->value(); + settings.useMM = ui->useMM->isChecked(); + settings.movingSource = ui->movingSourceRb->isChecked(); + settings.knifeOnLeft = ui->knifeOnLeftCb->isChecked(); + settings.clearCenter = ui->clearCenterCb->isChecked(); + + // Pass the class members that aren't driven by UI widgets + settings.heightMultiply = this->heightMultiply; + settings.lateralOffset = this->lateralOffset; + // Note: outputLambda should be defined/accessible in your scope + settings.outputLambda = 550.0; + + // 2. Call the refactored static engine for both images + QImage ronchiImg = generateOpticalTestImage(OpticalTestType::Ronchi, m_wf, settings); + QImage foucaultImg = generateOpticalTestImage(OpticalTestType::Foucault, m_wf, settings); + + // Store for potential saving/external access + m_foucultQimage = foucaultImg; + + // 3. UI Painting Helper (to avoid duplicating the Painter logic) + auto paintAndDisplay = [&](QLabel* label, QImage img, double offsetValue) { + if (img.isNull()) return; + + QSize s = label->size(); + QPixmap pix = QPixmap::fromImage(img.scaledToWidth(s.width())); + + QPainter painter(&pix); + painter.save(); + painter.setPen(QPen(QColor(Qt::white))); + painter.setFont(QFont("Arial", 15)); + + QString unit = ui->useMM->isChecked() ? "mm" : "in"; + QString zStr = QString("%1 %2").arg(offsetValue, 6, 'f', 3).arg(unit); + painter.drawText(20, 40, zStr); + + if (ui->overlayProfile->isChecked()) { + QPolygonF profile = m_sm->m_profilePlot->createProfile(1.0, m_wf); + // Assuming scaleProfile is a helper accessible in this scope + QVector profilePoints = scaleProfile(profile, pix.width(), M_PI/4.0); + painter.setPen(QPen(QColor(Qt::yellow), 3)); + painter.drawLines(profilePoints); + } + painter.restore(); + label->setPixmap(pix); + }; + + // 4. Update the actual labels + paintAndDisplay(ui->ronchiViewLb, ronchiImg, settings.ronchiX * settings.rocOffset); + paintAndDisplay(ui->foucaultViewLb, foucaultImg, settings.rocOffset); + + QApplication::restoreOverrideCursor(); +} + + + +void foucaultView::generateBatchRonchiImage(const QList& wavefrontList) +{ + // 1. Initial Checks + if (wavefrontList.isEmpty() || !m_wf) return; + + // 2. Ask user for Grid Layout + bool ok; + int cols = QInputDialog::getInt(this, tr("Batch Ronchi"), + tr("Number of columns:"), 2, 1, 10, 1, &ok); + if (!ok) return; + + // 3. Prepare Optical Settings from UI + OpticalTestSettings s; + s.rocOffset = ui->rocOffsetSb->value(); + s.ronchiX = ui->RonchiX->value(); + s.lpi = ui->lpiSb->value(); + s.gamma = ui->gammaSb->value(); + s.slitWidth = ui->slitWidthSb->value(); + s.useMM = ui->useMM->isChecked(); + s.movingSource = ui->movingSourceRb->isChecked(); + s.knifeOnLeft = ui->knifeOnLeftCb->isChecked(); + s.clearCenter = ui->clearCenterCb->isChecked(); + s.heightMultiply = this->heightMultiply; + s.lateralOffset = this->lateralOffset; + s.outputLambda = 550.0; + + // 4. Calculate Grid and Canvas Geometry + int count = wavefrontList.size(); + int rows = (count + cols - 1) / cols; + int imgDim = m_wf->data.cols; + + int headerHeight = 70; // Space for the parameters at the top + int textBuffer = 40; // Space for labels at the bottom of each image + + int cellW = imgDim; + int cellH = imgDim + textBuffer; + + // Total Canvas size + QImage canvas(cellW * cols, (cellH * rows) + headerHeight, QImage::Format_RGB32); + canvas.fill(Qt::black); + + QPainter painter(&canvas); + painter.setRenderHint(QPainter::Antialiasing); + QApplication::setOverrideCursor(Qt::WaitCursor); + + // 5. Draw Simulation Header (Removed Slit) + painter.setPen(Qt::white); + painter.setFont(QFont("Arial", 12, QFont::Bold)); + QString unit = s.useMM ? "mm" : "in"; + QString headerText = QString("Ronchi Analysis | LPI: %1 | Offset: %2 %3 | Gamma: %4") + .arg(s.lpi).arg(s.rocOffset).arg(unit).arg(s.gamma); + painter.drawText(20, 35, headerText); + + // Draw separator line + painter.setPen(QPen(Qt::gray, 2)); + painter.drawLine(10, headerHeight - 15, canvas.width() - 10, headerHeight - 15); + + // 6. Loop through and Render Ronchi Patterns + painter.setFont(QFont("Arial", 11, QFont::Bold)); + QFontMetrics fm(painter.font()); + + for (int i = 0; i < count; ++i) { + wavefront* currentWf = wavefrontList[i]; + int row = i / cols; + int col = i % cols; + + QImage ronchi = generateOpticalTestImage(OpticalTestType::Ronchi, currentWf, s); + + if (!ronchi.isNull()) { + int xPos = col * cellW; + int yPos = headerHeight + (row * cellH); // Offset by header + + painter.drawImage(xPos, yPos, ronchi); + + // Handle Filename Label + QFileInfo fileInfo(currentWf->name); + QString displayName = fileInfo.baseName(); + + int textWidth = fm.horizontalAdvance(displayName); + int xText = xPos + (cellW - textWidth) / 2; + int yText = yPos + imgDim + (textBuffer / 2) + (fm.ascent() / 2); + + painter.setPen(Qt::yellow); + painter.drawText(xText, yText, displayName); + } + } + painter.end(); + QApplication::restoreOverrideCursor(); + + // 7. Configure Preview Dialog (75% Screen Width) + QScreen *screen = QGuiApplication::primaryScreen(); + int dlgW = static_cast(screen->availableGeometry().width() * 0.75); + int dlgH = static_cast(screen->availableGeometry().height() * 0.85); + + QDialog previewDlg(this); + previewDlg.setWindowTitle(tr("Batch Ronchi Analysis Preview")); + previewDlg.resize(dlgW, dlgH); + + QVBoxLayout *layout = new QVBoxLayout(&previewDlg); + QScrollArea *scroll = new QScrollArea(&previewDlg); + scroll->setWidgetResizable(true); + scroll->setAlignment(Qt::AlignCenter); + scroll->setStyleSheet("background-color: #1a1a1a;"); + + QLabel *imgLabel = new QLabel(); + imgLabel->setAlignment(Qt::AlignCenter); + scroll->setWidget(imgLabel); + layout->addWidget(scroll); + + // 8. Zoom Slider Integration + QPixmap previewPixmap = QPixmap::fromImage(canvas); + QHBoxLayout *zoomLayout = new QHBoxLayout(); + QSlider *slider = new QSlider(Qt::Horizontal); + slider->setRange(10, 400); + slider->setValue(100); + + QLabel *zoomValueLabel = new QLabel("100%"); + zoomLayout->addWidget(new QLabel(tr("Zoom: "))); + zoomLayout->addWidget(slider); + zoomLayout->addWidget(zoomValueLabel); + layout->addLayout(zoomLayout); + + auto updateZoom = [previewPixmap, imgLabel, zoomValueLabel, dlgW](int val) { + int targetWidth = (dlgW - 80) * val / 100; + QPixmap scaled = previewPixmap.scaledToWidth(targetWidth, Qt::SmoothTransformation); + imgLabel->setPixmap(scaled); + imgLabel->setFixedSize(scaled.size()); + zoomValueLabel->setText(QString("%1%").arg(val)); + }; + + connect(slider, &QSlider::valueChanged, updateZoom); + updateZoom(100); + + // 9. Save and Cancel Buttons + QHBoxLayout *btns = new QHBoxLayout(); + QPushButton *saveBtn = new QPushButton(tr("Save Full Resolution")); + QPushButton *cancelBtn = new QPushButton(tr("Cancel")); + btns->addStretch(); + btns->addWidget(saveBtn); + btns->addWidget(cancelBtn); + layout->addLayout(btns); + + connect(saveBtn, &QPushButton::clicked, &previewDlg, &QDialog::accept); + connect(cancelBtn, &QPushButton::clicked, &previewDlg, &QDialog::reject); + + // 10. Execute Dialog and Save + if (previewDlg.exec() == QDialog::Accepted) { + QString path = QFileDialog::getSaveFileName(this, tr("Save Ronchi Grid"), + imageDir, tr("Images (*.png *.jpg)")); + if (!path.isEmpty()) { + canvas.save(path); + } + } +} +#ifdef NOTNOWOLD WAY void foucaultView::on_makePb_clicked() { m_guiTimer.stop(); @@ -427,7 +766,7 @@ void foucaultView::on_makePb_clicked() QApplication::restoreOverrideCursor(); } - +#endif void foucaultView::on_gammaSb_valueChanged(double /*arg1*/) { m_guiTimer.start(500); diff --git a/foucaultview.h b/foucaultview.h index 23e4be92..8c57d703 100644 --- a/foucaultview.h +++ b/foucaultview.h @@ -2,8 +2,37 @@ #define FOUCAULTVIEW_H #include -#include "surfacemanager.h" +#include +#include +#include #include +#include +#include "surfacemanager.h" + +// Enum to specify which optical test to simulate +enum class OpticalTestType { + Foucault, + Ronchi +}; + +// Parameter container so this can be called without needing the UI pointers +struct OpticalTestSettings { + double rocOffset = 0.0; + double ronchiX = 1.0; + double lpi = 100.0; + double gamma = 1.0; + double slitWidth = 0.001; + bool useMM = true; + bool movingSource = true; + bool knifeOnLeft = false; + bool clearCenter = false; + + // Internal state variables used by the original logic + int heightMultiply = 1; + int lateralOffset = 0; + double outputLambda = 550.0; +}; + namespace Ui { class foucaultView; } @@ -22,63 +51,42 @@ class foucaultView : public QWidget bool saveOnlyFoucault(); bool needsDrawing; + // The Refactored Static Engine - Callable from other classes + static QImage generateOpticalTestImage(OpticalTestType type, wavefront* wf, const OpticalTestSettings& settings); + public slots: void on_makePb_clicked(); - QImage *render(); private slots: void showContextMenu(QPoint pos); - void on_gammaSb_valueChanged(double arg1); - void on_lpiSb_valueChanged(double arg1); - void on_movingSourceRb_clicked(bool /*unused*/); - void on_radioButton_2_clicked(); - void on_knifeOnLeftCb_clicked(); - void on_lpiSb_editingFinished(); - void on_rocOffsetSb_editingFinished(); - void on_slitWidthSb_editingFinished(); - void on_useMM_clicked(bool checked); - void on_scanPb_clicked(); - void on_h1x_clicked(); - void on_h2x_clicked(); - void on_h4x_clicked(); - void on_rocOffsetSlider_valueChanged(int value); - void on_clearCenterCb_clicked(); - void on_autoStepSize_clicked(bool checked); - void on_rocStepSize_editingFinished(); - void on_lateralOffset_valueChanged(int arg1); - void on_SaveImageCB_clicked(bool checked); - void saveRonchiImage(); - void saveFoucaultImage(); - void on_overlayProfile_stateChanged(int arg1); - void on_RonchiX_valueChanged(double arg1); - void on_pushButton_clicked(); - void on_autocollimation_clicked(bool checked); + void showSelectedRonchiImages(); + void generateBatchRonchiImage(const QList& wavefrontList); private: Ui::foucaultView *ui; @@ -96,7 +104,6 @@ private slots: cv::Mat compute_star_test(int pupil_size, double defocus, double pad, bool use_OPD); double getStep(); void draw_ROC_Scale(); - }; #endif // FOUCAULTVIEW_H From de7ec73d9ae3e809fed777144061e43b62791640 Mon Sep 17 00:00:00 2001 From: gr5 Date: Wed, 24 Dec 2025 11:31:48 -0500 Subject: [PATCH 07/13] Dale added a new call to CreateSurfaceFromPhaseMap() and coincidentally I had added a new parameter to that function. Fixed now. Also Dale added a file to one project file but forgot to add to the other 2. --- DFTFringe.pro | 2 ++ DFTFringe_QT5.pro | 2 ++ profileplot.cpp | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DFTFringe.pro b/DFTFringe.pro index a2383a1d..303dcea2 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -243,6 +243,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ surfacegraph.cpp \ surfacelightingproxy.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ transformwavefrontdlg.cpp \ unwraperrorsview.cpp \ usercolormapdlg.cpp \ @@ -361,6 +362,7 @@ HEADERS += bezier/bezier.h \ surfacegraph.h \ surfacelightingproxy.h \ surfacemanager.h \ + wavefrontloaderworker.h \ transformwavefrontdlg.h \ unwraperrorsview.h \ usercolormapdlg.h \ diff --git a/DFTFringe_QT5.pro b/DFTFringe_QT5.pro index c3d9b979..b0a090a7 100644 --- a/DFTFringe_QT5.pro +++ b/DFTFringe_QT5.pro @@ -242,6 +242,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ surfacegraph.cpp \ surfacelightingproxy.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ transformwavefrontdlg.cpp \ unwraperrorsview.cpp \ usercolormapdlg.cpp \ @@ -360,6 +361,7 @@ HEADERS += bezier/bezier.h \ surfacegraph.h \ surfacelightingproxy.h \ surfacemanager.h \ + wavefrontloaderworker.h \ transformwavefrontdlg.h \ unwraperrorsview.h \ usercolormapdlg.h \ diff --git a/profileplot.cpp b/profileplot.cpp index ab0cc0f1..482e5e15 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -1156,5 +1156,5 @@ void ProfilePlot::CreateWaveFrontFromAverage(){ sm->createSurfaceFromPhaseMap(result, m_wf->m_outside, m_wf->m_inside, - QString("avg")); + QString("avg"), WavefrontOrigin::Average); } From 80178b0150f3b6c67273c553c2c4aca4dcbe882c Mon Sep 17 00:00:00 2001 From: gr5 Date: Wed, 24 Dec 2025 16:16:34 -0500 Subject: [PATCH 08/13] Update mainwindow.cpp Co-authored-by: Julien Staub --- mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index 90198428..f94ef72b 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2139,7 +2139,7 @@ void MainWindow::on_actionastig_in_polar_triggered() void MainWindow::on_actionStop_auto_invert_triggered() { - m_surfaceManager->m_inverseMode = invCONIC; + m_surfaceManager->m_inverseMode = invNOTSET; m_mirrorDlg->updateAutoInvertStatus(); //QMessageBox::information(this, "auto invert", "DFTFringe will now ask if it thinks it needs to invert a wave front."); } From 90db8092f70b2deb6ba8fe51e97045ebe984562e Mon Sep 17 00:00:00 2001 From: Julien STAUB Date: Thu, 25 Dec 2025 09:44:17 +0100 Subject: [PATCH 09/13] fix qt6 compilation issue (due to different qwt version ?) --- profileplotpicker.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/profileplotpicker.cpp b/profileplotpicker.cpp index 7be18500..e09ebccb 100644 --- a/profileplotpicker.cpp +++ b/profileplotpicker.cpp @@ -8,6 +8,7 @@ #include #include #include +#include profilePlotPicker::profilePlotPicker( QwtPlot *plot ): QObject( plot ), @@ -163,7 +164,3 @@ void profilePlotPicker::move( QPoint pos ) } - - - - From 5194e628f9a8c337ef847aea6cf86f6a7688c0c8 Mon Sep 17 00:00:00 2001 From: Dale Eason Date: Fri, 26 Dec 2025 16:01:37 -0600 Subject: [PATCH 10/13] added ronchi comparison of two files. Corrected black buttons --- DFTFringe_Dale.pro | 2 + foucaultview.cpp | 335 +++++----------------------------------- foucaultview.ui | 10 -- ronchicomparedialog.cpp | 150 ++++++++++++++++++ ronchicomparedialog.h | 34 ++++ 5 files changed, 228 insertions(+), 303 deletions(-) create mode 100644 ronchicomparedialog.cpp create mode 100644 ronchicomparedialog.h diff --git a/DFTFringe_Dale.pro b/DFTFringe_Dale.pro index d0ee71ea..e0c6cb34 100644 --- a/DFTFringe_Dale.pro +++ b/DFTFringe_Dale.pro @@ -47,6 +47,7 @@ SOURCES += main.cpp \ percentcorrectiondlg.cpp \ profileplot.cpp \ profileplotpicker.cpp \ + ronchicomparedialog.cpp \ settingsigramimportconfig.cpp \ startestmoviedlg.cpp \ surface3dcontrolsdlg.cpp \ @@ -164,6 +165,7 @@ HEADERS += mainwindow.h \ percentcorrectiondlg.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ settingsigramimportconfig.h \ startestmoviedlg.h \ surface3dcontrolsdlg.h \ diff --git a/foucaultview.cpp b/foucaultview.cpp index 66b0d8df..0d6fda2e 100644 --- a/foucaultview.cpp +++ b/foucaultview.cpp @@ -8,7 +8,7 @@ #include #include #include "zernikeprocess.h" - +#include "ronchicomparedialog.h" extern double outputLambda; foucaultView::foucaultView(QWidget *parent, SurfaceManager *sm) : @@ -335,7 +335,6 @@ void foucaultView::on_makePb_clicked() } - void foucaultView::generateBatchRonchiImage(const QList& wavefrontList) { // 1. Initial Checks @@ -367,8 +366,8 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi int rows = (count + cols - 1) / cols; int imgDim = m_wf->data.cols; - int headerHeight = 70; // Space for the parameters at the top - int textBuffer = 40; // Space for labels at the bottom of each image + int headerHeight = 70; + int textBuffer = 40; int cellW = imgDim; int cellH = imgDim + textBuffer; @@ -381,15 +380,17 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi painter.setRenderHint(QPainter::Antialiasing); QApplication::setOverrideCursor(Qt::WaitCursor); - // 5. Draw Simulation Header (Removed Slit) + // NEW: Container for individual Ronchi images to be used in comparison + QList individualRonchis; + QList names; + // 5. Draw Simulation Header painter.setPen(Qt::white); painter.setFont(QFont("Arial", 12, QFont::Bold)); QString unit = s.useMM ? "mm" : "in"; - QString headerText = QString("Ronchi Analysis | LPI: %1 | Offset: %2 %3 | Gamma: %4") - .arg(s.lpi).arg(s.rocOffset).arg(unit).arg(s.gamma); + QString headerText = QString("Ronchi Analysis | LPI: %1 | Offset: %2 %3") + .arg(s.lpi).arg(s.rocOffset).arg(unit); painter.drawText(20, 35, headerText); - // Draw separator line painter.setPen(QPen(Qt::gray, 2)); painter.drawLine(10, headerHeight - 15, canvas.width() - 10, headerHeight - 15); @@ -405,15 +406,17 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi QImage ronchi = generateOpticalTestImage(OpticalTestType::Ronchi, currentWf, s); if (!ronchi.isNull()) { + // Store a copy for the comparison feature + individualRonchis.append(ronchi); + int xPos = col * cellW; - int yPos = headerHeight + (row * cellH); // Offset by header + int yPos = headerHeight + (row * cellH); painter.drawImage(xPos, yPos, ronchi); - // Handle Filename Label QFileInfo fileInfo(currentWf->name); QString displayName = fileInfo.baseName(); - + names << displayName; int textWidth = fm.horizontalAdvance(displayName); int xText = xPos + (cellW - textWidth) / 2; int yText = yPos + imgDim + (textBuffer / 2) + (fm.ascent() / 2); @@ -425,7 +428,7 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi painter.end(); QApplication::restoreOverrideCursor(); - // 7. Configure Preview Dialog (75% Screen Width) + // 7. Configure Preview Dialog QScreen *screen = QGuiApplication::primaryScreen(); int dlgW = static_cast(screen->availableGeometry().width() * 0.75); int dlgH = static_cast(screen->availableGeometry().height() * 0.85); @@ -438,7 +441,7 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi QScrollArea *scroll = new QScrollArea(&previewDlg); scroll->setWidgetResizable(true); scroll->setAlignment(Qt::AlignCenter); - scroll->setStyleSheet("background-color: #1a1a1a;"); + //scroll->setStyleSheet("background-color: #1a1a1a;"); QLabel *imgLabel = new QLabel(); imgLabel->setAlignment(Qt::AlignCenter); @@ -469,19 +472,41 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi connect(slider, &QSlider::valueChanged, updateZoom); updateZoom(100); - // 9. Save and Cancel Buttons + // 9. Navigation and Comparison Buttons QHBoxLayout *btns = new QHBoxLayout(); - QPushButton *saveBtn = new QPushButton(tr("Save Full Resolution")); - QPushButton *cancelBtn = new QPushButton(tr("Cancel")); + + // NEW: Comparison button + QPushButton *compareBtn = new QPushButton(tr("Compare Top Two Patterns")); + compareBtn->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); + // Feature only enabled if 2 or more wavefronts were processed + compareBtn->setEnabled(individualRonchis.size() >= 2); + + QPushButton *saveBtn = new QPushButton(tr("Save Grid Image")); + QPushButton *cancelBtn = new QPushButton(tr("Close")); + + btns->addWidget(compareBtn); btns->addStretch(); btns->addWidget(saveBtn); btns->addWidget(cancelBtn); layout->addLayout(btns); + // Connect the comparison trigger + connect(compareBtn, &QPushButton::clicked, [=, &previewDlg]() { + // [=] copies individualRonchis and names so they stay + // valid even after generateBatchRonchiImage() returns. + if (individualRonchis.size() >= 2) { + RonchiCompareDialog compDlg(individualRonchis[0], names[0], + individualRonchis[1], names[1], &previewDlg); + compDlg.exec(); + } + }); + + connect(saveBtn, &QPushButton::clicked, &previewDlg, &QDialog::accept); connect(cancelBtn, &QPushButton::clicked, &previewDlg, &QDialog::reject); - // 10. Execute Dialog and Save + + // 10. Execute Dialog and Save Grid if (previewDlg.exec() == QDialog::Accepted) { QString path = QFileDialog::getSaveFileName(this, tr("Save Ronchi Grid"), imageDir, tr("Images (*.png *.jpg)")); @@ -490,283 +515,7 @@ void foucaultView::generateBatchRonchiImage(const QList& wavefrontLi } } } -#ifdef NOTNOWOLD WAY -void foucaultView::on_makePb_clicked() -{ - m_guiTimer.stop(); - if (m_wf == 0 ||( m_wf->data.cols == 0)) - return; - if (mirrorDlg::get_Instance()->isEllipse()){ - QMessageBox::warning(0,"warning","Foucaualt is not suppported for flat surfaces"); - return; - } - qDebug() << "slider" << ui->rocOffsetSlider->value(); - QApplication::setOverrideCursor(Qt::WaitCursor); - - double pad = 1.1; - int size = m_wf->data.cols * pad; - size = size/2; - size *= 2; - - pad = (double)size/m_wf->data.cols; - double moving_constant = (ui->movingSourceRb->isChecked()) ? 1. : 2.; - - double gamma = ui->gammaSb->value(); - mirrorDlg *md = mirrorDlg::get_Instance(); - double Radius = md->diameter/2.; - double r2 = Radius * Radius; - double Fnumber = .5 * md->roc/md->diameter; //ROC is twice FL - double unitMultiplyer = 1.; - if (!ui->useMM->isChecked()){ - unitMultiplyer = 25.4; - } - - - double coc_offset_mm = ui->rocOffsetSb->value() * unitMultiplyer; - - double b = (md->roc) + coc_offset_mm; - - double pv = ( sqrt((r2)+(md->roc * md->roc)) - - (sqrt(r2+ b * b) - coc_offset_mm) )/ (md->lambda* 1.E-6); - - std::vector zerns = m_wf->InputZerns; - std::vector newZerns = zerns; - double z3 = pv / ( moving_constant); - - bool oldDoNull = md->doNull; - if (!ui->autocollimation->isChecked()){ - md->doNull = false; - } - - cv::Mat surf_fft; - SimulationsView *sv = SimulationsView::getInstance(0); - newZerns[3] = newZerns[3] - 3 * newZerns[8]; - m_wf->InputZerns = newZerns; - sv->setSurface(m_wf); - - cv::Mat surf_fftRonchi; - surf_fft = sv->computeStarTest( heightMultiply * sv->nulledSurface(z3), size, pad ,true); - surf_fftRonchi = sv->computeStarTest( heightMultiply * - sv->nulledSurface(ui->RonchiX->value() * z3), size, pad ,true); - //showMag(surf_fft, true, "star ", true, gamma); - size = surf_fft.cols; - - int hx = (size -1)/2 + lateralOffset; - m_wf->InputZerns = zerns; - - md->doNull = oldDoNull; - - double hy = hx; - - cv::Mat vknife[] = {cv::Mat::zeros(size,size,CV_64FC1) - ,cv::Mat::zeros(cv::Size(size,size),CV_64FC1)}; - - cv::Mat ronchiGrid[] = {cv::Mat::zeros(size,size,CV_64FC1) - ,cv::Mat::zeros(cv::Size(size,size),CV_64FC1)}; - cv::Mat slit[] = {cv::Mat::zeros(size,size,CV_64FC1) - ,cv::Mat::zeros(cv::Size(size,size),CV_64FC1)}; - - - cv::Mat ronchiSlit[] = {cv::Mat::zeros(size,size,CV_64FC1) - ,cv::Mat::zeros(cv::Size(size,size),CV_64FC1)}; - - // compute real world pixel width. - double pixwidth = outputLambda * 1.E-6* Fnumber * 2./(25.4 * pad); - - double lpi = ui->lpiSb->value() * ((ui->useMM->isChecked()) ? 25.4 : 1.); - - double linewidth = .5/lpi; - int ppl = linewidth/pixwidth; // pixels per line - if (ppl <= 0) - ppl = 1; - - int start = ((double)(size)/2.) -(double)ppl/2.; - bool even = ((start / ppl) % 2) == 0; - - if (ui->clearCenterCb->isChecked()) - even = !even; - - start = ppl - start % (ppl);// + ui->lateralKnifeSb->value() * unitMultiplyer; - - double pixels_per_thou = .001 / pixwidth; - - double slitWidthHalf = pixels_per_thou * ui->slitWidthSb->value() * 1000 * ((ui->useMM->isChecked()) ? 1./25.4 : 1.); - if (slitWidthHalf < .75){ - QString msg = QString("warning the slit width of %1 may too small. Using one pixel slit instead").arg(ui->slitWidthSb->value(), 6, 'f', 5); - QMessageBox::warning(0,"warning", msg); - - } - QString parms = QString(" Pixel width %1 slit size in pixels %2").arg(pixwidth, 6, 'f', 5).arg((int)(2 * slitWidthHalf)); - ui->pixeParms->setText(parms); - // compute offset so that line is at center - for (int y = 0; y < size; ++y) - { - double ry = double(y - hy)/(double)hy; - int roffset = start; - int line_no = 0; - for (int x = 0; x < size; ++x) - { - double rx = double(x - hx)/(double)hx; - double r = sqrt(rx * rx + ry *ry); - - // foucault setup - if (r <= 1.) - { - //slit width is in inches convert to 1/1000 s. - if ((x > hx -slitWidthHalf) && (x < hx + slitWidthHalf)) - { - slit[0].at(y,x) = 255.; - } - } - - int knife_side = x; - if (ui->knifeOnLeftCb->isChecked()) - knife_side = size - x; - - if (knife_side > hx ) - { - vknife[0].at(y,x) = 255.; - } - - - // ronchi setup - if ((even && (line_no%2 == 0)) || (!even && (line_no%2 != 0))) - { - ronchiGrid[0].at(y,x) = 1; - } - if(++roffset >= ppl) - { - ++line_no; - roffset = 0; - } - - - if (x> hx - ppl/2. && x < hx + ppl/2.) - { - ronchiSlit[0].at(y,x) = 1; - } - - } - } - - cv::Mat FFT1, FFT2; - //fftw_plan p; - cv::Mat complexIn; - cv::Mat complexIn2; - - merge(ronchiGrid, 2, complexIn); - merge(ronchiSlit,2,complexIn2); - - //showData("grid", ronchiGrid[0]); - //showData("rslit", ronchiSlit[0]); - - - dft(complexIn, FFT1, cv::DFT_REAL_OUTPUT); - shiftDFT(FFT1); - dft(complexIn2, FFT2, cv::DFT_REAL_OUTPUT); - shiftDFT(FFT2); - cv::Mat knifeSlit; - mulSpectrums(FFT1, FFT2, knifeSlit, 0, true); - idft(knifeSlit, knifeSlit, cv::DFT_SCALE); // gives us the correlation result... - shiftDFT(knifeSlit); - cv::Mat knifeSurf; - - mulSpectrums(knifeSlit, surf_fftRonchi, knifeSurf,0,true); - idft(knifeSurf, knifeSurf, cv::DFT_SCALE); - shiftDFT(knifeSurf); - - QImage ronchi = showMag(knifeSurf, false,"", false, gamma); - int startx = size - m_wf->data.cols; - ronchi = ronchi.copy(startx,startx,m_wf->data.cols, m_wf->data.cols); - - ronchi = ronchi.mirrored(true,false); - QSize s = ui->ronchiViewLb->size(); - QPixmap rp = QPixmap::fromImage(ronchi.scaledToWidth(s.width())); - - QPainter painter(&rp); - painter.save(); - painter.drawPixmap(0, 0, rp); - painter.setPen(QPen(QColor(Qt::white))); - painter.setFont(QFont("Arial", 15)); - QString zoffsetStr = QString("%1 %2").arg(ui->RonchiX->value() * ui->rocOffsetSb->value(), 6, 'f', 3) - .arg(ui->useMM->isChecked()? "mm" : "in"); - painter.drawText(20, 40, zoffsetStr); - QVector profilePoints; - if (ui->overlayProfile->isChecked()){ - // overlay profile onto ronchi plot - QPolygonF profile = m_sm->m_profilePlot->createProfile(1.,m_wf); - profilePoints= scaleProfile(profile, rp.width(), M_PI/4.); - painter.setPen(QPen(QColor(Qt::yellow),3)); - painter.drawLines(profilePoints); - - } - - painter.restore(); - - - ui->ronchiViewLb->setPixmap(rp); - - merge(vknife, 2, complexIn); - merge(slit,2,complexIn2); - - - dft(complexIn, FFT1, cv::DFT_REAL_OUTPUT); - dft(complexIn2, FFT2, cv::DFT_REAL_OUTPUT); - - mulSpectrums(FFT1, FFT2, knifeSlit, 0, true); - idft(knifeSlit, knifeSlit, cv::DFT_SCALE); // gives us the correlation result... - - - mulSpectrums(knifeSlit, surf_fft, knifeSurf,0,true); - idft(knifeSurf, knifeSurf, cv::DFT_SCALE); - - m_foucultQimage = showMag(knifeSurf, false,"", false, gamma); - - startx = size - m_wf->data.cols; - QImage foucault = m_foucultQimage.copy(startx,startx,m_wf->data.cols, m_wf->data.cols); - qDebug() << "foucult" << foucault.size(); -/* - cv::Mat iMat(foucault.height(), foucault.width(), CV_8UC3, foucault.bits(), foucault.bytesPerLine()); - cv::Mat flipped; - cv::flip(iMat,flipped, 1); - cv::Mat diffed; - cv::absdiff(iMat, flipped, diffed); - diffed = diffed * 10; - cv::imshow("diffed", diffed); - cv::waitKey(1); - foucault = QImage((uchar*)diffed.data, diffed.cols, diffed.rows, diffed.step, QImage::Format_RGB888).copy(); -*/ - foucault = foucault.mirrored(true, false); - s = ui->foucaultViewLb->size(); - - QPixmap rpf = QPixmap::fromImage(foucault.scaledToWidth(s.width())); - - QPainter painterf(&rpf); - painterf.save(); - painterf.drawPixmap(0, 0, rpf); - painterf.setPen(QPen(QColor(Qt::white))); - painterf.setFont(QFont("Arial", 15)); - zoffsetStr = QString("%1 %2").arg(ui->rocOffsetSb->value(), 6 , 'f', 3) - .arg(ui->useMM->isChecked()? "mm" : "in"); - painterf.drawText(20, 40, zoffsetStr); - if (ui->overlayProfile->isChecked()){ - // overlay profile onto ronchi plot - painterf.setPen(QPen(QColor(Qt::yellow),3)); - painterf.drawLines(profilePoints); - } - - painterf.restore(); - - - ui->foucaultViewLb->setPixmap(rpf); - //ui->foucaultViewLb->setPixmap(QPixmap::fromImage(foucault.scaledToWidth(s.width()))); - - - - QApplication::restoreOverrideCursor(); -} -#endif void foucaultView::on_gammaSb_valueChanged(double /*arg1*/) { m_guiTimer.start(500); diff --git a/foucaultview.ui b/foucaultview.ui index 9d5c24e4..613c55e9 100644 --- a/foucaultview.ui +++ b/foucaultview.ui @@ -13,15 +13,6 @@ Form - - QPushButton{ border-style: outset; - background-color: rgb(0, 0, 0); - border-width: 3px; - border-radius: 7px; - border-color: darkgray; - font: normal 12px; - padding: 2px; } - @@ -523,7 +514,6 @@ - -1 false false diff --git a/ronchicomparedialog.cpp b/ronchicomparedialog.cpp new file mode 100644 index 00000000..d515dc14 --- /dev/null +++ b/ronchicomparedialog.cpp @@ -0,0 +1,150 @@ +#include "ronchicomparedialog.h" +#include +#include +#include +#include +#include +#include +#include +RonchiCompareDialog::RonchiCompareDialog(const QImage& img1, const QString name1, const QImage &img2, + const QString name2, QWidget* parent) + : QDialog(parent), m_q1(img1), m_q2(img2) +{ + setWindowTitle(tr("Ronchi Comparison Overlay")); + + + QScreen *screen = QGuiApplication::primaryScreen(); + int dlgW = static_cast(screen->availableGeometry().width() * 0.75); + int dlgH = static_cast(screen->availableGeometry().height() * 0.85); + resize(dlgW, dlgH); + // Main layout for the dialog + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + // 1. Color Legend Labels at the top + QHBoxLayout* legendLayout = new QHBoxLayout(); + + m_baseLabel = new QLabel(name1); + m_baseLabel->setStyleSheet("color: #00FFFF; font-weight: bold; font-size: 18px;"); + // 1. Get the current font + QFont font = m_baseLabel->font(); + + // 2. Set the desired font size in points (device-independent) + font.setPointSize(18); + m_baseLabel->setFont(font); + + m_compLabel = new QLabel(name2); + m_compLabel->setStyleSheet("color: #FF0000; font-weight: bold; font-size: 18px;"); + + legendLayout->addWidget(m_baseLabel); + legendLayout->addStretch(); + legendLayout->addWidget(m_compLabel); + mainLayout->addLayout(legendLayout); + + // 2. Main Image Display Area + m_displayLabel = new QLabel(); + m_displayLabel->setAlignment(Qt::AlignCenter); + m_displayLabel->setStyleSheet("background-color: black; border: 5px solid #555;"); + m_displayLabel->setMinimumSize(dlgW * .8, dlgH * .8); + mainLayout->addWidget(m_displayLabel, 1); + + // 3. Controls Area + mainLayout->addWidget(new QLabel(tr("Blend Ratio (Slide to compare difference):"))); + + QSlider* slider = new QSlider(Qt::Horizontal); + slider->setRange(0, 100); + slider->setValue(50); + mainLayout->addWidget(slider); + + QPushButton* saveBtn = new QPushButton(tr("Save This Comparison Image")); + mainLayout->addWidget(saveBtn); + + // Signal/Slot Connections + connect(slider, &QSlider::valueChanged, this, &RonchiCompareDialog::updateOverlay); + connect(saveBtn, &QPushButton::clicked, this, &RonchiCompareDialog::onSaveClicked); + + // Perform initial render + updateOverlay(50); + +} + +RonchiCompareDialog::~RonchiCompareDialog() +{ + // Destructor implementation to satisfy vtable requirements +} + +cv::Mat RonchiCompareDialog::qImageToMat(const QImage& image) +{ + // Force conversion to 4-channel ARGB to ensure predictable memory layout for OpenCV + QImage swapped = image.convertToFormat(QImage::Format_ARGB32); + // clone() is critical here to ensure the Mat owns its data buffer + return cv::Mat(swapped.height(), swapped.width(), CV_8UC4, + const_cast(swapped.bits()), + swapped.bytesPerLine()).clone(); +} + +void RonchiCompareDialog::updateOverlay(int val) +{ + double alpha = (100 - val) / 100.0; + + cv::Mat m1 = qImageToMat(m_q1); + cv::Mat m2 = qImageToMat(m_q2); + + if (m1.empty() || m2.empty()) return; + + // Convert to grayscale for initial processing + cv::cvtColor(m1, m1, cv::COLOR_BGRA2GRAY); + cv::cvtColor(m2, m2, cv::COLOR_BGRA2GRAY); + + + + // SAFETY: Match sizes to prevent OpenCV arithm.cpp crashes + if (m1.size() != m2.size()) { + cv::resize(m2, m2, m1.size(), 0, 0, cv::INTER_LINEAR); + } + cv::Mat zeros = cv::Mat::zeros(m1.size(), CV_8UC1); + // 2. Threshold the image + // Target: pixels < 127 (approx 1/2 intensity) + // We create a binary mask where the pixels below 128 are "active" (255) + + cv::Mat mask1, mask2; + + cv::threshold(m1, mask1, 127, 255, cv::THRESH_BINARY_INV); + // Grayscale values below threshold are preserved as intensity in specific channels + cv::Mat zero = cv::Mat::zeros(m1.size(), CV_8UC1); + std::vector channels1; + channels1 = {zero, zero, m1}; + cv::Mat result1; + cv::merge(channels1, result1); + + + cv::threshold(m2, mask2, 127, 255, cv::THRESH_BINARY_INV); + cv::Mat result2; + std::vector channels2; + channels2 = {m2, m2, zero}; + + cv::merge(channels2, result2); + + + + + + // Perform the additive blend (Overlay) + cv::addWeighted(result1, 1.0 - alpha, result2, alpha, 0.0, m_currentMat); + + // Convert the result back to QImage for the Qt Label + QImage res(m_currentMat.data, m_currentMat.cols, m_currentMat.rows, + m_currentMat.step, QImage::Format_RGB888); + + // Display result (Swap BGR back to RGB for correct colors in Qt) + m_displayLabel->setPixmap(QPixmap::fromImage(res.rgbSwapped()) + .scaled(m_displayLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); +} + +void RonchiCompareDialog::onSaveClicked() +{ + QString path = QFileDialog::getSaveFileName(this, tr("Save Ronchi Comparison"), + "", tr("PNG Images (*.png);;JPG Images (*.jpg)")); + if (!path.isEmpty()) { + cv::imwrite(path.toStdString(), m_currentMat); + } +} diff --git a/ronchicomparedialog.h b/ronchicomparedialog.h new file mode 100644 index 00000000..4061c29c --- /dev/null +++ b/ronchicomparedialog.h @@ -0,0 +1,34 @@ +#ifndef RONCHICOMPAREDIALOG_H +#define RONCHICOMPAREDIALOG_H + +#include +#include +#include +#include +#include + +class RonchiCompareDialog : public QDialog { + Q_OBJECT + +public: + explicit RonchiCompareDialog(const QImage& img1, const QString name1, const QImage &img2, + const QString name2, QWidget* parent); + virtual ~RonchiCompareDialog(); + +private slots: + void updateOverlay(int val); + void onSaveClicked(); + +private: + cv::Mat qImageToMat(const QImage& image); + + QImage m_q1; + QImage m_q2; + cv::Mat m_currentMat; + + QLabel* m_displayLabel; + QLabel* m_baseLabel; + QLabel* m_compLabel; +}; + +#endif // RONCHICOMPAREDIALOG_H From cfa0e9ab8185df2e9d7516fe2b897f2b4dccccf2 Mon Sep 17 00:00:00 2001 From: gr5 Date: Sat, 27 Dec 2025 12:49:37 -0500 Subject: [PATCH 11/13] Dale edited one project file but not the other two. Fixed now. Hopefully. --- DFTFringe.pro | 2 ++ DFTFringe_QT5.pro | 2 ++ 2 files changed, 4 insertions(+) diff --git a/DFTFringe.pro b/DFTFringe.pro index 303dcea2..f722d1bd 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -212,6 +212,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ plotcolor.cpp \ profileplot.cpp \ profileplotpicker.cpp \ + ronchicomparedialog.cpp \ psfplot.cpp \ psi_dlg.cpp \ psiphasedisplay.cpp \ @@ -331,6 +332,7 @@ HEADERS += bezier/bezier.h \ plotcolor.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ psfplot.h \ psi_dlg.h \ psiphasedisplay.h \ diff --git a/DFTFringe_QT5.pro b/DFTFringe_QT5.pro index b0a090a7..e7c3fbdf 100644 --- a/DFTFringe_QT5.pro +++ b/DFTFringe_QT5.pro @@ -211,6 +211,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ plotcolor.cpp \ profileplot.cpp \ profileplotpicker.cpp \ + ronchicomparedialog.cpp \ psfplot.cpp \ psi_dlg.cpp \ psiphasedisplay.cpp \ @@ -330,6 +331,7 @@ HEADERS += bezier/bezier.h \ plotcolor.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ psfplot.h \ psi_dlg.h \ psiphasedisplay.h \ From cc7f73f535d6facc97d740e606c9dba58ca52fcb Mon Sep 17 00:00:00 2001 From: gr5 Date: Sat, 27 Dec 2025 15:18:52 -0500 Subject: [PATCH 12/13] Update surfacemanager.cpp Yeah we need this to be "not set". Note that wavefront files (and much more) no longer prompt the users to change the wavefront. Co-authored-by: Julien Staub --- surfacemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfacemanager.cpp b/surfacemanager.cpp index 34e6d5ad..2d1d6899 100644 --- a/surfacemanager.cpp +++ b/surfacemanager.cpp @@ -535,7 +535,7 @@ SurfaceManager::SurfaceManager(QObject *parent, surfaceAnalysisTools *tools, m_surfaceTools(tools),m_profilePlot(profilePlot), m_contourView(contourView), m_SurfaceGraph(glPlot), m_metrics(mets),m_gbValue(21), m_GB_enabled(false),m_currentNdx(-1),m_standAvg(0),insideOffset(0),outsideOffset(0), - m_inverseMode(invCONIC),m_ignoreInverse(false), m_standAstigWizard(nullptr), workToDo(0), m_wftStats(0) + m_inverseMode(invNOTSET),m_ignoreInverse(false), m_standAstigWizard(nullptr), workToDo(0), m_wftStats(0) { okToUpdateSurfacesOnGenerateComplete = true; From 4a5293bbb2ca491645e03358e15c7564a0df27e7 Mon Sep 17 00:00:00 2001 From: gr5 Date: Sat, 27 Dec 2025 15:20:21 -0500 Subject: [PATCH 13/13] Update plotcolor.cpp Co-authored-by: Julien Staub --- plotcolor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotcolor.cpp b/plotcolor.cpp index 9ff08123..601a01f8 100644 --- a/plotcolor.cpp +++ b/plotcolor.cpp @@ -1,6 +1,6 @@ #include "plotcolor.h" const char *plotColors[]= -{ "BLack", +{ "Black", "Red", "Green", "LightSalmon",