From 74f36710e48bb8bc26b1f20a4b0db019e0f561e9 Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Sun, 21 Jun 2026 17:09:20 +0200 Subject: [PATCH] viewer: converge processing settings into one tabbed window + Bragg/scaling One "Processing settings" window with tabs: Spot finding & indexing | Azimuthal | Bragg integration | Scaling, replacing the two separate settings windows. The spot/index and azimuthal tabs reuse the existing windows' widgets unchanged (their content is lifted into tabs via takeCentralWidget), so all their logic/signals keep working; Bragg integration and scaling are new editable panels (previously not adjustable in the GUI). JFJochImageReadingWorker gains UpdateBraggIntegrationSettings / UpdateScalingSettings; both persist as worker state and are re-imported into curr_experiment on file load / dataset update (like the indexing/azint settings), so they apply to interactive analysis (Bragg) and flow into processing jobs via GetReprocessingInputs (Bragg + scaling). Scaling only affects the job post-pass, so it is just stored, not reanalyzed. Verified: jfjoch_viewer builds and runs (offscreen) with the converged window. Co-Authored-By: Claude Opus 4.8 --- viewer/CMakeLists.txt | 6 ++ viewer/JFJochImageReadingWorker.cpp | 24 +++++- viewer/JFJochImageReadingWorker.h | 8 ++ viewer/JFJochViewerWindow.cpp | 18 +++-- .../windows/JFJochBraggIntegrationPanel.cpp | 51 ++++++++++++ viewer/windows/JFJochBraggIntegrationPanel.h | 29 +++++++ viewer/windows/JFJochScalingPanel.cpp | 77 +++++++++++++++++++ viewer/windows/JFJochScalingPanel.h | 33 ++++++++ viewer/windows/JFJochSettingsWindow.cpp | 40 ++++++++++ viewer/windows/JFJochSettingsWindow.h | 33 ++++++++ 10 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 viewer/windows/JFJochBraggIntegrationPanel.cpp create mode 100644 viewer/windows/JFJochBraggIntegrationPanel.h create mode 100644 viewer/windows/JFJochScalingPanel.cpp create mode 100644 viewer/windows/JFJochScalingPanel.h create mode 100644 viewer/windows/JFJochSettingsWindow.cpp create mode 100644 viewer/windows/JFJochSettingsWindow.h diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index 728c498a..8bddcb3f 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -99,6 +99,12 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView windows/JFJochMagnifierWindow.h windows/JFJochProcessingJobsWindow.cpp windows/JFJochProcessingJobsWindow.h + windows/JFJochSettingsWindow.cpp + windows/JFJochSettingsWindow.h + windows/JFJochBraggIntegrationPanel.cpp + windows/JFJochBraggIntegrationPanel.h + windows/JFJochScalingPanel.cpp + windows/JFJochScalingPanel.h ) TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Charts Qt6::Concurrent diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index d5af4b4b..509fe1c9 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -89,10 +89,14 @@ JFJochImageReadingWorker::JFJochImageReadingWorker(const SpotFindingSettings &se const DiffractionExperiment &experiment, QObject *parent) : QObject(parent), indexing_settings(experiment.GetIndexingSettings()), - azint_settings(experiment.GetAzimuthalIntegrationSettings()) { + azint_settings(experiment.GetAzimuthalIntegrationSettings()), + bragg_settings(experiment.GetBraggIntegrationSettings()), + scaling_settings(experiment.GetScalingSettings()) { qRegisterMetaType>("QVector"); qRegisterMetaType("BrokerStatus"); qRegisterMetaType("ROIDefinition"); + qRegisterMetaType("BraggIntegrationSettings"); + qRegisterMetaType("ScalingSettings"); spot_finding_settings = settings; indexing = std::make_unique(indexing_settings); @@ -276,6 +280,8 @@ void JFJochImageReadingWorker::LoadFile_i(const QString &filename, qint64 image_ roi_override_.reset(); // new file: use its ROIs, forget any earlier edits curr_experiment.ImportIndexingSettings(indexing_settings); curr_experiment.ImportAzimuthalIntegrationSettings(azint_settings); + curr_experiment.ImportBraggIntegrationSettings(bragg_settings); + curr_experiment.ImportScalingSettings(scaling_settings); UpdateAzint_i(dataset.get()); } @@ -457,6 +463,8 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optional) @@ -67,6 +71,8 @@ private: std::optional roi_override_; IndexingSettings indexing_settings; AzimuthalIntegrationSettings azint_settings; + BraggIntegrationSettings bragg_settings; + ScalingSettings scaling_settings; std::unique_ptr indexing; std::shared_ptr current_image_ptr; @@ -177,6 +183,8 @@ public slots: void UpdateSpotFindingSettings(const SpotFindingSettings &settings, const IndexingSettings &indexing, int64_t max_spots); void ReanalyzeImages(bool input); void UpdateAzintSettings(const AzimuthalIntegrationSettings& settings); + void UpdateBraggIntegrationSettings(BraggIntegrationSettings settings); + void UpdateScalingSettings(ScalingSettings settings); void SetROIDefinition(const ROIDefinition &rois); void DownloadROIsFromServer(); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 36139c4c..c2948e21 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -20,6 +20,7 @@ #endif #include "windows/JFJochViewerProcessingWindow.h" #include "windows/JFJochProcessingJobsWindow.h" +#include "windows/JFJochSettingsWindow.h" #include "windows/JFJochViewerSpotListWindow.h" #include "windows/JFJochViewerReflectionListWindow.h" #include "windows/JFJochCalibrationWindow.h" @@ -103,11 +104,13 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString auto metadataWindow = new JFJochViewerMetadataWindow(this); auto spotWindow = new JFJochViewerSpotListWindow(this); auto reflectionWindow = new JFJochViewerReflectionListWindow(this); - auto processingWindow = new JFJochViewerProcessingWindow(spot_finding_settings, indexing_settings, this); + auto settingsWindow = new JFJochSettingsWindow(spot_finding_settings, indexing_settings, + experiment.GetAzimuthalIntegrationSettings(), + experiment.GetBraggIntegrationSettings(), + experiment.GetScalingSettings(), this); auto calibrationWindow = new JFJochCalibrationWindow(this); auto reciprocalWindow = new JFJochViewerReciprocalSpaceWindow(this); - auto azintWindow = new JFJochAzIntWindow(experiment.GetAzimuthalIntegrationSettings(), this); auto azintImageWindow = new JFJoch2DAzintImageWindow(this); auto magnifierWindow = new JFJochMagnifierWindow(this); @@ -117,10 +120,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString menuBar->AddWindowEntry(spotWindow, "Spot list"); menuBar->AddWindowEntry(reflectionWindow, "Reflection list"); menuBar->AddWindowEntry(metadataWindow, "Image metadata"); - menuBar->AddWindowEntry(processingWindow, "Image processing settings"); + menuBar->AddWindowEntry(settingsWindow, "Processing settings"); menuBar->AddWindowEntry(calibrationWindow, "Calibration image viewer"); menuBar->AddWindowEntry(reciprocalWindow, "Reciprocal space viewer"); - menuBar->AddWindowEntry(azintWindow, "Azimuthal integration settings"); menuBar->AddWindowEntry(azintImageWindow, "Azimuthal integration 2D image"); menuBar->AddWindowEntry(magnifierWindow, "Magnifier"); menuBar->AddWindowEntry(processingJobsWindow, "Processing"); @@ -260,8 +262,12 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(reading_worker, &JFJochImageReadingWorker::imageLoaded, reflectionWindow, &JFJochViewerReflectionListWindow::imageLoaded); - connect(processingWindow, &JFJochViewerProcessingWindow::settingsChanged, + connect(settingsWindow, &JFJochSettingsWindow::spotFindingChanged, reading_worker, &JFJochImageReadingWorker::UpdateSpotFindingSettings); + connect(settingsWindow, &JFJochSettingsWindow::braggChanged, + reading_worker, &JFJochImageReadingWorker::UpdateBraggIntegrationSettings); + connect(settingsWindow, &JFJochSettingsWindow::scalingChanged, + reading_worker, &JFJochImageReadingWorker::UpdateScalingSettings); connect(reflectionWindow, &JFJochHelperWindow::zoom, viewer, &JFJochDiffractionImage::centerOnSpot); connect(spotWindow, &JFJochHelperWindow::zoom, viewer, &JFJochDiffractionImage::centerOnSpot); @@ -343,7 +349,7 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(side_panel, &JFJochViewerSidePanel::setFeatureColor, reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::setFeatureColor); - connect(azintWindow, &JFJochAzIntWindow::settingsChanged, + connect(settingsWindow, &JFJochSettingsWindow::azintChanged, reading_worker, &JFJochImageReadingWorker::UpdateAzintSettings); connect(azintImageWindow, &JFJoch2DAzintImageWindow::zoomOnBin, diff --git a/viewer/windows/JFJochBraggIntegrationPanel.cpp b/viewer/windows/JFJochBraggIntegrationPanel.cpp new file mode 100644 index 00000000..10fa7e64 --- /dev/null +++ b/viewer/windows/JFJochBraggIntegrationPanel.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochBraggIntegrationPanel.h" + +#include +#include +#include + +JFJochBraggIntegrationPanel::JFJochBraggIntegrationPanel(const BraggIntegrationSettings &settings, QWidget *parent) + : QWidget(parent) { + auto *layout = new QVBoxLayout(this); + auto *group = new QGroupBox("Bragg integration", this); + auto *form = new QFormLayout(group); + + m_r1 = new SliderPlusBox(1.0, 20.0, 0.5, 1, this); + m_r1->setValue(settings.GetR1()); + form->addRow("Signal box radius r1 [px]", m_r1); + + m_r2 = new SliderPlusBox(1.0, 25.0, 0.5, 1, this); + m_r2->setValue(settings.GetR2()); + form->addRow("Background inner radius r2 [px]", m_r2); + + m_r3 = new SliderPlusBox(1.0, 30.0, 0.5, 1, this); + m_r3->setValue(settings.GetR3()); + form->addRow("Background outer radius r3 [px]", m_r3); + + m_profileMultiplier = new SliderPlusBox(1.0, 15.0, 0.5, 1, this); + m_profileMultiplier->setValue(settings.GetProfileMultiplier()); + form->addRow("Profile multiplier (PixelRefine)", m_profileMultiplier); + + m_dMin = new SliderPlusBox(0.3, 5.0, 0.1, 1, this); + m_dMin->setValue(settings.GetDMinLimit_A()); + form->addRow("High-resolution limit [Å]", m_dMin); + + layout->addWidget(group); + layout->addStretch(); + + for (auto *slider: {m_r1, m_r2, m_r3, m_profileMultiplier, m_dMin}) + connect(slider, &SliderPlusBox::valueChanged, this, [this](double) { emitChanged(); }); +} + +void JFJochBraggIntegrationPanel::emitChanged() { + BraggIntegrationSettings s; + s.R1(static_cast(m_r1->value())) + .R2(static_cast(m_r2->value())) + .R3(static_cast(m_r3->value())) + .ProfileMultiplier(static_cast(m_profileMultiplier->value())) + .DMinLimit_A(static_cast(m_dMin->value())); + emit settingsChanged(s); +} diff --git a/viewer/windows/JFJochBraggIntegrationPanel.h b/viewer/windows/JFJochBraggIntegrationPanel.h new file mode 100644 index 00000000..daddeba6 --- /dev/null +++ b/viewer/windows/JFJochBraggIntegrationPanel.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "../widgets/SliderPlusBox.h" +#include "../../common/BraggIntegrationSettings.h" + +// Bragg integration settings as a tab panel for the converged settings window. Emits a fresh +// BraggIntegrationSettings whenever a control changes (only the exposed fields are controlled; +// the rest keep their defaults). +class JFJochBraggIntegrationPanel : public QWidget { + Q_OBJECT + SliderPlusBox *m_r1; + SliderPlusBox *m_r2; + SliderPlusBox *m_r3; + SliderPlusBox *m_profileMultiplier; + SliderPlusBox *m_dMin; + + void emitChanged(); + +public: + explicit JFJochBraggIntegrationPanel(const BraggIntegrationSettings &settings, QWidget *parent = nullptr); + +signals: + void settingsChanged(BraggIntegrationSettings settings); +}; diff --git a/viewer/windows/JFJochScalingPanel.cpp b/viewer/windows/JFJochScalingPanel.cpp new file mode 100644 index 00000000..31378da3 --- /dev/null +++ b/viewer/windows/JFJochScalingPanel.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochScalingPanel.h" + +#include +#include +#include +#include +#include + +JFJochScalingPanel::JFJochScalingPanel(const ScalingSettings &settings, QWidget *parent) + : QWidget(parent) { + auto *layout = new QVBoxLayout(this); + auto *group = new QGroupBox("Scaling && merging", this); + auto *form = new QFormLayout(group); + + m_partiality = new QComboBox(this); + m_partiality->addItem("Fixed", static_cast(PartialityModel::Fixed)); + m_partiality->addItem("Rotation", static_cast(PartialityModel::Rotation)); + m_partiality->addItem("Unity", static_cast(PartialityModel::Unity)); + const auto pm = settings.GetPartialityModel().value_or(PartialityModel::Fixed); + if (const int idx = m_partiality->findData(static_cast(pm)); idx >= 0) + m_partiality->setCurrentIndex(idx); + form->addRow("Partiality model", m_partiality); + + m_mergeFriedel = new QCheckBox("Merge Friedel pairs", this); + m_mergeFriedel->setChecked(settings.GetMergeFriedel()); + form->addRow("", m_mergeFriedel); + + m_refineB = new QCheckBox("Refine per-image B-factor", this); + m_refineB->setChecked(settings.GetRefineB()); + form->addRow("", m_refineB); + + m_minPartiality = new SliderPlusBox(0.0, 1.0, 0.01, 2, this); + m_minPartiality->setValue(settings.GetMinPartiality()); + form->addRow("Minimum partiality", m_minPartiality); + + m_outlierNsigma = new SliderPlusBox(0.0, 10.0, 0.5, 1, this); + m_outlierNsigma->setValue(settings.GetOutlierRejectNsigma()); + form->addRow("Outlier rejection [σ, 0 = off]", m_outlierNsigma); + + m_limitResolution = new QCheckBox("Limit resolution", this); + m_limitResolution->setChecked(settings.GetHighResolutionLimit_A().has_value()); + form->addRow("", m_limitResolution); + + m_highRes = new SliderPlusBox(0.5, 5.0, 0.1, 1, this); + m_highRes->setValue(settings.GetHighResolutionLimit_A().value_or(2.0)); + m_highRes->setEnabled(m_limitResolution->isChecked()); + form->addRow("High-resolution limit [Å]", m_highRes); + + layout->addWidget(group); + layout->addStretch(); + + connect(m_partiality, &QComboBox::currentIndexChanged, this, [this](int) { emitChanged(); }); + connect(m_mergeFriedel, &QCheckBox::toggled, this, [this](bool) { emitChanged(); }); + connect(m_refineB, &QCheckBox::toggled, this, [this](bool) { emitChanged(); }); + connect(m_minPartiality, &SliderPlusBox::valueChanged, this, [this](double) { emitChanged(); }); + connect(m_outlierNsigma, &SliderPlusBox::valueChanged, this, [this](double) { emitChanged(); }); + connect(m_limitResolution, &QCheckBox::toggled, this, [this](bool checked) { + m_highRes->setEnabled(checked); + emitChanged(); + }); + connect(m_highRes, &SliderPlusBox::valueChanged, this, [this](double) { emitChanged(); }); +} + +void JFJochScalingPanel::emitChanged() { + ScalingSettings s; + s.SetPartialityModel(static_cast(m_partiality->currentData().toInt())); + s.MergeFriedel(m_mergeFriedel->isChecked()); + s.RefineB(m_refineB->isChecked()); + s.MinPartiality(m_minPartiality->value()); + s.OutlierRejectNsigma(m_outlierNsigma->value()); + if (m_limitResolution->isChecked()) + s.HighResolutionLimit_A(m_highRes->value()); + emit settingsChanged(s); +} diff --git a/viewer/windows/JFJochScalingPanel.h b/viewer/windows/JFJochScalingPanel.h new file mode 100644 index 00000000..4cc87ca2 --- /dev/null +++ b/viewer/windows/JFJochScalingPanel.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "../widgets/SliderPlusBox.h" +#include "../../common/ScalingSettings.h" + +class QComboBox; +class QCheckBox; + +// Scaling / merging settings as a tab panel for the converged settings window. These feed +// processing jobs (the scale/merge post-pass); the viewer's single-image analysis ignores them. +class JFJochScalingPanel : public QWidget { + Q_OBJECT + QComboBox *m_partiality; + QCheckBox *m_mergeFriedel; + QCheckBox *m_refineB; + QCheckBox *m_limitResolution; + SliderPlusBox *m_highRes; + SliderPlusBox *m_minPartiality; + SliderPlusBox *m_outlierNsigma; + + void emitChanged(); + +public: + explicit JFJochScalingPanel(const ScalingSettings &settings, QWidget *parent = nullptr); + +signals: + void settingsChanged(ScalingSettings settings); +}; diff --git a/viewer/windows/JFJochSettingsWindow.cpp b/viewer/windows/JFJochSettingsWindow.cpp new file mode 100644 index 00000000..51b6ee2d --- /dev/null +++ b/viewer/windows/JFJochSettingsWindow.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochSettingsWindow.h" + +#include + +JFJochSettingsWindow::JFJochSettingsWindow(const SpotFindingSettings &spot, const IndexingSettings &indexing, + const AzimuthalIntegrationSettings &azint, + const BraggIntegrationSettings &bragg, + const ScalingSettings &scaling, QWidget *parent) + : JFJochHelperWindow(parent) { + setWindowTitle("Processing settings"); + + // Reuse the existing spot/index + azimuthal windows: build them (kept hidden) and lift their + // content into tabs, so all their existing widgets, logic and signals keep working. + m_processing = new JFJochViewerProcessingWindow(spot, indexing, this); + m_azint = new JFJochAzIntWindow(azint, this); + m_bragg = new JFJochBraggIntegrationPanel(bragg, this); + m_scaling = new JFJochScalingPanel(scaling, this); + + auto *tabs = new QTabWidget(this); + tabs->addTab(m_processing->takeCentralWidget(), "Spot finding && indexing"); + tabs->addTab(m_azint->takeCentralWidget(), "Azimuthal"); + tabs->addTab(m_bragg, "Bragg integration"); + tabs->addTab(m_scaling, "Scaling"); + setCentralWidget(tabs); + + m_processing->hide(); // the emptied shells stay alive only to drive their signals + m_azint->hide(); + + connect(m_processing, &JFJochViewerProcessingWindow::settingsChanged, + this, &JFJochSettingsWindow::spotFindingChanged); + connect(m_azint, &JFJochAzIntWindow::settingsChanged, + this, &JFJochSettingsWindow::azintChanged); + connect(m_bragg, &JFJochBraggIntegrationPanel::settingsChanged, + this, &JFJochSettingsWindow::braggChanged); + connect(m_scaling, &JFJochScalingPanel::settingsChanged, + this, &JFJochSettingsWindow::scalingChanged); +} diff --git a/viewer/windows/JFJochSettingsWindow.h b/viewer/windows/JFJochSettingsWindow.h new file mode 100644 index 00000000..ccb2dc27 --- /dev/null +++ b/viewer/windows/JFJochSettingsWindow.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include "JFJochHelperWindow.h" +#include "JFJochViewerProcessingWindow.h" +#include "JFJochAzIntWindow.h" +#include "JFJochBraggIntegrationPanel.h" +#include "JFJochScalingPanel.h" + +// One tabbed window converging all processing settings: Spot finding & indexing | Azimuthal | +// Bragg integration | Scaling. The first two tabs reuse the existing settings widgets (their +// content is lifted into tabs, keeping all their logic), Bragg + scaling are new. Single source of +// truth for both interactive single-image analysis and processing jobs. +class JFJochSettingsWindow : public JFJochHelperWindow { + Q_OBJECT + JFJochViewerProcessingWindow *m_processing; + JFJochAzIntWindow *m_azint; + JFJochBraggIntegrationPanel *m_bragg; + JFJochScalingPanel *m_scaling; + +public: + JFJochSettingsWindow(const SpotFindingSettings &spot, const IndexingSettings &indexing, + const AzimuthalIntegrationSettings &azint, const BraggIntegrationSettings &bragg, + const ScalingSettings &scaling, QWidget *parent = nullptr); + +signals: + void spotFindingChanged(const SpotFindingSettings &settings, const IndexingSettings &indexing, int64_t max_spots); + void azintChanged(const AzimuthalIntegrationSettings &settings); + void braggChanged(BraggIntegrationSettings settings); + void scalingChanged(ScalingSettings settings); +};