Panel id on spots and the panel table in the start message

SpotToSave gains int16_t panel (-1 = no panel table). It sits in the padding
after the two bools, so sizeof(SpotToSave) stays 80. It is serialised only
where it is set: CBOR spot key "panel", HDF5 /entry/MX/peakPanel (data file,
plus a virtual dataset in a VDS master when the start message has panels),
read back by HDF5MetadataSource. For a flat detector none of these is written,
so files and streams for existing data are byte-for-byte unchanged.

The start message carries the panel table (detector_modules, CBOR key absent
when empty), filled from the experiment; JFJochHttpReader takes it, so a
stream viewer knows the panels too.

AssignModules takes the spot's panel id where it has one - the stored pixel box
it was found in - and falls back to the plane position otherwise (every spot
until per-panel spot finding sets the id).

Reflection deliberately gets no panel id (owner decision): it is needed only
between prediction and integration, and the struct feeds GPU layout and
memory budgets.

Tests: CBORSerialize_SpotPanelId, CBORSerialize_StartDetectorModules,
JFJochReader_PanelId (VDS/legacy/integrated, with and without panels).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-21 10:28:23 +02:00
1 parent c8fb472f4d
commit dc784c073d
15 files changed
+274 -4

No files matched your search

+81
View File
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <algorithm>
#include <catch2/catch_all.hpp>
#include "../frame_serialize/CBORStream2Serializer.h"
@@ -1507,3 +1508,83 @@ TEST_CASE("CBORDeserialize_Start_CompressedLookupTable", "[CBOR]") {
REQUIRE(deserialized->start_message);
CHECK(deserialized->start_message->countrate_correction_lookup_table == lut);
}
// The panel id of a spot is a CBOR key only for a detector with a panel table: a spot with panel -1
// serialises to the same bytes as before the key existed.
TEST_CASE("CBORSerialize_SpotPanelId", "[CBOR][PanelId]") {
std::vector<uint8_t> buffer(1 << 20);
const std::string key = "panel";
std::vector<uint16_t> test(512);
CompressedImage image(test, 256, 2);
auto serialize = [&](int16_t p0, int16_t p1) {
CBORStream2Serializer serializer(buffer.data(), buffer.size());
DataMessage message{
.number = 1,
.image = image,
.spots = {SpotToSave{.x = 7, .y = 8, .intensity = 34, .panel = p0},
SpotToSave{.x = 37, .y = 48, .intensity = 123, .panel = p1}}
};
serializer.SerializeImage(message);
return std::vector<uint8_t>(buffer.begin(), buffer.begin() + serializer.GetBufferSize());
};
const auto absent = serialize(-1, -1);
CHECK(std::search(absent.begin(), absent.end(), key.begin(), key.end()) == absent.end());
auto deserialized = CBORStream2Deserialize(absent.data(), absent.size());
REQUIRE(deserialized->data_message);
REQUIRE(deserialized->data_message->spots.size() == 2);
CHECK(deserialized->data_message->spots[0].panel == -1);
CHECK(deserialized->data_message->spots[1].panel == -1);
const auto present = serialize(3, 0);
CHECK(std::search(present.begin(), present.end(), key.begin(), key.end()) != present.end());
deserialized = CBORStream2Deserialize(present.data(), present.size());
REQUIRE(deserialized->data_message);
REQUIRE(deserialized->data_message->spots.size() == 2);
CHECK(deserialized->data_message->spots[0].panel == 3);
CHECK(deserialized->data_message->spots[1].panel == 0);
CHECK(deserialized->data_message->spots[1].x == 37);
}
// The start message carries the panel table only when there is one.
TEST_CASE("CBORSerialize_StartDetectorModules", "[CBOR][PanelId]") {
std::vector<uint8_t> buffer(8 * 1024 * 1024);
const std::string key = "detector_modules";
StartMessage message{};
message.image_size_x = 256;
message.image_size_y = 128;
{
CBORStream2Serializer serializer(buffer.data(), buffer.size());
serializer.SerializeSequenceStart(message);
CHECK(std::search(buffer.begin(), buffer.begin() + serializer.GetBufferSize(), key.begin(), key.end())
== buffer.begin() + serializer.GetBufferSize());
auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize());
REQUIRE(deserialized->start_message);
CHECK(deserialized->start_message->detector_modules.empty());
}
DetectorModule turned{.x0 = 0, .y0 = 64, .width = 256, .height = 64,
.ox = 300.25, .oy = -12.5, .fx = 0, .fy = 1, .sx = -1, .sy = 0};
message.detector_modules = {DetectorModule::AsImaged(0, 0, 256, 64), turned};
{
CBORStream2Serializer serializer(buffer.data(), buffer.size());
serializer.SerializeSequenceStart(message);
auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize());
REQUIRE(deserialized->start_message);
const auto &m = deserialized->start_message->detector_modules;
REQUIRE(m.size() == 2);
CHECK(m[0].x0 == 0);
CHECK(m[0].height == 64);
CHECK(m[0].fx == 1);
CHECK(m[1].y0 == 64);
CHECK(m[1].width == 256);
CHECK(m[1].ox == 300.25);
CHECK(m[1].oy == -12.5);
CHECK(m[1].fx == 0);
CHECK(m[1].fy == 1);
CHECK(m[1].sx == -1);
CHECK(m[1].sy == 0);
}
}