Skip to content

Commit

Permalink
small refactor of PixelStats
Browse files Browse the repository at this point in the history
  • Loading branch information
wkjarosz committed Mar 8, 2024
1 parent 2025935 commit 881d56c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@ void HDRViewApp::normalize_exposure()
float m = 0.f;
auto &group = img->groups[img->selected_group];
for (int c = 0; c < group.num_channels && c < 3; ++c)
m = std::max(m, img->channels[group.channels[c]].get_stats()->maximum);
m = std::max(m, img->channels[group.channels[c]].get_stats()->summary.maximum);

m_exposure_live = m_exposure = log2(1.f / m);
}
Expand Down
58 changes: 24 additions & 34 deletions src/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ float2 PixelStats::x_limits(float e, AxisScale_ scale) const

float2 ret;
ret[1] = pow(2.f, -e);
if (minimum < 0.f)
if (summary.minimum < 0.f)
ret[0] = -ret[1];
else
{
Expand All @@ -105,10 +105,10 @@ void PixelStats::calculate(const Array2Df &img, float new_exposure, AxisScale_ n
spdlog::trace("Computing pixel statistics");

// initialize values
*this = PixelStats();
exposure = new_exposure;
hist_x_scale = new_x_scale;
hist_y_scale = new_y_scale;
*this = PixelStats();
settings.exposure = new_exposure;
settings.x_scale = new_x_scale;
settings.y_scale = new_y_scale;

//
// compute pixel summary statistics
Expand All @@ -117,23 +117,14 @@ void PixelStats::calculate(const Array2Df &img, float new_exposure, AxisScale_ n
{
size_t block_size = 1024 * 1024;
const size_t num_threads = estimate_threads(img.num_elements(), block_size, *Scheduler::singleton());
struct Stats
{
float minimum = std::numeric_limits<float>::infinity();
float maximum = -std::numeric_limits<float>::infinity();
double average = 0.0f;
int nan_pixels = 0;
int inf_pixels = 0;
int valid_pixels = 0;
};
std::vector<Stats> partials(max<size_t>(1, num_threads));
std::vector<Summary> partials(max<size_t>(1, num_threads));
spdlog::trace("Breaking summary stats into {} work units.", partials.size());

parallel_for(
blocked_range<int>(0, img.num_elements(), block_size),
[&img, &partials, &canceled](int begin, int end, int unit_index, int thread_index)
{
Stats partial = partials[unit_index]; //< compute over local symbols.
Summary partial = partials[unit_index]; //< compute over local symbols.

for (int i = begin; i != end; ++i)
{
Expand Down Expand Up @@ -161,35 +152,34 @@ void PixelStats::calculate(const Array2Df &img, float new_exposure, AxisScale_ n
num_threads);

// final reduction from partial results
double accum = 0.f;
int valid_pixels = 0;
double accum = 0.f;
for (auto &p : partials)
{
minimum = std::min(p.minimum, minimum);
maximum = std::max(p.maximum, maximum);
nan_pixels += p.nan_pixels;
inf_pixels += p.inf_pixels;

valid_pixels += p.valid_pixels;
summary.minimum = std::min(p.minimum, summary.minimum);
summary.maximum = std::max(p.maximum, summary.maximum);
summary.nan_pixels += p.nan_pixels;
summary.inf_pixels += p.inf_pixels;
summary.valid_pixels += p.valid_pixels;
accum += p.average;
}
average = valid_pixels ? float(accum / valid_pixels) : 0.f;
summary.average = summary.valid_pixels ? float(accum / summary.valid_pixels) : 0.f;
}

spdlog::trace("Summary stats computed in {} ms:\nMin: {}\nMean: {}\nMax: {}", timer.lap(), minimum, average,
maximum);
spdlog::trace("Summary stats computed in {} ms:\nMin: {}\nMean: {}\nMax: {}", timer.lap(), summary.minimum,
summary.average, summary.maximum);
//

//
// compute histograms

bool LDR_scale = hist_x_scale == AxisScale_Linear || hist_x_scale == AxisScale_SRGB;
bool LDR_scale = settings.x_scale == AxisScale_Linear || settings.x_scale == AxisScale_SRGB;

auto hist_x_limits = x_limits(exposure, hist_x_scale);
auto hist_x_limits = x_limits(settings.exposure, settings.x_scale);

hist_normalization[0] = axis_scale_fwd_xform(LDR_scale ? hist_x_limits[0] : minimum, &hist_x_scale);
hist_normalization[0] = axis_scale_fwd_xform(LDR_scale ? hist_x_limits[0] : summary.minimum, &settings.x_scale);
hist_normalization[1] =
axis_scale_fwd_xform(LDR_scale ? hist_x_limits[1] : maximum, &hist_x_scale) - hist_normalization[0];
axis_scale_fwd_xform(LDR_scale ? hist_x_limits[1] : summary.maximum, &settings.x_scale) -
hist_normalization[0];

// compute bin center values
for (int i = 0; i < NUM_BINS; ++i) hist_xs[i] = bin_to_value(i + 0.5);
Expand Down Expand Up @@ -217,7 +207,7 @@ void PixelStats::calculate(const Array2Df &img, float new_exposure, AxisScale_ n
// put the 10th largest value in index 10
std::nth_element(ys.begin(), ys.begin() + idx, ys.end(), std::greater<float>());
// for logarithmic y-axis, we need a non-zero lower y-limit, so use half the smallest possible value
hist_y_limits[0] = hist_y_scale == AxisScale_Linear ? 0.f : hist_y_limits[0]; // / 2.f;
hist_y_limits[0] = settings.y_scale == AxisScale_Linear ? 0.f : hist_y_limits[0]; // / 2.f;
// for upper y-limit, use the 10th largest value if its non-zero, then the largest, then just 1
if (ys[idx] != 0.f)
hist_y_limits[1] = ys[idx] * 1.15f;
Expand Down Expand Up @@ -337,7 +327,7 @@ void Channel::update_stats()
};

// if the cached stats match and are valid, no need to recompute
if (cached_stats->settings().match(desired_settings) && cached_stats->computed)
if (cached_stats->settings.match(desired_settings) && cached_stats->computed)
return;

// cached stats are outdated, need to recompute
Expand All @@ -358,7 +348,7 @@ void Channel::update_stats()
async_stats = make_shared<PixelStats>();

// if these newer stats are still outdated, schedule a new async computation
if (!cached_stats->settings().match(desired_settings))
if (!cached_stats->settings.match(desired_settings))
recompute_async_stats();
}
}
Expand Down
36 changes: 20 additions & 16 deletions src/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,32 @@ struct PixelStats

struct Settings
{
float exposure;
AxisScale_ x_scale, y_scale;
float exposure = 0.f;
AxisScale_ x_scale = AxisScale_Linear;
AxisScale_ y_scale = AxisScale_Linear;

bool match(const Settings &other) const;
};

float exposure = 0.f;
float minimum = std::numeric_limits<float>::infinity();
float maximum = -std::numeric_limits<float>::infinity();
float average = 0.0f;
int nan_pixels = 0;
int inf_pixels = 0;
Settings settings;

struct Summary
{
float minimum = std::numeric_limits<float>::infinity();
float maximum = -std::numeric_limits<float>::infinity();
float average = 0.0f;
int nan_pixels = 0;
int inf_pixels = 0;
int valid_pixels = 0;
};

Summary summary;

bool computed = false; ///< Did we finish computing the stats?

// histogram
AxisScale_ hist_x_scale = AxisScale_Linear;
AxisScale_ hist_y_scale = AxisScale_Linear;
float2 hist_y_limits = {0.f, 1.f};
float2 hist_normalization = {0.f, 1.f};
float2 hist_y_limits = {0.f, 1.f};
float2 hist_normalization = {0.f, 1.f};

std::array<float, NUM_BINS> hist_xs{}; // {}: value-initialized to zeros
std::array<float, NUM_BINS> hist_ys{}; // {}: value-initialized to zeros
Expand All @@ -90,23 +96,21 @@ struct PixelStats
void calculate(const Array2Df &img, float exposure, AxisScale_ x_scale, AxisScale_ y_scale,
std::atomic<bool> &canceled);

Settings settings() const { return {exposure, hist_x_scale, hist_y_scale}; }

int clamp_idx(int i) const { return std::clamp(i, 0, NUM_BINS - 1); }
float &bin_x(int i) { return hist_xs[clamp_idx(i)]; }
float &bin_y(int i) { return hist_ys[clamp_idx(i)]; }

int value_to_bin(double value) const
{
return int(std::floor((axis_scale_fwd_xform(value, (void *)&hist_x_scale) - hist_normalization[0]) /
return int(std::floor((axis_scale_fwd_xform(value, (void *)&settings.x_scale) - hist_normalization[0]) /
hist_normalization[1] * NUM_BINS));
}

double bin_to_value(double value)
{
static constexpr float inv_bins = 1.f / NUM_BINS;
return axis_scale_inv_xform(hist_normalization[1] * value * inv_bins + hist_normalization[0],
(void *)&hist_x_scale);
(void *)&settings.x_scale);
}

float2 x_limits(float exposure, AxisScale_ x_scale) const;
Expand Down
22 changes: 11 additions & 11 deletions src/image_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,18 @@ void Image::draw_histogram()
ImPlot::PushStyleColor(ImPlotCol_InlayText, float4{colors[c].xyz(), 1.0f});
ImPlot::PushStyleColor(ImPlotCol_Line, float4{colors[c].xyz(), 1.0f});

ImPlot::PlotInfLines("##min", &stats[c]->minimum, 1);
ImPlot::PlotText(fmt::format("min({})", names[c]).c_str(), stats[c]->minimum,
ImPlot::PlotInfLines("##min", &stats[c]->summary.minimum, 1);
ImPlot::PlotText(fmt::format("min({})", names[c]).c_str(), stats[c]->summary.minimum,
lerp(y_limits[0], y_limits[1], 0.5f), {-HelloImGui::EmSize(), 0.f},
ImPlotTextFlags_Vertical);

ImPlot::PlotInfLines("##min", &stats[c]->average, 1);
ImPlot::PlotText(fmt::format("avg({})", names[c]).c_str(), stats[c]->average,
ImPlot::PlotInfLines("##min", &stats[c]->summary.average, 1);
ImPlot::PlotText(fmt::format("avg({})", names[c]).c_str(), stats[c]->summary.average,
lerp(y_limits[0], y_limits[1], 0.5f), {-HelloImGui::EmSize(), 0.f},
ImPlotTextFlags_Vertical);

ImPlot::PlotInfLines("##max", &stats[c]->maximum, 1);
ImPlot::PlotText(fmt::format("max({})", names[c]).c_str(), stats[c]->maximum,
ImPlot::PlotInfLines("##max", &stats[c]->summary.maximum, 1);
ImPlot::PlotText(fmt::format("max({})", names[c]).c_str(), stats[c]->summary.maximum,
lerp(y_limits[0], y_limits[1], 0.5f), {-HelloImGui::EmSize(), 0.f},
ImPlotTextFlags_Vertical);
ImPlot::PopStyleColor(2);
Expand Down Expand Up @@ -555,12 +555,12 @@ void Image::draw_info()
ImGui::TableNextColumn();
switch (s)
{
case 0: ImGui::Text("%-6.3g", channel_stats[c]->minimum); break;
case 1: ImGui::Text("%-6.3g", channel_stats[c]->average); break;
case 2: ImGui::Text("%-6.3g", channel_stats[c]->maximum); break;
case 3: ImGui::Text("%d", channel_stats[c]->nan_pixels); break;
case 0: ImGui::Text("%-6.3g", channel_stats[c]->summary.minimum); break;
case 1: ImGui::Text("%-6.3g", channel_stats[c]->summary.average); break;
case 2: ImGui::Text("%-6.3g", channel_stats[c]->summary.maximum); break;
case 3: ImGui::Text("%d", channel_stats[c]->summary.nan_pixels); break;
case 4:
default: ImGui::Text("%d", channel_stats[c]->inf_pixels); break;
default: ImGui::Text("%d", channel_stats[c]->summary.inf_pixels); break;
}
}
}
Expand Down

0 comments on commit 881d56c

Please sign in to comment.