Images reach the buffer in order give or take a few, so one still missing when the buffer holds an image 100 past it is lost, not late: the read returns false instead of waiting for the end of the collection. The newest image number comes from ImageBuffer::GetStatus, which scans every slot under the buffer's lock, so it is asked only every 100 polls (~10 Hz). The test puts CBOR images into an ImageBuffer the way the receiver does and checks read-back of the pixels, waiting for an image not yet there, the lag limit, the end of the collection and the bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
3.8 KiB
C++
98 lines
3.8 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 <cstring>
|
|
#include <future>
|
|
#include <thread>
|
|
|
|
#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<int32_t> &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<int32_t> 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)));
|
|
}
|