Stream receivers: saturated and error counts on the puller path are the card's
In an unsigned image the card writes saturated and error pixels with one value, so the analysis could not tell them apart: JFJochReceiverLite reported card saturated 8, error 8 as saturated 0, error 16. The puller already places the card's module statistics in the message, so the analysis no longer overwrites counts a stream states. A DECTRIS message carries none and is not affected. It is a variant of Analyze the two stream receivers that analyse call, rather than a rule inside Analyze, because Analyze runs again on one message elsewhere (the viewer on a changed mask), where it has to recount. Rotation forwards the message as it came, so it needs nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK
This commit is contained in:
1 parent
29afe06a78
commit
4c3a7852e9
5 files changed
+53
-3
No files matched your search
@@ -448,6 +448,19 @@ void MXAnalysisWithoutFPGA::RebuildROI() {
|
||||
roi = std::make_unique<ROIIntegrationCPU>(experiment);
|
||||
}
|
||||
|
||||
void MXAnalysisWithoutFPGA::AnalyzeStreamImage(DataMessage &output,
|
||||
AzimuthalIntegrationProfile &profile,
|
||||
const SpotFindingSettings &spot_finding_settings) {
|
||||
// In an unsigned image from the card saturated and error pixels have the same value: only the card knows which is which
|
||||
const auto saturated_pixel_count = output.saturated_pixel_count;
|
||||
const auto error_pixel_count = output.error_pixel_count;
|
||||
Analyze(output, profile, spot_finding_settings);
|
||||
if (saturated_pixel_count)
|
||||
output.saturated_pixel_count = saturated_pixel_count;
|
||||
if (error_pixel_count)
|
||||
output.error_pixel_count = error_pixel_count;
|
||||
}
|
||||
|
||||
void MXAnalysisWithoutFPGA::AnalyzeROIOnly(DataMessage &output) {
|
||||
if ((output.image.GetWidth() != xpixels)
|
||||
|| (output.image.GetWidth() * output.image.GetHeight() != npixels))
|
||||
|
||||
@@ -117,6 +117,8 @@ public:
|
||||
MXAnalysisWithoutFPGA(const DiffractionExperiment &experiment, const AzimuthalIntegrationMapping &integration,
|
||||
const PixelMask &mask, IndexAndRefine &indexer, bool enable_fused_adaptive_gpu = false);
|
||||
void Analyze(DataMessage &output, AzimuthalIntegrationProfile &profile, const SpotFindingSettings &spot_finding_settings);
|
||||
// Analyze for an image from a stream: saturated and error counts the stream states stay, a DECTRIS message has none.
|
||||
void AnalyzeStreamImage(DataMessage &output, AzimuthalIntegrationProfile &profile, const SpotFindingSettings &spot_finding_settings);
|
||||
|
||||
// Surgical ROI-only paths used when a full re-analysis is not wanted: rebuild the
|
||||
// ROI engine after the ROI set changes, recompute ROIs after preprocessing a new
|
||||
|
||||
@@ -342,7 +342,7 @@ void JFJochReceiverLite::DataAnalysisThread(uint32_t id) {
|
||||
|
||||
AzimuthalIntegrationProfile profile(*az_int_mapping);
|
||||
if (analysis)
|
||||
analysis->Analyze(data_msg, profile, GetSpotFindingSettings());
|
||||
analysis->AnalyzeStreamImage(data_msg, profile, GetSpotFindingSettings());
|
||||
|
||||
auto image_end_time = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<float> image_duration = image_end_time - image_start_time;
|
||||
|
||||
@@ -330,7 +330,7 @@ void StillsGPUAnalysis::AnalysisThread() {
|
||||
profile.emplace(*setup.mapping);
|
||||
try {
|
||||
if (analysis)
|
||||
analysis->Analyze(message, *profile, settings());
|
||||
analysis->AnalyzeStreamImage(message, *profile, settings());
|
||||
} catch (const JFJochException &e) {
|
||||
// The frame is still written, without its analysis.
|
||||
logger.ErrorException(e);
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace {
|
||||
JFJochReceiverOutput output;
|
||||
};
|
||||
|
||||
PathResult Run(const DiffractionExperiment &x, bool puller, int64_t distinct_frames) {
|
||||
PathResult Run(const DiffractionExperiment &x, bool puller, int64_t distinct_frames, bool rugnux = false) {
|
||||
auto devices = Devices(x);
|
||||
Logger logger("FPGAImagePullerTest");
|
||||
CollectingPusher pusher;
|
||||
@@ -150,6 +150,7 @@ namespace {
|
||||
service.NumThreads(4);
|
||||
service.SetSpotFindingSettings(Spots());
|
||||
service.UseFPGAImagePuller(puller);
|
||||
service.RugnuxLive(rugnux, nullptr, 2, 30);
|
||||
|
||||
const bool jungfrau = x.GetDetectorType() == DetectorType::JUNGFRAU;
|
||||
for (int64_t f = 0; f < distinct_frames; f++) {
|
||||
@@ -675,6 +676,40 @@ TEST_CASE("FPGAImagePuller_RugnuxReceiver", "[FPGAImagePuller]") {
|
||||
CHECK(a == b); // the third frame in the generator's memory repeats
|
||||
}
|
||||
|
||||
// In an unsigned image the card gives saturated and error pixels one value: the counts are the card's, on every receiver.
|
||||
TEST_CASE("FPGAImagePuller_SaturatedAndErrorCountsFromTheCard", "[FPGAImagePuller]") {
|
||||
auto x = Base(DetJF(2), 4, 2);
|
||||
x.Conversion();
|
||||
x.Compression(CompressionAlgorithm::BSHUF_LZ4).BitDepthImage(16).PixelSigned(false).FilePrefix("fpga_puller_counts");
|
||||
const auto card = Run(x, false, 2);
|
||||
|
||||
PathResult puller;
|
||||
SECTION("JFJochReceiverLite") {
|
||||
puller = Run(x, true, 2);
|
||||
}
|
||||
SECTION("JFJochReceiverRugnux, stills") {
|
||||
puller = Run(x, true, 2, true);
|
||||
}
|
||||
SECTION("JFJochReceiverRugnux, rotation") {
|
||||
auto rotation = x;
|
||||
rotation.Goniometer(GoniometerAxis("omega", 0, 0.1, Coord(-1, 0, 0), std::nullopt));
|
||||
puller = Run(rotation, true, 2, true);
|
||||
}
|
||||
|
||||
REQUIRE(card.images.size() == 4);
|
||||
REQUIRE(puller.images.size() == 4);
|
||||
int64_t saturated = 0, error = 0;
|
||||
for (const auto &[n, image]: puller.images) {
|
||||
INFO("image " << n);
|
||||
CHECK(image.message.saturated_pixel_count == card.images.at(n).message.saturated_pixel_count);
|
||||
CHECK(image.message.error_pixel_count == card.images.at(n).message.error_pixel_count);
|
||||
saturated += image.message.saturated_pixel_count.value_or(0);
|
||||
error += image.message.error_pixel_count.value_or(0);
|
||||
}
|
||||
CHECK(saturated == 8);
|
||||
CHECK(error == 8);
|
||||
}
|
||||
|
||||
// Rotation forwards the images unanalysed, so nothing would replace what the card's ROI map, not loaded for this run, gave.
|
||||
TEST_CASE("FPGAImagePuller_NoStaleROI", "[FPGAImagePuller]") {
|
||||
auto x = Base(DetJF(1), 4, 1);
|
||||
|
||||
Reference in new issue
Block a user