// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include "../../common/CrystalLattice.h" #include "../../common/DiffractionGeometry.h" #include "../../common/UnitCell.h" #include "gemmi/symmetry.hpp" // Offline global (multi-frame) geometry refinement for serial stills. Pass 1 indexes many frames // independently; this joint bundle adjustment then determines the ONE shared detector geometry (beam // centre, distance, cell scale) that per-image refinement cannot reach from a single sparse still, so // re-indexing with it converts more frames. Mirrors the validated Python prototype: one Ceres problem // with shared beam/distance/cell blocks and a per-frame orientation block, robust loss, and a cell // regularizer that anchors the (known) cell to break the distance<->cell-scale degeneracy at low // resolution. Detector tilt (rot1/rot2) is NOT refined (gauge-coupled with the beam; zero indexing gain). struct GeomRefineSpot { float x = 0, y = 0; // observed (raw) spot position, pixels int32_t h = 0, k = 0, l = 0; // integer Miller index assigned in pass 1 }; struct GeomRefineFrame { CrystalLattice lattice; // per-frame indexed lattice (real space), for the orientation seed std::vector spots; // indexed spots on this frame }; struct GeometryRefinerSettings { gemmi::CrystalSystem crystal_system = gemmi::CrystalSystem::Triclinic; // Cell-scale regularizer strength (anchors the cell lengths to the input cell so the otherwise // degenerate distance is determined). Large => cell effectively fixed (the validated safe regime). double cell_reg_coeff = 30.0; int rounds = 3; // hkl-reassignment / tolerance-tightening rounds double beam_range_px = 8.0; // beam bound around the nominal value double distance_range_mm = 8.0; // distance bound around the nominal value int min_spots_total = 100; // refuse to refine below this many indexed spots int num_threads = 1; }; struct GeometryRefinerResult { bool ok = false; double beam_x_px = 0, beam_y_px = 0; // refined beam centre (== direct beam, tilt not refined) double distance_mm = 0; UnitCell cell{}; // refined cell int frames_used = 0; int spots_used = 0; double median_residual_px = 0; // final per-spot detector residual (fit quality) }; // nominal_geom supplies the starting beam/distance/tilt/wavelength/pixel size; input_cell is anchored by // the regularizer. Returns ok=false (and leaves the caller's geometry untouched) on any failure. GeometryRefinerResult RefineGlobalGeometry(const DiffractionGeometry &nominal_geom, const UnitCell &input_cell, const std::vector &frames, const GeometryRefinerSettings &settings);