jfjoch_viewer: Auto foreground comes from integer histogram - it is much faster compared to previous implementation

This commit is contained in:
2025-12-12 15:17:55 +01:00
parent f235346467
commit 01adfa342b
4 changed files with 74 additions and 95 deletions
+25 -41
View File
@@ -50,82 +50,66 @@ public:
}
};
class FloatHistogram {
class Histogram {
std::vector<uint64_t> 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<float>(count[i]);
}
MultiLinePlot ret;
ret.AddPlot(plot);
[[nodiscard]] std::vector<float> GetCount() const {
std::vector<float> 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<float> Percentile(float percent) const {
[[nodiscard]] std::optional<int32_t> 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<double>(percent) / 100.0;
const auto target = static_cast<uint64_t>(std::floor(q * static_cast<double>(total_count - 1)));
const auto target = static_cast<int64_t>(std::floor(q * static_cast<double>(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<float>(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;
}
};
+11 -13
View File
@@ -8,12 +8,13 @@
#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(),
@@ -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<int32_t>(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<std::pair<int32_t, int32_t>> 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<int32_t>(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<JFJochReaderDataset> JFJochReaderImage::CreateMutableDataset() {
int32_t JFJochReaderImage::GetAutoContrastValue() const {
return auto_foreground;
}
std::vector<float> JFJochReaderImage::GetHistogram() const {
return count_histogram.GetCount();
}
+2 -2
View File
@@ -40,7 +40,7 @@ class JFJochReaderImage {
std::vector<std::pair<int32_t, int32_t>> 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<float> GetHistogram() const;
};
#endif //JFJOCH_JFJOCHREADERIMAGE_H
+36 -39
View File
@@ -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);
}