Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m56s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m10s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 13m51s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 16m56s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 20m59s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m25s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 21m17s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 14m14s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 25m48s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 15m52s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m33s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m49s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 25m31s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 25m24s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 26m5s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 22m19s
Build Packages / Unit tests (push) Failing after 2h28m24s
Build Packages / Validation Battery (push) Successful in 40m33s
* Rugnux: Processes a collection live while jfjoch_broker writes it (`rugnux <master.h5> <broker URL>`). * Rugnux: Processing starts on the first images and confirms its choices once all are in, so analysis can begin during collection. * Rugnux: Faster processing, especially on servers with several GPUs and CPU sockets and on computers without a GPU (`--no-gpu`, macOS). * Rugnux: Uses much less GPU memory, so large data sets stay on the GPU of smaller cards, and a data set too large for the card is processed on the CPU instead. * Rugnux: On macOS, Bragg integration runs on the Apple GPU. * Rugnux: Better handling of twinning and pseudo-symmetry, with the twin hypothesis written as `<prefix>_twin_<SG>.mtz`, and a wrong `--model` no longer confirms the data or decides the hand. * Rugnux: `--model` accepts predicted (AlphaFold) models and falls back to molecular replacement when the model is not placed as a rigid body (experimental `--mr` forces it), settling space group, hand and twinning and writing a different answer as `<prefix>_mr.mtz`. * Rugnux: Molecular replacement is faster and settles the space group in more cases with several copies in the asymmetric unit. * Rugnux: Deposition-ready mmCIF, a richer report, and numbered steps on the terminal with the full log in `<prefix>_log.txt`; `_P1.mtz`, `--estimate-beam-center` and `--beam-center-search` are removed. * Rugnux: The beam-stop shadow is found correctly on tilted detectors. * Rugnux: Stills files that record a stationary goniometer axis get the default stills geometry refinement. * jfjoch_viewer: "Analyze dataset" uses the same defaults as a bare `rugnux <file>`, and shows the results of processed CBF, marCCD and SMV sweeps. * jfjoch_viewer: The Files panel refreshes on F5 and every few seconds, and the plot type and other selections stay put during live collection. * jfjoch_writer: Memory stays bounded and old data files are closed efficiently on long runs, any failure ends the run in an error state the broker is told about, and the master file appears only after its data files; the ZeroMQ pusher logs when a stalled writer makes it drop images. * Fixes: The TCP image pusher no longer blocks the broker's receiver, GPU out-of-memory failures stop with a clear message, and the XDS plugin masks module and chip edges. --------- Co-authored-by: Filip Leonarski <jungfrau@filips-mac-mini.home> Reviewed-on: #85 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
103 lines
5.5 KiB
C++
103 lines
5.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 <array>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../common/CompressedImage.h"
|
|
|
|
// PILATUS miniCBF: an ASCII header, four separator bytes, then one byte-offset compressed image.
|
|
// No CIF parser and no libcbf are needed - every value is on a "# " comment line or a MIME line.
|
|
namespace minicbf {
|
|
|
|
// The four bytes that end the MIME header and begin the binary section.
|
|
inline constexpr unsigned char BINARY_SEPARATOR[4] = {0x0c, 0x1a, 0x04, 0xd5};
|
|
|
|
struct Header {
|
|
std::string detector; // "PILATUS3 6M, S/N 60-0136"
|
|
int64_t nx = 0; // fast dimension (columns)
|
|
int64_t ny = 0; // slow dimension (rows)
|
|
int64_t nelem = 0; // pixel count declared by the MIME header
|
|
double pixel_x_m = 0;
|
|
double pixel_y_m = 0;
|
|
double thickness_m = 0;
|
|
std::string material = "Si"; // NORMALISED: the file says "Silicon", the rest of the code wants "Si"
|
|
double distance_m = 0;
|
|
double beam_x_px = 0;
|
|
double beam_y_px = 0;
|
|
double wavelength_A = 0;
|
|
double start_angle_deg = 0;
|
|
double angle_increment_deg = 0;
|
|
double two_theta_deg = 0;
|
|
// Goniometer head angles and the per-image increment of each, in degrees. A writer spells "this
|
|
// head has no such axis" as -9999, which is a sentinel and not an angle, so an axis the header
|
|
// does not really carry is absent here rather than 9999 degrees away from zero.
|
|
std::optional<double> chi_deg;
|
|
std::optional<double> omega_deg;
|
|
double chi_increment_deg = 0;
|
|
double phi_increment_deg = 0;
|
|
double omega_increment_deg = 0;
|
|
double exposure_s = 0;
|
|
double period_s = 0;
|
|
int64_t count_cutoff = 0; // saturation
|
|
std::string axis_name = "omega";
|
|
bool byte_offset = false; // the only conversion supported
|
|
|
|
// The imgCIF axis table the CBF template block carries after the "# " lines, as the file states
|
|
// it: vectors in the imgCIF laboratory frame (Z from the sample towards the source, Y opposite
|
|
// gravity, X completing a right-handed set). Turning them into any other frame is the caller's
|
|
// business, not this format reader's. Most headers carry no such table and leave all of these
|
|
// empty, which is not the same as their stating the usual mounting.
|
|
std::optional<std::array<double, 3>> fast_direction; // laboratory direction of a +1 column step
|
|
std::optional<std::array<double, 3>> slow_direction; // ... of a +1 row step
|
|
std::optional<std::array<double, 3>> spindle_axis; // the base goniometer rotation axis
|
|
// The goniometer rotation axis carried by the base one, stated in the base axis' own frame. A
|
|
// head whose inner circle is INCLINED - a fixed-chi stage, a kappa arm - says so only here: its
|
|
// "# Chi" and "# Kappa" lines read zero because no such circle is driven. Empty where the table
|
|
// states one rotation axis only, which is every Eulerian head.
|
|
std::optional<std::array<double, 3>> inner_spindle_axis;
|
|
std::optional<std::array<double, 3>> detector_axis; // the detector's own rotation axis: a 2theta arm
|
|
// "# Oscillation_axis X.CW +SLOW". Some writers say which of the two image directions the spindle
|
|
// runs along instead of stating a vector. The axis NAME on that line is a poor discriminator (see
|
|
// ScansPhi, and note that the one header carrying both a name and a table says "X" where its table
|
|
// says Y) but this token is not: where both are present they agree.
|
|
bool spindle_along_slow = false;
|
|
};
|
|
|
|
// Whether the sweep turns PHI rather than the base (omega) axis. What moved is the axis with a
|
|
// non-zero increment - a header routinely carries a "# Phi" angle for a phi that stands still - so
|
|
// the name is consulted only when no increment is stated at all. It is a poor discriminator: some
|
|
// writers put a direction convention there ("X.CW") instead of an axis name.
|
|
bool ScansPhi(const Header &h);
|
|
|
|
// Byte offset of the binary section (just past the separator), or nothing if there is none.
|
|
std::optional<size_t> FindBinarySection(const uint8_t *data, size_t size);
|
|
|
|
// Parse the ASCII header. Pass the bytes BEFORE the separator; headers are not a fixed size (one
|
|
// Diamond I24 set carries 6335 bytes, well past a 4 kB guess), so never parse a fixed prefix.
|
|
Header ParseHeader(const char *data, size_t size);
|
|
|
|
// Header + pixels of one file, read from disk.
|
|
Header Read(const std::string &path, std::vector<int32_t> &out);
|
|
|
|
// The same, decoding into memory the caller already has (one read of the file, no extra copy).
|
|
// The compressed file is read through scratch, which the caller keeps between frames: a fresh
|
|
// vector per image is megabytes of allocation and of zeroing nothing ever reads.
|
|
// Throws if the image does not fit in capacity pixels.
|
|
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch);
|
|
|
|
// The whole file read into buf, header parsed and checked, without decoding the pixels: the byte-offset
|
|
// stream starts at binary_start. For a consumer that decodes it itself - on the device, where there is one.
|
|
Header ReadCompressed(const std::string &path, RawByteBuffer &buf, size_t &binary_start);
|
|
|
|
// Header only - reads just enough of the file to reach the separator. Cheap enough to call per
|
|
// frame for the rotation angles.
|
|
Header ReadHeader(const std::string &path);
|
|
|
|
} // namespace minicbf
|