diff --git a/viewer/resources/jfjoch.icns b/viewer/resources/jfjoch.icns index dac5ef7a..8d85f24e 100644 Binary files a/viewer/resources/jfjoch.icns and b/viewer/resources/jfjoch.icns differ diff --git a/viewer/resources/jfjoch.ico b/viewer/resources/jfjoch.ico index be518b48..d677bbcf 100644 Binary files a/viewer/resources/jfjoch.ico and b/viewer/resources/jfjoch.ico differ diff --git a/viewer/resources/jfjoch.png b/viewer/resources/jfjoch.png index aa75a7ea..56315f96 100644 Binary files a/viewer/resources/jfjoch.png and b/viewer/resources/jfjoch.png differ diff --git a/viewer/resources/jfjoch.svg b/viewer/resources/jfjoch.svg new file mode 100644 index 00000000..422769c0 --- /dev/null +++ b/viewer/resources/jfjoch.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/viewer/windows/JFJochAzIntWindow.cpp b/viewer/windows/JFJochAzIntWindow.cpp deleted file mode 100644 index 5a5b5647..00000000 --- a/viewer/windows/JFJochAzIntWindow.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#include "JFJochAzIntWindow.h" -#include -#include -#include - -#include "../widgets/SliderPlusBox.h" - -JFJochAzIntWindow::JFJochAzIntWindow( - const AzimuthalIntegrationSettings &settings, - QWidget *parent) - : JFJochHelperWindow(parent), - m_settings(settings) -{ - setWindowTitle("Azimuthal integration settings"); - QWidget *centralWidget = new QWidget(this); - setCentralWidget(centralWidget); - - auto mainLayout = new QVBoxLayout(centralWidget); - - // Group box similar to TS Paper + stacked inputs - auto group = new QGroupBox("Azimuthal integration", this); - auto formLayout = new QFormLayout(group); - - // Q spacing - m_qSpacing = new SliderPlusBox(1e-5, 1.0, 0.001, 5, this, SliderPlusBox::ScaleType::Logarithmic); - m_qSpacing->setValue(m_settings.GetQSpacing_recipA()); - formLayout->addRow(tr("Q spacing [Å⁻¹]:"), m_qSpacing); - - // Low Q - m_lowQ = new SliderPlusBox(1e-5, 10.0, 0.001, 4, this); - m_lowQ->setValue(m_settings.GetLowQ_recipA()); - formLayout->addRow(tr("Low Q [Å⁻¹]:"), m_lowQ); - - // High Q - m_highQ = new SliderPlusBox(2e-5, 10.0, 0.001, 4, this); - m_highQ->setValue(m_settings.GetHighQ_recipA()); - formLayout->addRow(tr("High Q [Å⁻¹]:"), m_highQ); - - // Solid angle / polarization correction checkboxes - m_solidAngleCheckBox = new QCheckBox(tr("Solid angle correction"), this); - m_solidAngleCheckBox->setChecked(m_settings.IsSolidAngleCorrection()); - formLayout->addRow(QString(), m_solidAngleCheckBox); - - m_polarizationCheckBox = new QCheckBox(tr("Polarization correction"), this); - m_polarizationCheckBox->setChecked(m_settings.IsPolarizationCorrection()); - formLayout->addRow(QString(), m_polarizationCheckBox); - - // Azimuthal bins (radio buttons like TS frontend) - auto azimGroupBox = new QGroupBox(tr("Azimuthal bins"), this); - auto azimLayout = new QHBoxLayout(azimGroupBox); - m_azimuthalBinsGroup = new QButtonGroup(this); - - const QList binOptions = {1, 2, 4, 8, 16, 32, 64, 128}; - const int currentBins = m_settings.GetAzimuthalBinCount(); - - for (int bins : binOptions) { - auto *btn = new QRadioButton(QString::number(bins), azimGroupBox); - m_azimuthalBinsGroup->addButton(btn, bins); - m_azimuthalBinButtons.push_back(btn); - azimLayout->addWidget(btn); - if (bins == currentBins) { - btn->setChecked(true); - } - } - - group->setLayout(formLayout); - mainLayout->addWidget(group); - mainLayout->addWidget(azimGroupBox); - - // Error label at bottom (no buttons) - m_errorLabel = new QLabel(this); - m_errorLabel->setStyleSheet("color: rgb(200, 0, 0);"); // red-ish text for errors - m_errorLabel->setWordWrap(true); - mainLayout->addWidget(m_errorLabel); - - // Connections – on change, update errors, settings, and emit - connect(m_qSpacing, &SliderPlusBox::valueChanged, [this](double val) { - m_qSpacingError = (val < 1e-5); - Update(); - }); - - connect(m_lowQ, &SliderPlusBox::valueChanged, [this](double val) { - m_lowQError = (val < 1e-5 || val > 10.0); - Update(); - }); - - connect(m_highQ, &SliderPlusBox::valueChanged, [this](double val) { - m_highQError = (val < 2e-5 || val > 10.0); - Update(); - }); - - connect(m_solidAngleCheckBox, &QCheckBox::toggled, [this](bool /*checked*/) { - Update(); - }); - - connect(m_polarizationCheckBox, &QCheckBox::toggled, [this](bool /*checked*/) { - Update(); - }); - - connect(m_azimuthalBinsGroup, - QOverload::of(&QButtonGroup::buttonClicked), - [this](QAbstractButton * /*btn*/) { - Update(); - }); - - Update(); -} - - -void JFJochAzIntWindow::UpdateErrorLabel() { - const double lowQ = m_lowQ->value(); - const double highQ = m_highQ->value(); - - QStringList messages; - - if (m_qSpacingError) { - messages << tr("Q spacing must be ≥ 1e-5 Å⁻¹."); - } - if (m_lowQError) { - messages << tr("Low Q must be between 1e-5 and 10 Å⁻¹."); - } - if (m_highQError) { - messages << tr("High Q must be between 2e-5 and 10 Å⁻¹."); - } - if (highQ <= lowQ) { - messages << tr("High Q must be greater than Low Q."); - } - - if (messages.isEmpty()) { - m_errorLabel->clear(); - } else { - m_errorLabel->setText(messages.join(' ')); - } -} - -void JFJochAzIntWindow::ApplyToSettings() { - const float lowQ = static_cast(m_lowQ->value()); - const float highQ = static_cast(m_highQ->value()); - const float qSpacing = static_cast(m_qSpacing->value()); - - m_settings.SolidAngleCorrection(m_solidAngleCheckBox->isChecked()); - m_settings.PolarizationCorrection(m_polarizationCheckBox->isChecked()); - m_settings.QRange_recipA(lowQ, highQ); - m_settings.QSpacing_recipA(qSpacing); - - const int bins = m_azimuthalBinsGroup->checkedId(); - if (bins > 0) { - m_settings.AzimuthalBinCount(bins); - } -} - -void JFJochAzIntWindow::Update() { - // Update error label first - UpdateErrorLabel(); - - // If there are validation errors, do not update settings / emit - const double lowQ = m_lowQ->value(); - const double highQ = m_highQ->value(); - const bool rangeError = (highQ <= lowQ); - const bool anyError = m_lowQError || m_highQError || m_qSpacingError || rangeError; - - if (anyError) - return; - - // Update internal settings from widgets - ApplyToSettings(); - - // Emit updated settings when valid - emit settingsChanged(m_settings); -} \ No newline at end of file diff --git a/viewer/windows/JFJochAzIntWindow.h b/viewer/windows/JFJochAzIntWindow.h deleted file mode 100644 index 3e7d796b..00000000 --- a/viewer/windows/JFJochAzIntWindow.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#pragma once - -#include "JFJochHelperWindow.h" -#include "../widgets/SliderPlusBox.h" -#include -#include -#include - -class JFJochAzIntWindow : public JFJochHelperWindow { - Q_OBJECT - - AzimuthalIntegrationSettings m_settings; - - SliderPlusBox *m_qSpacing; - SliderPlusBox *m_lowQ; - SliderPlusBox *m_highQ; - - QCheckBox *m_solidAngleCheckBox; - QCheckBox *m_polarizationCheckBox; - - QButtonGroup *m_azimuthalBinsGroup; - QList m_azimuthalBinButtons; - - QLabel *m_errorLabel; - - bool m_lowQError = false; - bool m_highQError = false; - bool m_qSpacingError = false; - - void UpdateErrorLabel(); - void ApplyToSettings(); - void Update(); -public: - explicit JFJochAzIntWindow( - const AzimuthalIntegrationSettings &settings, - QWidget *parent = nullptr - ); - -signals: - void settingsChanged(const AzimuthalIntegrationSettings &settings); -}; - diff --git a/viewer/windows/JFJochBraggIntegrationPanel.cpp b/viewer/windows/JFJochBraggIntegrationPanel.cpp deleted file mode 100644 index 10fa7e64..00000000 --- a/viewer/windows/JFJochBraggIntegrationPanel.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// 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 deleted file mode 100644 index daddeba6..00000000 --- a/viewer/windows/JFJochBraggIntegrationPanel.h +++ /dev/null @@ -1,29 +0,0 @@ -// 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 deleted file mode 100644 index 31378da3..00000000 --- a/viewer/windows/JFJochScalingPanel.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// 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 deleted file mode 100644 index 4cc87ca2..00000000 --- a/viewer/windows/JFJochScalingPanel.h +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index 1988d5a1..00000000 --- a/viewer/windows/JFJochSettingsWindow.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// 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->TakeSpotFindingPage(), "Spot finding"); - tabs->addTab(m_processing->TakeIndexingPage(), "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); -} - -void JFJochSettingsWindow::datasetLoaded(std::shared_ptr in_dataset) { - m_processing->setUnitCellKnown(in_dataset && in_dataset->experiment.GetUnitCell().has_value()); -} diff --git a/viewer/windows/JFJochSettingsWindow.h b/viewer/windows/JFJochSettingsWindow.h deleted file mode 100644 index 387810f5..00000000 --- a/viewer/windows/JFJochSettingsWindow.h +++ /dev/null @@ -1,36 +0,0 @@ -// 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); - - // Forwards the dataset's unit-cell state to the indexing tab (to resolve the Auto algorithm). - void datasetLoaded(std::shared_ptr in_dataset) override; - -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); -}; diff --git a/viewer/windows/JFJochViewerProcessingWindow.cpp b/viewer/windows/JFJochViewerProcessingWindow.cpp deleted file mode 100644 index a5cdc3a1..00000000 --- a/viewer/windows/JFJochViewerProcessingWindow.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#include "JFJochViewerProcessingWindow.h" - -#include -#include -#include -#include -#include -#include - -#include "../../common/CUDAWrapper.h" // get_gpu_count() - -namespace { - struct RadioChoice { const char *label; int value; }; - - QString algorithm_name(IndexingAlgorithmEnum a) { - switch (a) { - case IndexingAlgorithmEnum::FFBIDX: return "FFBIDX (GPU)"; - case IndexingAlgorithmEnum::FFT: return "FFT (GPU)"; - case IndexingAlgorithmEnum::FFTW: return "FFTW (CPU)"; - case IndexingAlgorithmEnum::Auto: return "Auto"; - default: return "None"; - } - } - - // A titled group of mutually-exclusive radio buttons, with an optional explanatory line. - QGroupBox *MakeRadioGroup(const QString &title, const QString &help, - const std::vector &choices, int current, - QButtonGroup *group, QWidget *parent) { - auto *box = new QGroupBox(title, parent); - auto *layout = new QVBoxLayout(box); - if (!help.isEmpty()) { - auto *label = new QLabel(help, box); - label->setWordWrap(true); - layout->addWidget(label); - } - for (const auto &c: choices) { - auto *button = new QRadioButton(c.label, box); - button->setChecked(c.value == current); - group->addButton(button, c.value); - layout->addWidget(button); - } - return box; - } -} - -JFJochViewerProcessingWindow::JFJochViewerProcessingWindow(const SpotFindingSettings &settings, const IndexingSettings& indexing,QWidget *parent) - : JFJochHelperWindow(parent), m_settings(settings), m_indexing(indexing) { - - setWindowTitle("Image processing settings"); - - // --- Spot finding page --- - m_spotFindingPage = new QWidget(this); - auto spotLayout = new QVBoxLayout(m_spotFindingPage); - - auto generalGroup = new QGroupBox("Spot finding", m_spotFindingPage); - auto generalLayout = new QFormLayout(generalGroup); - - m_enableCheckBox = new QCheckBox("Enable", this); - m_enableCheckBox->setChecked(m_settings.enable); - generalLayout->addRow("", m_enableCheckBox); - - m_signalToNoise = new SliderPlusBox(1.0, 10.0, 0.1, 1, this); - m_signalToNoise->setValue(m_settings.signal_to_noise_threshold); - generalLayout->addRow("Signal to Noise threshold:", m_signalToNoise); - - m_photonCount = new SliderPlusBox(0.0, 100.0, 1.0, 0, this); - m_photonCount->setValue(std::lround(m_settings.photon_count_threshold)); - generalLayout->addRow("Photon count threshold:", m_photonCount); - - m_minPixPerSpot = new SliderPlusBox(1.0, 20.0, 1.0, 0, this); - m_minPixPerSpot->setValue(std::lround(m_settings.min_pix_per_spot)); - generalLayout->addRow("Minimum pixels per spot:", m_minPixPerSpot); - - m_maxPixPerSpot = new SliderPlusBox(10.0, 200.0, 1.0, 0, this); - m_maxPixPerSpot->setValue(std::lround(m_settings.max_pix_per_spot)); - generalLayout->addRow("Maximum pixels per spot:", m_maxPixPerSpot); - - // New: Max spot count slider [100 .. 2000] - m_maxSpotCount = new SliderPlusBox(100.0, 2000.0, 10.0, 0, this); - // If there is no existing value source, initialize to a sensible default - m_maxSpotCount->setValue(1000.0); - generalLayout->addRow("Maximum spots per image:", m_maxSpotCount); - - m_highResolution = new SliderPlusBox(0.8, 10.0, 0.1, 1, this); - m_highResolution->setValue(m_settings.high_resolution_limit); - generalLayout->addRow("High resolution", m_highResolution); - - m_lowResolution = new SliderPlusBox(10.0, 100.0, 0.1, 1, this); - m_lowResolution->setValue(m_settings.low_resolution_limit); - generalLayout->addRow("Low resolution", m_lowResolution); - - // High-res spurious spot filter (gap in 1/d) - m_highResSpuriousFilterCheckBox = new QCheckBox("Enable high-res spurious spot filter", this); - const bool gapFilterEnabled = m_settings.high_res_gap_Q_recipA > 0.0f; - m_highResSpuriousFilterCheckBox->setChecked(m_settings.high_res_gap_Q_recipA.has_value()); - generalLayout->addRow("", m_highResSpuriousFilterCheckBox); - - m_highResSpuriousGapOneOverD = new SliderPlusBox(0.1, 5.0, 0.01, 2, this); - // If disabled by setting = 0, show a reasonable default on the slider but keep disabled - const double initialGap = m_settings.high_res_gap_Q_recipA.value_or(0.25); - m_highResSpuriousGapOneOverD->setValue(initialGap); - m_highResSpuriousGapOneOverD->setEnabled(gapFilterEnabled); - generalLayout->addRow("Gap threshold in Q-space [A^-1]:", m_highResSpuriousGapOneOverD); - - m_iceRingWidthQRecipA = new SliderPlusBox(0.0, 0.3, 0.001, 3, this); - m_iceRingWidthQRecipA->setValue(m_settings.ice_ring_width_Q_recipA); - generalLayout->addRow("Ice ring width in Q-space [A^-1]:", m_iceRingWidthQRecipA); - - auto processingGroup = new QGroupBox("Other steps", m_spotFindingPage); - auto processingLayout = new QFormLayout(processingGroup); - - m_quickIntegrationCheckBox = new QCheckBox("Enable Bragg Integration", this); - m_quickIntegrationCheckBox->setChecked(m_settings.quick_integration); - processingLayout->addRow("", m_quickIntegrationCheckBox); - - spotLayout->addWidget(generalGroup); - spotLayout->addWidget(processingGroup); - spotLayout->addStretch(); - - // --- Indexing page --- - m_indexingPage = new QWidget(this); - auto indexLayout = new QVBoxLayout(m_indexingPage); - - m_indexingCheckBox = new QCheckBox("Enable Indexing", this); - m_indexingCheckBox->setChecked(m_settings.indexing); - indexLayout->addWidget(m_indexingCheckBox); - - m_indexAlgGroup = new QButtonGroup(this); - indexLayout->addWidget(MakeRadioGroup( - "Indexing algorithm", - "FFBIDX is fast but needs a known cell; FFT/FFTW index de-novo (FFT on GPU, FFTW on CPU). " - "Auto picks the best available.", - {{"Auto", static_cast(IndexingAlgorithmEnum::Auto)}, - {"FFBIDX — GPU, needs known cell", static_cast(IndexingAlgorithmEnum::FFBIDX)}, - {"FFT — GPU, de-novo", static_cast(IndexingAlgorithmEnum::FFT)}, - {"FFTW — CPU, de-novo", static_cast(IndexingAlgorithmEnum::FFTW)}, - {"None — skip indexing", static_cast(IndexingAlgorithmEnum::None)}}, - static_cast(m_indexing.GetAlgorithm()), m_indexAlgGroup, m_indexingPage)); - -#ifndef JFJOCH_USE_CUDA - // The GPU indexers are not built without CUDA - only FFTW (CPU) is available. - for (int id: {static_cast(IndexingAlgorithmEnum::FFBIDX), static_cast(IndexingAlgorithmEnum::FFT)}) { - if (auto *button = m_indexAlgGroup->button(id)) { - button->setEnabled(false); - button->setToolTip("Requires a CUDA build"); - } - } -#endif - - m_resolvedAlgLabel = new QLabel(m_indexingPage); - indexLayout->addWidget(m_resolvedAlgLabel); - - m_geomRefGroup = new QButtonGroup(this); - indexLayout->addWidget(MakeRadioGroup( - "Geometry refinement", - "How the geometry is refined once a lattice is found.", - {{"None", static_cast(GeomRefinementAlgorithmEnum::None)}, - {"Orientation only", static_cast(GeomRefinementAlgorithmEnum::OrientationOnly)}, - {"Beam center + lattice", static_cast(GeomRefinementAlgorithmEnum::BeamCenter)}, - {"Pixel refinement (experimental)", static_cast(GeomRefinementAlgorithmEnum::PixelRefine)}}, - static_cast(m_indexing.GetGeomRefinementAlgorithm()), m_geomRefGroup, m_indexingPage)); - - auto indexingGroup = new QGroupBox("Indexing parameters", m_indexingPage); - auto indexingLayout = new QFormLayout(indexingGroup); - - m_idxIndexIceRings = new QCheckBox("Index ice rings", this); - m_idxIndexIceRings->setChecked(m_indexing.GetIndexIceRings()); - indexingLayout->addRow("", m_idxIndexIceRings); - - m_idxTolerance = new SliderPlusBox(0.0, 0.5, 0.001, 3, this); - m_idxTolerance->setValue(m_indexing.GetTolerance()); - indexingLayout->addRow("Indexing tolerance", m_idxTolerance); - - m_idxUnitCellDistTolerance = new SliderPlusBox(0.001, 0.200, 0.001, 3, this); - m_idxUnitCellDistTolerance->setValue(m_indexing.GetUnitCellDistTolerance()); - indexingLayout->addRow("Unit cell dist tol vs ref", m_idxUnitCellDistTolerance); - - m_idxViableCellMinSpots = new SliderPlusBox(6.0, 200.0, 1.0, 0, this); - m_idxViableCellMinSpots->setValue(static_cast(m_indexing.GetViableCellMinSpots())); - indexingLayout->addRow("Viable cell min spots", m_idxViableCellMinSpots); - - indexLayout->addWidget(indexingGroup); - indexLayout->addStretch(); - - // --- Connections --- - connect(m_enableCheckBox, &QCheckBox::toggled, [this](bool checked) { - m_settings.enable = checked; - Update(); - }); - - connect(m_signalToNoise, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.signal_to_noise_threshold = val; - Update(); - }); - - connect(m_photonCount, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.photon_count_threshold = val; - Update(); - }); - - connect(m_minPixPerSpot, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.min_pix_per_spot = std::lround(val); - Update(); - }); - - connect(m_maxPixPerSpot, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.max_pix_per_spot = std::lround(val); - Update(); - }); - - // New: update on max spot count change - connect(m_maxSpotCount, &SliderPlusBox::valueChanged, [this](double /*val*/) { - Update(); - }); - - connect(m_highResolution, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.high_resolution_limit = val; - Update(); - }); - - connect(m_lowResolution, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.low_resolution_limit = val; - Update(); - }); - - // Toggle for high-res spurious spot filter - connect(m_highResSpuriousFilterCheckBox, &QCheckBox::toggled, [this](bool checked) { - m_highResSpuriousGapOneOverD->setEnabled(checked); - if (checked) { - // If currently disabled (zero), restore a sensible default - if (m_settings.high_res_gap_Q_recipA) { - m_settings.high_res_gap_Q_recipA = static_cast( - m_highResSpuriousGapOneOverD->value()); - } - } else - m_settings.high_res_gap_Q_recipA = std::nullopt; - - Update(); - }); - - // Gap slider handler - connect(m_highResSpuriousGapOneOverD, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.high_res_gap_Q_recipA = static_cast(val); - if (val > 0.0 && !m_highResSpuriousFilterCheckBox->isChecked()) { - m_highResSpuriousFilterCheckBox->setChecked(true); - } - Update(); - }); - - // Indexing enable in its own group now - connect(m_indexingCheckBox, &QCheckBox::toggled, [this](bool checked) { - m_settings.indexing = checked; - Update(); - }); - - connect(m_quickIntegrationCheckBox, &QCheckBox::toggled, [this](bool checked) { - m_settings.quick_integration = checked; - Update(); - }); - - connect(m_indexAlgGroup, &QButtonGroup::idClicked, [this](int id) { - m_indexing.Algorithm(static_cast(id)); - UpdateResolvedAlgorithmLabel(); - Update(); - }); - - connect(m_geomRefGroup, &QButtonGroup::idClicked, [this](int id) { - m_indexing.GeomRefinementAlgorithm(static_cast(id)); - Update(); - }); - - // Indexing settings signals - connect(m_idxTolerance, &SliderPlusBox::valueChanged, [this](double val) { - m_indexing.Tolerance(static_cast(val)); - Update(); - }); - - connect(m_idxUnitCellDistTolerance, &SliderPlusBox::valueChanged, [this](double val) { - m_indexing.UnitCellDistTolerance(static_cast(val)); - Update(); - }); - - connect(m_idxIndexIceRings, &QCheckBox::toggled, [this](bool checked) { - m_indexing.IndexIceRings(checked); - Update(); - }); - - connect(m_idxViableCellMinSpots, &SliderPlusBox::valueChanged, [this](double val) { - m_indexing.ViableCellMinSpots(static_cast(std::lround(val))); - Update(); - }); - - connect(m_iceRingWidthQRecipA, &SliderPlusBox::valueChanged, [this](double val) { - m_settings.ice_ring_width_Q_recipA = static_cast(val); - Update(); - }); - - UpdateResolvedAlgorithmLabel(); -} - -void JFJochViewerProcessingWindow::Update() { - const auto max_spots = std::lround(m_maxSpotCount->value()); - emit settingsChanged(m_settings, m_indexing, max_spots); -} - -void JFJochViewerProcessingWindow::setUnitCellKnown(bool known) { - m_unitCellKnown = known; - UpdateResolvedAlgorithmLabel(); -} - -void JFJochViewerProcessingWindow::UpdateResolvedAlgorithmLabel() { - // Mirror DiffractionExperiment::GetIndexingAlgorithm() so the user sees what Auto/FFBIDX - // actually resolve to on this machine (GPU present?) and dataset (unit cell known?). - const bool gpu = get_gpu_count() > 0; - IndexingAlgorithmEnum resolved; - switch (m_indexing.GetAlgorithm()) { - case IndexingAlgorithmEnum::FFBIDX: - resolved = m_unitCellKnown ? IndexingAlgorithmEnum::FFBIDX : IndexingAlgorithmEnum::None; - break; - case IndexingAlgorithmEnum::Auto: - resolved = !gpu ? IndexingAlgorithmEnum::FFTW - : (m_unitCellKnown ? IndexingAlgorithmEnum::FFBIDX : IndexingAlgorithmEnum::FFT); - break; - case IndexingAlgorithmEnum::FFT: resolved = IndexingAlgorithmEnum::FFT; break; - case IndexingAlgorithmEnum::FFTW: resolved = IndexingAlgorithmEnum::FFTW; break; - default: resolved = IndexingAlgorithmEnum::None; break; - } - m_resolvedAlgLabel->setText("Effective on this system: " + algorithm_name(resolved) - + (gpu ? "" : " (no GPU detected)")); -} diff --git a/viewer/windows/JFJochViewerProcessingWindow.h b/viewer/windows/JFJochViewerProcessingWindow.h deleted file mode 100644 index ee37c1d7..00000000 --- a/viewer/windows/JFJochViewerProcessingWindow.h +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#pragma once - -#include -#include -#include -#include - -#include "JFJochHelperWindow.h" -#include "../common/IndexingSettings.h" -#include "../widgets/SliderPlusBox.h" -#include "../../image_analysis/spot_finding/SpotFindingSettings.h" - -class QButtonGroup; - -// Holds the spot finding and indexing controls. The two are exposed as separate pages -// (TakeSpotFindingPage / TakeIndexingPage) so the settings window can show them as two tabs. -class JFJochViewerProcessingWindow : public JFJochHelperWindow { - Q_OBJECT - - QCheckBox *m_enableCheckBox; - - SliderPlusBox *m_signalToNoise; - SliderPlusBox *m_photonCount; - SliderPlusBox *m_minPixPerSpot; - SliderPlusBox *m_maxPixPerSpot; - SliderPlusBox *m_highResolution; - SliderPlusBox *m_lowResolution; - SliderPlusBox *m_maxSpotCount; // New: Max spot count slider - QCheckBox *m_highResSpuriousFilterCheckBox; - SliderPlusBox *m_highResSpuriousGapOneOverD; // [0.01 .. 0.5], step 0.001 - - QCheckBox *m_indexingCheckBox; - QCheckBox *m_quickIntegrationCheckBox; - - QButtonGroup *m_indexAlgGroup; // indexing algorithm (FFBIDX/FFT/FFTW/Auto/None) - QButtonGroup *m_geomRefGroup; // geometry refinement (None/Orientation/Beam/Pixel) - QLabel *m_resolvedAlgLabel; // what Auto/FFBIDX resolves to on this system - bool m_unitCellKnown = false; // drives FFBIDX vs FFT resolution of Auto - - SliderPlusBox *m_idxTolerance; // [0.0 .. 0.5], step 0.001 - SliderPlusBox *m_idxUnitCellDistTolerance; // [0.0001 .. 0.2001], step 0.0001 - QCheckBox *m_idxIndexIceRings; - SliderPlusBox *m_idxViableCellMinSpots; // integer >= 6 - - SliderPlusBox *m_iceRingWidthQRecipA; - - QWidget *m_spotFindingPage; - QWidget *m_indexingPage; - - SpotFindingSettings m_settings; - IndexingSettings m_indexing; - - void Update(); - void UpdateResolvedAlgorithmLabel(); // refresh the "effective on this system" hint -public: - explicit JFJochViewerProcessingWindow(const SpotFindingSettings &settings, const IndexingSettings& indexing, - QWidget *parent = nullptr); - - // The two pages, for lifting into tabs. Ownership passes to the caller (e.g. a QTabWidget). - QWidget *TakeSpotFindingPage() { return m_spotFindingPage; } - QWidget *TakeIndexingPage() { return m_indexingPage; } - -public slots: - // Whether the current dataset has a known unit cell (affects how Auto/FFBIDX resolve). - void setUnitCellKnown(bool known); - -signals: - void settingsChanged(const SpotFindingSettings &settings, const IndexingSettings &indexing, int64_t max_spots); -};