Trying to understand masking - python

I've been trying to understand masking and how it works with image filters. I'm using the following code to try to develop my understanding.
import scipy.ndimage as ndi
import matplotlib.pyplot as plt
import numpy as np
# Generate a random binary mask
np.random.seed(seed=182)
mask = np.random.randint(2, size=(901, 877))
img = np.random.rand(901, 877)
img_masked = np.ma.masked_array(img, mask = mask)
img_masked_filtered = ndi.median_filter(img_masked, size=10)
img_unmasked_filtered = ndi.median_filter(img, size=10)
median_masked = np.ma.median(img_masked)
median_unmasked = np.ma.median(img)
In the results, median_unmasked != median_masked as I expect, but img_masked_filtered == img_unmasked_filtered which I don't want. scipy.ndimage.median_filter does exactly the job I need, but it doesn't work with masked images. What can I use that will do the same thing as the median filter, but which will work on a masked image?
The weird size I'm using for the array is because that's the size of the image I eventually want to filter.

The ndimage filters do not respect masked arrays' masks. Instead, "mask" an ordinary NumPy array with nan values, and then use ndimage.generic_filter to call np.nanmedian:
import scipy.ndimage as ndi
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(seed=182)
# h, w = 901, 877
h, w = 10, 10
mask = np.random.randint(2, size=(h, w))
img = np.random.rand(h, w)
img_masked = np.where(mask, img, np.nan)
size = 3
img_masked_median = ndi.generic_filter(img_masked, np.nanmedian, size=size)
img_unmasked_median = ndi.median_filter(img, size=size)
fig, ax = plt.subplots(nrows=2, ncols=2)
ax[0,0].imshow(img)
ax[0,0].set_title('img')
ax[0,1].imshow(img_masked)
ax[0,1].set_title('img_masked')
ax[1,0].imshow(img_unmasked_median)
ax[1,0].set_title('img_unmasked_median')
ax[1,1].imshow(img_masked_median)
ax[1,1].set_title('img_masked_median')
plt.show()

Related

Extract N number of patches from an image

I have an image of dimension 155 x 240. Like the following:
I want to extract certain shape of patchs (25 x 25).
I don't want to patch from the whole image.
I want to extract N number of patch from non-zero (not background) area of the image. How can I do that? Any idea or suggestion or implementation will be appreciated. You can try with either Matlab or Python.
Note:
I have generated a random image so that you can process it for patching. image_process variable is that image in this code.
import numpy as np
from scipy.ndimage.filters import convolve
import matplotlib.pyplot as plt
background = np.ones((155,240))
background[78,120] = 2
n_d = 50
y,x = np.ogrid[-n_d: n_d+1, -n_d: n_d+1]
mask = x**2+y**2 <= n_d**2
mask = 254*mask.astype(float)
image_process = convolve(background, mask)-sum(sum(mask))+1
image_process[image_process==1] = 0
image_process[image_process==255] = 1
plt.imshow(image_process)
Lets assume that the pixels values you want to omit is 0.
In this case what you could do, is first find the indices of the non-zero values, then slice the image in the min/max position to get only the desired area, and then simply apply extract_patches_2d with the desired window size and number of patches.
For example, given the dummy image you supplied:
import numpy as np
from scipy.ndimage.filters import convolve
import matplotlib.pyplot as plt
background = np.ones((155,240))
background[78,120] = 2
n_d = 50
y,x = np.ogrid[-n_d: n_d+1, -n_d: n_d+1]
mask = x**2+y**2 <= n_d**2
mask = 254*mask.astype(float)
image_process = convolve(background, mask)-sum(sum(mask))+1
image_process[image_process==1] = 0
image_process[image_process==255] = 1
plt.figure()
plt.imshow(image_process)
plt.show()
from sklearn.feature_extraction.image import extract_patches_2d
x, y = np.nonzero(image_process)
xl,xr = x.min(),x.max()
yl,yr = y.min(),y.max()
only_desired_area = image_process[xl:xr+1, yl:yr+1]
window_shape = (25, 25)
B = extract_patches_2d(only_desired_area, window_shape, max_patches=100) # B shape will be (100, 25, 25)
If you plot the only_desired_area you will get the following image:
This is the main logic if you wish an even tighter bound you should adjust the slicing properly.

To fix an error : could not broadcast input array from shape (5,5) into shape (5,5,4)

I want to preprocess such an image dataset using an unsupervised wiener algorithm. But it doesn't work properly. when I run the code, it shows me a value attribute error. For convenience, my code is given below -
import cv2
import glob
from matplotlib import pyplot as plt
from skimage import io, restoration, img_as_float
import scipy.stats as st
import numpy as np
dataset = glob.glob('input/train/*.png')
directory = 'output/train/'
for img_id, img_path in enumerate(dataset):
img = img_as_float(io.imread(img_path))
def gkern(kernlen=21, nsig=2): #Returns a 2D Gaussian kernel.
lim = kernlen//2 + (kernlen % 2)/2
x = np.linspace(-lim, lim, kernlen+1)
kern1d = np.diff(st.norm.cdf(x))
kern2d = np.outer(kern1d, kern1d)
return kern2d/kern2d.sum()
psf = gkern(5,3) #Kernel length and sigma
deconvolved, _ = restoration.unsupervised_wiener(img, psf)
cl2 = cv2.resize(deconvolved, (512,512), interpolation = cv2.INTER_CUBIC)
plt.imsave(f"output/unsupervised_{img_id}.png", cl2, cmap='gray')
I am getting the error :
File "C:\Users\Junaed\.spyder-py3\unsupervised_wiener.py", line 33, in <module>
deconvolved, _ = restoration.unsupervised_wiener(img, psf)
ValueError: could not broadcast input array from shape (5,5) into shape (5,5,4)
How could I fix this issue, Can someone help me here?

Get a three dimensional array as a parameter and return a three dimensional arrays

I have to write a function to_red that should zero out the green and blue color components and return the result. I wrote the below code for an Image(.png) to zero out green and blue color and return red and it worked. However, as mentioned in title, the input parameter has to be a 3-d array and return a 3-d array. How should my below code be changed for that.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
def to_red()
src = plt.imread("C:\src\painting.png")
red_channel = src[:,:,0]
red_img = np.zeros(src.shape)
red_img[:,:,0] = red_channel
plt.imshow(red_img)
plt.show()
You can write your function like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
def to_red(src):
# Check if the input dimension is 3
if not src.ndim == 3:
# Raise exception or do something
print ("Dimension mismatch")
return 0
red_channel = src[:,:,0]
red_img = np.zeros(src.shape)
red_img[:,:,0] = red_channel
return red_img
And then you can call it like this
source_image = plt.imread("C:\src\painting.png")
red_image = to_red(source_image)
plt.imshow(red_image)
plt.show()
I also added a line to check if the input is actually 3 dimensional.
You can use numpy's powerful indexing capabilities
def to_red(src):
ret = a.copy()
ret[:,:,1:] = 0
return ret

Point in Polygon in a numpy array

I am using skimage. I need to create a mask equal in area to an image. The mask will have a region which will hide part of the image. I am building it as in the sample below but this is very slow and am sure there is a pythonic way of doing it. Could anyone highlight this please?
Code am using presently:
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
import skimage as sk
sourceimage = './sample.jpg'
img = np.copy(io.imread(sourceimage, as_gray=True))
mask = np.full(img.shape, 1)
maskpolygon = [(1,200),(300,644),(625,490),(625,1)]
from shapely.geometry import Point
from shapely.geometry.polygon import Polygon
pgon = Polygon(maskpolygon)
for r in range(mask.shape[0]):
for c in range(mask.shape[1]):
p = Point(r,c)
if pgon.contains(p):
mask[r,c] = 0
Expected result is like (for a 9x9 image - but I am working on 700x700)
[1,1,1,1,1,1,1,1,1]
[1,1,1,1,1,1,1,1,1]
[1,1,0,0,1,1,1,1,1]
[1,1,0,0,1,1,1,1,1]
[1,1,0,0,0,0,1,1,1]
[1,1,0,0,0,0,0,1,1]
[1,1,1,0,0,0,0,1,1]
[1,1,1,1,0,0,1,1,1]
[1,1,1,1,1,1,1,1,1]
I can invert 1's and 0's to show/hide region.
Thank you.
I have been able to resolve this thanks to #HansHirse.
Below is how I worked it out
sourceimage = './sample.jpg'
figuresize = (100, 100)
from skimage.draw import polygon
#open source and create a copy
img = np.copy(io.imread(sourceimage, as_gray=True))
mask = np.full(img.shape, 0)
maskpolygon = [(1,1), (280,1),(625, 280),(460, 621),(15, 625)]
maskpolygonr = [x[0] for x in maskpolygon]
maskpolygonc = [x[1] for x in maskpolygon]
rr, cc = polygon(maskpolygonr, maskpolygonc)
mask[rr ,cc] = 1
masked_image = img * mask
# show step by step what is happening
fig, axs = plt.subplots(nrows = 3, ncols = 1, sharex=True, sharey = True, figsize=figuresize )
ax = axs.ravel()
ax[0].imshow(img)#, cmap=plt.cm.gray)
ax[1].imshow(mask)#, cmap=plt.cm.gray)
ax[2].imshow(masked_image)#, cmap=plt.cm.gray)

Shape recognition with numpy/scipy (perhaps watershed)

My goal is to trace drawings that have a lot of separate shapes in them and to split these shapes into individual images. It is black on white. I'm quite new to numpy,opencv&co - but here is my current thought:
scan for black pixels
black pixel found -> watershed
find watershed boundary (as polygon path)
continue searching, but ignore points within the already found boundaries
I'm not very good at these kind of things, is there a better way?
First I tried to find the rectangular bounding box of the watershed results (this is more or less a collage of examples):
from numpy import *
import numpy as np
from scipy import ndimage
np.set_printoptions(threshold=np.nan)
a = np.zeros((512, 512)).astype(np.uint8) #unsigned integer type needed by watershed
y, x = np.ogrid[0:512, 0:512]
m1 = ((y-200)**2 + (x-100)**2 < 30**2)
m2 = ((y-350)**2 + (x-400)**2 < 20**2)
m3 = ((y-260)**2 + (x-200)**2 < 20**2)
a[m1+m2+m3]=1
markers = np.zeros_like(a).astype(int16)
markers[0, 0] = 1
markers[200, 100] = 2
markers[350, 400] = 3
markers[260, 200] = 4
res = ndimage.watershed_ift(a.astype(uint8), markers)
unique(res)
B = argwhere(res.astype(uint8))
(ystart, xstart), (ystop, xstop) = B.min(0), B.max(0) + 1
tr = a[ystart:ystop, xstart:xstop]
print tr
Somehow, when I use the original array (a) then argwhere seems to work, but after the watershed (res) it just outputs the complete array again.
The next step could be to find the polygon path around the shape, but the bounding box would be great for now!
Please help!
#Hooked has already answered most of your question, but I was in the middle of writing this up when he answered, so I'll post it in the hopes that it's still useful...
You're trying to jump through a few too many hoops. You don't need watershed_ift.
You use scipy.ndimage.label to differentiate separate objects in a boolean array and scipy.ndimage.find_objects to find the bounding box of each object.
Let's break things down a bit.
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
def draw_circle(grid, x0, y0, radius):
ny, nx = grid.shape
y, x = np.ogrid[:ny, :nx]
dist = np.hypot(x - x0, y - y0)
grid[dist < radius] = True
return grid
# Generate 3 circles...
a = np.zeros((512, 512), dtype=np.bool)
draw_circle(a, 100, 200, 30)
draw_circle(a, 400, 350, 20)
draw_circle(a, 200, 260, 20)
# Label the objects in the array.
labels, numobjects = ndimage.label(a)
# Now find their bounding boxes (This will be a tuple of slice objects)
# You can use each one to directly index your data.
# E.g. a[slices[0]] gives you the original data within the bounding box of the
# first object.
slices = ndimage.find_objects(labels)
#-- Plotting... -------------------------------------
fig, ax = plt.subplots()
ax.imshow(a)
ax.set_title('Original Data')
fig, ax = plt.subplots()
ax.imshow(labels)
ax.set_title('Labeled objects')
fig, axes = plt.subplots(ncols=numobjects)
for ax, sli in zip(axes.flat, slices):
ax.imshow(labels[sli], vmin=0, vmax=numobjects)
tpl = 'BBox:\nymin:{0.start}, ymax:{0.stop}\nxmin:{1.start}, xmax:{1.stop}'
ax.set_title(tpl.format(*sli))
fig.suptitle('Individual Objects')
plt.show()
Hopefully that makes it a bit clearer how to find the bounding boxes of the objects.
Use the ndimage library from scipy. The function label places a unique tag on each block of pixels that are within a threshold. This identifies the unique clusters (shapes). Starting with your definition of a:
from scipy import ndimage
image_threshold = .5
label_array, n_features = ndimage.label(a>image_threshold)
# Plot the resulting shapes
import pylab as plt
plt.subplot(121)
plt.imshow(a)
plt.subplot(122)
plt.imshow(label_array)
plt.show()

Categories