From 314e502733d65210cd1c7c7461bef16997b651bb Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 1 Nov 2026 12:56:47 +0100 Subject: [PATCH] Stills: the shell past the CC1/2 crossing is not written where only the detector corners record it The automatic cut writes one shell past the CC1/2 crossing. Past the finest whole ring about the beam only the detector's corners record, and a reflection there is seen a few times at most, on whichever weak frames reach it, scaled up tens to hundreds of times: that shell is pure noise, and on several still runs its handful of reflections decided the plain overall CC1/2 and R_split. On stills the extension now stops at the finest whole ring, or at the crossing where that already lies in the corners - also where no cut was made because the extension ran past the measured edge. Rotation is unchanged. Open still battery, against the branch before both changes: plain CC1/2 and R_split recover on every set whose last shell was corner-only (e.g. 0.00 -> 0.99 / 5.5 % on one PAL set), the deposited-model R-free is within 0.005 or better on all eight sets checked, at a written d_min 0.05-0.13 A coarser. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37 --- .../scale_merge/ResolutionCutoff.cpp | 16 ++++++++++++- image_analysis/scale_merge/ResolutionCutoff.h | 6 ++++- rugnux/RugnuxScaleMerge.cpp | 23 ++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/image_analysis/scale_merge/ResolutionCutoff.cpp b/image_analysis/scale_merge/ResolutionCutoff.cpp index 9f7b5aa62..6796d3ffe 100644 --- a/image_analysis/scale_merge/ResolutionCutoff.cpp +++ b/image_analysis/scale_merge/ResolutionCutoff.cpp @@ -244,6 +244,7 @@ ResolutionCutoffResult ComputeCCHalfLogisticCutoff(const std::vector= s_hi) { + result.extension_past_edge = true; result.note = "CC1/2 does not fall off within the measured range"; return result; } @@ -278,7 +279,8 @@ std::optional ApplyResolutionCutoff(std::vector &merge double cc_target, bool for_search, Logger &logger, - std::optional *fit_limit_out) { + std::optional *fit_limit_out, + std::optional whole_ring_d) { std::optional effective_d_min = manual_limit; if (!effective_d_min && !for_search && method == ResolutionCutoffMethod::CCHalfLogistic) { const auto rc = ComputeCCHalfLogisticCutoff(merged, cc_target, logger); @@ -295,6 +297,18 @@ std::optional ApplyResolutionCutoff(std::vector &merge } else { logger.Info("Auto resolution cutoff: none ({}); keeping the full resolution range", rc.note); } + // Past the finest whole ring the detector records only its corners, and there a reflection is seen + // a few times at most, on whichever frames reach it: the shell written past the crossing is then + // made of those reflections, and pure noise. So the extension stops at the whole ring, or at the + // crossing where that is already in the corners - which is where established programs stop too + // (the inscribed circle of XDS's default TRUSTED_REGION). + if (rc.d_fit && whole_ring_d + && (rc.d_cut ? *rc.d_cut < *whole_ring_d : rc.extension_past_edge)) { + effective_d_min = std::max(rc.d_cut.value_or(0.0), std::min(*rc.d_fit, *whole_ring_d)); + logger.Info("Auto resolution cutoff: {:.2f} A instead - past the detector's finest whole ring at {:.2f} A " + "only its corners record, so the shell past the crossing is not written there", + *effective_d_min, *whole_ring_d); + } } if (effective_d_min) merged.erase(std::remove_if(merged.begin(), merged.end(), diff --git a/image_analysis/scale_merge/ResolutionCutoff.h b/image_analysis/scale_merge/ResolutionCutoff.h index 8252fbb1b..d8a92c7f0 100644 --- a/image_analysis/scale_merge/ResolutionCutoff.h +++ b/image_analysis/scale_merge/ResolutionCutoff.h @@ -31,6 +31,7 @@ struct ResolutionCutoffResult { // reflections are actually written to. This is the number to quote. Set whenever the fit produced // a crossing inside the measured range, even when no cut was applied. std::optional d_fit; + bool extension_past_edge = false; // no cut because the one-shell extension runs past the measured edge std::string note; // human-readable description of the decision (for logging) }; @@ -59,10 +60,13 @@ ResolutionCutoffResult ComputeCCHalfLogisticCutoff(const std::vector ApplyResolutionCutoff(std::vector &merged, std::optional manual_limit, ResolutionCutoffMethod method, double cc_target, bool for_search, Logger &logger, - std::optional *fit_limit_out = nullptr); + std::optional *fit_limit_out = nullptr, + std::optional whole_ring_d = std::nullopt); diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index 294179608..6f9b99278 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -83,6 +83,7 @@ #include "../image_analysis/WriteReflections.h" #include "../image_analysis/bragg_integration/CalcISigma.h" #include "../common/Definitions.h" +#include "../common/AssembleToPlane.h" #include "../common/CorrelationCoefficient.h" #include #include @@ -92,6 +93,25 @@ using namespace rugnux_internal; namespace { + // The finest resolution the detector records on a whole ring about the beam: the coarsest along its edge. + double WholeRingResolution_A(const DiffractionExperiment &x) { + const DiffractionGeometry geom = x.GetDiffractionGeometry(); + const PlaneRaster raster = PlaneRasterOf(x.GetModules(), x.GetXPixelsNumConv(), x.GetYPixelsNumConv()); + double d = 0.0; + const auto at = [&](int64_t i, int64_t j) { + d = std::max(d, geom.PxlToRes(static_cast(raster.x0 + i), static_cast(raster.y0 + j))); + }; + for (int64_t i = 0; i < raster.width; i++) { + at(i, 0); + at(i, raster.height - 1); + } + for (int64_t j = 0; j < raster.height; j++) { + at(0, j); + at(raster.width - 1, j); + } + return d; + } + // Two groups of a given cell's frames whose mean volumes differ by less than this (relative) are one // crystal form (see the given-cell population below). constexpr double MIN_FORM_SEPARATION = 0.01; @@ -741,7 +761,8 @@ StillsMergeResult StillsScaleMerge(const DiffractionExperiment &experiment, std: // space-group search still sees the full range. const auto &cut_ss = experiment.GetScalingSettings(); out.d_min = ApplyResolutionCutoff(out.merged, cut_ss.GetHighResolutionLimit_A(), cut_ss.GetResolutionCutoff(), - cut_ss.GetResolutionCCTarget(), for_search, logger, &out.resolution_fit_A); + cut_ss.GetResolutionCCTarget(), for_search, logger, &out.resolution_fit_A, + WholeRingResolution_A(experiment)); phase("Computing statistics"); TimingMark stats_mark(logger, "stills statistics");