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

+72
View File
@@ -4931,3 +4931,75 @@ TEST_CASE("JFJochXTCReader", "[portable]") {
std::filesystem::remove_all("xtc_test");
}
// The panel id of spots goes to /entry/MX/peakPanel only for a detector with a panel table; for a flat
// detector the dataset is not written, so such files are unchanged.
TEST_CASE("JFJochReader_PanelId", "[HDF5][PanelId]") {
const auto format = GENERATE(FileWriterFormat::NXmxVDS, FileWriterFormat::NXmxLegacy,
FileWriterFormat::NXmxIntegrated);
const bool panels = GENERATE(true, false);
INFO("format " << static_cast<int>(format) << " panels " << panels);
DiffractionExperiment x(DetJF(1));
x.FilePrefix("test_panel_id").ImagesPerTrigger(2).ImagesPerFile(10).OverwriteExistingFiles(true)
.BitDepthImage(16).PixelSigned(true).SetFileWriterFormat(format)
.IndexingAlgorithm(IndexingAlgorithmEnum::FFT).Compression(CompressionAlgorithm::NO_COMPRESSION);
if (panels)
x.Modules(DetectorModules({DetectorModule::AsImaged(0, 0, x.GetXPixelsNum(), x.GetYPixelsNum() / 2),
DetectorModule::AsImaged(0, x.GetYPixelsNum() / 2, x.GetXPixelsNum(),
x.GetYPixelsNum() / 2)}));
const int16_t p0 = panels ? 0 : -1;
const int16_t p1 = panels ? 1 : -1;
std::vector<int16_t> image(x.GetPixelsNum());
RegisterHDF5Filter();
{
StartMessage start_message;
x.FillMessage(start_message);
CHECK(start_message.detector_modules.size() == (panels ? 2 : 0));
FileWriter file_set(start_message);
for (int i = 0; i < 2; i++) {
DataMessage message{};
message.number = i;
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.indexing_result = true;
message.indexing_lattice = CrystalLattice(40, 50, 60, 90, 90, 90);
message.spots = {SpotToSave{.x = 10, .y = 50, .intensity = 80, .panel = p0},
SpotToSave{.x = 20, .y = 400, .intensity = 90, .panel = p1}};
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
}
EndMessage end_message;
end_message.max_image_number = 2;
file_set.WriteHDF5(end_message);
file_set.Finalize();
}
{
HDF5ReadOnlyFile master("test_panel_id_master.h5");
const bool separate_data = format != FileWriterFormat::NXmxIntegrated;
// The master of a VDS set exposes it as a virtual dataset, the integrated file holds it itself
CHECK(master.Exists("/entry/MX/peakPanel") == (panels && format != FileWriterFormat::NXmxLegacy));
if (separate_data) {
HDF5ReadOnlyFile data("test_panel_id_data_000001.h5");
CHECK(data.Exists("/entry/MX/peakPanel") == panels);
}
}
{
JFJochHDF5Reader reader;
REQUIRE_NOTHROW(reader.ReadFile("test_panel_id_master.h5"));
for (int i = 0; i < 2; i++) {
std::shared_ptr<JFJochReaderImage> reader_image;
REQUIRE_NOTHROW(reader_image = reader.LoadImage(i));
REQUIRE(reader_image);
const auto &spots = reader_image->ImageData().spots;
REQUIRE(spots.size() == 2);
CHECK(spots[0].panel == p0);
CHECK(spots[1].panel == p1);
}
}
remove("test_panel_id_master.h5");
remove("test_panel_id_data_000001.h5");
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}