From 2978a9f92888ad75c5a43c3c1f28a2c4278907ef Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 8 Nov 2026 11:15:25 +0100 Subject: [PATCH] receiver: a rotation run the image buffer cannot hold gets the per-image analysis Live rugnux reads the images back from the image buffer, so a rotation collection with more images than slots was only forwarded: no spots, no scan result, no pixel sum. JFJochReceiverService now sees this when the run starts and picks JFJochReceiverLite instead. The rugnux live status is Skipped with the reason, and the receiver output carries the warning for the broker's status, as before. The slot count is from the experiment as set up; the rotation receiver keeps its own check for a stream whose start message changes the image size. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- receiver/JFJochReceiverService.cpp | 24 +++++++++++++++++--- receiver/JFJochReceiverService.h | 1 + tests/JFJochReceiverRugnuxTest.cpp | 36 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/receiver/JFJochReceiverService.cpp b/receiver/JFJochReceiverService.cpp index 47c1b2743..6a8022a47 100644 --- a/receiver/JFJochReceiverService.cpp +++ b/receiver/JFJochReceiverService.cpp @@ -101,6 +101,7 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, try { // First clean-up old measurement receiver.reset(); + analysis_warning.clear(); if (fpga_puller) { image_puller.reset(); fpga_puller.reset(); @@ -152,6 +153,19 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, else image_puller = std::make_shared(experiment.GetDetectorSetup().GetDECTRISStream2Addr()); + bool rugnux_rotation = rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard) + && RugnuxRotationData(experiment); + // Rugnux reads the images back from the buffer: a collection that does not fit gets the per-image analysis instead + const int64_t slots = image_buffer.GetBufferSize() / experiment.GetImageBufferLocationSize(); + if (rugnux_rotation && (slots < experiment.GetImageNum())) { + analysis_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection " + "has {}", slots, experiment.GetImageNum()); + logger.Warning("{}", analysis_warning); + rugnux_status.Start(experiment.GetImageNum(), {}, std::chrono::steady_clock::now()); + rugnux_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); + rugnux_rotation = false; + } + // The dark frames of the mask sequence have a receiver of their own, with rugnux on as well. if (experiment.GetDetectorMode() == DetectorMode::DarkMask) receiver = std::make_unique(experiment, @@ -164,13 +178,14 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, receiver_status, plots, image_buffer); - else if (rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard) && RugnuxRotationData(experiment)) + else if (rugnux_rotation) receiver = std::make_unique( experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads, std::max(1, static_cast(AvailableCpus()) - rugnux_reserved_cpus), rugnux_model, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, image_buffer, zmq_preview_socket.get(), zmq_metadata_socket.get()); - else if (rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard)) + else if (rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard) + && !RugnuxRotationData(experiment)) receiver = std::make_unique( experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads, rugnux_model, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, @@ -235,7 +250,10 @@ JFJochReceiverOutput JFJochReceiverService::Stop() { logger.Warning("Request to stop while receiver not running"); throw JFJochException(JFJochExceptionCategory::WrongReceiverState, "Receiver idle, cannot stop"); } - return receiver->GetFinalStatistics(); + auto ret = receiver->GetFinalStatistics(); + if (!analysis_warning.empty()) + ret.analysis_warning = analysis_warning; + return ret; } void JFJochReceiverService::SetSpotFindingSettings(const SpotFindingSettings &settings) { diff --git a/receiver/JFJochReceiverService.h b/receiver/JFJochReceiverService.h index 8a580ae65..beabb604c 100644 --- a/receiver/JFJochReceiverService.h +++ b/receiver/JFJochReceiverService.h @@ -35,6 +35,7 @@ class JFJochReceiverService { int rugnux_reserved_cpus = 4; // left to the forward threads and the pusher double rugnux_deadline_s = 60; // the analysis is cut this long after the last image (0 = never) RugnuxLiveStatus rugnux_status; // of the last collection, kept after it ends + std::string analysis_warning; // why the collection was not analysed as asked, for the broker's status // A PSI detector's frames go through FPGAImagePuller and the stream receivers. Off unless asked for. bool fpga_image_puller = false; diff --git a/tests/JFJochReceiverRugnuxTest.cpp b/tests/JFJochReceiverRugnuxTest.cpp index 4acc7e11b..def6341d9 100644 --- a/tests/JFJochReceiverRugnuxTest.cpp +++ b/tests/JFJochReceiverRugnuxTest.cpp @@ -619,6 +619,42 @@ TEST_CASE("JFJochReceiverRugnux_Rotation", "[JFJochReceiverRugnux]") { CHECK(live.state != RugnuxLiveState::Running); } +TEST_CASE("JFJochReceiverRugnux_Rotation_DoesNotFit", "[JFJochReceiverRugnux]") { + // A scan the image buffer cannot hold is analysed image by image, and the live status says why rugnux did not run. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + // Three slots + const size_t buffer_MiB = (3 * x.GetImageBufferLocationSize()) / (1024 * 1024) + 1; + REQUIRE(buffer_MiB * 1024 * 1024 / x.GetImageBufferLocationSize() < images); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher, buffer_MiB); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.status.images_collected == images); + CHECK(out.scan_result.images.size() == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + CHECK(out.analysis_warning.find("image buffer") != std::string::npos); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Skipped); + CHECK(live.message.find("image buffer") != std::string::npos); +} + TEST_CASE("JFJochReceiverRugnux_Rotation_Cancel", "[JFJochReceiverRugnux]") { Logger logger(Catch::getResultCapture().getCurrentTestName()); RegisterHDF5Filter();