The spot-width stage of the pre-scan copied every preprocessed frame back to the host (72 MB a frame on a 16M detector, page-locked host mirrors per worker) for measurements that read a few pixels about each spot. Now: - MeasureSpotsOnTiles: the flux curves and the footprint read (2*24+1)^2 squares about each spot, gathered on the card (ImagePreprocessorBufferGPU::GatherTiles) - no copy_image_to_host, no host mirror. The flux curves are bounded at R_BKG_OUT = 20 px. The footprint keeps its arithmetic; a per-iteration check that its window is inside the square, and a spot whose window walks off it is measured again from scratch on an 80 px square, then on the whole frame. Where the squares would be more pixels than the frame, the frame is read. Same pixels in the same order, so the same bits. Harness over 34 rotation sets (~1500 frames): 0 differences; misses at 24 on a few spots of most frames, at 54 on up to 80% of frames of a few sets, at 80 on none. - The azimuthal mapping and the per-worker spot engines are built while the frames are read. - The tiers no longer wait for each other: workers run on through the visit order, each tier's answer is drawn from exactly its own frames, and frames measured past the tier the answer stops at are thrown away - a frame is measured the same whichever tier it is read for. - PreScanMemo keeps the spot measurement, the defective pixels and the background gain under the pre-scan key plus the spot-finding and azimuthal settings, so a copy of the run that pre-scans again on the same inputs reads no frames. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
57 lines
2.8 KiB
C++
57 lines
2.8 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <climits>
|
|
|
|
class ImagePreprocessorBuffer {
|
|
protected:
|
|
// The image size, kept apart from `buffer` so size() still answers when the host copy was not
|
|
// allocated at all (see the host_mirror constructor).
|
|
const size_t npixels;
|
|
std::vector<int32_t> buffer;
|
|
|
|
// host_mirror = false leaves `buffer` empty. On the GPU path the preprocessed image lives on the
|
|
// device and no CPU engine reads it back, so the four-bytes-per-pixel host copy would be
|
|
// allocated, zeroed and page-locked for nothing - 72 MB per worker on a 16M-pixel detector.
|
|
ImagePreprocessorBuffer(size_t npixels, bool host_mirror);
|
|
public:
|
|
explicit ImagePreprocessorBuffer(size_t npixels);
|
|
virtual ~ImagePreprocessorBuffer() = default;
|
|
|
|
// Standard CPU operation. Defined here rather than in the .cpp: these are called once per pixel by
|
|
// the CPU preprocessor, the CPU azimuthal integrator and the CPU spot finder, and the build has no
|
|
// link-time optimisation - so out of line they are a real call per pixel, and they stop the callers
|
|
// vectorising at all. Inlining them costs about a third of the per-image CPU work on every path
|
|
// that does not have a GPU, and leaves the results bit-identical.
|
|
std::vector<int32_t> &getBuffer() { return buffer; }
|
|
const std::vector<int32_t> &getBuffer() const { return buffer; }
|
|
int32_t &operator[](size_t i) { return buffer[i]; }
|
|
const int32_t &operator[](size_t i) const { return buffer[i]; }
|
|
size_t size() const { return npixels; }
|
|
|
|
int32_t *data() { return buffer.data(); }
|
|
const int32_t *data() const { return buffer.data(); }
|
|
|
|
// GPU operations (overriden in ImagePreprocessorBufferGPU
|
|
virtual int32_t *getGPUBuffer();
|
|
virtual const int32_t *getGPUBuffer() const;
|
|
|
|
// Values of the pixels with the given flat indices. Not just operator[] in a loop: on the GPU the
|
|
// preprocessed image can live in device memory only, and then this gathers them there.
|
|
virtual void Gather(const std::vector<uint32_t> &npixel, std::vector<int32_t> &values) const;
|
|
|
|
// The (2*half+1)^2 pixels about each (x[i], y[i]) of the image, `width` pixels wide: square after
|
|
// square, each row by row, with a pixel off the image read as INT32_MIN. Valid until the next call.
|
|
virtual const int32_t *GatherTiles(const std::vector<int32_t> &x, const std::vector<int32_t> &y,
|
|
int width, int half);
|
|
// The whole image on the host - on the GPU copied there first, where it was not already.
|
|
virtual const int32_t *HostImage();
|
|
private:
|
|
std::vector<int32_t> tiles;
|
|
};
|