Point in Polygon in a numpy array - python

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)

Related

Doing naive affine_transforms (shear numpy image using numpy)

from scipy import ndimage
height, width, colors = image.shape
transform = [[1, 0, 0],
[0.5, 1, 0],
[0, 0, 1]]
sheared_array = ndimage.affine_transform(image,
transform,
offset=(0, -height*0.7, 0),
output_shape=(height, width*2, colors))
plt.imshow(sheared_array)
My current code does this. My aim is to shear the image by any degree X.
I want to do the same thing with a naive approach. As in, without any pre-defined functions. Just python/numpy code from scratch.
Given the image:
the following code should do what you want to achieve. It copies y-rows of pixels from the numpy array representing the source image to a new created wider image at appropriate x-offsets calculated from the given shear angle. The variable names in a following code are chosen in a way explaining what they are used for providing further details about what the code does:
from PIL import Image
import numpy as np
shearAngleDegrees = 30
PILimg = Image.open('shearNumpyImageByAngle.jpg')
#PILimg.show()
npImg = np.asarray(PILimg)
def shearNpImgByAngle(numpyImageArray, shearAngleDegrees, maxShearAngle=75):
import numpy as np
from math import tan, radians
assert -maxShearAngle <= shearAngleDegrees <= maxShearAngle
ccw = True if shearAngleDegrees > 0 else False # shear counter-clockwise?
imgH, imgW, imgRGBtplItems = npImg.shape
shearAngleRadians = radians(shearAngleDegrees)
imgWplus2imgH = abs(tan(shearAngleRadians)) # (plus in width)/(image height)
imgWplus = int((imgH-1)*imgWplus2imgH) # image width increase in pixels
npImgOut = np.zeros((imgH, imgW+imgWplus, imgRGBtplItems), dtype='uint8')
Wplus, Wplus2H = (0, -imgWplus2imgH) if ccw else (imgWplus,imgWplus2imgH)
for y in range(imgH):
shiftX = Wplus-int(y*Wplus2H)
npImgOut[y][shiftX:shiftX+imgW] = npImg[y]
return npImgOut
#:def
npImgOut = shearNpImgByAngle(npImg, shearAngleDegrees)
PILout = Image.fromarray(npImgOut)
PILout.show()
PILout.save('shearNumpyImageByAngle_shearedBy30deg.jpg')
gives:
As a nice add-on to the above code an extension filling the black edges of the sheared image mirroring the source picture around its sides:
def filledShearNpImgByAngle(npImg, angleDeg, fill=True, maxAngle=75):
import numpy as np
from math import tan, radians
assert -maxAngle <= angleDeg <= maxAngle
ccw = True if angleDeg > 0 else False # shear counter-clockwise?
imgH, imgW, imgRGBtplItems = npImg.shape
angleRad = radians(angleDeg)
imgWplus2imgH = abs(tan(angleRad)) # (plus in width)/(image height)
imgWplus = int((imgH-1)*imgWplus2imgH) # image add. width in pixels
npImgOut = np.zeros((imgH, imgW+imgWplus, imgRGBtplItems),
dtype=npImg.dtype) # 'uint8')
Wplus, Wplus2H = (0, -imgWplus2imgH) if ccw else (imgWplus, imgWplus2imgH)
for y in range(imgH):
shiftXy = Wplus-int(y*Wplus2H)
npImgOut[y][shiftXy:shiftXy+imgW] = npImg[y]
if fill:
assert imgW > imgWplus
npImgOut[y][0:shiftXy] = np.flip(npImg[y][0:shiftXy], axis=0)
npImgOut[y][imgW+shiftXy:imgW+imgWplus] = np.flip(npImg[y][imgW-imgWplus-1+shiftXy:imgW-1], axis=0)
[imgW-x-2]
return npImgOut
#:def
from PIL import Image
import numpy as np
PILimg = Image.open('shearNumpyImageByAngle.jpg')
npImg = np.asarray(PILimg)
shearAngleDegrees = 20
npImgOut = filledShearNpImgByAngle(npImg, shearAngleDegrees)#, fill=False)
shearAngleDegrees = 10
npImgOut = filledShearNpImgByAngle(npImgOut, shearAngleDegrees)#, fill=False)
PILout = Image.fromarray(npImgOut)
PILout.show()
PILout.save('shearNumpyImageByAngle_filledshearBy30deg.jpg')
gives:
or other way around:

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.

getting a dark image using make_lupton_rgb

I am using astropy visualization to make a colored image of M66 in this case.
Before doing anything I learnt that I have to cast my RGB .fts array with numpy.float_()
forCasting = np.float_()
### READING
b = fits.open("data/"+"M66-Blue.fts")[0].data
r = fits.open("data/"+"M66-Red.fts")[0].data
g = fits.open("data/"+"M66-Green.fts")[0].data
### CASTING
r = np.array(r,forCasting)
g = np.array(g,forCasting)
b = np.array(b,forCasting)
so that I could proceed with my stretch like :
stretch = SqrtStretch() + ZScaleInterval()
r = stretch(b)
g = stretch(r)
b = stretch(g)
plt.imshow(r, origin='lower')
plt.show()
plt.imshow(g, origin='lower')
plt.show()
plt.imshow(b, origin='lower')
plt.show()
Then I just use the method make_lupton_rgb from astropy.visualizaion as follow, but I have a super dark image that I cannot distinguish anything. Does anybody know why I have a dark final image here? Do you have any suggestions?
### SAVING
# rgb_default = make_lupton_rgb(r, g, b, minimum=1000, stretch=900, Q=100, filename="provafinale.png")
rgb_default = make_lupton_rgb(r, g, b, filename="provafinale.png")
plt.imshow(rgb_default, origin='lower')
plt.show()
Thanks!
It looks like you have to set stretch and Q arguments of make_lupton_rgb.
The default values are stretch=5 and Q=8, that gives dark result.
I have no experience with astropy or with Astronomy.
I just played with the arguments, and got bright image using stretch=1 and Q=0.
rgb_default = make_lupton_rgb(r, g, b, minimum=0, stretch=1, Q=0, filename="provafinale.png")
I tried computing minimum and stretch using np.percentile, for linear stretching the output.
I tested the code using an m8_050507_9i9m image from index_fits.
Here is the code I used for testing:
import numpy as np
from astropy.io import fits
from astropy.visualization import SqrtStretch
from astropy.visualization import ZScaleInterval
from astropy.visualization import make_lupton_rgb
from matplotlib import pyplot as plt
forCasting = np.float_()
### READING
# http://www.mistisoftware.com/astronomy/index_fits.htm
r = fits.open("m8_050507_9i9m_R.FIT")[0].data
g = fits.open("m8_050507_9i9m_G.FIT")[0].data
b = fits.open("m8_050507_9i9m_B.FIT")[0].data
# Crop the top and the right margin (contains black pixels)
r = r[:, :-200]
g = g[:, :-200]
b = b[:, :-200]
### CASTING
r = np.array(r,forCasting)
g = np.array(g,forCasting)
b = np.array(b,forCasting)
stretch = SqrtStretch() + ZScaleInterval()
r = stretch(b)
g = stretch(r)
b = stretch(g)
plt.imshow(r, origin='lower')
plt.imshow(g, origin='lower')
plt.imshow(b, origin='lower')
### SAVING
# https://docs.astropy.org/en/stable/api/astropy.visualization.make_lupton_rgb.html
# astropy.visualization.make_lupton_rgb(image_r, image_g, image_b, minimum=0, stretch=5, Q=8, fil/ename=None)[source]
# Return a Red/Green/Blue color image from up to 3 images using an asinh stretch.
# The input images can be int or float, and in any range or bit-depth.
lo_val, up_val = np.percentile(np.hstack((r.flatten(), g.flatten(), b.flatten())), (0.5, 99.5)) # Get the value of lower and upper 0.5% of all pixels
stretch_val = up_val - lo_val
rgb_default = make_lupton_rgb(r, g, b, minimum=lo_val, stretch=stretch_val, Q=0, filename="provafinale.png")
# Cut the top rows - contains black pixels
rgb_default = rgb_default[100:, :, :]
plt.imshow(rgb_default, origin='lower')
plt.show()
Result:

Trying to understand masking

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()

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