Stills keep an overload as a lower bound only where it has pixels at the limit; run-level gate removed
The lower bound counts each saturated pixel at the saturation limit, so it needs one. An observation overloaded only by pixels unreadable on this frame (a per-frame bad-pixel flag, data value 0) was kept as a box sum with those pixels counted as zero, which bounds nothing; it now stays overloaded and is dropped, per observation. With the stuck pixels masked by the pre-scan, the run-level gate (kept overloads must be enriched coarser than 4 A) has nothing left to catch: without it every still set gives the gated result (clipped CCD and Rayonix sets identical to the fourth decimal, photon-counting sets unchanged), so it is removed with Reflection::overload_kept. A neighbour-brightness test per saturated pixel was tried and not kept: after the mask it only dropped 9-20% of the Rayonix overloads and worsened R_split on one set (0.245 -> 0.278). 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
b7e4ea23a3
commit
5905d34cba
5 files changed
+101
-44
No files matched your search
@@ -307,3 +307,47 @@ TEST_CASE("BraggIntegrationEngineCPU_StillKeepsOverloadAsLowerBound", "[Integrat
|
||||
CHECK(flagged[0].overloaded);
|
||||
CHECK(rotation.Counts().overloads_kept == 0);
|
||||
}
|
||||
|
||||
// A still keeps an overloaded observation as a lower bound only where it has pixels at the saturation
|
||||
// limit. One overloaded by a pixel unreadable on this frame alone has nothing to count at the limit:
|
||||
// it stays flagged and is dropped.
|
||||
TEST_CASE("BraggIntegrationEngineCPU_StillKeepsOnlySaturatedOverloads", "[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);
|
||||
experiment.ImportBraggIntegrationSettings(BraggIntegrationSettings());
|
||||
const int32_t limit = 500, bkg = 12;
|
||||
experiment.Detector().SaturationLimit(limit);
|
||||
const size_t width = experiment.GetXPixelsNum(), npixel = experiment.GetPixelsNum();
|
||||
|
||||
ImagePreprocessorBuffer image(npixel);
|
||||
for (size_t i = 0; i < npixel; ++i)
|
||||
image[i] = bkg;
|
||||
auto spot = [&](int cx, int cy, double amplitude) {
|
||||
for (int dy = -6; dy <= 6; ++dy)
|
||||
for (int dx = -6; dx <= 6; ++dx) {
|
||||
const int32_t v = bkg + static_cast<int32_t>(std::lround(amplitude * std::exp(-(dx * dx + dy * dy) / (2 * 1.3 * 1.3))));
|
||||
image[(cy + dy) * width + cx + dx] = v >= limit ? INT32_MAX : v;
|
||||
}
|
||||
};
|
||||
spot(600, 300, 2000.0); // clipped: a plateau of saturated pixels in a bright spot
|
||||
spot(300, 300, 40.0);
|
||||
image[300 * width + 300] = INT32_MIN; // its peak unreadable on this frame, not in the mask
|
||||
|
||||
std::vector<Reflection> predicted;
|
||||
for (const auto &[x, y] : {std::pair{600, 300}, std::pair{300, 300}}) {
|
||||
Reflection r{};
|
||||
r.h = x; r.k = y; r.l = 1;
|
||||
r.predicted_x = x; r.predicted_y = y;
|
||||
r.d = 2.0f;
|
||||
r.prescaling_corr = 1.0f;
|
||||
r.partiality = 1.0f;
|
||||
predicted.push_back(r);
|
||||
}
|
||||
|
||||
BraggIntegrationEngineCPU still(experiment, PixelMask(experiment));
|
||||
const auto out = still.Run(image, predicted, predicted.size(), 0);
|
||||
REQUIRE(out.size() == 2);
|
||||
CHECK_FALSE(out[0].overloaded);
|
||||
CHECK(out[1].overloaded);
|
||||
CHECK(still.Counts().overloads_kept == 1);
|
||||
}
|
||||
Reference in new issue
Block a user