From 2456bbe7f6fd8e167943dc26d46cb8462fdf0f95 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 2 Sep 2026 17:38:40 +0200 Subject: [PATCH] viewer: calibrate a sample cell against its own space group, as the CLI does The powder-calibration widget built its ring list with CalculateXtalRings(cell), which assumes a primitive lattice. The overload taking a space group exists precisely because the fit pairs the innermost OBSERVED ring with the innermost LISTED one, so a list opening with a reflection the symmetry forbids scales the whole calibration by the ratio between that ring and the first real one. rugnux_cli was moved to the new overload when it landed; the viewer was not. So on any centred sample cell the viewer's calibration was scaled wrong - the exact failure the overload was added to prevent. The widget already takes the cell from the loaded dataset; it now takes the group with it and uses both. Where the dataset has no group the old call stands, which is the same P-lattice assumption as before and no worse. Found by a whole-branch review, which saw the two call sites side by side where a per-commit read of either could not. Co-Authored-By: Claude Opus 5 (1M context) --- viewer/widgets/PowderCalibrationWidget.cpp | 13 +++++++++++-- viewer/widgets/PowderCalibrationWidget.h | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/viewer/widgets/PowderCalibrationWidget.cpp b/viewer/widgets/PowderCalibrationWidget.cpp index 14dd62208..e1d11a4d2 100644 --- a/viewer/widgets/PowderCalibrationWidget.cpp +++ b/viewer/widgets/PowderCalibrationWidget.cpp @@ -80,8 +80,15 @@ std::vector PowderCalibrationWidget::GetCalibrantRings() const { const auto &table = Calibrants(); if (idx >= 0 && idx < static_cast(table.size())) return CalibrantRings(table[idx].name); - if (sample_cell) + 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); } @@ -108,8 +115,10 @@ void PowderCalibrationWidget::updateCalibrantList() { } void PowderCalibrationWidget::loadImage(std::shared_ptr image) { - if (image) + if (image) { sample_cell = image->Dataset().experiment.GetUnitCell(); + sample_sg = image->Dataset().experiment.GetGemmiSpaceGroup(); + } updateCalibrantList(); } diff --git a/viewer/widgets/PowderCalibrationWidget.h b/viewer/widgets/PowderCalibrationWidget.h index 699f7a54f..a4be9cc23 100644 --- a/viewer/widgets/PowderCalibrationWidget.h +++ b/viewer/widgets/PowderCalibrationWidget.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -26,6 +27,9 @@ struct CalibrationSelection { class PowderCalibrationWidget : public QWidget { Q_OBJECT std::optional sample_cell; + // Taken with the cell, and used with it: the ring list a user-supplied cell produces has to carry + // the group's absences, or the fit pairs the innermost OBSERVED ring with a forbidden one. + std::optional sample_sg; QComboBox* calibrantCombo{nullptr}; // stores current calibrant selection QComboBox* methodCombo{nullptr}; // rings / spots, for the whole-dataset run