Files
Jungfraujoch/viewer/windows/JFJochPixelRefineWindow.cpp
T
leonarski_f efe882f4b6
Build Packages / Unit tests (push) Failing after 1s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 25m52s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 29m5s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 29m54s
Build Packages / build:rpm (rocky8) (push) Successful in 31m55s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 32m12s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 32m48s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 35m27s
Build Packages / Generate python client (push) Successful in 25s
Build Packages / build:rpm (rocky9) (push) Successful in 31m59s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m36s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 24m8s
Build Packages / XDS test (neggia plugin) (push) Successful in 17m46s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m36s
Build Packages / XDS test (durin plugin) (push) Successful in 19m40s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 19m38s
Build Packages / DIALS test (push) Successful in 26m30s
jfjoch_viewer: Better display (to be tested) of pixel refine
2026-06-09 16:28:17 +02:00

228 lines
9.4 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochPixelRefineWindow.h"
#include "../image_viewer/JFJochSimpleImage.h"
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFormLayout>
#include <QGroupBox>
#include <QFileDialog>
#include <cmath>
JFJochPixelRefineWindow::JFJochPixelRefineWindow(QWidget *parent)
: JFJochHelperWindow(parent) {
setWindowTitle("PixelRefine (experimental)");
auto central = new QWidget(this);
setCentralWidget(central);
auto layout = new QHBoxLayout(central);
// --- predicted image (left, expanding) ---------------------------------
m_image = new JFJochSimpleImage(this);
m_image->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(m_image, 1);
// --- control panel (right) ---------------------------------------------
auto controls = new QWidget(this);
controls->setMinimumWidth(320);
auto controlsLayout = new QVBoxLayout(controls);
layout->addWidget(controls, 0);
// --- what the left image shows ------------------------------------------
m_displayMode = new QComboBox(this);
m_displayMode->addItem(tr("Prediction"));
m_displayMode->addItem(tr("Squared difference |pred - image|²"));
m_displayMode->addItem(tr("χ² (weighted residual = LSQ cost)"));
auto displayForm = new QFormLayout();
displayForm->addRow(tr("Display:"), m_displayMode);
controlsLayout->addLayout(displayForm);
auto paramBox = new QGroupBox(tr("Model parameters"), this);
auto form = new QFormLayout(paramBox);
m_R0 = new SliderPlusBox(1e-4, 0.05, 1e-4, 4, this); m_R0->setValue(0.005);
m_R1 = new SliderPlusBox(1e-4, 0.05, 1e-4, 4, this); m_R1->setValue(0.005);
m_bw = new SliderPlusBox(0.0, 0.05, 1e-4, 4, this); m_bw->setValue(0.0);
m_scale = new SliderPlusBox(1e-3, 1e4, 1e-3, 3, this, SliderPlusBox::Logarithmic); m_scale->setValue(1.0);
m_B = new SliderPlusBox(0.0, 200.0, 0.1, 1, this); m_B->setValue(0.0);
m_beamx = new SliderPlusBox(0.0, 5000.0, 0.5, 1, this); m_beamx->setValue(0.0);
m_beamy = new SliderPlusBox(0.0, 5000.0, 0.5, 1, this); m_beamy->setValue(0.0);
form->addRow(tr("R0 radial [Å⁻¹]:"), m_R0);
form->addRow(tr("R1 tangential [Å⁻¹]:"), m_R1);
form->addRow(tr("Bandwidth FWHM (Δλ/λ):"), m_bw);
form->addRow(tr("Scale G:"), m_scale);
form->addRow(tr("B-factor [Ų]:"), m_B);
m_overrideBeam = new QCheckBox(tr("Override beam centre"), this);
form->addRow(QString(), m_overrideBeam);
form->addRow(tr("Beam X [px]:"), m_beamx);
form->addRow(tr("Beam Y [px]:"), m_beamy);
m_beamx->setEnabled(false);
m_beamy->setEnabled(false);
controlsLayout->addWidget(paramBox);
// --- what "Refine" is allowed to move ----------------------------------
auto refBox = new QGroupBox(tr("Refine (Ceres)"), this);
auto refLayout = new QVBoxLayout(refBox);
m_refOrientation = new QCheckBox(tr("Orientation"), this); m_refOrientation->setChecked(true);
m_refCell = new QCheckBox(tr("Unit cell"), this);
m_refBeam = new QCheckBox(tr("Beam centre"), this);
m_refScale = new QCheckBox(tr("Scale G"), this); m_refScale->setChecked(true);
m_refB = new QCheckBox(tr("B-factor"), this);
m_refR = new QCheckBox(tr("Widths R0/R1"), this); m_refR->setChecked(true);
for (auto *cb : {m_refOrientation, m_refCell, m_refBeam, m_refScale, m_refB, m_refR})
refLayout->addWidget(cb);
controlsLayout->addWidget(refBox);
// --- buttons + readouts -------------------------------------------------
m_loadRef = new QPushButton(tr("Load reference MTZ…"), this);
m_refine = new QPushButton(tr("Refine"), this);
controlsLayout->addWidget(m_loadRef);
controlsLayout->addWidget(m_refine);
m_residual = new QLabel(tr("Residual: —"), this);
m_pipelineCC = new QLabel(tr("Pipeline CC (ref): —"), this);
m_status = new QLabel(QString(), this);
m_status->setWordWrap(true);
m_status->setStyleSheet("color: rgb(80, 80, 80);");
controlsLayout->addWidget(m_residual);
controlsLayout->addWidget(m_pipelineCC);
controlsLayout->addWidget(m_status);
controlsLayout->addStretch(1);
// --- debounce timer for live preview -----------------------------------
m_debounce = new QTimer(this);
m_debounce->setSingleShot(true);
m_debounce->setInterval(150);
connect(m_debounce, &QTimer::timeout, this, [this] {
emit paramsChanged(currentParams());
});
for (auto *s : {m_R0, m_R1, m_bw, m_scale, m_B, m_beamx, m_beamy})
connect(s, &SliderPlusBox::valueChanged, this, [this](double) { onControlChanged(); });
connect(m_displayMode, &QComboBox::currentIndexChanged, this, [this](int) { onControlChanged(); });
connect(m_overrideBeam, &QCheckBox::toggled, this, [this](bool on) {
m_beamx->setEnabled(on);
m_beamy->setEnabled(on);
onControlChanged();
});
connect(m_loadRef, &QPushButton::clicked, this, [this] {
const QString path = QFileDialog::getOpenFileName(
this, tr("Load reference MTZ"), QString(), tr("MTZ files (*.mtz);;All files (*)"));
if (!path.isEmpty())
emit loadReferenceRequested(path);
});
connect(m_refine, &QPushButton::clicked, this, [this] {
// Cancel any pending live-preview: otherwise a debounce armed by a slider
// move just before this click fires after the refine and overwrites the
// refined residual/preview with the stale pre-refine slider values.
m_debounce->stop();
PixelRefineParams p = currentParams();
p.max_iterations = 5;
emit refineRequested(p);
});
}
PixelRefineParams JFJochPixelRefineWindow::currentParams() const {
PixelRefineParams p;
p.R0 = m_R0->value();
p.R1 = m_R1->value();
p.bandwidth_fwhm = m_bw->value();
p.scale_factor = m_scale->value();
p.B_factor = m_B->value();
if (m_overrideBeam->isChecked()) {
p.beam_x = m_beamx->value();
p.beam_y = m_beamy->value();
} else {
p.beam_x = NAN;
p.beam_y = NAN;
}
p.refine_orientation = m_refOrientation->isChecked();
p.refine_unit_cell = m_refCell->isChecked();
p.refine_beam_center = m_refBeam->isChecked();
p.refine_scale = m_refScale->isChecked();
p.refine_B = m_refB->isChecked();
p.refine_R = m_refR->isChecked();
p.display_mode = m_displayMode->currentIndex();
return p;
}
void JFJochPixelRefineWindow::onControlChanged() {
if (m_suppress)
return;
m_debounce->start();
}
void JFJochPixelRefineWindow::imageLoaded(std::shared_ptr<const JFJochReaderImage> image) {
if (!image)
return;
// Initialise the beam-centre sliders from the geometry once.
if (!m_beamInit) {
const auto geom = image->Dataset().experiment.GetDiffractionGeometry();
m_beamx->setMax(static_cast<double>(image->Dataset().experiment.GetXPixelsNum()));
m_beamy->setMax(static_cast<double>(image->Dataset().experiment.GetYPixelsNum()));
m_suppress = true;
m_beamx->setValue(geom.GetBeamX_pxl());
m_beamy->setValue(geom.GetBeamY_pxl());
m_suppress = false;
m_beamInit = true;
}
// Show the standard ScaleOnTheFly pipeline's per-image CC vs the reference (set
// on the message during analysis when a reference is loaded), as a baseline to
// compare PixelRefine against.
const auto pipeline_cc = image->ImageData().image_scale_cc;
if (pipeline_cc.has_value() && std::isfinite(pipeline_cc.value()))
m_pipelineCC->setText(tr("Pipeline CC (ref): %1%")
.arg(pipeline_cc.value() * 100.0, 0, 'f', 1));
else
m_pipelineCC->setText(tr("Pipeline CC (ref): —"));
// Request a predicted-image preview for the (re)loaded image. Without this the
// preview only refreshed on a slider change, so opening/reanalyzing an image
// left the predicted view empty. The worker no-ops if there is no reference or
// the image is not integrated yet.
onControlChanged();
}
void JFJochPixelRefineWindow::setPredictedImage(std::shared_ptr<const SimpleImage> image) {
m_image->setImage(std::move(image));
}
void JFJochPixelRefineWindow::setResidual(double cost, double cc, int64_t n_reflections) {
const QString cc_str = std::isfinite(cc) ? QString::number(cc * 100.0, 'f', 1) + "%"
: QStringLiteral("");
m_residual->setText(tr("Residual: %1 CC: %2 (%3 reflections)")
.arg(cost, 0, 'g', 6)
.arg(cc_str)
.arg(n_reflections));
}
void JFJochPixelRefineWindow::setRefinedParams(PixelRefineParams params) {
m_suppress = true;
m_R0->setValue(params.R0);
m_R1->setValue(params.R1);
m_bw->setValue(params.bandwidth_fwhm);
m_scale->setValue(params.scale_factor);
m_B->setValue(params.B_factor);
if (std::isfinite(params.beam_x) && std::isfinite(params.beam_y)) {
m_beamx->setValue(params.beam_x);
m_beamy->setValue(params.beam_y);
}
m_suppress = false;
}
void JFJochPixelRefineWindow::setStatus(QString message) {
m_status->setText(message);
}