From 01adfa342b1844d183ce89f260c2c8868621444e Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 12 Dec 2025 15:17:55 +0100 Subject: [PATCH] jfjoch_viewer: Auto foreground comes from integer histogram - it is much faster compared to previous implementation --- common/Histogram.h | 66 ++++++++++++------------------- reader/JFJochReaderImage.cpp | 24 ++++++------ reader/JFJochReaderImage.h | 4 +- tests/HistogramTest.cpp | 75 +++++++++++++++++------------------- 4 files changed, 74 insertions(+), 95 deletions(-) diff --git a/common/Histogram.h b/common/Histogram.h index 6f9eb2c3..c4643569 100644 --- a/common/Histogram.h +++ b/common/Histogram.h @@ -50,82 +50,66 @@ public: } }; -class FloatHistogram { +class Histogram { std::vector count; - int64_t total_count = 0; - mutable std::mutex m; - float min; - float max; - float spacing; + int32_t total_count = 0; + int32_t max_bin = 0; public: - explicit FloatHistogram(size_t bins, float in_min, float in_max) : count(bins), min(in_min), max(in_max) { - if (bins == 0) - throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, - "FloatHistogram neds to have non-zero bins"); - spacing = (max - min) / bins; - } + explicit Histogram(size_t bins) : count(bins) {} - void Add(float val) { - std::unique_lock ul(m); - - size_t bin = (val - min) / spacing; - - if (bin < count.size()) - count[bin] += 1; - ++total_count; + void Add(int32_t val) { + if (val > 0 && val < count.size()) { + count[val] += 1; + if (val > max_bin) + max_bin = val; + ++total_count; + } } void clear() { - std::unique_lock ul(m); for (auto &c: count) c = 0; total_count = 0; + max_bin = 0; } - MultiLinePlot GetPlot() const { - std::unique_lock ul(m); - - MultiLinePlotStruct plot; - plot.x.resize(count.size()); - plot.y.resize(count.size()); - for (int i = 0; i < count.size(); i++) { - plot.x[i] = min + (i + 0.5f) * spacing; - plot.y[i] = static_cast(count[i]); - } - MultiLinePlot ret; - ret.AddPlot(plot); + [[nodiscard]] std::vector GetCount() const { + std::vector ret; + ret.reserve(max_bin + 1); + for (size_t i = 0; i < max_bin + 1; i++) + ret.emplace_back(count[i]); return ret; } - uint64_t TotalCount() const { - std::unique_lock ul(m); + [[nodiscard]] uint64_t GetTotalCount() const { return total_count; } // Returns the value x such that approximately `percent`% of samples are <= x. // - percent must be in [0, 100] // - returns std::nullopt if histogram is empty - std::optional Percentile(float percent) const { + [[nodiscard]] std::optional Percentile(float percent) const { if (!std::isfinite(percent) || percent < 0.0f || percent > 100.0f) { throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, "FloatHistogram Percentile expects percent in [0, 100]"); } - std::unique_lock ul(m); + if (total_count == 0) + return std::nullopt; // Target rank in [0, total-1] const double q = static_cast(percent) / 100.0; - const auto target = static_cast(std::floor(q * static_cast(total_count - 1))); + const auto target = static_cast(std::floor(q * static_cast(total_count - 1))); uint64_t cumulative = 0; - for (size_t i = 0; i < count.size(); i++) { + for (int64_t i = 0; i < max_bin + 1; i++) { cumulative += count[i]; if (target < cumulative && count[i] > 0) - return min + static_cast(i) * spacing + 0.5f * spacing; + return i; } // If due to rounding we didn't return inside the loop, clamp to the last bin's upper edge. - return max; + return count.size() - 1; } }; diff --git a/reader/JFJochReaderImage.cpp b/reader/JFJochReaderImage.cpp index 6391d660..20f8a577 100644 --- a/reader/JFJochReaderImage.cpp +++ b/reader/JFJochReaderImage.cpp @@ -8,12 +8,13 @@ #include #include +#include JFJochReaderImage::JFJochReaderImage(const DataMessage &in_message, const std::shared_ptr &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(), @@ -124,13 +125,12 @@ void JFJochReaderImage::ProcessInputImage(const void *data, size_t npixel, int64 valid_max = std::max(valid_max, val); } valid_count++; - + count_histogram.Add(val); top_pixels_acc.Add(val, static_cast(i)); } } - // For now: auto-foreground based purely on max valid element - auto_foreground = has_valid ? std::max(1, valid_max) : 10; + auto_foreground = count_histogram.Percentile(99.9).value_or(10); // Export top pixels (already sorted descending) into the existing vector interface for (int i = 0; i < top_pixels_acc.Size(); i++) { @@ -139,12 +139,6 @@ void JFJochReaderImage::ProcessInputImage(const void *data, size_t npixel, int64 } } - -void JFJochReaderImage::CalcAutoContrast() { - // Now simplified: based on max element (valid_max) - auto_foreground = has_valid ? std::max(1, valid_max) : 10; -} - std::optional> JFJochReaderImage::ValidMinMax() const { if (!has_valid) return {}; @@ -200,6 +194,7 @@ void JFJochReaderImage::AddImage(const JFJochReaderImage &other) { 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) { @@ -231,14 +226,13 @@ void JFJochReaderImage::AddImage(const JFJochReaderImage &other) { valid_max = std::max(valid_max, val); } valid_count++; - + count_histogram.Add(val); top_pixels_acc.Add(val, static_cast(i)); } } } - // For now: auto-foreground based purely on max valid element - auto_foreground = has_valid ? std::max(1, valid_max) : 10; + auto_foreground = count_histogram.Percentile(99.9).value_or(10); for (int i = 0; i < top_pixels_acc.Size(); i++) { const auto &e = top_pixels_acc[i]; @@ -281,3 +275,7 @@ std::shared_ptr JFJochReaderImage::CreateMutableDataset() { int32_t JFJochReaderImage::GetAutoContrastValue() const { return auto_foreground; } + +std::vector JFJochReaderImage::GetHistogram() const { + return count_histogram.GetCount(); +} diff --git a/reader/JFJochReaderImage.h b/reader/JFJochReaderImage.h index f82b9467..84eaaf9d 100644 --- a/reader/JFJochReaderImage.h +++ b/reader/JFJochReaderImage.h @@ -40,7 +40,7 @@ class JFJochReaderImage { std::vector> top_pixels; // This histogram operates in square root of count from 0 to 10^20 - FloatHistogram count_histogram{2048, 0.0f, 1000.0}; + Histogram count_histogram{100000}; constexpr static float auto_foreground_range = 0.001f; int32_t auto_foreground; @@ -71,7 +71,7 @@ public: int32_t GetAutoContrastValue() const; - MultiLinePlot GetHistogram() const; + std::vector GetHistogram() const; }; #endif //JFJOCH_JFJOCHREADERIMAGE_H diff --git a/tests/HistogramTest.cpp b/tests/HistogramTest.cpp index 8672ffe8..fb570794 100644 --- a/tests/HistogramTest.cpp +++ b/tests/HistogramTest.cpp @@ -29,61 +29,58 @@ TEST_CASE("SetAverage") { CHECK(p.GetPlots()[0].y[99] == 0.0); } -TEST_CASE("FloatHistogram") { - FloatHistogram h(100, 100.0, 200.0); - h.Add(150.5); - h.Add(100.2); - h.Add(100.9999); - h.Add(100.0); - h.Add(100.00001); - h.Add(101.0); - h.Add(200.0); +TEST_CASE("Histogram") { + Histogram h(200); + h.Add(150); + h.Add(100); + h.Add(100); + h.Add(100); + h.Add(100); + h.Add(101); + h.Add(199); + h.Add(200); // Outside or range + h.Add(-1); // Outside of range - auto p = h.GetPlot(); - REQUIRE(p.GetPlots().size() == 1); - REQUIRE(p.GetPlots()[0].x.size() == 100); - REQUIRE(p.GetPlots()[0].y.size() == 100); - - CHECK(p.GetPlots()[0].x[0] == 100.5); - CHECK(p.GetPlots()[0].x[34] == 134.5); - CHECK(p.GetPlots()[0].x[99] == 199.5); - - CHECK(p.GetPlots()[0].y[0] == 4.0); - CHECK(p.GetPlots()[0].y[1] == 1.0); - CHECK(p.GetPlots()[0].y[50] == 1.0); - CHECK(p.GetPlots()[0].y[99] == 0.0); + CHECK(h.GetTotalCount() == 7); + auto p = h.GetCount(); + REQUIRE(p.size() == 200); + CHECK(p[100] == 4); + CHECK(p[150] == 1); + CHECK(p[199] == 1); + CHECK(p[101] == 1); + CHECK(p[55] == 0); } -TEST_CASE("FloatHistogram_Percentile") { - FloatHistogram h(111, 89.5, 200.5); +TEST_CASE("Histogram_Percentile") { + Histogram h(500); for (int i = 0; i < 8; i++) h.Add(100.0f); h.Add(150.0f); h.Add(200.0f); - CHECK(h.Percentile(0.0f).value_or(-1) == Catch::Approx(100.0f)); - CHECK(h.Percentile(30.0f).value_or(-1) == Catch::Approx(100.0f)); - CHECK(h.Percentile(50.0f).value_or(-1) == Catch::Approx(100.0f)); - CHECK(h.Percentile(80.0f).value_or(-1) == Catch::Approx(100.0f)); - CHECK(h.Percentile(90.0f).value_or(-1) == Catch::Approx(150.0f)); - CHECK(h.Percentile(100.0f).value_or(-1) == Catch::Approx(200.0f)); + CHECK(h.Percentile(0.0f).value_or(-1) == 100); + CHECK(h.Percentile(30.0f).value_or(-1) == 100); + CHECK(h.Percentile(50.0f).value_or(-1) == 100); + CHECK(h.Percentile(80.0f).value_or(-1) == 100); + CHECK(h.Percentile(90.0f).value_or(-1) == 150); + CHECK(h.Percentile(100.0f).value_or(-1) == 200); - CHECK(h.TotalCount() == 10); + CHECK(h.GetTotalCount() == 10); } -TEST_CASE("FloatHistogram_Clear") { - FloatHistogram h(111, 89.5, 200.5); +TEST_CASE("Histogram_Clear") { + Histogram h(300); for (int i = 0; i < 8; i++) - h.Add(100.0f); - h.Add(150.0f); - h.Add(200.0f); + h.Add(100); + h.Add(150); + h.Add(200); - CHECK(h.TotalCount() == 10); + CHECK(h.GetTotalCount() == 10); h.clear(); - CHECK(h.TotalCount() == 0); + CHECK(h.GetTotalCount() == 0); h.Add(200.0f); - CHECK(h.TotalCount() == 1); + CHECK(h.GetTotalCount() == 1); } \ No newline at end of file