Additive poisson noise to an image - python

I have written a function to add poisson noise to an image using numpy with np.random.poisson(..). The image is already in numpy array form, using grayscale (0-255). I am wandering if it makes more physical sense to provide the numpy function with the pixel values as the rates for the distribution, or use a set value over all the image.
In the first case, the function will be expressed as:
import numpy as np
def poisson_noise(X):
noise = np.random.poisson(X, X.shape)
return noise + X
In the second:
import numpy as np
def poisson_noise(X):
noise = np.random.poisson(CONSTANT_RATE, X.shape)
return noise + X
In the first case, the pixels with higher grayscale value (lighter) will be more influenced by the noise, would that have any physical interpretation ?
Thank you!

Related

Create a probability map of image with Kernel Density Estimation

I'm working with mammograms that have calcifications (which are brighter spots than the surrounding tissue).
This is one of the images I have:
original image
I got this image by creating a heat map:
heat map of original image
But the heat map considers the overall brightness of the image. To create a probability map that considers the local luminosity I have thought about making a Kernel Density Estimation with a Gaussian filter, but I am having problems with the implementation.
My aim is to get a result similar to scikit-learn's "Kernel Density Estimate of Species Distributions" example. This is the code I tried to use:
import numpy as np
import cv2 as cv
from sklearn.neighbors import KernelDensity
img = cv.imread("mammo.tif", 0)
kde = KernelDensity(kernel="gaussian")
kde.fit(img)
sco = kde.score_samples(img)
The original image has a size of 4084x3328. I would like to get a probability map of the same size, but what I get in the variable sco is a vector of 4084x1 with all negative values.
In kde.fit(img) every row of img is treated as a single observation with 3328 features. Thus you are fitting a kernel density estimate where the kernel is a multivariate Gaussian distribution with 3328 variables. Then kde.score_samples(img) again computes the score for each row of img which results in 4084 values. Moreover, these values are logarithms of probabilities - hence they are negative.

Sliding window on an image to calculate variance of pixels in that window

I am trying to build a function that uses sliding window over and image and calculates the variance of pixels in the window and returns a bounding box where there is the most variance observed.
I'm new to coding and I've tried solutions from this post but I don't know how to input image in that instead of array.
I'm on a deadline here and been trying this since a while so any help is much appreciated . TIA
Edit: Also, if someone could help me with how to call the rolling_window_lastaxis function and modify it to what I'm trying to do then it would mean a lot.
Here is one way to compute the sliding window variance (or standard deviation) using Python/OpenCV/Skimage.
This approach makes use of the following form for computing the variance (see https://en.wikipedia.org/wiki/Variance):
Variance = mean of square of image - square of mean of image
However, since the variance will be outside the 8-bit range, we take the square root to form the standard deviation.
I also use the (local) mean filter from the Skimage rank filter module.
Input:
import cv2
import numpy as np
from skimage.morphology import rectangle
import skimage.filters as filters
# Variance = mean of square of image - square of mean of image
# See # see https://en.wikipedia.org/wiki/Variance
# read the image
# convert to 16-bits grayscale since mean filter below is limited
# to single channel 8 or 16-bits, not float
# and variance will be larger than 8-bit range
img = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE).astype(np.uint16)
# compute square of image
img_sq = cv2.multiply(img, img)
# compute local mean in 5x5 rectangular region of each image
# note: python will give warning about slower performance when processing 16-bit images
region = rectangle(5,5)
mean_img = filters.rank.mean(img, selem=region)
mean_img_sq = filters.rank.mean(img_sq, selem=region)
# compute square of local mean of img
sq_mean_img = cv2.multiply(mean_img, mean_img)
# compute variance using float versions of images
var = cv2.add(mean_img_sq.astype(np.float32), -sq_mean_img.astype(np.float32))
# compute standard deviation and convert to 8-bit format
std = cv2.sqrt(var).clip(0,255).astype(np.uint8)
# save results
# multiply by 2 to make brighter as an example
cv2.imwrite('lena_std.png',2*std)
# show results
# multiply by 2 to make brighter as an example
cv2.imshow('std', 2*std)
cv2.waitKey(0)
cv2.destroyAllWindows()
Local Standard Deviation Image for 5x5 Sliding Window:
ADDITION
Here is a version that finds the bounding box for the maximum average variance for the bounding box size and draws it on the variance image (actually standard deviation).
import cv2
import numpy as np
from skimage.morphology import rectangle
import skimage.filters as filters
# Variance = mean of square of image - square of mean of image
# See # see https://en.wikipedia.org/wiki/Variance
# set the bounding box size
bbox_size = 25
# read the image
# convert to 16-bits grayscale since mean filter below is limited
# to single channel 8 or 16-bits, not float
# and variance will be larger than 8-bit range
img = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE).astype(np.uint16)
# compute square of image
img_sq = cv2.multiply(img, img)
# compute local mean in bbox_size x bbox_size rectangular region of each image
# note: python will give warning about slower performance when processing 16-bit images
region = rectangle(bbox_size, bbox_size)
mean_img = filters.rank.mean(img, selem=region)
mean_img_sq = filters.rank.mean(img_sq, selem=region)
# compute square of local mean of img
sq_mean_img = cv2.multiply(mean_img, mean_img)
# compute variance using float versions of images
var = cv2.add(mean_img_sq.astype(np.float32), -sq_mean_img.astype(np.float32))
# compute standard deviation and convert to 8-bit format
std = cv2.sqrt(var).clip(0,255).astype(np.uint8)
# find bbox_size x bbox_size region with largest var (or std)
# get the moving window average at each pixel
std_ave = (cv2.sqrt(var)).astype(np.uint8)
# find the pixel x,y with the largest mean
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(std_ave)
x,y = max_loc
print("x:", x, "y:", y, "max:", max_val)
# draw rectangle for bounding box on copy of std image
result = std.copy()
result = cv2.merge([result, result, result])
cv2.rectangle(result, (x, y), (x+bbox_size, y+bbox_size), (0,0,255), 1)
# save results
# multiply by 2 to make brighter as an example
cv2.imwrite('lena_std.png',std)
cv2.imwrite('lena_std_bbox.png',result)
# show results
# multiply by 2 to make brighter as an example
cv2.imshow('std', std)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
x: 208 y: 67 max: 79.0
Resulting Bounding Box:
An alternative method to compute the windowed/rolling variance in regions of WxH is to use just numpy and scipy with convolutions, which are computed fairly quickly. An example:
import numpy as np
import scipy.signal
# Create image data
original = np.zeros((811,123))
img = original + np.random.normal(0, 1, original.shape)
# Create averaging kernel
H, W = 5, 5
mean_op = np.ones((H,W))/(H*W)
# Carry out convolution to compute mean of square, and square of mean
mean_of_sq = scipy.signal.convolve2d( img**2, mean_op, mode='same', boundary='symm')
sq_of_mean = scipy.signal.convolve2d( img , mean_op, mode='same', boundary='symm') **2
win_var = mean_of_sq - sq_of_mean

How to return Scikit-image Segmentation in 3D array?

I have a four band raster images (which is a 3D array) and I would like to segment the image band by band. The shape of this raster (after converted into a 3D array) is (12200, 7200, 4).
I have tried using scikit-image:
skimage.segmentation.slic(image[, …])
and
skimage.segmentation.quickshift(image[, …])
and the result is only a 2D array (= 1 band raster), with the shape of (12200, 7200).
The command seemed to have flatten the image and I cannot find anything in their documentation regarding the matter.
Here is what I've tried:
from __future__ import print_function
from osgeo import gdal
import numpy as np
from skimage import io
from skimage.segmentation import felzenszwalb, slic, quickshift
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float
# The input 4-band TSX image
image = r'raw_img/09K0153_20140501T084638_TSX.tif'
#convert image to Np array
img = io.imread(image, as_gray=False, plugin="gdal")
# Run the quick shift segmentation
segments = quickshift(img, kernel_size=3, convert2lab=False, max_dist=6, ratio=0.5)
How do I produce 3D segmentation result (that means, input: 3D array, output: 3D array) using Scikit-image, instead of 2D? Or is there another library that I can use for this purpose?
welcome to StackOverflow.
Indeed, the quickshift algorithm only returns a single (x, y) mask. You can find the implementation here. If you give more than one channel to the algorithm, it will take all of the channels into account to compute the segmentation mask.
If you want to apply the algorithm independently to each channel, you have to do it iteratively, for instance with the following code
from __future__ import print_function
from osgeo import gdal
import numpy as np
from skimage import io
from skimage.segmentation import felzenszwalb, slic, quickshift
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float
# The input 4-band TSX image
image = r'raw_img/09K0153_20140501T084638_TSX.tif'
#convert image to Np array
img = io.imread(image, as_gray=False, plugin="gdal")
# Instantiate a segments array of same shape than image
segments = np.zeros(img.shape)
Nc = img.shape[-1] # number of channels
# Run the quick shift segmentation for each channel
for c in range(Nc):
segments[:,:,c] = quickshift(img[:,:,c], kernel_size=3, convert2lab=False, max_dist=6, ratio=0.5)
How do I produce 3D segmentation result (that means, input: 3D array,
output: 3D array) using Scikit-image, instead of 2D?
Let's look at what it really means to have a 3D segmentation result as a 3D array. Let's consider a 3 channel image shown below. The segmentation we are interested in is shown in the blue dotted line.
Assume for now that there is some algorithm that will segment the blue dotted segment for us. But then how to represent the blue dotted segment?
If it was a square of a rectangle shape we could have represented the segment in a 3D numpy array (3D slice of the original image). But if the image is arbitary shape then how do we do it?
Segmentation information is captured using a mask. A mask 2D array corresponding to the size of the image is created. Let's assume we have only one segment in our image. In such a case all the pixels inside the segment of the image are marked as 1 in the corresponding mask image. This is a boolean mask (1 segment). If there are multiple segments then pixels in each segment as a unique number.
This is the reason you see that the result of the quickshift is a 2D array with the same dimension of the image. The result is the mask, the locations in the mask having the same values correspond to a segment which will represent a slice (which will be the same for all the channels). However, you will not be able to represent the slice using a numpy array if it of arbitary shape.
Just to add it is a bad idea to run quickshift of one channel at a time because it uses 5D space consisting of color information and image location 2 for segmentation. Using it on 1 channel at a time will give you bad segmentation results.

Low Pass Filter for blurring an image

I'm trying to blur an image using fft by passing a low pass filter that I created but the output yields to be an image full of gray noise. I'm just trying to follow the basics here but it seems like there is something wrong with my implementation:
from scipy import fftpack
import numpy as np
import imageio
from PIL import Image, ImageDraw
image1 = imageio.imread('image.jpg',as_gray=True)
#convert image to numpy array
image1_np=np.array(image)
#fft of image
fft1 = fftpack.fftshift(fftpack.fft2(image1_np))
#Create a low pass filter image
x,y = image1_np.shape[0],image1_np.shape[1]
#size of circle
e_x,e_y=50,50
#create a box
bbox=((x/2)-(e_x/2),(y/2)-(e_y/2),(x/2)+(e_x/2),(y/2)+(e_y/2))
low_pass=Image.new("L",(image1_np.shape[0],image1_np.shape[1]),color=0)
draw1=ImageDraw.Draw(low_pass)
draw1.ellipse(bbox, fill=255)
low_pass_np=np.array(low_pass)
low_pass_fft=fftpack.fftshift(fftpack.fft2(low_pass))
#multiply both the images
filtered=np.multiply(fft1,low_pass_fft)
#inverse fft
ifft2 = abs(fftpack.ifft2(fftpack.ifftshift(filtered)))
#save the image
imageio.imsave('fft-then-ifft.png', ifft2.astype(np .uint8))
As mentioned in comments by Cris Luengo, there are a few things that need to be corrected:
The provided elliptical shape for the low-pass filter makes sense in the frequency-domain, so you shouldn't be computing its FFT.
The filter magnitude of 255 scales the results by the same amount. As you store such large values, the uint8 type wraps around to keep only the 8 least significant bits, resulting in something that looks like noise. This can be fixed by simply changing the value of the filter:
draw1.ellipse(bbox, fill=1)
After readjusting the scaling, there computed filtered may still get slightly out of the desired 0-255 range in some areas of the image. This creates wrap-around spots (black areas in regions surrounded by white pixels, white areas in regions surrounded by black pixels, or even gradient bands where the image goes from white to black to white). To avoid this is common to clip the values to the 0-255 range with the following:
ifft2 = np.real(fftpack.ifft2(fftpack.ifftshift(filtered)))
ifft2 = np.maximum(0, np.minimum(ifft2, 255))
After making these corrections, you should have the following code:
from scipy import fftpack
import numpy as np
import imageio
from PIL import Image, ImageDraw
image1 = imageio.imread('image.jpg',as_gray=True)
#convert image to numpy array
image1_np=np.array(image1)
#fft of image
fft1 = fftpack.fftshift(fftpack.fft2(image1_np))
#Create a low pass filter image
x,y = image1_np.shape[0],image1_np.shape[1]
#size of circle
e_x,e_y=50,50
#create a box
bbox=((x/2)-(e_x/2),(y/2)-(e_y/2),(x/2)+(e_x/2),(y/2)+(e_y/2))
low_pass=Image.new("L",(image1_np.shape[0],image1_np.shape[1]),color=0)
draw1=ImageDraw.Draw(low_pass)
draw1.ellipse(bbox, fill=1)
low_pass_np=np.array(low_pass)
#multiply both the images
filtered=np.multiply(fft1,low_pass_np)
#inverse fft
ifft2 = np.real(fftpack.ifft2(fftpack.ifftshift(filtered)))
ifft2 = np.maximum(0, np.minimum(ifft2, 255))
#save the image
imageio.imsave('fft-then-ifft.png', ifft2.astype(np .uint8))
And the following filtered image:

insert white gaussian nois in images using python

I tried to search python function which corresponding to the Matlab function(imnoise). I want to Augment the images using the white Gaussian noise.
Tha matlab code for white Gaussian noise::
[I, map]=imread("img.png");
I=double(I)/255;
V=var(I(:)); %compute the image variance
J=imnoise(I, 'gaussian', 0, V/10); %insert gaussian white noise with mean zero and tenth of that variance
you can use numpy and Pillow for that!
from PIL import Image
import numpy as np
# Load the image into a numpy array
I = Image.open(filename)
I_array = np.array(im)
# Calculate the variance for the image and the noise
M = 0
V = np.var(im_array)
noise = np.random.normal(mean, variance, I_array.shape)
# Add the noise to the image numpy array and convert
# everything back to a PIL image.
I_array_noise = np.add(I_array, noise)
J = Image.fromarray(I_array_noise)

Categories

Resources