A rotation reflection arrives as a run of partials and is cut into rocking events wherever two successive frames are more than MAX_FRAME_GAP apart. That constant was 2.0 frames, in five places, and the quantity it stands for is not a frame count at all: it is an angle, a reflecting range of a tenth to half a degree. At a tenth of a degree per image two frames is a fifth of a degree of dead rotation and the bridge does what it was meant to; at a degree per image it is two degrees, as wide as a whole event, and two genuine crossings of the Ewald sphere are joined into one "full". Reconstructing the events on a paired control - the same photons sliced two ways - the fraction of events whose partialities sum past 1.5 is 14.3% at 1.0 degree against 6.0% at 0.1, the fused ones spanning a median of five frames. So the gap becomes half a degree of rotation, floored at one frame so an event is never cut at its own neighbours and capped at the two frames that were always allowed. For any oscillation of 0.25 degrees or less the quotient is at least two and the cap returns the literal 2.0f, so every finely sliced sweep is bridged exactly as before, on both the CPU and the GPU path. The value is taken once per run and carried to the device in CombineParams rather than recomputed in the kernel, so the two paths compare the same float by construction and the bit-parity contract the combine documents is preserved. The defect above is measured; the repair is not. No arm has been run with this change, which is why it is a commit of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
33 lines
1.5 KiB
C++
33 lines
1.5 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
#include "../common/Reflection.h"
|
|
#include "../common/CrystalLattice.h"
|
|
#include "../common/DiffractionGeometry.h"
|
|
|
|
struct IntegrationOutcome {
|
|
DiffractionGeometry geom;
|
|
CrystalLattice latt;
|
|
std::vector<Reflection> reflections;
|
|
std::optional<float> mosaicity_deg;
|
|
std::optional<float> image_scale_cc;
|
|
std::optional<int64_t> image_scale_cc_n;
|
|
std::optional<float> image_scale_g;
|
|
std::optional<float> image_scale_wedge_deg;
|
|
};
|
|
|
|
// How far apart, in frames, two partials of one raw hkl may sit and still belong to the same rocking
|
|
// event. The quantity being bridged is an ANGLE - a reflecting range, a tenth to half a degree - so
|
|
// spelling it as a frame count makes the bridge grow with the slicing: at 1 deg per frame the two
|
|
// frames that were always allowed are 2 deg of dead rotation, as wide as a whole event, and two
|
|
// genuine Ewald crossings fuse into one "full". Half a degree of bridge instead, floored at one frame
|
|
// (never cut an event at its own neighbours) and capped at the two frames that were always used. For
|
|
// any wedge of 0.25 deg or less the quotient is at least two and the cap returns the literal 2.0f, so
|
|
// finely sliced data is bridged exactly as before.
|
|
inline float RockingEventFrameGap(float wedge_deg) {
|
|
return std::min(2.0f, std::max(1.0f, 0.5f / wedge_deg));
|
|
} |