diff --git a/image_analysis/WriteReflections.cpp b/image_analysis/WriteReflections.cpp index e1c1bce98..faf3b5075 100644 --- a/image_analysis/WriteReflections.cpp +++ b/image_analysis/WriteReflections.cpp @@ -1033,6 +1033,15 @@ gemmi::Mtz UnmergedMtz(const std::vector &outcomes, // removing them turns its test into an assumption. XDS and DIALS draw the line in the same place. const char centering = mtz.spacegroup ? mtz.spacegroup->hm[0] : 'P'; + // On stills only the crystals the merge takes are written: one whose cell is not the run's was indexed + // in another lattice (on few spots, typically an axis multiple), so its indices do not name the + // reflections they would be read as. A rotation run has the one lattice. + const auto &indexing = experiment.GetIndexingSettings(); + const auto merged_crystal = [&](const IntegrationOutcome &outcome) { + return scanning || outcome.latt.GetUnitCell().is_close(unitCell, indexing.GetUnitCellDistTolerance(), + indexing.GetUnitCellAngleTolerance_deg()); + }; + std::vector> fulls; if (scanning && sum_partials) fulls = SumRockingEvents(outcomes, @@ -1057,8 +1066,9 @@ gemmi::Mtz UnmergedMtz(const std::vector &outcomes, add_row(r); } else { for (const auto &outcome : outcomes) - for (const auto &r : outcome.reflections) - add_row(r); + if (merged_crystal(outcome)) + for (const auto &r : outcome.reflections) + add_row(r); } // Every row has its own slot, so they are filled on several workers. @@ -1104,7 +1114,7 @@ gemmi::Mtz UnmergedMtz(const std::vector &outcomes, // the sweep, which is the one matrix POINTLESS also writes into every batch. std::optional lattice_at_zero; for (const auto &outcome : outcomes) { - if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f) + if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f || !merged_crystal(outcome)) continue; const float mid_deg = phi_start_deg(outcome.reflections.front().image_number) + wedge_deg / 2.0f; lattice_at_zero = gon ? outcome.latt.Multiply(gon->GetTransformationAngle(mid_deg)) : outcome.latt; diff --git a/tests/WriteReflectionsTest.cpp b/tests/WriteReflectionsTest.cpp index 848a3d02c..51512f4ec 100644 --- a/tests/WriteReflectionsTest.cpp +++ b/tests/WriteReflectionsTest.cpp @@ -335,3 +335,29 @@ TEST_CASE("MTZ history: command line and input, in records of at most 80 charact CHECK(joined.find("Input file: " + provenance.input_identity) != std::string::npos); CHECK(mtz.history.size() <= 30); } + +TEST_CASE("Unmerged MTZ: on stills, only the crystals on the run's cell", "[write_reflections][portable]") { + // A still indexed with its c axis doubled names other reflections by the same indices, so its rows + // would be read as the wrong reflections; it is left out, the crystal on the run's cell is written. + const auto experiment = TestExperiment(); // no goniometer: stills + std::vector outcomes(2); + outcomes[0].latt = CrystalLattice(TETRAGONAL_CELL); + outcomes[1].latt = CrystalLattice(UnitCell{47.0f, 47.0f, 126.0f, 90.0f, 90.0f, 90.0f}); + for (int f = 0; f < 2; ++f) + for (int l = 1; l <= 5; ++l) { + Reflection r{}; + r.h = 1; r.k = 2; r.l = l; + r.image_number = static_cast(f); + r.I = 100.0f; + r.sigma = 10.0f; + r.prescaling_corr = 1.0f; + r.partiality = 1.0f; + outcomes[f].reflections.push_back(r); + } + const gemmi::Mtz mtz = UnmergedMtz(outcomes, TETRAGONAL_CELL, experiment, false, OutputProvenance{}, 1); + CHECK(mtz.nreflections == 5); + const auto batch = mtz.column_with_label("BATCH"); + REQUIRE(batch != nullptr); + for (int i = 0; i < mtz.nreflections; ++i) + CHECK((*batch)[i] == 1.0f); +}