Pre-scan masks a pixel at the saturation limit on most frames, as one holding the error value
A pixel stuck at the saturation limit was never masked: the defective-pixel scan counted only the error value as "invalid on most frames", and a saturated pixel merely as lit, which the hot rule does not judge on stills. A Rayonix stills set has 3,627 pixels at 65535 on every frame, at high resolution; each flagged every reflection whose disk it fell in as overloaded, and the spot finder found them on every frame. The saturation limit now counts with the error value (CPU and CUDA), so such a pixel is masked like one holding the error value. On that set the indexed fraction rises from 10% to 33% (and 7% to 30% on the second lattice of the same run), R_split 0.120 -> 0.106 and the unrefined R-free against the deposition -0.001 / -0.024. Rotation merges checked bit-identical (three in-house sets with overloads). 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
0cf62452cd
commit
b7e4ea23a3
5 files changed
+19
-13
No files matched your search
@@ -203,7 +203,8 @@ void HotPixelFinder::AddImage(const int32_t *image, std::vector<int32_t> &scratc
|
||||
for (size_t i = begin; i < end; i++) {
|
||||
const int32_t k = key[i], v = image[i];
|
||||
if (k < 0) continue;
|
||||
if (v == INT32_MIN) {
|
||||
// The error value and the saturation limit alike: no count (see the class comment).
|
||||
if (v == INT32_MIN || v == INT32_MAX) {
|
||||
n_error[i]++;
|
||||
if (ring_ok[k / SECTORS]) { // counted in its ring-sector's frames, but not valid here
|
||||
n_error_ring_ok[i]++;
|
||||
@@ -213,7 +214,7 @@ void HotPixelFinder::AddImage(const int32_t *image, std::vector<int32_t> &scratc
|
||||
}
|
||||
if (!ring_ok[k / SECTORS]) continue;
|
||||
sum_value[i] += v;
|
||||
if (v == INT32_MAX || static_cast<float>(v) > threshold[k])
|
||||
if (static_cast<float>(v) > threshold[k])
|
||||
n_lit[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@
|
||||
// is above the Poisson bound: a weaker one cannot make an outlier. Alone or as part of a patch - a
|
||||
// patch lit through the whole sweep is stationary in the lab, so it is not a reflection of the
|
||||
// rotating crystal, and a reflection crossing it reads the patch. A pixel holding the
|
||||
// detector's error value on most frames is masked with it: every frame already treats it as invalid,
|
||||
// but the static mask did not know.
|
||||
// detector's error value or its saturation limit on most frames is masked with it: every frame already
|
||||
// treats it as invalid, but the static mask did not know. A pixel stuck at the limit is no overload -
|
||||
// a CCD can have thousands, at 65535 on every frame - and unmasked, every reflection whose disk it
|
||||
// falls in is flagged as overloaded.
|
||||
//
|
||||
// A DEAD pixel is the mirror of a hot one: it reads on average at most half of its own sector's level,
|
||||
// and its mean deficit is above the bound a hot pixel's excess has to clear. Nothing but a defect keeps
|
||||
@@ -85,7 +87,7 @@ public:
|
||||
struct Result {
|
||||
std::vector<uint32_t> mask; // non-zero = masked (1 hot, 2 error value, 3 dead), converted geometry
|
||||
size_t hot = 0; // persistent above the ring
|
||||
size_t error = 0; // holding the error value on most frames
|
||||
size_t error = 0; // holding the error value or the saturation limit on most frames
|
||||
size_t dead = 0; // reading at most half of its sector's level
|
||||
uint32_t frames = 0;
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ __global__ void accumulate_kernel(const int32_t *__restrict__ image, const int32
|
||||
i += static_cast<size_t>(blockDim.x) * gridDim.x) {
|
||||
const int32_t k = key[i], v = image[i];
|
||||
if (k < 0) continue;
|
||||
if (v == INT32_MIN) {
|
||||
if (v == INT32_MIN || v == INT32_MAX) { // the error value or the saturation limit: no count
|
||||
n_error[i]++;
|
||||
if (ring_ok[k / sectors]) {
|
||||
n_error_ring_ok[i]++;
|
||||
@@ -142,7 +142,7 @@ __global__ void accumulate_kernel(const int32_t *__restrict__ image, const int32
|
||||
}
|
||||
if (!ring_ok[k / sectors]) continue;
|
||||
sum_value[i] += v;
|
||||
if (v == INT32_MAX || static_cast<float>(v) > threshold[k])
|
||||
if (static_cast<float>(v) > threshold[k])
|
||||
n_lit[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,7 +1218,7 @@ void Rugnux::MaskDefectivePixels(int start_image, const std::vector<int> &sample
|
||||
pixel_mask_.LoadHotPixelMask(experiment_, mask);
|
||||
const size_t masked = defects->hot + defects->error + defects->dead;
|
||||
logger.Info("Defective pixels: {} masked from {} pre-scan frames - {} lit above their ring on more "
|
||||
"frames than one reflection or chance explains, {} holding the detector's error value, {} dead",
|
||||
"frames than one reflection or chance explains, {} holding the detector's error value or saturation limit, {} dead",
|
||||
masked, defects->frames, defects->hot, defects->error, defects->dead);
|
||||
Note(fmt::format("{} defective pixel{} masked", masked, masked == 1 ? "" : "s"));
|
||||
};
|
||||
|
||||
@@ -25,9 +25,10 @@ namespace {
|
||||
|
||||
// A Poisson background with a powder ring, and on it four kinds of pixel: one that reads 60 counts
|
||||
// high on every frame, one only 20 high - persistent, but too weak to make an outlier - one that holds
|
||||
// the error value on every frame, and one crossed by a genuine reflection. The last sits close to the rotation axis, where one reflection stays on a pixel for a
|
||||
// the error value on every frame, one stuck at the saturation limit, and one crossed by a genuine
|
||||
// reflection, saturated on a few frames. The last sits close to the rotation axis, where one reflection stays on a pixel for a
|
||||
// long stretch of rotation - here nine consecutive sampled frames, more than the chance bound allows
|
||||
// but fewer than the one-reflection bound at that zeta. Only the first and the third are masked - and
|
||||
// but fewer than the one-reflection bound at that zeta. Only the first, third and fourth are masked - and
|
||||
// a 3x3 patch reading high on every frame, which is stationary in the lab and so no reflection either.
|
||||
// Three dark pixels besides: one dead on the powder ring, which is masked, one on the ring reading 70%
|
||||
// of it, and one dead on the 5-count background, whose deficit is too small to make an outlier.
|
||||
@@ -40,7 +41,7 @@ TEST_CASE("HotPixelFinder_PersistentPixelNotBragg", "[HotPixelFinder]") {
|
||||
HotPixelFinder finder(x, pixel_mask, 4);
|
||||
|
||||
constexpr int HOT_X = 200, HOT_Y = 200, WARM_X = 190, WARM_Y = 60, ERR_X = 60, ERR_Y = 190;
|
||||
constexpr int BRAGG_X = 40, BRAGG_Y = 115;
|
||||
constexpr int BRAGG_X = 40, BRAGG_Y = 115, STUCK_X = 100, STUCK_Y = 30;
|
||||
constexpr int PATCH_X = 150, PATCH_Y = 225;
|
||||
constexpr int DEAD_X = C + 82, DEAD_Y = C, DIM_X = C, DIM_Y = C + 82, DEAD_BKG_X = 20, DEAD_BKG_Y = 20;
|
||||
std::mt19937 rng(1);
|
||||
@@ -57,11 +58,12 @@ TEST_CASE("HotPixelFinder_PersistentPixelNotBragg", "[HotPixelFinder]") {
|
||||
frame[I(HOT_X, HOT_Y)] += 60;
|
||||
frame[I(WARM_X, WARM_Y)] += 20;
|
||||
frame[I(ERR_X, ERR_Y)] = INT32_MIN;
|
||||
frame[I(STUCK_X, STUCK_Y)] = INT32_MAX;
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
frame[I(PATCH_X + dx, PATCH_Y + dy)] += 60;
|
||||
if (f >= 20 && f < 29)
|
||||
frame[I(BRAGG_X, BRAGG_Y)] += 500;
|
||||
frame[I(BRAGG_X, BRAGG_Y)] = f < 23 ? INT32_MAX : frame[I(BRAGG_X, BRAGG_Y)] + 500;
|
||||
frame[I(DEAD_X, DEAD_Y)] = 0;
|
||||
frame[I(DIM_X, DIM_Y)] = static_cast<int32_t>(0.7 * frame[I(DIM_X, DIM_Y)]);
|
||||
frame[I(DEAD_BKG_X, DEAD_BKG_Y)] = 0;
|
||||
@@ -72,6 +74,7 @@ TEST_CASE("HotPixelFinder_PersistentPixelNotBragg", "[HotPixelFinder]") {
|
||||
CHECK(result.frames == NFRAMES);
|
||||
CHECK(result.mask[I(HOT_X, HOT_Y)] == 1); // 1 = hot, 2 = error value, 3 = dead
|
||||
CHECK(result.mask[I(ERR_X, ERR_Y)] == 2);
|
||||
CHECK(result.mask[I(STUCK_X, STUCK_Y)] == 2);
|
||||
CHECK(result.mask[I(WARM_X, WARM_Y)] == 0);
|
||||
CHECK(result.mask[I(BRAGG_X, BRAGG_Y)] == 0);
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
@@ -81,7 +84,7 @@ TEST_CASE("HotPixelFinder_PersistentPixelNotBragg", "[HotPixelFinder]") {
|
||||
CHECK(result.mask[I(DIM_X, DIM_Y)] == 0);
|
||||
CHECK(result.mask[I(DEAD_BKG_X, DEAD_BKG_Y)] == 0);
|
||||
CHECK(result.hot == 10);
|
||||
CHECK(result.error == 1);
|
||||
CHECK(result.error == 2);
|
||||
CHECK(result.dead == 1);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user