jfjoch_viewer: Add azimuthal integration 2D viewer

This commit is contained in:
2025-11-14 16:53:49 +01:00
parent 96df485822
commit a4b6231ed8
10 changed files with 191 additions and 106 deletions
+2
View File
@@ -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
-8
View File
@@ -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);
}
-2
View File
@@ -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<const JFJochReaderImage> 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);
-25
View File
@@ -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<size_t>(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_ptr<const JFJochReaderIma
image = in_image;
redrawPlot();
}
void JFJochViewerSidePanelChart::setColorMap(int color_map) {
azint_image->setColorMap(color_map);
}
-5
View File
@@ -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<const JFJochReaderImage> 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<const JFJochReaderImage> image);
void setColorMap(int color_map);
};
+12 -5
View File
@@ -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);
}
+67 -50
View File
@@ -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<float> &data,
const std::vector<float> &in_phi,
const std::vector<float> &in_q,
int azimuthal_bins) {
if (azimuthal_bins <= 0) {
void JFJochAzIntImage::imageLoaded(std::shared_ptr<const JFJochReaderImage> 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<size_t>(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<float>::infinity();
local_max = -std::numeric_limits<float>::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<float>::infinity();
local_max = -std::numeric_limits<float>::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<int>(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);
}
}
}
void JFJochAzIntImage::mouseDoubleClickEvent(QMouseEvent *event) {
if (!scene() || !image || W == 0 || H == 0) return;
QPointF scenePos = mapToScene(event->pos());
int x = static_cast<int>(scenePos.x());
int y = static_cast<int>(scenePos.y());
if (x >= 0 && x < static_cast<int>(W) && y >= 0 && y < static_cast<int>(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));
}
}
+8 -11
View File
@@ -12,7 +12,7 @@
#include <vector>
#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<float> data;
std::vector<float> phi;
std::vector<float> q;
std::shared_ptr<const JFJochReaderImage> 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<const JFJochReaderImage> image);
public:
explicit JFJochAzIntImage(QWidget *parent = nullptr);
void Clear();
void SetData(const std::vector<float> &data,
const std::vector<float> &phi,
const std::vector<float> &q,
int azimuthal_bins);
void SetRangeAuto();
void SetRange(float min_val, float max_val);
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <QStatusBar>
#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<const JFJochReaderImage> image) {
viewer->imageLoaded(std::move(image));
}
void JFJoch2DAzintImageWindow::setColorMap(int color_map) {
viewer->setColorMap(color_map);
}
+36
View File
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#ifndef JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H
#define JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H
#include <QComboBox>
#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<const JFJochReaderImage> in_dataset) override;
void setColorMap(int color_map);
};
#endif //JFJOCH_JFJOCH2DAZINTIMAGEWINDOW_H