Record the corrections a DECTRIS detector applied; read multichannel NXmx

The broker rebuilds the outgoing StartMessage from DiffractionExperiment,
and FillMessage hard-coded countrate_correction_enabled and
flatfield_enabled to false. JFJochReceiverLite parsed the true values from
the detector's stream2 start message and dropped them, so every DECTRIS
file written through the broker said neither correction was applied -
DECTRIS enables both by default. pixel_mask_applied had the same defect:
it reported the local apply_mask setting (an FPGA feature), not what the
detector did to the pixels, which ReceiverLite forwards byte for byte.

These now come from the stream: DetectorSetup carries them, ReceiverLite
copies them in Configure, FillMessage reads them. Two more fields the
DECTRIS stream sends and NXmx defines are passed through to the master
file: countrate_correction_lookup_table (uint32, possibly bslz4/bszstd
compressed in the stream) and virtual_pixel_interpolation_applied. The
flatfield is deliberately not written - it makes the master file too
large. PSI EIGER is unchanged: jfjoch never enables rate correction and
the detector server starts with it off.

The reader also opens the DECTRIS "hdf5 nexus v2024.2 nxmx" layout, where
/entry/data/data is 4D [image, channel, y, x] and the pixel mask is kept
per channel. Only the first channel is read; this is compatibility, not
full multichannel support. A _process.h5 made from such a file links its
pictures to that channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
This commit is contained in:
2026-09-23 21:04:16 +02:00
co-authored by Claude Opus 5.5
parent 0252880e06
commit aea13044b5
18 changed files with 397 additions and 31 deletions
+15 -7
View File
@@ -4,6 +4,8 @@
#include "HDF5ImageSource.h"
#include "../common/JFJochException.h"
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#else
@@ -97,22 +99,26 @@ HDF5ImageSource::GetDataset(const HDF5ImageLocator::Location &loc) const {
HDF5DataType datatype(*entry.dataset);
HDF5Dcpl dcpl(*entry.dataset);
if (dataspace.GetNumOfDimensions() != 3)
const auto rank = dataspace.GetNumOfDimensions();
if (rank != 3 && rank != 4)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
loc.dataset + " dataset must be 3D");
loc.dataset + " dataset must be 3D or 4D");
entry.multichannel = (rank == 4);
if (datatype.IsFloat())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Float datasets not supported at this time");
auto dim = dataspace.GetDimensions();
entry.height = dim[1];
entry.width = dim[2];
entry.height = dim[rank - 2];
entry.width = dim[rank - 1];
entry.mode = CalcImageMode(datatype.GetElemSize(), datatype.IsFloat(), datatype.IsSigned());
// One chunk per image: [1, h, w], or [1, 1, h, w] for a multichannel dataset
auto chunk_size = dcpl.GetChunking();
entry.direct_chunk = (chunk_size.size() == 3) && (chunk_size[0] == 1)
&& (chunk_size[1] == dim[1]) && (chunk_size[2] == dim[2]);
entry.direct_chunk = (chunk_size.size() == rank)
&& std::all_of(chunk_size.begin(), chunk_size.end() - 2, [](hsize_t c) { return c == 1; })
&& (chunk_size[rank - 2] == entry.height) && (chunk_size[rank - 1] == entry.width);
if (entry.direct_chunk)
entry.algorithm = dcpl.GetCompression();
@@ -155,7 +161,9 @@ HDF5ImageSource::PrepareDirectRead(const HDF5ImageLocator::Location &loc) const
if (!ds.raw)
return {};
const hsize_t coord[3] = {static_cast<hsize_t>(loc.local_index), 0, 0};
const hsize_t coord_3d[3] = {loc.local_index, 0, 0};
const hsize_t coord_4d[4] = {loc.local_index, loc.channel, 0, 0};
const hsize_t *coord = ds.multichannel ? coord_4d : coord_3d;
unsigned filter_mask = 0;
haddr_t address = HADDR_UNDEF;
hsize_t size = 0;