Remove the experimental PixelRefine integrator

On the LysozymeJet5 serial stills the default Gaussian profile-fit
integrator (ProfileIntegrate2D) + reference scaling matched or beat
whole-PixelRefine on every per-shell CC1/2 (overall 95.7% vs 91.9%), ISa
(1.6 vs 1.2) and R-meas (98.5% vs 175%), with CCref a tie -- so PixelRefine
has no remaining advantage. Reference-based per-image scaling is
integrator-agnostic (IndexAndRefine::ReferenceIntensities builds a
ScaleOnTheFly(experiment, reference) applied to any integrator's output),
so the reference-dataset feature (CCref + reference scaling) is kept.

Delete image_analysis/pixel_refinement/, GeomRefinementAlgorithmEnum::
PixelRefine and its gates, BraggIntegrationSettings::ProfileMultiplier
(PixelRefine-only; R1 is shared and kept), and the -r pixelrefine /
--profile-multiplier CLI. The inherited lessons (mean background, de-biased
variance, tight-profile-loses / centroid floor, R-refinement futile) are
folded into NEXTGEN_INTEGRATOR.md.

NOTE: this transiently breaks the viewer build -- the committed viewer
still references the removed enum and ProfileMultiplier. It is fixed in the
next commit (the viewer feature work), held separate while the viewer UI is
being tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 20:43:04 +02:00
co-authored by Claude Opus 4.8
parent 18d9c8ad63
commit 38ea0ec237
20 changed files with 64 additions and 1518 deletions
+14 -101
View File
@@ -206,10 +206,6 @@ void IndexAndRefine::RefineGeometryIfNeeded(DataMessage &msg, IndexAndRefine::In
XtalOptimizerRotationOnly(data, msg.spots, 0.05);
break;
case GeomRefinementAlgorithmEnum::BeamCenter:
case GeomRefinementAlgorithmEnum::PixelRefine:
// PixelRefine still benefits from the classical beam-center + cell
// refinement as a starting point; the pixel-level refinement runs
// later, during integration.
if (XtalOptimizer(data, {msg.spots})) {
outcome.experiment.BeamX_pxl(data.geom.GetBeamX_pxl())
.BeamY_pxl(data.geom.GetBeamY_pxl());
@@ -323,34 +319,20 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg,
.bandwidth_sigma = experiment.GetBandwidthFWHM().value_or(0.0f) / 2.3548f
};
// Select the integration path: classical 2D integration, or the experimental
// PixelRefine joint geometry/profile/scale refinement (which also produces the
// integrated reflections that flow into the normal save/merge).
const bool use_pixel_refine =
experiment.GetIndexingSettings().GetGeomRefinementAlgorithm() == GeomRefinementAlgorithmEnum::PixelRefine
&& !pixel_reference_.empty();
// Predict, then integrate with the selected integrator (box-sum or profile-fit).
auto pred_start_time = std::chrono::steady_clock::now();
auto nrefl = prediction.Calc(outcome.experiment, latt, settings_prediction);
auto pred_end_time = std::chrono::steady_clock::now();
msg.bragg_prediction_time_s = std::chrono::duration<float>(pred_end_time - pred_start_time).count();
if (use_pixel_refine) {
auto integration_start_time = std::chrono::steady_clock::now();
PixelRefineIntegrate(msg, image, prediction, outcome, i_outcome);
msg.integrated_reflections = i_outcome.reflections.size();
auto integration_end_time = std::chrono::steady_clock::now();
msg.integration_time_s = std::chrono::duration<float>(integration_end_time - integration_start_time).count();
} else {
auto pred_start_time = std::chrono::steady_clock::now();
auto nrefl = prediction.Calc(outcome.experiment, latt, settings_prediction);
auto pred_end_time = std::chrono::steady_clock::now();
msg.bragg_prediction_time_s = std::chrono::duration<float>(pred_end_time - pred_start_time).count();
auto integration_start_time = std::chrono::steady_clock::now();
i_outcome.reflections =
outcome.experiment.GetBraggIntegrationSettings().GetIntegrator() == IntegratorMode::BoxSum
? BraggIntegrate2D(outcome.experiment, image, prediction.GetReflections(), nrefl, msg.number)
: ProfileIntegrate2D(outcome.experiment, image, prediction.GetReflections(), nrefl, msg.number);
msg.integrated_reflections = i_outcome.reflections.size();
auto integration_end_time = std::chrono::steady_clock::now();
msg.integration_time_s = std::chrono::duration<float>(integration_end_time - integration_start_time).count();
}
auto integration_start_time = std::chrono::steady_clock::now();
i_outcome.reflections =
outcome.experiment.GetBraggIntegrationSettings().GetIntegrator() == IntegratorMode::BoxSum
? BraggIntegrate2D(outcome.experiment, image, prediction.GetReflections(), nrefl, msg.number)
: ProfileIntegrate2D(outcome.experiment, image, prediction.GetReflections(), nrefl, msg.number);
msg.integrated_reflections = i_outcome.reflections.size();
auto integration_end_time = std::chrono::steady_clock::now();
msg.integration_time_s = std::chrono::duration<float>(integration_end_time - integration_start_time).count();
constexpr size_t kMaxReflections = 10000;
if (i_outcome.reflections.size() > kMaxReflections) {
@@ -371,10 +353,7 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg,
CalcISigma(msg, i_outcome.reflections);
CalcWilsonBFactor(msg, i_outcome.reflections);
// PixelRefine produces already-scaled reflections; only the classical path
// needs the separate ScaleOnTheFly step.
if (!use_pixel_refine)
ScaleImage(msg, i_outcome);
ScaleImage(msg, i_outcome);
// Copy reflections to outgoing message
msg.reflections = i_outcome.reflections;
@@ -434,7 +413,6 @@ std::optional<RotationIndexerResult> IndexAndRefine::FinalizeRotationIndexing()
IndexAndRefine &IndexAndRefine::ReferenceIntensities(std::vector<MergedReflection> &reference) {
scaling_engine = std::make_unique<ScaleOnTheFly>(experiment, reference);
pixel_reference_ = reference; // kept for the experimental PixelRefine path
return *this;
}
@@ -454,71 +432,6 @@ void IndexAndRefine::ScaleImage(DataMessage &msg, IntegrationOutcome& outcome) {
msg.image_scale_time_s = std::chrono::duration<float>(scaling_end_time - scaling_start_time).count();
}
bool IndexAndRefine::PixelRefineIntegrate(DataMessage &msg,
const CompressedImage &image,
BraggPrediction &prediction,
const IndexAndRefine::IndexingOutcome &outcome,
IntegrationOutcome &i_outcome) {
if (!outcome.lattice_candidate)
return false;
// Build the engine once (lazy).
std::call_once(pixel_refine_once_, [&] {
pixel_refine_ = std::make_unique<PixelRefine>(experiment, pixel_reference_);
});
if (!pixel_refine_)
return false;
PixelRefineData prd;
prd.geom = outcome.experiment.GetDiffractionGeometry();
prd.latt = *outcome.lattice_candidate;
prd.centering = outcome.symmetry.centering;
if (const auto bw = experiment.GetBandwidthFWHM())
prd.bandwidth = bw.value() / 2.3548; // FWHM -> sigma
// Signal-box radius from the shared integration setting (same knob as BraggIntegrate2D).
prd.shoebox_radius = static_cast<int>(std::lround(experiment.GetBraggIntegrationSettings().GetR1()));
prd.r1_multiplier = experiment.GetBraggIntegrationSettings().GetProfileMultiplier();
std::vector<uint8_t> buffer;
const uint8_t *ptr = image.GetUncompressedPtr(buffer);
switch (image.GetMode()) {
case CompressedImageMode::Int8:
pixel_refine_->Run(reinterpret_cast<const int8_t *>(ptr), prediction, prd); break;
case CompressedImageMode::Int16:
pixel_refine_->Run(reinterpret_cast<const int16_t *>(ptr), prediction, prd); break;
case CompressedImageMode::Int32:
pixel_refine_->Run(reinterpret_cast<const int32_t *>(ptr), prediction, prd); break;
case CompressedImageMode::Uint8:
pixel_refine_->Run(reinterpret_cast<const uint8_t *>(ptr), prediction, prd); break;
case CompressedImageMode::Uint16:
pixel_refine_->Run(reinterpret_cast<const uint16_t *>(ptr), prediction, prd); break;
case CompressedImageMode::Uint32:
pixel_refine_->Run(reinterpret_cast<const uint32_t *>(ptr), prediction, prd); break;
default:
return false;
}
// PixelRefine output flows into the normal save/merge path: the refined
// geometry/lattice and the already-scaled reflections become the outcome.
i_outcome.reflections = std::move(prd.reflections);
i_outcome.geom = prd.geom;
i_outcome.latt = prd.latt;
i_outcome.image_scale_g = static_cast<float>(prd.scale_factor);
i_outcome.image_scale_b_factor_Ang2 = static_cast<float>(prd.B_factor);
if (std::isfinite(prd.cc)) {
i_outcome.image_scale_cc = static_cast<float>(prd.cc);
i_outcome.image_scale_cc_n = prd.cc_n;
}
msg.image_scale_factor = static_cast<float>(prd.scale_factor);
msg.image_scale_cc = i_outcome.image_scale_cc;
if (prd.B_factor != 0.0)
msg.image_scale_b_factor = static_cast<float>(prd.B_factor);
return true;
}
ScalingResult IndexAndRefine::ScaleAllImages(const std::vector<MergedReflection> &reference, size_t nthreads) {
ScaleOnTheFly scaling(experiment, reference);
scaling.Scale(integration_outcome, nthreads);