Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 22m36s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 30m21s
Build Packages / Build documentation (push) Successful in 45s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 30m22s
Build Packages / Validation Battery (push) Successful in 44m53s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 31m42s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 38m51s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 26m6s
Build Packages / Unit tests (push) Successful in 2h20m32s
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m41s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 26m37s
Build Packages / Generate python client (push) Successful in 1m3s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 11m41s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 17m13s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 26m16s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 32m10s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 30m12s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 30m19s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 33m53s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 33m7s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 35m27s
* jfjoch_broker (experimental): Serial data are merged live, with intermediate results during the collection. * Rugnux: More robust processing of serial data, including CCD detectors. * Rugnux: Parts of a rotation sweep that spoil the merge are removed automatically. * Rugnux: Clearer maps, and anomalous peaks not explained by the model are reported. * Rugnux: SHELX files work with SHELXL; XFEL pulse IDs are kept in `_process.h5`. * Web frontend: Spot-finding settings change only when the user changes them. * Rugnux: Faster processing. Reviewed-on: #94 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
128 lines
5.5 KiB
C++
128 lines
5.5 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 <atomic>
|
|
|
|
#include "../common/ImageBuffer.h"
|
|
#include "../receiver/StillsGPUAnalysis.h"
|
|
|
|
namespace {
|
|
DiffractionExperiment StillsExperiment() {
|
|
DiffractionExperiment x(DetJF(1));
|
|
x.ImagesPerTrigger(StillsGPUAnalysis::PREPASS_IMAGES + 200).NumTriggers(1).DetectorDistance_mm(100).BeamX_pxl(500).BeamY_pxl(250)
|
|
.IncidentEnergy_keV(12.4);
|
|
return x;
|
|
}
|
|
|
|
// A frame after the pre-pass's sample (numbers from PREPASS_IMAGES on are never in it), in a slot of `buffer`.
|
|
std::shared_ptr<HeldImage> Frame(ImageBuffer &buffer, int64_t number) {
|
|
auto held = std::make_shared<HeldImage>();
|
|
held->slot = buffer.GetImageSlot();
|
|
REQUIRE(held->slot != nullptr);
|
|
held->message.number = StillsGPUAnalysis::PREPASS_IMAGES + number;
|
|
held->message.image_collection_efficiency = 1.0f;
|
|
return held;
|
|
}
|
|
|
|
StartMessage Start(const DiffractionExperiment &x) {
|
|
StartMessage start;
|
|
x.FillMessage(start);
|
|
return start;
|
|
}
|
|
}
|
|
|
|
TEST_CASE("StillsGPUAnalysis_StartFailureReleasesThreads", "[StillsGPUAnalysis]") {
|
|
Logger logger(Catch::getResultCapture().getCurrentTestName());
|
|
const DiffractionExperiment x = StillsExperiment();
|
|
const PixelMask mask(x);
|
|
ImageBuffer buffer(64 * 1024 * 1024);
|
|
buffer.StartMeasurement(x);
|
|
RugnuxLiveStatus status;
|
|
|
|
std::atomic<int> failed = 0, sunk = 0;
|
|
{
|
|
StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 4, 100, logger,
|
|
[](const StillsGPUAnalysis::Setup &) {
|
|
throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Timeout on pushing start message");
|
|
},
|
|
[&](DataMessage &, const AzimuthalIntegrationProfile &) { ++sunk; },
|
|
[] { return DiffractionExperiment::DefaultDataProcessingSettings(); },
|
|
[&](const JFJochException &) { ++failed; }, &status);
|
|
for (int i = 0; i < 3; i++)
|
|
REQUIRE(gpu.Hold(Frame(buffer, i)));
|
|
REQUIRE_THROWS_AS(gpu.Finish(), JFJochException);
|
|
}
|
|
CHECK(failed == 1);
|
|
CHECK(sunk == 0);
|
|
CHECK(status.Get().state == RugnuxLiveState::Failed);
|
|
// Every slot the frames were held in is back.
|
|
CHECK(buffer.GetAvailSlots() == buffer.GetStatus().total_slots);
|
|
}
|
|
|
|
TEST_CASE("StillsGPUAnalysis_HoldIsBounded", "[StillsGPUAnalysis]") {
|
|
Logger logger(Catch::getResultCapture().getCurrentTestName());
|
|
const DiffractionExperiment x = StillsExperiment();
|
|
const PixelMask mask(x);
|
|
ImageBuffer buffer(64 * 1024 * 1024);
|
|
buffer.StartMeasurement(x);
|
|
REQUIRE(buffer.GetStatus().total_slots >= 4);
|
|
|
|
StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 2, 3, logger,
|
|
[](const StillsGPUAnalysis::Setup &) {
|
|
throw JFJochException(JFJochExceptionCategory::ZeroMQ, "no writer");
|
|
},
|
|
[](DataMessage &, const AzimuthalIntegrationProfile &) {},
|
|
[] { return DiffractionExperiment::DefaultDataProcessingSettings(); });
|
|
// The analysis waits for the pre-pass, which waits for its sample: nothing is taken off the queue.
|
|
for (int i = 0; i < 3; i++)
|
|
REQUIRE(gpu.Hold(Frame(buffer, i)));
|
|
{
|
|
const auto refused = Frame(buffer, 3);
|
|
CHECK(!gpu.Hold(refused));
|
|
}
|
|
CHECK(buffer.GetStatus().preparation_slots == 3);
|
|
REQUIRE_THROWS(gpu.Finish());
|
|
CHECK(buffer.GetStatus().preparation_slots == 0);
|
|
}
|
|
|
|
TEST_CASE("StillsGPUAnalysis_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalysis]") {
|
|
Logger logger(Catch::getResultCapture().getCurrentTestName());
|
|
const DiffractionExperiment x = StillsExperiment();
|
|
const PixelMask mask(x);
|
|
ImageBuffer buffer(64 * 1024 * 1024);
|
|
buffer.StartMeasurement(x);
|
|
RugnuxLiveStatus status;
|
|
|
|
std::atomic<int> sunk = 0;
|
|
StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 1, 100, logger,
|
|
[](const StillsGPUAnalysis::Setup &) {},
|
|
[&](DataMessage &m, const AzimuthalIntegrationProfile &) {
|
|
++sunk;
|
|
if (m.number == StillsGPUAnalysis::PREPASS_IMAGES)
|
|
throw std::runtime_error("not a JFJochException");
|
|
},
|
|
[] { return DiffractionExperiment::DefaultDataProcessingSettings(); },
|
|
{}, &status);
|
|
// The frames carry no image, so their analysis fails too - they are passed on all the same.
|
|
for (int i = 0; i < 3; i++)
|
|
REQUIRE(gpu.Hold(Frame(buffer, i)));
|
|
REQUIRE_NOTHROW(gpu.Finish());
|
|
CHECK(sunk == 3);
|
|
// The outcome waits for everything to be written, and comes after the latency.
|
|
CHECK(status.Get().state == RugnuxLiveState::Running);
|
|
CHECK(!status.Get().last_image_to_written_s);
|
|
gpu.Written();
|
|
const auto s = status.Get();
|
|
CHECK(s.state == RugnuxLiveState::Failed);
|
|
CHECK(!s.message.empty());
|
|
REQUIRE(s.last_image_to_written_s);
|
|
REQUIRE(s.last_image_s);
|
|
CHECK(s.elapsed_s >= s.last_image_s.value() + s.last_image_to_written_s.value() - 1e-6);
|
|
REQUIRE(!s.steps.empty());
|
|
CHECK(s.steps.back().title == "Writing");
|
|
CHECK(s.steps.back().duration_s);
|
|
CHECK(buffer.GetStatus().preparation_slots == 0);
|
|
}
|