From dc784c073dc746aaadefc9a3975ebb130a3277da Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 21 Oct 2026 10:28:23 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu --- common/DiffractionExperiment.cpp | 1 + common/JFJochMessages.h | 5 ++ common/SpotToSave.h | 1 + docs/CBOR.md | 2 + docs/HDF5.md | 1 + frame_serialize/CBORStream2Deserializer.cpp | 53 ++++++++++++++ frame_serialize/CBORStream2Serializer.cpp | 30 ++++++++ reader/HDF5MetadataSource.cpp | 9 +++ rugnux/RugnuxStills.cpp | 10 ++- tests/CBORTest.cpp | 81 +++++++++++++++++++++ tests/JFJochReaderTest.cpp | 72 ++++++++++++++++++ viewer/JFJochHttpReader.cpp | 2 + writer/HDF5DataFilePluginMX.cpp | 7 ++ writer/HDF5DataFilePluginMX.h | 2 + writer/HDF5NXmx.cpp | 2 + 15 files changed, 274 insertions(+), 4 deletions(-) diff --git a/common/DiffractionExperiment.cpp b/common/DiffractionExperiment.cpp index db1ce112b..52d50a89a 100644 --- a/common/DiffractionExperiment.cpp +++ b/common/DiffractionExperiment.cpp @@ -652,6 +652,7 @@ SpotFindingSettings DiffractionExperiment::DefaultDataProcessingSettings() { void DiffractionExperiment::FillMessage(StartMessage &message) const { message.images_per_file = GetImagesPerFile(); + message.detector_modules = GetModules().Get(); message.beam_center_x = GetBeamX_pxl(); message.beam_center_y = GetBeamY_pxl(); const auto direct_beam = GetDiffractionGeometry().GetDirectBeam_pxl(); diff --git a/common/JFJochMessages.h b/common/JFJochMessages.h index 449929d2f..ec9ce70d6 100644 --- a/common/JFJochMessages.h +++ b/common/JFJochMessages.h @@ -24,6 +24,7 @@ #include "CrystalLattice.h" #include "IndexingSettings.h" #include "XrayFluorescenceSpectrum.h" +#include "DetectorModules.h" #include "../gemmi_gph/gemmi/symmetry.hpp" constexpr const uint64_t user_data_release = 6; @@ -324,6 +325,10 @@ struct StartMessage { // Per-pixel ROI bitmask (converted geometry), bit i set for ROI i in rois. std::vector roi_map; + // The detector's panels (DetectorModules.h), where the image's pixels are on the detector plane. Empty + // for one flat detector, the image being the plane; then the CBOR key is absent. + std::vector detector_modules; + std::map threshold_energy; std::optional total_flux; diff --git a/common/SpotToSave.h b/common/SpotToSave.h index d866acbc1..ed935058b 100644 --- a/common/SpotToSave.h +++ b/common/SpotToSave.h @@ -18,6 +18,7 @@ struct SpotToSave { float dist_ewald_sphere = 0.0; bool ice_ring = false; bool indexed = false; + int16_t panel = -1; // index into the experiment's DetectorModules; -1 = no panel table Coord ReciprocalCoord(const DiffractionGeometry &experiment) const; }; diff --git a/docs/CBOR.md b/docs/CBOR.md index ff647958a..ddc46d4ab 100644 --- a/docs/CBOR.md +++ b/docs/CBOR.md @@ -32,6 +32,7 @@ There are minor differences at the moment: | mirror_y | bool | Whether the assembled image is mirrored in Y relative to the detector's raw readout order. True is the MX convention - row 0 at the top of the detector seen from the sample - and is what absence of the key means | | | detector_orientation_mirror_y | bool | Whether the assembled image is mirrored in Y relative to the frame the PONI angles are stated in. A different setting from `mirror_y` above, which is about the module layout; this one changes no pixel. Absence means false | | | detector_orientation_quarter_turns | int | How many multiples of 90 degrees about the beam the assembled image is turned by, relative to the frame the PONI angles are stated in (0-3). Absence means 0 | | +| detector_modules | Array(object) (optional) | Panels: where the image's pixels lie on the detector plane the geometry describes. Each has `x0`, `y0`, `width`, `height` (int, the panel's box in the image) and `ox`, `oy`, `fx`, `fy`, `sx`, `sy` (double, plane pixels): image pixel (x, y) of the panel is at `(ox, oy) + (x - x0) (fx, fy) + (y - y0) (sx, sy)`. Absent for a flat detector, the image being the plane; spot and reflection positions are always plane coordinates | | | incident_energy | float | X-ray energy \[eV\] | X | | incident_wavelength | float | X-ray wavelength \[Angstrom\] | X | | incident_wavelength_spread | float (optional) | FWHM of the X-ray wavelength distribution \[Angstrom\] (NXmx incident_wavelength_spread); omitted when the beam is monochromatic | | @@ -150,6 +151,7 @@ See [DECTRIS documentation](https://github.com/dectris/documentation/tree/main/s | - indexed | bool | indexed solution | | | | - latt | int64 | Lattice to which the peak belongs (negative number = not indexed) | | | | - image | int64 | image number the spot belongs to | | | +| - panel | int64 (optional) | panel (index into `detector_modules`) the spot was found on; absent for a detector without a panel table | | | | - h | int64 | Miller index (indexed spots only) | | | | - k | int64 | Miller index (indexed spots only) | | | | - l | int64 | Miller index (indexed spots only) | | | diff --git a/docs/HDF5.md b/docs/HDF5.md index 0bddb0b26..fa82e587b 100644 --- a/docs/HDF5.md +++ b/docs/HDF5.md @@ -342,6 +342,7 @@ In legacy/VDS mode these live in the data files and are linked/virtual-stacked i | `peakDistEwaldSphere` | Å⁻¹ | distance of the spot from the Ewald sphere | ✓ | | `peakIndexed` | | spot fits the indexing solution | ✓ | | `peakLattice` | | lattice the spot belongs to (`-1` = unindexed) | ✓ | +| `peakPanel` | | panel the spot was found on (`-1` = none); written only for a detector with a panel table | | **Per-image vectors `[n_images]`:** diff --git a/frame_serialize/CBORStream2Deserializer.cpp b/frame_serialize/CBORStream2Deserializer.cpp index 2341bbc3f..a7c0dba3f 100644 --- a/frame_serialize/CBORStream2Deserializer.cpp +++ b/frame_serialize/CBORStream2Deserializer.cpp @@ -59,6 +59,16 @@ namespace { throw JFJochException(JFJochExceptionCategory::CBORError, "Float expected"); } + double GetCBORDouble(CborValue &value) { + if (cbor_value_is_double(&value)) { + double tmp; + cborErr(cbor_value_get_double(&value, &tmp)); + cborErr(cbor_value_advance(&value)); + return tmp; + } + return GetCBORFloat(value); + } + bool GetCBORBool(CborValue &value) { if (!cbor_value_is_boolean(&value)) { throw JFJochException(JFJochExceptionCategory::CBORError, @@ -514,6 +524,8 @@ namespace { s.dist_ewald_sphere = GetCBORFloat(map_value); else if (key == "image") s.image = GetCBORInt(map_value); + else if (key == "panel") + s.panel = static_cast(GetCBORInt(map_value)); else cbor_value_advance(&map_value); } @@ -1299,6 +1311,45 @@ namespace { } } + void GetDetectorModules(CborValue &value, std::vector &modules) { + const size_t array_len = GetCBORArrayLen(value); + CborValue array_value; + cborErr(cbor_value_enter_container(&value, &array_value)); + for (size_t i = 0; i < array_len; i++) { + DetectorModule m; + CborValue map_value; + cborErr(cbor_value_enter_container(&array_value, &map_value)); + while (!cbor_value_at_end(&map_value)) { + const auto key = GetCBORString(map_value); + if (key == "x0") + m.x0 = GetCBORInt(map_value); + else if (key == "y0") + m.y0 = GetCBORInt(map_value); + else if (key == "width") + m.width = GetCBORInt(map_value); + else if (key == "height") + m.height = GetCBORInt(map_value); + else if (key == "ox") + m.ox = GetCBORDouble(map_value); + else if (key == "oy") + m.oy = GetCBORDouble(map_value); + else if (key == "fx") + m.fx = GetCBORDouble(map_value); + else if (key == "fy") + m.fy = GetCBORDouble(map_value); + else if (key == "sx") + m.sx = GetCBORDouble(map_value); + else if (key == "sy") + m.sy = GetCBORDouble(map_value); + else + cbor_value_advance(&map_value); + } + cborErr(cbor_value_leave_container(&array_value, &map_value)); + modules.push_back(m); + } + cborErr(cbor_value_leave_container(&value, &array_value)); + } + bool ProcessStartMessageElement(StartMessage &message, CborValue &value) { if (cbor_value_at_end(&value)) return false; @@ -1370,6 +1421,8 @@ namespace { ProcessAzintMapElement(message, value); else if (key == "roi_map") ProcessROIMapElement(message, value); + else if (key == "detector_modules") + GetDetectorModules(value, message.detector_modules); else if (key == "channels") GetCBORStringArray(value, message.channels); else if (key == "detector_translation") diff --git a/frame_serialize/CBORStream2Serializer.cpp b/frame_serialize/CBORStream2Serializer.cpp index eaf732fd1..07412333e 100644 --- a/frame_serialize/CBORStream2Serializer.cpp +++ b/frame_serialize/CBORStream2Serializer.cpp @@ -238,6 +238,8 @@ inline void CBOR_ENC(CborEncoder &encoder, const SpotToSave& spot) { CBOR_ENC(mapEncoder, "indexed", spot.indexed); CBOR_ENC(mapEncoder, "latt", spot.lattice); CBOR_ENC(mapEncoder, "image", spot.image); + if (spot.panel >= 0) + CBOR_ENC(mapEncoder, "panel", static_cast(spot.panel)); if (spot.indexed) { CBOR_ENC(mapEncoder, "h", spot.h); CBOR_ENC(mapEncoder, "k", spot.k); @@ -484,6 +486,33 @@ inline void CBOR_ENC_AZINT_MAP(CborEncoder &encoder, const StartMessage &msg) { CBOR_ENC_2D_TYPED_ARRAY(encoder, image); } +// Not a DECTRIS field. Absent for a flat detector (no panel table), which is what every stream written +// before the key existed means. +inline void CBOR_ENC_DETECTOR_MODULES(CborEncoder &encoder, const std::vector &modules) { + if (modules.empty()) + return; + CborEncoder arrayEncoder; + cborErr(cbor_encode_text_stringz(&encoder, "detector_modules")); + cborErr(cbor_encoder_create_array(&encoder, &arrayEncoder, modules.size())); + for (const auto &m: modules) { + CborEncoder mapEncoder; + cborErr(cbor_encoder_create_map(&arrayEncoder, &mapEncoder, 10)); + CBOR_ENC(mapEncoder, "x0", m.x0); + CBOR_ENC(mapEncoder, "y0", m.y0); + CBOR_ENC(mapEncoder, "width", m.width); + CBOR_ENC(mapEncoder, "height", m.height); + const std::pair placement[] = { + {"ox", m.ox}, {"oy", m.oy}, {"fx", m.fx}, {"fy", m.fy}, {"sx", m.sx}, {"sy", m.sy} + }; + for (const auto &[key, val]: placement) { + cborErr(cbor_encode_text_stringz(&mapEncoder, key)); + cborErr(cbor_encode_double(&mapEncoder, val)); + } + cborErr(cbor_encoder_close_container(&arrayEncoder, &mapEncoder)); + } + cborErr(cbor_encoder_close_container(&encoder, &arrayEncoder)); +} + inline void CBOR_ENC_ROI_MAP(CborEncoder &encoder, const StartMessage &msg) { if (msg.roi_map.empty()) return; @@ -835,6 +864,7 @@ void CBORStream2Serializer::SerializeSequenceStart(const StartMessage& message) CBOR_ENC_PIXEL_MASK(mapEncoder, message); CBOR_ENC_AZINT_MAP(mapEncoder, message); CBOR_ENC_ROI_MAP(mapEncoder, message); + CBOR_ENC_DETECTOR_MODULES(mapEncoder, message.detector_modules); CBOR_ENC(mapEncoder, "channels", message.channels); CBOR_ENC(mapEncoder, "max_spot_count", message.max_spot_count); diff --git a/reader/HDF5MetadataSource.cpp b/reader/HDF5MetadataSource.cpp index 33b287c0f..8b2299cee 100644 --- a/reader/HDF5MetadataSource.cpp +++ b/reader/HDF5MetadataSource.cpp @@ -1291,6 +1291,13 @@ static void ReadSpotsFromFiles(HDF5Object &master_file, {master_image, 0}, {source_image, 0}, {1, spot_count} ); + // Written only for a detector with a panel table. + auto spot_panel = ReadVectorMasterFirst( + master_file, source_file, + "/entry/MX/peakPanel", + {master_image, 0}, {source_image, 0}, {1, spot_count} + ); + message.spots.reserve(message.spots.size() + spot_count); for (size_t i = 0; i < spot_count; i++) { const auto x = spot_x.at(i); @@ -1317,6 +1324,8 @@ static void ReadSpotsFromFiles(HDF5Object &master_file, s.ice_ring = (spot_ice.at(i) != 0); if (spot_lattice.size() > i) s.lattice = spot_lattice.at(i); + if (spot_panel.size() > i) + s.panel = spot_panel.at(i); message.spots.emplace_back(s); } diff --git a/rugnux/RugnuxStills.cpp b/rugnux/RugnuxStills.cpp index 8f37aee11..361bcd1ed 100644 --- a/rugnux/RugnuxStills.cpp +++ b/rugnux/RugnuxStills.cpp @@ -89,12 +89,14 @@ using namespace rugnux_internal; namespace { -// The module of each spot, by where it lies on the detector plane (spots are on the plane already). +// The module of each spot: the panel the spot finder found it on, or - where the spot carries no panel id - +// the module where it lies on the detector plane (spots are on the plane already). void AssignModules(std::vector &frames, const DetectorModules &modules) { for (auto &f: frames) for (auto *list: {&f.spots, &f.unindexed}) for (auto &s: *list) - s.module = static_cast(modules.ModuleOfPlanePoint(s.x, s.y)); + if (s.module < 0) + s.module = static_cast(modules.ModuleOfPlanePoint(s.x, s.y)); } } // namespace @@ -217,9 +219,9 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ for (const auto &s : msg.spots) { if (s.indexed && s.lattice == 0) f.spots.push_back(GeomRefineSpot{s.x, s.y, - static_cast(s.h), static_cast(s.k), static_cast(s.l)}); + static_cast(s.h), static_cast(s.k), static_cast(s.l), s.panel}); else - f.unindexed.push_back(GeomRefineSpot{s.x, s.y}); + f.unindexed.push_back(GeomRefineSpot{s.x, s.y, 0, 0, 0, s.panel}); } if (f.spots.size() >= 6) { if (static_cast(f.spots.size()) >= MIN_STRONG_SPOTS) diff --git a/tests/CBORTest.cpp b/tests/CBORTest.cpp index 8df4dd5a6..05b2e1790 100644 --- a/tests/CBORTest.cpp +++ b/tests/CBORTest.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only +#include #include #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 buffer(1 << 20); + const std::string key = "panel"; + std::vector 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(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 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); + } +} diff --git a/tests/JFJochReaderTest.cpp b/tests/JFJochReaderTest.cpp index 1c83d63f7..7b13caef3 100644 --- a/tests/JFJochReaderTest.cpp +++ b/tests/JFJochReaderTest.cpp @@ -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(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 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 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); +} diff --git a/viewer/JFJochHttpReader.cpp b/viewer/JFJochHttpReader.cpp index 08d5026dd..66bbd52c2 100644 --- a/viewer/JFJochHttpReader.cpp +++ b/viewer/JFJochHttpReader.cpp @@ -224,6 +224,8 @@ std::shared_ptr JFJochHttpReader::UpdateDataset_i() { detector.MinCountTime(std::chrono::microseconds(0)); detector.ReadOutTime(std::chrono::microseconds (0)); dataset->experiment.Detector(detector); + // Absent (empty) for a flat detector, the image being the detector plane + dataset->experiment.Modules(DetectorModules(msg->start_message->detector_modules)); dataset->experiment.FrameTime( std::chrono::microseconds(std::lround(msg->start_message->frame_time * 1e6)), diff --git a/writer/HDF5DataFilePluginMX.cpp b/writer/HDF5DataFilePluginMX.cpp index 3acf351ab..dfe604710 100644 --- a/writer/HDF5DataFilePluginMX.cpp +++ b/writer/HDF5DataFilePluginMX.cpp @@ -67,6 +67,7 @@ void HDF5DataFilePluginMX::OpenFile(HDF5File &data_file, const DataMessage &msg, spot_k.reserve(max_spots * images_per_file); spot_l.reserve(max_spots * images_per_file); spot_dist_ewald.reserve(max_spots * images_per_file); + spot_panel.reserve(max_spots * images_per_file); if (indexing) { spot_indexed.reserve(max_spots * images_per_file); @@ -120,6 +121,7 @@ void HDF5DataFilePluginMX::Write(const DataMessage &msg, uint64_t image_number) spot_l.resize(max_spots * (max_image_number + 1)); spot_lattice.resize(max_spots * (max_image_number + 1), -1); spot_dist_ewald.resize(max_spots * (max_image_number + 1), NAN); + spot_panel.resize(max_spots * (max_image_number + 1), -1); if (indexing) { spot_indexed.resize(max_spots * (max_image_number + 1)); @@ -134,6 +136,9 @@ void HDF5DataFilePluginMX::Write(const DataMessage &msg, uint64_t image_number) spot_x[max_spots * image_number + i] = msg.spots[i].x; spot_y[max_spots * image_number + i] = msg.spots[i].y; spot_int[max_spots * image_number + i] = msg.spots[i].intensity; + spot_panel[max_spots * image_number + i] = msg.spots[i].panel; + if (msg.spots[i].panel >= 0) + spot_panel_present = true; if (indexing) { spot_indexed[max_spots * image_number + i] = msg.spots[i].indexed; spot_lattice[max_spots * image_number + i] = msg.spots[i].lattice; @@ -213,6 +218,8 @@ void HDF5DataFilePluginMX::WriteFinal(HDF5File &data_file) { data_file.SaveVector("/entry/MX/peakTotalIntensity", spot_int, {(hsize_t) (max_image_number + 1), max_spots}); data_file.SaveVector("/entry/MX/peakIceRingRes", spot_ice_ring, {(hsize_t) (max_image_number + 1), max_spots}); data_file.SaveVector("/entry/MX/nPeaks", npeaks.vec()); + if (spot_panel_present) + data_file.SaveVector("/entry/MX/peakPanel", spot_panel, {(hsize_t) (max_image_number + 1), max_spots}); if (indexing) { data_file.SaveVector("/entry/MX/peakH", spot_h, {(hsize_t) (max_image_number + 1), max_spots}); diff --git a/writer/HDF5DataFilePluginMX.h b/writer/HDF5DataFilePluginMX.h index d369f8b0d..df418e6ff 100644 --- a/writer/HDF5DataFilePluginMX.h +++ b/writer/HDF5DataFilePluginMX.h @@ -21,6 +21,8 @@ class HDF5DataFilePluginMX : public HDF5DataFilePlugin { std::vector spot_k; std::vector spot_l; std::vector spot_dist_ewald; + std::vector spot_panel; + bool spot_panel_present = false; // written only for a detector with a panel table AutoIncrVector beam_corr_x{NAN}; AutoIncrVector beam_corr_y{NAN}; diff --git a/writer/HDF5NXmx.cpp b/writer/HDF5NXmx.cpp index 6f99af71f..d8503b112 100644 --- a/writer/HDF5NXmx.cpp +++ b/writer/HDF5NXmx.cpp @@ -132,6 +132,8 @@ void NXmx::LinkToData_VDS(const StartMessage &start, const EndMessage &end) { VDS(start, "/entry/MX/peakTotalIntensity",{total_images, start.max_spot_count}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/peakIceRingRes", {total_images, start.max_spot_count}, HDF5DataType(static_cast(0))); VDS(start, "/entry/MX/nPeaks", {total_images}, HDF5DataType((uint32_t) 0)); + if (!start.detector_modules.empty()) + VDS(start, "/entry/MX/peakPanel", {total_images, start.max_spot_count}, HDF5DataType(static_cast(-1)), -1); } if (start.indexing_algorithm != IndexingAlgorithmEnum::None) {