Files
Jungfraujoch/reader/SMV.h
T
leonarski_f 8261b7354e
Build Packages / Create release (push) Successful in 9s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m8s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 11m12s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 16m43s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 20m56s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 22m4s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 26m57s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 28m24s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 27m58s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 29m29s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 18m23s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m55s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 27m20s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 29m27s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 32m41s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 34m46s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 37m25s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 35m56s
Build Packages / Unit tests (push) Successful in 2h8m44s
Build Packages / Validation Battery (push) Successful in 33m35s
v1.0.0-rc.176 (#86)
* jfjoch_broker: Optional live Rugnux analysis of DECTRIS rotation collections inside the broker, with merged data, maps and the placed model written beside the master file, the rotation lattice in the scan result, and a "Rugnux live" tab and dashboard row in the web frontend.
* Rugnux: Faster processing, with molecular replacement and model validation spread over several GPUs and run on the Apple GPU on macOS.
* Rugnux: The polarization stated in miniCBF and d*TREK headers is used, twinned crystals' space groups are decided more reliably, and a run that finds no lattice still reports every image.

Reviewed-on: #86
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-19 07:03:28 +02:00

97 lines
4.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 <array>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <vector>
// SMV: the format ADSC Quantum detectors wrote, and which Rayonix and several others still write.
// A plain ASCII header of "KEY=value;" lines between braces, then the pixels - no container, no
// compression, no binary header to decode by offset. The header states its own length, so even
// that is not assumed.
//
// {
// HEADER_BYTES= 512; DIM=2; BYTE_ORDER=little_endian; TYPE=unsigned_short;
// SIZE1=3072; SIZE2=3072; PIXEL_SIZE=0.102588; DISTANCE=250.000000;
// OSC_START=325.000000; OSC_RANGE=0.200000; WAVELENGTH=0.999839;
// BEAM_CENTER_X=157.500000; BEAM_CENTER_Y=157.500000;
// }
//
// Note the beam centre is in MILLIMETRES, not pixels, and its convention is the one thing writers
// disagree about - see the comment on beam_x_mm in the struct.
//
// Rigaku's d*TREK writes the same container with a different vocabulary: the detector and the
// goniometer as named circles with their vectors (CCD_GONIO_VALUES, ROTATION_VECTOR, ...), the beam
// position in pixels, and its own overflow encoding for 16-bit pixels. A header naming
// DETECTOR_NAMES is read that way.
namespace smv {
struct Header {
int64_t nx = 0; // SIZE1, the fast dimension
int64_t ny = 0; // SIZE2, the slow dimension
size_t data_offset = 0; // HEADER_BYTES
bool little_endian = true;
int64_t bytes_per_pixel = 2; // TYPE: unsigned_short
double pixel_x_m = 0;
double pixel_y_m = 0;
double distance_m = 0;
// BEAM_CENTER_X/Y, converted to pixels here. Writers disagree about this field more than about
// any other: it is in millimetres, and which of the two is the fast direction is a convention
// rather than a rule. Where a file also carries the ADSC-era synonyms (BEAM_CENTRE_X, or the
// MOSFLM-ordered pair) they are read too. A run whose centre is wrong is recoverable - the
// beam-centre estimator measures it from the data - but a wrong one is not detectable here.
double beam_x_px = 0;
double beam_y_px = 0;
double wavelength_A = 0;
double start_angle_deg = 0; // OSC_START, falling back to PHI
double angle_increment_deg = 0; // OSC_RANGE
double two_theta_deg = 0;
double exposure_s = 0;
int64_t saturation = 0; // CCD_IMAGE_SATURATION: the value a saturated pixel carries
std::string detector; // DETECTOR_SN, where stated
std::string axis_name = "phi";
// d*TREK only (Rigaku CrystalClear): the geometry is stated as vectors rather than the ADSC
// scalars, in the imgCIF laboratory frame - z from the sample to the source, y up. Absent on an
// ADSC header, where two_theta_deg above carries the one detector circle there is.
std::optional<std::array<double, 3>> fast_direction;
std::optional<std::array<double, 3>> slow_direction;
std::optional<std::array<double, 3>> spindle_axis;
struct Circle {
std::array<double, 3> axis;
double angle_deg;
};
std::vector<Circle> detector_circles; // header order: the outermost circle first
// SOURCE_POLARZ: the fraction of the beam polarized in a plane, and that plane's normal (the
// order XDS states FRACTION_OF_POLARIZATION and POLARIZATION_PLANE_NORMAL in). 0.5 is unpolarized.
std::optional<double> polarization_fraction;
std::optional<std::array<double, 3>> polarization_normal;
// RAXIS_COMPRESSION_RATIO: a stored value above 32767 is (value - 32768) * ratio. 0 = not used.
int64_t overflow_ratio = 0;
std::map<std::string, std::string> raw; // every key, for anything not modelled above
};
// True if the path names something this reader can open: an SMV file, or a directory holding at
// least one. Reads a few hundred bytes at most.
bool CanRead(const std::string &path);
// The files of one sweep, in collection order. path is a directory, or one frame inside it.
std::vector<std::string> CollectSweep(const std::string &path);
// Header only - reads just the leading brace block. Cheap enough to call per frame.
Header ReadHeader(const std::string &path);
// Header + pixels. out is resized to nx * ny.
Header Read(const std::string &path, std::vector<int32_t> &out);
// The same, widening the stored pixels into memory the caller already has. scratch carries the
// raw bytes between frames so a worker walking a sweep allocates once - on Windows only; elsewhere the
// file is mapped and scratch is not used.
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch);
} // namespace smv