calibration: fix four ways the powder fit quietly loses its input
None of these changes the answer on a well-separated cubic standard - the LaB6 distance series is bit-identical by both methods - but each one is a case where input is dropped or mis-assigned without saying so. The circumcentre vote grid was a fixed 4000x4000 box, and the caller never passed anything else. That allocated 128 MB whatever the detector, and on a detector larger than 4000 px in either direction it put the beam centre outside the grid, so every vote was discarded and the guess failed with "Beam center not found". Span the spots' own bounding box instead: a powder ring encloses its centre, so that is where the answer has to be. uint32 votes while there - the most any bin can take is C(500,3). Spots were assigned to the FIRST calibrant ring within a fixed 0.1 1/A, not the nearest. Silver behenate's orders sit 0.108 1/A apart and hexagonal ice has three rings inside 0.06, so for those two standards the window reaches the neighbour and every point lands on the lower-q ring of the pair, biasing the distance. Take the nearest ring, and clamp the window to half the gap to the neighbour - which is what the profile path already did inline, now shared as RingMatchWindow and covered by a test that checks it actually narrows on the crowded standards and not on LaB6. A profile bin no pixel fell in is NaN. SectorPeakQ dropped such a sector by accident, through NaN comparisons falling false; check the four background bins and return explicitly. Ice-ring handling is switched off in calibration mode. Flagged spots are sorted last by the spot budget and so discarded first, which for --calibrant ice throws away exactly what is being calibrated on. The two per-ring std::cout lines in GuessGeometry are gone: a library has no business writing to a terminal, and constructing a Logger to keep them would emit a version banner from inside a fit. What matched belongs in the result struct, which the quality gating still to come needs anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NfuDvf5ipV3Hi8TiCUKD27
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "../common/Definitions.h"
|
||||
#include "../common/JFJochMath.h"
|
||||
#include "../image_analysis/geom_refinement/AssignSpotsToRings.h"
|
||||
#include "../image_analysis/geom_refinement/Calibrants.h"
|
||||
#include "../image_analysis/geom_refinement/PowderCalibration.h"
|
||||
|
||||
@@ -128,3 +129,42 @@ TEST_CASE("Calibration_PoniFileAxisConvention", "[DetGeomCalib]") {
|
||||
+ std::to_string(x.GetXPixelsNumConv()) + "]";
|
||||
CHECK(keys["Detector_config"].find(shape) != std::string::npos);
|
||||
}
|
||||
|
||||
// The match window may never reach the neighbouring ring, for any calibrant. Where two rings are closer
|
||||
// together than twice the nominal window, a fixed window takes in the neighbour's flank - which the
|
||||
// rings path reads as this ring's background, and which the spots path (before it took the NEAREST ring)
|
||||
// resolved by assigning both to the lower-q one.
|
||||
TEST_CASE("Calibration_RingMatchWindowNeverReachesTheNeighbour", "[DetGeomCalib]") {
|
||||
for (const auto &c : Calibrants()) {
|
||||
const auto q = CalibrantRings(c.name);
|
||||
REQUIRE(q.size() > 1);
|
||||
for (size_t i = 0; i < q.size(); ++i) {
|
||||
const float w = RingMatchWindow(q, i, RING_MATCH_Q_RECIPA);
|
||||
CHECK(w <= RING_MATCH_Q_RECIPA);
|
||||
CHECK(w > 0.0f);
|
||||
if (i > 0)
|
||||
CHECK(q[i] - w >= 0.5f * (q[i] + q[i - 1]) - 1e-6f);
|
||||
if (i + 1 < q.size())
|
||||
CHECK(q[i] + w <= 0.5f * (q[i] + q[i + 1]) + 1e-6f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...and the clamp is not a no-op. Silver behenate's orders sit about 0.108 1/A apart and hexagonal ice
|
||||
// has rings inside 0.06, so both are narrowed below the nominal window - while LaB6, whose rings are
|
||||
// well separated at low q, keeps it. Without a standard that actually crowds, the test above would pass
|
||||
// on a clamp that never fired.
|
||||
TEST_CASE("Calibration_CrowdedStandardsNarrowTheWindow", "[DetGeomCalib]") {
|
||||
auto narrowed = [](const std::string &name) {
|
||||
const auto q = CalibrantRings(name);
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < q.size(); ++i)
|
||||
if (RingMatchWindow(q, i, RING_MATCH_Q_RECIPA) < RING_MATCH_Q_RECIPA) ++n;
|
||||
return n;
|
||||
};
|
||||
CHECK(narrowed("agbh") > 0);
|
||||
CHECK(narrowed("ice") > 0);
|
||||
// The innermost LaB6 rings are more than 0.2 1/A apart, so nothing narrows them.
|
||||
const auto lab6 = CalibrantRings("lab6");
|
||||
CHECK(RingMatchWindow(lab6, 0, RING_MATCH_Q_RECIPA) == Catch::Approx(RING_MATCH_Q_RECIPA));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user