-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Update rescale_intensity to prevent under/overflow and produce proper output dtype #4585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
de6f3a1
f08edc0
51103d5
510b9a0
c509c67
8f27ee7
b33743d
8cfbdfb
57a1570
7568935
4aadf3d
1e80704
71ab463
02d8d89
e50169e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -266,6 +266,50 @@ def intensity_range(image, range_values='image', clip_negative=False): | |
return i_min, i_max | ||
|
||
|
||
def _output_dtype(dtype_or_range): | ||
"""Determine the output dtype for rescale_intensity. | ||
|
||
The dtype is determined according to the following rules: | ||
- if ``dtype_or_range`` is a dtype, that is the output dtype. | ||
- if ``dtype_or_range`` is a dtype string, that is the dtype used, unless | ||
it is not a NumPy data type (e.g. 'uint12' for 12-bit unsigned integers), | ||
in which case the data type that can contain it will be used | ||
(e.g. uint16 in this case). | ||
- if ``dtype_or_range`` is a pair of values, the output data type will be | ||
float. | ||
|
||
Parameters | ||
---------- | ||
dtype_or_range : type, string, or 2-tuple of int/float | ||
The desired range for the output, expressed as either a NumPy dtype or | ||
as a (min, max) pair of numbers. | ||
|
||
Returns | ||
------- | ||
out_dtype : type | ||
The data type appropriate for the desired output. | ||
""" | ||
if type(dtype_or_range) in [list, tuple, np.ndarray]: | ||
# pair of values: always return float. | ||
return np.float_ | ||
if type(dtype_or_range) == type: | ||
# already a type: return it | ||
return dtype_or_range | ||
if dtype_or_range in DTYPE_RANGE: | ||
# string key in DTYPE_RANGE dictionary | ||
try: | ||
# if it's a canonical numpy dtype, convert | ||
return np.dtype(dtype_or_range).type | ||
except TypeError: # uint10, uint12, uint14 | ||
# otherwise, return uint16 | ||
return np.uint16 | ||
else: | ||
raise ValueError( | ||
'Incorrect value for out_range, should be a valid image data ' | ||
f'type or a pair of values, got {dtype_or_range}.' | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add an else here to raise a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand and support the first part of your suggestion. But why should
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lagru probably what I wrote was not clear, please tell me if it's clearer now. The function should never return None, it should return a valid value or error out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, then we are in agreement. 👍 |
||
|
||
def rescale_intensity(image, in_range='image', out_range='dtype'): | ||
"""Return image after stretching or shrinking its intensity levels. | ||
|
||
|
@@ -297,6 +341,12 @@ def rescale_intensity(image, in_range='image', out_range='dtype'): | |
Image array after rescaling its intensity. This image is the same dtype | ||
as the input image. | ||
|
||
Notes | ||
----- | ||
.. versionchanged:: 0.17 | ||
The dtype of the output array has changed to match the output dtype, or | ||
float if the output range is specified by a pair of floats. | ||
|
||
See Also | ||
-------- | ||
equalize_hist | ||
|
@@ -334,17 +384,26 @@ def rescale_intensity(image, in_range='image', out_range='dtype'): | |
array([0.5, 1. , 1. ]) | ||
|
||
If you have an image with signed integers but want to rescale the image to | ||
just the positive range, use the `out_range` parameter: | ||
just the positive range, use the `out_range` parameter. In that case, the | ||
output dtype will be float: | ||
|
||
>>> image = np.array([-10, 0, 10], dtype=np.int8) | ||
>>> rescale_intensity(image, out_range=(0, 127)) | ||
array([ 0, 63, 127], dtype=int8) | ||
array([ 0. , 63.5, 127. ]) | ||
|
||
To get the desired range with a specific dtype, use ``.astype()``: | ||
|
||
>>> rescale_intensity(image, out_range=(0, 127)).astype(np.int8) | ||
array([ 0, 63, 127], dtype=int8) | ||
""" | ||
dtype = image.dtype.type | ||
if out_range in ['dtype', 'image']: | ||
out_dtype = _output_dtype(image.dtype.type) | ||
else: | ||
out_dtype = _output_dtype(out_range) | ||
|
||
imin, imax = intensity_range(image, in_range) | ||
omin, omax = intensity_range(image, out_range, clip_negative=(imin >= 0)) | ||
imin, imax = map(float, intensity_range(image, in_range)) | ||
omin, omax = map(float, intensity_range(image, out_range, | ||
clip_negative=(imin >= 0))) | ||
|
||
# Fast test for multiple values, operations with at least 1 NaN return NaN | ||
if np.isnan(imin + imax + omin + omax): | ||
|
@@ -358,8 +417,8 @@ def rescale_intensity(image, in_range='image', out_range='dtype'): | |
image = np.clip(image, imin, imax) | ||
|
||
if imin != imax: | ||
image = (image - imin) / float(imax - imin) | ||
return np.asarray(image * (omax - omin) + omin, dtype=dtype) | ||
image = (image - imin) / (imax - imin) | ||
return np.asarray(image * (omax - omin) + omin, dtype=out_dtype) | ||
|
||
|
||
def _assert_non_negative(image): | ||
|
Uh oh!
There was an error while loading. Please reload this page.