Title: Contrast adjustment with 'eliminate outliers' failed for float images with high dynamic range · Issue #46 · PlotPyStack/PlotPy · GitHub
Open Graph Title: Contrast adjustment with 'eliminate outliers' failed for float images with high dynamic range · Issue #46 · PlotPyStack/PlotPy
X Title: Contrast adjustment with 'eliminate outliers' failed for float images with high dynamic range · Issue #46 · PlotPyStack/PlotPy
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...
Open Graph 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 ...
X 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 ...
Opengraph URL: https://github.com/PlotPyStack/PlotPy/issues/46
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Contrast adjustment with 'eliminate outliers' failed for float images with high dynamic range","articleBody":"## 🐞 Problem Summary\n\nThe `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*.\n\nThis 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.\n\nHowever, when applied to **float-valued images**, the function produced incorrect results due to a mismatch between `hist` and `bin_edges`.\n\n## ⚠️ Observed Symptoms\n\n- The computed `(vmin, vmax)` bounds were sometimes shifted or too narrow/wide.\n- The assumption that the first bin should always be removed caused misalignment when `bin_edges` were non-integer floats (e.g. from `np.linspace`).\n- The end bin (`i_bin_max`) could point to the wrong edge when the alignment was lost.\n\n## ✅ Resolution\n\nWe reimplemented the function to:\n\n- **Remove the first bin only** if `bin_edges` are of integer type (typically meaning the histogram comes from an integer-valued image where zero has a special meaning).\n- Maintain the correct alignment between `hist[i]` and the interval `[bin_edges[i], bin_edges[i+1])`.\n- **Fix an off-by-one error** that previously caused inconsistencies, especially for edge cases like `percent = 0`.\n\n## 🎯 Additional Fix: Index Alignment\n\nAn important correction was made to the computation of the output range:\n\n- Previously, accessing `bin_edges[i_bin_max]` assumed that the end of the last bin was `bin_edges[-1]`, which was not guaranteed after trimming.\n- Now, we properly return `bin_edges[i_bin_min]` and `bin_edges[i_bin_max]`, based on explicitly corrected indices and consistent bin count logic.\n- As a result, setting `percent = 0` now returns a `(vmin, vmax)` that spans exactly one bin — meaning that **no contrast adjustment occurs** in this edge case.\n\n## 🧪 Final Function\n\n```python\nimport numpy as np\n\ndef hist_range_threshold(\n hist: np.ndarray, bin_edges: np.ndarray, percent: float\n) -\u003e tuple[float, float]:\n \"\"\"\n Return the value range corresponding to the central `percent` of the histogram mass,\n optionally excluding the first bin (assumed to represent zero-valued pixels in integer images).\n\n Args:\n hist: Histogram values (length N)\n bin_edges: Bin edges (length N+1)\n percent: Percent of the histogram mass to retain (between 0 and 100)\n\n Returns:\n (vmin, vmax): Value range corresponding to the central mass\n \"\"\"\n if not (0 \u003c= percent \u003c= 100):\n raise ValueError(\"percent must be in [0, 100]\")\n\n hist_len = len(hist)\n i_offset = 0\n\n # Remove first bin only for integer-based histograms (e.g. zero-valued pixels)\n if np.issubdtype(bin_edges.dtype, np.integer):\n hist = hist[1:]\n i_offset = 1\n\n threshold = 0.5 * percent / 100 * hist.sum()\n\n i_bin_min = max(np.cumsum(hist).searchsorted(threshold) - i_offset, 0)\n i_bin_max = hist_len - np.searchsorted(np.cumsum(np.flipud(hist)), threshold)\n\n vmin, vmax = bin_edges[i_bin_min], bin_edges[i_bin_max]\n return vmin, vmax\n","author":{"url":"https://github.com/PierreRaybaut","@type":"Person","name":"PierreRaybaut"},"datePublished":"2025-07-02T14:07:51.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/46/PlotPy/issues/46"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:a83f237d-3536-ef09-d89f-5a44e16dbd85 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B2DC:B4897:18FA30E:21B24C0:6972B4B2 |
| html-safe-nonce | 19f03498107904fe087f3abe506f90e22c97852d53798321ab9a0328165e9330 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCMkRDOkI0ODk3OjE4RkEzMEU6MjFCMjRDMDo2OTcyQjRCMiIsInZpc2l0b3JfaWQiOiIzMDAxMjkzNjYzMjYzNDM4MDAyIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | ae15df8014fe3b5c58b01ec5a7b37acd08084e6e146ebb9972efaa9c78819170 |
| hovercard-subject-tag | issue:3196054750 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/PlotPyStack/PlotPy/46/issue_layout |
| twitter:image | https://opengraph.githubassets.com/0dc6029d3f872ee7f84f50930b07da5d5c939b3e4ccfc81e232e1a4f0d698584/PlotPyStack/PlotPy/issues/46 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/0dc6029d3f872ee7f84f50930b07da5d5c939b3e4ccfc81e232e1a4f0d698584/PlotPyStack/PlotPy/issues/46 |
| og:image:alt | 🐞 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 ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | PierreRaybaut |
| hostname | github.com |
| expected-hostname | github.com |
| None | 51c0d0848f5569c6fa2198e9d69bd5f8f94a83c9fa3659e40728e7732afab130 |
| turbo-cache-control | no-preview |
| go-import | github.com/PlotPyStack/PlotPy git https://github.com/PlotPyStack/PlotPy.git |
| octolytics-dimension-user_id | 145201262 |
| octolytics-dimension-user_login | PlotPyStack |
| octolytics-dimension-repository_id | 671159499 |
| octolytics-dimension-repository_nwo | PlotPyStack/PlotPy |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 671159499 |
| octolytics-dimension-repository_network_root_nwo | PlotPyStack/PlotPy |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 10c5e2f2307495b2750073db87e9a5d3356a924f |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width