Merge channels of NIfTI (.nii) images in python - python

I am trying to superimpose a CT .nii image and its mask in another color (possibly red). This is easily achievable for example with imageJ, thanks to the "Merge Channels" functionality. What I would like to obtain looks like this:
given my original image:
and its mask:
So basically I need to "convert to red" my mask and superimpose it on my grayscale image.
I've looked into SimpleITK (this is how I made the contour mask) but I can't proceed forward from here. Can anyone help?

Here's a SimpleITK script that overlays a mask on an image:
import SimpleITK as sitk
img = sitk.ReadImage("ct.jpg")
mask = sitk.ReadImage("mask.png")
# Extract one channel since the images are actually RGB
img = sitk.VectorIndexSelectionCast(img, 0)
mask = sitk.VectorIndexSelectionCast(mask, 0)
mask = mask>200
red = [255, 0, 0]
color_overlay = sitk.LabelMapOverlay( sitk.Cast(mask, sitk.sitkLabelUInt8),
img, opacity=0.5, colormap=red )
sitk.WriteImage(color_overlay, "overlay.png")
And here's resulting output image:
Note that the script first extracts one channel from image and mask to create grayscale images (since your examples were RGB). Also, it does a threshold on the mask to create a binary mask (0s and 1s).

Related

How can I overlay one image over another so that dark background is transparent?

I have 2 images, test1.jpg and test2.jpg that are RGB images. They have been converted from a 2D numpy array so they are monochrome images. They have the same shape. When I use the paste function, I only see one of the images instead of both.
Here are the test1 and test2 jpgs:
.
This is what I get after doing test1.paste(test2) and test1.save('final.jpg'):
Why is it only showing test2?
Here is my code:
im1 = Image.open('test1.jpg')
im2 = Image.open('test2.jpg')
im1.paste(im2)
im1.save('final.jpg')
You simply need to choose the lighter of your two images at each point with PIL Channel Operations:
from PIL import Image, ImageChops
im1 = Image.open('test1.jpeg')
im2 = Image.open('test2.jpeg')
# Choose lighter of the two images at each pixel location
combined = ImageChops.lighter(im1,im2)
Note that you could use paste() as you originally intended, but that it will paste all the black as well as the white pixels from image2 over image1. In order to avoid that, you would need to make a mask and only paste where image2 is non-zero. That might look like this:
im1 = Image.open('test1.jpeg')
im2 = Image.open('test2.jpeg')
# Make greyscale mask from image2
mask = im2.convert('L')
mask = mask.point(lambda i: 255 if i>0 else 0)
# Paste image2 into image1 only where image2 has non-black content
im1.paste(im2, mask=mask)
I just think the ImageChops.lighter() method is simpler.
Note that these two methods will give subtly different results. For example, if a pixel is 192 in image1 and 67 in image2, the ImageChops.lighter() method will result in 192, whereas the paste() method will see there is something in image2, and therefore give you the 67. Your choice!
paste is the wrong tool for this job, because it completely replaces the original image with the image you're pasting. It appears you wanted something closer to the blend function which produces a mix of both images. Unfortunately this has the side effect of darkening the image, because white blended with black becomes a middle gray; you'll probably want to double the values to compensate.
final = Image.blend(test1, test2, 0.5)
final = Image.eval(final, lambda x: 2*x))
One solution would be to use openCV instead of PIL. OpenCV's imagery is identical to a 2D numpy array, which means you can instead just use basic matrix addition to produce a combined image given "black" pixels are just 0.
import cv2 as cv
im1 = cv.imread('test1.jpg')
im2 = cv.imread('test2.jpg')
im3 = im1 + im2
cv.imwrite('test3.jpg', im3)
The black pixels remain black, as you're adding 0 to 0, while the filled pixels have the filled values of the two images combined (in this case, 0 + a maximum of 255 given no overlap between the two images)
The resulting image:
In the case of overlapping non-zero pixels, you may want to apply a 255 ceiling to the arrays, or instead normalize the combined image by the new maximum if that is preferred for your use case.

Removing pixels below a certain threshold

I have a grayscale image with something written in the front and something at the back. I'd like to filter out the back part of the letters and only have the front. It's only grayscale and not RGB, and I'd rather not have to calculate pixels manually.
Is there any library function I can use to do this? I'm new to python and at the moment, using PIL library, so that's my preference. But if there are other libraries, I'm open to that as well.
Here's the image:
Are you looking for a function that automatically strips the background for any given image, or just one that can filter out pixels that meet a certain criteria for this particular image?
The eval function applies the same transformation to every pixel in an image. This works for your image.
with Image.open("jFmbt.jpg") as im:
im = im.convert("L")
out_image = Image.eval(im, lambda x: 256 if x > 175 and x < 250 else x)
A very commonly used library for that would be OpenCV.
import cv2 as cv
# 0 flag -> read image as greyscale
img = cv.imread("img.jpg", 0)
# threshold
ret, thresh = cv.threshold(img, 150, 255, cv.THRESH_BINARY)
# result
cv.imwrite("output.jpg", thresh)
The resulting image would be:

How to automatically detect a specific feature from one image and map it to another mask image? Then how to smoothen only the corners of the image?

Using the dlib library I was able to mask the mouth feature from one image (masked).
masked
Similarly, I have another cropped image of the mouth that does not have the mask (colorlip).
colorlip
I had scaled and replaced the images (replaced) and using np.where as shown in the code below.
replaced
#Get the values of the lip and the target mask
lip = pred_toblackscreen[bbox_lip[0]:bbox_lip[1], bbox_lip[2]:bbox_lip[3],:]
target = roi[bbox_mask[0]:bbox_mask[1], bbox_mask[2]:bbox_mask[3],:]
cv2.namedWindow('masked', cv2.WINDOW_NORMAL)
cv2.imshow('masked', target)
#Resize the lip to be the same scale/shape as the mask
lip_h, lip_w, _ = lip.shape
target_h, target_w, _ = target.shape
fy = target_h / lip_h
fx = target_w / lip_w
scaled_lip = cv2.resize(lip,(0,0),fx=fx,fy=fy)
cv2.namedWindow('colorlip', cv2.WINDOW_NORMAL)
cv2.imshow('colorlip', scaled_lip)
update = np.where(target==[0,0,0],scaled_lip,target)
cv2.namedWindow('replaced', cv2.WINDOW_NORMAL)
cv2.imshow('replaced', update)
But the feature shape (lip) in 'colorlip' does not match the 'masked' image. So, there is a misalignment and the edges of the mask look sharp as if the image has been overlayed. How to solve this problem? And how to make the final replaced image look more subtle and normal?
**Update #2: OpenCV Image Inpainting to smooth jagged borders.
OpenCV python inpainting should help with rough borders. Using the mouth landmark model, mouth segmentation mask from DL model or anything that was used the border location can be found. From that draw border with a small chosen width around the mouth contour in a new image and use it as a mask for inpainting. The mask I provided need to be inverted to work.
In input masks one of the mask is wider, one has shadow and last one is narrow. The six output images are generated with radius value of 5 and 20 for all three masks.
Code
import numpy as np
# import cv2 as cv2
# import cv2
import cv2.cv2 as cv2
img = cv2.imread('images/lip_img.png')
#mask = cv2.imread('images/lip_img_border_mask.png',0)
mask = cv2.imread('images/lip_img_border_mask2.png',0)
#mask = cv2.imread('images/lip_img_border_mask3.png',0)
mask = np.invert(mask)
# Choose appropriate method and radius.
radius = 20
dst = cv2.inpaint(img, mask, radius, cv2.INPAINT_TELEA)
# dst = cv2.inpaint(img, mask, radius, cv2.INPAINT_NS)
cv2.imwrite('images/inpainted_lip.jpg', dst)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
Input Image and Masks
Output Images
**Update #1: Added Deep Image harmonization based blending methods.
Try OpenCV seamless cloning for subtle replacement and getting rid of sharp edges. Also deep learning based image inpainting on sharp corners or combining it with seamless clone may provide better results.
Deep learning based Image Harmonization can be another approach to blend together two images such that the cropped part matches the style of background image. Even in this case the pixel intensity will change to match the background but blending will be smoother. Links are added to bottom of the post.
Example
This code example is based on learnopencv seamless cloning example,
# import cv2
from cv2 import cv2
import numpy as np
src = cv2.imread("images/src_img.jpg")
dst = cv2.imread("images/dest_img.jpg")
src_mask = cv2.imread("images/src_img_rough_mask.jpg")
src_mask = np.invert(src_mask)
cv2.namedWindow('src_mask', cv2.WINDOW_NORMAL)
cv2.imshow('src_mask', src_mask)
cv2.waitKey(0)
# Where to place image.
center = (500,500)
# Clone seamlessly.
output = cv2.seamlessClone(src, dst, src_mask, center, cv2.NORMAL_CLONE)
# Write result
cv2.imwrite("images/opencv-seamless-cloning-example.jpg", output)
cv2.namedWindow('output', cv2.WINDOW_NORMAL)
cv2.imshow('output', output)
cv2.waitKey(0)
Source Image
Rough Mask Image
Destination Image
Final Image
Reference
https://docs.opencv.org/4.5.4/df/da0/group__photo__clone.html
https://learnopencv.com/seamless-cloning-using-opencv-python-cpp/
https://learnopencv.com/face-swap-using-opencv-c-python/
https://github.com/JiahuiYu/generative_inpainting
https://docs.opencv.org/4.x/df/d3d/tutorial_py_inpainting.html
Deep Image Harmonization
https://github.com/bcmi/Image-Harmonization-Dataset-iHarmony4
https://github.com/wasidennis/DeepHarmonization
https://github.com/saic-vul/image_harmonization
https://github.com/wuhuikai/GP-GAN
https://github.com/junleen/RainNet
https://github.com/bcmi/BargainNet-Image-Harmonization
https://github.com/vinthony/s2am

How to superimpose masked part of image to a new image?

I'm really sorry for this basic question, but I'm new to OpenCV and image processing in general, and couldn't figure this out after fiddling around for a while.
Here's what I'm trying to do:
I have a transparent PNG image:
I created a binary mask out of this with the transparent region being black and the object being white:
Now, I have another image like this, having the same dimensions:
Now, I wish to superimpose the white masked part from the first image (the actual object) onto this image. How do I do this?
Here is one way of doing it:
import cv2
# Load images
bg = cv2.imread('bg.png')
obj = cv2.imread('object.png')
mask = cv2.imread('mask.png')
# Zero background where we want to overlay
bg[mask>0]=0
# Add object to zeroed out space
bg += obj*(mask>0)
cv2.imwrite('result.png',bg)
Using the fact that we have numpy arrays at hands here, we can first extend the mask to three dimensions:
# Case 1: original mask is OpenCV mask (foreground values 255)
mask = np.dstack([(mask > 0)]*3)
# Case 2: original mask is already boolean
mask = np.dstack([mask]*3)
And then copy the image over using the mask:
np.copyto(background, foreground, where=mask)

Make white and gradient into transparent PNG file using Python

I need to convert this image (left one) into another with transparent background preserving the shadow below, that is like a gradient, I know that PNG files have multiple levels of transparent, I have another image that acts as mask for the part of the image that must NO be transparent.
Note: Images seems different because I crop them out manually but the mask file is a perfect match for the image (same size and position).
Lighter portion of the left image must be fully transparent and darker portion of the shadow must be less transparent.
This is a bit of a strange request. Anyways, this code should do what you need. Does this help?
import cv2
input_image = cv2.imread('input.png',0)
# Dummy mask to try since I did not have the mask image.
#ret, dummy_mask = cv2.threshold(input_image, 0, 50, cv2.THRESH_BINARY)
#mask = cv2.bitwise_not(dummy_mask)
# Your mask with 0s and 1s
mask = cv2.imread('mask.png',0)
# Retain the masked parts
non_alpha = cv2.bitwise_and(input_image, input_image, mask=mask)
# Get the alpha mask
alpha_mask = cv2.bitwise_not(mask)
alpha_source = cv2.bitwise_and(input_image, input_image, mask=mask)
alpha = cv2.bitwise_not(alpha_source)
#merge them
composite = cv2.merge((non_alpha, non_alpha, non_alpha, alpha));
cv2.imwrite("output.png", composite)

Categories

Resources