What is the difference between images in 'P' and 'L' mode in PIL?
Normally, images are RGB, which means they have 3 channels, one for red, one for green and one for blue. That normally means that each pixel takes 3 bytes of storage, one for red, one for green and one for blue.
If you have a
P
mode image, that means it is palettised. That means there is a palette with up to 256 different colours in it, and instead of storing 3 bytes for R, G and B for each pixel, you store 1 byte which is the index into the palette. This confers both advantages and disadvantages. The advantage is that your image requires 1/3 of the space in memory and on disk. The disadvantage is that it can only represent 256 unique colours - so you may get banding or artefacts.If you have an
L
mode image, that means it is a single channel image - normally interpreted as greyscale. TheL
means that is just stores the Luminance. It is very compact, but only stores a greyscale, not colour.
You can tell which mode your image has by looking at:
image.mode
If your image is palettised it will be P
, or PA
if palettised with an alpha channel as well. If your image is greyscale it will be L
, or LA
if greyscale with alpha channel.
You convert between them using the convert(mode) function, e.g. to go to RGB mode, use:
image.convert('RGB')
I used the word "normally" quite a lot! Why? Because you can do abnormal things!
You can store a grey-looking image in an RGB format. All you do, is make the red component equal to the green component equal to the blue component (R=G=B) and it will appear grey but be stored in an inefficient RGB format that takes 3x the space it might otherwise need to.
You can store a grey-looking image in a P format, you just make sure all the palette entries have the R=G=B.
Here's the kicker... if you want and expect an RGB image, you should just convert to RGB on opening:
im = Image.open("image.jpg").convert('RGB')
that way you will never have problems with GIF files (which are always palettised) nor with PNG files which can be palettised and can be greyscale or RGB. You will not normally get problems with JPEG images because they are pretty much always RGB anyway.
Here's an example to demonstrate. Start with this red-blue gradient image:
Let's use IPython
to look at in RGB space. First, look at the Red channel:
In [21]: im = Image.open('a.png').convert('RGB')
In [22]: np.array(im.getchannel(0))
Out[22]:
array([[255, 255, 255, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255],
[254, 254, 254, ..., 254, 254, 254],
...,
[ 1, 1, 1, ..., 1, 1, 1],
[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 0, 0, ..., 0, 0, 0]], dtype=uint8)
Notice it has 255 at the top because it is red, and 0 at the bottom because there is no red there.
Now let's look at the Green channel, it is 0 everywhere because there is no green.
In [23]: np.array(im.getchannel(1))
Out[23]:
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)
And finally, let's look at the Blue channel. It is 0 at the top where the image is pure Red and 255 at the bottom where the image is pure Blue.
In [24]: np.array(im.getchannel(2))
Out[24]:
array([[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 0, 0, ..., 0, 0, 0],
[ 1, 1, 1, ..., 1, 1, 1],
...,
[254, 254, 254, ..., 254, 254, 254],
[255, 255, 255, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255]], dtype=uint8)
Now let's look at the same image in palette mode.
# Convert to palette mode
im = Image.open('a.png').convert('P')
# Extract the palette and reshape as 256 entries of 3 RGB bytes each
In [27]: np.array(im.getpalette()).reshape(256,3)
Out[27]:
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],
[ 51, 0, 0],
[102, 0, 0],
[153, 0, 0],
[204, 0, 0],
[255, 0, 0], <--- entry 15 = rgb(255,0,0) = Red
[ 0, 51, 0],
[ 51, 51, 0],
[102, 51, 0],
[153, 51, 0],
[204, 51, 0],
[255, 51, 0],
[ 0, 102, 0],
[ 51, 102, 0],
[102, 102, 0],
[153, 102, 0],
[204, 102, 0],
[255, 102, 0],
[ 0, 153, 0],
[ 51, 153, 0],
[102, 153, 0],
[153, 153, 0],
[204, 153, 0],
[255, 153, 0],
[ 0, 204, 0],
[ 51, 204, 0],
[102, 204, 0],
[153, 204, 0],
[204, 204, 0],
[255, 204, 0],
[ 0, 255, 0],
[ 51, 255, 0],
[102, 255, 0],
[153, 255, 0],
[204, 255, 0],
[255, 255, 0],
...
... up to 256 entries
Now get the indices into the palette:
In [28]: np.array(im.getchannel(0))
Out[28]:
array([[ 15, 15, 15, ..., 15, 15, 15],
[ 15, 15, 15, ..., 15, 15, 15],
[ 15, 15, 15, ..., 15, 15, 15],
...,
[190, 190, 190, ..., 190, 190, 190],
[190, 190, 190, ..., 190, 190, 190],
[190, 190, 190, ..., 190, 190, 190]], dtype=uint8)
Now you can see that the top row of the image has palette index 15, which, if you look it up in the preceding palette, you will see is Red.
Now let's look at the same image in L mode - remember L means "Luminance" which is just a fancy way of saying "brightness" on a scale of black to white, i.e. greyscale :
# Open into greyscale, or L mode
In [1]: im = Image.open('a.png').convert('L')
# Dump the pixels
In [2]: np.array(im.getchannel(0))
Out[2]:
array([[76, 76, 76, ..., 76, 76, 76],
[76, 76, 76, ..., 76, 76, 76],
[76, 76, 76, ..., 76, 76, 76],
...,
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29]], dtype=uint8)
So, now the top row of the image is 76 and the bottom row is 29. What are those? Well, the formula for converting RGB to L is:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
So, in the top row, R=255, G=0, B=0, so the Luminance has become:
L = 255 * 299/1000 + 0 + 0
L = 76
And on the bottom row, R=0, G=0, B=255, so the Luminance has become:
L = 0 + 0 + 255 * 114/1000
L = 29
Keywords: Python, PIL, Pillow, palette, image processing, prime.
PIL Image convert from I mode to P mode
I think the issue is that your values are scaled to the range 0..65535 rather than 0..255.
If you do this, you will see the values are larger than you expected:
i = Image.open('depth.png')
n = np.array(i)
print(n.max(),n.mean())
# prints 32257, 6437.173
So, I quickly tried:
n = (n/256).astype(np.uint8)
r = Image.fromarray(n)
r=r.convert('P')
r.putpalette(custom_palette) # I grabbed this from your pastebin
PIL Image mode P -> RGBA
This issue was reported here:
https://bitbucket.org/effbot/pil-2009-raclette/issue/8/corrupting-images-in-palette-mode
In March 2012, a comment says it's now fixed in development version of PIL. The most recent released version is 1.1.7, so the fix won't be available until 1.2 comes out. PIL updates very slowly, so don't expect this to come out soon.
List of Image modes
There are two distinct concepts in Pillow, with confusingly similar names:
"Modes"
These are listed at https://pillow.readthedocs.io/en/latest/handbook/concepts.html#modes.
Per those docs:
The mode of an image defines the type and depth of a pixel in the image.
This kind of "mode" is what is exposed through an Image
's .mode
attribute, can be changed through the .convert()
method, and can be passed to methods that take a mode
parameter. They are not the same as "raw modes".
"Raw modes"
These are used internally by the raw decoder, which converts uncompressed data from an image file into a format that a PIL Image
object can understand. There are several times more "raw modes" than "modes", and they convey information about not only the type (colored or grayscale) and bit depth of pixels in an image, but also their layout in the file. For example, raw mode RGB;L
is documented as meaning "24-bit true colour, line interleaved (first all red pixels, then all green pixels, finally all blue pixels)."
As noted in the docs linked above (and also in the old PIL documentation), a list of raw modes can be found in Unpack.c
. You'll find the list near the end of the file.
Unpack.c
from the current main branch of Pillow: https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Unpack.c
Unpack.c
from the final release of PIL: http://svn.effbot.org/public/tags/pil-1.1.7/libImaging/Unpack.c
PIL Image mode I is grayscale?
Is there a way of specifying a coloured image based on a 32-bit integer?
Yes, use the RGB format for that, but instead use an integer instead of "red" as the color argument:
from PIL import Image
r, g, b = 255, 240, 227
intcolor = (b << 16 ) | (g << 8 ) | r
print intcolor # 14938367
ImgRGB = Image.new("RGB", (200, 200), intcolor)
ImgRGB.show()
Mapping values of pixels in an image with Python
As I mentioned in the comments, for
loops are slow, inefficient and error-prone for processing images in Python.
Here's a more efficient way of doing what you ask. Your colours of 0, 1, 2 and 255 are hard to see on StackOverflow's background and hard to differentiate from each other because 3 of them are essentially black, so I am transforming 64 and 150 into 192 and 32 but you can adapt to your own numbers:
from PIL import Image
# Load image and ensure greyscale, i.e. 'L'
im = Image.open('image.png').convert('L')
# Remap colours 150 -> 32, 64 -> 192
res = im.point((lambda p: 32 if p==150 else (192 if p==64 else 0)))
res.save('result.png')
If you want to use Numpy instead, which is also vectorised and optimised, it might look like this (other techniques are also possible):
from PIL import Image
import numpy as np
# Load image and ensure greyscale, i.e. 'L'
im = Image.open('image.png').convert('L')
# Make into Numpy array
na = np.array(im)
# Anywhere pixels are 150, make them 32
na[na==150] = 32
# Anywhere pixels are 64, make them 192
na[na==64] = 192
# Convert back to PIL Image and save
Image.fromarray(na).save('result.png')
What does the 'L' mean when someone is explaining code?
From the docs for PIL:
The default method of converting a greyscale (“L”)
Related Topics
How to Modify List Entries During For Loop
Why Is "Except: Pass" a Bad Programming Practice
How to Define a Two-Dimensional Array
Convert Hex String to Integer in Python
Difference Between Text and Innerhtml Using Selenium
"Ask Forgiveness Not Permission" - Explain
How to Find the Cumulative Sum of Numbers in a List
Multiple Assignment and Evaluation Order in Python
Reading Binary File and Looping Over Each Byte
List Comprehension Vs. Lambda + Filter
Python Requests Throwing Sslerror
Text Progress Bar in Terminal With Block Characters
Create List of Single Item Repeated N Times
Python List of Dictionaries Search
Split Pandas Dataframe Based on Groupby
How to Read Large Text Files Line by Line, Without Loading It into Memory