// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include #include "../reader/JFJochImageBufferReader.h" #include "../common/ZeroCopyReturnValue.h" #include "../frame_serialize/CBORStream2Serializer.h" // The receiver's way of putting an image in the buffer: serialize the message into a slot, hand it out. static void PutImage(ImageBuffer &buffer, const DiffractionExperiment &experiment, int64_t number, const std::vector &pixels) { DataMessage msg{}; msg.number = number; msg.image = CompressedImage(pixels, experiment.GetXPixelsNum(), experiment.GetYPixelsNum()); auto loc = buffer.GetImageSlot(); REQUIRE(loc != nullptr); CBORStream2Serializer serializer((uint8_t *) loc->GetImage(), experiment.GetImageBufferLocationSize()); serializer.SerializeImage(msg); loc->SetImageNumber(number); loc->SetImageSize(serializer.GetBufferSize()); loc->SetIndexed(false); loc->ReadyToSend(); loc->release(); } TEST_CASE("JFJochImageBufferReader", "[JFJochReader]") { DiffractionExperiment experiment(DetJF(1)); experiment.ImagesPerTrigger(200); ImageBuffer buffer(4 * experiment.GetImageBufferLocationSize()); buffer.StartMeasurement(experiment); StartMessage start{}; experiment.FillMessage(start); REQUIRE(start.number_of_images == 200); JFJochImageBufferReader reader(buffer); reader.Open(start); REQUIRE(reader.GetNumberOfImages() == 200); REQUIRE(reader.GetDataset()); const size_t npixel = experiment.GetXPixelsNum() * experiment.GetYPixelsNum(); std::vector pixels(npixel, 7); pixels[123] = 4567; PutImage(buffer, experiment, 0, pixels); JFJochReaderRawImage raw; REQUIRE(reader.ReadRawImage(0, raw)); REQUIRE(raw.image.GetWidth() == experiment.GetXPixelsNum()); REQUIRE(raw.image.GetHeight() == experiment.GetYPixelsNum()); REQUIRE(raw.image_buffer.size() == npixel * sizeof(int32_t)); int32_t value; memcpy(&value, raw.image.GetCompressed() + 123 * sizeof(int32_t), sizeof(int32_t)); REQUIRE(value == 4567); auto image = reader.LoadImage(0); REQUIRE(image); REQUIRE(image->ImageData().number == 0); REQUIRE_THROWS(reader.ReadRawImage(200, raw)); // An image not there yet is waited for: the read returns once the receiver puts it in. auto pending = std::async(std::launch::async, [&] { JFJochReaderRawImage r; return reader.ReadRawImage(1, r); }); REQUIRE(pending.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout); PutImage(buffer, experiment, 1, pixels); REQUIRE(pending.wait_for(std::chrono::seconds(5)) == std::future_status::ready); REQUIRE(pending.get()); // Images arrive in order, give or take a few: one still missing when an image MAX_IMAGE_LAG past // it is there is lost, while one nearer may still come. PutImage(buffer, experiment, 150, pixels); auto lost = std::async(std::launch::async, [&] { JFJochReaderRawImage r; return reader.ReadRawImage(150 - JFJochImageBufferReader::MAX_IMAGE_LAG, r); }); REQUIRE(lost.wait_for(std::chrono::seconds(5)) == std::future_status::ready); REQUIRE(!lost.get()); // After the end, an image that never came is not waited for. auto missing = std::async(std::launch::async, [&] { JFJochReaderRawImage r; return reader.ReadRawImage(150 - JFJochImageBufferReader::MAX_IMAGE_LAG + 1, r); }); REQUIRE(missing.wait_for(std::chrono::milliseconds(300)) == std::future_status::timeout); reader.Ended(); REQUIRE(missing.wait_for(std::chrono::seconds(5)) == std::future_status::ready); REQUIRE(!missing.get()); REQUIRE(buffer.Finalize(std::chrono::microseconds(1))); }