153

I have just done some image processing using the Python image library (PIL) using a post I found earlier to perform fourier transforms of images and I can't get the save function to work. The whole code works fine but it just wont save the resulting image:

from PIL import Image
import numpy as np

i = Image.open("C:/Users/User/Desktop/mesh.bmp")
i = i.convert("L")
a = np.asarray(i)
b = np.abs(np.fft.rfft2(a))
j = Image.fromarray(b)
j.save("C:/Users/User/Desktop/mesh_trans",".bmp")

The error I get is the following:

save_handler = SAVE[string.upper(format)] # unknown format
    KeyError: '.BMP'

How can I save an image with Pythons PIL?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
user1999274
  • 1,557
  • 2
  • 10
  • 6

5 Answers5

182

The error regarding the file extension has been handled, you either use BMP (without the dot) or pass the output name with the extension already. Now to handle the error you need to properly modify your data in the frequency domain to be saved as an integer image, PIL is telling you that it doesn't accept float data to save as BMP.

Here is a suggestion (with other minor modifications, like using fftshift and numpy.array instead of numpy.asarray) for doing the conversion for proper visualization:

import sys
import numpy
from PIL import Image

img = Image.open(sys.argv[1]).convert('L')

im = numpy.array(img)
fft_mag = numpy.abs(numpy.fft.fftshift(numpy.fft.fft2(im)))

visual = numpy.log(fft_mag)
visual = (visual - visual.min()) / (visual.max() - visual.min())

result = Image.fromarray((visual * 255).astype(numpy.uint8))
result.save('out.bmp')
mmgp
  • 18,901
  • 3
  • 53
  • 80
  • @user1999274 the major difference is in the use of `fft2` instead of `rfft2` since for discrete data I don't see the point in using the later. The rest is purely cosmetic, i.e., you wouldn't be able to properly visualize the Fourier transform without applying a log factor to it. Then the normalization is to keep things simple for the conversion to the range [0, 255]. – mmgp Jan 23 '13 at 04:53
  • 1
    `visual = (visual - visual.min()) / (visual.max() - visual.min())` will default if `visual` has no variance (unlikely, but worth catching) –  Dec 10 '13 at 20:53
  • 1
    I tried to use this code for my problem, but I get plain black images. Does anyone have any idea about this? http://stackoverflow.com/questions/24266000/using-python-to-save-a-jpg-image-that-was-edited-in-the-script/24266283#24266283 – user961627 Jun 17 '14 at 15:08
41

You should be able to simply let PIL get the filetype from extension, i.e. use:

j.save("C:/Users/User/Desktop/mesh_trans.bmp")
wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    Thanks for the input. however when i try to let the save function get the filetype like you suggested above i get the following error: IOError: cannot write mode F as BMP. any advice? – user1999274 Jan 23 '13 at 03:10
  • 2
    Yes, you should convert the array to `numpy.uint8` dtype rather than float. – wim Jan 23 '13 at 03:53
6

Try removing the . before the .bmp (it isn't matching BMP as expected). As you can see from the error, the save_handler is upper-casing the format you provided and then looking for a match in SAVE. However the corresponding key in that object is BMP (instead of .BMP).

I don't know a great deal about PIL, but from some quick searching around it seems that it is a problem with the mode of the image. Changing the definition of j to:

j = Image.fromarray(b, mode='RGB')

Seemed to work for me (however note that I have very little knowledge of PIL, so I would suggest using @mmgp's solution as s/he clearly knows what they are doing :) ). For the types of mode, I used this page - hopefully one of the choices there will work for you.

RocketDonkey
  • 36,383
  • 7
  • 80
  • 84
  • I tried removing the .bmp in favour of the bmp and the it still didnt work. the error i now get is: IOError: cannot write mode F as BMP. i checked the PIL pages for documentation and it said to use the .bmp so overall i still have no idea why it isnt working. – user1999274 Jan 23 '13 at 03:13
  • @user1999274 Posted an update that seemed to work for me (apologies for the lack of detail - piecing it together as I go along :) ). – RocketDonkey Jan 23 '13 at 03:46
  • @RocketDonkey it has be a little smarter than that, because converting the results of a Fourier transform to a RGB colorspace will give a meaningless image. Just because it no longer raises an exception, it doesn't mean the result is correct. – mmgp Jan 23 '13 at 03:50
  • @mmgp Agreed - that's why I +1'd yours and hope the OP accepts it :) – RocketDonkey Jan 23 '13 at 03:51
4

I know that this is old, but I've found that (while using Pillow) opening the file by using open(fp, 'w') and then saving the file will work. E.g:

with open(fp, 'w') as f:
    result.save(f)

fp being the file path, of course.

Code Enjoyer
  • 681
  • 4
  • 18
  • 1
    Pillow's [documentation](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save) says that the `format` parameter should be used if a file object is used in `save`. Is this not necessary? – Rob Rose Sep 06 '18 at 22:07
  • @RobRose during my testing, when I posted the answer, I did not find anything like that to be necessary. However, it may be the case now. If any testing you do proves it necessary let me know and I'll edit my answer – Code Enjoyer Sep 06 '18 at 22:11
  • 1
    The mode should be `'wb'` to store bytes – Ivan De Paz Centeno Mar 20 '21 at 23:52
0

Here is what I did to import and then export an bmp image with PIL.

def read_img(path):
   """
   Read image and store it as an array, given the image path. 
   Returns the 3 dimensional image array.
   """
   img = Image.open(path)
   img_arr = np.array(img, dtype='int32')
   img.close()
   return img_arr


def write_image(arr, filename):
   """
   write the image
   input : 3 dimensional array
   """
   path = "output/" + filename + ".bmp"
   arr = arr.astype(dtype='uint8')
   img = Image.fromarray(arr, 'RGB')
   img.save(path)
Aaron C
  • 301
  • 1
  • 8