Files
Jungfraujoch/image_analysis/IntegrationOutcome.h
T
leonarski_fandClaude Opus 5 ca9a21c45e rugnux: keep a pass's per-image reflections in large blocks
The whole-run passes retain every frame's integrated reflections until scaling is done - thousands
of vectors of a few megabytes each, allocated by the image workers in the allocator's per-thread
arenas. When a pass hands them back, most of that memory stays in those arenas as holes, and the
next pass's workers (new threads) do not reuse it, so on a fine-sliced long axis gigabytes of freed
reflections were carried to the end of the run.

IndexAndRefine now copies each retained frame's reflections into a ReflectionArena: 64 MiB blocks,
each its own mapping, carved by a bump pointer and returned to the system in one piece when the
last vector in them is gone. IntegrationOutcome::reflections becomes a std::vector with an allocator
that uses the arena when given one and plain new/delete otherwise (copies go to the heap), so the
read sites are unchanged; the few functions that took the vector by type now take a span.

No arithmetic changes; merged output byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
2026-09-20 18:45:17 +02:00

38 lines
1.7 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"
#include "ReflectionArena.h"
// A frame's integrated reflections. The whole-run passes keep them in a ReflectionArena (see there);
// anywhere else the allocator is plain new/delete.
using ReflectionVector = std::vector<Reflection, ArenaAllocator<Reflection>>;
struct IntegrationOutcome {
DiffractionGeometry geom;
CrystalLattice latt;
ReflectionVector 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));
}