diff --git a/image_analysis/IndexAndRefine.cpp b/image_analysis/IndexAndRefine.cpp index 7b9563f8..61b30861 100644 --- a/image_analysis/IndexAndRefine.cpp +++ b/image_analysis/IndexAndRefine.cpp @@ -108,20 +108,31 @@ IndexAndRefine::IndexingOutcome IndexAndRefine::DetermineLatticeAndSymmetry(Data // If space group and cell provided => enforce that symmetry in refinement. // If not => detect the symmetry from the lattice. if (sg && experiment.GetUnitCell()) { - // FFBIDX returns the lattice already in the reference setting, so use it - // as-is. De-novo indexers (FFT/FFTW) return an arbitrarily-oriented primitive - // cell that must first be reduced to the conventional setting, else enforcing - // the crystal system on mis-assigned axes rejects every frame. Centering is - // taken from the user's space group; niggli_class is left unassigned (0) - it - // is a property of the primitive cell incl. centering, which LatticeSearch - // cannot recover from a cell the user may have given centered (e.g. C2). A - // proper primitive-cell indexing path (CrystFEL-style) is deferred. outcome.symmetry = LatticeMessage{ .centering = sg->centring_type(), .niggli_class = 0, .crystal_system = sg->crystal_system() }; - outcome.lattice_candidate = de_novo ? LatticeSearch(latt).conventional : latt; + // Place every frame's cell in ONE consistent setting for the whole dataset: + // mixed axis orders (e.g. [78,78,38] vs [38,78,78]) index the same reflection + // as different HKLs and cannot be merged. LatticeSearch gives the conventional + // setting when its detected symmetry agrees with the user's space group. On + // noisy frames it can instead pick an alternative Bravais setting (e.g. the + // sqrt2 C-centred description of a primitive tetragonal cell, + // [78,78,38]->[110,111,38]); there: + // - FFBIDX already returns the reference setting (c-last), consistent with the + // conventional frames, so its raw lattice is safe -> use it (FFBIDX neutral); + // - de-novo indexers (FFT/FFTW) return a Niggli-primitive cell with a DIFFERENT + // axis order (c-first) that would corrupt the merge -> reject the frame. + // niggli_class is left unassigned (0): it needs the primitive cell incl. + // centering, which LatticeSearch cannot recover from a (possibly centred, e.g. + // C2) user cell. A proper primitive-cell indexing path (CrystFEL-style) is deferred. + auto sym_result = LatticeSearch(latt); + if (sym_result.system == sg->crystal_system()) + outcome.lattice_candidate = sym_result.conventional; + else if (!de_novo) + outcome.lattice_candidate = latt; + // else: de-novo + symmetry mismatch -> leave unset, frame is not indexed } else { auto sym_result = LatticeSearch(latt); outcome.symmetry = LatticeMessage{ @@ -136,7 +147,7 @@ IndexAndRefine::IndexingOutcome IndexAndRefine::DetermineLatticeAndSymmetry(Data // lattice to each accepted extra lattice. Candidates are materialized later // in RefineGeometryIfNeeded so they're rooted in the refined (and, for // monoclinic, reordered) main lattice. - if (indexer_result.lattice.size() > 1) { + if (outcome.lattice_candidate && indexer_result.lattice.size() > 1) { auto ml_latt = MultiLatticeSearch(indexer_result.lattice); for (auto &ml : ml_latt) { if (outcome.extra_lattice_rotations.size() >= experiment.GetIndexingSettings().GetMaxExtraLattices())