Building a mapping evaluates the geometry of every pixel (resolution, azimuth, solid-angle and polarization corrections, bin) - about 2 CPU-s and 0.1-0.25 s of wall on a 16 Mpx detector - and a rotation run built seven, several for a geometry it had already evaluated: the first pass repeats the pre-scan's, and the canonical pass and the second of two concurrent probes repeat the probes'. None of it depends on the mask beyond skipping masked pixels, so AzimuthalIntegrationGeometryCache keeps the unmasked tables of the last geometry (keyed bit for bit on everything SetupPixel reads), and a mapping for the same geometry copies them and blanks its own masked pixels to what SetupPixel leaves there. One entry, shared by the run's copies; on myob four of the seven builds now evaluate, three copy (~40-120 ms against ~240+ ms on a loaded box). The pre-scan's mapping is only read by the spot measurement, so it is built at the start of that background task rather than on the main thread ahead of the projection read. It also fills the cache for the first pass. Output identical on the three GPU reference sweeps; a new test checks cached against fresh mappings under different masks and centres. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
633 lines
25 KiB
C++
633 lines
25 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);
|
|
}
|
|
|
|
// A mapping that takes its geometry from the cache - built there under another mask, or at another
|
|
// centre before - is the mapping built from scratch, bit for bit.
|
|
TEST_CASE("AzimuthalIntegrationMapping_GeometryCacheExact", "[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);
|
|
|
|
PixelMask plain_mask(x);
|
|
PixelMask user_mask(x);
|
|
std::vector<uint32_t> blocked(x.GetPixelsNum(), 0);
|
|
for (int row = 900; row < 1100; row++)
|
|
for (int col = 0; col < 1100; col++)
|
|
blocked[row * x.GetXPixelsNum() + col] = 1;
|
|
user_mask.LoadUserMask(x, blocked);
|
|
|
|
AzimuthalIntegrationGeometryCache cache;
|
|
const auto check = [&](const DiffractionExperiment &experiment, const PixelMask &mask) {
|
|
const AzimuthalIntegrationMapping ref(experiment, mask);
|
|
const AzimuthalIntegrationMapping cached(experiment, mask, cache);
|
|
RequireVectorsEqual(ref.GetPixelToBin(), cached.GetPixelToBin(), "pixel_to_bin", 0);
|
|
RequireVectorsEqual(ref.Resolution(), cached.Resolution(), "resolution", 0);
|
|
RequireVectorsEqual(ref.Corrections(), cached.Corrections(), "corrections", 0);
|
|
CHECK(ref.GetPixelToBinChecksum() == cached.GetPixelToBinChecksum());
|
|
CHECK(ref.GetCorrectionsChecksum() == cached.GetCorrectionsChecksum());
|
|
};
|
|
check(x, plain_mask); // computed
|
|
check(x, user_mask); // taken from the cache, under another mask
|
|
check(x, plain_mask);
|
|
|
|
DiffractionExperiment moved(x);
|
|
moved.BeamX_pxl(1003.5f);
|
|
check(moved, user_mask); // another centre: computed again
|
|
check(x, user_mask);
|
|
}
|
|
|
|
// 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)));
|
|
}
|