Most facilities still archive rotation data as a directory of miniCBF frames, which until now had to be converted to HDF5 before rugnux could see it. Nothing in that format needs a CIF parser or a library: it is an ASCII header, four separator bytes, then one byte-offset compressed image, and every value the reader wants sits on a "# " comment line or a MIME line. MiniCBF holds the format itself - header parse and the byte-offset decoder, which is a running value with deltas stored smallest-container-first. Verified byte-exact against dxtbx on PILATUS 6M, 6M-F, 300K, silicon and CdTe sensors, and three sensor thicknesses. JFJochCBFReader is a sibling of JFJochHDF5Reader under the JFJochReader base. NAMING ANY FRAME READS ITS WHOLE SWEEP: the sweep is identified by the template (prefix + digit count) the named frame belongs to, not by "every .cbf in the directory", so a directory holding two sweeps does not splice two crystals together. Naming a directory takes the sweep with the most frames in it. Images decode on demand, one per call, so any number of workers can read at once - there is no global lock as there is on the HDF5 path, HDF5 not being thread-safe. A raw CBF carries no analysis results, so the dataset it builds is the geometry, the mask and nothing else, exactly as a plain DECTRIS file with no /entry/MX gives. Two header quirks are handled because real files have them: the sensor material is written "Silicon" where the rest of the code compares against "CdTe", and the thickness unit is sometimes omitted. Headers are not a fixed size either - one set carries 6335 bytes - so the parse runs to the binary separator rather than over a fixed prefix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
65 lines
2.7 KiB
C++
65 lines
2.7 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>
|
|
|
|
// 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;
|
|
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
|
|
};
|
|
|
|
// 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);
|
|
|
|
// x-CBF_BYTE_OFFSET -> int32. Deltas against a running value, smallest container first: int8,
|
|
// escaping to int16 via -128, to int32 via -32768, to int64 via INT32_MIN. Little-endian, packed.
|
|
// Throws if the stream ends before n_pixels are produced. out must hold n_pixels.
|
|
void DecodeByteOffset(const uint8_t *data, size_t size, int32_t *out, size_t n_pixels);
|
|
|
|
// 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).
|
|
// Throws if the image does not fit in capacity pixels.
|
|
Header ReadInto(const std::string &path, int32_t *out, size_t capacity);
|
|
|
|
// 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
|