Files
Jungfraujoch/reader/MarCCD.h
T
leonarski_fandClaude Opus 5.5 dfd69fcb91 marCCD frames are widened on the device
The marCCD reader read each frame's 16-bit scanlines into scratch and widened them to int32 on the
worker thread (marccd::ReadInto, ~20 % of the image-loop worker CPU on 4nwv), and the 4-byte image was
uploaded. ReadRawImage now reads the scanlines straight into the image's buffer and hands them over as
a new read-only CompressionAlgorithm::UINT16_WIDENED (mode Int32): two bytes a pixel cross PCIe, from
page-locked memory, and the preprocessing kernel widens them itself.

It is not CompressedImageMode::Uint16, which calls 65535 an error pixel; a marCCD's 65535 is its
saturation marker. So the kernel compares each pixel with int32's error marker and the int32 saturation
cap - the comparisons are made on the pixel's value, in int64, rather than in the storage type - and
decides every pixel exactly as for the int32 image the host would have widened it to. The host widening
(JFJochDecompressPtr) is the CPU path.

Test: ImagePreprocessorGPU_StoredEncodingsMatchCPU, with a frame carrying 65535 at a saturation of
65535, against the CPU preprocessor on the host-widened image.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
2026-10-12 22:37:47 +02:00

71 lines
3.1 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 <string>
#include <vector>
// marCCD: an ordinary uncompressed TIFF whose 3072-byte instrument header sits in the gap between
// the TIFF header and the pixels. The image is read through libtiff, which the build already
// carries; the instrument header is a fixed-offset block of little-endian int32, so it is read
// directly. Rayonix MX-series and mar Mosaic detectors both write it, under half a dozen file
// extensions (.mccd, .img, .tif, or a bare frame number).
//
// Every value the header stores is an integer times a fixed scale: lengths in micrometres or
// nanometres, angles in millidegrees, the wavelength in 1e-5 A. They are converted here, so the
// rest of the code sees metres, degrees and angstroms like every other reader.
namespace marccd {
// Byte offset of the instrument header when the file does not say. Every writer seen puts it here
// and states it in TIFF tag 34710 as well.
inline constexpr uint32_t DEFAULT_HEADER_OFFSET = 1024;
// The private TIFF tag in which marCCD states where its own header begins.
inline constexpr uint16_t TIFFTAG_MARCCD_HEADER_OFFSET = 34710;
struct Header {
std::string detector; // from the TIFF Model/Software tags where they say anything
int64_t nx = 0; // fast dimension (columns)
int64_t ny = 0; // slow dimension (rows)
double pixel_x_m = 0;
double pixel_y_m = 0;
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;
int64_t saturated_value = 0;
std::string axis_name = "phi"; // the circle that moved between start and end
};
// True if the path names something this reader can open: a marCCD file, or a directory holding at
// least one. Reads a few kB at most.
bool CanRead(const std::string &path);
// The files of one sweep, in collection order. path is a directory, or one frame inside the sweep
// to take the whole sweep from. Empty where there is none.
std::vector<std::string> CollectSweep(const std::string &path);
// Header only - reads the first few kB of the file. Cheap enough to call per frame for the
// rotation angles.
Header ReadHeader(const std::string &path);
// Header + pixels of one file. out is resized to nx * ny.
Header Read(const std::string &path, std::vector<int32_t> &out);
// Header + the stored unsigned 16-bit pixels, as the file holds them, into memory the caller already
// has. Throws if the image does not fit in capacity pixels.
Header ReadRaw(const std::string &path, uint16_t *out, size_t capacity);
// The same, widening the stored uint16 into memory the caller already has. scratch carries the
// 16-bit strip between frames, so a worker walking a sweep allocates once. 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);
} // namespace marccd