Do not let a marCCD file name 2-theta as the scanned circle

The scanned circle was read as the first start/end pair that differs,
and two_theta is the first pair in the goniostat block. A file that
leaves its end block at zero while the detector sits on a swung-out
2-theta arm therefore named the arm as the spindle and stopped before
reaching phi; with no rotation range recorded either, the increment fell
back to -start_angle, which differs per frame, and the sweep was refused
outright.

The header's own rotation_axis index (offset 732) is taken where it
names a spindle circle, and the pair scan is the fallback for files that
never filled it in - read past two_theta, which no sweep turns.

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 19:02:12 +02:00
co-authored by Claude Opus 5
parent aecbcf3aaf
commit ba5b7cda81
+25 -12
View File
@@ -38,6 +38,7 @@ constexpr size_t OFF_END_XTAL_TO_DETECTOR = 728; // ... and at its end
constexpr size_t OFF_EXPOSURE_TIME = 656; // milliseconds
constexpr size_t OFF_START_TWOTHETA = 668; // millidegrees, and the seven that follow it
constexpr size_t OFF_END_TWOTHETA = 700; // the same seven, at the end of the exposure
constexpr size_t OFF_ROTATION_AXIS = 732; // which circle the sweep turned, as an index below
constexpr size_t OFF_ROTATION_RANGE = 736; // millidegrees
constexpr size_t OFF_PIXELSIZE_X = 772; // nanometres
constexpr size_t OFF_PIXELSIZE_Y = 776;
@@ -146,21 +147,33 @@ Header Parse(const std::vector<uint8_t> &h, const std::string &path) {
out.exposure_s = I32(h, OFF_EXPOSURE_TIME) * 1e-3;
out.saturated_value = I32(h, OFF_SATURATED);
// Which circle moved. The header also carries a rotation_axis index, but the start/end pair
// that differs is the same answer read off the angles themselves, and it stays right on a file
// whose index field was never filled in.
// Which circle moved. The header names it as an index into the circle list, and that is taken
// where it names a spindle circle: reading the start/end pairs alone takes the FIRST pair that
// differs, and two_theta is the first of them, so a file that left its end block at zero while
// the detector sat on a swung-out 2-theta arm had the arm named as the spindle - and, with no
// rotation range recorded either, an increment of -start_angle that differs on every frame,
// which SweepLayout then refuses the whole sweep over.
//
// The index is not always filled in, and a file that leaves it at zero cannot be told from one
// that means two_theta by it, so the fallback is the pairs as before - read past two_theta,
// which is a detector circle and never the one a sweep turns.
out.angle_increment_deg = I32(h, OFF_ROTATION_RANGE) * 1e-3;
for (size_t i = 0; i < N_CIRCLES; i++) {
const auto take_circle = [&](size_t i) {
const double start = I32(h, OFF_START_TWOTHETA + 4 * i) * 1e-3;
const double end = I32(h, OFF_END_TWOTHETA + 4 * i) * 1e-3;
if (start != end) {
out.axis_name = CIRCLE_NAMES[i];
out.start_angle_deg = start;
if (out.angle_increment_deg == 0.0)
out.angle_increment_deg = end - start;
break;
}
}
if (start == end)
return false;
out.axis_name = CIRCLE_NAMES[i];
out.start_angle_deg = start;
if (out.angle_increment_deg == 0.0)
out.angle_increment_deg = end - start;
return true;
};
const int32_t named = I32(h, OFF_ROTATION_AXIS);
if (named <= 0 || static_cast<size_t>(named) >= N_CIRCLES || !take_circle(static_cast<size_t>(named)))
for (size_t i = 1; i < N_CIRCLES; i++)
if (take_circle(i))
break;
return out;
}