python-pillow / Pillow

Python Imaging Library (Fork)
https://python-pillow.org
Other
12.1k stars 2.21k forks source link

Image conversion should scale pixel values accordingly #3159

Open SolarLiner opened 6 years ago

SolarLiner commented 6 years ago

What did you do?

I tried to convert grayscale images of different modes together

What did you expect to happen?

Conversion should scale values; for example, converting from float to 8-bit should have the values scaled by 255, converting from 8-bit to 16-bit should have the values scaled by 65535/255, etc.

What actually happened?

Values are being clamped

>>> img = Image.open('16bit_image.png')
>>> img.mode
'I'
>>> numpy.array(img)
array([[51559, 52726, 50875, ..., 30493, 30991, 29907],
       [51743, 52185, 51221, ..., 30841, 29920, 30793],
       [51279, 50534, 51128, ..., 31532, 30852, 30651],
       ...,
       [28288, 27868, 28032, ..., 34367, 34235, 34312],
       [26900, 27567, 28120, ..., 36229, 34607, 33399],
       [27966, 28224, 27962, ..., 36223, 35851, 34477]], dtype=int32)
>>> numpy.array(img.convert('L'))
array([[255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       ...,
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255]], dtype=uint8)

Floating point data really doesn't go over well either

>>> img_float = Image.fromarray(numpy.divide(numpy.array(img), 2**16-1))
>>> numpy.array(img_float)
array([[0.7867399 , 0.8045472 , 0.77630275, ..., 0.46529335, 0.47289234,
        0.45635158],
       [0.78954756, 0.79629207, 0.78158236, ..., 0.4706035 , 0.45654994,
        0.46987107],
       [0.78246737, 0.7710994 , 0.7801633 , ..., 0.48114747, 0.47077134,
        0.46770427],
       ...,
       [0.4316472 , 0.42523843, 0.4277409 , ..., 0.5244068 , 0.52239263,
        0.52356756],
       [0.41046768, 0.42064545, 0.4290837 , ..., 0.55281913, 0.52806896,
        0.50963604],
       [0.4267338 , 0.43067065, 0.42667276, ..., 0.5527275 , 0.5470512 ,
        0.5260853 ]], dtype=float32)
>>> img_oct = img_float.convert('L')
>>> numpy.array(img_oct)
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)
>>>

The input image is a 16 bit PNG made with GIMP, as attached below. terrain_input.png

What versions of Pillow and Python are you using?

Using Python 3.6.5 and Pillow 5.1.0

radarhere commented 5 years ago

With regards to the floating point data, if I change if from 2**16-1 to 2**8-1, it works fine.

import numpy
from PIL import Image
img = Image.open('/Users/andrewmurray/Desktop/16bit_image.png')
img_float = Image.fromarray(numpy.divide(numpy.array(img), 2**8-1))
print(numpy.array(img_float))

img_oct = img_float.convert('L')
print(numpy.array(img_oct))
[[202.19215  206.76863  199.50981  ... 119.58039  121.53333  117.28236 ]
 [202.91373  204.64706  200.86667  ... 120.9451   117.333336 120.75687 ]
 [201.09412  198.17255  200.50197  ... 123.6549   120.988235 120.2     ]
 ...
 [110.933334 109.28628  109.92941  ... 134.77255  134.2549   134.55687 ]
 [105.4902   108.10588  110.27451  ... 142.07451  135.71373  130.97647 ]
 [109.670586 110.68235  109.6549   ... 142.05098  140.59216  135.20392 ]]
[[202 206 199 ... 119 121 117]
 [202 204 200 ... 120 117 120]
 [201 198 200 ... 123 120 120]
 ...
 [110 109 109 ... 134 134 134]
 [105 108 110 ... 142 135 130]
 [109 110 109 ... 142 140 135]]
radarhere commented 5 years ago

With regards to the first part, I've created PR #3838 to address this.

SolarLiner commented 5 years ago

Thanks a lot for that. It'll help a lot for my terrain generation library!

radarhere commented 5 years ago

3838 has been merged.

radarhere commented 5 years ago

It turns out that this situation is more complicated. See https://github.com/python-pillow/Pillow/pull/3838#discussion_r292114051

radarhere commented 5 years ago

4044 also reports this issue for I to RGBA conversion, and #5642 for I to RGB conversion.

makslevental commented 3 years ago

I don't understand is this happening or not?

fp = "/home/max/dev_projects/cuda_blob/data/S_000_1752450056/Tile_r1-c1_S_000_1752450056.tif"
img = Image.open(fp).convert('I').convert('F')
imarray = cp.array(img)
print(imarray.min(), imarray.max())

gives

0.0 254.0

Shouldn't the pixel values be scaled to [0,1]?

using

pillow                    8.0.1 
python                    3.8.5
radarhere commented 2 years ago

This issue is more complex to resolve than I initially thought, and there is not even a consensus that it should be fixed.

Yay295 commented 2 years ago

I think this is related. I noticed that https://github.com/python-pillow/Pillow/blob/3f960d9a94e5f2cd789da8b9fd0d1d6db8a60cba/src/libImaging/Fill.c uses the same 0-255 range for every image type.

suneeta-mall commented 1 month ago

I am currently using this snippet to work around this issue:

def safe_image_loading(path: str) -> Image:
    img = Image.open(path)
    bit_size = re.findall(r"\d+", img.mode)
    bit_size = int(bit_size[0]) if bit_size else 8
    if bit_size not in [8, 16, 32]:
        raise ValueError(f"Unsupported file type, supported bit size is {bit_size}")
    if bit_size != 8:
        max_value = 2**bit_size - 1
        img_arr = (np.array(img) / max_value) * 255.0
        img = Image.fromarray(img_arr.astype(np.uint8))
    return img.convert("L")

would be great to have native support for bit sizes in PIL.