diff --git a/DFTFringe.pro b/DFTFringe.pro index a2383a1d..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 \ @@ -243,6 +244,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ surfacegraph.cpp \ surfacelightingproxy.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ transformwavefrontdlg.cpp \ unwraperrorsview.cpp \ usercolormapdlg.cpp \ @@ -330,6 +332,7 @@ HEADERS += bezier/bezier.h \ plotcolor.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ psfplot.h \ psi_dlg.h \ psiphasedisplay.h \ @@ -361,6 +364,7 @@ HEADERS += bezier/bezier.h \ surfacegraph.h \ surfacelightingproxy.h \ surfacemanager.h \ + wavefrontloaderworker.h \ transformwavefrontdlg.h \ unwraperrorsview.h \ usercolormapdlg.h \ diff --git a/DFTFringe_Dale.pro b/DFTFringe_Dale.pro index 17d54447..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 \ @@ -59,6 +60,7 @@ SOURCES += main.cpp \ dftcolormap.cpp \ surfaceanalysistools.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ zernikedlg.cpp \ zernikepolar.cpp \ zernikeprocess.cpp \ @@ -163,6 +165,7 @@ HEADERS += mainwindow.h \ percentcorrectiondlg.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ settingsigramimportconfig.h \ startestmoviedlg.h \ surface3dcontrolsdlg.h \ @@ -175,6 +178,7 @@ HEADERS += mainwindow.h \ dftcolormap.h \ surfaceanalysistools.h \ surfacemanager.h \ + wavefrontloaderworker.h \ zernikedlg.h \ zernikepolar.h \ zernikeprocess.h \ diff --git a/DFTFringe_QT5.pro b/DFTFringe_QT5.pro index c3d9b979..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 \ @@ -242,6 +243,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ surfacegraph.cpp \ surfacelightingproxy.cpp \ surfacemanager.cpp \ + wavefrontloaderworker.cpp \ transformwavefrontdlg.cpp \ unwraperrorsview.cpp \ usercolormapdlg.cpp \ @@ -329,6 +331,7 @@ HEADERS += bezier/bezier.h \ plotcolor.h \ profileplot.h \ profileplotpicker.h \ + ronchicomparedialog.h \ psfplot.h \ psi_dlg.h \ psiphasedisplay.h \ @@ -360,6 +363,7 @@ HEADERS += bezier/bezier.h \ surfacegraph.h \ surfacelightingproxy.h \ surfacemanager.h \ + wavefrontloaderworker.h \ transformwavefrontdlg.h \ unwraperrorsview.h \ usercolormapdlg.h \ 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 2c4bfbcd..66f818e8 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/foucaultview.cpp b/foucaultview.cpp index e31d2bd7..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) : @@ -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,280 +169,351 @@ QVector scaleProfile(QPolygonF points, int width, return results; } -void foucaultView::on_makePb_clicked() + + +QImage foucaultView::generateOpticalTestImage(OpticalTestType type, wavefront* wf, const OpticalTestSettings& s) { - 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); + if (!wf || wf->data.cols == 0) return QImage(); + // 1. Setup Constants 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.; + 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; - 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); + 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; - 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; - } - + 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; } } + } - 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); + // 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); } - painter.restore(); + 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); - ui->ronchiViewLb->setPixmap(rp); + // 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); +} - merge(vknife, 2, complexIn); - merge(slit,2,complexIn2); +void foucaultView::on_makePb_clicked() +{ + m_guiTimer.stop(); + if (m_wf == nullptr || m_wf->data.cols == 0) + return; - dft(complexIn, FFT1, cv::DFT_REAL_OUTPUT); - dft(complexIn2, FFT2, cv::DFT_REAL_OUTPUT); + if (mirrorDlg::get_Instance()->isEllipse()){ + QMessageBox::warning(0,"warning","Foucaualt is not suppported for flat surfaces"); + return; + } - mulSpectrums(FFT1, FFT2, knifeSlit, 0, true); - idft(knifeSlit, knifeSlit, cv::DFT_SCALE); // gives us the correlation result... + 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); + }; - mulSpectrums(knifeSlit, surf_fft, knifeSurf,0,true); - idft(knifeSurf, knifeSurf, cv::DFT_SCALE); + // 4. Update the actual labels + paintAndDisplay(ui->ronchiViewLb, ronchiImg, settings.ronchiX * settings.rocOffset); + paintAndDisplay(ui->foucaultViewLb, foucaultImg, settings.rocOffset); - m_foucultQimage = showMag(knifeSurf, false,"", false, gamma); + QApplication::restoreOverrideCursor(); +} - 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())); +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; + int textBuffer = 40; + + 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); - 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); + // 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") + .arg(s.lpi).arg(s.rocOffset).arg(unit); + painter.drawText(20, 35, headerText); + + 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()) { + // Store a copy for the comparison feature + individualRonchis.append(ronchi); + + int xPos = col * cellW; + int yPos = headerHeight + (row * cellH); + + painter.drawImage(xPos, yPos, ronchi); + + 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); + + painter.setPen(Qt::yellow); + painter.drawText(xText, yText, displayName); + } } + painter.end(); + QApplication::restoreOverrideCursor(); - painterf.restore(); - + // 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); + + 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. Navigation and Comparison Buttons + QHBoxLayout *btns = new QHBoxLayout(); + + // 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(); + } + }); - ui->foucaultViewLb->setPixmap(rpf); - //ui->foucaultViewLb->setPixmap(QPixmap::fromImage(foucault.scaledToWidth(s.width()))); + connect(saveBtn, &QPushButton::clicked, &previewDlg, &QDialog::accept); + connect(cancelBtn, &QPushButton::clicked, &previewDlg, &QDialog::reject); - QApplication::restoreOverrideCursor(); + // 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)")); + if (!path.isEmpty()) { + canvas.save(path); + } + } } void foucaultView::on_gammaSb_valueChanged(double /*arg1*/) 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 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/plotcolor.cpp b/plotcolor.cpp index ca265b1e..601a01f8 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 3a75b537..482e5e15 100644 --- a/profileplot.cpp +++ b/profileplot.cpp @@ -50,13 +50,17 @@ #include "zernikeprocess.h" #include #include "plotcolor.h" -#include "profileplotpicker.h" + 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) @@ -77,21 +81,14 @@ ProfilePlot::ProfilePlot(QWidget *parent , ContourTools *tools): m_plot = new QwtPlot(this); m_plot->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_plot, &QwtPlot::customContextMenuRequested, this, &ProfilePlot::showContextMenu); - - new profilePlotPicker(m_plot); - type = ProfileType::SHOW_ONE; + m_plot->setCanvasBackground(Qt::darkGray); + 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); 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); @@ -224,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; @@ -259,23 +263,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; @@ -307,7 +295,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); @@ -337,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(); } @@ -422,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.; @@ -479,6 +469,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 +485,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,9 +537,103 @@ void ProfilePlot::make_correction_graph(){ m_pcdlg->setData(surfs); } -void ProfilePlot::populate() + + + + + + +// 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() +{ +qDebug() << "Populate"; m_plot->detachItems(QwtPlotItem::Rtti_PlotItem); compass->setGeometry(QRect(80,80,70,70)); QString tmp("nanometers"); @@ -611,120 +695,15 @@ void ProfilePlot::populate() m_plot->detachItems( QwtPlotItem::Rtti_PlotMarker); - - 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; - } - - case ProfileType::SHOW_16: { - - surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); - QList list = saTools->SelectedWaveFronts(); - 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 (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++])); - } - 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; - - - } - - - break; - } - case ProfileType::SHOW_ALL:{ - 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; - } - } + // if no wave front was selected then use the last one + for (int i = 0; i < list.size(); ++i){ - QStringList path = wfs->at(list[i])->name.split("/"); + wavefront* wf = wfs->at(list[i]); + QStringList path = wf->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","")) @@ -732,16 +711,121 @@ qDebug() << "name " << name; 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 ); - } - break; + 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)){ + + cprofile->setSamples( createProfile( units,wf, true)); + cprofile->attach( m_plot ); + } + if (m_show_16_diameters){ + // compute 16 diameters + QColor penColor = QColor(Settings2::m_profile->getColor(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; + qDebug() << "yoffsets" << m_waveFrontyOffsets << title.text(); + 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( penColor); + 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) + 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 (m_show_16_diameters){ + cprofile->setSamples( points); + cprofile->attach( m_plot ); + } + } + } + if (m_showAvg){ + 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 + 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){ + rr *= wf->diameter/2; + rr /=25.4; + } + else { // convert rr into mm. + rr *= wf->diameter/2; + + } + double val = (units * avgRadius[i] * wf->lambda/outputLambda) + y_offset * units; + + 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 // ...a horizontal line at y = 0... @@ -815,15 +899,52 @@ 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 *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); + show16->setChecked(m_show_16_diameters); + connect(show16, &QAction::triggered, this, &ProfilePlot::toggleShow16); + myMenu.addAction(show16); + + 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 *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."); + myMenu.addAction(showCorrection); // Show context menu at handling position myMenu.exec(globalPos); @@ -833,6 +954,24 @@ void ProfilePlot::saveXscaleSettings(){ set.setValue("xScalepercent", m_displayPercent); set.setValue("xScaleInches", m_displayInches); } +void ProfilePlot::toggleShow16(){ + m_show_16_diameters = !m_show_16_diameters; +qDebug() << "show 16"; + populate(); + m_plot->replot(); +} +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; @@ -900,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"), WavefrontOrigin::Average); +} diff --git a/profileplot.h b/profileplot.h index 474181e1..2071d999 100644 --- a/profileplot.h +++ b/profileplot.h @@ -31,6 +31,7 @@ #include #include #include "percentcorrectiondlg.h" +#include "profileplotpicker.h" namespace Ui { class ProfilePlot; } @@ -54,13 +55,14 @@ 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_showAvg = false; + bool m_show_16_diameters = false; + bool m_show_oneAngle = true; double slopeLimitArcSec; @@ -76,9 +78,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,13 +92,14 @@ public slots: void showXPercent(); void showXInches(); void showXMM(); + void toggleShowAvg(); + void toggleShow16(); + void toggleOneAngle(); + void CreateWaveFrontFromAverage(); + void SetYoffset(QString name, double value); //QPolygonF createZernProfile(wavefront *wf); private: - enum class ProfileType { - SHOW_ONE = 0, - SHOW_16 = 1, - SHOW_ALL = 2 - }; + void updateGradient(); void saveXscaleSettings(); @@ -115,12 +116,11 @@ public slots: bool m_displayInches = false; private: - ProfileType type; - - 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 ce68e902..e09ebccb 100644 --- a/profileplotpicker.cpp +++ b/profileplotpicker.cpp @@ -8,6 +8,7 @@ #include #include #include +#include profilePlotPicker::profilePlotPicker( QwtPlot *plot ): QObject( plot ), @@ -131,10 +132,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; @@ -149,7 +150,7 @@ qDebug() << "Move" << plot()->invTransform(d_selectedCurve->yAxis(), pos.y()); 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. @@ -163,7 +164,3 @@ qDebug() << "Move" << plot()->invTransform(d_selectedCurve->yAxis(), pos.y()); } - - - - 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/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 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 + + + + diff --git a/surfacemanager.cpp b/surfacemanager.cpp index acb6c010..2d1d6899 100644 --- a/surfacemanager.cpp +++ b/surfacemanager.cpp @@ -1109,6 +1109,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_origin = origin; wf->m_outside = outside; diff --git a/surfacemanager.h b/surfacemanager.h index ac19c46b..ba1df11a 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; 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