writer: link ROI results into the VDS master file

In VDS mode the per-image ROI results (max/sum/sum_sq/npixel/x/y) are
written into the data files but were not exposed in the master, so a VDS
master surfaced no ROI statistics. Add virtual datasets under
/entry/roi/<name> in LinkToData_VDS, one group per ROI, mirroring how the
spot-finding and azimuthal-integration arrays are linked. Integrated and
legacy formats are unaffected (the results are already reachable there).

Extended the reader round-trip test to write real ROI results and check
they read back from the master for both VDS and integrated formats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 10:38:24 +02:00
co-authored by Claude Opus 4.8
parent c6e7f8aa15
commit 6b95600260
3 changed files with 29 additions and 1 deletions
+3 -1
View File
@@ -322,7 +322,9 @@ In the master file these per-image groups are exposed through `/entry/reflection
### 4.4 `/entry/roi` — regions of interest (per-image results)
`/entry/roi/<roi_name>` has one sub-group per configured ROI, holding the **per-image result
vectors** `[n_images]` (in the data files):
vectors** `[n_images]`. These are written into the data files; in VDS mode they are exposed from
the master file through virtual datasets, and in integrated mode they are in the single file.
(In legacy mode they remain only in the data files.)
| Dataset | Meaning |
|---------|---------|
+10
View File
@@ -288,6 +288,9 @@ TEST_CASE("JFJochReader_ROIDefinitions", "[HDF5][Full]") {
DataMessage message{};
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = 0;
for (const auto &name : {"mybox", "mycircle", "mywedge"})
message.roi[name] = ROIMessage{.sum = 100, .sum_square = 1000, .max_count = 50,
.pixels = 10, .x_weighted = 500, .y_weighted = 600};
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
generator.Add(message);
@@ -319,6 +322,13 @@ TEST_CASE("JFJochReader_ROIDefinitions", "[HDF5][Full]") {
CHECK(dataset->roi_map.size() == x.GetXPixelsNumConv() * x.GetYPixelsNumConv());
CHECK(dataset->roi_bit_index.size() == 3);
CHECK(dataset->roi_bit_index.at("mybox") == 0);
// per-image ROI results surface from the master (VDS-linked for VDS format)
REQUIRE(dataset->roi.size() == 3);
auto it = std::find(dataset->roi.begin(), dataset->roi.end(), "mybox");
REQUIRE(it != dataset->roi.end());
const size_t idx = std::distance(dataset->roi.begin(), it);
CHECK(dataset->roi_sum.at(idx).at(0) == 100);
}
remove("test_roi_master.h5");
remove("test_roi_data_000001.h5");
+16
View File
@@ -160,6 +160,22 @@ void NXmx::LinkToData_VDS(const StartMessage &start, const EndMessage &end) {
}
}
if (!start.rois.empty()) {
// Per-image ROI results live in the data files; expose them in the master
// through virtual datasets, one /entry/roi/<name> group per ROI.
HDF5Group(*hdf5_file, "/entry/roi").NXClass("NXcollection");
for (const auto &r: start.rois) {
const std::string base = "/entry/roi/" + r.name;
HDF5Group(*hdf5_file, base);
VDS(start, base + "/max", {total_images}, HDF5DataType((int64_t) 0));
VDS(start, base + "/sum", {total_images}, HDF5DataType((int64_t) 0));
VDS(start, base + "/sum_sq", {total_images}, HDF5DataType((int64_t) 0));
VDS(start, base + "/npixel", {total_images}, HDF5DataType((int64_t) 0));
VDS(start, base + "/x", {total_images}, HDF5DataType((float) 0));
VDS(start, base + "/y", {total_images}, HDF5DataType((float) 0));
}
}
if (start.xfel_pulse_id.value_or(false)) {
HDF5Group(*hdf5_file, "/entry/xfel").NXClass("NXcollection");
VDS(start, "/entry/xfel/pulseID", {total_images}, HDF5DataType((uint64_t) 0));