Integration: the shadow-clip test reads only each row's span of shadow pixels
DropShadowClipped scanned the whole signal disk of every predicted reflection in the mask, ~10% of all cycles of a rotation run. The analysis now takes, once per object, the first and last beam-stop pixel of each mask row (ShadowRows), and the test reads only that part of the disk's rows; no shadow pixel lies outside it, so the same reflections are dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqXYdL4qBGYRiSXSGXfCLu
This commit is contained in:
1 parent
79a80f086c
commit
c76304d899
3 files changed
+20
-5
No files matched your search
@@ -48,6 +48,8 @@ MXAnalysisWithoutFPGA::MXAnalysisWithoutFPGA(const DiffractionExperiment &in_exp
|
||||
indexer(in_indexer),
|
||||
prediction(CreateBraggPrediction(experiment.IsRotationIndexing())),
|
||||
mask(in_mask),
|
||||
shadow_rows(ShadowRows(in_mask.GetMask(), static_cast<int>(in_experiment.GetXPixelsNumConv()),
|
||||
static_cast<int>(in_experiment.GetYPixelsNumConv()))),
|
||||
mask_high_res(-1),
|
||||
mask_low_res(-1) {
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
@@ -535,7 +537,7 @@ std::vector<Reflection> MXAnalysisWithoutFPGA::IntegrateOnImage(const std::vecto
|
||||
size_t npredicted, int64_t image_number) {
|
||||
const auto &modules = experiment.GetModules();
|
||||
const auto drop_shadow_clipped = [this](std::vector<Reflection> &reflections) {
|
||||
DropShadowClipped(reflections, mask.GetMask(), static_cast<int>(experiment.GetXPixelsNumConv()),
|
||||
DropShadowClipped(reflections, mask.GetMask(), shadow_rows, static_cast<int>(experiment.GetXPixelsNumConv()),
|
||||
static_cast<int>(experiment.GetYPixelsNumConv()), experiment.GetBraggIntegrationSettings().GetR1());
|
||||
};
|
||||
if (modules.empty()) {
|
||||
@@ -584,15 +586,25 @@ std::vector<Reflection> MXAnalysisWithoutFPGA::IntegrateOnImage(const std::vecto
|
||||
// does not end where the background's does, so the rest of such a disk is attenuated too. Measured against
|
||||
// their symmetry mates, these observations came out 12-60 % low on the sweeps whose shadow edge crosses
|
||||
// busy low-resolution reflections (CCD and pixel detectors alike), and level with them elsewhere.
|
||||
std::vector<std::pair<int, int>> ShadowRows(const std::vector<uint32_t> &mask, int W, int H) {
|
||||
std::vector<std::pair<int, int>> rows(H, {W, -1});
|
||||
for (int y = 0; y < H; y++)
|
||||
for (int x = 0; x < W; x++)
|
||||
if (mask[static_cast<size_t>(y) * W + x] & (1u << PixelMask::BeamStopPixelBit))
|
||||
rows[y] = {std::min(rows[y].first, x), x};
|
||||
return rows;
|
||||
}
|
||||
|
||||
void DropShadowClipped(std::vector<Reflection> &reflections, const std::vector<uint32_t> &mask,
|
||||
int W, int H, float r1) {
|
||||
const std::vector<std::pair<int, int>> &shadow_rows, int W, int H, float r1) {
|
||||
std::erase_if(reflections, [&](const Reflection &r) {
|
||||
const int x0 = std::max(0, static_cast<int>(std::floor(r.predicted_x - r1)));
|
||||
const int x1 = std::min(W - 1, static_cast<int>(std::ceil(r.predicted_x + r1)));
|
||||
const int y0 = std::max(0, static_cast<int>(std::floor(r.predicted_y - r1)));
|
||||
const int y1 = std::min(H - 1, static_cast<int>(std::ceil(r.predicted_y + r1)));
|
||||
for (int y = y0; y <= y1; y++)
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
// Only the part of the row between its first and last shadow pixel can hold one.
|
||||
for (int x = std::max(x0, shadow_rows[y].first); x <= std::min(x1, shadow_rows[y].second); x++) {
|
||||
const float dx = static_cast<float>(x) - r.predicted_x, dy = static_cast<float>(y) - r.predicted_y;
|
||||
if (dx * dx + dy * dy < r1 * r1 && (mask[static_cast<size_t>(y) * W + x] & (1u << PixelMask::BeamStopPixelBit)))
|
||||
return true;
|
||||
|
||||
@@ -29,8 +29,10 @@ class AdaptiveSpotFinderGPU;
|
||||
// Leave out the reflections whose signal disk - radius r1 about the predicted position, in image
|
||||
// coordinates - reaches a pixel of the beam-stop shadow (PixelMask::BeamStopPixelBit of `mask`, a
|
||||
// width x height image).
|
||||
// `shadow_rows` is ShadowRows of the same mask: per row, the first and last x of a shadow pixel.
|
||||
void DropShadowClipped(std::vector<Reflection> &reflections, const std::vector<uint32_t> &mask,
|
||||
int width, int height, float r1);
|
||||
const std::vector<std::pair<int, int>> &shadow_rows, int width, int height, float r1);
|
||||
std::vector<std::pair<int, int>> ShadowRows(const std::vector<uint32_t> &mask, int width, int height);
|
||||
|
||||
// MXAnalysisWithoutFPGA is not thread safe - it has to owned by a single thread
|
||||
class MXAnalysisWithoutFPGA {
|
||||
@@ -81,6 +83,7 @@ class MXAnalysisWithoutFPGA {
|
||||
bool count_crowding = false;
|
||||
std::unique_ptr<ImagePreprocessorBuffer> preprocessor_buffer;
|
||||
const PixelMask &mask;
|
||||
const std::vector<std::pair<int, int>> shadow_rows; // ShadowRows of mask
|
||||
|
||||
// Decompress the image into decompression_buffer (or read it straight from the message, when it is
|
||||
// not compressed) and return where it landed.
|
||||
|
||||
@@ -565,7 +565,7 @@ TEST_CASE("DropShadowClipped_SignalDiskTouchingTheShadow", "[Integration][portab
|
||||
at(33.9f, 5.0f), // 3.9 px on the other side: dropped
|
||||
at(34.0f, 5.0f), // 4 px, the disk's own edge: kept
|
||||
at(1.0f, 1.0f)}; // near the image corner: kept
|
||||
DropShadowClipped(refl, mask, W, H, r1);
|
||||
DropShadowClipped(refl, mask, ShadowRows(mask, W, H), W, H, r1);
|
||||
REQUIRE(refl.size() == 4);
|
||||
CHECK(refl[0].predicted_x == 10.0f);
|
||||
CHECK(refl[1].predicted_x == 25.0f);
|
||||
|
||||
Reference in new issue
Block a user