v1.0.0-rc.121 (#28)
All checks were successful
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 9m28s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m25s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m4s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m27s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m36s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / Build documentation (push) Successful in 45s
Build Packages / Create release (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Successful in 8m45s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 7m51s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 8m57s
Build Packages / build:rpm (rocky9) (push) Successful in 9m35s
Build Packages / Unit tests (push) Successful in 1h13m45s

This is an UNSTABLE release.

* jfjoch_broker: Report changes in the image buffer, so viewer doesn't reload constantly
* jfjoch_viewer: Improve performance of loading images
* jfjoch_viewer: Auto-throttle image loading in HTTP-sync / movie modes
* jfjoch_viewer: Auto-foreground calculated with histogram
* jfjoch_viewer: Fix rare segmentation fault

Reviewed-on: #28
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
Co-committed-by: Filip Leonarski <filip.leonarski@psi.ch>
This commit was merged in pull request #28.
This commit is contained in:
2025-12-12 21:24:20 +01:00
committed by leonarski_f
parent a0a659a02c
commit e2b240356c
156 changed files with 732 additions and 260 deletions

View File

@@ -6,11 +6,15 @@
#include "JFJochDecompress.h"
#include "../image_analysis/bragg_integration/BraggIntegrate2D.h"
#include <queue>
#include <algorithm>
#include <cmath>
JFJochReaderImage::JFJochReaderImage(const DataMessage &in_message,
const std::shared_ptr<const JFJochReaderDataset> &in_dataset)
: message(in_message),
dataset(in_dataset),
image(in_dataset->experiment.GetPixelsNum(), 0){
image(in_dataset->experiment.GetPixelsNum(), 0) {
ProcessInputImage(in_message.image);
message.image = CompressedImage(image, in_dataset->experiment.GetXPixelsNum(),
@@ -72,11 +76,22 @@ void JFJochReaderImage::ProcessInputImage(const void *data, size_t npixel, int64
const T* img_ptr = reinterpret_cast<const T*>(data);
size_t good_pixel = 0;
// Reset per-image stats
saturated_pixel.clear();
error_pixel.clear();
valid_count = 0;
has_valid = false;
valid_pixel.reserve(image.size());
top_pixels_acc.Clear();
top_pixels.clear();
top_pixels.reserve(top_pixels_acc.Capacity());
for (int i = 0; i < npixel; i++) {
bool has_input_mask = false;
const auto &mask = dataset->pixel_mask.GetMask();
if (mask.size() == npixel)
has_input_mask = true;
for (size_t i = 0; i < npixel; i++) {
int32_t val;
if (img_ptr[i] <= INT32_MAX)
val = static_cast<int32_t>(img_ptr[i]);
@@ -84,44 +99,54 @@ void JFJochReaderImage::ProcessInputImage(const void *data, size_t npixel, int64
val = INT32_MAX;
uint32_t mask_val = 0;
if (!dataset->pixel_mask.GetMask().empty())
mask_val = dataset->pixel_mask.GetMask().at(i);
if (has_input_mask)
mask_val = mask[i];
if ((mask_val & (
(1<<PixelMask::ModuleGapPixelBit)
| (1<<PixelMask::ChipGapPixelBit)
| (1<<PixelMask::ModuleEdgePixelBit))) != 0) {
image[i] = GAP_PXL_VALUE;
} else if ((mask_val != 0) || (img_ptr[i] == special_value)){
} else if ((mask_val != 0) || (img_ptr[i] == special_value)) {
image[i] = ERROR_PXL_VALUE;
error_pixel.emplace(i);
error_pixel.emplace(static_cast<int64_t>(i));
} else if (val >= sat_value) {
image[i] = SATURATED_PXL_VALUE;
saturated_pixel.emplace(i);
saturated_pixel.emplace(static_cast<int64_t>(i));
} else {
good_pixel++;
image[i] = val;
valid_pixel.emplace_back(std::make_pair(val, i));
if (!has_valid) {
has_valid = true;
valid_min = val;
valid_max = val;
} else {
valid_min = std::min(valid_min, val);
valid_max = std::max(valid_max, val);
}
valid_count++;
count_histogram.Add(val);
top_pixels_acc.Add(val, static_cast<int32_t>(i));
}
}
// Sort based on the first element only
std::sort(valid_pixel.begin(), valid_pixel.end(),
[](const auto& a, const auto& b) {return a.first < b.first;});
CalcAutoContrast();
auto_foreground = count_histogram.Percentile(auto_foreground_range).value_or(10);
// Export top pixels (already sorted descending) into the existing vector interface
for (int i = 0; i < top_pixels_acc.Size(); i++) {
const auto &e = top_pixels_acc[i];
top_pixels.emplace_back(e.value, e.index);
}
}
std::optional<std::pair<int32_t, int32_t>> JFJochReaderImage::ValidMinMax() const {
if (!has_valid)
return {};
return std::make_pair(valid_min, valid_max);
}
void JFJochReaderImage::CalcAutoContrast() {
if (valid_pixel.empty())
auto_foreground = 10;
else {
auto it = valid_pixel.crbegin();
const auto index = static_cast<size_t>(valid_pixel.size() * auto_foreground_range);
std::advance(it, std::max<size_t>(1ULL, index) - 1);
auto_foreground = std::max(1, it->first);
}
const std::vector<std::pair<int32_t, int32_t>> &JFJochReaderImage::GetTopPixels() const {
return top_pixels;
}
const DataMessage &JFJochReaderImage::ImageData() const {
@@ -144,10 +169,6 @@ const std::unordered_set<int64_t> &JFJochReaderImage::ErrorPixels() const {
return error_pixel;
}
const std::vector<std::pair<int32_t, int32_t>> &JFJochReaderImage::ValidPixels() const {
return valid_pixel;
}
const JFJochReaderDataset &JFJochReaderImage::Dataset() const {
if (!dataset)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
@@ -167,37 +188,56 @@ void JFJochReaderImage::AddImage(const JFJochReaderImage &other) {
error_pixel.clear();
saturated_pixel.clear();
valid_pixel.clear();
valid_pixel.reserve(image.size());
for (int i = 0; i < image.size(); i++) {
if (image[i] == GAP_PXL_VALUE || other.image[i] == GAP_PXL_VALUE)
valid_count = 0;
has_valid = false;
top_pixels_acc.Clear();
top_pixels.clear();
top_pixels.reserve(top_pixels_acc.Capacity());
count_histogram.clear();
for (size_t i = 0; i < image.size(); i++) {
if (image[i] == GAP_PXL_VALUE || other.image[i] == GAP_PXL_VALUE) {
image[i] = GAP_PXL_VALUE;
else if (image[i] == ERROR_PXL_VALUE || other.image[i] == ERROR_PXL_VALUE) {
} else if (image[i] == ERROR_PXL_VALUE || other.image[i] == ERROR_PXL_VALUE) {
image[i] = ERROR_PXL_VALUE;
error_pixel.emplace(i);
error_pixel.emplace(static_cast<int64_t>(i));
} else if (image[i] == SATURATED_PXL_VALUE || other.image[i] == SATURATED_PXL_VALUE) {
image[i] = SATURATED_PXL_VALUE;
saturated_pixel.emplace(i);
saturated_pixel.emplace(static_cast<int64_t>(i));
} else {
int64_t sum = static_cast<int64_t>(image[i]) + static_cast<int64_t>(other.image[i]);
if (sum <= INT32_MIN + 5) [[unlikely]] {
image[i] = ERROR_PXL_VALUE;
error_pixel.emplace(i);
error_pixel.emplace(static_cast<int64_t>(i));
} else if (sum > dataset->experiment.GetSaturationLimit()) [[unlikely]] {
image[i] = SATURATED_PXL_VALUE;
saturated_pixel.emplace(i);
saturated_pixel.emplace(static_cast<int64_t>(i));
} else {
image[i] = static_cast<int32_t>(sum);
valid_pixel.emplace_back(std::make_pair(sum, i));
const int32_t val = static_cast<int32_t>(sum);
image[i] = val;
if (!has_valid) {
has_valid = true;
valid_min = val;
valid_max = val;
} else {
valid_min = std::min(valid_min, val);
valid_max = std::max(valid_max, val);
}
valid_count++;
count_histogram.Add(val);
top_pixels_acc.Add(val, static_cast<int32_t>(i));
}
}
}
// Sort based on the first element only
std::sort(valid_pixel.begin(), valid_pixel.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
auto_foreground = count_histogram.Percentile(auto_foreground_range).value_or(10);
CalcAutoContrast();
for (int i = 0; i < top_pixels_acc.Size(); i++) {
const auto &e = top_pixels_acc[i];
top_pixels.emplace_back(e.value, e.index);
}
}
std::vector<float> JFJochReaderImage::GetAzInt1D() const {
@@ -235,3 +275,7 @@ std::shared_ptr<JFJochReaderDataset> JFJochReaderImage::CreateMutableDataset() {
int32_t JFJochReaderImage::GetAutoContrastValue() const {
return auto_foreground;
}
std::vector<float> JFJochReaderImage::GetHistogram() const {
return count_histogram.GetCount();
}