rugnux: two-pass writes both passes (_01 header geometry, _02 refined)
The two-pass geometry post-refinement previously wrote only the second
(refined-geometry) pass. Write both instead, so the header-geometry and
post-refined results can be compared directly and the better one kept:
<prefix>_01_* first pass, at the header geometry (the plain single-pass
baseline)
<prefix>_02_* second pass, at the post-refined geometry
Each is a complete standalone result - its own merged reflections
(.mtz/.cif), per-image _process.h5 (with --write-process-h5), ice-ring
flags and mmCIF detector geometry, all consistent with the geometry that
pass actually used.
Mechanics: the pre-pass now stores the post-refined detector geometry
instead of applying it mid-pass (so the first pass finishes and writes at
the header geometry), and no longer returns early - it continues through the
full write. Run() then applies the stored geometry, lets the second pass
re-search a reindex-derived space group, and routes the two passes to the
_01 / _02 prefixes. The second pass's result is unchanged from before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+50
-25
@@ -217,18 +217,42 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_
|
||||
}
|
||||
|
||||
ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
// Rotation two-pass geometry post-refinement: a first pass integrates and post-refines the detector
|
||||
// geometry (distance + beam from the observed spot positions, cell scale + axis from phi_obs); the second
|
||||
// pass re-indexes and re-integrates with the refined geometry and is the only one written out - so the
|
||||
// _process.h5 / mtz (and the ice-ring flags, which move with the geometry) reflect the refined geometry.
|
||||
// Rotation two-pass geometry post-refinement: the first pass integrates at the header geometry and
|
||||
// post-refines the detector geometry (distance + beam from the observed spot positions, cell scale + axis
|
||||
// from phi_obs); the second pass re-indexes and re-integrates with the refined geometry. BOTH passes are
|
||||
// written out - "<prefix>_01_*" is the first (header-geometry) pass and "<prefix>_02_*" the second (refined
|
||||
// geometry) - so the two can be compared and the better one kept, and the ice-ring flags / geometry stored
|
||||
// in each master reflect the geometry that pass actually used.
|
||||
if (config_.mode == ProcessMode::FullAnalysis && config_.rotation_postrefine_geometry
|
||||
&& experiment_.IsRotationIndexing()) {
|
||||
Logger logger("Rugnux");
|
||||
const std::string base_prefix = config_.output_prefix;
|
||||
const auto gonio_snapshot = experiment_.GetGoniometer();
|
||||
Logger("Rugnux").Info("Rotation two-pass geometry post-refinement: running the geometry pre-pass");
|
||||
RunPipeline(observer, /*write_output=*/false, /*geometry_prepass=*/true);
|
||||
if (cancelled_) return ProcessResult{};
|
||||
prepass_detector_geometry_.reset();
|
||||
prepass_sg_reindexed_ = false;
|
||||
|
||||
logger.Info("Rotation two-pass geometry post-refinement: first pass (header geometry) -> {}_01_*",
|
||||
base_prefix);
|
||||
config_.output_prefix = base_prefix + "_01";
|
||||
auto pass1 = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/true);
|
||||
if (cancelled_) { config_.output_prefix = base_prefix; return pass1; }
|
||||
if (gonio_snapshot) experiment_.Goniometer(*gonio_snapshot); // undo the pre-pass goniometer shift
|
||||
return RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false);
|
||||
|
||||
// Apply the post-refined detector geometry for the second pass; let the second pass re-search a
|
||||
// reindex-derived space group (see the pre-pass note above).
|
||||
if (prepass_detector_geometry_) {
|
||||
const auto &g = *prepass_detector_geometry_;
|
||||
experiment_.BeamX_pxl(g[0]).BeamY_pxl(g[1]).DetectorDistance_mm(g[2]);
|
||||
}
|
||||
if (prepass_sg_reindexed_)
|
||||
experiment_.SpaceGroupNumber(std::nullopt);
|
||||
|
||||
logger.Info("Rotation two-pass geometry post-refinement: second pass (refined geometry) -> {}_02_*",
|
||||
base_prefix);
|
||||
config_.output_prefix = base_prefix + "_02";
|
||||
auto pass2 = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false);
|
||||
config_.output_prefix = base_prefix;
|
||||
return pass2;
|
||||
}
|
||||
return RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false);
|
||||
}
|
||||
@@ -929,11 +953,14 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
|
||||
const auto pr = PostRefineRotationGeometry(outcomes, *rot->axis,
|
||||
experiment_.GetDiffractionGeometry(), rot->lattice, prs, logger);
|
||||
if (pr.ok && pr.detector_refined) {
|
||||
experiment_.BeamX_pxl(static_cast<float>(pr.beam_x_after_px))
|
||||
.BeamY_pxl(static_cast<float>(pr.beam_y_after_px))
|
||||
.DetectorDistance_mm(static_cast<float>(pr.distance_after_mm));
|
||||
logger.Info("Two-pass: applied refined detector geometry (distance {:.3f} mm, "
|
||||
"beam {:.2f},{:.2f} px) for the second integration pass",
|
||||
// Store, don't apply: this pass continues to write its own (header-geometry) output;
|
||||
// Run() applies the refined geometry before the second pass.
|
||||
prepass_detector_geometry_ = std::array<float, 3>{
|
||||
static_cast<float>(pr.beam_x_after_px),
|
||||
static_cast<float>(pr.beam_y_after_px),
|
||||
static_cast<float>(pr.distance_after_mm)};
|
||||
logger.Info("Two-pass: post-refined detector geometry (distance {:.3f} mm, "
|
||||
"beam {:.2f},{:.2f} px) will drive the second integration pass",
|
||||
pr.distance_after_mm, pr.beam_x_after_px, pr.beam_y_after_px);
|
||||
} else {
|
||||
logger.Info("Two-pass: geometry post-refine committed no detector change "
|
||||
@@ -1122,19 +1149,17 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
|
||||
result.space_group_number = experiment_.GetSpaceGroupNumber();
|
||||
}
|
||||
|
||||
// Rotation two-pass geometry pre-pass done: the determined space group is now fixed on experiment_, so
|
||||
// the second pass REUSES it instead of re-searching at the slightly changed geometry (which could flip
|
||||
// a borderline determination and mix the two passes). The lattice is deliberately NOT forced - the
|
||||
// second pass re-indexes at the refined geometry so the CELL comes out self-consistent with it (forcing
|
||||
// the pre-pass lattice would pin the nominal cell and undo the geometry refinement). EXCEPTION: a
|
||||
// Rotation two-pass geometry pre-pass: the determined space group is now fixed on experiment_, so the
|
||||
// second pass REUSES it instead of re-searching at the slightly changed geometry (which could flip a
|
||||
// borderline determination and mix the two passes). The lattice is deliberately NOT forced - the second
|
||||
// pass re-indexes at the refined geometry so the CELL comes out self-consistent with it (forcing the
|
||||
// pre-pass lattice would pin the nominal cell and undo the geometry refinement). EXCEPTION: a
|
||||
// reindex-derived space group (the centred-lattice test) is in a conventional setting the indexer's
|
||||
// primitive frame does not share, so reusing it would break the second pass's indexing - clear it and
|
||||
// let the second pass re-search (it re-runs the same test). Stop here; the caller re-runs the pass.
|
||||
if (geometry_prepass) {
|
||||
if (sg_reindexed)
|
||||
experiment_.SpaceGroupNumber(std::nullopt);
|
||||
return result;
|
||||
}
|
||||
// primitive frame does not share, so reusing it would break the second pass's indexing - remember it so
|
||||
// the caller clears it and lets the second pass re-search. Unlike a throwaway pre-pass, this one now
|
||||
// CONTINUES to the write below so its own (header-geometry) result is saved as the first pass.
|
||||
if (geometry_prepass)
|
||||
prepass_sg_reindexed_ = sg_reindexed;
|
||||
|
||||
// Reference-based indexing-ambiguity resolution (rotation). When a reference MTZ is supplied and
|
||||
// the crystal has an indexing ambiguity (merohedral: lattice symmetry higher than the Laue group),
|
||||
|
||||
+14
-2
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -70,8 +71,9 @@ struct ProcessConfig {
|
||||
// Rotation two-pass geometry post-refinement (FullAnalysis, rotation only). When set, a first pass
|
||||
// integrates and post-refines the detector distance + beam (from the observed spot positions) and the
|
||||
// cell scale + rotation axis (from the observed rocking centroids phi_obs), each committed only if it
|
||||
// improves a held-out residual; the second pass re-integrates with the refined geometry and lattice and
|
||||
// is the one written out (--rotation-refine-geometry).
|
||||
// improves a held-out residual; the second pass re-integrates with the refined geometry and lattice.
|
||||
// BOTH passes are written out - "<prefix>_01_*" (first pass, header geometry) and "<prefix>_02_*" (second
|
||||
// pass, refined geometry) - so the two can be compared and the better one kept (--rotation-post-refine).
|
||||
bool rotation_postrefine_geometry = false;
|
||||
|
||||
// Scaling / merging (FullAnalysis). reference_data (from a reference MTZ) also drives the
|
||||
@@ -146,6 +148,16 @@ class Rugnux {
|
||||
// fed to the second pass's Bragg prediction. Empty outside the rotation two-pass.
|
||||
std::vector<float> prepass_mosaicity_;
|
||||
|
||||
// Two-pass geometry pre-pass: the post-refined detector geometry (beam_x, beam_y in px; distance in mm)
|
||||
// fitted by the first pass and applied by Run() before the second pass. Empty when the pre-pass
|
||||
// committed no change (the second pass then reproduces the first).
|
||||
std::optional<std::array<float, 3>> prepass_detector_geometry_;
|
||||
|
||||
// Two-pass geometry pre-pass: whether the first pass's space group came from the centred-lattice reindex
|
||||
// test (a conventional setting the indexer's primitive frame does not share), so Run() clears it and lets
|
||||
// the second pass re-search rather than reuse it.
|
||||
bool prepass_sg_reindexed_ = false;
|
||||
|
||||
// Stills global geometry-refinement first pass (config_.refine_geometry): index a spread sample of
|
||||
// frames, bundle-adjust the shared beam/distance/cell from the strongest ones, and apply the result
|
||||
// to experiment_ so the main pass re-indexes with it. No-op (leaves experiment_ unchanged) if it
|
||||
|
||||
@@ -74,7 +74,7 @@ void print_usage() {
|
||||
std::cout << " --single-pass-rotation[=num] Use online-like single-pass rotation indexing (optional: min angular range deg)" << std::endl;
|
||||
std::cout << " --redo-rotation-spots Redo spot finding for two-pass rotation indexing" << std::endl;
|
||||
std::cout << " --force-rotation-lattice <vec> Force rotation indexer with external lattice (in Angstrom) : \"a0x,a0y,a0z,a1x,a1y,a1z,a2x,a2y,a2z\" (9 floats, skips first pass)" << std::endl;
|
||||
std::cout << " --rotation-post-refine Two-pass rotation: post-refine detector distance/beam + cell/axis, then re-integrate with them (writes the 2nd pass)" << std::endl;
|
||||
std::cout << " --rotation-post-refine Two-pass rotation: post-refine detector distance/beam + cell/axis, then re-integrate with them; writes both passes (_01 header geometry, _02 refined)" << std::endl;
|
||||
std::cout << " -X, --indexing-algorithm <txt> Indexing algorithm (FFBIDX|FFT|FFTW|Auto|None)" << std::endl;
|
||||
std::cout << " -S, --space-group <num|symbol> Space group number (92) or symbol (P43212) - for indexing and scaling" << std::endl;
|
||||
std::cout << " -C, --unit-cell <cell> Fix reference unit cell: \"a,b,c,alpha,beta,gamma\"" << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user