From a509fd8022267efb43c826ebc5b2a6ee65ff381a Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 14 Oct 2026 12:05:05 +0200 Subject: [PATCH] rugnux stills: mask the pixels holding the detector's error value (and dead pixels) in the pre-scan The defective-pixel finder ran only on scanning rotation data. On stills, a pixel that holds the detector's error value on every frame but is missing from the file's static mask is read by the integrator as a per-frame unreadable pixel, so every reflection whose signal disk touches it is flagged overloaded and the merge drops it. On a SwissFEL JUNGFRAU stills run, 14k such pixels made 3.9% of all integrated reflections "overloaded" (mostly finer than 1.6 A, where they are scarcest) and raised a false OVERLOADS warning; with them masked, 0.05% remain flagged and the profile fit keeps the reflections whose disk stays at least MINPK readable. On stills only the error-value and dead rules apply: the hot-pixel rule counts a reflection's stay on a pixel in rotation, so spacing is passed as 0 there, which HotPixelFinder already treats as "no hot-pixel test". Rotation is unchanged (same gate, same oscillation and spacing). Result-changing for stills; merged-statistics A/B on full runs and the battery ci check still to do. Co-Authored-By: Claude Opus 5.5 (1M context) --- rugnux/RugnuxPreScan.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rugnux/RugnuxPreScan.cpp b/rugnux/RugnuxPreScan.cpp index 5061f0d2e..b2131b876 100644 --- a/rugnux/RugnuxPreScan.cpp +++ b/rugnux/RugnuxPreScan.cpp @@ -190,10 +190,12 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru // radius the first one measured rather than re-reading the frames. const bool want_width = config_.adaptive_integration_radius && !spot_width_measured_; // Defective pixels, for the same reason on the same pass: each pixel is compared against its - // resolution ring. Only under rotation, where a reflection moves off a pixel and a defect does not. + // resolution ring. Only under rotation, where a reflection moves off a pixel and a defect does not - + // except for the pixels holding the detector's error value and the dead ones, which need no + // rotation and are measured on stills too (MaskDefectivePixels). const auto goniometer = experiment_.GetGoniometer(); const bool want_hot = !hot_pixels_measured_ - && experiment_.IsRotationIndexing() && goniometer && goniometer->IsScanning(); + && (!experiment_.IsRotationIndexing() || (goniometer && goniometer->IsScanning())); // The background noise per unit level of a CCD, read off the same frames (BackgroundGain.h). const bool want_gain = config_.counts_in_adu && !background_gain_measured_; if (!want_shadow && !want_width && !want_hot && !want_gain) @@ -1193,9 +1195,11 @@ void Rugnux::MaskDefectivePixels(int start_image, const std::vector &sample } }); + // On stills there is no rotation to count a reflection's stay on a pixel in, so spacing stays 0 and + // the hot-pixel rule is not applied (HotPixelFinder::GetMask); the error-value and dead pixels are. const auto goniometer = experiment_.GetGoniometer(); - const double oscillation = std::abs(goniometer->GetIncrement_deg()); - const double spacing = sample.size() > 1 + const double oscillation = goniometer ? std::abs(goniometer->GetIncrement_deg()) : 0.0; + const double spacing = goniometer && sample.size() > 1 ? oscillation * (sample.back() - sample.front()) / static_cast(sample.size() - 1) : 0.0; const auto hot = finder.GetMask(oscillation, spacing, nthreads);