17

I am having the following problem: I am saving 16-bit tiff images with a microscope and I need to analyze them. I want to do that with numpy and matplotlib, but when I want to do something as simple as plotting the image in green (I will later need to superpose other images), it fails.

Here is an example when I try to plot the image either as a RGB array, or with the default jet colormap.

import numpy as np
import matplotlib.pyplot as plt
import cv2

imageName = 'image.tif'

# image as luminance 
img1 = cv2.imread(imageName,-1)

# image as RGB array
shape = (img1.shape[0], img1.shape[1], 3)
img2 = np.zeros(shape,dtype='uint16')
img2[...,1] += img1

fig = plt.figure(figsize=(20,8))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)

im1 = ax1.imshow(img1,interpolation='none')
im2 = ax2.imshow(img2,interpolation='none')
fig.show()

Which to me yields the following figure: enter image description here

I am sorry if the question is too basic, but I have no idea why the right plot is showing this artifacts. I would like to get with the green scale, something like how the figure looks (imageJ also yields somthing similar to the left plot).

Thank you very much for your collaboration.

  • What you're seeing is probably the result of overflow of the `uint16` values in `img2`. What does `print img1.dtype` yield? What about `print img1.min(), img1.max()`? – Joe Kington Jul 14 '14 at 16:05
  • Is it just me or are the two images structurally the same but it looks like they have only a different colormap? Is that the problem? – Brian Cain Jul 14 '14 at 16:51

2 Answers2

40

I find the right plot much more artistic...

matplotlib is rather complicated when it comes to interpreting images. It goes roughly as follows:

  • if the image is a NxM array of any type, it is interpreted through the colormap (autoscale, if not indicated otherwise). (In principle, if the array is a float array scaled to 0..1, it should be interpreted as a grayscale image. This is what the documentation says, but in practice this does not happen.)

  • if the image is a NxMx3 float array, the RGB components are interpreted as RGB components between 0..1. If the values are outside of this range, they are taken with positive modulo 1, i.e. 1.2 -> 0.2, -1.7 -> 0.3, etc.

  • if the image is a NxMx3 uint8 array, it is interpreted as a standard image (0..255 components)

  • if the image is NxMx4, the interpretation is as above, but the fourth component is the opacity (alpha)

So, if you give matplotlib a NxMx3 array of integers other than uint8 or float, the results are not defined. However, by looking at the source code, the odd behavour can be understood:

if A.dtype != np.uint8:
    A = (255*A).astype(np.uint8)

where A is the image array. So, if you give it uint16 values 0, 1, 2, 3, 4..., you get 0, 255, 254, 253, ... Yes, it will look very odd. (IMHO, the interpretation could be a bit more intuitive, but this is how it is done.)


In this case the easiest solution is to divide the array by 65535., and then the image should be as expected. Also, if your original image is truly linear, then you'll need to make the reverse gamma correction:

img1_corr = (img1 / 65535.)**(1/2.2)

Otherwise your middle tones will be too dark.

DrV
  • 22,637
  • 7
  • 60
  • 72
  • Excellent answer. I always find these integer types (uint8, byte, etc) to be very limiting for any kind of image-processing, although they make sense when you have to save the file. With matplotlib, the sanest approach IMO is to normalize every integer-typed image to the range 0-1 as soon as you get it, and only then perform math operations (for example, correcting gamma like this: `im **= gamma` [can you believe this works?!]). Then plot with desired colormap, and de-normalize upon saving. – heltonbiker May 16 '15 at 18:08
0

I approached this by normalising the image by the maximum value of the given datatype, which said by DrV, for uint16 is 65535. The helper function would look something like:

def normalise_bits(img):
    bits = 1.0  # catch all
    try:
        # Test integer value, e.g. np.uint16
        bits = np.iinfo(img.dtype).max
    except ValueError:
        # Try float maximum, e.g. np.float32
        bits = np.finfo(img.dtype).max
    return (img / bits).astype(float)

Then the image can be handled by matplotlib as a float [0.0, 1.0]

RobT
  • 1