viewer: converge processing settings into one tabbed window + Bragg/scaling
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 11m43s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 12m24s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m33s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 12m34s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 12m58s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m38s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m33s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m22s
Build Packages / build:rpm (rocky8) (push) Successful in 13m41s
Build Packages / Generate python client (push) Successful in 14s
Build Packages / build:rpm (rocky9) (push) Successful in 14m14s
Build Packages / Create release (push) Skipped
Build Packages / DIALS test (push) Successful in 13m55s
Build Packages / Build documentation (push) Successful in 45s
Build Packages / XDS test (durin plugin) (push) Successful in 6m54s
Build Packages / XDS test (neggia plugin) (push) Successful in 5m50s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m31s
Build Packages / Unit tests (push) Failing after 1h0m10s

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 17:09:20 +02:00
co-authored by Claude Opus 4.8
parent c916cf8b2d
commit 74f36710e4
10 changed files with 312 additions and 7 deletions
+6
View File
@@ -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
+23 -1
View File
@@ -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<QRect>>("QVector<QRect>");
qRegisterMetaType<BrokerStatus>("BrokerStatus");
qRegisterMetaType<ROIDefinition>("ROIDefinition");
qRegisterMetaType<BraggIntegrationSettings>("BraggIntegrationSettings");
qRegisterMetaType<ScalingSettings>("ScalingSettings");
spot_finding_settings = settings;
indexing = std::make_unique<IndexerThreadPool>(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<DiffractionEx
curr_experiment.ROI().SetROI(*roi_override_); // keep edited ROIs across settings changes
curr_experiment.ImportIndexingSettings(indexing_settings);
curr_experiment.ImportAzimuthalIntegrationSettings(azint_settings);
curr_experiment.ImportBraggIntegrationSettings(bragg_settings);
curr_experiment.ImportScalingSettings(scaling_settings);
UpdateAzint_i(dataset.get());
emit datasetLoaded(dataset);
@@ -579,6 +587,20 @@ void JFJochImageReadingWorker::UpdateAzintSettings(const AzimuthalIntegrationSet
UpdateDataset_i(std::nullopt);
}
void JFJochImageReadingWorker::UpdateBraggIntegrationSettings(BraggIntegrationSettings settings) {
QMutexLocker locker(&m);
bragg_settings = settings;
UpdateDataset_i(std::nullopt);
}
void JFJochImageReadingWorker::UpdateScalingSettings(ScalingSettings settings) {
QMutexLocker locker(&m);
scaling_settings = settings;
// Scaling only affects the processing-job post-pass (jobs read curr_experiment via
// GetReprocessingInputs), not interactive single-image analysis - just store + import it.
curr_experiment.ImportScalingSettings(scaling_settings);
}
void JFJochImageReadingWorker::SetROIDefinition(const ROIDefinition &rois) {
QMutexLocker locker(&m);
SetROIDefinition_i(rois);
+8
View File
@@ -15,6 +15,8 @@
#include "../common/Logger.h"
#include "../reader/JFJochHttpReader.h"
#include "../image_analysis/MXAnalysisWithoutFPGA.h"
#include "../common/BraggIntegrationSettings.h"
#include "../common/ScalingSettings.h"
#include "../image_analysis/bragg_prediction/BraggPrediction.h"
#include "SimpleImage.h"
#include "../common/MovingAverage.h"
@@ -27,6 +29,8 @@ Q_DECLARE_METATYPE(DiffractionExperiment)
Q_DECLARE_METATYPE(SpotFindingSettings)
Q_DECLARE_METATYPE(IndexingSettings)
Q_DECLARE_METATYPE(AzimuthalIntegrationSettings)
Q_DECLARE_METATYPE(BraggIntegrationSettings)
Q_DECLARE_METATYPE(ScalingSettings)
Q_DECLARE_METATYPE(UnitCell)
Q_DECLARE_METATYPE(std::shared_ptr<const SimpleImage>)
@@ -67,6 +71,8 @@ private:
std::optional<ROIDefinition> roi_override_;
IndexingSettings indexing_settings;
AzimuthalIntegrationSettings azint_settings;
BraggIntegrationSettings bragg_settings;
ScalingSettings scaling_settings;
std::unique_ptr<IndexerThreadPool> indexing;
std::shared_ptr<JFJochReaderImage> 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();
+12 -6
View File
@@ -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,
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochBraggIntegrationPanel.h"
#include <QFormLayout>
#include <QGroupBox>
#include <QVBoxLayout>
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<float>(m_r1->value()))
.R2(static_cast<float>(m_r2->value()))
.R3(static_cast<float>(m_r3->value()))
.ProfileMultiplier(static_cast<float>(m_profileMultiplier->value()))
.DMinLimit_A(static_cast<float>(m_dMin->value()));
emit settingsChanged(s);
}
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QWidget>
#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);
};
+77
View File
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochScalingPanel.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QVBoxLayout>
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<int>(PartialityModel::Fixed));
m_partiality->addItem("Rotation", static_cast<int>(PartialityModel::Rotation));
m_partiality->addItem("Unity", static_cast<int>(PartialityModel::Unity));
const auto pm = settings.GetPartialityModel().value_or(PartialityModel::Fixed);
if (const int idx = m_partiality->findData(static_cast<int>(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<PartialityModel>(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);
}
+33
View File
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QWidget>
#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);
};
+40
View File
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochSettingsWindow.h"
#include <QTabWidget>
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);
}
+33
View File
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// 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);
};