How to combine 3 TIFF images into 1 PNG image with python? - python

I have 1 tif image for each RGB colour channel, and I would like to combine the 3 images to make a single RGB image with all 3 channels in png format using python. I have tried several experiments using the PIL library but I can't get it.
I uploaded 3 sample images to Google Drive here. Does anyone know how to do this?

The answer depends on what you are really trying to achieve...
If you want an accurate merge of the 3 channels, you should probably use the tifffile module to understand the floating point values in your input files and accurately represent them in your output files. In fact, gdal would probably be even better as it understands the GeoTIFF tags in your file. PIL is unable to handle RGB float32 images.
If you want something that vaguely allows some sort of approximate visualisation as a PNG, you will need to do some work to scale your values to something sensible (but not accurate) because PNG cannot represent float data like your images contain.
Here is a more accurate merge of your channels with tifffile:
from tifffile import imread, imwrite
import numpy as np
r = imread('r.tif')
g = imread('g.tif')
b = imread('b.tif')
RGB = np.dstack((r,g,b))
imwrite('result.tif', RGB)
With PIL you would use Image.merge() but your data is float, so you will need to convert it to uint8/uint16 first to get something you can store in a PNG:
from PIL import Image
import numpy as np
# Open images
red = Image.open('red_channel.tif')
green = Image.open('green_channel.tif')
blue = Image.open('blue_channel.tif')
# Convert PIL Images to Numpy arrays
npRed = np.array(red)
npGreen = np.array(green)
npBlue = np.array(blue)
# Get rid of the pesky -3.4e+38 marker for out-of-bounds pixels
npRed[npRed < 0] = 0
npBlue[npBlue < 0] = 0
npGreen[npGreen < 0] = 0
# Find maximum across all channels for scaling
max = np.max([npRed,npGreen,npBlue])
# Scale all channels equally to range 0..255 to fit in a PNG (could use 65,535 and np.uint16 instead)
R = (npRed * 255/max).astype(np.uint8)
G = (npGreen * 255/max).astype(np.uint8)
B = (npBlue * 255/max).astype(np.uint8)
# Build a PNG
RGB = np.dstack((R,G,B))
Image.fromarray(RGB).save('result.png')

Related

How to get the FFT image from a raw TEM tiff?

I'm trying to write code that takes TEM (Transmission Electron Microscope) TITFF images, and computes the FFT. But I always get plain Red, Green or Blue images.
Here's what the RAW TEM images look like :
Here's what the FFT image should look like :
But instead I get :
Here's my code :
import numpy as np
import diplib as dip
import matplotlib.pyplot as plt
from PIL import Image
from ncempy.io import dm
img1 = dip.ImageReadTIFF('RAW_FFT.tif')
f = np.fft.fft2(img1)
f = np.fft.fftshift(f)
plt.imshow(abs(f))
plt.show()
Do you have any idea what could be the problem? I even tried to convert the image to np.array and do FFT step by step but I get the same result.
FFT is complex and without a logarithm, Fourier transform would be so much brighter than all the other points that everything else will appear black.
see for details: https://homepages.inf.ed.ac.uk/rbf/HIPR2/fourier.htm
import cv2
import numpy as np
img=cv2.imread('inputfolder/yourimage.jpg',0)
def fft_image_inv(image):
f = np.fft.fft2(image)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 15*np.log(np.abs(fshift))
return magnitude_spectrum
fft= fft_image_inv(img)
cv2.imwrite('outputfolder/yourimage.jpg',fft)
output:
There are multiple issues here. First, sometimes grayscale images are written to file as if they were RGB images (in a TIFF file, this could be as simple as storing a grayscale color map, the pixel values will be interpreted as indices into the map, and the loaded image will be an RGB image instead of a grayscale image, even through it has only grayscale colors).
This is the case here. All three channels have exactly the same information, but there are three channels stored, and your FFT will compute the same thing three times!
After loading the image with dip.ImageReadTIFF(), you can use parentheses to index one of the channels:
img1 = dip.ImageReadTIFF('RAW_FFT.tif')
img1 = img1(0)
We now have an actual gray-scale image. This should get rid of the red color in the output.
After computing the FFT, we have a floating-point image with a very high dynamic range (the largest magnitude, at the middle pixel, is 437536704). pyplot, by default, will show floating-point images with 0 and all negative values as black, and 1 and all larger values as white (actual colors depend of course on the color map it uses). So your display will be all white. Use the vmax parameter to imshow to determine the value shown as white. Setting this to 1e6 should give you a similar display as in the GMS software.
Instead of pyplot you can use DIPlib for display. Its interactive viewer will let you use a slider to manually set the grayscale limits, and you can manually select to display the magnitude, as well as choose a logarithmic mapping (which tend to be most useful for displaying the frequency domain).
f = dip.FourierTransform(img)
dip.viewer.ShowModal(f)
Alternatively, you can use a static display, which uses pyplot under the hood:
f.Show((0, 1e6))
or
f.Show('log')

convert image saved in hexadecimal in a np.array to import it in opencv

I get an image stored as an object from a camera that look like this (here reduced to make it understandable):
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
is it possible to 'import' it as an 'image' in opencv?
I tried to look at the documentation of cv2.imdecode but could get it to work.
I could preprocess this array to get it to another format but I am not sure what could 'fit' to opencv.
Thank you for your help
This is a very succinct and pythonic (using NumPy) way to implement a conversion from your hexadecimal values matrix to an RGB matrix that could be read by OpenCV.
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
def to_rgb(v):
return np.array([np.int(v[1:3],16), np.int(v[3:5],16) , np.int(v[5:7],16)])
image_cv = np.array([to_rgb(h) for h in image.flatten()]).reshape(3, 4, 3)
cv2.imwrite('result.png', image_cv)
OpenCV requires either a RGB or a BGR input, which is to say you need to give the values of Red Green Blue or Blue Green Red on a scale from 0-255 (8 bit). I have shared with you the code to convert your array to an image.
Initially, I count the number of rows to find the height in terms of pixels. Then I count the number of items in a row to find the width.
Then I create an empty array of the given dimensions using np.zeros.
I then go to each cell and convert the hex code to its RGB equivalent, using the following formula #RRGGBB, R = int(RR,16), G = int(GG, 16), B = int(BB, 16). This converts the hexadecimal string to int.
#!/usr/bin/env python3
import numpy as np
import re
import cv2
# Your image
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
# Enter the image height and width
height = int(len(image[0]))
width = int(len(image[0][0]))
# Create numpy array of BGR triplets
im = np.zeros((height,width,3), dtype=np.uint8)
for row in range (height):
for col in range(width):
hex = image[row, col][1:]
R = int(hex[0:2],16)
G = int(hex[2:4],16)
B = int(hex[4:6],16)
im[row,col] = (B,G,R)
# Save to disk
cv2.imwrite('result.png', im)

Convert 16-bit Tiff image to 8-bit RGB

I'm dealing with some satellite images, consisting of 16-bit .tiff images. The color is encoded as 16-bit per channel. I would like to know how I can convert these images to normal 8-bit RGB for further CNN processing.
I have tried OpenCV (cv2.read('file',-1)) and PIL (read('file')), but these two packages cannot recognize and read 16-bit tiff images.
Generally, when you want to read or write images in Python — of any bit-depth and format — it is best to use ImageIO. As the name suggests, its singular goal is to input/output images. Only caveat: It may ignore the image's meta data. That is: It may not deal correctly with images defining a color space other than the standard sRGB, or it might fail to preserve the image's intended orientation.
You would read in the image, say example.tif, like so:
import imageio
image = imageio.imread('example.tif')
As for the conversion, that's just basic math. The data structure in which you'll receive the pixel data is a NumPy array. Introspect image.shape and image.dtype. You should expect your images to have a shape of (y, x, 3), where y is the number of pixels in the vertical, x in the horizontal direction, and 3 represents the three color channels: red, green, blue. Its dtype (data type) should be uint16, meaning unsigned 16-bit integers.
Side note: As there are three color channels, each sampled with a 16-bit resolution, the color depth of the image is more commonly described as "48 bits" (per pixel).
16-bit integer numbers range between 0 and 65535 (= 216−1). They need to be coerced to the 8-bit range: 0 to 255 (= 28−1). So divide by 256 (= 28):
image = image / 256
This will yield an array of floating-point pixel values. Its data type must be explicitly cast to 8-bit integer in order to drop any fractions.
image = image.astype('uint8')
Equivalently, and more efficiently, you may also bit-shift the 16-bit values 8 bits to the right:
image = (image >> 8).astype('uint8')
This makes the conversion faster (by a factor of 2 or so on modern hardware) as it skips the floating-point operations.
Then, either use the final image array for further processing, or save it to a new file:
imageio.imwrite('example.png', image)
If all you want is to convert, your .tiff file's color space to RGB. Then Try:-
from PIL import Image
img = Image.open(r"Path_to_tiff_image")
img = img.convert("RGB")
img.save(r"path_of_destination_image")
The above code, first opens a .tiff image, then changes its color mode to RGB. And then saves it to the destination location.
Hey I used tifffile to handle the file and a calculation that I've found in a different thread here for rescaling the 16-bit image to 8-bit.
import numpy as np
import tifffile as tif
import cv2
image = tif.imread('/home/trance/test.tiff')
# Rescale 16-bit to 8-bit
img_rescaled = 255 * (image - image.min()) / (image.max() - image.min())
# Colourising image and saving it with opencv
img_col = cv2.applyColorMap(img_rescaled.astype(np.uint8), cv2.COLORMAP_INFERNO)
cv2.imwrite('/home/trance/test.png', img_col)

How to create Mask matrix variable for OpenCV minMaxLoc function in Python?

I am using OpenCV in Python.
I am trying to create a Mask Matrix variable to be used in this function: cv.minMaxLoc. The Matrix variable should have the same size like my template image, with type = CV_8UC1.
My template image has a alpha channel which consists of only 0% or 100% transparency pixels. How can I extract the alpha channel data from my image and put it into the Mask Matrix with 0 = 100% transparency and 1 = 0% transparency?
import numpy as np
import cv
from PIL import Image
# open the image
img = Image.open('./pic.png', 'r')
r,g,b, alpha_channel = img.split()
mask = np.array(alpha_channel)
# all elements in alpha_channel that have value 0
# are set to 1 in the mask matrix
mask[alpha_channel==0] = 1
# all elements in alpha_channel that have value 100
# are set to 0 in the mask matrix
mask[alpha_channel==100] = 0
credit to other posts:
How to get alpha value of a PNG image with PIL?
Converting numpy array having image data to CvMat
To convert numpy array to cvmat do:
cv_arr = cv.fromarray(mask)
Check the dtype of mask. It should be dtype('uint8')
When I convert it my cv_arr is
cvmat(type=42424000 8UC1 rows=1245 cols=2400 step=2400 )
I am not an expert in opencv, but it seems to me that 8UC1 is automatically selected based on the fact that dtype is uint8 (I am guessing here because I couldn't find the documentation about that).

convert image to value matrix

I have an image which is like a chess board with 4 colors (Black, white, Red, Blue). I have to convert this image to a matrix of numbers: 1 for white, 2 for black, 3 for red so on.
For example the image:
should be converted to the matrix:
[[1,2,1,2,1,2...]
[2,1,2,1,2,1...]
...]
I'd prefer a solution in python.
I am not sure about SVG Images but lets suppose you have an image format readable by PIL (e.g. GIF, TIFF, JPEG, BMP, ...). Then you can read it using PIL like that:
import Image
img = Image.open("Chess_Board.bmp")
Now we want do do quantization, so the image pixels are not RGB anymore but a color index from 0 to 3 (suppose you want 4 different colors):
quantized = img.convert('P', palette=Image.ADAPTIVE, colors=4)
Next I suppose we convert it to numpy for easier access of the individual pixels. Then we do numpy magic to count how many go into one block:
import numpy as np
a = np.array(quantized)
blockLengthX = np.argmin(a[0]==a[0,0])
blockLengthY = np.argmin(a[:,0]==a[0,0])
After that it is easy. We just access the array using stepsize blockLengthX for cols and blockLengthY for rows:
result = a[::blockLengthX, ::blockLengthY]
Of course this assumes all of your blocks are exactly the same size.
Here is the complete program for easier copy and paste. I also shortened a bit:
import Image
import numpy as np
img = Image.open("Chess_Board.bmp")
a = np.array(img.convert('P', palette=Image.ADAPTIVE, colors=4))
blockLengthX = np.argmin(a[0]==a[0,0])
blockLengthY = np.argmin(a[:,0]==a[0,0])
result = a[::blockLengthX, ::blockLengthY]

Categories