// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include "../../common/DiffractionGeometry.h" #include "../../common/CrystalLattice.h" #include "../../common/GoniometerAxis.h" #include "../../common/UnitCell.h" #include "../../common/Logger.h" #include "../IntegrationOutcome.h" #include "gemmi/symmetry.hpp" // Post-integration geometry refinement for rotation data. Unlike the at-indexing XtalOptimizer, this runs // AFTER integration/merge, where each reflection has an OBSERVED rocking centroid phi_obs (the intensity- // weighted mean goniometer angle over the frames it spans) and an observed spot position. It refines one // shared crystal orientation + cell (+ optionally the detector distance) against two residuals: // * an Ewald excitation residual evaluated at phi_obs (distance-independent) -> pins the absolute cell // scale that the positional residual leaves degenerate with the distance. Because phi_obs is the real // rocking angle (not a frame centre) it is unbiased. // * the positional detector<->reciprocal residual at each partial's observed spot -> pins the distance. // Reflections are weighted by their merged I/sigma (strong, well-measured reflections dominate). struct PostRefineResult { bool ok = false; DiffractionGeometry geom; // refined (distance / beam left as configured) UnitCell cell{}; // refined unit cell int events_used = 0; int obs_used = 0; double phi_rms_deg = 0.0; // RMS(phi_obs - phi_pred) at the refined model (fit quality) double distance_before_mm = 0.0, distance_after_mm = 0.0; double beam_x_before_px = 0.0, beam_x_after_px = 0.0; // refined beam centre (GEOM mode) double beam_y_before_px = 0.0, beam_y_after_px = 0.0; bool cell_refined = false; // GEOM step A (cell scale + axis) passed cross-validation bool detector_refined = false; // GEOM step B (distance + beam) passed cross-validation double est_mosaicity_deg = 0.0; // mosaicity estimated from the observed rocking widths (0 = n/a) }; struct PostRefineSettings { gemmi::CrystalSystem crystal_system = gemmi::CrystalSystem::Triclinic; bool refine_geometry = false; // XtalOptimizer-equivalent: cell scale + axis (from phi_obs) and detector // distance + beam centre (from the observed spot positions X,Y), as two // separate cross-validated steps. The only supported refinement mode. double excitation_weight = 1.0; // weight of the phi/excitation residual vs the positional one int min_events = 50; int num_threads = 1; }; // nominal_geom / reference_latt: the current detector geometry and the phi=0 reference lattice (orientation // + cell) from rotation indexing. outcomes: the per-image integrated reflections (observed_x/y, I, sigma, // image_number). axis: the goniometer. Returns ok=false (geometry untouched) on failure. PostRefineResult PostRefineRotationGeometry(const std::vector &outcomes, const GoniometerAxis &axis, const DiffractionGeometry &nominal_geom, const CrystalLattice &reference_latt, const PostRefineSettings &settings, Logger &logger);