calibration: --no-refine-tilt holds the header tilt, it does not zero it
Build Packages / build:windows:nocuda (push) Successful in 16m28s
Build Packages / build:windows:cuda (push) Successful in 21m49s
Build Packages / build:rugnux:windows (push) Successful in 15m57s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m3s
Build Packages / build:viewer-tgz:cpu (push) Successful in 15m16s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 16m33s
Build Packages / build:viewer-tgz:cuda (push) Successful in 18m23s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 19m46s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 21m21s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m45s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m56s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 31m0s
Build Packages / build:rpm (rocky9) (push) Successful in 22m45s
Build Packages / build:rpm (rocky8) (push) Successful in 24m48s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 28m30s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 22m27s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 16m37s
Build Packages / XDS test (durin plugin) (push) Successful in 9m53s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m58s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m39s
Build Packages / DIALS test (push) Successful in 19m49s
Build Packages / Unit tests (push) Successful in 2h5m32s

GuessInitialGeometry resets rot1/rot2/rot3 to zero before seeding, and the branch
that puts the header's tilt back was guarded by `refine_tilt && !tilt_refined`. With
--no-refine-tilt the tilt is never free, so tilt_refined is false, so the guard is
false, so the restore never ran - and the fit reported a zero tilt while the CLI
printed "held at the header value".

The guard only needs !tilt_refined: a tilt that was declined and a tilt nobody asked
to refine both want the header put back and the beam centre and distance refitted
around it. That is what the branch already does.

Measured on a LaB6 sweep with a tilt imposed on the command line and asked to be
held, --calibration spots:

  before  Rot1= +0.0000 deg  (+0.001400 rad from the header)
  after   Rot1= -0.0802 deg  (+0.000000 rad from the header)

The rings path reaches this only when the spot-derived start wins, which is why it
does not show on a file whose profile start is chosen; the spots path always did.
A calibration whose header tilt is already zero is unaffected - checked, byte-equal
distance either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
2026-08-31 19:47:02 +02:00
co-authored by Claude Opus 5
parent dcfcff5bc4
commit efca844bce
2 changed files with 13 additions and 2 deletions
+1
View File
@@ -17,6 +17,7 @@
* Twinning is no longer reported when the L-test contradicts it.
* The `rugnux` report gives the detector tilt and the direct beam beside the beam centre, and carries the tilt in the `dataset_settings` block.
* The `rugnux` report gives the detector tilt rotation indexing measured, so it can be checked against a powder calibration.
* `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots.
* The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area.
### 1.0.0-rc.165
@@ -295,7 +295,12 @@ CalibrationResult CalibrateFromProfile(const std::vector<float> &profile,
&& (best->uncertainty.sigma_rot1_rad > 0.0 || best->uncertainty.sigma_rot2_rad > 0.0);
const float significance = tilt_was_free ? TiltSignificance(best->geometry, best->uncertainty) : 0.0f;
bool tilt_refined = refine_tilt && tilt_was_free && significance >= TILT_MIN_SIGNIFICANCE;
if (refine_tilt && !tilt_refined) {
// Not `refine_tilt && !tilt_refined`: with --no-refine-tilt the tilt is never free, so
// tilt_refined is false and this branch was skipped entirely - leaving the rotations at the ZERO
// that GuessInitialGeometry sets, while the CLI reported them as held at the header value. A
// declined tilt and a tilt nobody asked to refine both need the header put back and the rest
// refitted around it.
if (!tilt_refined) {
// From the header's tilt, not from the one being declined. fit_from now takes a whole geometry,
// so without this the "pinned" refit would pin rot1/rot2 at exactly the unvalidated values the
// gate just rejected - which is the same fault the gate exists to catch, reintroduced one level
@@ -346,7 +351,12 @@ CalibrationResult CalibrateFromSpots(const std::vector<SpotToSave> &spots,
&& (unc.sigma_rot1_rad > 0.0 || unc.sigma_rot2_rad > 0.0);
const float significance = tilt_was_free ? TiltSignificance(fitted, unc) : 0.0f;
const bool tilt_refined = refine_tilt && tilt_was_free && significance >= TILT_MIN_SIGNIFICANCE;
if (refine_tilt && !tilt_refined) {
// Not `refine_tilt && !tilt_refined`: with --no-refine-tilt the tilt is never free, so
// tilt_refined is false and this branch was skipped entirely - leaving the rotations at the ZERO
// that GuessInitialGeometry sets, while the CLI reported them as held at the header value. A
// declined tilt and a tilt nobody asked to refine both need the header put back and the rest
// refitted around it.
if (!tilt_refined) {
fitted.PoniRot1_rad(geom.GetPoniRot1_rad()).PoniRot2_rad(geom.GetPoniRot2_rad());
OptimizeGeometry(fitted, spots, calibrant_ring_q, false, &unc);
}