The per-image ice score was read off the PLAIN azimuthal profile. That profile is a per-ring mean, so a few strong Bragg reflections landing in a ring's q bin lift it exactly as ice would. Measured over 37 rotation crystals, that did not merely add noise - it INVERTED the metric: the two highest-scoring crystals had no ice at all (4.23 and 4.06), while a clean control read 1.57. A decoy null - the identical statistic evaluated at q positions where hexagonal ice cannot be - reaches 1.51 at its 99th percentile and 2.70 at its maximum, so that metric cannot support any absolute threshold whatsoever. The adaptive spot finder already computes the right input for its own threshold: a sigma-clipped per-resolution-ring background, in the same bins. A powder ring is azimuthally smooth and survives the clip; Bragg peaks do not. On the clipped profile the clean population tightens to 1.00-1.22 and the crystals with confirmed ice sit at 2.08-2.37, against a decoy null that never exceeds 1.29. That channel is blind to one thing: ice in large crystallites diffracts as DISCRETE spots and leaves the radial profile flat. So a second channel counts found spots on the rings against the same q width of ice-free flanks beside them. The two barely overlap - the smooth-ice crystals read 2.1-2.4 / ~1.0 and the textured ones ~1.1 / 3.8-17.6, while a clean crystal reads 1.04 on both. Both are then used as a GATE (--ice-min-score 1.5, --ice-min-spot-ratio 2.0, both calibrated on the battery, 0 disables): the eleven fixed hexagonal bands cover 16-26 % of the unique reflections at typical resolutions whether or not the crystal has ice, so flagging, the exclusion from the scale fit and the merge-time CC1/2 ring mask are now all skipped when neither channel sees any. The gate is applied in the full pipeline and in --scale, which reads the stored per-image values back out of the _process.h5. Also fixes the merge-time mask's control: the shoulder now excludes reflections that are themselves on an ice ring. The rings are not evenly spaced - 1.947/1.916/1.882 A sit 0.05-0.06 apart in q - so for those three the [w,3w) shoulder landed squarely on the neighbours and the test compared ice against ice. Measured, that is the only thing this changes: it removes firings on those three rings and leaves every other firing's CC pair identical to three decimals. And the online ice half-width, which was 0.02 in the API against 0.03 offline, so the same data got a narrower band online than the measured ~0.06 ring FWHM justifies. Battery (37 rotation crystals, against the previous behaviour): space groups 34/37 in both and NO crystal's space group changes; 6 crystals gain unique reflections, 1 loses. Best of them gains 7082 unique reflections with R_meas 16.0 -> 14.3, CC1/2 95.9 -> 97.3 and ISa 13.7 -> 19.0; another goes R_meas 54.9 -> 42.9, CC1/2 84.0 -> 90.4, ISa 3.9 -> 5.5; a third reaches CC1/2 99.4 from 95.7 at an unchanged reflection count. The one crystal that loses reflections improves on both R_meas and CC1/2. Not done here: the ScanResult/API/plot-type/frontend/viewer layers for the new spot_count_ice_control (they need the OpenAPI regeneration). Message, CBOR, HDF5 write/read and the receiver plots are. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
597 lines
23 KiB
C++
597 lines
23 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
|
|
#include "../common/AzimuthalIntegrationProfile.h"
|
|
#include "../common/AzimuthalIntegrationMapping.h"
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_Constructor","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
|
|
REQUIRE(x.GetPixelsNum() == 2164*2068);
|
|
|
|
std::unique_ptr<AzimuthalIntegrationMapping> radial;
|
|
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 5);
|
|
|
|
PixelMask pixel_mask(x);
|
|
REQUIRE_NOTHROW(radial = std::make_unique<AzimuthalIntegrationMapping>(x, pixel_mask));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBinNumber","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
REQUIRE(mapping.GetBinNumber() == 39);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBinNumber_mask","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 9);
|
|
|
|
std::vector<uint32_t> pixel_mask(x.GetPixelsNum(), 0);
|
|
auto geom = x.GetDiffractionGeometry();
|
|
for (int row = 0; row < x.GetYPixelsNum(); row++) {
|
|
for (int col = 0; col < x.GetXPixelsNum(); col++) {
|
|
float d = geom.PxlToRes(col, row);
|
|
float q = 2 * M_PI / d;
|
|
if (q >= 3.1)
|
|
pixel_mask[row * x.GetXPixelsNum() + col] = 1;
|
|
}
|
|
}
|
|
|
|
PixelMask pixel_mask_obj(x);
|
|
pixel_mask_obj.LoadUserMask(x, pixel_mask);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask_obj);
|
|
REQUIRE(mapping.GetBinNumber() == 89);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBinNumber_DetectorLimit","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 9.9);
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
REQUIRE(mapping.GetBinNumber() == 98);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBinToQ","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
auto bin_to_q = mapping.GetBinToQ();
|
|
|
|
REQUIRE(bin_to_q.size() == 39);
|
|
CHECK(bin_to_q[0] == Catch::Approx(0.15));
|
|
CHECK(bin_to_q[1] == Catch::Approx(0.25));
|
|
CHECK(bin_to_q[15] == Catch::Approx(1.65));
|
|
CHECK(bin_to_q[38] == Catch::Approx(3.95));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBinToPhi","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
|
|
AzimuthalIntegrationSettings settings;
|
|
|
|
settings.QSpacing_recipA(0.1).QRange_recipA(0.1, 4.1).AzimuthalBinCount(4);
|
|
x.ImportAzimuthalIntegrationSettings(settings);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
auto &bin_to_q = mapping.GetBinToQ();
|
|
|
|
REQUIRE(bin_to_q.size() == 40 * 4);
|
|
CHECK(bin_to_q[0] == Catch::Approx(0.15));
|
|
CHECK(bin_to_q[1] == Catch::Approx(0.25));
|
|
CHECK(bin_to_q[15] == Catch::Approx(1.65));
|
|
CHECK(bin_to_q[38] == Catch::Approx(3.95));
|
|
|
|
auto &bin_to_phi = mapping.GetBinToPhi();
|
|
|
|
REQUIRE(bin_to_phi.size() == 40 * 4);
|
|
CHECK(bin_to_phi[0] == Catch::Approx(0));
|
|
CHECK(bin_to_phi[1] == Catch::Approx(0));
|
|
CHECK(bin_to_phi[38] == Catch::Approx(0));
|
|
CHECK(bin_to_phi[40] == Catch::Approx(90));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetBin","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
|
|
AzimuthalIntegrationSettings settings;
|
|
|
|
settings.QSpacing_recipA(0.1).QRange_recipA(0.1, 4.1).AzimuthalBinCount(4);
|
|
|
|
CHECK(settings.GetBin(0.11f, 0.0f) == 0);
|
|
CHECK(settings.GetBin(0.11f, 95.0f) == 40);
|
|
CHECK(settings.GetBin(0.11f, 185.0f) == 80);
|
|
CHECK(settings.GetBin(4.02f, 280.0f) == 120 + 39);
|
|
}
|
|
|
|
#include "../preview/JFJochTIFF.h"
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_GetMapping","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(100).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
|
|
AzimuthalIntegrationSettings settings;
|
|
|
|
settings.QSpacing_recipA(0.1).QRange_recipA(0.1, 4.1).AzimuthalBinCount(4);
|
|
x.ImportAzimuthalIntegrationSettings(settings);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
auto map = mapping.GetPixelToBin();
|
|
|
|
CompressedImage image(map, x.GetXPixelsNum(), x.GetYPixelsNum());
|
|
WriteTIFFToFile("test.tiff", image);
|
|
|
|
CHECK(map[x.GetXPixelsNum() * 500 + 1500] / 40 == 3);
|
|
CHECK(map[x.GetXPixelsNum() * 500 + 500] / 40 == 2);
|
|
CHECK(map[x.GetXPixelsNum() * 1500 + 500] / 40 == 1);
|
|
CHECK(map[x.GetXPixelsNum() * 1500 + 1500] / 40 == 0);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_QToBin","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
REQUIRE(mapping.QToBin(0.0) == 0);
|
|
REQUIRE(std::floor(mapping.QToBin(0.200001)) == 1);
|
|
REQUIRE(mapping.QToBin(0.6) == Catch::Approx(5));
|
|
REQUIRE(mapping.QToBin(50.0) == Catch::Approx(38));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
AzimuthalIntegrationProfile profile(mapping);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber()), sum2(mapping.GetBinNumber());
|
|
std::vector<uint32_t> count(mapping.GetBinNumber());
|
|
|
|
for (int i = 0; i < mapping.GetBinNumber(); i++) {
|
|
sum[i] = i * i * 4;
|
|
sum2[i] = i * i * i * i * 4;
|
|
count[i] = i;
|
|
}
|
|
REQUIRE_NOTHROW(profile.Add(sum, sum2, count));
|
|
REQUIRE_NOTHROW(profile.Add(sum, sum2, count));
|
|
|
|
std::vector<float> sum_wr(mapping.GetBinNumber() - 1);
|
|
REQUIRE_THROWS(profile.Add(sum_wr, sum2, count));
|
|
|
|
auto plot = profile.GetPlot();
|
|
REQUIRE(plot.GetPlots().size() == 1);
|
|
|
|
REQUIRE(plot.GetPlots()[0].x.size() == mapping.GetBinNumber());
|
|
REQUIRE(plot.GetPlots()[0].y.size() == mapping.GetBinNumber());
|
|
REQUIRE(plot.GetPlots()[0].x[0] == Catch::Approx(mapping.GetBinToQ()[0]));
|
|
REQUIRE(std::isnan(plot.GetPlots()[0].y[0]));
|
|
|
|
for (int i = 1; i < mapping.GetBinNumber(); i++) {
|
|
REQUIRE(plot.GetPlots()[0].x[i] == Catch::Approx(mapping.GetBinToQ()[i]));
|
|
REQUIRE(plot.GetPlots()[0].y[i] == Catch::Approx(i * 4));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_GetStd","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
AzimuthalIntegrationProfile profile(mapping);
|
|
|
|
REQUIRE(mapping.GetBinNumber() >= 4);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber()), sum2(mapping.GetBinNumber());
|
|
std::vector<uint32_t> count(mapping.GetBinNumber());
|
|
|
|
sum[0] = 2 + 3 + 4 + 5;
|
|
sum2[0] = 4 + 9 + 16 + 25;
|
|
count[0] = 4;
|
|
|
|
sum[1] = 1 + 1;
|
|
sum2[1] = 1 + 1;
|
|
count[1] = 2;
|
|
|
|
sum[2] = 1;
|
|
sum2[2] = 1;
|
|
count[2] = 1;
|
|
|
|
sum[3] = 0;
|
|
sum2[3] = 0;
|
|
count[3] = 0;
|
|
|
|
REQUIRE_NOTHROW(profile.Add(sum, sum2, count));
|
|
|
|
auto ret_mean = profile.GetResult();
|
|
auto ret_stddev = profile.GetStd();
|
|
auto ret_count = profile.GetPixelCount();
|
|
REQUIRE(ret_mean.size() == mapping.GetBinNumber());
|
|
REQUIRE(ret_stddev.size() == mapping.GetBinNumber());
|
|
REQUIRE(ret_count.size() == mapping.GetBinNumber());
|
|
|
|
CHECK(ret_mean[0] == Catch::Approx(3.5));
|
|
CHECK(ret_stddev[0] == Catch::Approx(std::sqrt(5.0/ 3.0)));
|
|
CHECK(ret_count[0] == 4);
|
|
|
|
CHECK(ret_mean[1] == Catch::Approx(1.0));
|
|
CHECK(ret_stddev[1] == 0.0f);
|
|
CHECK(ret_count[1] == 2);
|
|
|
|
CHECK(ret_mean[2] == Catch::Approx(1.0));
|
|
CHECK(std::isnan(ret_stddev[2]));
|
|
CHECK(ret_count[2] == 1);
|
|
|
|
CHECK(std::isnan(ret_mean[3]));
|
|
CHECK(std::isnan(ret_stddev[3]));
|
|
CHECK(ret_count[3] == 0);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_DimensionsMatchPixelToBin","[AzimuthalIntegration]") {
|
|
// The reported dimensions have to describe the image pixel_to_bin was built for, in both
|
|
// geometry modes - the adaptive spot finders walk the image with them and index pixel_to_bin.
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(80).BeamX_pxl(1030).BeamY_pxl(1080);
|
|
x.QSpacingForAzimInt_recipA(0.05).QRangeForAzimInt_recipA(0.05, 5.0);
|
|
|
|
PixelMask converted_mask(x);
|
|
AzimuthalIntegrationMapping converted(x, converted_mask);
|
|
CHECK(converted.GetWidth() * converted.GetHeight() == converted.GetPixelToBin().size());
|
|
|
|
x.GeometryTransformation(false);
|
|
PixelMask raw_mask(x);
|
|
AzimuthalIntegrationMapping raw(x, raw_mask);
|
|
CHECK(raw.GetWidth() * raw.GetHeight() == raw.GetPixelToBin().size());
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_GetStd_AfterClear","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
AzimuthalIntegrationProfile profile(mapping);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber()), sum2(mapping.GetBinNumber());
|
|
std::vector<uint32_t> count(mapping.GetBinNumber());
|
|
|
|
sum[0] = 2 + 3 + 4 + 5;
|
|
sum2[0] = 4 + 9 + 16 + 25;
|
|
count[0] = 4;
|
|
|
|
// The same frame twice, with a Clear() in between: a profile is reused for every image of a
|
|
// dataset, so the second image has to give exactly the first one's standard deviation.
|
|
profile.Add(sum, sum2, count);
|
|
const auto first = profile.GetStd();
|
|
|
|
profile.Clear(mapping);
|
|
profile.Add(sum, sum2, count);
|
|
const auto second = profile.GetStd();
|
|
|
|
CHECK(first[0] == Catch::Approx(std::sqrt(5.0 / 3.0)));
|
|
CHECK(second[0] == Catch::Approx(first[0]));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_ClearToLargerMapping","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping small(x, pixel_mask);
|
|
|
|
// A wider q range - the viewer re-uses one profile across datasets, so every vector Clear()
|
|
// touches has to end up the size the new mapping asks for.
|
|
x.QRangeForAzimInt_recipA(0.1, 9);
|
|
AzimuthalIntegrationMapping large(x, pixel_mask);
|
|
REQUIRE(large.GetBinNumber() > small.GetBinNumber());
|
|
|
|
AzimuthalIntegrationProfile profile(small);
|
|
profile.Clear(large);
|
|
|
|
std::vector<float> sum(large.GetBinNumber()), sum2(large.GetBinNumber());
|
|
std::vector<uint32_t> count(large.GetBinNumber());
|
|
|
|
// A bin that exists only in the wider mapping.
|
|
const auto bin = small.GetBinNumber();
|
|
sum[bin] = 2 + 3 + 4 + 5;
|
|
sum2[bin] = 4 + 9 + 16 + 25;
|
|
count[bin] = 4;
|
|
|
|
profile.Add(sum, sum2, count);
|
|
|
|
const auto stddev = profile.GetStd();
|
|
REQUIRE(stddev.size() == large.GetBinNumber());
|
|
CHECK(stddev[bin] == Catch::Approx(std::sqrt(5.0 / 3.0)));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_operatorAdd","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
AzimuthalIntegrationProfile profile0(mapping), profile1(mapping);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber()), sum2(mapping.GetBinNumber());
|
|
std::vector<uint32_t> count(mapping.GetBinNumber());
|
|
|
|
for (int i = 0; i < mapping.GetBinNumber(); i++) {
|
|
sum[i] = (i + 1) * i * 4;
|
|
sum2[i] = (i+ 1) * i * 5;
|
|
count[i] = i + 1;
|
|
}
|
|
REQUIRE_NOTHROW(profile0.Add(sum, sum2, count));
|
|
REQUIRE_NOTHROW(profile1 += profile0);
|
|
|
|
auto plot = profile1.GetPlot();
|
|
|
|
REQUIRE(plot.GetPlots().size() == 1);
|
|
REQUIRE(plot.GetPlots()[0].x.size() == mapping.GetBinNumber());
|
|
REQUIRE(plot.GetPlots()[0].y.size() == mapping.GetBinNumber());
|
|
for (int i = 0; i < mapping.GetBinNumber(); i++) {
|
|
REQUIRE(plot.GetPlots()[0].x[i] == Catch::Approx(mapping.GetBinToQ()[i]));
|
|
REQUIRE(plot.GetPlots()[0].y[i] == Catch::Approx(i * 4));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_GetMeanValueOfBins","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 4);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
|
|
AzimuthalIntegrationProfile profile(mapping);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber());
|
|
std::vector<float> sum2(mapping.GetBinNumber());
|
|
std::vector<uint32_t> count(mapping.GetBinNumber());
|
|
|
|
for (int i = 0; i < mapping.GetBinNumber(); i++) {
|
|
sum[i] = i * i * 4;
|
|
sum2[i] = i * i * i * i * 4;
|
|
count[i] = i;
|
|
}
|
|
REQUIRE_NOTHROW(profile.Add(sum, sum2, count));
|
|
|
|
REQUIRE(profile.GetMeanValueOfBins(0,2) == Catch::Approx((sum[0] + sum[1] + sum[2]) / double(count[0] + count[1] + count[2])));
|
|
REQUIRE(profile.GetMeanValueOfBins(5,7) == Catch::Approx((sum[5] + sum[6] + sum[7]) / double (count[5] + count[6] + count[7])));
|
|
|
|
x.BkgEstimateQRange_recipA(0.7, 0.8);
|
|
|
|
REQUIRE(profile.GetBkgEstimate(x.GetAzimuthalIntegrationSettings()) == Catch::Approx((sum[5] + sum[6] + sum[7]) / double (count[5] + count[6] + count[7])));
|
|
|
|
x.BkgEstimateQRange_recipA(0.01, 0.345);
|
|
REQUIRE(profile.GetBkgEstimate(x.GetAzimuthalIntegrationSettings()) == Catch::Approx((sum[0] + sum[1] + sum[2]) / double(count[0] + count[1] + count[2])));
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationProfile_GetResult1D","[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF4M());
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
|
|
AzimuthalIntegrationSettings settings;
|
|
settings.QSpacing_recipA(0.1f).QRange_recipA(0.1f, 0.4f).AzimuthalBinCount(3);
|
|
x.ImportAzimuthalIntegrationSettings(settings);
|
|
|
|
PixelMask pixel_mask(x);
|
|
AzimuthalIntegrationMapping mapping(x, pixel_mask);
|
|
AzimuthalIntegrationProfile profile(mapping);
|
|
|
|
REQUIRE(mapping.GetQBinCount() == 3);
|
|
REQUIRE(mapping.GetAzimuthalBinCount() == 3);
|
|
REQUIRE(mapping.GetBinNumber() == 9);
|
|
|
|
std::vector<float> sum(mapping.GetBinNumber(), 0.0f);
|
|
std::vector<float> sum2(mapping.GetBinNumber(), 20.0f);
|
|
std::vector<uint32_t> count(mapping.GetBinNumber(), 0);
|
|
|
|
// Layout is [azimuth][q], flattened:
|
|
// az0: q0 q1 q2
|
|
// az1: q0 q1 q2
|
|
// az2: q0 q1 q2
|
|
//
|
|
// Choose values so the correct collapsed result is easy to verify:
|
|
// q0 -> (10 + 20 + 30) / 3 = 20
|
|
// q1 -> (11 + 21 + 31) / 3 = 21
|
|
// q2 -> (12 + 22 + 32) / 3 = 22
|
|
sum[0] = 10; count[0] = 1; // az0 q0
|
|
sum[1] = 11; count[1] = 1; // az0 q1
|
|
sum[2] = 12; count[2] = 1; // az0 q2
|
|
|
|
sum[3] = 20; count[3] = 1; // az1 q0
|
|
sum[4] = 21; count[4] = 1; // az1 q1
|
|
sum[5] = 22; count[5] = 1; // az1 q2
|
|
|
|
sum[6] = 30; count[6] = 1; // az2 q0
|
|
sum[7] = 31; count[7] = 1; // az2 q1
|
|
sum[8] = 32; count[8] = 1; // az2 q2
|
|
|
|
REQUIRE_NOTHROW(profile.Add(sum, sum2, count));
|
|
|
|
auto result_1d = profile.GetResult1D();
|
|
|
|
REQUIRE(result_1d.size() == 3);
|
|
CHECK(result_1d[0] == Catch::Approx(20.0f));
|
|
CHECK(result_1d[1] == Catch::Approx(21.0f));
|
|
CHECK(result_1d[2] == Catch::Approx(22.0f));
|
|
}
|
|
|
|
template <class T>
|
|
static void RequireVectorsEqual(const std::vector<T> &ref,
|
|
const std::vector<T> &other,
|
|
const std::string &name,
|
|
int nthreads) {
|
|
INFO(name << ", threads=" << nthreads);
|
|
REQUIRE(ref.size() == other.size());
|
|
CHECK(memcmp(ref.data(), other.data(), sizeof(T) * ref.size()) == 0);
|
|
}
|
|
|
|
static void CheckAzimuthalIntegrationMappingThreadingExact(const DiffractionExperiment &experiment) {
|
|
PixelMask pixel_mask(experiment);
|
|
|
|
AzimuthalIntegrationMapping mapping_1(experiment, pixel_mask, 1);
|
|
AzimuthalIntegrationMapping mapping_2(experiment, pixel_mask, 2);
|
|
AzimuthalIntegrationMapping mapping_16(experiment, pixel_mask, 16);
|
|
AzimuthalIntegrationMapping mapping_0(experiment, pixel_mask, 0);
|
|
|
|
REQUIRE(mapping_1.GetBinNumber() == mapping_2.GetBinNumber());
|
|
REQUIRE(mapping_1.GetBinNumber() == mapping_16.GetBinNumber());
|
|
REQUIRE(mapping_1.GetBinNumber() == mapping_0.GetBinNumber());
|
|
|
|
REQUIRE(mapping_1.GetQBinCount() == mapping_2.GetQBinCount());
|
|
REQUIRE(mapping_1.GetQBinCount() == mapping_16.GetQBinCount());
|
|
REQUIRE(mapping_1.GetQBinCount() == mapping_0.GetQBinCount());
|
|
|
|
REQUIRE(mapping_1.GetAzimuthalBinCount() == mapping_2.GetAzimuthalBinCount());
|
|
REQUIRE(mapping_1.GetAzimuthalBinCount() == mapping_16.GetAzimuthalBinCount());
|
|
REQUIRE(mapping_1.GetAzimuthalBinCount() == mapping_0.GetAzimuthalBinCount());
|
|
|
|
RequireVectorsEqual(mapping_1.GetPixelToBin(), mapping_2.GetPixelToBin(), "pixel_to_bin", 2);
|
|
RequireVectorsEqual(mapping_1.GetPixelToBin(), mapping_16.GetPixelToBin(), "pixel_to_bin", 16);
|
|
RequireVectorsEqual(mapping_1.GetPixelToBin(), mapping_0.GetPixelToBin(), "pixel_to_bin", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.Resolution(), mapping_2.Resolution(), "resolution", 2);
|
|
RequireVectorsEqual(mapping_1.Resolution(), mapping_16.Resolution(), "resolution", 16);
|
|
RequireVectorsEqual(mapping_1.Resolution(), mapping_0.Resolution(), "resolution", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.Corrections(), mapping_2.Corrections(), "corrections", 2);
|
|
RequireVectorsEqual(mapping_1.Corrections(), mapping_16.Corrections(), "corrections", 16);
|
|
RequireVectorsEqual(mapping_1.Corrections(), mapping_0.Corrections(), "corrections", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.GetBinToQ(), mapping_2.GetBinToQ(), "bin_to_q", 2);
|
|
RequireVectorsEqual(mapping_1.GetBinToQ(), mapping_16.GetBinToQ(), "bin_to_q", 16);
|
|
RequireVectorsEqual(mapping_1.GetBinToQ(), mapping_0.GetBinToQ(), "bin_to_q", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.GetBinToD(), mapping_2.GetBinToD(), "bin_to_d", 2);
|
|
RequireVectorsEqual(mapping_1.GetBinToD(), mapping_16.GetBinToD(), "bin_to_d", 16);
|
|
RequireVectorsEqual(mapping_1.GetBinToD(), mapping_0.GetBinToD(), "bin_to_d", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.GetBinToTwoTheta(), mapping_2.GetBinToTwoTheta(), "bin_to_2theta", 2);
|
|
RequireVectorsEqual(mapping_1.GetBinToTwoTheta(), mapping_16.GetBinToTwoTheta(), "bin_to_2theta", 16);
|
|
RequireVectorsEqual(mapping_1.GetBinToTwoTheta(), mapping_0.GetBinToTwoTheta(), "bin_to_2theta", 0);
|
|
|
|
RequireVectorsEqual(mapping_1.GetBinToPhi(), mapping_2.GetBinToPhi(), "bin_to_phi", 2);
|
|
RequireVectorsEqual(mapping_1.GetBinToPhi(), mapping_16.GetBinToPhi(), "bin_to_phi", 16);
|
|
RequireVectorsEqual(mapping_1.GetBinToPhi(), mapping_0.GetBinToPhi(), "bin_to_phi", 0);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_Threading_FixedGeometry_2000x2000", "[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetDECTRIS(2000, 2000, "E16M", ""));
|
|
x.DetectorDistance_mm(50).BeamX_pxl(1000).BeamY_pxl(1000);
|
|
x.QSpacingForAzimInt_recipA(0.1).QRangeForAzimInt_recipA(0.1, 10);
|
|
x.PolarizationFactor(0.99f);
|
|
|
|
REQUIRE(x.IsGeometryTransformed());
|
|
|
|
CheckAzimuthalIntegrationMappingThreadingExact(x);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_Threading_RawGeometry_18Modules", "[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF9M());
|
|
x.Raw();
|
|
x.DetectorDistance_mm(100).BeamX_pxl(1500).BeamY_pxl(1500);
|
|
x.QSpacingForAzimInt_recipA(0.05).QRangeForAzimInt_recipA(0.1, 8.0);
|
|
x.PolarizationFactor(0.99f);
|
|
|
|
REQUIRE(!x.IsGeometryTransformed());
|
|
REQUIRE(x.GetModulesNum() == 18);
|
|
|
|
CheckAzimuthalIntegrationMappingThreadingExact(x);
|
|
}
|
|
|
|
TEST_CASE("AzimuthalIntegrationMapping_Threading_ConvertedGeometry_18Modules", "[AzimuthalIntegration]") {
|
|
DiffractionExperiment x(DetJF9M());
|
|
x.Conversion();
|
|
x.DetectorDistance_mm(100).BeamX_pxl(1500).BeamY_pxl(1500);
|
|
x.QSpacingForAzimInt_recipA(0.05).QRangeForAzimInt_recipA(0.1, 8.0);
|
|
x.PolarizationFactor(0.99f);
|
|
|
|
REQUIRE(x.IsGeometryTransformed());
|
|
REQUIRE(x.GetModulesNum() == 18);
|
|
|
|
CheckAzimuthalIntegrationMappingThreadingExact(x);
|
|
}
|
|
|
|
// The ice score on an explicit profile: flat means no ice (1.0), and a bump planted on a hexagonal
|
|
// ring is reported at its own height over the background. Uses the static entry point, which is what
|
|
// feeds the score the peak-excluded per-ring background instead of the plain profile.
|
|
TEST_CASE("AzimuthalIntegrationProfile_IceRingScore","[AzimuthalIntegration]") {
|
|
AzimuthalIntegrationSettings settings;
|
|
settings.QSpacing_recipA(0.01f).QRange_recipA(0.1f, 4.5f);
|
|
|
|
const int q_bins = settings.GetQBinCount();
|
|
REQUIRE(q_bins > 400);
|
|
|
|
auto bin_of = [&](float d_A) {
|
|
const float q = 6.283185307f / d_A;
|
|
return static_cast<int>(std::lround((q - settings.GetLowQ_recipA()) / settings.GetQSpacing_recipA() - 0.5f));
|
|
};
|
|
|
|
std::vector<float> profile(q_bins, 100.0f);
|
|
CHECK(AzimuthalIntegrationProfile::IceRingScore(profile, q_bins, settings, 0.03f) == Catch::Approx(1.0f));
|
|
|
|
// A bump at the 2.249 A hexagonal ring, on a background the running median still reads as 100.
|
|
profile[bin_of(2.249f)] = 250.0f;
|
|
CHECK(AzimuthalIntegrationProfile::IceRingScore(profile, q_bins, settings, 0.03f) == Catch::Approx(2.5f));
|
|
|
|
// A bump of the same size well away from every ring is not ice and must not be reported.
|
|
std::vector<float> off_ring(q_bins, 100.0f);
|
|
off_ring[bin_of(2.500f)] = 250.0f;
|
|
CHECK(AzimuthalIntegrationProfile::IceRingScore(off_ring, q_bins, settings, 0.03f) == Catch::Approx(1.0f));
|
|
}
|
|
|
|
// A profile given per (q, azimuth) bin is averaged over azimuth first, so a ring seen in every sector
|
|
// scores the same as the equivalent 1-D profile.
|
|
TEST_CASE("AzimuthalIntegrationProfile_IceRingScore_Azimuthal","[AzimuthalIntegration]") {
|
|
AzimuthalIntegrationSettings settings;
|
|
settings.QSpacing_recipA(0.01f).QRange_recipA(0.1f, 4.5f).AzimuthalBinCount(4);
|
|
|
|
const int q_bins = settings.GetQBinCount();
|
|
const float q = 6.283185307f / 2.249f;
|
|
const int ring = static_cast<int>(std::lround((q - settings.GetLowQ_recipA()) / settings.GetQSpacing_recipA() - 0.5f));
|
|
|
|
std::vector<float> flat(q_bins, 100.0f);
|
|
std::vector<float> sectors(static_cast<size_t>(q_bins) * 4, 100.0f);
|
|
flat[ring] = 250.0f;
|
|
for (int az = 0; az < 4; az++)
|
|
sectors[static_cast<size_t>(az) * q_bins + ring] = 250.0f;
|
|
|
|
CHECK(AzimuthalIntegrationProfile::IceRingScore(sectors, q_bins, settings, 0.03f)
|
|
== Catch::Approx(AzimuthalIntegrationProfile::IceRingScore(flat, q_bins, settings, 0.03f)));
|
|
}
|