Files
leonarski_fandClaude Opus 5.5 49abb4a079 XDSPluginTest: expect a signed image's INTx_MAX to pass through as saturated
Plugin 1.1.0 (2629f3821) passes a saturated pixel of a signed image (INTx_MAX) through with its
value so XDS flags it against OVERLOAD; only the error marker (INTx_MIN signed, UINTx_MAX unsigned)
becomes -1. The legacy test still expected INT16_MAX -> -1. It now checks the saturated value
explicitly and also that module-edge (bit 30) and chip-edge (bit 31) mask pixels read as -2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
2026-10-08 12:36:40 +02:00

266 lines
8.9 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <catch2/catch_all.hpp>
#include "../common/DiffractionExperiment.h"
#include "../writer/FileWriter.h"
#include "../xds-plugin/plugin.h"
namespace {
void CleanupFiles(const std::string& prefix, int file_count) {
remove((prefix + "_master.h5").c_str());
for (int i = 1; i <= file_count; ++i) {
char data_file[256];
snprintf(data_file, sizeof(data_file), "%s_data_%06d.h5", prefix.c_str(), i);
remove(data_file);
}
}
void ClosePlugin() {
int error_flag = 0;
plugin_close(&error_flag);
REQUIRE(error_flag == 0);
}
}
TEST_CASE("XDSPlugin_GetData_VDS", "[HDF5][XDS][Plugin]") {
DiffractionExperiment x(DetJF(1));
x.FilePrefix("test_xds_vds").ImagesPerTrigger(3).ImagesPerFile(1).OverwriteExistingFiles(true);
x.SetFileWriterFormat(FileWriterFormat::NXmxVDS);
x.BitDepthImage(16).PixelSigned(false);
x.BeamX_pxl(100).BeamY_pxl(200).DetectorDistance_mm(150)
.IncidentEnergy_keV(WVL_1A_IN_KEV)
.FrameTime(std::chrono::microseconds(500), std::chrono::microseconds(10));
x.Compression(CompressionAlgorithm::NO_COMPRESSION);
RegisterHDF5Filter();
std::vector<uint32_t> pixel_mask(x.GetPixelsNum(), 0);
std::vector<uint16_t> image(x.GetPixelsNum(), 7);
image[0] = UINT16_MAX;
image[1] = 123;
{
StartMessage start_message;
x.FillMessage(start_message);
start_message.pixel_mask["default"] = pixel_mask;
FileWriter file_set(start_message);
for (int i = 0; i < 3; ++i) {
DataMessage message{};
image[5678] = static_cast<uint16_t>(200 + i);
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = i;
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
}
EndMessage end_message;
end_message.max_image_number = 3;
file_set.WriteHDF5(end_message);
file_set.Finalize();
}
{
int info[1024] = {};
int error_flag = 0;
REQUIRE_NOTHROW(plugin_open("test_xds_vds_master.h5", info, &error_flag));
REQUIRE(error_flag == 0);
int nx = 0, ny = 0, nbytes = 0, number_of_frames = 0;
float qx = 0.0f, qy = 0.0f;
REQUIRE_NOTHROW(plugin_get_header(&nx, &ny, &nbytes, &qx, &qy, &number_of_frames, info, &error_flag));
REQUIRE(error_flag == 0);
CHECK(nx == static_cast<int>(x.GetXPixelsNum()));
CHECK(ny == static_cast<int>(x.GetYPixelsNum()));
CHECK(nbytes == 2);
CHECK(number_of_frames == 3);
std::vector<int> data(static_cast<size_t>(nx) * static_cast<size_t>(ny), 0);
int frame_number = 2;
REQUIRE_NOTHROW(plugin_get_data(&frame_number, &nx, &ny, data.data(), info, &error_flag));
REQUIRE(error_flag == 0);
CHECK(data[0] == -1);
CHECK(data[1] == 123);
CHECK(data[5678] == 201);
ClosePlugin();
}
CleanupFiles("test_xds_vds", 3);
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
TEST_CASE("XDSPlugin_GetData_Legacy", "[HDF5][XDS][Plugin]") {
DiffractionExperiment x(DetJF(1));
x.FilePrefix("test_xds_legacy").ImagesPerTrigger(2).ImagesPerFile(1).OverwriteExistingFiles(true);
x.SetFileWriterFormat(FileWriterFormat::NXmxLegacy);
x.BitDepthImage(16).PixelSigned(true);
x.BeamX_pxl(100).BeamY_pxl(200).DetectorDistance_mm(150)
.IncidentEnergy_keV(WVL_1A_IN_KEV)
.FrameTime(std::chrono::microseconds(500), std::chrono::microseconds(10));
x.Compression(CompressionAlgorithm::NO_COMPRESSION);
RegisterHDF5Filter();
std::vector<uint32_t> pixel_mask(x.GetPixelsNum(), 0);
pixel_mask[3] = 1u << 30; // module edge
pixel_mask[4] = 1u << 31; // chip edge
std::vector<int16_t> image(x.GetPixelsNum(), 0);
image[0] = INT16_MAX; // saturated
image[1] = INT16_MIN; // error marker
image[2] = 456;
image[3] = 50;
image[4] = 60;
{
StartMessage start_message;
x.FillMessage(start_message);
start_message.pixel_mask["default"] = pixel_mask;
FileWriter file_set(start_message);
for (int i = 0; i < 2; ++i) {
DataMessage message{};
image[5678] = static_cast<int16_t>(10 + i);
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = i;
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
}
EndMessage end_message;
end_message.max_image_number = 2;
file_set.WriteHDF5(end_message);
file_set.Finalize();
}
{
int info[1024] = {};
int error_flag = 0;
REQUIRE_NOTHROW(plugin_open("test_xds_legacy_master.h5", info, &error_flag));
REQUIRE(error_flag == 0);
int nx = 0, ny = 0, nbytes = 0, number_of_frames = 0;
float qx = 0.0f, qy = 0.0f;
REQUIRE_NOTHROW(plugin_get_header(&nx, &ny, &nbytes, &qx, &qy, &number_of_frames, info, &error_flag));
REQUIRE(error_flag == 0);
REQUIRE(number_of_frames == 2);
std::vector<int> data(static_cast<size_t>(nx) * static_cast<size_t>(ny), 0);
int frame_number = 1;
REQUIRE_NOTHROW(plugin_get_data(&frame_number, &nx, &ny, data.data(), info, &error_flag));
REQUIRE(error_flag == 0);
// A saturated pixel of a signed image is passed through, so XDS sees it above OVERLOAD
CHECK(data[0] == INT16_MAX);
CHECK(data[1] == -1);
CHECK(data[2] == 456);
CHECK(data[3] == -2);
CHECK(data[4] == -2);
CHECK(data[5678] == 10);
frame_number = 2;
REQUIRE_NOTHROW(plugin_get_data(&frame_number, &nx, &ny, data.data(), info, &error_flag));
REQUIRE(error_flag == 0);
CHECK(data[5678] == 11);
ClosePlugin();
}
CleanupFiles("test_xds_legacy", 2);
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
TEST_CASE("XDSPlugin_GetData_Integrated", "[HDF5][XDS][Plugin]") {
DiffractionExperiment x(DetJF(1));
x.FilePrefix("test_xds_integrated").ImagesPerTrigger(3).OverwriteExistingFiles(true);
x.SetFileWriterFormat(FileWriterFormat::NXmxIntegrated);
x.BitDepthImage(16).PixelSigned(false);
x.BeamX_pxl(100).BeamY_pxl(200).DetectorDistance_mm(150)
.IncidentEnergy_keV(WVL_1A_IN_KEV)
.FrameTime(std::chrono::microseconds(500), std::chrono::microseconds(10));
x.Compression(CompressionAlgorithm::NO_COMPRESSION);
RegisterHDF5Filter();
AzimuthalIntegrationSettings azint_settings;
azint_settings.AzimuthalBinCount(4);
x.ImportAzimuthalIntegrationSettings(azint_settings);
// Unset high q = "as far as the detector reaches"; the experiment resolves it against the geometry.
azint_settings = x.GetAzimuthalIntegrationSettings();
std::vector<uint16_t> image(x.GetPixelsNum(), 0);
image[0] = UINT16_MAX;
image[1] = 321;
image[5678] = 777;
{
StartMessage start_message;
x.FillMessage(start_message);
start_message.pixel_mask["default"] = std::vector<uint32_t>(x.GetPixelsNum(), 0);
FileWriter file_set(start_message);
for (int i = 0; i < 3; ++i) {
DataMessage message{};
image[5678] = static_cast<uint16_t>(777 + i);
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = i;
message.az_int_profile = std::vector<float>(azint_settings.GetBinCount(), static_cast<float>(10 + i));
REQUIRE_NOTHROW(file_set.WriteHDF5(message));
}
EndMessage end_message;
end_message.max_image_number = 3;
file_set.WriteHDF5(end_message);
file_set.Finalize();
}
{
int info[1024] = {};
int error_flag = 0;
REQUIRE_NOTHROW(plugin_open("test_xds_integrated_master.h5", info, &error_flag));
REQUIRE(error_flag == 0);
int nx = 0, ny = 0, nbytes = 0, number_of_frames = 0;
float qx = 0.0f, qy = 0.0f;
REQUIRE_NOTHROW(plugin_get_header(&nx, &ny, &nbytes, &qx, &qy, &number_of_frames, info, &error_flag));
REQUIRE(error_flag == 0);
CHECK(nx == static_cast<int>(x.GetXPixelsNum()));
CHECK(ny == static_cast<int>(x.GetYPixelsNum()));
CHECK(qx == Catch::Approx(0.075));
CHECK(qy == Catch::Approx(0.075));
CHECK(nbytes == 2);
CHECK(number_of_frames == 3);
std::vector<int> data(static_cast<size_t>(nx) * static_cast<size_t>(ny), 0);
int frame_number = 3;
REQUIRE_NOTHROW(plugin_get_data(&frame_number, &nx, &ny, data.data(), info, &error_flag));
REQUIRE(error_flag == 0);
CHECK(data[0] == -1);
CHECK(data[1] == 321);
CHECK(data[5678] == 779);
ClosePlugin();
}
remove("test_xds_integrated_master.h5");
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}