rugnux pre-scan: measure the width on settled geometry, and skip the unread projection
Two things the pre-scan did regardless of what was asked of it. The spot width is read off the geometry as it stands. Candidates are excluded within MIN_BEAM_DISTANCE_PX of the beam centre and stratified into resolution bands about it, so with --estimate-beam-center the width was measured against the centre the pass was in the middle of correcting - a placeholder header centre both selects a different set of spots and labels them at the wrong resolution - and spot_width_measured_ then stopped the corrected pass from ever revisiting it. The width is now left to the second pass where one follows, which starts from the corrected centre; a run with no second pass measures it as before, a provisional radius being better than none. ShadowFinder::AddImage projects the whole image and ran for every frame of the shadow sample whether or not a shadow was wanted - with --detect-beam-stop=off, or on the second pass where the mask already holds one, that is the pass's largest per-frame cost paid for a result nobody reads. Now gated on want_shadow. The frame set is unchanged, so the mask is still built from the frames it was validated on. Gating it exposed the read-failure guard below, which asked the shadow finder how many frames it had seen. With the finder no longer fed, a pass that read every frame for the beam centre alone would have reported "no image could be read" and returned before placing it. Frames read are now counted directly. Inert on the shipped defaults, as intended: --estimate-beam-center is off by default, so the width deferral cannot fire, and the projection is wanted on the first pass. Verified by building the parent commit and this one and running eight crystals of the rotation battery through both at -N 10: every column identical - space group, unique reflections, observations, completeness, R_meas, CC1/2, <I/sigma> - on all eight. Warm-cache wall clock 1m03s against 1m02s, i.e. a wash; the saving only falls on passes that were discarding the result anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016L1qig74oYQzfUJJZbbxFh
This commit is contained in:
+24
-3
@@ -366,7 +366,18 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru
|
||||
const bool want_beam_center = config_.estimate_beam_center && !beam_center_placed_;
|
||||
// The spot width is a property of the crystal, not of the geometry, so the second pass reuses the
|
||||
// radius the first one measured rather than re-reading the frames.
|
||||
const bool want_width = config_.adaptive_integration_radius && !spot_width_measured_;
|
||||
//
|
||||
// It is READ OFF the geometry as it stands, though, and a beam centre this same pass is about to
|
||||
// correct leaves that geometry provisional: candidates are excluded within MIN_BEAM_DISTANCE_PX
|
||||
// of the centre and stratified into resolution bands about it, so a placeholder header centre
|
||||
// both measures a different set of spots and labels them at the wrong resolution - and
|
||||
// spot_width_measured_ would then stop the corrected pass from ever revisiting it. Where a
|
||||
// second pass follows, leave the width to that one, which starts from the corrected centre. A
|
||||
// run with no second pass measures it here: a provisional radius beats none.
|
||||
const bool second_pass_follows = want_beam_center && config_.rotation_indexing
|
||||
&& config_.two_pass_rotation && experiment_.IsRotationIndexing();
|
||||
const bool want_width = config_.adaptive_integration_radius && !spot_width_measured_
|
||||
&& !second_pass_follows;
|
||||
if (!want_shadow && !want_beam_center && !want_width)
|
||||
return;
|
||||
|
||||
@@ -486,6 +497,10 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru
|
||||
// Images the width ended up being measured on, for the log: the tiers below stop as soon as the
|
||||
// answer has settled, so this is a property of the crystal and worth reporting.
|
||||
size_t width_images = 0;
|
||||
// Frames that could actually be read. Counted here rather than asked of the shadow finder, which
|
||||
// is only fed when the shadow is wanted and would report zero for a pass that read every frame
|
||||
// for the beam centre alone.
|
||||
std::atomic<size_t> frames_read{0};
|
||||
|
||||
// Read the sample on several workers. The reader serialises on the HDF5 lock, but the
|
||||
// decompression, the projection and the spot finding - which is all of the cost on a large
|
||||
@@ -568,7 +583,13 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru
|
||||
msg.image = img->image;
|
||||
msg.number = ordinal;
|
||||
msg.original_number = image_idx;
|
||||
if (shadow_set.contains(ordinal))
|
||||
frames_read.fetch_add(1, std::memory_order_relaxed);
|
||||
// Gated on want_shadow, not on the frame set alone: AddImage projects the
|
||||
// whole image, and with --detect-beam-stop=off (or a shadow already in the
|
||||
// mask) that is the pass's largest per-frame cost paid for a result nobody
|
||||
// reads. The set still decides WHICH frames feed it, so the mask is built
|
||||
// from the frames it always was.
|
||||
if (want_shadow && shadow_set.contains(ordinal))
|
||||
finder.AddImage(msg, w.shadow_buffer, t);
|
||||
|
||||
// The width is measured on the projection's own even spread over the sweep,
|
||||
@@ -644,7 +665,7 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru
|
||||
}
|
||||
}
|
||||
|
||||
if (finder.GetFrameCount() == 0) {
|
||||
if (frames_read.load() == 0) {
|
||||
logger.Warning("Pre-scan: no image could be read. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user