-
Notifications
You must be signed in to change notification settings - Fork 2
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
🐞 Problem Summary
The hist_range_threshold function was designed to compute the value range covering a central percentage of the histogram mass (e.g. 98%), in order to eliminate symmetric outliers — similar to MATLAB’s Eliminate outliers.
This worked correctly for integer-valued images (e.g. 8-bit or 16-bit), where the first histogram bin typically corresponds to zero-valued pixels and can safely be ignored.
However, when applied to float-valued images, the function produced incorrect results due to a mismatch between hist and bin_edges.
⚠️ Observed Symptoms
- The computed
(vmin, vmax)bounds were sometimes shifted or too narrow/wide. - The assumption that the first bin should always be removed caused misalignment when
bin_edgeswere non-integer floats (e.g. fromnp.linspace). - The end bin (
i_bin_max) could point to the wrong edge when the alignment was lost.
✅ Resolution
We reimplemented the function to:
- Remove the first bin only if
bin_edgesare of integer type (typically meaning the histogram comes from an integer-valued image where zero has a special meaning). - Maintain the correct alignment between
hist[i]and the interval[bin_edges[i], bin_edges[i+1]). - Fix an off-by-one error that previously caused inconsistencies, especially for edge cases like
percent = 0.
🎯 Additional Fix: Index Alignment
An important correction was made to the computation of the output range:
- Previously, accessing
bin_edges[i_bin_max]assumed that the end of the last bin wasbin_edges[-1], which was not guaranteed after trimming. - Now, we properly return
bin_edges[i_bin_min]andbin_edges[i_bin_max], based on explicitly corrected indices and consistent bin count logic. - As a result, setting
percent = 0now returns a(vmin, vmax)that spans exactly one bin — meaning that no contrast adjustment occurs in this edge case.
🧪 Final Function
import numpy as np
def hist_range_threshold(
hist: np.ndarray, bin_edges: np.ndarray, percent: float
) -> tuple[float, float]:
"""
Return the value range corresponding to the central `percent` of the histogram mass,
optionally excluding the first bin (assumed to represent zero-valued pixels in integer images).
Args:
hist: Histogram values (length N)
bin_edges: Bin edges (length N+1)
percent: Percent of the histogram mass to retain (between 0 and 100)
Returns:
(vmin, vmax): Value range corresponding to the central mass
"""
if not (0 <= percent <= 100):
raise ValueError("percent must be in [0, 100]")
hist_len = len(hist)
i_offset = 0
# Remove first bin only for integer-based histograms (e.g. zero-valued pixels)
if np.issubdtype(bin_edges.dtype, np.integer):
hist = hist[1:]
i_offset = 1
threshold = 0.5 * percent / 100 * hist.sum()
i_bin_min = max(np.cumsum(hist).searchsorted(threshold) - i_offset, 0)
i_bin_max = hist_len - np.searchsorted(np.cumsum(np.flipud(hist)), threshold)
vmin, vmax = bin_edges[i_bin_min], bin_edges[i_bin_max]
return vmin, vmaxMetadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working