Stills integration: background ring reach measured on empty apertures, radial curvature from the whole frame
Two changes to the stills integration background, both stills-only (rotation keeps its path bit for bit): 1. The r2..r3 background ring's outer radius is measured per run. The pre-scan lays apertures on a grid over its frames, away from every found spot, and for each candidate r3 (r2+2 .. 14) takes the mean square difference between the r1 disk mean and the clipped ring mean. With nothing in the aperture that is exactly the background error the ring makes (noise + bias); the run keeps the minimum. On a photon-counting pixel detector the noise wins and the shipped r3=14 is kept; on a binned CCD the background structure wins and the narrowest ring is taken. r1 stays 6: a smaller disk (4,6,14) costs 0.0013 dimple R_free on a JUNGFRAU set, so it does not pass the photon-counting gate. --adaptive-integration-radius now defaults on for stills too (=off restores the fixed 6/8/14). The pre-scan bandwidth estimate is not applied to stills. 2. The radial background-curvature correction on stills takes its curve from the adaptive spot finder's peak-excluded per-ring background (whole frame) instead of the predicted reflections' own ring pixels, and auto applies it to every stills image (no ice-score gate: a solvent/carrier hump curves the background as much as ice). New engine call RadialBackgroundFromRings; CPU, GPU and Metal read the handed-over curve. The GPU box sum now sizes the radial bin index separately from accumulating, so a handed-over curve is read at the right radius. Measured (dimple R_free, deposited free set, battery arguments, all frames unless noted): pal86 (Rayonix 4x4, 156 um): 0.354 -> 0.218 (ring 10; at 4,6,10 0.213) lcls222 (Rayonix 4x4, 177 um): 0.205 -> 0.196 (ring 10) sacla236 (MPCCD): 0.2588 -> 0.2558 (ring 11) alvra213_int16 (JUNGFRAU): 0.1986 -> 0.1986 (ring 14, unchanged) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
1 parent
987e812e23
commit
e8145d8d2f
17 files changed
+558
-42
No files matched your search
@@ -10,6 +10,7 @@
|
||||
#include "../common/BraggIntegrationSettings.h"
|
||||
#include "../common/DetectorSetup.h"
|
||||
#include "../common/DiffractionExperiment.h"
|
||||
#include "../common/JFJochMath.h"
|
||||
#include "../common/Reflection.h"
|
||||
#include "../image_analysis/bragg_integration/BraggIntegrationEngineCPU.h"
|
||||
#include "../image_analysis/image_preprocessing/ImagePreprocessorBuffer.h"
|
||||
@@ -148,3 +149,57 @@ TEST_CASE("BraggIntegrationEngineCPU_SaturatedPeakIsFlaggedNotDropped", "[Integr
|
||||
CHECK(saturated[0].overloaded);
|
||||
CHECK(saturated[0].clipped);
|
||||
}
|
||||
|
||||
// An empty aperture on a background that is a curved function of the radius alone: the flat ring mean
|
||||
// reads it wrong by the curvature, and the radial correction with the curve taken from a per-ring
|
||||
// profile of the whole frame (as the spot finder hands it over on stills) takes that back out.
|
||||
TEST_CASE("BraggIntegrationEngineCPU_RadialCurveFromRings", "[Integration][portable]") {
|
||||
DiffractionExperiment experiment(DetJF(2));
|
||||
experiment.DetectorDistance_mm(100.0f).IncidentEnergy_keV(WVL_1A_IN_KEV).BeamX_pxl(400.0f).BeamY_pxl(400.0f);
|
||||
const float bx = 400.0f, by = 400.0f;
|
||||
const auto background = [](double r) { return 50.0 + 0.05 * (r - 300.0) * (r - 300.0); };
|
||||
const size_t width = experiment.GetXPixelsNum(), npixel = experiment.GetPixelsNum();
|
||||
ImagePreprocessorBuffer image(npixel);
|
||||
for (size_t i = 0; i < npixel; ++i)
|
||||
image[i] = static_cast<int32_t>(std::lround(
|
||||
background(std::hypot(static_cast<double>(i % width) - bx, static_cast<double>(i / width) - by))));
|
||||
|
||||
// The profile per q bin, as the spot finder's rings would hold it.
|
||||
const float low_q = 0.05f, dq = 0.01f;
|
||||
const double F_px = 100.0 / 0.075, lambda = 1.0;
|
||||
std::vector<float> rings(500);
|
||||
for (size_t i = 0; i < rings.size(); ++i) {
|
||||
const double q = low_q + (i + 0.5) * dq;
|
||||
rings[i] = static_cast<float>(background(F_px * std::tan(2.0 * std::asin(q * lambda / (4.0 * PI)))));
|
||||
}
|
||||
rings[100] = NAN; // a ring with no value is bridged
|
||||
|
||||
Reflection r{};
|
||||
r.h = 1; r.k = 0; r.l = 0;
|
||||
r.predicted_x = bx + 300.0f;
|
||||
r.predicted_y = by;
|
||||
r.d = 2.0f;
|
||||
r.prescaling_corr = 1.0f;
|
||||
r.partiality = 1.0f;
|
||||
const std::vector<Reflection> predicted{r};
|
||||
|
||||
auto integrate = [&](bool correct) {
|
||||
experiment.ImportBraggIntegrationSettings(
|
||||
BraggIntegrationSettings().Integrator(IntegratorMode::BoxSum).BackgroundRadialCorrection(correct));
|
||||
BraggIntegrationEngineCPU engine(experiment, PixelMask(experiment));
|
||||
engine.RadialBackgroundFromRings(rings, static_cast<int32_t>(rings.size()), low_q, dq);
|
||||
REQUIRE(!engine.GetRadialBackground().empty());
|
||||
const auto out = engine.Run(image, predicted, predicted.size(), 0);
|
||||
REQUIRE(out.size() == 1);
|
||||
return out[0].I;
|
||||
};
|
||||
// The curvature is along the radius only, so the error is n_inner x 0.05 x (<u^2>_ring - <u^2>_disk)
|
||||
// with u the radial offset: 49 px x 0.05 x ((6^2 + 13^2) / 4 - 4^2 / 4) = 116 counts read too low.
|
||||
CHECK(integrate(false) < -80.0f);
|
||||
CHECK(std::fabs(integrate(true)) < 5.0f);
|
||||
|
||||
// An empty profile hands the curve back to the reflections' own rings.
|
||||
BraggIntegrationEngineCPU engine(experiment, PixelMask(experiment));
|
||||
engine.RadialBackgroundFromRings({}, 500, low_q, dq);
|
||||
CHECK(engine.GetRadialBackground().empty());
|
||||
}
|
||||
Reference in new issue
Block a user