Files
Jungfraujoch/reader/DetectorLayout.h
T
leonarski_fandClaude Opus 5.5 d345deaecd XFELReader: per-frame mask and photon conversion without a library call or a branch per pixel
On a deflated Cheetah CXI with a per-frame mask (AGIPD 1M), half of the reader's time after the inflate
went to turning the stored frame into the image: the mask plane widened into a zeroed uint32 copy before
being tested, MarkBad testing it with a branch per pixel through a reference the store could alias, and
the photon conversion calling lroundf and branching on the mask, the finiteness and the clamp. Now the
mask is tested as stored (uint16 or uint32), and both loops are written so that the compiler vectorises
them: std::lround spelled out exactly (truncate, the exact fraction, a half or more away from zero) and
the selects done as arithmetic (Pick). Plain C++, no intrinsics.

Bit-identical: FNV hash of every image handed out (2000 frames) plus the pixel mask, before and after,
on a CXI with a CrystFEL geometry (with and without a gain), a panel-table HDF5 (with and without a
gain) and a bitshuffle/LZ4 int16 set: all identical. Reader alone, one thread: 14.1 -> 7.0 ms per
frame. rugnux on the 14,445-frame CXI run (16 workers, A/B back to back): geometry refinement sample
11.4 -> 8.9 s, image pass 27.6 -> 21.1 s, whole run 67.4 -> 58.2 s; geometry and refinement log lines
identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
2026-10-23 10:47:56 +02:00

82 lines
3.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
// Where the panels of a stills detector lie and what their pixels are worth, as the XFEL readers
// gather it from a file's own metadata (or, while it is still read, a CrystFEL geometry file): the
// panels with their pixel ranges in the stored frame, fast/slow-scan directions and corners, the camera
// length, the pixel size, the gain and the regions to leave out. The conventions are CrystFEL's - a
// corner is the very corner of the panel's first pixel, x and y in pixels in the detector plane.
//
// Lengths are in pixels, except the camera length, which is held in metres. A value taken from the
// image files (a camera length or photon energy stated per shot) is kept as its path, with the factor
// that turns the stored number into the unit held here.
namespace xfel {
// A number stated in the layout, or a path to it in the image file, times scale.
struct Value {
std::string path; // empty: value holds the number
double value = 0;
double scale = 1; // what one stored unit at path is worth
};
struct Panel {
std::string name;
int64_t min_fs = 0, max_fs = 0, min_ss = 0, max_ss = 0; // inclusive, in the stored frame
double fs_x = 1, fs_y = 0, ss_x = 0, ss_y = 1;
double corner_x = 0, corner_y = 0;
double res = 0; // pixels per metre
std::optional<Value> clen; // metres
double coffset = 0; // metres
std::optional<double> adu_per_eV;
std::optional<double> adu_per_photon;
std::optional<double> max_adu;
int64_t mask_edge_pixels = 0;
bool no_index = false;
};
// A region to leave out: either a pixel range on one panel (or, in the old syntax, on the whole
// frame), or a box in the laboratory plane, in pixels.
struct BadRegion {
std::string panel; // empty: fs/ss are whole-frame coordinates
bool in_lab = false;
double min_x = 0, max_x = 0, min_y = 0, max_y = 0;
int64_t min_fs = 0, max_fs = 0, min_ss = 0, max_ss = 0;
};
// A mask array: a pixel is bad where it has any of bad's bits set, or lacks any of good's - or, with
// nonzero_good, where the array is zero.
struct Mask {
std::string data; // HDF5 path; a % in it stands for the shot group, as in the data path
std::string file; // file holding it, or empty for the image file itself
uint32_t good = 0, bad = 0;
bool nonzero_good = false;
std::string panel; // empty: every panel; otherwise only this panel's pixels
bool operator==(const Mask &) const = default;
// Whether a pixel the mask reads as v is bad, given the mask's good, bad and nonzero_good (passed rather than
// read from the mask, so that a loop over the pixels keeps them in registers). Each term is false where its
// field is unset; bitwise, so that the loop has no branch.
[[nodiscard]] static bool Bad(uint32_t v, uint32_t good, uint32_t bad, bool nonzero_good) {
return ((v & bad) != 0) | ((v & good) != good) | (nonzero_good & (v == 0));
}
};
struct Layout {
std::vector<Panel> panels;
std::vector<BadRegion> bad;
std::vector<Mask> masks;
std::string data = "/data/data";
std::optional<Value> photon_energy; // eV
std::optional<Value> wavelength; // Angstrom; used where no photon_energy is stated
};
} // namespace xfel