PIL adding text to a gif frames adds noise to the picture - python

I'm creating a simple GIF animation using PIL:
from PIL import Image, ImageDraw, ImageFont
images = []
for x, i in enumerate(range(10)):
image = Image.new(mode="RGB", size=(320, 60), color="orange")
draw = ImageDraw.Draw(image)
fnt = ImageFont.truetype('font.ttf', size=10)
draw.text((10, 10), ("%s" % x), fill=(0, 0, 255), font=fnt)
images.append(image)
images[0].save("result/pil.gif", save_all=True, append_images=images[1:], duration=1000, loop=0, format="GIF")
The problem is that whenever I use Draw.text, image's background is getting some kind of white noze:
I found some info that I have to use getpalette from the first frame and putpalette for all the other frames like this:
for x, i in enumerate(range(10)):
image = Image.new(mode="RGB", size=(320, 60), color="orange")
if x == 0:
palette = image.getpalette()
else:
image.putpalette(palette)
But it just gives me: ValueError: illegal image mode.
What's the reason of the noizy background and how can I fix it?
UPD I was able to fix the background by changing image mode to "P", but in this case my fonts became unreadable. These are examples with RGB mode (fonts are well) and P mode (fonts are awful):
Why am I getting either nice background or nice fonts but not both? Is there a workaround?

This is dithering that happens, because gif can contain only colors from palette of size 256. Most likely PIL uses very basic algorithm to convert from RGB format to indexed format, which is required by gif. As your image contains colors #ff9900 and #ffcc00, then palette presumably consists of hex values 00, 33, 66, 99, cc, ff for each byte and has size 6x6x6 = 216, which fits nicely into 256 possible values. 'orange' has value of #ffa500 and can't be represented by such palette, so the background is filled by nearest available colors.
You can try to use color '#ff9900' instead of 'orange'. Hopefully this color can be represented by palette, as it is present in your noisy image.
You can also try to convert from RGB to indexed format using your own palette as an argument in quantize method, as suggested in this answer. Adding the following line results in nice solid background:
image = image.quantize(method=Image.MEDIANCUT)
Or you can just save RGB image with PNG format. In this case it will be saved as APNG image.
images[0].save("pil.png", save_all=True, append_images=images[0:],duration=1000, loop=0, format="PNG")

user13044086 has given a generalised version of the problem, the specifics are that a gif is a palletised format, to convert your original RGB to a palette pillow needs to restrict it from "true color" to just 256 colors.
To do that, it will convert the image to mode L, and as you can see in the documentation if no transformation matrix is given that's just an alias for calling quantize with the default parameters. The relevant default parameter here is dither, meaning if the image has more than 256 colors try to "emulate" missing ones by having individual dots of nearby colors.
Now you might complain that you don't have more than 256 colors, but you probably do due to font antialiasing, and algorithms don't necessarily have "taste" so instead of dithering in the letter, it dithers very visibly in the background.
You can mitigate this issue by explicitly quantizing providing an explicit method as suggested, or just disabling dithering (which will probably yield lower-quality results but will certainly be faster):
images.append(image.quantize(dither=Image.NONE))
Manually crafting your own palette and passing that to quantize could also work.

Related

Convert gray pixel_array from DICOM to RGB image

I'm reading DICOM gray image file as
gray = dicom.dcmread(file).pixel_array
There I've got (x,y) shape but I need RGB (x,y,3) shape
I'm trying to convert using CV
img = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
And for testing I'm writing it to file cv2.imwrite('dcm.png', img)
I've got extremely dark image on output which is wrong, what is correct way to convert pydicom image to RGB?
To answer your question, you need to provide a bit more info, and be a bit clearer.
First what are you trying to do? Are you trying to only get an (x,y,3) array in memory? or are you trying to convert the dicom file to a .png file? ...they are very different things.
Secondly, what modality is your dicom image?
It's likely (unless its ultrasound or perhaps nuc med) a 16 bit greyscale image, meaning the data is 16 bit, meaning your gray array above is 16 bit data.
So the first thing to understand is window levelling and how to display a 16-bit image in 8 bits. have a look here: http://www.upstate.edu/radiology/education/rsna/intro/display.php.
If it's a 16-bit image, if you want to view it as a greyscale image in rgb format, then you need to know what window level you're using or need, and adjust appropriately before saving.
Thirdly, like lenik mention above, you need to apply the dicom slope/intercept values to your pixel data prior to using.
If your problem is just making a new array with extra dimension for rgb (so sizes (r,c) to (r,c,3)), then it's easy
# orig is your read in dcmread 2D array:
r, c = orig.shape
new = np.empty((w, h, 3), dtype=orig.dtype)
new[:,:,2] = new[:,:,1] = new[:,:,0] = orig
# or with broadcasting
new[:,:,:] = orig[:,:, np.newaxis]
That will give you the 3rd dimension. BUT the values will still all be 16-bit, not 8 bit as needed if you want it to be RGB. (Assuming your image you read with dcmread is CT, MR or equivalent 16-bit dicom - then the dtype is likely uint16).
If you want it to be RGB, then you need to convert the values to 8-bit from 16-bit. For that you'll need to decide on a window/level and apply it to select the 8-bit values from the full 16-bit data range.
Likely your problem above - I've got extremely dark image on output which is wrong - is actually correct, but it's dark because the window/level cv is using by default makes it 'look' dark, or it's correct but you didn't apply the slope/intercept.
If what you want to do is convert the dicom to png (or jpg), then you should probably use PIL or matplotlib rather than cv. Both of those offer easy ways to save a 16 bit 2D array (which is what you 'gray' is in your code above), both which allow you to specify window and level when saving to png or jpg. CV is complete overkill (meaning much bigger/slower to load, and much higher learning curve).
Some psueudo code using matplotlib. The vmin/vmax values you need to adjust - the ones here would be approximately ok for a CT image.
import matplotlib.pyplot as plt
df = dcmread(file)
slope = float(df.RescaleSlope)
intercept = float(df.RescaleIntercept)
df_data = intercept + df.pixel_array * slope
# tell matplotlib to 'plot' the image, with 'gray' colormap and set the
# min/max values (ie 'black' and 'white') to correspond to
# values of -100 and 300 in your array
plt.imshow(df_data, cmap='gray', vmin=-100, vmax=300)
# save as a png file
plt.savefig('png-copy.png')
that will save a png version, but with axes drawn as well. To save as just an image, without axes and no whitespace, use this:
inches = (3,3)
dpi = 150
fig, ax = plt.subplots(figsize=inches, dpi=dpi)
fig.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
ax.imshow(df_data, cmap='gray', vmin=-100, vmax=300)
fig.save('copy-without-whitespace.png')
The full tutorial on reading DICOM files is here: https://www.kaggle.com/gzuidhof/full-preprocessing-tutorial
Basically, you have to extract parameters slope and interception from the DICOM file and do the math for every pixel: hu = pixel_value * slope + intercept -- all this explained in the tutorial with the code samples and pictures.

Python PIL remove every alpha channel completely

I tried so hard to converting PNG to Bitmap smoothly but failed every time.
but now I think I might found a reason.
it's because of the alpha channels.
('feather' in Photoshop)
Input image:
Output I've expected:
Current output:
I want to convert it to 8bit Bitmap and colour every invisible(alpha) pixels to purple(#FF00FF) and set them to dot zero. (very first palette)
but apparently, the background area and the invisible area around the actual image has a different colour.
i want all of them coloured same as background.
what should i do?
i tried these three
image = Image.open(file).convert('RGB')
image = Image.open(file)
image = image.convert('P')
pp = image.getpalette()
pp[0] = 255
pp[1] = 0
pp[2] = 255
image.putpalette(pp)
image = Image.open('feather.png')
result = image.quantize(colors=256, method=2)
the third method looks better but it becomes the same when I save it as a bitmap.
I just want to get it over now. I wasted too much time on this.
if i remove background from the output file,
it still looks awkward.
You question is kind of misleading as You stated:-
I want to convert it to 8bit Bitmap and colour every invisible(alpha) pixels to purple(#FF00FF) and set them to dot zero. (very first palette)
But in the description you gave an input image having no alpha channel. Luckily, I have seen your previous question Convert PNG to 8 bit bitmap, therefore I obtained the image containing alpha (that you mentioned in the description) but didn't posted.
HERE IS THE IMAGE WITH ALPHA:-
Now we have to obtain .bmp equivalent of this image, in P mode.
from PIL import Image
image = Image.open(r"Image_loc")
new_img = Image.new("RGB", (image.size[0],image.size[1]), (255, 0, 255))
cmp_img = Image.composite(image, new_img, image).quantize(colors=256, method=2)
cmp_img.save("Destination_path.bmp")
OUTPUT IMAGE:-

PyGI Edit Image Color

Can any tell me how to change image color using PyGI (or PyGTK)?
I need method or property, like "ImageColour" in CEGUI, what changing non-alpha channels of image. For example:
I have one picture, its just white round. I need to use this round in a different places of interface, with different colors. And I won't to create another dublicates of this round, bcs, for example, I need 256 different colors.
And Pictures Example:
This is picture with white round, what I've got
This is picture with round, what color I want to see
Here is functions, what I'm using to change color:
image = gtk.Image()
image.set_from_file("images/button.png")
pix_buffer = image.get_pixbuf()
pix_buffer.fill(0xA32432FF)
image.set_from_pixbuf(pix_buffer)
Thats doesn't work correctly. Thats fill full image to quad of red color.
Another idea is modify_fg/modify_base, but here works only modify_bg what changing only background (and doesn't changing white color)
I've been playing with this for the last days, and it is not entirely easy to treat pixbuf as a immediate representation of the pixels. One of the reasons is that the GdkPixbuf software determines a 'rowstride' which causes 'jumps' in the addressing of the image.
Until I can investigate more, the simplest solution I've found is to convert the pixbuf to a PIL.Image, do the operations there, and convert back to pixbuf. These are the two function that do the conversion:
def pixbuf2image(self, pxb):
""" Convert GdkPixbuf.Pixbuf to PIL image """
data = pxb.get_pixels()
w = pxb.get_width()
h = pxb.get_height()
stride = pxb.get_rowstride()
mode = "RGB"
if pxb.get_has_alpha():
mode = "RGBA"
img = Image.frombytes(mode, (w, h), data, "raw", mode, stride)
return img
def image2pixbuf(self, img):
""" Convert PIL or Pillow image to GdkPixbuf.Pixbuf """
data = img.tobytes()
w, h = img.size
data = GLib.Bytes.new(data)
pxb = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
False, 8, w, h, w * 3)
return pxb
Luckily, new_from_bytes takes into account the rowstride automatically, and saves the continuous bytes in data in the correct manner in memory.
In PIL (Pillow for Python3) you can do many operations on the image, including pixel-by-pixel access. Do note that pixbuf is always using RGB(A) components, so you have to be careful with the conversions and operations!
In any case, the latter function shows how to convert a memory (bytes) array to a GdkPixbuf, if you want to construct the image directly.

Pillow handles PNG files incorrectly

I can successfully convert a rectangular image into a png with transparent rounded corners like this:
However, when I take this transparent cornered image and I want to use it in another image generated with Pillow, I end up with this:
The transparent corners become black. I've been playing around with this for a while but I can't find any way in which the transparent parts of an image don't turn black once I place them on another image with Pillow.
Here is the code I use:
mask = Image.open('Test mask.png').convert('L')
im = Image.open('boat.jpg')
im.resize(mask.size)
output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5))
output.putalpha(mask)
output.save('output.png')
im = Image.open('output.png')
image_bg = Image.new('RGBA', (1292,440), (255,255,255,100))
image_fg = im.resize((710, 400), Image.ANTIALIAS)
image_bg.paste(image_fg, (20, 20))
image_bg.save('output2.jpg')
Is there a solution for this? Thanks.
Per some suggestions I exported the 2nd image as a PNG, but then I ended up with an image with holes in it:
Obviously I want the second image to have a consistent white background without holes.
Here is what I actually want to end up with. The orange is only placed there to highlight the image itself. It's a rectangular image with white background, with a picture placed into it with rounded corners.
If you paste an image with transparent pixels onto another image, the transparent pixels are just copied as well. It looks like you only want to paste the non-transparent pixels. In that case, you need a mask for the paste function.
image_bg.paste(image_fg, (20, 20), mask=image_fg)
Note the third argument here. From the documentation:
If a mask is given, this method updates only the regions indicated by
the mask. You can use either "1", "L" or "RGBA" images (in the latter
case, the alpha band is used as mask). Where the mask is 255, the
given image is copied as is. Where the mask is 0, the current value
is preserved. Intermediate values will mix the two images together,
including their alpha channels if they have them.
What we did here is provide an RGBA image as mask, and use the alpha channel as mask.

Using Python Pillow lib to set Color depth

I am using the Python Pillow lib to change an image before sending it to device.
I need to change the image to make sure it meets the following requirements
Resolution (width x height) = 298 x 144
Grayscale
Color Depth (bits) = 4
Format = .png
I can do all of them with the exception of Color Depth to 4 bits.
Can anyone point me in the right direction on how to achieve this?
So far, I haven't been able to save 4-bit images with Pillow. You can use Pillow to reduce the number of gray levels in an image with:
import PIL.Image as Image
im = Image.open('test.png')
im1 = im.point(lambda x: int(x/17)*17)
Assuming test.png is a 8-bit graylevel image, i.e. it contains values in the range 0-255 (im.mode == 'L'), im1 now only contains 16 different values (0, 17, 34, ..., 255). This is what ufp.image.changeColorDepth does, too. However, you still have a 8-bit image. So instead of the above, you can do
im2 = im.point(lambda x: int(x/17))
and you end up with an image that only contains 16 different values (0, 1, 2, ..., 15). So these values would all fit in an uint4-type. However, if you save such an image with Pillow
im2.save('test.png')
the png will still have a color-depth of 8bit (and if you open the image, you see only really dark gray pixels). You can use PyPng to save a real 4-bit png:
import png
import numpy as np
png.fromarray(np.asarray(im2, np.uint8),'L;4').save('test4bit_pypng.png')
Unfortunately, PyPng seems to take much longer to save the images.
using changeColorDepth function in ufp.image module.
import ufp.image
import PIL
im = PIL.Image.open('test.png')
im = im.convert('L') # change to grayscale image
im.thumbnail((298, 144)) # resize to 294x144
ufp.image.changeColorDepth(im, 16) # change 4bits depth(this function change original PIL.Image object)
#if you will need better convert. using ufp.image.quantizeByImprovedGrayScale function. this function quantized image.
im.save('changed.png')
see example : image quantize by Improved Gray Scale. [Python]

Categories

Resources