Do not read the oscillation width as the rotation step

SweepLayout rejected any inter-frame difference below half the header's
oscillation width as read-back jitter. A series that steps by less than
it exposes - overlapping wedges, or a writer that puts the whole sweep's
range in that field - then had every difference rejected, no step was
found, and the files fell into the "series that never turns" branch,
which lays them out end to end and, unlike the gapped path, said
nothing. The jitter bound is now absolute (1e-4 deg, below any step an
instrument makes and above 32-bit float read-back noise), and the
end-to-end fallback warns when the angles do span a rotation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
This commit is contained in:
2026-09-20 18:59:59 +02:00
co-authored by Claude Opus 5
parent d2edd74089
commit 5c6dedea71
2 changed files with 41 additions and 4 deletions
+21
View File
@@ -153,3 +153,24 @@ TEST_CASE("SweepLayout_RepeatedAngleRefused") {
// Two sweeps of the same crystal concatenated: the second covers angles the first already has.
CHECK_THROWS_AS(sweep::Place(Series({0.0, 0.1, 0.2, 0.1, 0.2}, 0.1), "test"), JFJochException);
}
TEST_CASE("SweepLayout_StepFinerThanTheOscillationWidth") {
// The header field is the oscillation WIDTH, and a series can step by far less than it exposes -
// or, on some writers, hold the whole sweep's range in that field. Taking it for the step
// rejected every inter-frame difference as jitter: the sweep then read as one that never turns
// and was laid out end to end, every frame past the first at the wrong angle.
std::vector<double> angles;
for (int i = 0; i < 100; i++)
angles.push_back(30.0 + 0.1 * i);
const auto wedges = sweep::Place(Series(angles, 1.0), "test"); // 1 deg wedges, 0.1 deg apart
REQUIRE(wedges.files.size() == 100);
CHECK(wedges.present == 100);
CHECK(wedges.start_deg == Catch::Approx(30.0));
CHECK(wedges.increment_deg == Catch::Approx(0.1));
const auto total = sweep::Place(Series(angles, 10.0), "test"); // the field holds the range
REQUIRE(total.files.size() == 100);
CHECK(total.present == 100);
CHECK(total.increment_deg == Catch::Approx(0.1));
}