diff --git a/image_analysis/geom_refinement/GeometryRefiner.h b/image_analysis/geom_refinement/GeometryRefiner.h index 8bb3b787..5dce6c73 100644 --- a/image_analysis/geom_refinement/GeometryRefiner.h +++ b/image_analysis/geom_refinement/GeometryRefiner.h @@ -27,6 +27,8 @@ struct GeomRefineSpot { struct GeomRefineFrame { CrystalLattice lattice; // per-frame indexed lattice (real space), for the orientation seed std::vector spots; // indexed spots on this frame + int32_t ordinal = 0; // image this came from; ties are broken on it, so the + // selected bundle does not depend on collection order }; struct GeometryRefinerSettings { diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index b00d94e8..1bf6de90 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -126,13 +126,22 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ // draining the whole budget. The budget is sized for a low-hit-rate dataset (refine_frames * 50, at // least 8000), so on data that indexes well it meant re-indexing the ENTIRE run to keep 200 frames - // 99% of this pass was sampling. Four times the bundle leaves the "strongest N" selection a real - // pool to choose from; the frames are equally spaced, so an early stop still spans the run. + // pool to choose from. const int strong_target = refine_frames * 4; + // The sample is cut into a FIXED number of interleaved stripes - stripe s is sample positions + // s, s+STRIPES, s+2*STRIPES, ... - and each stripe stops once it has contributed its share of the + // strong frames. Two things follow, both of which the previous shared-cursor-with-a-shared-stop + // arrangement got wrong. Every stripe is spread over the WHOLE run, so stopping early no longer + // means fitting the geometry to the beginning of it. And which frames get examined depends only + // on the data: it is not a race between the workers, and it does not change with -N, because a + // stripe is processed identically whichever worker happens to claim it. + constexpr int STRIPES = 32; + const int stripe_strong_target = std::max(1, (strong_target + STRIPES - 1) / STRIPES); + std::vector frames; std::mutex frames_mutex; - std::atomic next_i = 0; - std::atomic strong_found = 0; + std::atomic next_stripe = 0; std::atomic examined = 0; auto worker = [&]() { @@ -141,9 +150,11 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ /*enable_fused_adaptive_gpu=*/true); AzimuthalIntegrationProfile profile(mapping); - while (!cancelled_ && strong_found.load(std::memory_order_relaxed) < strong_target) { - const int idx = next_i.fetch_add(1); - if (idx >= static_cast(sample.size())) break; + for (int stripe = next_stripe.fetch_add(1); stripe < STRIPES && !cancelled_; + stripe = next_stripe.fetch_add(1)) { + int stripe_strong = 0; + for (int idx = stripe; idx < static_cast(sample.size()) && !cancelled_ + && stripe_strong < stripe_strong_target; idx += STRIPES) { examined.fetch_add(1, std::memory_order_relaxed); const int ordinal = sample[idx]; const int image_idx = start_image + ordinal * config_.stride; @@ -174,17 +185,19 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ GeomRefineFrame f; f.lattice = *msg.indexing_lattice; + f.ordinal = ordinal; for (const auto &s : msg.spots) if (s.indexed && s.lattice == 0) f.spots.push_back(GeomRefineSpot{s.x, s.y, static_cast(s.h), static_cast(s.k), static_cast(s.l)}); if (f.spots.size() >= 6) { if (static_cast(f.spots.size()) >= MIN_STRONG_SPOTS) - strong_found.fetch_add(1, std::memory_order_relaxed); + stripe_strong++; const std::unique_lock ul(frames_mutex); frames.push_back(std::move(f)); } } + } }; std::vector> futures; @@ -203,8 +216,14 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ for (auto &f : frames) if (static_cast(f.spots.size()) >= MIN_STRONG_SPOTS) strong.push_back(std::move(f)); + // frames is in the order the workers happened to finish, so the spot count alone does not order + // it: without the ordinal tie-break, equally strong frames would swap places between runs and a + // different bundle would be refined. std::sort(strong.begin(), strong.end(), - [](const GeomRefineFrame &a, const GeomRefineFrame &b) { return a.spots.size() > b.spots.size(); }); + [](const GeomRefineFrame &a, const GeomRefineFrame &b) { + if (a.spots.size() != b.spots.size()) return a.spots.size() > b.spots.size(); + return a.ordinal < b.ordinal; + }); if (static_cast(strong.size()) > refine_frames) strong.resize(refine_frames);