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:
leonarski_fandClaude Opus 5.5 committed 2026-09-23 21:04:16 +02:00
1 parent 0252880e06
commit aea13044b5
18 files changed
+397 -31

No files matched your search

+22 -6
View File
@@ -12,7 +12,8 @@ namespace {
const std::string &dataset,
uint64_t source_first_image,
uint64_t virtual_first_image,
uint64_t image_count) {
uint64_t image_count,
std::optional<uint64_t> source_channel = {}) {
if (image_count == 0)
return;
@@ -20,6 +21,7 @@ namespace {
auto &last = ret.back();
if (last.filename == filename
&& last.dataset == dataset
&& last.source_channel == source_channel
&& last.source_first_image + last.image_count == source_first_image
&& last.virtual_first_image + last.image_count == virtual_first_image) {
last.image_count += image_count;
@@ -32,9 +34,22 @@ namespace {
.dataset = dataset,
.source_first_image = source_first_image,
.virtual_first_image = virtual_first_image,
.image_count = image_count
.image_count = image_count,
.source_channel = source_channel
});
}
// A 4D mapping, [image, channel, y, x], may cover only some of the channels; the reader wants
// the first one.
bool CoversFirstChannel(const HDF5VirtualDatasetMapping &mapping) {
return mapping.virtual_start.size() != 4 || mapping.virtual_start[1] == 0;
}
std::optional<hsize_t> SourceChannel(const HDF5VirtualDatasetMapping &mapping) {
if (mapping.source_start.size() == 4)
return mapping.source_start[1];
return {};
}
}
void HDF5ImageLocator::Configure(Layout layout) {
@@ -71,10 +86,10 @@ HDF5ImageLocator::Location HDF5ImageLocator::Resolve(int64_t global_image) const
&& layout_.data_layout == HDF5DataSetLayout::VIRTUAL) {
const auto image = static_cast<hsize_t>(global_image);
for (const auto &mapping: layout_.vds_mappings) {
if (!mapping.ContainsVirtualImage(image))
if (!CoversFirstChannel(mapping) || !mapping.ContainsVirtualImage(image))
continue;
return {OpenCached(mapping.filename), static_cast<uint32_t>(mapping.SourceImage(image)),
mapping.filename, mapping.dataset};
mapping.filename, mapping.dataset, SourceChannel(mapping).value_or(0)};
}
throw JFJochException(JFJochExceptionCategory::HDF5,
"Image not covered by /entry/data/data VDS mappings");
@@ -127,13 +142,14 @@ std::vector<HDF5DataSourceMessage> HDF5ImageLocator::GetSourceMapping(uint64_t f
bool found = false;
for (const auto &mapping: layout_.vds_mappings) {
if (!mapping.ContainsVirtualImage(virtual_image))
if (!CoversFirstChannel(mapping) || !mapping.ContainsVirtualImage(virtual_image))
continue;
const uint64_t source_image = mapping.SourceImage(virtual_image);
const std::string dataset = mapping.dataset.empty() ? "/entry/data/data" : mapping.dataset;
AppendOrExtendSourceMapping(ret, mapping.filename, dataset, source_image, local_image, 1);
AppendOrExtendSourceMapping(ret, mapping.filename, dataset, source_image, local_image, 1,
SourceChannel(mapping));
found = true;
break;
}