Files
Jungfraujoch/tests/ROIIntegrationCPUTest.cpp
leonarski_f 75e401f0e5
Build Packages / Unit tests (push) Successful in 1h31m59s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 8m43s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m27s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m56s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m24s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 10m27s
Build Packages / build:rpm (rocky8) (push) Successful in 9m20s
Build Packages / build:rpm (rocky9) (push) Successful in 10m50s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m54s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m38s
Build Packages / DIALS test (push) Successful in 12m13s
Build Packages / XDS test (durin plugin) (push) Successful in 7m8s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 7m8s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m50s
Build Packages / Generate python client (push) Successful in 16s
Build Packages / Build documentation (push) Successful in 50s
Build Packages / Create release (push) Skipped
v1.0.0-rc.153 (#63)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* jfjoch_broker: Add EXPERIMENTAL pixelrefine mode for image processing
* jfjoch_broker: Allow to load user mask from 8-bit and 16-bit TIFF files
* jfjoch_broker: Add ROI calculation in non-FPGA workflow
* jfjoch_broker: Fixes to TCP image pusher
* jfjoch_broker: Remove NUMA bindings
* jfjoch_broker: Improvements to indexing
* jfjoch_broker: For PSI EIGER, trimming energies are taken from the detector configuration (now compulsory) instead of hardcoded values
* jfjoch_writer: Save ROI definitions and the per-pixel ROI bitmap in the master file; azimuthal ROIs support phi (angular) sectors
* jfjoch_viewer: Major redesign with dockable panels and saved layouts, plus on-canvas creation/move/resize of box, circle and azimuthal ROIs
* jfjoch_viewer: Run jfjoch_process reprocessing jobs from inside the GUI and overlay per-run results

Reviewed-on: #63
2026-06-23 20:29:49 +02:00

96 lines
3.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <catch2/catch_all.hpp>
#include "../image_analysis/roi/ROIIntegrationCPU.h"
#include "../common/DiffractionExperiment.h"
TEST_CASE("ROIIntegrationCPU") {
DiffractionExperiment experiment(DetJF(1));
experiment.ROI().SetROI(ROIDefinition{.boxes = {
ROIBox("roiA", 10, 20, 30, 40),
ROIBox("roiB", 15, 25, 35, 45)
}});
ROIIntegrationCPU roi(experiment);
REQUIRE(roi.Count() == 2);
REQUIRE_FALSE(roi.empty());
const auto roi_map = experiment.ExportROIMap();
const size_t width = experiment.GetXPixelsNumConv();
const size_t npixel = roi_map.size();
ImagePreprocessorBuffer image(npixel);
for (size_t i = 0; i < npixel; i++)
image[i] = static_cast<int32_t>((i * 7 + 3) % 251); // deterministic, well below INT32_MAX
// Inject one saturated pixel into roiA (bit 0) and one masked pixel into roiB (bit 1)
int64_t saturated_index = -1;
int64_t masked_index = -1;
for (size_t i = 0; i < npixel && (saturated_index < 0 || masked_index < 0); i++) {
if (saturated_index < 0 && (roi_map[i] & (1 << 0)))
saturated_index = static_cast<int64_t>(i);
else if (masked_index < 0 && (roi_map[i] & (1 << 1)))
masked_index = static_cast<int64_t>(i);
}
REQUIRE(saturated_index >= 0);
REQUIRE(masked_index >= 0);
image[saturated_index] = INT32_MAX;
image[masked_index] = INT32_MIN;
// Independent reference matching the documented semantics (masked excluded
// entirely; saturated excluded from sums but counted towards the max)
struct Ref { int64_t sum = 0; uint64_t sum2 = 0; uint64_t pixels = 0;
int64_t xw = 0; int64_t yw = 0; int64_t max = INT64_MIN; };
Ref ref[2];
for (size_t i = 0; i < npixel; i++) {
const uint16_t mask = roi_map[i];
if (mask == 0)
continue;
const int32_t v = image[i];
if (v == INT32_MIN)
continue;
const bool saturated = (v == INT32_MAX);
const int64_t val = v;
const int64_t x = static_cast<int64_t>(i % width);
const int64_t y = static_cast<int64_t>(i / width);
for (int r = 0; r < 2; r++) {
if (!(mask & (1 << r)))
continue;
if (!saturated) {
ref[r].sum += val;
ref[r].sum2 += static_cast<uint64_t>(val * val);
ref[r].pixels += 1;
ref[r].xw += val * x;
ref[r].yw += val * y;
}
if (val > ref[r].max)
ref[r].max = val;
}
}
std::map<std::string, ROIMessage> out;
roi.Run(image, out);
REQUIRE(out.size() == 2);
REQUIRE(out.contains("roiA"));
REQUIRE(out.contains("roiB"));
const std::pair<std::string, int> roi_ids[2] = {{"roiA", 0}, {"roiB", 1}};
for (const auto &[name, r] : roi_ids) {
const auto &msg = out.at(name);
CHECK(msg.sum == ref[r].sum);
CHECK(msg.sum_square == ref[r].sum2);
CHECK(msg.pixels == ref[r].pixels);
CHECK(msg.x_weighted == ref[r].xw);
CHECK(msg.y_weighted == ref[r].yw);
CHECK(msg.max_count == ref[r].max);
CHECK(msg.pixels_masked == 0);
}
// Targeted checks: saturated pixel sets the max for roiA but is not summed
CHECK(out.at("roiA").max_count == INT32_MAX);
CHECK(ref[0].pixels > 0);
}