Files
Jungfraujoch/viewer/widgets/PowderCalibrationWidget.cpp
T
leonarski_f 9aae0c2ba7
Build Packages / Create release (push) Successful in 21s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m40s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m49s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m40s
Build Packages / build:windows:nocuda (push) Successful in 17m44s
Build Packages / build:windows:cuda (push) Successful in 20m13s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m41s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m5s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m35s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m53s
Build Packages / build:rugnux:windows (push) Successful in 11m29s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m51s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m43s
Build Packages / Generate python client (push) Successful in 51s
Build Packages / build:rpm (rocky8) (push) Successful in 18m51s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 18m38s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m24s
Build Packages / build:rpm (rocky9) (push) Successful in 19m19s
Build Packages / Unit tests (push) Successful in 1h37m15s
v1.0.0-rc.169 (#79)
* Building Jungfraujoch no longer needs zlib or Eigen installed on the machine, and the dependencies the build fetches are pinned and updated to current releases.
* rugnux: improvements in indexing, lattice selection and geometry post-refinement, which index crystals that previously returned no lattice and keep the better of the two geometries a run measures.
* rugnux: improvements in beam-centre measurement, beam-stop detection and space-group determination.
* rugnux: the unit cell reported with a determined space group now obeys that group - a cell whose symmetry was confirmed from the intensities is re-refined under it, and a cell the group cannot describe is reported with a warning rather than as it stands.
* rugnux drops the stretches of a rotation sweep whose removal measurably improves the merged intensities and reports what became of every frame, and decides the resolution cut on the crystal's own diffraction rather than on its ice rings.
* The rugnux results report is machine-readable - every line that is not `KEY= value` data starts with `#` - and states the build it was written by, its authorship and its terms of use (`REPORT_VERSION= 8`).
* `jfjoch_viewer`: improvements in the file manager (CBF frames beside HDF5 datasets, a remembered root), the dataset plots, the inspector and the image statistics, plus a settable font size, a view of the rugnux results report, usable performance over a remote display (`ssh -X`) and a reset of all settings to defaults; the reciprocal-space window is removed.
* Broker fixes around DECTRIS collections and dark-mask calibration: re-initialising after a run that never started no longer freezes the broker, a cancelled calibration is abandoned instead of reported as done, and a collection whose start message never arrives ends by itself.

Reviewed-on: #79
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-15 17:09:31 +02:00

147 lines
7.4 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "PowderCalibrationWidget.h"
#include <QPushButton>
#include <QGridLayout>
#include <QLabel>
#include "../../common/JFJochMath.h"
#include "../image_analysis/geom_refinement/AssignSpotsToRings.h"
#include "../image_analysis/geom_refinement/Calibrants.h"
PowderCalibrationWidget::PowderCalibrationWidget(QWidget *parent) : QWidget(parent) {
auto layout = new QVBoxLayout(this);
calibrantCombo = new QComboBox(this);
// Without this a combo asks to be as wide as its longest entry - and it is never allowed to
// shrink below that, which is what used to pin the whole settings dock wide as soon as the
// powder section was expanded. The current text elides instead; the tooltip carries the rest.
calibrantCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
calibrantCombo->setMinimumContentsLength(12);
updateCalibrantList();
// How a whole-dataset run measures the rings. The two buttons below always work from the spots of
// the image on screen, so this only takes effect for "Analyze dataset".
methodCombo = new QComboBox(this);
methodCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
methodCombo->setMinimumContentsLength(12);
methodCombo->addItem("Rings (summed profile)", static_cast<int>(CalibrationMethod::Rings));
methodCombo->addItem("Spots (pooled spot lists)", static_cast<int>(CalibrationMethod::Spots));
methodCombo->setToolTip("How \"Analyze dataset\" measures the powder rings: rings sums the "
"(q x azimuth) azimuthal profile over every image and fits the ring arcs in it; "
"spots pools the found spots and fits those.");
// The detector tilt is a free parameter of every one of these fits. Unticking it holds rot1/rot2
// where the geometry already has them - what a calibration handed to a program with no way to
// express a tilted detector (XDS) needs. It applies to the two buttons and to "Analyze dataset".
refineTiltCheck = new QCheckBox("Refine detector tilt", this);
refineTiltCheck->setChecked(true);
refineTiltCheck->setToolTip("Fit the detector tilt (PONI rot1/rot2) together with the beam centre "
"and the distance. Untick to hold the tilt where it is and fit only the "
"beam centre and the distance - for a calibration that will be given to "
"a program which cannot express a tilted detector, such as XDS.");
auto findBeamCenterButton = new QPushButton("Guess detector calibration", this);
connect(findBeamCenterButton, &QPushButton::clicked,this, &PowderCalibrationWidget::findBeamCenterClicked);
auto optimizeBeamCenterButton = new QPushButton("Refine detector calibration", this);
connect(optimizeBeamCenterButton, &QPushButton::clicked,this, &PowderCalibrationWidget::optimizeBeamCenterClicked);
auto calibrantRingsButton = new QPushButton("Display calibrant rings", this);
connect(calibrantRingsButton, &QPushButton::clicked, this, [this]() {
QVector<float> d_rings;
for (float q : GetCalibrantRings())
d_rings.append(2 * PI / q);
emit ringsFromCalibration(d_rings);
});
// One button per row rather than two side by side: the panel lives in a narrow left-hand dock,
// and two buttons abreast cannot be narrower than the sum of their two labels.
auto refine_row = new QGridLayout();
refine_row->setSpacing(12);
refine_row->addWidget(new QLabel("Calibrant:"),0,0);
refine_row->addWidget(calibrantCombo,0,1);
refine_row->addWidget(new QLabel("Dataset method:"),1,0);
refine_row->addWidget(methodCombo,1,1);
refine_row->addWidget(findBeamCenterButton,2, 0, 1, 2);
refine_row->addWidget(optimizeBeamCenterButton,3, 0, 1, 2);
refine_row->addWidget(calibrantRingsButton,4, 0, 1, 2);
refine_row->addWidget(refineTiltCheck,5, 0, 1, 2);
auto hint = new QLabel("The two buttons fit the image on screen; \"Analyze dataset\" fits the whole "
"run and writes a .poni.", this);
hint->setWordWrap(true);
hint->setStyleSheet("color: gray;");
layout->addLayout(refine_row);
layout->addWidget(hint);
setLayout(layout);
}
std::vector<float> PowderCalibrationWidget::GetCalibrantRings() const {
// The combo lists the shared calibrant table (in its order), then the current sample's own cell,
// which is not a powder standard but is what a single-crystal image's spots actually sit on.
const int idx = calibrantCombo->currentIndex();
const auto &table = Calibrants();
if (idx >= 0 && idx < static_cast<int>(table.size()))
return CalibrantRings(table[idx].name);
if (sample_cell) {
// With the group where the dataset has one: the absences then come from the symmetry rather
// than from a bare P-lattice assumption, and a centred cell's list no longer opens with a
// reflection that is not there - which would scale the whole calibration by the ratio between
// that ring and the first real one. Same call the CLI makes.
if (sample_sg)
return CalculateXtalRings(sample_cell.value(), *sample_sg);
return CalculateXtalRings(sample_cell.value());
}
return CalibrantRings(table.front().name);
}
CalibrationSelection PowderCalibrationWidget::Selection() const {
return {calibrantCombo->currentText(), GetCalibrantRings(),
static_cast<CalibrationMethod>(methodCombo->currentData().toInt()),
refineTiltCheck->isChecked()};
}
void PowderCalibrationWidget::updateCalibrantList() {
calibrantCombo->clear();
for (const auto &c : Calibrants())
calibrantCombo->addItem(QString::fromStdString(c.name));
if (sample_cell) {
// The cell goes in the tooltip, not in the entry: a 40-character item would set the combo's
// minimum width and, through it, the minimum width of the dock this widget sits in.
calibrantCombo->addItem("Current sample cell");
calibrantCombo->setItemData(calibrantCombo->count() - 1,
QString("%1 %2 %3 %4 %5 %6")
.arg(QString::number(sample_cell->a, 'f', 1))
.arg(QString::number(sample_cell->b, 'f', 1))
.arg(QString::number(sample_cell->c, 'f', 1))
.arg(QString::number(sample_cell->alpha, 'f', 1))
.arg(QString::number(sample_cell->beta, 'f', 1))
.arg(QString::number(sample_cell->gamma, 'f', 1)), Qt::ToolTipRole);
}
calibrantCombo->setCurrentIndex(0);
}
void PowderCalibrationWidget::loadImage(std::shared_ptr<const JFJochReaderImage> image) {
if (image) {
sample_cell = image->Dataset().experiment.GetUnitCell();
sample_sg = image->Dataset().experiment.GetGemmiSpaceGroup();
}
updateCalibrantList();
}
void PowderCalibrationWidget::findBeamCenterClicked() {
const std::vector<float> rings = GetCalibrantRings();
emit findBeamCenter(QVector<float>(rings.begin(), rings.end()), true, refineTiltCheck->isChecked());
}
void PowderCalibrationWidget::optimizeBeamCenterClicked() {
const std::vector<float> rings = GetCalibrantRings();
emit findBeamCenter(QVector<float>(rings.begin(), rings.end()), false, refineTiltCheck->isChecked());
}