Skip to content

Pillow cannot read from array gray image? #3781

Description

@zjplab

I have a pic:
391

Do some conversion:

AB = Image.open("./391.jpg").convert('RGB')
arr=np.array(AB)
gray=arr@np.array([0.2125, 0.7154, 0.0721]) # gray scale now 

Whatever mode you use, it just looks like fucked up:
image

But if you use scipy.misc.toimage
it is still usable:
image

I don't know how. Just very strange

Activity

  1. radarhere commented on Apr 10, 2019

    @radarhere
    Member

    Taking your code, I find that fromarray with L looks as you described, but if I omit the mode, it looks fine. Does this look okay for you? If not, what operating system and Pillow version are you using?

    import numpy as np
    from PIL import Image
    
    AB = Image.open("im.jpg")
    arr=np.array(AB)
    arr=arr@np.array([0.2125, 0.7154, 0.0721])
    
    # I agree that this does not look good
    im = Image.fromarray(arr, 'L')
    
    # This looks fine however
    im = Image.fromarray(arr)
    
    im.convert('RGB').save('out.jpg')
  2. zjplab commented on Apr 12, 2019

    @zjplab
    Author

    @radarhere This is very strange since fromarray by default should read data as RGB. I checked the code from scipy.misc.toimage they use a different way to circumvent this bug:

        if len(shape) == 2:
            shape = (shape[1], shape[0])  # columns show up first
            if mode == 'F':
                data32 = data.astype(numpy.float32)
                image = Image.frombytes(mode, shape, data32.tostring())
                return image
            if mode in [None, 'L', 'P']:
                bytedata = bytescale(data, high=high, low=low,
                                     cmin=cmin, cmax=cmax)
                image = Image.frombytes('L', shape, bytedata.tostring())
                if pal is not None:
                    image.putpalette(asarray(pal, dtype=uint8).tostring())
                    # Becomes a mode='P' automagically.
                elif mode == 'P':  # default gray-scale
                    pal = (arange(0, 256, 1, dtype=uint8)[:, newaxis] *
                           ones((3,), dtype=uint8)[newaxis, :])
                    image.putpalette(asarray(pal, dtype=uint8).tostring())
                return image
            if mode == '1':  # high input gives threshold for 1
                bytedata = (data > high)
                image = Image.frombytes('1', shape, bytedata.tostring())
                return image
            if cmin is None:
                cmin = amin(ravel(data))
            if cmax is None:
                cmax = amax(ravel(data))
            data = (data*1.0 - cmin)*(high - low)/(cmax - cmin) + low
            if mode == 'I':
                data32 = data.astype(numpy.uint32)
                image = Image.frombytes(mode, shape, data32.tostring())
            else:
                raise ValueError(_errstr)
            return image
  3. radarhere commented on Apr 12, 2019

    @radarhere
    Member

    fromarray by default should read data as RGB

    What leads you to this conclusion?

    https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.fromarray

    mode – Mode to use (will be determined from type if None) See: Modes.

    scipy's toimage doesn't even read it as RGB by default, it reads it as L.

    >>> import numpy as np
    >>> import scipy.misc
    >>> from PIL import Image
    >>> AB = Image.open("391.jpg").convert('RGB')
    >>> arr=np.array(AB)
    >>> gray=arr@np.array([0.2125, 0.7154, 0.0721]) # gray scale now 
    >>> scipy.misc.toimage(gray).mode
    'L'

    I'm confused. Could you clarify what you are after in this issue? Do you feel that Image.fromarray(arr, 'L') should work, and are asking why it does not?

  4. zjplab commented on Apr 14, 2019

    @zjplab
    Author

    @radarhere Yes I used scipy.misc.toimage in my code and I issued this just because I think Image.fromarray(arr, 'L') should work(many users might think this way too) but it does not.

  5. jgodwin commented on Jun 20, 2019

    @jgodwin

    Here's an even simpler example that triggers this behavior:

    from PIL import Image
    import numpy as np
    x = np.tile(np.arange(0,100).reshape(-1,1),300)
    Image.fromarray(x,mode='L').show()

    Should get this (with scipy.misc.toimage):
    image

    Instead get this:
    image

  6. dettmar commented on Jan 28, 2020

    @dettmar

    Having been dealing with similar problems for a waay to long time, I realized that the issue was resolved by tacking on .astype(np.uint8) to your array before passing it into Image.fromarray(), as such:

    AB = Image.open("im.jpg")
    arr=np.array(AB)
    arr=arr@np.array([0.2125, 0.7154, 0.0721])
    im = Image.fromarray(arr.astype(np.uint8), 'L')

    or

    x = np.tile(np.arange(0,100).reshape(-1,1),300)
    Image.fromarray(x.astype(np.uint8), mode='L').show()

    Although the most intuitive approach from the PIL library in my opinion would be to cast whatever array that is passed in as an argument to a uint8 since that's what the format mode="L" supports, I guess for now we can simply do it ourselves.

  7. radarhere commented on Jan 7, 2023

    @radarhere
    Member

    Regarding the original image, it is an RGB image.

    from PIL import Image
    AB = Image.open("391.jpg")
    print(AB.mode)  # RGB

    If you try to apply fromarray() to this using "L",

    import numpy as np
    from PIL import Image
    
    AB = Image.open("391.jpg")
    arr=np.array(AB)
    im = Image.fromarray(arr, "L")

    you get ValueError: Too many dimensions: 3 > 2. L is a single channel image, with two dimensions, width and height. RGB is a three channel image, and so has a third dimension.

    However, you've used the following line.

    gray=arr@np.array([0.2125, 0.7154, 0.0721]) # gray scale now 

    Before this, the "typestr" of the NumPy array is "|u1". The above line turns it into "<f8". If the mode argument is not given when using fromarray, then Pillow observes that the "typestr" of the NumPy array is "<f8", which is an F image. If you would like fromarray() to correctly understand the image as an L mode image, then it should have only one channel and "typestr" should be kept as "|u1"

    Pillow is internally calling arr.tobytes(), and then stepping over each row. It's not trying to interpret each value in the array in sequence. So if the data isn't related to the mode in a meaningful way, then the final image will not be meaningful.

    In the second image, the array has a "typestr" of "<i8". This isn't a type that Pillow supports.

    Image.fromarray(arr.astype(np.uint8), 'L') is certainly a solution, converting the NumPy data to a form that Pillow will understand. However, I stand by my original note on the first image that Image.fromarray(arr) works. If you would like it to be an L mode image afterwards, Image.fromarray(arr).convert('L') will take care of that.

    From what I see above, scipy is calling astype internally. So why doesn't Pillow also do that, to match the mode parameter?

    #2856 (comment)

    My impression of this parameter (which, tbh, is not well documented) is that it's for overriding the mode that's detected from the dtype. Raising an error where it doesn't match is going to be problematical for the intended use case.

    If the idea is that it is to override the detected mode, then automatically converting data to match the mode will break backwards compatibility.

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