Skip to content

Image conversion should scale pixel values accordingly #3159

Description

@SolarLiner

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

Activity

  1. radarhere commented on May 8, 2019

    @radarhere
    Member

    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]]
  2. radarhere commented on May 9, 2019

    @radarhere
    Member

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

  3. SolarLiner commented on May 9, 2019

    @SolarLiner
    Author

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

  4. radarhere commented on Jun 5, 2019

    @radarhere
    Member

    #3838 has been merged.

  5. radarhere commented on Jun 11, 2019

    @radarhere
    Member

    It turns out that this situation is more complicated. See #3838 (comment)

  6. radarhere commented on Sep 5, 2019

    @radarhere
    Member

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

  7. makslevental commented on Dec 31, 2020

    @makslevental

    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
    
  8. radarhere commented on Mar 2, 2022

    @radarhere
    Member

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

  9. Yay295 commented on Sep 1, 2022

    @Yay295
    Contributor

    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.

  10. suneeta-mall commented on Jul 18, 2024

    @suneeta-mall

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions