Matplotlib - how to rescale pixel intensities for RGB image - python

I am confused regarding how matplotlib handles fp32 pixel intensities. To my understanding, it rescales the values between max and min values of the image. However, when I try to view images originally in [0,1] by rescaling their pixel intensites to [-1,1] (by im*2-1) using imshow(), the image appears differently colored. How do I rescale so that images don't differ ?
EDIT : Please look at the image -
PS: I need to do this as part of a program that outputs those values in [-1,1]
Following is the code used for this:
img = np.float32(misc.face(gray=False))
fig,ax = plt.subplots(1,2)
img = img/255 # Convert to 0,1 range
print (np.max(img), np.min(img))
img0 = ax[0].imshow(img)
plt.colorbar(img0,ax=ax[0])
print (np.max(2*img-1), np.min(2*img-1))
img1 = ax[1].imshow(2*img-1) # Convert to -1,1 range
plt.colorbar(img1,ax=ax[1])
plt.show()
The max,min output is :
(1.0, 0.0)
(1.0, -1.0)

You are probably using matplotlib wrong here.
The normalization-step should work correctly, if it's active. The docs tell us, that is only active by default, if the input-image is of type float!
Code
import numpy as np
import matplotlib.pyplot as plt
from scipy import misc
fig, ax = plt.subplots(2,2)
# This usage shows different colors because there is no normalization
# FIRST ROW
f = misc.face(gray=True)
print(f.dtype)
g = f*2 # just some operation to show the difference between usages
ax[0,0].imshow(f)
ax[0,1].imshow(g)
# This usage makes sure that the input-image is of type float
# -> automatic normalization is used!
# SECOND ROW
f = np.asarray(misc.face(gray=True), dtype=float) # TYPE!
print(f.dtype)
g = f*2 # just some operation to show the difference between usages
ax[1,0].imshow(f)
ax[1,1].imshow(g)
plt.show()
Output
uint8
float64
Analysis
The first row shows the wrong usage, because the input is of type int and therefore no normalization will be used.
The second row shows the correct usage!
EDIT:
sascha has correctly pointed out in the comments that rescaling is not applied for RGB images and inputs must be ensured to be in [0,1] range.

Related

Convert RGBA image to array in specific range in python

I have an array of values in range of 1500 to 4500.
I managed to convert the data using matplotlib function. The code as follows:
import matplotlib.pyplot as plt
import numpy as np
norm = plt.Normalize(vmin=1500, vmax=4500)
jet = plt.cm.jet
# generate 100x100 with value in range 1500-4500
original = np.random.randInt(1500,4500, (100,100))
# array in shape (100,100)
# convert the array to rgba image
converted = jet(norm(original))
# image in shape (100,100,4)
How to get the original array from converted images?
Some rounding will take place because of the limited amount of colors in the colormap, so a perfect reversal is not possible.
But you can get close by simply inverting the colormap and subsequent normalization.
Starting with some sample data:
import matplotlib as mpl
import numpy as np
rng = np.random.default_rng(seed=0)
data = rng.integers(1500,4500, (3,3))
# array([[4051, 3410, 3033],
# [2309, 2423, 1622],
# [1725, 1549, 2025]], dtype=int64)
Which can be converted to RGBA:
norm = mpl.colors.Normalize(vmin=1500, vmax=4500)
cmap = mpl.colormaps["jet"].copy()
data_rgb = cmap(norm(data))
Converting the colormap to a lookup table, I'll drop the alpha for simplicity since this colormap doesn't use it.
lut = np.zeros((256,) * 3, dtype=np.uint8)
for i in range(cmap.N):
r,g,b,a = cmap(i)
lut[int(r*255), int(g*255), int(b*255)] = i
The lookup table can then be indexed with the RGB expressed as bytes:
data_rgb_byte = (data_rgb*255).astype(np.uint16)
data_inv_norm = lut[
data_rgb_byte[:,:,0],
data_rgb_byte[:,:,1],
data_rgb_byte[:,:,2],
]/255
data_recovered = norm.inverse(data_inv_norm).data
data_recovered
# array([[4052.94117647, 3405.88235294, 3029.41176471],
# [2311.76470588, 2417.64705882, 1617.64705882],
# [1723.52941176, 1547.05882353, 2017.64705882]])
I guess the loss in accuracy relates to the range of initial normalization (4500 - 1500 = 3000) compared to the resolution of the colormap (N=256), so 3000/256 ~= 11.7.

matplotlib create figure without frames, axes, plot a 2D array with a colormap, save plot to numpy array of same size as input

I wrote a function with this purpose:
to create a matplotlib figure, but not display it
with no frames, axes, etc.
to plot in the figure an input 2D array using a user-passed colormap
to save the colormapped 2D array from the canvas to a numpy array
that the output array should be the same size as the input
There are lots of questions with answers for tasks similar to either points 1-2 or point 4; for me it was also important to automate point 5. So I started by combining parts from both #joe-kington 's answer and from #matehat 's answer and comments to it, and with small modifications I got to this:
def mk_cmapped_data(data, mpl_cmap_name):
# This is to define figure & ouptput dimensions from input
r, c = data.shape
dpi = 72
w = round(c/dpi, 2)
h = round(r/dpi, 2)
# This part modified from #matehat's SO answer:
# https://stackoverflow.com/a/8218887/1034648
fig = plt.figure(frameon=False)
fig.set_size_inches((w, h))
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
plt.set_cmap(mpl_cmap_name)
ax.imshow(data, aspect='auto', cmap = mpl_cmap_name, interpolation = 'none')
fig.canvas.draw()
# This part is to save the canvas to numpy array
# Adapted rom Joe Kington's SO answer:
# https://stackoverflow.com/a/7821917/1034648
mat = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
mat = mat.reshape(fig.canvas.get_width_height()[::-1] + (3,))
mat = normalise(mat) # this is just using a helper function to normalize output range
plt.close(fig=None)
return mat
The function does what it is supposed to do and is fast enough.
My question is whether I can make it more efficient and or more pythonic in any way.
If you're wanting RGB output that exactly matches the shape of the input array, it's probably easiest to not create a figure, and instead use the colormap objects directly. For example:
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# Random data with a non 0-1 range.
data = 500 * np.random.random((100, 100)) - 200
# We'll use `LinearSegementedColormap` and `Normalize` instances directly
cmap = plt.get_cmap('viridis')
norm = plt.Normalize(data.min(), data.max())
# The norm instance scales data to a 0-1 range, cmap makes it RGB
rgb = cmap(norm(data))
# MPL uses a 0-1 float RGB representation, so we'll scale to 0-255
rgb = (255 * rgb).astype(np.uint8)
Image.fromarray(rgb).save('test.png')
Note that you likely don't want the additional step of saving it as a PNG, but I wanted to be able to show the result visually. This is exactly a 100x100 image where each pixel corresponds to the original input data.
This is what matplotlib does behind-the-scenes when you call imshow. The data is first run through a Normalize instance to scale it from its original range to 0-1. Then any Colormap instance can be called directly with the 0-1 results to turn the scalar data into RGB data.
One letter variables are hard to understand.
Change:
r -> n_rows
c -> n_cols
w -> width
h -> height

Visualize multiple 2d Array with same color scheme

I am currently trying to visualize three 2D arrays with the same color. The arrays are 13x13 and contain integers. In an external file I have a color code in hex for each integer.
When I now try to visualize the arrays, two out of three arrays look good. All numbers match the color codes and display the arrays correctly. But in the last picture a part of the data is not assigned correctly.
.
color_names = [c.strip() for c in open(colors).readlines()]
color_dict = {v: k for v, k in enumerate(color_names)}
unique_classes = (np.unique(np.asarray(feature_map))).tolist()
number_classes = len(unique_classes)
color_code = [color_dict.get(cla) for cla in unique_classes]
cmap = plt.colors.ListedColormap(color_code)
norm = plt.colors.BoundaryNorm(unique_classes, cmap.N)
img = pyplot.imshow(feature_map[0],interpolation='nearest',
cmap = cmap,norm=norm)
pyplot.colorbar(img,cmap=cmap,
norm=norm,boundaries=unique_classes)
pyplot.show()
img1 = pyplot.imshow(feature_map[1],interpolation='nearest',
cmap = cmap,norm=norm)
pyplot.show()
img2 = pyplot.imshow(feature_map[2],interpolation='nearest',
cmap = cmap,norm=norm)
pyplot.colorbar(img2,cmap=cmap,
norm=norm,boundaries=unique_classes)
pyplot.show()
Exactly the same data as on the picture:
feature_map = [[[25,25,25,25,56,56,2,2,2,2,2,2,25],[25,25,25,25,25,25,59,7,72,72,72,72,2],[25,25,25,25,25,25,59,72,72,72,72,72,2],[25,25,25,24,24,24,62,0,0,0,0,25,25],[25,25,24,24,24,24,24,24,24,24,25,25,25],[26,26,24,24,24,24,24,26,26,26,6,6,6],[26,26,26,24,24,26,26,26,26,26,26,6,6],[26,26,26,0,0,26,26,26,26,26,26,6,6],[28,28,28,28,28,28,28,26,26,26,26,6,6],[28,28,28,28,28,28,28,26,26,26,13,13,6],[28,28,28,28,28,28,28,26,13,13,13,13,13],[28,28,28,28,28,28,28,13,13,13,13,13,13],[28,28,28,28,28,28,28,13,13,13,13,13,13]],[[25,25,25,25,59,56,59,2,0,0,0,0,0],[25,25,25,25,25,59,59,7,72,72,72,72,72],[25,25,25,25,25,25,59,72,72,72,72,72,72],[25,25,25,0,0,25,25,6,0,0,0,72,0],[25,25,0,0,0,0,6,0,0,0,0,25,6],[26,26,26,0,0,0,24,26,0,0,6,6,6],[26,26,26,0,0,0,26,26,26,26,26,6,6],[0,26,0,0,0,0,26,26,0,26,26,6,6],[0,28,28,28,28,28,28,26,0,26,26,6,6],[28,28,28,28,28,28,28,26,0,26,0,0,0],[28,28,28,28,28,28,28,26,13,13,13,13,0],[56,56,28,28,28,28,28,13,13,13,13,13,13]],[[0,28,28,28,28,28,28,13,13,13,13,13,0],[25,25,25,25,59,59,59,4,0,0,0,0,0],[25,25,25,25,59,59,59,7,7,7,72,72,6],[25,25,25,25,25,25,59,7,7,73,73,25,0],[25,25,25,0,0,25,6,7,0,6,6,6,0],[25,0,0,0,6,6,6,6,0,0,6,6,6],[0,0,0,0,0,6,6,6,0,0,6,6,6],[0,0,0,0,0,0,6,6,0,0,6,6,6],[0,0,0,0,0,0,6,0,0,0,6,6,6],[0,0,28,0,28,28,13,0,0,0,6,6,6],[28,28,28,28,28,28,13,13,13,0,13,6,6],[28,28,28,28,28,28,28,13,13,13,13,13,13],[56,28,28,28,28,28,28,13,13,13,13,13,13],[28,28,28,28,28,28,28,13,13,13,13,13,13]]]
The color code file is simply a file where each line contains a single hex code such as: #deb887
I have been working on this problem for several hours and can't reproduce the problem at the moment
I have tried to reproduce your results and something got my attention.
If you look closely to the feature_map[2] values you might see that the pixel you claim miss classified has actually a different value than the pixels around it. So it actually has the correct color for its value. So I think it is not because of a misclassification it is beacause of your data. That would be my answer IF what you mean by "part of the data" is the pixel at position (0,11) otherwise i have gotten it all wrong and sorry about this answer.
NOTE: About colors, I just picked some random colors. Don't worry if they don't match.

Unformatted histogram values

I am trying to find the histogram values of an image by using my own function but when i run my code it prints the histogram values like [1.000e+00 4.000e+00 1.000e+00 8.000e+00 8.000e+00 2.500e+01 2.100e+01
4.500e+01 5.500e+01 8.800e+01 1.110e+02 1.220e+02 1.280e+02 1.370e+02
Is it normal or is there any other method that i can display histogram values in an understandable way? Here is my function;
import numpy as np
import cv2
def histogram(img):
height = img.shape[0]
width = img.shape[1]
hist = np.zeros((256))
for i in np.arange(height):
for j in np.arange(width):
a = img.item(i,j)
hist[a] += 1
print(hist)
img = cv2.imread('rose.jpg', cv2.IMREAD_GRAYSCALE)
histogram(img)
Where you initialize your histogram, set its type to np.uint32 or similar since you can only ever have a whole, non-negative number of pixels of a given colour:
hist = np.zeros(256, dtype=np.uint32)
Check the type of your current array and find it is float64 with:
print(hist.dtype)
Hint: See also here.
You can set suppress to True using np.set_printoptions see https://docs.scipy.org/doc/numpy/reference/generated/numpy.set_printoptions.html
Alternatively you can print like this:
with np.printoptions(suppress=True):
print(hist)

How Do I Change the Axis SimpleITK::ImageSeriesWriter Using?

The SimpleITK::ImageSeriesWriter default to slice given 3D volume along Z-axis and write slices of 2D images in XY view.
How do I change the axis so that the output is in XZ or YZ view?
In another word, if the default Z axis slices are in Axial view, how do I get the slices of Coronal and Sagittal view?
I tried the GitHub:FNNDSC/med2image's output xyz function.
But the images array are blindly written, so sometimes the X and Y are transposed, or one of the axis are reversed(flipped).
So I feel the need to write my own code to have full control.
def slice(dcm_folder, output_stem):
print('Reading Dicom directory:', path.abspath(dcm_folder))
reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames(dcm_folder)
reader.SetFileNames(dicom_names)
image = reader.Execute()
# cast the bit depth to PNG compatible "unsigned char"
image = sitk.Cast(sitk.RescaleIntensity(image), sitk.sitkUInt8)
size = image.GetSize()
print( "Image size:", size[0], size[1], size[2] )
# need Z filenames to write
series_filenames = list([output_stem + '-slice' + str(i).zfill(3) + '.png' for i in range(size[2])])
print('Writing {} image slices'.format(size[2]))
writer = sitk.ImageSeriesWriter()
writer.SetFileNames( series_filenames )
writer.Execute(image)
The code above will write out slices of Z axis successfully.
How do I modify the code so that I can get the slices of another 2 views?
You should be able to use the PermuteAxesImageFilter to swap the axes of your volume. Here's the documentation for that filter:
https://itk.org/SimpleITKDoxygen/html/classitk_1_1simple_1_1PermuteAxesImageFilter.html
Or if you prefer a procedural interface (as I do), you can use the PermuteAxes function.
Well, I think you've fixed your issue. But what I've done is just importing a .mha file (or another extension supported by simple ITK) and converting it to a 3D array. Then what you need to do is just slice this array in different axis at a time. Take a look (python code):
import SimpleITK as sitk #importing package
path = '/current/folder/mha/file'
ct = sitk.ReadImage(path) #var_type is SimpleITK.Image
ndarray = sitk.GetArrayFromImage(ct) #converting from SimpleITK.Image to numpy ndarray
# Axial view:
plt.imshow(ndarray[100,:,:], cmap='gray') # plotting 100º image from axial view
#Coronal view:
plt.imshow(ndarray[:,100,:], cmap='gray') # plotting 100º image from coronal view
#Sagittal view:
plt.imshow(ndarray[:,:,100], cmap='gray') # plotting 100º image from sagittal view

Categories

Resources