diff --git a/image_analysis/beam_stop/ShadowFinder.cpp b/image_analysis/beam_stop/ShadowFinder.cpp index f755e5309..860d0b6f1 100644 --- a/image_analysis/beam_stop/ShadowFinder.cpp +++ b/image_analysis/beam_stop/ShadowFinder.cpp @@ -834,7 +834,7 @@ std::vector ShadowFinder::GetMask(size_t nthreads) const { for (int i = lo; i < hi; i++) { if (reflection_grown[i]) region[i] = 0; - mask[i] = region[i] ? 1 : 0; + mask[i] = region[i] ? SHADOW : 0; } }); @@ -885,7 +885,7 @@ std::vector ShadowFinder::GetMask(size_t nthreads) const { if (n_dim >= MIN_SHADOW_PIXELS && n_low >= MIN_CORE_PIXELS) for (const int i : component) if (dim[i]) - mask[i] = 1; + mask[i] = TRANSMITTING; } return mask; } diff --git a/image_analysis/beam_stop/ShadowFinder.h b/image_analysis/beam_stop/ShadowFinder.h index 5ca1a44bf..834474e2b 100644 --- a/image_analysis/beam_stop/ShadowFinder.h +++ b/image_analysis/beam_stop/ShadowFinder.h @@ -98,6 +98,9 @@ class ShadowFinder { [[nodiscard]] Projection Reduce() const; public: + static constexpr uint32_t SHADOW = 1; + static constexpr uint32_t TRANSMITTING = 2; + ShadowFinder(const DiffractionExperiment &experiment, const PixelMask &mask); // The centre the rings are drawn about. It starts as the file's, which is the only one there @@ -116,7 +119,9 @@ public: // decompression, reused across the calls of one worker. void AddImage(const DataMessage &data, std::vector &buffer, size_t shard = 0); - // Compute the shadow mask (1 = shadow, 0 = keep), of the converted pixel count. + // Compute the shadow mask (SHADOW, TRANSMITTING or 0 = keep), of the converted pixel count. + // TRANSMITTING marks the pieces of hardware that let part of the beam through, added after the + // shadow proper; both are masked, and a consumer that must not see those pieces can tell them apart. // Recomputed from the accumulators on each call - meant to be called once at the end. // nthreads = 0 asks for all hardware threads. The per-pixel passes over a 16M-pixel detector // dominate this, and they are all exactly parallel. diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 2276ae8e2..f931f8dff 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -1310,6 +1310,7 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru // Where the scattered background puts the beam, when the projection exists to say so. std::optional> ring_centre; + std::vector opaque_shadow; if (want_shadow) { // Every pixel is compared against the ring it sits on, so the rings have to be drawn about // the beam. A centre far enough out draws them across the background's own radial fall-off @@ -1337,8 +1338,12 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru experiment_.GetBeamX_pxl(), experiment_.GetBeamY_pxl()); } const auto shadow = finder.GetMask(config_.nthreads); - const auto shadowed = std::count(shadow.begin(), shadow.end(), 1u); + const auto shadowed = std::count_if(shadow.begin(), shadow.end(), [](uint32_t v) { return v != 0; }); pixel_mask_.LoadBeamStopMask(experiment_, shadow); + opaque_shadow = shadow; + for (auto &v : opaque_shadow) + if (v == ShadowFinder::TRANSMITTING) + v = 0; // Not "this beamline has no beam stop": it is also what a comparison drawn about the wrong // point returns, so an unexpected answer here is worth reading against the centre named // above first. @@ -1383,11 +1388,20 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru if (want_hot) MaskDefectivePixels(start_image, shadow_sample, ring_centre); + // The centre is fitted with the shadow masked but NOT the transmitting pieces the shadow finder + // adds after it. Their dimming is multiplicative in azimuth, which the fit's per-sector amplitude + // absorbs; masked, a thin arm running into the beam takes cells out of the rings it crosses, and + // which rings stay covered then changes as the trial centre moves - a walk seeded beside such an + // arm stalled on that edge 44 px from the centre it reaches with the arm left in. + PixelMask centre_mask = pixel_mask_; + if (!opaque_shadow.empty()) + centre_mask.LoadBeamStopMask(experiment_, opaque_shadow); + // It reads nothing the steps after it change, and nothing reads its answer before the first pass's // beam-centre check (RunPipeline), so it runs beside them - on copies of what it measures - and is // taken there (JoinBeamCenterCapture). It logs from its own thread. if (want_shadow && config_.beam_center_check) - background_center_capture_ = std::async(std::launch::async, [x = experiment_, mask = pixel_mask_, + background_center_capture_ = std::async(std::launch::async, [x = experiment_, mask = centre_mask, projection = finder.GetMeanProjection(), nthreads = config_.nthreads] { Logger logger("Rugnux"); @@ -1593,7 +1607,7 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru // same geometry, same projection - so this asks for it again only where it was not run. estimate = background_center_ ? background_center_ - : FindBeamCenter(experiment_, pixel_mask_, finder.GetMeanProjection(), + : FindBeamCenter(experiment_, centre_mask, finder.GetMeanProjection(), config_.nthreads); } if (!estimate) { diff --git a/tests/ShadowFinderTest.cpp b/tests/ShadowFinderTest.cpp index 0cae4a368..9d8617c5d 100644 --- a/tests/ShadowFinderTest.cpp +++ b/tests/ShadowFinderTest.cpp @@ -144,9 +144,9 @@ TEST_CASE("ShadowFinder_FindsAnArmThatLetsPartOfTheBeamThrough", "[ShadowFinder] const auto mask = finder.GetMask(); CHECK(mask[I(C, C)] == 1); // the stop - CHECK(mask[I(GAP_X0 - 10, C)] == 1); // the arm where it transmits, before the gap - CHECK(mask[I(GAP_X1 + 10, C + ARM_HALF_WIDE - 3)] == 1); // ... and after it - CHECK(mask[I(W - 3, C)] == 1); // where it is opaque + CHECK(mask[I(GAP_X0 - 10, C)] == ShadowFinder::TRANSMITTING); // the arm where it transmits, before the gap + CHECK(mask[I(GAP_X1 + 10, C + ARM_HALF_WIDE - 3)] == ShadowFinder::TRANSMITTING); // ... and after it + CHECK(mask[I(W - 3, C)] != 0); // where it is opaque CHECK(mask[I(GAP_X1 + 10, C + ARM_HALF_WIDE + 25)] == 0); // and nothing beside it CHECK(mask[I(0, 0)] == 0); CHECK(mask[I(C - 60, C)] == 0);