// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "RotationIndexer.h" #include "geom_refinement/XtalOptimizer.h" #include "indexing/FFTIndexer.h" #include "lattice_search/LatticeSearch.h" #include RotationIndexer::RotationIndexer(const DiffractionExperiment &x, IndexerThreadPool &indexer) : experiment(x), index_ice_rings(x.GetIndexingSettings().GetIndexIceRings()), v_(experiment.GetImageNum()), axis_(x.GetGoniometer()), geom_(x.GetDiffractionGeometry()), updated_geom_(geom_), indexer_(indexer) { } void RotationIndexer::RunIndexing() { std::unique_lock ul(m); std::vector coords; coords.reserve(max_spots_per_image * v_.size()); for (int i = 0; i < v_.size(); i++) { const float angle_deg = axis_->GetAngle_deg(i) + axis_->GetWedge_deg() / 2.0f; const auto rot = axis_->GetTransformationAngle(angle_deg); for (const auto &s: v_[i]) coords.emplace_back(rot * s.ReciprocalCoord(geom_)); } const auto indexer_result = indexer_.Run(experiment, coords); if (!indexer_result.lattice.empty() && indexer_result.lattice[0].CalcVolume() > 1.0) { auto sg = experiment.GetGemmiSpaceGroup(); if (sg) { search_result_ = LatticeSearchResult{ .niggli_class = 0, // Since Niggli class was not searched for, we don't know which one .conventional = indexer_result.lattice[0], // If lattice provided, it is for now primitive == conventional .system = sg->crystal_system(), .centering = sg->centring_type(), }; } else { // Find lattice type based on cell search_result_ = LatticeSearch(indexer_result.lattice[0]); } // Run refinement DiffractionExperiment experiment_copy(experiment); XtalOptimizerData data{ .geom = experiment_copy.GetDiffractionGeometry(), .latt = search_result_.conventional, .crystal_system = search_result_.system, .min_spots = experiment.GetIndexingSettings().GetViableCellMinSpots(), .refine_beam_center = true, .refine_distance_mm = false, .refine_detector_angles = true, .refine_rotation_axis = true, .index_ice_rings = experiment.GetIndexingSettings().GetIndexIceRings(), .axis = axis_ }; if (data.crystal_system == gemmi::CrystalSystem::Trigonal) data.crystal_system = gemmi::CrystalSystem::Hexagonal; if (data.crystal_system == gemmi::CrystalSystem::Monoclinic) data.latt.ReorderMonoclinic(); if (XtalOptimizer(data, v_)) { indexed_lattice = data.latt; updated_geom_ = data.geom; axis_ = data.axis; } } } void RotationIndexer::ProcessImage(int64_t image, const std::vector &spots) { std::unique_lock ul(m); // For non-rotation just ignore the whole procedure if (!axis_) return; if (accumulated_spots >= max_spots) return; if (indexed_lattice) return; v_[image].reserve(spots.size()); for (const auto &s: spots) { if (index_ice_rings || !s.ice_ring) v_[image].emplace_back(s); } // truncate spots, so we don't get above max_spots (total) and max_spots_per_image (for this image) size_t max_spots_limit = std::min(max_spots_per_image, max_spots - accumulated_spots); if (v_[image].size() > max_spots_limit) { std::ranges::nth_element(v_[image], v_[image].begin() + max_spots_limit, [](const SpotToSave &a, const SpotToSave &b) { return a.intensity > b.intensity; } ); v_[image].resize(max_spots_limit); } accumulated_spots += v_[image].size(); } std::optional RotationIndexer::GetLattice() const { std::unique_lock ul(m); if (!indexed_lattice) return {}; return RotationIndexerResult{ .lattice = indexed_lattice.value(), .search_result = search_result_, .geom = updated_geom_, .axis = axis_ }; }