Files
Jungfraujoch/common/DiffractionSpot.cpp
T
leonarski_fandClaude Opus 5 8f1b0b2281 spot_finding: accumulate spot centroids in integers
The photon-weighted position sums were floats, so the centroid's last bit
depended on the build rather than on the data: gcc contracts the multiply-add
in AddPixel into an FMA under -march=x86-64-v3 and cannot at the baseline,
and MSVC does not contract at all under /fp:precise. The GPU extractor had to
match with __fmaf_rn, and the parity test still needed a two-ulp slack for
hosts that do not fuse.

Column, line and the per-pixel count are all integral, so the sums are exact
in int64 and both implementations reach the same bits with nothing to match.
The parity test now demands exact equality unconditionally and gets it,
including on a baseline build.

ConvertToImageCoordinates keeps the sums integral too: the raw -> image map
is a signed axis swap plus an integer translation, so it is applied to the
sums instead of to the centroid.

Drops the SpotToSave constructor, which had no callers and could not have
been converted without quantising the stored centroid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:03:35 +02:00

105 lines
4.1 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "DiffractionSpot.h"
#include "DiffractionGeometry.h"
#include "RawToConvertedGeometry.h"
DiffractionSpot::DiffractionSpot(uint32_t col, uint32_t line, int64_t in_photons) {
if (in_photons < 0) in_photons = 0;
x = static_cast<int64_t>(col) * in_photons;
y = static_cast<int64_t>(line) * in_photons;
pixel_count = 1;
photons = in_photons;
max_photons = in_photons;
}
DiffractionSpot& DiffractionSpot::operator+=(const DiffractionSpot &other) {
this->x += other.x;
this->y += other.y;
this->photons += other.photons;
this->max_photons = std::max(this->max_photons, other.max_photons);
this->pixel_count += other.pixel_count;
return *this;
}
DiffractionSpot::DiffractionSpot(int64_t x_sum, int64_t y_sum, int64_t in_pixel_count,
int64_t in_photons, int64_t in_max_photons)
: x(x_sum), y(y_sum), pixel_count(in_pixel_count), photons(in_photons), max_photons(in_max_photons) {}
int64_t DiffractionSpot::Count() const {
return photons;
}
int64_t DiffractionSpot::MaxCount() const {
return max_photons;
}
Coord DiffractionSpot::RawCoord() const {
if (photons == 0)
return {0, 0, 0};
// In double: the sums run past what a float mantissa holds on a bright spot, and the centroid is
// wanted to better than the last pixel bit.
return {static_cast<float>(static_cast<double>(x) / static_cast<double>(photons)),
static_cast<float>(static_cast<double>(y) / static_cast<double>(photons)), 0};
}
int64_t DiffractionSpot::PixelCount() const {
return pixel_count;
}
void DiffractionSpot::AddPixel(uint32_t col, uint32_t line, int64_t photons) {
this->x += static_cast<int64_t>(col) * photons;
this->y += static_cast<int64_t>(line) * photons;
this->photons += photons;
this->max_photons = std::max(this->max_photons, photons);
this->pixel_count += 1;
}
void DiffractionSpot::ConvertToImageCoordinates(const DiffractionExperiment &experiment, uint16_t module_number) {
// The raw -> image map is a signed axis swap (module axes are +/-1 along X or Y, never rotated -
// see DetectorGeometryModular::GetDirection) plus an integer translation: the module's origin in
// the assembled image, and the two-pixel multipixel gaps, which depend on where the centroid
// falls. Applied to the SUMS rather than to the centroid, it therefore leaves them exact
// integers; converting the centroid and multiplying it back by the photon count would put the
// rounding this class avoids straight back in.
const Coord centroid = RawCoord();
const Coord fast = experiment.GetModuleFastDirection(module_number);
const Coord slow = experiment.GetModuleSlowDirection(module_number);
// Everything the map adds on top of the axis swap - origin plus gap corrections, an integer.
const Coord shift = RawToConvertedCoordinate(experiment, module_number, centroid)
- fast * centroid.x - slow * centroid.y;
const int64_t x_raw = x, y_raw = y;
x = std::lround(shift.x) * photons + std::lround(fast.x) * x_raw + std::lround(slow.x) * y_raw;
y = std::lround(shift.y) * photons + std::lround(fast.y) * x_raw + std::lround(slow.y) * y_raw;
}
std::optional<SpotToSave> DiffractionSpot::Export(const DiffractionGeometry &geometry, int64_t image_num) const {
if (photons == 0)
return std::nullopt;
const Coord centroid = RawCoord();
auto d = geometry.PxlToRes(centroid.x, centroid.y);
float phi = 0.0f;
if (geometry.GetRotation()) {
// Rotation angle is considered as increment + half wedge.
// It ignores the starting angle.
phi = geometry.GetRotation()->GetAngle_deg(image_num) + geometry.GetRotation()->GetWedge_deg() / 2.0f;
}
return SpotToSave{
.x = centroid.x,
.y = centroid.y,
.phi = phi,
.intensity = static_cast<float>(photons),
.maxc = max_photons,
.lattice = -1,
.image = image_num,
.d_A = d,
.ice_ring = false,
.indexed = false
};
}