|
29 | 29 | def hist_range_threshold( |
30 | 30 | hist: np.ndarray, bin_edges: np.ndarray, percent: float |
31 | 31 | ) -> tuple[float, float]: |
32 | | - """Return the range corresponding to the given percent of the histogram |
| 32 | + """Return the range corresponding to the central `percent` of the histogram mass. |
| 33 | + This can be used to eliminate outliers symmetrically, e.g. for contrast adjustment. |
| 34 | +
|
| 35 | + Notes: |
| 36 | + - If the histogram comes from an image with integer values (e.g. 0-255), |
| 37 | + the first bin is assumed to correspond to value 0 and will be ignored. |
| 38 | + - For floating-point images, all bins are considered. |
33 | 39 |
|
34 | 40 | Args: |
35 | | - hist (numpy.ndarray): The histogram |
36 | | - bin_edges (numpy.ndarray): The bin edges |
37 | | - percent (float): The percent of the histogram |
| 41 | + hist: The histogram (length N) |
| 42 | + bin_edges: The bin edges (length N+1) |
| 43 | + percent: Percent of histogram mass to retain (between 0 and 100) |
38 | 44 |
|
39 | 45 | Returns: |
40 | | - tuple[float, float]: The range |
| 46 | + A tuple containing the minimum and maximum values of the range |
| 47 | + corresponding to the central `percent` of the histogram mass. |
41 | 48 | """ |
42 | | - hist = np.concatenate((hist, [0])) |
43 | | - hist = hist[1:] |
44 | | - bin_edges = bin_edges[1:] |
| 49 | + if not (0 <= percent <= 100): |
| 50 | + raise ValueError("percent must be in (0, 100]") |
| 51 | + |
| 52 | + hist_len = len(hist) |
| 53 | + i_offset = 0 |
| 54 | + |
| 55 | + # 1. Remove the first bin (typically corresponding to 0), only for integer images |
| 56 | + if np.issubdtype(bin_edges.dtype, np.integer): |
| 57 | + hist = hist[1:] |
| 58 | + i_offset = 1 |
| 59 | + |
| 60 | + # 3. Threshold: keep `percent`% of the mass → remove (1 - percent)% symmetrically |
45 | 61 | threshold = 0.5 * percent / 100 * hist.sum() |
46 | 62 |
|
47 | | - i_bin_min = np.cumsum(hist).searchsorted(threshold) |
48 | | - i_bin_max = -1 - np.cumsum(np.flipud(hist)).searchsorted(threshold) |
| 63 | + # 4. Find index where left cumulative sum exceeds threshold |
| 64 | + i_bin_min = max(np.cumsum(hist).searchsorted(threshold) - i_offset, 0) |
| 65 | + |
| 66 | + # 5. Find index where right cumulative sum exceeds threshold |
| 67 | + i_bin_max = hist_len - np.searchsorted(np.cumsum(np.flipud(hist)), threshold) |
| 68 | + |
| 69 | + # 6. Return bounds as [bin_edges[i_min], bin_edges[i_max + 1]] |
| 70 | + vmin, vmax = bin_edges[i_bin_min], bin_edges[i_bin_max] |
49 | 71 |
|
50 | | - return bin_edges[i_bin_min], bin_edges[i_bin_max] |
| 72 | + return vmin, vmax |
51 | 73 |
|
52 | 74 |
|
53 | 75 | def lut_range_threshold( |
|
0 commit comments