Files
Jungfraujoch/tests/BraggIntegrationEngineCPUTest.cpp
T
leonarski_f 84228bf8be
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
v1.0.0-rc.173 (#83)
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports.
* jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls.
* Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results.
* Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable.
* Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate.
* Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do.
* Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence.
* Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags.
* Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check.
* Rugnux: Clear error messages when a data set needs more GPU or host memory than is available.

Reviewed-on: #83
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-29 15:57:32 +02:00

79 lines
3.7 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 <cmath>
#include <cstdint>
#include <vector>
#include "../common/BraggIntegrationSettings.h"
#include "../common/DetectorSetup.h"
#include "../common/DiffractionExperiment.h"
#include "../common/Reflection.h"
#include "../image_analysis/bragg_integration/BraggIntegrationEngineCPU.h"
#include "../image_analysis/image_preprocessing/ImagePreprocessorBuffer.h"
// One spot on a flat background, inside a grid of predictions 8 px apart that put no flux on the
// frame - the tails of reflections recorded on the frames either side of a finely sliced one. Their
// r2 regions cover every background ring, so whether the spot keeps a clean ring depends only on
// whether those predictions are allowed to mask it.
TEST_CASE("BraggIntegrationEngineCPU_NeighbourMaskFollowsPartiality", "[Integration][portable]") {
DiffractionExperiment experiment(DetJF(2));
experiment.DetectorDistance_mm(100.0f).IncidentEnergy_keV(WVL_1A_IN_KEV).BeamX_pxl(400.0f).BeamY_pxl(400.0f);
experiment.ImportBraggIntegrationSettings(BraggIntegrationSettings());
const size_t width = experiment.GetXPixelsNum(), npixel = experiment.GetPixelsNum();
const float cx = 600.3f, cy = 300.2f, amp = 800.0f, sigma = 1.3f;
ImagePreprocessorBuffer image(npixel);
for (size_t i = 0; i < npixel; ++i)
image[i] = 12;
for (int dy = -6; dy <= 6; ++dy)
for (int dx = -6; dx <= 6; ++dx) {
const int x = static_cast<int>(std::lround(cx)) + dx, y = static_cast<int>(std::lround(cy)) + dy;
const float ex = x - cx, ey = y - cy;
image[y * width + x] += static_cast<int32_t>(std::lround(amp * std::exp(-(ex * ex + ey * ey) / (2 * sigma * sigma))));
}
auto run = [&](float neighbour_partiality, BraggIntegrationCounts &counts) {
std::vector<Reflection> predicted;
for (int gy = -4; gy <= 4; ++gy)
for (int gx = -4; gx <= 4; ++gx) {
Reflection r{};
r.h = gx; r.k = gy; r.l = 1;
r.predicted_x = cx + 8.0f * gx;
r.predicted_y = cy + 8.0f * gy;
r.d = 2.0f;
r.prescaling_corr = 1.0f;
r.partiality = (gx == 0 && gy == 0) ? 0.5f : neighbour_partiality;
predicted.push_back(r);
}
BraggIntegrationEngineCPU engine(experiment);
const auto out = engine.Run(image, predicted, predicted.size(), 0);
counts = engine.Counts();
std::vector<Reflection> spot;
for (const auto &r : out)
if (r.h == 0 && r.k == 0)
spot.push_back(r);
return spot;
};
// Tails: no reflection is dropped, and the spot is measured against the clean flat background -
// while the density the widened-radius guard reads still sees every prediction.
BraggIntegrationCounts tails;
const auto spot = run(0.01f, tails);
CHECK(tails.bkg_starved == 0);
CHECK(tails.bkg_starved_by_neighbour > 0);
REQUIRE(spot.size() == 1);
CHECK(spot[0].bkg == Catch::Approx(12.0f));
CHECK(spot[0].I == Catch::Approx(6.2831853 * sigma * sigma * amp).epsilon(0.05));
// The same predictions with their flux on this frame do take the rings, the spot's among them, and
// it is the neighbours, not the detector, that starve them - the same rings the density counted.
BraggIntegrationCounts on_frame;
CHECK(run(1.0f, on_frame).empty());
CHECK(on_frame.bkg_starved > 0);
CHECK(on_frame.bkg_starved_by_neighbour == on_frame.bkg_starved);
CHECK(on_frame.bkg_starved_by_neighbour == tails.bkg_starved_by_neighbour);
}