diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index 10fc2f51..8e049dbf 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -75,6 +75,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView windows/JFJoch2DAzintImageWindow.h JFJochViewerROIResult.cpp JFJochViewerROIResult.h + widgets/ResolutionRingWidget.cpp + widgets/ResolutionRingWidget.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 babf3348..f999dd54 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -43,14 +43,15 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) connect(predictionsToggleCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::predictionsToggled); + res_rings = new ResolutionRingWidget(this); - resRingsCheckBox = new QCheckBox("Show resolution rings", this); - resRingsCheckBox->setCheckState(Qt::Unchecked); + connect(res_rings, &ResolutionRingWidget::resRingsSet, [this] (QVector v) { + emit resRingsSet(v); + }); - autoResRingsCheckBox = new QCheckBox("Resolution rings auto.", this); - res_rings_edit = new QLineEdit(this); - res_rings_edit->setPlaceholderText("Ring positions, e.g., 1.0,2.5,3.7"); - res_rings_edit->setEnabled(false); // Initially disabled as "Auto Res Rings" is checked by default + connect(res_rings, &ResolutionRingWidget::ringModeSet, [this] (JFJochDiffractionImage::RingMode mode) { + emit ringModeSet(mode); + }); auto highestPixelsComboBox = new QComboBox(this); highestPixelsComboBox->addItem("Show 0 highest pixels", 0); @@ -92,24 +93,15 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) image_feature_grid->addWidget(spotToggleCheckBox, 0, 0); image_feature_grid->addWidget(highlightIceRingToggleCheckBox, 0, 1); image_feature_grid->addWidget(predictionsToggleCheckBox, 1, 0); - image_feature_grid->addWidget(resRingsCheckBox, 2, 0); - image_feature_grid->addWidget(autoResRingsCheckBox, 2, 1); - image_feature_grid->addWidget(res_rings_edit, 3, 0, 1, 2); - image_feature_grid->addWidget(saturatedPixelsCheckBox, 4, 0); - image_feature_grid->addWidget(highestPixelsComboBox, 4, 1); - image_feature_grid->addWidget(colorSelectButton, 5, 0); - image_feature_grid->addWidget(spotColorSelectButton, 5, 1); + image_feature_grid->addWidget(saturatedPixelsCheckBox, 2, 0); + image_feature_grid->addWidget(highestPixelsComboBox, 2, 1); + image_feature_grid->addWidget(colorSelectButton, 3, 0); + image_feature_grid->addWidget(spotColorSelectButton, 3, 1); + image_feature_grid->addWidget(res_rings, 4, 0, 1, 2); + layout->addLayout(image_feature_grid); - connect(autoResRingsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::enableAutoResRings); - - connect(res_rings_edit, &QLineEdit::editingFinished, - this, &JFJochViewerSidePanel::editingFinished); - - connect(resRingsCheckBox, &QCheckBox::toggled, - this, &JFJochViewerSidePanel::enableResRings); - layout->addWidget(new TitleLabel("Image statistics plot", this)); chart = new JFJochViewerSidePanelChart(this); layout->addWidget(chart); @@ -162,7 +154,7 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) for (float ring : rings) { q_rings.append(2 * M_PI / ring); } - setRings(q_rings); + res_rings->setRings(q_rings); }); // Add preset ice rings button below LaB6 calibration @@ -170,7 +162,7 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) connect(iceRingsButton, &QPushButton::clicked, this, [this]() { // Set manual rings and enable display const QVector ice_rings(ICE_RING_RES_A.begin(), ICE_RING_RES_A.end()); - setRings(ice_rings); + res_rings->setRings(ice_rings); }); auto refine_row = new QGridLayout(); @@ -231,28 +223,6 @@ void JFJochViewerSidePanel::findBeamCenterClicked() { emit findBeamCenter(GetCalibrant(), true); } -void JFJochViewerSidePanel::setRings(const QVector &v) { - QString txt; - for (float i : v) { - txt += QString::number(i, 'f', 4) + ","; - } - - resRingsCheckBox->setChecked(true); - autoResRingsCheckBox->setChecked(false); - res_rings_edit->setEnabled(true); - res_rings_edit->setText(txt); - emit setResRings(v); -} - -void JFJochViewerSidePanel::enableAutoResRings(bool input) { - res_rings_edit->setEnabled(!input); // Enable line edit only if "Auto Res Rings" is unchecked - if (input) { - emit autoResRings(); - } else { - editingFinished(); - } -} - void JFJochViewerSidePanel::spotsToggled(bool input) { emit showSpots(input); } @@ -261,31 +231,6 @@ void JFJochViewerSidePanel::predictionsToggled(bool input) { emit showPredictions(input); } -void JFJochViewerSidePanel::editingFinished() { - QString text = res_rings_edit->text(); - QStringList stringList = text.split(',', Qt::SkipEmptyParts); - QVector manualResRings; - - bool validInput = true; - for (const QString &str: stringList) { - bool ok; - float value = str.toFloat(&ok); - if (ok && value >= 0) { - manualResRings.append(value); - } else { - validInput = false; - break; - } - } - - if (!validInput) { - res_rings_edit->setStyleSheet("border: 1px solid red;"); // Highlight error - } else { - res_rings_edit->setStyleSheet(""); // Reset to default style - emit setResRings(manualResRings); // Update resolution ring vector - } -} - void JFJochViewerSidePanel::loadImage(std::shared_ptr image) { if (image) sample_cell = image->Dataset().experiment.GetUnitCell(); @@ -294,17 +239,6 @@ void JFJochViewerSidePanel::loadImage(std::shared_ptr i emit imageLoaded(image); } -void JFJochViewerSidePanel::enableResRings(bool input) { - if (!input) { - autoResRingsCheckBox->setEnabled(false); - res_rings_edit->setEnabled(false); - emit setResRings({}); - } else { - autoResRingsCheckBox->setEnabled(true); - enableAutoResRings(autoResRingsCheckBox->isChecked()); - } -} - void JFJochViewerSidePanel::saturatedPixelsToggled(bool input) { emit showSaturatedPixels(input); } @@ -324,3 +258,8 @@ void JFJochViewerSidePanel::SetROICircle(double x, double y, double radius) { void JFJochViewerSidePanel::SetROIResult(ROIMessage msg) { roi->SetROIResult(msg); } + +void JFJochViewerSidePanel::SetRings(QVector v) { + res_rings->setRings(v); +} + diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 005db794..49c610c5 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -13,25 +13,20 @@ #include "../reader/JFJochReader.h" #include "widgets/JFJochSimpleChartView.h" #include "JFJochViewerSidePanelChart.h" - +#include "widgets/ResolutionRingWidget.h" class JFJochViewerSidePanel : public QWidget { Q_OBJECT std::optional sample_cell; - QLineEdit *res_rings_edit = nullptr; - - QCheckBox *autoResRingsCheckBox; - QCheckBox *resRingsCheckBox; QComboBox* calibrantCombo{nullptr}; // stores current calibrant selection JFJochViewerSidePanelChart *chart = nullptr; JFJochViewerImageROIStatistics *roi = nullptr; + ResolutionRingWidget *res_rings = nullptr; UnitCell GetCalibrant() const; signals: void showSpots(bool input); - void autoResRings(); - void setResRings(QVector v); void setFeatureColor(QColor input); void setSpotColor(QColor input); void showHighestPixels(int32_t v); @@ -48,6 +43,9 @@ signals: void AddROIToUserMask(); void SubtractROIFromUserMask(); void writeStatusBar(QString string, int timeout_ms = 0); + + void ringModeSet(JFJochDiffractionImage::RingMode mode); + void resRingsSet(QVector v); public: JFJochViewerSidePanel(QWidget *parent); public slots: @@ -55,15 +53,13 @@ public slots: void SetROIBox(QRect box); void SetROICircle(double x, double y, double radius); void SetROIResult(ROIMessage msg); + void SetRings(QVector v); + private slots: - void editingFinished(); - void enableResRings(bool input); - void enableAutoResRings(bool input); void spotsToggled(bool input); void predictionsToggled(bool input); void highlightIceRingsToggled(bool input); void saturatedPixelsToggled(bool input); - void setRings(const QVector &v); void findBeamCenterClicked(); void optimizeBeamCenterClicked(); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index b76d1b6e..d1f7805d 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -271,8 +271,6 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(side_panel, &JFJochViewerSidePanel::showPredictions, viewer, &JFJochDiffractionImage::showPredictions); - connect(side_panel, &JFJochViewerSidePanel::autoResRings, - viewer, &JFJochDiffractionImage::setResolutionRingAuto); connect(side_panel, &JFJochViewerSidePanel::setFeatureColor, viewer, &JFJochDiffractionImage::setFeatureColor); @@ -285,8 +283,7 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString viewer, &JFJochDiffractionImage::showHighestPixels); connect(side_panel, &JFJochViewerSidePanel::showSaturatedPixels, viewer, &JFJochDiffractionImage::showSaturation); - connect(side_panel, &JFJochViewerSidePanel::setResRings, - viewer, &JFJochDiffractionImage::setResolutionRing); + connect(viewer, &JFJochDiffractionImage::writeStatusBar, statusbar, &JFJochViewerStatusBar::display); connect(side_panel, &JFJochViewerSidePanel::writeStatusBar, @@ -296,8 +293,11 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString reading_worker, &JFJochImageReadingWorker::UpdateDataset); connect(reading_worker, &JFJochImageReadingWorker::setRings, - side_panel, &JFJochViewerSidePanel::setResRings); - + side_panel, &JFJochViewerSidePanel::SetRings); + connect(side_panel, &JFJochViewerSidePanel::resRingsSet, + viewer, &JFJochDiffractionImage::setResolutionRing); + connect(side_panel, &JFJochViewerSidePanel::ringModeSet, + viewer, &JFJochDiffractionImage::setResolutionRingMode); connect(side_panel, &JFJochViewerSidePanel::highlightIceRings, viewer, &JFJochDiffractionImage::highlightIceRings); diff --git a/viewer/widgets/JFJochDiffractionImage.cpp b/viewer/widgets/JFJochDiffractionImage.cpp index c569b7bb..caf8d028 100644 --- a/viewer/widgets/JFJochDiffractionImage.cpp +++ b/viewer/widgets/JFJochDiffractionImage.cpp @@ -146,6 +146,9 @@ void JFJochDiffractionImage::DrawPredictions() { } void JFJochDiffractionImage::DrawResolutionRings() { + if (ring_mode == RingMode::None) + return; + // Get the visible area in the scene coordinates QRectF visibleRect = mapToScene(viewport()->geometry()).boundingRect(); @@ -159,7 +162,8 @@ void JFJochDiffractionImage::DrawResolutionRings() { auto geom = image->Dataset().experiment.GetDiffractionGeometry(); geom.PoniRot3_rad(0.0); - if (res_ring_auto) { + + if (ring_mode == RingMode::Auto) { float radius_x_0 = geom.GetBeamX_pxl() - startX; float radius_x_1 = endX - geom.GetBeamX_pxl(); @@ -183,8 +187,16 @@ void JFJochDiffractionImage::DrawResolutionRings() { }; else res_ring = {}; + } else if (ring_mode == RingMode::Estimation) { + if (image && image->ImageData().resolution_estimate) + res_ring = {*image->ImageData().resolution_estimate}; + else + res_ring = {}; } + if (res_ring.empty()) + return; + QPen pen(feature_color, 5); pen.setCosmetic(true); @@ -278,7 +290,6 @@ void JFJochDiffractionImage::addCustomOverlay() { DrawSpots(); if (show_predictions) DrawPredictions(); - if (show_saturation) DrawSaturation(); } @@ -316,12 +327,7 @@ void JFJochDiffractionImage::setAutoForeground(bool input) { void JFJochDiffractionImage::setResolutionRing(QVector v) { res_ring = v; - res_ring_auto = false; - updateOverlay(); -} - -void JFJochDiffractionImage::setResolutionRingAuto() { - res_ring_auto = true; + ring_mode = RingMode::Manual; updateOverlay(); } @@ -385,3 +391,8 @@ void JFJochDiffractionImage::changeForeground(float val) { emit autoForegroundChanged(false); JFJochImage::changeForeground(val); } + +void JFJochDiffractionImage::setResolutionRingMode(RingMode mode) { + ring_mode = mode; + updateOverlay(); +} diff --git a/viewer/widgets/JFJochDiffractionImage.h b/viewer/widgets/JFJochDiffractionImage.h index b51e429b..7460e747 100644 --- a/viewer/widgets/JFJochDiffractionImage.h +++ b/viewer/widgets/JFJochDiffractionImage.h @@ -14,12 +14,13 @@ Q_OBJECT QColor ice_ring_color = Qt::cyan; public: - JFJochDiffractionImage(QWidget *parent = nullptr); + enum class RingMode {Auto, Estimation, Manual, None}; + Q_ENUM(RingMode) + JFJochDiffractionImage(QWidget *parent = nullptr); private: void addCustomOverlay() override; - void LoadImageInternal(); void DrawResolutionRings(); void DrawSpots(); @@ -34,7 +35,9 @@ private: int32_t show_highest_pixels = 0; QVector res_ring = {}; - bool res_ring_auto = false; + + RingMode ring_mode = RingMode::Manual; + bool show_spots = true; bool show_predictions = false; @@ -46,13 +49,13 @@ private: signals: void autoForegroundChanged(bool input); - public slots: void loadImage(std::shared_ptr image); void setAutoForeground(bool input); void setResolutionRing(QVector v); - void setResolutionRingAuto(); + void setResolutionRingMode(RingMode mode); + void showSpots(bool input); void showPredictions(bool input); diff --git a/viewer/widgets/ResolutionRingWidget.cpp b/viewer/widgets/ResolutionRingWidget.cpp new file mode 100644 index 00000000..defe7b6d --- /dev/null +++ b/viewer/widgets/ResolutionRingWidget.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include +#include +#include + +#include "ResolutionRingWidget.h" + +ResolutionRingWidget::ResolutionRingWidget(QWidget *parent) + : QWidget(parent) { + + res_rings_edit = new QLineEdit(this); + res_rings_edit->setPlaceholderText("Ring positions, e.g., 1.0,2.5,3.7"); + res_rings_edit->setEnabled(false); // Initially disabled as "Auto Res Rings" is checked by default + + none_button = new QRadioButton("None", this); + auto_button = new QRadioButton("Auto", this); + manual_button = new QRadioButton("Manual", this); + est_button = new QRadioButton("Resolution estimation", this); + + none_button->setChecked(true); + + button_group = new QButtonGroup(this); + button_group->addButton(none_button, 0); + button_group->addButton(auto_button, 1); + button_group->addButton(manual_button, 2); + button_group->addButton(est_button, 3); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(new QLabel("Resolution rings")); + layout->addWidget(none_button); + layout->addWidget(auto_button); + + QHBoxLayout *manual_layout = new QHBoxLayout(); + manual_layout->addWidget(manual_button); + manual_layout->addWidget(res_rings_edit); + layout->addLayout(manual_layout); + layout->addWidget(est_button); + + connect(res_rings_edit, &QLineEdit::editingFinished, this, &ResolutionRingWidget::editingFinished); + connect(auto_button, &QRadioButton::clicked, this, &ResolutionRingWidget::autoButtonClicked); + connect(manual_button, &QRadioButton::clicked, this, &ResolutionRingWidget::manualButtonClicked); + connect(est_button, &QRadioButton::clicked, this, &ResolutionRingWidget::estButtonClicked); + connect(none_button, &QRadioButton::clicked, this, &ResolutionRingWidget::noneButtonClicked); +} + +void ResolutionRingWidget::setRings(const QVector &v) { + QString txt; + for (float i : v) { + txt += QString::number(i, 'f', 4) + ","; + } + res_rings_edit->setText(txt); + res_rings_edit->setEnabled(true); + manual_button->setChecked(true); + editingFinished(); +} + +void ResolutionRingWidget::autoButtonClicked() { + res_rings_edit->setEnabled(false); + emit ringModeSet(JFJochDiffractionImage::RingMode::Auto); +} + +void ResolutionRingWidget::estButtonClicked() { + res_rings_edit->setEnabled(false); + emit ringModeSet(JFJochDiffractionImage::RingMode::Estimation); +} + +void ResolutionRingWidget::noneButtonClicked() { + res_rings_edit->setEnabled(false); + emit ringModeSet(JFJochDiffractionImage::RingMode::None); +} + +void ResolutionRingWidget::manualButtonClicked() { + res_rings_edit->setEnabled(true); + editingFinished(); +} + +void ResolutionRingWidget::editingFinished() { + QString text = res_rings_edit->text(); + QStringList stringList = text.split(',', Qt::SkipEmptyParts); + QVector manualResRings; + + bool validInput = true; + for (const QString &str: stringList) { + bool ok; + float value = str.toFloat(&ok); + if (ok && value >= 0) { + manualResRings.append(value); + } else { + validInput = false; + break; + } + } + + if (!validInput) { + res_rings_edit->setStyleSheet("border: 1px solid red;"); // Highlight error + } else { + res_rings_edit->setStyleSheet(""); // Reset to default style + emit resRingsSet(manualResRings); // Update resolution ring vector + } +} + +void ResolutionRingWidget::setRingMode(JFJochDiffractionImage::RingMode mode) { + /* */ +} diff --git a/viewer/widgets/ResolutionRingWidget.h b/viewer/widgets/ResolutionRingWidget.h new file mode 100644 index 00000000..1792f5ee --- /dev/null +++ b/viewer/widgets/ResolutionRingWidget.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#ifndef JFJOCH_RESOLUTIONRINGWIDGET_H +#define JFJOCH_RESOLUTIONRINGWIDGET_H + +#include +#include +#include +#include + +#include "JFJochDiffractionImage.h" + + +class ResolutionRingWidget : public QWidget { + Q_OBJECT + + QRadioButton *auto_button; + QRadioButton *est_button; + QRadioButton *manual_button; + QRadioButton *none_button; + + QButtonGroup *button_group; + QLineEdit *res_rings_edit = nullptr; + +public: + explicit ResolutionRingWidget(QWidget *parent = nullptr); + +signals: + void ringModeSet(JFJochDiffractionImage::RingMode mode); + void resRingsSet(QVector v); +public slots: + void setRings(const QVector &v); + void setRingMode(JFJochDiffractionImage::RingMode mode); + +private slots: + void editingFinished(); + void autoButtonClicked(); + void estButtonClicked(); + void manualButtonClicked(); + void noneButtonClicked(); +}; + + +#endif //JFJOCH_RESOLUTIONRINGWIDGET_H \ No newline at end of file