Spot finding works panel by panel, as the FPGA path does per JUNGFRAU module. PanelBoxes (common/) gives one box per DetectorModule; an empty table gives one box, the whole image, so every clamp reduces to the old image bounds in the same evaluation order (rotation sets, EIGER, CCDs, our JUNGFRAU unchanged). - ImageSpotFinder::SetPanels: the CPU local-box pass (dense, count-threshold and the DetectAt/candidate path of the adaptive finder) slides its windows inside each panel box; host CCL labels panel by panel; pixels on no panel are folded into the resolution mask. - GPU: analyze_pixel tiles each panel (warps numbered panel after panel), analyze_candidates and union_neighbours clip to the pixel's panel, the device spot carries its panel; the GPU list is put in panel order so it equals the host's. - DiffractionSpot carries its panel; SpotAnalyze maps by ImageToPlane(panel, ...) (d_A on the plane). - ImagePreprocessorBuffer::GatherTiles (CPU+GPU) clips each square to the panel of its centre. - MXAnalysisWithoutFPGA and the pre-scan workers hand the module table to finders and buffers. Raster algorithms: new common/AssembleToPlane.h (PlaneRasterOf, AssembleToPlane nearest-pixel gather with gaps, GatherFromPlane back to stored pixels; empty table = copy). With panels, ShadowFinder::GetMask runs on the projection assembled to the plane raster and gathers the mask back per stored pixel (CPU route); FindBeamCenterFFT scores the assembled projection and reports plane coordinates; the detector diagnostic JPEG shows the assembled plane. Without panels all three take their old path. Harness: PanelLayout_SpotsNeverCrossPanels enabled (count threshold and SNR on the edge scene), new PanelLayout_SpotFinderParity (CPU vs GPU, local box and adaptive, on the stacked layout), PanelLayout_AssembleToPlane, PanelLayout_ShadowFinder, PanelLayout_BeamCenterFFT, PanelLayout_DetectorDiagnostic (stacked vs assembled image cut to the raster). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
117 lines
5.0 KiB
Plaintext
117 lines
5.0 KiB
Plaintext
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "ImagePreprocessorBufferGPU.h"
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
|
|
static inline void cuda_err(cudaError_t val) {
|
|
if (val != cudaSuccess)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, cudaGetErrorString(val));
|
|
}
|
|
|
|
__global__ void gather_kernel(const int32_t *__restrict__ image,
|
|
const uint32_t *__restrict__ npixel,
|
|
int32_t *__restrict__ values,
|
|
int count) {
|
|
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < count; i += blockDim.x * gridDim.x)
|
|
values[i] = image[npixel[i]];
|
|
}
|
|
|
|
// centre holds six values per tile: its centre x, y and the box x0, y0, x1, y1 it is clipped to.
|
|
__global__ void gather_tiles_kernel(const int32_t *__restrict__ image, int width,
|
|
const int32_t *__restrict__ centre, int half,
|
|
int32_t *__restrict__ tiles, size_t count) {
|
|
const int side = 2 * half + 1;
|
|
const size_t area = static_cast<size_t>(side) * side;
|
|
for (size_t i = blockIdx.x * blockDim.x + threadIdx.x; i < count; i += static_cast<size_t>(blockDim.x) * gridDim.x) {
|
|
const size_t tile = i / area;
|
|
const int p = static_cast<int>(i - tile * area);
|
|
const int32_t *c = centre + 6 * tile;
|
|
const int px = c[0] - half + p % side;
|
|
const int py = c[1] - half + p / side;
|
|
tiles[i] = (px < c[2] || py < c[3] || px >= c[4] || py >= c[5])
|
|
? INT32_MIN : image[static_cast<size_t>(py) * width + px];
|
|
}
|
|
}
|
|
|
|
ImagePreprocessorBufferGPU::ImagePreprocessorBufferGPU(size_t npixel, bool host_mirror)
|
|
: ImagePreprocessorBuffer(npixel, host_mirror),
|
|
gpu_image(npixel),
|
|
// A no-op when the mirror was not allocated: CudaRegisteredVector skips an empty vector.
|
|
buffer_reg(buffer),
|
|
max_gather(StrongPixelLimit(npixel)),
|
|
gpu_gather_index(max_gather),
|
|
gpu_gather_value(max_gather) {
|
|
}
|
|
|
|
int32_t *ImagePreprocessorBufferGPU::getGPUBuffer() {
|
|
return gpu_image;
|
|
}
|
|
|
|
const int32_t *ImagePreprocessorBufferGPU::getGPUBuffer() const {
|
|
return gpu_image;
|
|
}
|
|
|
|
void ImagePreprocessorBufferGPU::Gather(const std::vector<uint32_t> &npixel, std::vector<int32_t> &values) const {
|
|
values.resize(npixel.size());
|
|
if (npixel.empty())
|
|
return;
|
|
|
|
const int count = static_cast<int>(npixel.size());
|
|
cudaMemcpyAsync(gpu_gather_index.get(), npixel.data(), count * sizeof(uint32_t),
|
|
cudaMemcpyHostToDevice, gather_stream);
|
|
gather_kernel<<<(count + 255) / 256, 256, 0, gather_stream>>>(
|
|
gpu_image.get(), gpu_gather_index.get(), gpu_gather_value.get(), count);
|
|
cuda_err(cudaGetLastError());
|
|
cudaMemcpyAsync(values.data(), gpu_gather_value.get(), count * sizeof(int32_t),
|
|
cudaMemcpyDeviceToHost, gather_stream);
|
|
cudaStreamSynchronize(gather_stream);
|
|
}
|
|
|
|
const int32_t *ImagePreprocessorBufferGPU::GatherTiles(const std::vector<int32_t> &x, const std::vector<int32_t> &y,
|
|
int width, int half) {
|
|
const size_t side = 2 * half + 1;
|
|
const size_t count = x.size() * side * side;
|
|
if (count == 0)
|
|
return nullptr;
|
|
if (count > tile_capacity) {
|
|
tile_capacity = std::max(count, 2 * tile_capacity);
|
|
gpu_tiles = CudaDevicePtr<int32_t>(tile_capacity);
|
|
host_tiles.resize(tile_capacity);
|
|
}
|
|
if (6 * x.size() > tile_centre.size()) {
|
|
tile_centre.resize(std::max(6 * x.size(), 2 * tile_centre.size()));
|
|
gpu_tile_centre = CudaDevicePtr<int32_t>(tile_centre.size());
|
|
}
|
|
for (size_t i = 0; i < x.size(); i++) {
|
|
const PanelBox b = TileBox(x[i], y[i], width);
|
|
int32_t *c = tile_centre.data() + 6 * i;
|
|
c[0] = x[i];
|
|
c[1] = y[i];
|
|
c[2] = b.x0;
|
|
c[3] = b.y0;
|
|
c[4] = b.x1;
|
|
c[5] = b.y1;
|
|
}
|
|
cuda_err(cudaMemcpyAsync(gpu_tile_centre.get(), tile_centre.data(), 6 * x.size() * sizeof(int32_t),
|
|
cudaMemcpyHostToDevice, gather_stream));
|
|
const int blocks = static_cast<int>(std::min<size_t>((count + 255) / 256, 4096));
|
|
gather_tiles_kernel<<<blocks, 256, 0, gather_stream>>>(gpu_image.get(), width,
|
|
gpu_tile_centre.get(), half, gpu_tiles.get(), count);
|
|
cuda_err(cudaGetLastError());
|
|
cuda_err(cudaMemcpyAsync(host_tiles.data(), gpu_tiles.get(), count * sizeof(int32_t),
|
|
cudaMemcpyDeviceToHost, gather_stream));
|
|
cuda_err(cudaStreamSynchronize(gather_stream));
|
|
return host_tiles.data();
|
|
}
|
|
|
|
const int32_t *ImagePreprocessorBufferGPU::HostImage() {
|
|
buffer.resize(npixels);
|
|
cuda_err(cudaMemcpyAsync(buffer.data(), gpu_image.get(), npixels * sizeof(int32_t),
|
|
cudaMemcpyDeviceToHost, gather_stream));
|
|
cuda_err(cudaStreamSynchronize(gather_stream));
|
|
return buffer.data();
|
|
}
|