// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #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((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(i); else if (masked_index < 0 && (roi_map[i] & (1 << 1))) masked_index = static_cast(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(i % width); const int64_t y = static_cast(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(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 out; roi.Run(image, out); REQUIRE(out.size() == 2); REQUIRE(out.contains("roiA")); REQUIRE(out.contains("roiB")); const std::pair 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); }