beam centre: the FFT captures the whole detector, the walk refines what it captured

This commit is contained in:
leonarski_f committed 2026-09-13 07:37:05 +02:00
1 parent 1d1d7fd686
commit d75102b9b4
3 files changed
+106 -41

No files matched your search

@@ -82,7 +82,8 @@ float median_of(std::vector<float> &v) {
std::optional<BeamCenterEstimate>
FindBeamCenterFromBackground(const DiffractionExperiment &experiment, const PixelMask &mask,
const std::vector<float> &mean, size_t nthreads) {
const std::vector<float> &mean, size_t nthreads,
std::optional<std::pair<float, float>> start) {
if (nthreads == 0)
nthreads = std::max(1u, std::thread::hardware_concurrency());
const auto W = static_cast<int>(experiment.GetXPixelsNumConv());
@@ -106,8 +107,8 @@ FindBeamCenterFromBackground(const DiffractionExperiment &experiment, const Pixe
const float pixel_size = geom.GetPixelSize_mm();
const float distance = geom.GetDetectorDistance_mm();
float beam_x = geom.GetBeamX_pxl();
float beam_y = geom.GetBeamY_pxl();
float beam_x = start ? start->first : geom.GetBeamX_pxl();
float beam_y = start ? start->second : geom.GetBeamY_pxl();
constexpr int n_cells = RADIAL_BINS * SECTORS;
std::vector<int32_t> cell_of(n_pixels);
@@ -359,3 +360,38 @@ FindBeamCenterFromBackground(const DiffractionExperiment &experiment, const Pixe
return BeamCenterEstimate{beam_x, beam_y,
std::max({sigma_x, sigma_y, std::hypot(step_x, step_y)})};
}
std::optional<BeamCenterEstimate>
FindBeamCenter(const DiffractionExperiment &experiment, const PixelMask &mask,
const std::vector<float> &mean, size_t nthreads, BeamCenterFFTResult *capture) {
// The image the capture scores: the projection with everything the mask excludes taken out.
// The beam stop is in that mask by the time this runs, and it is the part that matters - an
// opaque region on one side of the beam is centrosymmetric about ITS own centre, and where it
// is large enough that preference beats the background's.
std::vector<float> scored = mean;
const auto &pixel_mask = mask.GetMask(experiment);
if (pixel_mask.size() == scored.size())
for (size_t i = 0; i < scored.size(); i++)
if (pixel_mask[i] != 0)
scored[i] = NAN;
const BeamCenterFFTResult fft = FindBeamCenterFFT(experiment, scored);
if (capture)
*capture = fft;
if (fft.point.empty())
return FindBeamCenterFromBackground(experiment, mask, mean, nthreads);
const std::pair<float, float> seed{fft.point[0].beam_x_pxl, fft.point[0].beam_y_pxl};
if (auto refined = FindBeamCenterFromBackground(experiment, mask, mean, nthreads, seed))
return refined;
// The walk has nothing to fit AT THE CAPTURE. That is a statement about the capture as much as
// about the background: a centre far enough out leaves too few fully covered rings inside the
// fitted band for the profile to exist at all. Measured on one small-molecule set whose capture
// lands 536 px out - the walk from the file's centre answers there to 2.9 px, and taking the
// capture instead would have been the composition's only regression on the corpus. So ask the
// walk again where it used to be asked, and keep the capture only where neither start works.
if (auto from_file = FindBeamCenterFromBackground(experiment, mask, mean, nthreads))
return from_file;
return BeamCenterEstimate{seed.first, seed.second, BEAM_CENTER_CAPTURE_SIGMA_PXL};
}