Files
leonarski_f b00c668e3a
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m11s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 10m32s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 18m26s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 22m11s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 23m32s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 25m36s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 27m28s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 28m10s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 28m10s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 29m24s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 21m45s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 24m2s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 29m25s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 30m27s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 27m12s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 32m20s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 33m36s
Build Packages / Unit tests (push) Successful in 2h7m18s
Build Packages / Validation Battery (push) Successful in 33m19s
v1.0.0-rc.179 (#90)
* Rugnux: Stills processing needs only the unit cell and space group beyond what the file records - detector distance, beam centre, tilt, panel placement, saturation level, gain and polarisation plane are found from the images - and is up to twice as fast.
* Rugnux: Stills can be indexed without a GPU, with a CPU implementation of the fast-feedback indexer (macOS, computers without a GPU).
* Rugnux: Rotation and stills data are integrated in the detector's own units with one gain model, improving data from CCD detectors.
* Web frontend: The Rugnux live page shows the placed model with its electron density and anomalous difference map in a Mol* viewer.

Reviewed-on: #90
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-24 14:52:29 +02:00

173 lines
6.5 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "MXAnalysisAfterFPGA.h"
#include <span>
#include "spot_finding/DetModuleSpotFinder_cpu.h"
#include "../common/CUDAWrapper.h"
#include "../common/JFJochException.h"
#include "spot_finding/SpotUtils.h"
#include "bragg_prediction/BraggPredictionFactory.h"
double stddev(const std::vector<float> &v) {
if (v.size() <= 1)
return 0.0;
double mean = 0.0f;
for (const auto &i: v)
mean += i;
mean /= v.size();
double stddev = 0.0f;
for (const auto &i: v)
stddev += (i - mean) * (i - mean);
return sqrt(stddev / (v.size() - 1));
}
MXAnalysisAfterFPGA::MXAnalysisAfterFPGA(const DiffractionExperiment &in_experiment,
const AzimuthalIntegrationMapping &in_integration,
IndexAndRefine &indexer)
: experiment(in_experiment),
integration(in_integration),
indexer(indexer),
prediction(CreateBraggPrediction(experiment.IsRotationIndexing())),
// No pixel mask reaches this path: the FPGA image marks its own masked pixels, so only a
// saturated pixel is read as an overload here. The edge mask is taken in the image's own layout,
// which is the raw one when the geometry is not transformed.
bragg_engine(std::make_unique<BraggIntegrationEngineCPU>(
in_experiment, PixelMask(PixelMask(in_experiment).GetMask(in_experiment)))) {
if (experiment.IsSpotFindingEnabled())
find_spots = true;
if (experiment.GetAzimuthalIntegrationSettings().IsForceCPUinFPGAWorkflow())
cpu_azint = std::make_unique<AzIntEngineCPU>(integration);
}
void MXAnalysisAfterFPGA::RunAzimuthalIntegration(const void *image, AzimuthalIntegrationProfile &profile) {
if (!cpu_azint)
return;
const size_t npixel = experiment.GetPixelsNum();
auto run = [&](auto tag) {
using pixel_t = decltype(tag);
cpu_azint->RunAzint(std::span<const pixel_t>(static_cast<const pixel_t *>(image), npixel), profile);
};
switch (experiment.GetByteDepthImage()) {
case 1:
experiment.IsPixelSigned() ? run(int8_t{}) : run(uint8_t{});
break;
case 2:
experiment.IsPixelSigned() ? run(int16_t{}) : run(uint16_t{});
break;
case 4:
experiment.IsPixelSigned() ? run(int32_t{}) : run(uint32_t{});
break;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported");
}
}
void MXAnalysisAfterFPGA::ReadFromFPGA(const DeviceOutput *output, const SpotFindingSettings &settings, size_t module_number) {
if (state == State::Disabled || !find_spots || !settings.enable) {
state = State::Disabled;
} else {
const auto t0 = std::chrono::steady_clock::now();
StrongPixelSet strong_pixel_set;
strong_pixel_set.ReadFPGAOutput(experiment, *output);
strong_pixel_set.FindSpots(experiment, settings, spots, module_number);
const auto t1 = std::chrono::steady_clock::now();
spot_finding_time_total += (t1 - t0);
spot_finding_timing_active = true;
state = State::Enabled;
}
}
void MXAnalysisAfterFPGA::ReadFromCPU(DeviceOutput *output, const SpotFindingSettings &settings, size_t module_number) {
std::unique_lock ul(read_from_cpu_mutex);
if (state == State::Disabled || !find_spots || !settings.enable) {
state = State::Disabled;
} else {
const auto t0 = std::chrono::steady_clock::now();
state = State::Enabled;
std::vector<float> d_map(RAW_MODULE_SIZE);
experiment.CalcSpotFinderResolutionMap(d_map.data(), module_number);
arr_mean.resize(RAW_MODULE_SIZE);
arr_sttdev.resize(RAW_MODULE_SIZE);
arr_valid_count.resize(RAW_MODULE_SIZE);
arr_strong_pixel.resize(RAW_MODULE_SIZE);
if (experiment.GetByteDepthImage() == 2)
FindSpots(*output,
settings,
d_map.data(),
arr_mean.data(),
arr_sttdev.data(),
arr_valid_count.data(),
arr_strong_pixel.data());
else if (experiment.GetByteDepthImage() == 4)
FindSpots<int32_t>(*output,
settings,
d_map.data(),
arr_mean.data(),
arr_sttdev.data(),
arr_valid_count.data(),
arr_strong_pixel.data());
else if (experiment.GetByteDepthImage() == 1)
FindSpots<int8_t>(*output,
settings,
d_map.data(),
arr_mean.data(),
arr_sttdev.data(),
arr_valid_count.data(),
arr_strong_pixel.data());
StrongPixelSet strong_pixel_set;
strong_pixel_set.ReadFPGAOutput(experiment, *output);
strong_pixel_set.FindSpots(experiment, settings, spots, module_number);
const auto t1 = std::chrono::steady_clock::now();
spot_finding_time_total += (t1 - t0);
spot_finding_timing_active = true;
}
}
void MXAnalysisAfterFPGA::Process(DataMessage &message, const SpotFindingSettings& spot_finding_settings) {
if (find_spots && (state == State::Enabled)) {
const auto t0 = std::chrono::steady_clock::now();
SpotAnalyze(experiment, spot_finding_settings, spots, message);
const auto t1 = std::chrono::steady_clock::now();
spot_finding_time_total += (t1 - t0);
if (spot_finding_settings.indexing)
indexer.ProcessImage(message, spot_finding_settings, *prediction,
[this, &message](const std::vector<Reflection> &predicted, size_t npredicted, int64_t image_number) {
return bragg_engine->Run(message.image, predicted, npredicted, image_number);
});
}
if (spot_finding_timing_active) {
// total spot-finding time for the whole image
message.spot_finding_time_s = spot_finding_time_total.count() / 1e6;
// report/store ms here
spot_finding_time_total = std::chrono::duration<double, std::micro>{0.0};
spot_finding_timing_active = false;
}
spots.clear();
state = State::Idle;
}