diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index a375906f..ca14bfc9 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -73,6 +73,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView widgets/JFJochImage.h windows/JFJochAzIntWindow.cpp windows/JFJochAzIntWindow.h + windows/JFJoch2DAzintImageWindow.cpp + windows/JFJoch2DAzintImageWindow.h ) TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Charts Qt6::DBus diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index c3d58b33..f5bf49ea 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -116,10 +116,6 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) connect(this, &JFJochViewerSidePanel::imageLoaded, chart, &JFJochViewerSidePanelChart::loadImage); - connect(chart, &JFJochViewerSidePanelChart::writeStatusBar, - [&] (QString string, int timeout_ms) { - emit writeStatusBar(string, timeout_ms); - }); layout->addWidget(new TitleLabel("ROI", this)); @@ -321,7 +317,3 @@ void JFJochViewerSidePanel::SetROIBox(QRect box) { void JFJochViewerSidePanel::SetROICircle(double x, double y, double radius) { roi->SetROICircle(x, y, radius); } - -void JFJochViewerSidePanel::setColorMap(int color_map) { - chart->setColorMap(color_map); -} diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 8ebb8088..b46e4f35 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -47,14 +47,12 @@ signals: void ROICircleConfigured(double center_x, double center_y, double radius); void AddROIToUserMask(); void SubtractROIFromUserMask(); - void writeStatusBar(QString string, int timeout_ms = 0); public: JFJochViewerSidePanel(QWidget *parent); public slots: void loadImage(std::shared_ptr image); void SetROIBox(QRect box); void SetROICircle(double x, double y, double radius); - void setColorMap(int color_map); private slots: void editingFinished(); void enableResRings(bool input); diff --git a/viewer/JFJochViewerSidePanelChart.cpp b/viewer/JFJochViewerSidePanelChart.cpp index 5001eb2e..b297f0af 100644 --- a/viewer/JFJochViewerSidePanelChart.cpp +++ b/viewer/JFJochViewerSidePanelChart.cpp @@ -9,7 +9,6 @@ JFJochViewerSidePanelChart::JFJochViewerSidePanelChart(QWidget *parent) : QWidge combo_box = new QComboBox(this); combo_box->addItem("Azimuthal integration (1D)", 0); - combo_box->addItem("Azimuthal integration (2D)", 3); combo_box->addItem("Wilson plot", 1); combo_box->addItem("I/sigma", 2); combo_box->addItem("Spots (count)", 5); @@ -22,22 +21,16 @@ JFJochViewerSidePanelChart::JFJochViewerSidePanelChart(QWidget *parent) : QWidge this, &JFJochViewerSidePanelChart::comboBoxSelected); azint_plot = new JFJochSimpleChartView(this); - azint_image = new JFJochAzIntImage(this); one_over_d_sq_plot = new JFJochOneOverResSqChartView(this); stack = new QStackedWidget(this); stack->addWidget(azint_plot); // index 0 - stack->addWidget(azint_image); // index 1 stack->addWidget(one_over_d_sq_plot); // index 2 layout->addWidget(stack); setLayout(layout); - connect(azint_image, &JFJochAzIntImage::writeStatusBar, - [&](QString string, int timeout_ms) { - emit writeStatusBar(string, timeout_ms); - }); } void JFJochViewerSidePanelChart::comboBoxSelected(int val) { @@ -86,20 +79,6 @@ void JFJochViewerSidePanelChart::redrawPlot() { one_over_d_sq_plot->UpdateData(x, y, "d [Å]", "Count"); stack->setCurrentWidget(one_over_d_sq_plot); break; - case 3: { - // Render 2D azimuthal integration as an image - const auto &profile = image->ImageData().az_int_profile; - const auto &ds = image->Dataset(); - int az_bins = ds.azimuthal_bins; - int q_bins = ds.q_bins; - if (az_bins > 0 && q_bins > 0 && profile.size() == static_cast(az_bins * q_bins)) { - azint_image->SetData(profile, ds.az_int_bin_to_phi, ds.az_int_bin_to_q, az_bins); - } else { - azint_image->Clear(); - } - stack->setCurrentWidget(azint_image); - break; - } } } } @@ -108,7 +87,3 @@ void JFJochViewerSidePanelChart::loadImage(std::shared_ptrsetColorMap(color_map); -} diff --git a/viewer/JFJochViewerSidePanelChart.h b/viewer/JFJochViewerSidePanelChart.h index da2e485f..1515e922 100644 --- a/viewer/JFJochViewerSidePanelChart.h +++ b/viewer/JFJochViewerSidePanelChart.h @@ -18,16 +18,12 @@ class JFJochViewerSidePanelChart : public QWidget { QStackedWidget *stack = nullptr; JFJochSimpleChartView *azint_plot = nullptr; - JFJochAzIntImage *azint_image = nullptr; JFJochOneOverResSqChartView *one_over_d_sq_plot = nullptr; std::shared_ptr image; QComboBox *combo_box; void redrawPlot(); -signals: - void writeStatusBar(QString string, int timeout_ms = 0); - private slots: void comboBoxSelected(int val); @@ -36,7 +32,6 @@ public: public slots: void loadImage(std::shared_ptr image); - void setColorMap(int color_map); }; diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index e95659a4..603ca3ba 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -20,6 +20,7 @@ #include "windows/JFJochCalibrationWindow.h" #include "toolbar/JFJochViewerToolbarDisplay.h" #include "toolbar/JFJochViewerToolbarImage.h" +#include "windows/JFJoch2DAzintImageWindow.h" #include "windows/JFJochAzIntWindow.h" JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString &file) : QMainWindow(parent) { @@ -98,14 +99,16 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString auto processingWindow = new JFJochViewerProcessingWindow(spot_finding_settings, indexing_settings, this); auto calibrationWindow = new JFJochCalibrationWindow(this); auto azintWindow = new JFJochAzIntWindow(experiment.GetAzimuthalIntegrationSettings(), this); + auto azintImageWindow = new JFJoch2DAzintImageWindow(this); menuBar->AddWindowEntry(tableWindow, "Image list"); menuBar->AddWindowEntry(spotWindow, "Spot list"); menuBar->AddWindowEntry(reflectionWindow, "Reflection list"); menuBar->AddWindowEntry(metadataWindow, "Metadata edit"); menuBar->AddWindowEntry(processingWindow, "Image processing settings"); - menuBar->AddWindowEntry(azintWindow, "Azimuthal integration"); + menuBar->AddWindowEntry(azintWindow, "Azimuthal integration settings"); menuBar->AddWindowEntry(calibrationWindow, "Calibration image viewer"); + menuBar->AddWindowEntry(azintImageWindow, "2D Azimuthal integration image"); if (dbus) { // Create adaptor attached to this window @@ -156,8 +159,11 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, viewer, &JFJochDiffractionImage::setColorMap); - connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, side_panel, - &JFJochViewerSidePanel::setColorMap); + connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, azintImageWindow, + &JFJoch2DAzintImageWindow::setColorMap); + + connect(reading_worker, &JFJochImageReadingWorker::imageLoaded, + azintImageWindow, &JFJoch2DAzintImageWindow::imageLoaded); connect(viewer, &JFJochDiffractionImage::foregroundChanged, toolBarDisplay, &JFJochViewerToolbarDisplay::updateForeground); @@ -277,8 +283,6 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString viewer, &JFJochDiffractionImage::setResolutionRing); connect(viewer, &JFJochDiffractionImage::writeStatusBar, statusbar, &JFJochViewerStatusBar::display); - connect(side_panel, &JFJochViewerSidePanel::writeStatusBar, - statusbar, &JFJochViewerStatusBar::display); connect(metadataWindow, &JFJochViewerMetadataWindow::datasetUpdated, reading_worker, &JFJochImageReadingWorker::UpdateDataset); @@ -300,6 +304,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(azintWindow, &JFJochAzIntWindow::settingsChanged, reading_worker, &JFJochImageReadingWorker::UpdateAzintSettings); + connect(azintImageWindow, &JFJoch2DAzintImageWindow::zoomOnBin, + viewer, &JFJochDiffractionImage::centerOnSpot); + if (!file.isEmpty()) LoadFile(file, 0, 1); } diff --git a/viewer/widgets/JFJochAzIntImage.cpp b/viewer/widgets/JFJochAzIntImage.cpp index e9812d93..fa362c9c 100644 --- a/viewer/widgets/JFJochAzIntImage.cpp +++ b/viewer/widgets/JFJochAzIntImage.cpp @@ -18,65 +18,63 @@ void JFJochAzIntImage::Clear() { scene()->clear(); } -// data: size = azimuthal_bins * q_bins -// Layout: q varies fastest (i % q_bins == q index) -void JFJochAzIntImage::SetData(const std::vector &data, - const std::vector &in_phi, - const std::vector &in_q, - int azimuthal_bins) { - if (azimuthal_bins <= 0) { +void JFJochAzIntImage::imageLoaded(std::shared_ptr in_image) { + if (!in_image) { Clear(); - throw std::runtime_error("azimuthal_bins <= 0"); + return; } - int q_bins = data.size() / azimuthal_bins; + const auto &profile = in_image->ImageData().az_int_profile; + const auto &ds = in_image->Dataset(); + int az_bins = ds.azimuthal_bins; + int q_bins = ds.q_bins; - if (q_bins <= 0 || in_phi.size() != data.size() || in_q.size() != data.size()) { - Clear(); - throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, - "Mismatch in input size"); - } + if (az_bins > 0 && q_bins > 0 && profile.size() == static_cast(az_bins * q_bins) + && ds.az_int_bin_to_phi.size() == profile.size() && ds.az_int_bin_to_q.size() == profile.size()) { - float local_min = range_min, local_max = range_max; - if (auto_range) { - local_min = std::numeric_limits::infinity(); - local_max = -std::numeric_limits::infinity(); - for (float v : data) { - if (std::isfinite(v)) { - if (v < local_min) local_min = v; - if (v > local_max) local_max = v; + image = in_image; + float local_min = range_min, local_max = range_max; + if (auto_range) { + local_min = std::numeric_limits::infinity(); + local_max = -std::numeric_limits::infinity(); + for (float v : profile) { + if (std::isfinite(v)) { + if (v < local_min) local_min = v; + if (v > local_max) local_max = v; + } + } + if (!std::isfinite(local_min) || !std::isfinite(local_max) || local_max <= local_min) { + Clear(); + return; + } + } else { + if (!(std::isfinite(local_min) && std::isfinite(local_max) && local_max > local_min)) { + Clear(); + return; } } - if (!std::isfinite(local_min) || !std::isfinite(local_max) || local_max <= local_min) { - Clear(); - return; - } + + // Update base class members + W = q_bins; + H = az_bins; + image_fp = profile; + + // Update foreground/background for color mapping + background = local_min; + foreground = local_max; + emit backgroundChanged(background); + emit foregroundChanged(foreground); + + // Generate pixmap and redraw using base class functionality + GeneratePixmap(); + Redraw(); } else { - if (!(std::isfinite(local_min) && std::isfinite(local_max) && local_max > local_min)) { - Clear(); - return; - } + Clear(); } - - phi = in_phi; - q = in_q; - - // Update base class members - W = q_bins; - H = azimuthal_bins; - image_fp = data; - - // Update foreground/background for color mapping - background = local_min; - foreground = local_max; - - // Generate pixmap and redraw using base class functionality - GeneratePixmap(); - Redraw(); } void JFJochAzIntImage::mouseHover(QMouseEvent* event) { - if (!scene() || W == 0 || H == 0) return; + if (!scene() || !image || W == 0 || H == 0) return; QPointF scenePos = mapToScene(event->pos()); int x = static_cast(scenePos.x()); @@ -86,10 +84,29 @@ void JFJochAzIntImage::mouseHover(QMouseEvent* event) { size_t idx = y * W + x; QString statusText = QString("Q: %1 Å^-1 phi: %2° value: %3") - .arg(QString::number(q[idx], 'f', 3)) - .arg(QString::number(phi[idx], 'f', 3)) + .arg(QString::number(image->Dataset().az_int_bin_to_q[idx], 'f', 3)) + .arg(QString::number(image->Dataset().az_int_bin_to_phi[idx], 'f', 3)) .arg(QString::number(image_fp[idx], 'f', 3)); emit writeStatusBar(statusText, 0); } -} \ No newline at end of file +} + +void JFJochAzIntImage::mouseDoubleClickEvent(QMouseEvent *event) { + if (!scene() || !image || W == 0 || H == 0) return; + + QPointF scenePos = mapToScene(event->pos()); + int x = static_cast(scenePos.x()); + int y = static_cast(scenePos.y()); + + if (x >= 0 && x < static_cast(W) && y >= 0 && y < static_cast(H)) { + size_t idx = y * W + x; + + float q = image->Dataset().az_int_bin_to_q[idx]; + float phi = image->Dataset().az_int_bin_to_phi[idx]; + + auto geom = image->Dataset().experiment.GetDiffractionGeometry(); + auto coord = geom.ResPhiToPxl(2 * M_PI / q, phi / 180.0 * M_PI); + emit zoomOnBin(QPointF(coord.first, coord.second)); + } +} diff --git a/viewer/widgets/JFJochAzIntImage.h b/viewer/widgets/JFJochAzIntImage.h index 3fbc52bf..e1e66362 100644 --- a/viewer/widgets/JFJochAzIntImage.h +++ b/viewer/widgets/JFJochAzIntImage.h @@ -12,7 +12,7 @@ #include #include "JFJochImage.h" -#include "../../common/ColorScale.h" +#include "../../reader/JFJochReaderImage.h" class JFJochAzIntImage : public JFJochImage { Q_OBJECT @@ -20,20 +20,17 @@ class JFJochAzIntImage : public JFJochImage { bool auto_range = true; float range_min = 0.0f; float range_max = 1.0f; - - std::vector data; - std::vector phi; - std::vector q; + std::shared_ptr image; void mouseHover(QMouseEvent* event) override; + void Clear(); + void mouseDoubleClickEvent(QMouseEvent *event) override; +signals: + void zoomOnBin(QPointF pos); +public slots: + void imageLoaded(std::shared_ptr image); public: explicit JFJochAzIntImage(QWidget *parent = nullptr); - void Clear(); - - void SetData(const std::vector &data, - const std::vector &phi, - const std::vector &q, - int azimuthal_bins); void SetRangeAuto(); void SetRange(float min_val, float max_val); diff --git a/viewer/windows/JFJoch2DAzintImageWindow.cpp b/viewer/windows/JFJoch2DAzintImageWindow.cpp new file mode 100644 index 00000000..b8f2277f --- /dev/null +++ b/viewer/windows/JFJoch2DAzintImageWindow.cpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include +#include "JFJoch2DAzintImageWindow.h" + + +JFJoch2DAzintImageWindow::JFJoch2DAzintImageWindow(QWidget *parent) : JFJochHelperWindow(parent) { + QWidget *centralWidget = new QWidget(this); + setWindowTitle("2D azimuthal integration viewer"); + setCentralWidget(centralWidget); + + auto grid_layout = new QGridLayout(); + + viewer = new JFJochAzIntImage(this); + + background_slider = new SliderPlusBox(-100, 100, 1.0, 0, this, SliderPlusBox::ScaleType::Linear); + foreground_slider = new SliderPlusBox(1, 32768, 1.0, 0, this, SliderPlusBox::ScaleType::Logarithmic); + background_slider->setValue(0); + + auto background_row = new QHBoxLayout(); + auto foreground_row = new QHBoxLayout(); + background_row->addWidget(new QLabel("Background:")); + background_row->addWidget(background_slider); + foreground_row->addWidget(new QLabel("Foreground:")); + foreground_row->addWidget(foreground_slider); + + grid_layout->addLayout(background_row, 0, 0, 1, 2); + grid_layout->addLayout(foreground_row, 1, 0, 1, 2); + grid_layout->addWidget(viewer, 2, 0, 1, 2); + centralWidget->setLayout(grid_layout); + + connect(viewer, &JFJochAzIntImage::backgroundChanged, + [this](float val) { + QSignalBlocker blocker(background_slider); + background_slider->setValue(val); + }); + + connect(viewer, &JFJochAzIntImage::foregroundChanged, + [this](float val) { + QSignalBlocker blocker(foreground_slider); + foreground_slider->setValue(val); + }); + + connect(background_slider, &SliderPlusBox::valueChanged, viewer, &JFJochAzIntImage::changeBackground); + connect(foreground_slider, &SliderPlusBox::valueChanged, viewer, &JFJochAzIntImage::changeForeground); + + statusBar = new QStatusBar(this); + setStatusBar(statusBar); + + connect(viewer, &JFJochAzIntImage::writeStatusBar, + statusBar, &QStatusBar::showMessage); + connect(viewer, &JFJochAzIntImage::zoomOnBin, this, &JFJoch2DAzintImageWindow::viewerZoomOnBin); +} + +void JFJoch2DAzintImageWindow::viewerZoomOnBin(QPointF input) { + emit zoomOnBin(input); +} + +void JFJoch2DAzintImageWindow::imageLoaded(std::shared_ptr image) { + viewer->imageLoaded(std::move(image)); +} + +void JFJoch2DAzintImageWindow::setColorMap(int color_map) { + viewer->setColorMap(color_map); +} diff --git a/viewer/windows/JFJoch2DAzintImageWindow.h b/viewer/windows/JFJoch2DAzintImageWindow.h new file mode 100644 index 00000000..2c11ba1c --- /dev/null +++ b/viewer/windows/JFJoch2DAzintImageWindow.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#ifndef JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H +#define JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H + +#include + +#include "../widgets/SliderPlusBox.h" +#include "JFJochHelperWindow.h" +#include "../widgets/JFJochAzIntImage.h" + +class JFJoch2DAzintImageWindow : public JFJochHelperWindow { + Q_OBJECT + + SliderPlusBox *foreground_slider; + SliderPlusBox *background_slider; + + QStatusBar *statusBar; + + JFJochAzIntImage *viewer = nullptr; + +public: + JFJoch2DAzintImageWindow(QWidget *parent = nullptr); + +private slots: + void viewerZoomOnBin(QPointF); +signals: + void zoomOnBin(QPointF point); +public slots: + void imageLoaded(std::shared_ptr in_dataset) override; + void setColorMap(int color_map); +}; + + +#endif //JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H \ No newline at end of file