7

Here is the code I am currently using:

from PIL import Image
import numpy as np

def save_np_img(np_img, path, name):
    """
    To save the image.
    :param np_img: numpy_array type image
    :param path: string type of the existing path where to save the image
    :param name: string type that includes the format (ex:"bob.png")
    :return: numpy array
    """

    assert isinstance(path, str), 'Path of wrong type! (Must be String)'
    assert isinstance(name, str), 'Name of wrong type! (Must be String)'

    im = Image.fromarray(np_img)
    im.save(path + name)

    return np_img

I would like to be able to save images which contain float values, just like I can currently save int valued-images.

I get a TypeError message when I try to save my images which have been transformed through np.divide(img, 255.), and thus when trying to save numpy_arrays which contain floats.

(You can suggest other librairies, too.)

NOTE: The values, when not integers between 0 and 255, are floats between 0 and 1. It is crucial for me to remain as lossless as possible. My initial thought was that I could simply use np.multiply(img, 255) but I wasn't sure if that would lose some precision (nor if it would return integers, actually).

EDIT: Basically, is this method a lossless convertion? That is, if I had a numpy_array of ints, divided it by 255., and then reconverted into ints, am I losing information? If so, how to avoid that?

payne
  • 4,691
  • 8
  • 37
  • 85
  • What's the problem with your code? – Mad Physicist Sep 25 '18 at 04:40
  • It won't let me save images when the values of the numpy_arrays are floats. – payne Sep 25 '18 at 04:42
  • 2
    See this [answer](https://stackoverflow.com/a/30946248/3954379), maybe is what you need – Lucas Sep 25 '18 at 04:55
  • What format are you trying to use? The PNG format can only store integers. – Warren Weckesser Sep 25 '18 at 04:56
  • Which format should I use? I thought I should avoid `JPG` and so that `PNG` would be a good pick. I didn't know it only supported integers. I need to preserve the 3 dimensions of colors (RGB, which I eventually plan on changing to YCbCr). – payne Sep 25 '18 at 04:58
  • 1
    PNG supports up to 16 bits per channel. Scaling up floating point values from the range [0, 1] to [0, 2**16-1] gives a floating point resolution of about 1.5e-5. You'll lose information in the conversion to integers, but only you can decide if the loss is too much. Alternatively, TIFF supports floating point; take a look at the [`tifffile`](https://pypi.org/project/tifffile/) package. – Warren Weckesser Sep 25 '18 at 05:04
  • You start with 8 bits then go to 32 bits then going back to 8 or 16 bits. So there really isn't any guarantee that you don't lose information. But, you can check `all(int((i/255.0)*255)==i for i in range(256))` – matt Sep 25 '18 at 12:00
  • @MarkSetchell With my university courses, I haven't had much time to work on the project much. I think the TIFF solution seems decent, though I'm unsure if anyone can just visualize a TIFF image without installing extra software. The numpy solution probably will not work (for the same reason I just mentioned for TIFF). Sorry for my lack of feedback, I'm very occupied out here. I will try out the TIFF method eventually. My model some times pops out negative numbers as values for the image: is there a way to clip values when saving to TIFF ? – payne Oct 02 '18 at 20:46
  • What packages/libraries besides tifffile support saving floating point images? – user3731622 Oct 11 '18 at 21:32

1 Answers1

11

You can save and read back numpy arrays of floats losslessly and natively without any libraries being required:

import numpy as np

# Create 10 random floats in range 0..1 in array "b"
b = np.random.random_sample((10,)).astype(np.float32)

# Save to file
np.save('BunchOfFloats.npy',b)

# Read back into different array "r"
r = np.load('BunchOfFloats.npy')

# Inspect b 
array([0.26565347, 0.7193414 , 0.19435954, 0.58980538, 0.28096624,
   0.88655137, 0.84847042, 0.80156026, 0.94315194, 0.76888901])

# Inspect r
array([0.26565347, 0.7193414 , 0.19435954, 0.58980538, 0.28096624,
   0.88655137, 0.84847042, 0.80156026, 0.94315194, 0.76888901])

Documentation is available here.


Alternatively, as suggested in the comments by @WarrenWekesser you could use a TIFF file which can store floats, and even doubles.

import numpy as np
from tifffile import imsave

# Generate float data
b=np.random.random_sample((768,1024,3)).astype(np.float32)

# Save as TIF - when reading, use "data = imread('file.tif')"
imsave('result.tif',b)

Yet another option is a PFM file, described here and here.

This is a very simple format that you could write and read yourself, and has the benefit that other packages such as ImageMagick and GIMP understand it, so you could save your data as a PFM file and then convert at the command line with ImageMagick to JPEG or PNG for viewing:

magick image.pfm -auto-level result.png
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • I forgot to mention: as part of the requirements, people should generally be able to visualize the image saved on their computer, and as an enormous bonus `matplotlib` should be able to display the saved image. The `np.save` avenue is quite interesting, but can it be visualize directly? – payne Sep 25 '18 at 11:08
  • In that case, you are probably better going with a floating point TIFF - I have added some demo code. **ImageMagick** can still convert and display such TIFFs. – Mark Setchell Sep 25 '18 at 11:22
  • And should my decision change in case the loaded dataset is already in PNG ? – payne Sep 25 '18 at 11:27
  • It depends on your needs and design. You could check the type of your input array inside your function and switch formats accordingly, by looking at `np_img.dtype` – Mark Setchell Sep 25 '18 at 11:32
  • Just a small point, but -normalize will clip a the high and low end of the histogram, which is not good if you are trying to visualize your data unchanged. If you want to view it, I would suggest using -auto-level to do the stretch from floats to visual range of integers. – fmw42 Sep 25 '18 at 16:17
  • @fmw42 Fred, yes, that's a better idea and I have edited it in. I was just trying to avoid the inevitable *"my image is black"* issue. Thank you. – Mark Setchell Sep 25 '18 at 16:38
  • @MarkSetchell I've had a little bit of time to work on this. As suggested ins ome other comments, I tried this and it seemed to work fine: `plt.imsave('my_image.png', image_to_save, cmap=plt.cm.gray)`. Is there anything wrong with it in regards to my requirements ? – payne Oct 12 '18 at 04:20