diff --git a/contourplot.cpp b/contourplot.cpp index fa16d270..8e9179ce 100644 --- a/contourplot.cpp +++ b/contourplot.cpp @@ -32,22 +32,29 @@ #include #include "wavefront.h" #include -#include #include #include #include "dftcolormap.h" #include #include #include "utils.h" +#include #include #include #include +#include +#include #include "spdlog/spdlog.h" +#include double zOffset = 0; double lastx = -1.; double lasty = -1.; + +// ============================================================================ +// MyZommer - Custom zoomer to show data value at cursor +// ============================================================================ class MyZoomer: public QwtPlotZoomer { public: @@ -58,26 +65,84 @@ class MyZoomer: public QwtPlotZoomer setTrackerMode( AlwaysOn ); } - virtual QwtText trackerTextF( const QPointF &pos ) const + // Override zoom rectangle to maintain image aspect ratio + void zoom(const QRectF &rect) override + { + if ((thePlot == nullptr) || (thePlot->m_wf == nullptr)) + { + QwtPlotZoomer::zoom(rect); + return; + } + + // Get the canvas dimensions + QWidget *canvas = thePlot->canvas(); + double canvasWidth = canvas->width(); + double canvasHeight = canvas->height(); + if (canvasWidth <= 0 || canvasHeight <= 0) + { + QwtPlotZoomer::zoom(rect); + return; + } + + // Get the selected zoom rectangle dimensions + double selWidth = rect.width(); + double selHeight = rect.height(); + + // Use shared aspect ratio adjustment logic to match canvas aspect ratio + QRectF adjustedRect = thePlot->adjustRectToAspectRatio( + selWidth, selHeight, canvasWidth, canvasHeight); + + // Translate adjusted dimensions back to rect position + QRectF zoomRect(rect.left() + adjustedRect.left(), + rect.top() + adjustedRect.top(), + adjustedRect.width(), + adjustedRect.height()); + + // Set guard to prevent intermediate aspect-ratio updates while zooming + thePlot->m_inZoomOperation = true; + QwtPlotZoomer::zoom(zoomRect); + // Ensure guard is cleared on next event loop tick in case rescale() isn't called + QTimer::singleShot(0, thePlot, SLOT(clearZoomFlag())); + } + + // Override zoom state change to reapply aspect ratio when unzooming + void rescale() override + { + QwtPlotZoomer::rescale(); + + // Check if we're back at the base zoom level + if ((thePlot != nullptr) && zoomRectIndex() == 0) + { + thePlot->updateAspectRatio(); + } + + // Clear zoom guard (may have been set in zoom()) + if (thePlot != nullptr){ + thePlot->clearZoomFlag(); + } + } + + // when holding shift key, show data value at cursor + QwtText trackerTextF( const QPointF &pos ) const override { if (thePlot->m_wf == 0) - return QwtText(""); + return QwtText(""); if(!QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) return QwtText(""); if (pos.x() == lastx && pos.y() == lasty){ - double v = thePlot->d_spectrogram->data()->value(pos.x(),pos.y()); - QString t = QString("%1").arg(v, 0, 'f'); + double v = thePlot->d_spectrogram->data()->value(pos.x(),pos.y()); + QString t = QString("%1").arg(v, 0, 'f'); - QwtText label(t); + QwtText label(t); - label.setColor(QColor(255,255,255)); - label.setBorderPen(QPen(QColor(100,0,0), 3)); - label.setBorderRadius(5); - label.setBackgroundBrush(QColor(65, 177, 225, 150)); + label.setColor(QColor(255,255,255)); + label.setBorderPen(QPen(QColor(100,0,0), 3)); + label.setBorderRadius(5); + label.setBackgroundBrush(QColor(65, 177, 225, 150)); - QFont font("MS Shell Dlg 2", 18); - label.setFont(font); - return QwtText(label); + QFont font("MS Shell Dlg 2", 18); + label.setFont(font); + return QwtText(label); } lastx = pos.x(); lasty = pos.y(); @@ -100,6 +165,30 @@ class MyZoomer: public QwtPlotZoomer void select(QString); }; +// ============================================================================ +// SpectrogramData - Raster data provider for the spectrogram display +// ============================================================================ +class SpectrogramData: public QwtRasterData +{ +public: + SpectrogramData(); + const wavefront *m_wf; + void setSurface(const wavefront *surface); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + // keep compatibility with newer version of QWT used in QT6 + QwtInterval interval(Qt::Axis axis) const override; + void setInterval(Qt::Axis axis, const QwtInterval &interval); +#endif + virtual double value( double x, double y ) const override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + // keep compatibility with newer version of QWT used in QT6 +private: + QwtInterval m_xInterval; + QwtInterval m_yInterval; + QwtInterval m_zInterval; +#endif +}; SpectrogramData::SpectrogramData(): m_wf(0) { @@ -142,7 +231,7 @@ void SpectrogramData::setInterval(Qt::Axis axis, const QwtInterval &interval) } #endif -void SpectrogramData::setSurface(wavefront *surface) { +void SpectrogramData::setSurface(const wavefront *surface) { m_wf = surface; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // keep compatibility with newer version of QWT used in QT6 @@ -155,8 +244,7 @@ void SpectrogramData::setSurface(wavefront *surface) { setInterval( Qt::YAxis, QwtInterval(0, m_wf->workData.rows)); #endif } -#include -extern double g_angle; + double SpectrogramData::value( double x, double y ) const { @@ -173,6 +261,10 @@ double SpectrogramData::value( double x, double y ) const } +// ============================================================================ +// ContourPlot - Main contour plot class +// ============================================================================ + void ContourPlot::setColorMap(int ndx){ QwtInterval iz = d_spectrogram->data()->interval( Qt::ZAxis ); d_spectrogram->setColorMap( new dftColorMap(ndx,m_wf,!m_useMiddleOffset )); @@ -182,8 +274,6 @@ void ContourPlot::setColorMap(int ndx){ setAxisScale( QwtPlot::yRight, iz.minValue() ,iz.maxValue() ); } - - void ContourPlot::ContourMapColorChanged(int ndx) { m_colorMapNdx = ndx; QSettings set; @@ -198,8 +288,6 @@ void ContourPlot::contourWaveRangeChanged(double val ){ replot(); } - - void ContourPlot::showContoursChanged(double val){ QSettings set; set.setValue("contourRange",val); @@ -299,10 +387,6 @@ void ContourPlot::drawCanvas(QPainter* p) QwtPlot::drawCanvas( p ); // <<--- } -#include -#include -#include - void ContourPlot::ruler(){ detachItems(QwtPlotItem::Rtti_PlotShape); @@ -372,9 +456,6 @@ void ContourPlot::ruler(){ xAxis->setYValue(m_wf->data.rows/2); xAxis->setLinePen(Qt::black,2); xAxis->attach(this); - - - } } @@ -394,7 +475,6 @@ void ContourPlot::selected(QPointF pos){ if (m_linkProfile) emit sigPointSelected(pos); m_lastAngle = angle; - } } @@ -428,7 +508,7 @@ void ContourPlot::drawProfileLine(const double angle){ } -void ContourPlot::setSurface(wavefront * wf) { +void ContourPlot::setSurface(const wavefront * wf) { m_wf = wf; if (wf == 0) return; @@ -451,11 +531,10 @@ void ContourPlot::setSurface(wavefront * wf) { rightAxis->setColorBarEnabled( true ); rightAxis->setColorBarWidth(30); if (!m_minimal){ - enableAxis( QwtPlot::yRight ); + enableAxis(QwtPlot::yRight); enableAxis(QwtPlot::yLeft); } - else - { + else{ enableAxis(QwtPlot::yLeft, false); enableAxis(QwtPlot::xBottom, false); } @@ -471,21 +550,34 @@ void ContourPlot::setSurface(wavefront * wf) { setFooter(name + QString(" %1 rms %2 X %3").arg(wf->std, 6, 'f', 3).arg(wf->data.cols).arg(wf->data.rows)); + setAxisScale(QwtPlot::xBottom, 0, wf->data.cols); + setAxisScale(QwtPlot::yLeft, 0, wf->data.rows); + + // Enforce aspect ratio on first draw + updateAspectRatio(); + + // Set canvas alignment after rescale plotLayout()->setAlignCanvasToScales(true); - showContoursChanged(contourRange); + + spdlog::get("logger")->trace("ContourPlot::setSurface {}x{}", wf->data.cols, wf->data.rows); + + showContoursChanged(contourRange); //TODO setSurface should not have to call showContoursChanged tracker_->setZoomBase(true); replot(); //resize(QSize(width()-1,height()-1)); //resize(QSize(width()+1,height()+1)); } + double ContourPlot::m_waveRange; bool ContourPlot::m_useMiddleOffset = true; int ContourPlot::m_colorMapNdx = 0; QString ContourPlot::m_zRangeMode("Auto"); double ContourPlot::m_zOffset = 0.; + ContourPlot::ContourPlot( QWidget *parent, ContourTools *tools, bool minimal ): - QwtPlot( parent ),m_wf(0),m_tools(tools), m_autoInterval(false),m_minimal(minimal), m_linkProfile(true),m_contourPen(Qt::white) + QwtPlot( parent ),m_wf(0),m_tools(tools),m_minimal(minimal), m_linkProfile(true), m_inZoomOperation(false), m_contourPen(Qt::white) { + spdlog::get("logger")->trace("ContourPlot::ContourPlot"); d_spectrogram = new QwtPlotSpectrogram(); picker_ = new QwtPlotPicker(this->canvas()); picker_->setStateMachine(new QwtPickerClickPointMachine); @@ -507,7 +599,9 @@ ContourPlot::ContourPlot( QWidget *parent, ContourTools *tools, bool minimal ): m_radialDeg = settings.value("contourRulerRadialDeg", 30).toInt(); m_linkProfile = settings.value("linkProfilePlot", true).toBool(); plotLayout()->setAlignCanvasToScales( true ); + initPlot(); + canvas()->installEventFilter(this); } void ContourPlot::initPlot(){ @@ -601,6 +695,89 @@ void ContourPlot::setAlpha( int alpha ) replot(); } +// Helper: Adjust rect to match aspect ratio of canvas, given wavefront data +// Takes wavefront dimensions and canvas, returns adjusted rect maintaining aspect ratio +QRectF ContourPlot::adjustRectToAspectRatio( + double baseWidth, double baseHeight, double canvasWidth, double canvasHeight) const +{ + double dataRatio = baseWidth / baseHeight; + double canvasRatio = canvasWidth / canvasHeight; + + double x1 = 0, x2 = baseWidth; + double y1 = 0, y2 = baseHeight; + + if (canvasRatio > dataRatio) + { + // Canvas is wider → expand X symmetrically + double newWidth = baseHeight * canvasRatio; + double extra = (newWidth - baseWidth) / 2.0; + x1 = -extra; + x2 = baseWidth + extra; + } + else if (canvasRatio < dataRatio) + { + // Canvas is taller → expand Y symmetrically + double newHeight = baseWidth / canvasRatio; + double extra = (newHeight - baseHeight) / 2.0; + y1 = -extra; + y2 = baseHeight + extra; + } + + return QRectF(x1, y1, x2 - x1, y2 - y1); +} + +void ContourPlot::updateAspectRatio() +{ + static bool isReentering = false; + + if (m_wf == nullptr){ + return; + } + + // Prevent re-entrancy + if (isReentering){ + return; + } + isReentering = true; + + QWidget *c = canvas(); + int canvas_w = c->width(); + int canvas_h = c->height(); + + if (canvas_w <= 0 || canvas_h <= 0){ + isReentering = false; + return; + } + + double imgWidth = m_wf->data.cols; + double imgHeight = m_wf->data.rows; + + QRectF adjustedRect = adjustRectToAspectRatio(imgWidth, imgHeight, canvas_w, canvas_h); + + setAxisScale(QwtPlot::xBottom, adjustedRect.left(), adjustedRect.right()); + setAxisScale(QwtPlot::yLeft, adjustedRect.top(), adjustedRect.bottom()); + + replot(); + + isReentering = false; +} + +bool ContourPlot::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == canvas() && event->type() == QEvent::Resize) + { + if (!m_inZoomOperation) { + updateAspectRatio(); + } + return false; + } + return QwtPlot::eventFilter(obj, event); +} + +void ContourPlot::clearZoomFlag() +{ + m_inZoomOperation = false; +} #ifndef QT_NO_PRINTER @@ -628,4 +805,3 @@ void ContourPlot::printPlot() } #endif - diff --git a/contourplot.h b/contourplot.h index 5ce87170..cf86c67c 100644 --- a/contourplot.h +++ b/contourplot.h @@ -27,30 +27,11 @@ #include "wavefront.h" #include #include +#include #include class MyZoomer; -class SpectrogramData: public QwtRasterData -{ -public: - SpectrogramData(); - wavefront *m_wf; - void setSurface(wavefront *surface); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // keep compatibility with newer version of QWT used in QT6 - QwtInterval interval(Qt::Axis axis) const override; - void setInterval(Qt::Axis axis, const QwtInterval &interval); -#endif - virtual double value( double x, double y ) const override; -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // keep compatibility with newer version of QWT used in QT6 -private: - QwtInterval m_xInterval; - QwtInterval m_yInterval; - QwtInterval m_zInterval; -#endif -}; class ContourPlot: public QwtPlot { Q_OBJECT @@ -60,7 +41,7 @@ class ContourPlot: public QwtPlot public: QwtPlotSpectrogram *d_spectrogram; - wavefront* m_wf; + const wavefront* m_wf; ContourTools *m_tools; static bool m_useMiddleOffset; static int m_colorMapNdx; @@ -68,18 +49,21 @@ class ContourPlot: public QwtPlot static double m_waveRange; double contourRange; static QString m_zRangeMode; - ContourPlot(QWidget * = NULL, ContourTools *tools = 0, bool minimal = false); - void setSurface(wavefront * mat); + ContourPlot(QWidget *parent = NULL, ContourTools *tools = 0, bool minimal = false); + void setSurface(const wavefront * wft); void applyZeroOffset(bool useMiddle); void setZRange(); void setColorMap(int ndx); void setTool(ContourTools* tool); - bool m_autoInterval; - bool m_minimal; + QRectF adjustRectToAspectRatio(double baseWidth, double baseHeight, + double canvasWidth, double canvasHeight) const; + void updateAspectRatio(); + bool m_minimal; // when true, hide axes and colorbar bool m_linkProfile; QPen m_rulerPen; int m_radialDeg; bool m_do_fill; + bool m_inZoomOperation; signals: @@ -102,6 +86,7 @@ public slots: void contourFillChanged(int); void newDisplayErrorRange(double min, double max); void drawProfileLine(const double ang); + void clearZoomFlag(); #ifndef QT_NO_PRINTER void printPlot(); #endif @@ -109,7 +94,7 @@ public slots: private: void drawCanvas(QPainter* p); void initPlot(); - + bool eventFilter(QObject *obj, QEvent *event); QColor m_contourPen; diff --git a/contourview.cpp b/contourview.cpp index 0b51f56e..5970c6c5 100644 --- a/contourview.cpp +++ b/contourview.cpp @@ -63,7 +63,7 @@ QImage contourView::getPixstatsImage(){ } -void contourView::setSurface(wavefront *wf){ +void contourView::setSurface(const wavefront *wf){ getPlot()->setSurface(wf); ps->setData(wf); } @@ -81,6 +81,7 @@ void contourView::showContextMenu(QPoint pos) // Show context menu at handling position myMenu.exec(globalPos); } + ContourPlot *contourView::getPlot(){ return ui->widget; } diff --git a/contourview.h b/contourview.h index 980d97e4..876f20bd 100644 --- a/contourview.h +++ b/contourview.h @@ -34,19 +34,19 @@ class contourView : public QWidget explicit contourView(QWidget *parent = 0, ContourTools *tools = 0); ~contourView(); ContourPlot *getPlot(); - void setSurface(wavefront * wf); - bool zoomed; + void setSurface(const wavefront * wf); + bool zoomed; // this is not about zooming in the plot, but zooming the contour view to full screen QImage getPixstatsImage(); pixelStats *getPixelstats(){ return ps;} signals: void lineSpacingChanged(double); void showAllContours(); - void zoomMe(bool); + void zoomMe(bool); // full screen related private slots: void on_doubleSpinBox_valueChanged(double arg1); void showContextMenu(QPoint pos); void on_pushButton_pressed(); - void zoom(); + void zoom(); // full screen related void on_histogram_clicked(); diff --git a/dftcolormap.cpp b/dftcolormap.cpp index 0786ea6a..9838f51c 100644 --- a/dftcolormap.cpp +++ b/dftcolormap.cpp @@ -26,7 +26,7 @@ QList dftColorMap::userStops; -dftColorMap::dftColorMap(int type, wavefront *wf, bool zeroBased, double errorMargin, double scale, +dftColorMap::dftColorMap(int type, const wavefront *wf, bool zeroBased, double errorMargin, double scale, QColor less, QColor more): QwtLinearColorMap( less, more ),m_wf(wf) { diff --git a/dftcolormap.h b/dftcolormap.h index 43d7d69b..c9c6ef33 100644 --- a/dftcolormap.h +++ b/dftcolormap.h @@ -24,11 +24,11 @@ class dftColorMap: public QwtLinearColorMap { public: - dftColorMap(int type = 0, wavefront *wf= 0, bool zeroBased = true, + dftColorMap(int type = 0, const wavefront *wf= 0, bool zeroBased = true, double errorMargin = .125, double scale = 1., QColor less = Qt::black, QColor more = Qt::white); void setRange(double low, double high); - wavefront *m_wf; + const wavefront *m_wf; static QList userStops; }; #endif // DFTCOLORMAP_H diff --git a/pixelstats.cpp b/pixelstats.cpp index a7e38f0d..f3f58c73 100644 --- a/pixelstats.cpp +++ b/pixelstats.cpp @@ -20,7 +20,6 @@ #include #include #include -#include "contourplot.h" #include "wavefront.h" #include "math.h" #include "utils.h" @@ -296,7 +295,7 @@ pixelStats::~pixelStats() } -void pixelStats::setData(wavefront *w){ +void pixelStats::setData(const wavefront *w){ m_wf = w; g_ub = m_wf->min + (m_wf->max-m_wf->min) * .9; g_lb = m_wf->min + (m_wf->max-m_wf->min) * .1; @@ -312,7 +311,7 @@ cv::Mat mat2gray(const cv::Mat& src) return dst; } -cv::Mat slope(wavefront * wf){ +cv::Mat slope(const wavefront * wf){ int half = wf->data.cols/2; cv::Mat gradx = cv::Mat::zeros(wf->data.rows,wf->data.cols, CV_64FC1); cv::Mat grady = cv::Mat::zeros(wf->data.rows,wf->data.cols, CV_64FC1); @@ -406,7 +405,7 @@ cv::Mat slope(wavefront * wf){ */ return mag; } -#include "dftarea.h" + void pixelStats::updateSurface(){ try { cv::Mat sur = cv::Mat::zeros(m_wf->data.rows, m_wf->data.cols, CV_8UC3); diff --git a/pixelstats.h b/pixelstats.h index eacd0406..60541f58 100644 --- a/pixelstats.h +++ b/pixelstats.h @@ -19,7 +19,7 @@ class pixelStats : public QWidget public: explicit pixelStats(QWidget *parent = 0); ~pixelStats(); - void setData(wavefront *w); + void setData(const wavefront *w); private slots: void bounds_valueChanged(); @@ -33,7 +33,7 @@ private slots: private: Ui::pixelStats *ui; - wavefront *m_wf; + const wavefront *m_wf; cv::Mat mask; void updateHisto(); void updateSurface(); diff --git a/surfacemanager.cpp b/surfacemanager.cpp index d594de2c..a6612311 100644 --- a/surfacemanager.cpp +++ b/surfacemanager.cpp @@ -2738,24 +2738,24 @@ void SurfaceManager::saveAllContours(){ m_allContours.save( fName ); } -#include "showallcontoursdlg.h" -void SurfaceManager::showAllContours(){ - showAllContoursDlg dlg; +#include "showallcontoursdlg.h" //TODO move +void SurfaceManager::showAllContours(){ //TODO move to contourview would make more sense as use only there + showAllContoursDlg dlg; //TODO not closing on app close if (!dlg.exec()) { return; } QRect rec = QGuiApplication::primaryScreen()->geometry(); QApplication::setOverrideCursor(Qt::WaitCursor); - ContourPlot *plot =new ContourPlot(0,0);//m_contourPlot; - //plot->m_minimal = true; - int cols = dlg.getColumns(); + ContourPlot *plot =new ContourPlot(0,0);//m_contourPlot; //TODO leaking ? + //plot->m_minimal = true; + int cols = dlg.getColumns(); //TODO parameter number of pixels unused here. update the dlg ui int width = rec.width()/cols; int height = width * .82; surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance(); QList list = saTools->SelectedWaveFronts(); - int rows = ceil((double)list.size()/cols); - int columns = std::min((int)list.size(),int(ceil((double)list.size()/rows))); + int rows = ceil((float)list.size()/cols); + int columns = std::min((int)list.size(),int(ceil((float)list.size()/rows))); const QSizeF size(columns * (width + 10), rows * (height + 10)); const QRect imageRect = QRect(0,0,size.width(),size.height()); qDebug() << "save all" << imageRect; @@ -2774,11 +2774,27 @@ void SurfaceManager::showAllContours(){ { wavefront * wf = m_wavefronts[list[i]]; plot->setSurface(wf); + + //All these replots and updates are necessary to get the canvas updateAspectRatio to work properly. + + // Resize the outer plot so its internal canvas area will match + // the requested image size. + plot->resize(width, height); + QCoreApplication::processEvents(); + // Now ensure the canvas is the target size + if (plot->canvas()) + plot->canvas()->resize(width, height); + plot->updateAspectRatio(); + plot->replot(); + plot->updateAspectRatio(); plot->replot(); + + int y_offset = height * (i/columns) + 10; int x_offset = width * (i%columns) + 10; const QRectF topRect( x_offset, y_offset, width, height ); renderer.render( plot, &painter, topRect ); + } painter.end();