Files
Jungfraujoch/image_analysis/spot_finding/StrongPixelSet.h
T
leonarski_fandClaude Opus 5 0af109b838
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m39s
Build Packages / build:windows:nocuda (push) Successful in 17m3s
Build Packages / build:windows:cuda (push) Successful in 19m14s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 19m38s
Build Packages / build:viewer-tgz:cpu (push) Successful in 20m32s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 23m30s
Build Packages / build:viewer-tgz:cuda (push) Successful in 23m43s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 27m8s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m12s
Build Packages / build:rugnux:windows (push) Successful in 10m52s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m27s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 21m13s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 26m49s
Build Packages / build:rpm (rocky9) (push) Successful in 23m36s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 24m9s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / build:rpm (rocky8) (push) Successful in 29m17s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 11m18s
Build Packages / Build documentation (push) Successful in 1m10s
Build Packages / DIALS test (push) Successful in 25m11s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 28m6s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m51s
Build Packages / Unit tests (push) Successful in 1h26m0s
Spot finding: bound how large a spot may be, not how bright
A component of more than 50 connected pixels was discarded. Under the
self-calibrating threshold that is an INTENSITY CEILING, not a size bound: the
threshold is an absolute per-ring contour, so a component's area above it grows
as sigma^2*ln(A/T), without bound in the peak amplitude. Measured on the strong
rotation set, footprints run 3 px at 30-100 counts to 50 px above 10000 - a
slope of 4.4 px per ln(peak) against the fixed local-box test's 0.8, which
saturates at 13 px and never reaches the bound at all. So the brighter a
reflection, the more certainly it was thrown away: 59% of the box finder's
d>3 A spots were missing from the adaptive finder's list, including every one
of its ten strongest, at an intensity ratio of 1.085 for those that did match.
Indexed spots per image collapsed from 220 to 7.

Raise the bound to 200 - CrystFEL peakfinder8's --max-pix-count, the only
directly comparable number in the field; XDS has no such parameter and guards
on shape instead - and ask a component above 50 pixels to be COMPACT: it must
fill a fifth of the square its bounding box fits inside. A Bragg reflection is
round and fills about half of that square however bright it is; an ice arc, a
cosmic-ray track or a lit detector row fills a fifth or less, and those are what
an upper bound was ever protecting against. Below 50 nothing is asked of the
shape, so every component accepted before still is. Integer arithmetic on both
sides, and on the GPU the bounding side fits in what was padding, so the device
struct does not grow.

The shape test is what makes the raise safe. With a flat 200 alone, two battery
crystals moved: one lost a little I/sigma, and the other's de-novo lattice was
NOT MONOTONE in the bound - correct at 50, 100 and 200, wrong at 150 and at 250
and above - so 200 was partly luck. With the shape test both are unchanged to
three decimals.

De novo at defaults on the strong set: indexing rate 0.574 -> 0.804,
completeness 97.3 -> 99.6%, <I/sigma> 3.42 -> 6.07, R_meas 0.299 -> 0.249,
CC1/2 0.947 -> 0.961, ISa 3.29 -> 4.13. The full 37-crystal battery, scored per
shell, is still owed.

Also documents what StrongPixelSet::AddStrongPixel has required since the
component search became linear - pixels in raster order - and puts the existing
test's insertion order into it. Both callers scan a bitmap in ascending flat
index and always satisfied it; the test did not, and was the only thing that
did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBumeJVx4oeXxiBRpkrE5H
2026-08-28 11:49:49 +02:00

52 lines
2.5 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "../../common/DiffractionExperiment.h"
#include "../../common/DiffractionSpot.h"
#include "../../fpga/pcie_driver/jfjoch_fpga.h" // DeviceOutput
#include "SpotFindingSettings.h"
struct strong_pixel {
uint16_t col;
uint16_t line;
int32_t counts;
};
class StrongPixelSet {
std::vector<strong_pixel> pixels;
std::vector<uint32_t> L;
static const constexpr size_t max_strong_pixel_per_module = 4000;
static const constexpr uint32_t xpixel = RAW_MODULE_COLS;
static const constexpr uint32_t ypixel = RAW_MODULE_LINES;
uint32_t strong_pixel_count;
uint32_t find_root(uint32_t e);
uint32_t make_union(uint32_t e1, uint32_t e2);
// Labels the strong pixels and returns the components that pass BOTH the size bound and
// SpotShapeAccepted. min-pix is deliberately not applied here (see FindComponentsImage).
std::vector<DiffractionSpot> sparseccl(const SpotFindingSettings &settings);
public:
void ReadFPGAOutput(const DiffractionExperiment& experiment,
const DeviceOutput& output);
StrongPixelSet();
// Pixels MUST arrive in raster order - line ascending, and column ascending within a line. The
// component search walks the previous line with a forward-only cursor and would miss neighbours
// handed to it out of order. Both callers satisfy this by construction, each scanning a bitmap in
// ascending flat index: ImageSpotFinder::ExtractComponentsHost over the image, ReadFPGAOutput over
// a module (whose row length is a multiple of 32, so no word straddles two lines).
void AddStrongPixel(uint16_t col, uint16_t line, int32_t photons = 1);
void FindSpots(const DiffractionExperiment &experiment, const SpotFindingSettings &settings, std::vector<DiffractionSpot> &spots,
uint16_t module_number);
// Every connected component of at most max-pix pixels. min-pix is deliberately NOT applied: it is
// the only spot setting that changes between the passes of the per-image min-pix search, so one
// connected-component search serves all of them (see ImageSpotFinder::ExtractComponents).
// The caller is responsible for not handing over more than StrongPixelLimit pixels.
void FindComponentsImage(const SpotFindingSettings &settings, std::vector<DiffractionSpot> &spots);
uint32_t GetStrongPixelCount() const;
};