Build Packages / Create release (push) Successful in 17s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m22s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m37s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m33s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 10m39s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 11m4s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 13m19s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 17m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 18m49s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 19m10s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m31s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 18m54s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m45s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m20s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m37s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m6s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 19m49s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 20m29s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m2s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m27s
Build Packages / Unit tests (push) Successful in 1h18m12s
* Rugnux: Performance improvements on GPU and CPU (more of the pre-scan and of scaling on the GPU, faster CPU spot finding and crystal refinement), with unchanged results. * Rugnux: More robust processing - patches of persistently hot pixels are masked, an inconsistent merge triggers a retry at the measured beam centre, and builds targeting different CPU levels give the same results. * Rugnux: Improved scaling and merging - reflections with an overloaded pixel are dropped, as in XDS, sparse rotation sweeps are scaled more reliably, and French-Wilson amplitudes use an anisotropic Wilson prior. * Rugnux: Improved space-group determination - glide planes in groups without a centre of symmetry, screw axes from short or weak axial rows kept when a higher group is adopted, and more reliable decisions on twinned and pseudo-symmetric crystals. * Rugnux: Improved small-molecule processing - spots that grow wider than the integration disk and split spots are integrated over their measured footprint, sparse lattices are integrated on every frame, and the `.hkl` file holds unmerged scaled reflections (SHELX HKLF 4). * Rugnux: Reads Rigaku d*TREK SMV images (Saturn CCD), including detector 2theta and encoded pixel overflows; home-source (rotating-anode) datasets were added to the validation battery. * jfjoch_viewer: Fixed processing failing at the end with "Wrong JPEG library version" on Linux; the merge window shows the space group with proper subscripts and a checklist of crystal pathologies. Reviewed-on: #84 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
116 lines
5.4 KiB
C++
116 lines
5.4 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, PixelMask(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);
|
|
}
|
|
|
|
// A saturated pixel (INT32_MAX, as the preprocessor marks it) at the peak of a spot: the reflection is
|
|
// not measured but kept, flagged overloaded, so that a rotation merge can drop its whole rocking event.
|
|
// The same spot without the saturated pixel is an ordinary measurement.
|
|
TEST_CASE("BraggIntegrationEngineCPU_SaturatedPeakIsFlaggedNotDropped", "[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 int cx = 600, cy = 300;
|
|
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)
|
|
image[(cy + dy) * width + cx + dx] += static_cast<int32_t>(std::lround(800.0 * std::exp(-(dx * dx + dy * dy) / (2 * 1.3 * 1.3))));
|
|
|
|
Reflection r{};
|
|
r.h = 1; r.k = 2; r.l = 3;
|
|
r.predicted_x = cx; r.predicted_y = cy;
|
|
r.d = 2.0f;
|
|
r.prescaling_corr = 1.0f;
|
|
r.partiality = 1.0f;
|
|
const std::vector<Reflection> predicted{r};
|
|
|
|
BraggIntegrationEngineCPU engine(experiment, PixelMask(experiment));
|
|
const auto clean = engine.Run(image, predicted, 1, 0);
|
|
REQUIRE(clean.size() == 1);
|
|
CHECK_FALSE(clean[0].overloaded);
|
|
|
|
image[cy * width + cx] = INT32_MAX;
|
|
const auto saturated = engine.Run(image, predicted, 1, 0);
|
|
REQUIRE(saturated.size() == 1);
|
|
CHECK(saturated[0].overloaded);
|
|
CHECK(saturated[0].clipped);
|
|
}
|