Update viewer icons from the new jfjoch.svg logo
Regenerate jfjoch.png (256-wide, transparent), jfjoch.ico (multi-resolution 16/32/48/64/128/256, square) and jfjoch.icns (16-512) from the new transparent-background jfjoch.svg, and add the SVG as the source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 176 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 69 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 313 KiB |
@@ -1,173 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochAzIntWindow.h"
|
||||
#include <QGroupBox>
|
||||
#include <QButtonGroup>
|
||||
#include <QFormLayout>
|
||||
|
||||
#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<int> 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<QAbstractButton *>::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<float>(m_lowQ->value());
|
||||
const float highQ = static_cast<float>(m_highQ->value());
|
||||
const float qSpacing = static_cast<float>(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);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JFJochHelperWindow.h"
|
||||
#include "../widgets/SliderPlusBox.h"
|
||||
#include <QCheckBox>
|
||||
#include <QRadioButton>
|
||||
#include <QLabel>
|
||||
|
||||
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<QRadioButton*> 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);
|
||||
};
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// 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);
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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);
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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->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<const JFJochReaderDataset> in_dataset) {
|
||||
m_processing->setUnitCellKnown(in_dataset && in_dataset->experiment.GetUnitCell().has_value());
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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);
|
||||
|
||||
// Forwards the dataset's unit-cell state to the indexing tab (to resolve the Auto algorithm).
|
||||
void datasetLoaded(std::shared_ptr<const JFJochReaderDataset> 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);
|
||||
};
|
||||
@@ -1,333 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochViewerProcessingWindow.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
#include <QButtonGroup>
|
||||
#include <QRadioButton>
|
||||
#include <vector>
|
||||
|
||||
#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<RadioChoice> &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<int>(IndexingAlgorithmEnum::Auto)},
|
||||
{"FFBIDX — GPU, needs known cell", static_cast<int>(IndexingAlgorithmEnum::FFBIDX)},
|
||||
{"FFT — GPU, de-novo", static_cast<int>(IndexingAlgorithmEnum::FFT)},
|
||||
{"FFTW — CPU, de-novo", static_cast<int>(IndexingAlgorithmEnum::FFTW)},
|
||||
{"None — skip indexing", static_cast<int>(IndexingAlgorithmEnum::None)}},
|
||||
static_cast<int>(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<int>(IndexingAlgorithmEnum::FFBIDX), static_cast<int>(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<int>(GeomRefinementAlgorithmEnum::None)},
|
||||
{"Orientation only", static_cast<int>(GeomRefinementAlgorithmEnum::OrientationOnly)},
|
||||
{"Beam center + lattice", static_cast<int>(GeomRefinementAlgorithmEnum::BeamCenter)},
|
||||
{"Pixel refinement (experimental)", static_cast<int>(GeomRefinementAlgorithmEnum::PixelRefine)}},
|
||||
static_cast<int>(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<double>(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<float>(
|
||||
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<float>(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<IndexingAlgorithmEnum>(id));
|
||||
UpdateResolvedAlgorithmLabel();
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_geomRefGroup, &QButtonGroup::idClicked, [this](int id) {
|
||||
m_indexing.GeomRefinementAlgorithm(static_cast<GeomRefinementAlgorithmEnum>(id));
|
||||
Update();
|
||||
});
|
||||
|
||||
// Indexing settings signals
|
||||
connect(m_idxTolerance, &SliderPlusBox::valueChanged, [this](double val) {
|
||||
m_indexing.Tolerance(static_cast<float>(val));
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_idxUnitCellDistTolerance, &SliderPlusBox::valueChanged, [this](double val) {
|
||||
m_indexing.UnitCellDistTolerance(static_cast<float>(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<int64_t>(std::lround(val)));
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_iceRingWidthQRecipA, &SliderPlusBox::valueChanged, [this](double val) {
|
||||
m_settings.ice_ring_width_Q_recipA = static_cast<float>(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)"));
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
#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);
|
||||
};
|
||||
Reference in New Issue
Block a user