v1.0.0-rc.173 (#83)
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
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
* 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>
This commit was merged in pull request #83.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
@@ -103,10 +104,54 @@ TEST_CASE("ShadowFinder_FindsAnInjectedBeamStop", "[ShadowFinder]") {
|
||||
|
||||
// Pinned from the serial implementation. A rewrite of the dilation, the hole fill or the ring
|
||||
// median that moves the mask by one pixel fails here, rather than in a merging statistic
|
||||
// several stages downstream.
|
||||
// several stages downstream. Nothing here lets part of the beam through, so the transmitting
|
||||
// shape may redraw the stop's edge but must not add to the mask: the count is the opaque one.
|
||||
CHECK(std::count(mask.begin(), mask.end(), 1u) == 2612);
|
||||
}
|
||||
|
||||
// A holder arm that lets part of the beam through, on a background bright enough that every pixel
|
||||
// reaches the reflection guard's count, crossing a module gap wider than the bridge. Each of the
|
||||
// three hid the arm on its own: the partly transmitting stretch never reaches SHADOW_RATIO, the gap
|
||||
// cut what did into pieces too small to be believed, and a count threshold read the attenuated
|
||||
// background under the arm as recorded reflections and gave every pixel of it back.
|
||||
TEST_CASE("ShadowFinder_FindsAnArmThatLetsPartOfTheBeamThrough", "[ShadowFinder]") {
|
||||
constexpr int32_t BRIGHT = 50; // even the arm reaches MIN_REFLECTION
|
||||
constexpr int ARM_HALF_WIDE = 15, GAP_X0 = 200, GAP_X1 = 216, OPAQUE_FROM_X = 232;
|
||||
|
||||
const DiffractionExperiment x = TestExperiment();
|
||||
const PixelMask pixel_mask(x);
|
||||
ShadowFinder finder(x, pixel_mask);
|
||||
|
||||
std::vector<std::vector<int32_t>> frames;
|
||||
std::vector<uint8_t> buffer;
|
||||
for (int f = 0; f < NFRAMES; f++) {
|
||||
frames.emplace_back(static_cast<size_t>(W) * H, BRIGHT);
|
||||
auto &frame = frames.back();
|
||||
for (int y = 0; y < H; y++)
|
||||
for (int xi = 0; xi < W; xi++) {
|
||||
const int dx = xi - C, dy = y - C;
|
||||
if (dx * dx + dy * dy <= STOP_R * STOP_R)
|
||||
frame[I(xi, y)] = 0;
|
||||
else if (dx >= 0 && std::abs(dy) <= ARM_HALF_WIDE)
|
||||
frame[I(xi, y)] = xi >= OPAQUE_FROM_X ? 0 : BRIGHT * 6 / 10;
|
||||
if (xi >= GAP_X0 && xi <= GAP_X1)
|
||||
frame[I(xi, y)] = INT32_MIN; // no data
|
||||
}
|
||||
DataMessage msg{};
|
||||
msg.image = CompressedImage(frame, W, H);
|
||||
finder.AddImage(msg, buffer);
|
||||
}
|
||||
|
||||
const auto mask = finder.GetMask();
|
||||
CHECK(mask[I(C, C)] == 1); // the stop
|
||||
CHECK(mask[I(GAP_X0 - 10, C)] == ShadowFinder::TRANSMITTING); // the arm where it transmits, before the gap
|
||||
CHECK(mask[I(GAP_X1 + 10, C + ARM_HALF_WIDE - 3)] == ShadowFinder::TRANSMITTING); // ... and after it
|
||||
CHECK(mask[I(W - 3, C)] != 0); // where it is opaque
|
||||
CHECK(mask[I(GAP_X1 + 10, C + ARM_HALF_WIDE + 25)] == 0); // and nothing beside it
|
||||
CHECK(mask[I(0, 0)] == 0);
|
||||
CHECK(mask[I(C - 60, C)] == 0);
|
||||
}
|
||||
|
||||
// The per-pixel passes are split across threads, so where the split falls must not be visible in the
|
||||
// answer. The x pass and the y pass of the dilation and of the pooled sum are written differently -
|
||||
// one a plain scan, the other blocked by column - so an x/y asymmetry is the plausible regression.
|
||||
@@ -338,6 +383,47 @@ TEST_CASE("ShadowFinder_APolarizedBackgroundIsNotAShadow", "[ShadowFinder]") {
|
||||
CHECK(mask_for(std::nullopt) > static_cast<long>(W) * H / 20);
|
||||
}
|
||||
|
||||
// The other side of that correction: a polarization factor that is not the beam's. Here the
|
||||
// background is unpolarized and the experiment states 0.99, so after the division every outer ring is
|
||||
// bright along x and dim along y - a smooth lobe below PENUMBRA_RATIO across the top and bottom of the
|
||||
// detector with nothing in the beam. On its own the lobe is never deep, but a small absorbing speck
|
||||
// inside it is, and the transmitting-hardware step joined the two into one piece "dim everywhere and
|
||||
// deep in places" and masked the whole lobe. The step compares a pixel with its ring's own second
|
||||
// harmonic, so the lobe is explained away and the speck, far below a shadow's size, stays unmasked.
|
||||
TEST_CASE("ShadowFinder_AWrongPolarizationFactorIsNotAnArm", "[ShadowFinder]") {
|
||||
constexpr float DISTANCE_MM = 6.0f;
|
||||
constexpr int LEVEL = 24;
|
||||
constexpr int SPECK_X = C, SPECK_Y = 20, SPECK_R = 11;
|
||||
|
||||
DiffractionExperiment x = TestExperiment();
|
||||
x.DetectorDistance_mm(DISTANCE_MM).PolarizationFactor(0.99f);
|
||||
const float pixel_mm = x.GetPixelSize_mm();
|
||||
|
||||
std::vector<int32_t> frame(static_cast<size_t>(W) * H);
|
||||
for (int y = 0; y < H; y++)
|
||||
for (int xi = 0; xi < W; xi++) {
|
||||
const double rho = std::hypot(xi - C, y - C) * pixel_mm;
|
||||
const double cos_2theta_sq = DISTANCE_MM * DISTANCE_MM / (DISTANCE_MM * DISTANCE_MM + rho * rho);
|
||||
frame[I(xi, y)] = static_cast<int32_t>(std::lround(LEVEL * 0.5 * (1.0 + cos_2theta_sq)));
|
||||
const int sx = xi - SPECK_X, sy = y - SPECK_Y;
|
||||
if (sx * sx + sy * sy <= SPECK_R * SPECK_R)
|
||||
frame[I(xi, y)] /= 10;
|
||||
}
|
||||
|
||||
const PixelMask pixel_mask(x);
|
||||
ShadowFinder finder(x, pixel_mask);
|
||||
std::vector<uint8_t> buffer;
|
||||
for (int f = 0; f < NFRAMES; f++) {
|
||||
DataMessage msg{};
|
||||
msg.image = CompressedImage(frame, W, H);
|
||||
finder.AddImage(msg, buffer);
|
||||
}
|
||||
const auto mask = finder.GetMask();
|
||||
CHECK(mask[I(C, 5)] == 0); // the dim lobe along y
|
||||
CHECK(mask[I(SPECK_X, SPECK_Y)] == 0); // the speck, too small to be a shadow
|
||||
CHECK(std::count(mask.begin(), mask.end(), 1u) == 0);
|
||||
}
|
||||
|
||||
// Inside the stop a whole ring is blocked, so its own median is blocked too and the per-pixel
|
||||
// comparison has nothing to work with; the walk that finds those rings is what covers the disk.
|
||||
// It decides by comparing a ring against what this detector's background typically is - and NOT
|
||||
|
||||
Reference in New Issue
Block a user