diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 15532abd..1e0d37c3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -18,6 +18,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as * HDF5 and image stream: `mirror_y` records whether the assembled image is mirrored in Y relative to the detector's raw readout. * rugnux: an image integrated in pyFAI through the `.poni` file written by `--mode calibration` now comes out with the correct azimuth. Radial integration is unchanged. * rugnux: the `.poni` file declares pyFAI's `orientation`, which needs pyFAI 2024.01 or newer. +* rugnux: fixing the space group with `-S` no longer prevents the lattice from being found; the group is applied to scaling and merging rather than to the indexing search. * rugnux: the detector geometry is also logged in XDS's convention (`ORGX`/`ORGY`, detector axis vectors, rotation axis), so it can be compared with an XDS refinement. * rugnux: `_process.h5` describes the pixel format of the images it links to. * A DECTRIS detector sending signed images is no longer declared unsigned in the image stream and in HDF5. diff --git a/image_analysis/rotation_indexer/RotationIndexer.cpp b/image_analysis/rotation_indexer/RotationIndexer.cpp index 8b2c62e3..89b8e898 100644 --- a/image_analysis/rotation_indexer/RotationIndexer.cpp +++ b/image_analysis/rotation_indexer/RotationIndexer.cpp @@ -143,33 +143,24 @@ void RotationIndexer::RunIndexing() { const auto indexer_result = indexer_.Run(experiment, coords); if (!indexer_result.lattice.empty() && indexer_result.lattice[0].CalcVolume() > 1.0) { - auto sg = experiment.GetGemmiSpaceGroup(); DiffractionExperiment experiment_copy(experiment); const float index_tol = experiment.GetIndexingSettings().GetTolerance(); const auto orig_axis = axis_; - // Map an FFT candidate cell to a (metric) space-group setting: the user-fixed SG's conventional - // cell, or the de-novo Bravais lattice. Re-express a metrically-hexagonal cell in conventional - // hexagonal axes (LatticeSearch can land on the ortho-hexagonal C setting) so the 3-fold is not - // hidden from scaling. + // Map an FFT candidate cell to its Bravais lattice. Re-express a metrically-hexagonal cell in + // conventional hexagonal axes (LatticeSearch can land on the ortho-hexagonal C setting) so the + // 3-fold is not hidden from scaling. + // + // A user-fixed space group is deliberately NOT stamped on here. The group names the symmetry; + // it does not say which basis the FFT candidate came back in, and the conventional cell above + // was reduced for whatever class the METRIC matched. Relabelling that cell with the group's + // system and centring leaves the constrained refine snapping the wrong angles to the ideal + // ones: measured, a C-centred orthorhombic cell relabelled primitive monoclinic indexed 1 of + // 60 validation frames and an F-cubic one relabelled trigonal indexed 0 of 60, where the same + // frames index at 36/60 and 51/60 without a group. The group is applied where it belongs - to + // the scaling and the merge. auto build_sr = [&](const CrystalLattice &cand) -> LatticeSearchResult { auto ls = LatticeSearch(cand); - if (sg) { - const auto is_hexagonal = [](gemmi::CrystalSystem s) { - return s == gemmi::CrystalSystem::Trigonal || s == gemmi::CrystalSystem::Hexagonal; - }; - CrystalLattice conventional = ls.conventional; - if (is_hexagonal(sg->crystal_system()) && !is_hexagonal(ls.system)) - conventional = HexagonalConventional(ls.primitive_reduced); - return LatticeSearchResult{ - .niggli_class = ls.niggli_class, - .primitive_reduced = ls.primitive_reduced, - .conventional = conventional, - .system = sg->crystal_system(), - .centering = sg->centring_type(), - .reindex = ls.reindex, - }; - } if (!IsHexagonalSystem(ls.system) && IsMetricallyHexagonal(ls.primitive_reduced)) { ls.conventional = HexagonalConventional(ls.primitive_reduced); ls.system = gemmi::CrystalSystem::Hexagonal; @@ -250,9 +241,12 @@ void RotationIndexer::RunIndexing() { const auto conv_uc = w.sr.conventional.GetUnitCell(); const float length_bound_A = 1.2f * static_cast(std::max({conv_uc.a, conv_uc.b, conv_uc.c})); w.constrained = make_data(w.sr.conventional, w.sr.system, length_bound_A); - // Pseudo-symmetry guard (de-novo only - never override a user-fixed space group): also refine - // unconstrained (triclinic) on the primitive cell. - w.has_tri = (!sg && w.sr.system != gemmi::CrystalSystem::Triclinic); + // Pseudo-symmetry guard: also refine unconstrained (triclinic) on the primitive cell. Run it + // with a user-fixed space group too. The metric promotion the constrained refine acts on is + // decided by the geometry, not by the group, so it can be false whether or not a group was + // given - and without this cell there is nothing to catch it with, which is how a fixed + // group turned crystals the de-novo path indexes at 60/60 into runs that index none. + w.has_tri = (w.sr.system != gemmi::CrystalSystem::Triclinic); if (w.has_tri) w.tri = make_data(w.sr.primitive_reduced, gemmi::CrystalSystem::Triclinic, length_bound_A); w.viable = true;