diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 81820dddd..bf6c3483f 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -3366,6 +3366,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // Full-analysis shared engines. std::unique_ptr indexer_pool; + // The leftover-lattice census, run in the background (see there); after indexer_pool, which it + // uses, so that it is waited for before the pool goes. + std::future census_future; std::unique_ptr indexer; // The FFT search bound indexer_pool was built with. The long-axis rescue needs it to tell whether a // cell it recovered still fits, since a pool's transform is sized once, at construction. @@ -5350,14 +5353,31 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // displaced-spindle-angle null as the first pass, its spots are marked in turn, and the search // goes on for up to three lattices. Its own IndexAndRefine instances and indexer, so nothing // the run goes on with is touched. Run on the pass that writes the result only. - if (!geometry_prepass) { - const auto census_start = std::chrono::steady_clock::now(); - const bool ice_ok = experiment_.GetIndexingSettings().GetIndexIceRings(); + // + // Its spots are found here; the rest runs in the background, beside the image loop, on + // copies of everything it reads, and its answer is collected before the pass returns. Not on + // a pass that only post-refines: nothing reads its result there. + if (!geometry_prepass && !postrefine_probe_only_) { std::vector census_frames = spread_ordinals; census_frames.insert(census_frames.end(), validation.begin(), validation.end()); std::ranges::sort(census_frames); census_frames.erase(std::unique(census_frames.begin(), census_frames.end()), census_frames.end()); prefetch_spots(census_frames); + std::map> census_spots; + for (const int f : census_frames) + census_spots.emplace(f, spot_cache.at(f)); + census_future = std::async(std::launch::async, + [&, x = experiment_, census_frames, census_spots, + lattice = *best.result, validation, spread_ordinals, + validation_settings]() -> LeftoverLatticeCensus { + const auto census_start = std::chrono::steady_clock::now(); + const bool ice_ok = x.GetIndexingSettings().GetIndexIceRings(); + const auto &spot_cache = census_spots; + const auto rot_angle = [&](int ordinal) -> std::optional { + if (const auto g = x.GetGoniometer()) + return g->GetAngle_deg(static_cast(ordinal)) + g->GetWedge_deg() / 2.0f; + return std::nullopt; + }; // assign[frame][spot]: -2 ice, -1 no lattice, 0 the crystal's lattice, 1.. a leftover lattice std::map> assign; for (const int f : census_frames) { @@ -5369,7 +5389,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b } // Mark the still unassigned spots lattice `r` takes as `id`. auto mark = [&](const RotationIndexerResult &r, int id) { - IndexAndRefine idx(experiment_, indexer_pool.get(), /*retain_outcomes=*/false); + IndexAndRefine idx(x, indexer_pool.get(), /*retain_outcomes=*/false); idx.ForceRotationIndexerResult(r); idx.IntegrateEveryFrame(true); std::mutex m_assign; @@ -5392,7 +5412,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // Leftover validation spots on `r` at the true spindle angles, and the median of five // displaced ones - the first pass's null (pooled_evidence) on the leftovers. auto leftover_evidence = [&](const RotationIndexerResult &r) -> std::pair { - IndexAndRefine idx(experiment_, indexer_pool.get(), /*retain_outcomes=*/false); + IndexAndRefine idx(x, indexer_pool.get(), /*retain_outcomes=*/false); idx.ForceRotationIndexerResult(r); const int nv = static_cast(validation.size()); auto at_shift = [&](int shift) { @@ -5442,7 +5462,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b return LatticeSearch(r.lattice.ToPrimitive(r.search_result.centering)).primitive_reduced; }; - mark(*best.result, 0); + mark(lattice, 0); int64_t n_all = 0; double i_all = 0.0; for (const auto &[f, a] : assign) @@ -5456,9 +5476,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b census.frames = static_cast(census_frames.size()); census.primary_spots_pct = 100.0 * t0.n / std::max(1, n_all); census.primary_intensity_pct = 100.0 * t0.intensity / std::max(1.0, i_all); - const CrystalLattice primary_reduced = reduced(*best.result); + const CrystalLattice primary_reduced = reduced(lattice); for (int id = 1; id <= 3; id++) { - RotationIndexer ri(experiment_, *indexer_pool); + RotationIndexer ri(x, *indexer_pool); for (const int f : spread_ordinals) { std::vector left; const auto &sp = spot_cache.at(f); @@ -5504,7 +5524,8 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b census.primary_spots_pct, n_all, census.primary_intensity_pct, census.frames, census.lattices.size(), std::chrono::duration(std::chrono::steady_clock::now() - census_start).count()); - result.leftover_lattices = census; + return census; + }); } if (best.result->tilt_walk) { // What the walked tilt does to the prediction: the keystone at the far corner of the @@ -8930,5 +8951,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b result.used_direct_beam_x_pxl = direct.first; result.used_direct_beam_y_pxl = direct.second; } + if (census_future.valid()) + result.leftover_lattices = census_future.get(); return result; }