Choose min-pix-per-spot adaptively per image for serial-stills indexing
For stills indexing the minimum-pixels-per-spot filter is now chosen per image instead of being fixed: the frame is indexed at min-pix 3/2/1 and the setting that maximises indexed-spot count weighted by indexed fraction (n_indexed^2 / n_total) is kept, then integrated once at that min-pix. The fraction factor keeps a smaller min-pix's extra spots only when the lattice actually explains them, so strong frames retain their real weak spots (extending resolution) while noise-flooded frames stay strict. The mode is selected by the presence of --min-pix-per-spot, now optional (SpotFindingSettings::min_pix_per_spot is std::optional<int64_t>): omit it for the adaptive per-image path, give a value to force a fixed min-pix. It applies only to the stills indexing path -- rotation indexing builds one global lattice and keeps a fixed min-pix, and the online receiver and the FPGA host path always carry a concrete value, so neither changes. IndexAndRefine::ProcessImage now returns whether the frame indexed, to drive the per-image selection. Exposed in the jfjoch_viewer spot-finding settings (adaptive-threshold and adaptive-min-pix checkboxes, each greying out the control it overrides); the broker uses neither. Validated on the full rotation regression battery (no regression) and the whole serial-stills target battery at full image count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "MXAnalysisWithoutFPGA.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "spot_finding/StrongPixelSet.h"
|
||||
#include "../compression/JFJochDecompress.h"
|
||||
|
||||
@@ -117,30 +119,68 @@ void MXAnalysisWithoutFPGA::Analyze(DataMessage &output,
|
||||
|| mask_low_res != spot_finding_settings.low_resolution_limit)
|
||||
UpdateMaskResolution(spot_finding_settings);
|
||||
|
||||
const auto spot_finding_start_time = std::chrono::steady_clock::now();
|
||||
ImageSpotFinder &finder = spot_finding_settings.adaptive_threshold
|
||||
? static_cast<ImageSpotFinder &>(*adaptiveSpotFinder)
|
||||
: *spotFinder;
|
||||
const std::vector<DiffractionSpot> spots = finder.Run(*preprocessor_buffer, spot_finding_settings, mask_resolution);
|
||||
SpotAnalyze(experiment, spot_finding_settings, spots, output);
|
||||
const auto spot_finding_end_time = std::chrono::steady_clock::now();
|
||||
output.spot_finding_time_s = std::chrono::duration<float>(spot_finding_end_time - spot_finding_start_time).count();
|
||||
const auto integrate_fn = [this](const std::vector<Reflection> &predicted, size_t npredicted,
|
||||
int64_t image_number) {
|
||||
return bragg_engine->Run(*preprocessor_buffer, predicted, npredicted, image_number);
|
||||
};
|
||||
|
||||
// A missing min-pix (std::nullopt) means "choose it per image". This applies only to the stills
|
||||
// indexing path (each frame is indexed independently); rotation indexing builds one lattice from
|
||||
// all frames, so it keeps the fixed min-pix and the single-pass finder.
|
||||
const bool adaptive_min_pix = !spot_finding_settings.min_pix_per_spot.has_value()
|
||||
&& spot_finding_settings.indexing
|
||||
&& !experiment.IsRotationIndexing();
|
||||
if (adaptive_min_pix) {
|
||||
// Choose the per-image min-pix adaptively instead of a fixed one. min-pix filters
|
||||
// connected components AFTER detection, so re-running the finder only re-does the cheap CCL +
|
||||
// spot filter, not the reduction; the azimuthal profile is identical across attempts. Index
|
||||
// at 3/2/1 (index-only, no integration/accumulation) and keep whichever maximises
|
||||
// n_indexed^2 / n_total (indexed count weighted by indexed fraction), then integrate once at
|
||||
// that min-pix. spot_finding_time_s covers the whole escalation.
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
SpotFindingSettings s = spot_finding_settings;
|
||||
int best_mp = 0;
|
||||
double best_score = -1.0;
|
||||
for (int mp : {3, 2, 1}) {
|
||||
s.min_pix_per_spot = mp;
|
||||
const std::vector<DiffractionSpot> spots = finder.Run(*preprocessor_buffer, s, mask_resolution);
|
||||
SpotAnalyze(experiment, s, spots, output);
|
||||
if (indexer.IndexFrameOnly(output, s)) {
|
||||
const double n_idx = static_cast<double>(output.spot_count_indexed.value_or(0));
|
||||
const double n_tot = static_cast<double>(std::max<int64_t>(1, output.spot_count.value_or(1)));
|
||||
const double score = n_idx * n_idx / n_tot;
|
||||
if (score > best_score) { best_score = score; best_mp = mp; }
|
||||
}
|
||||
}
|
||||
if (best_mp != 0) {
|
||||
// Re-run spot finding + index at the winning min-pix and integrate there.
|
||||
s.min_pix_per_spot = best_mp;
|
||||
const std::vector<DiffractionSpot> spots = finder.Run(*preprocessor_buffer, s, mask_resolution);
|
||||
SpotAnalyze(experiment, s, spots, output);
|
||||
indexer.ProcessImage(output, s, *prediction, integrate_fn);
|
||||
}
|
||||
output.spot_finding_time_s = std::chrono::duration<float>(std::chrono::steady_clock::now() - start_time).count();
|
||||
} else {
|
||||
const auto spot_finding_start_time = std::chrono::steady_clock::now();
|
||||
const std::vector<DiffractionSpot> spots = finder.Run(*preprocessor_buffer, spot_finding_settings, mask_resolution);
|
||||
SpotAnalyze(experiment, spot_finding_settings, spots, output);
|
||||
output.spot_finding_time_s = std::chrono::duration<float>(std::chrono::steady_clock::now() - spot_finding_start_time).count();
|
||||
if (spot_finding_settings.indexing)
|
||||
indexer.ProcessImage(output, spot_finding_settings, *prediction, integrate_fn);
|
||||
}
|
||||
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
if (fused) {
|
||||
// Lift the azimuthal profile the fused engine computed in the same pass; its azint cost is
|
||||
// folded into spot_finding_time_s above.
|
||||
// Lift the azimuthal profile the fused engine computed in the same pass (identical across
|
||||
// any min-pix retries); its azint cost is folded into spot_finding_time_s above.
|
||||
profile.Clear(integration);
|
||||
profile += fused_adaptive->GetProfile();
|
||||
output.azint_time_s = 0.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (spot_finding_settings.indexing)
|
||||
indexer.ProcessImage(output, spot_finding_settings, *prediction,
|
||||
[this](const std::vector<Reflection> &predicted, size_t npredicted, int64_t image_number) {
|
||||
return bragg_engine->Run(*preprocessor_buffer, predicted, npredicted, image_number);
|
||||
});
|
||||
}
|
||||
|
||||
output.max_viable_pixel_value = ret.max_value;
|
||||
|
||||
Reference in New Issue
Block a user