Filling holes in objects that touch the border of an image - python

I'm trying to fill holes in the below image.
When I use SciPy's binary_fill_holes(), I am generally successful, with the exception of objects that touch the image's border.
Are there any existing Python functions that can fill holes in objects that touch the border? I tried adding a white border around the image, but that just resulted in the entire image being filled.

This assumes that there is more background than other stuff. It basically does a connected component analysis on the image. Extract the largest component (assumed to be the background), and sets everything else to white.
import numpy as np
import matplotlib.pyplot as plt
import skimage.morphology, skimage.data
img = skimage.data.imread('j1ESv.png', 1)
labels = skimage.morphology.label(img)
labelCount = np.bincount(labels.ravel())
background = np.argmax(labelCount)
img[labels != background] = 255
plt.imshow(img, cmap=plt.cm.gray)
plt.show()

Related

Edge detection - remove background

Would like to use edge detection on microscope images to make the background white.
This is the code i have so far, could this be useful?
code:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def simple_edge_detection(image):
edges_detected = cv2.Canny(image , 100, 200)
images = [image , edges_detected]
location = [121, 122]
for loc, edge_image in zip(location, images):
plt.subplot(loc)
plt.imshow(edge_image, cmap='gray')
cv2.imwrite('edge_detected.png', edges_detected)
plt.savefig('edge_plot.png')
plt.show()
img = cv2.imread('gay2.0.jpg', 0)
simple_edge_detection(img)
result of the code:
result wished:
Use a Canny filter and binarize with a threshold sufficient to remove most of the background.
Remove the small blobs and detect those against the image borders (possibly fill their holes). These define a mask around the object of interest (don't worry about the green blob and orange lines in the picture).
You may add some preprocessing or postprocessing to remove the holders.

Plotting segmented color images using numpy masked array and imshow

I'm new to numpy's masked array data-structure, and I want to use it to work with segmented color images.
When I use matplotlib's plt.imshow( masked_gray_image, "gray") to display a masked gray image, the invalid regions will be displayed transparent, which is what I want.
However, when I do the same for color images it doesn't seem to work.
Interestingly the data-point cursor won't show the rgb values [r,g,b] but empty [], but still the color values are displayed instead of transparent.
Am I doing something wrong or is this not yet provided in matplotlib imshow?
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import face
img_col = face() #example image from scipy
img_gray = np.dot(img_col[...,:3], [0.299, 0.587, 0.114]) #convert to gray
threshold = 25
mask2D = img_gray < threshold # some exemplary mask
mask3D = np.atleast_3d(mask2D)*np.ones_like(img_col) # expand to 3D with broadcasting...
# using numpy's masked array to specify where data is valid
m_img_gray = np.ma.masked_where( mask2D, img_gray)
m_img_col = np.ma.masked_where( mask3D, img_col)
fig,axes=plt.subplots(1,4,num=2,clear=True)
axes[0].imshow(mask2D.astype(np.float32)) # plot mask
axes[0].set_title("simple mask")
axes[1].imshow(m_img_gray,"gray") #plot gray verison => works
axes[1].set_title("(works)\n masked gray")
axes[2].imshow(m_img_col) #plot color version, => does not work
axes[2].set_title("(doesn't work)\n masked color")
# manually adding mask as alpha channel to show what I want
axes[3].imshow( np.append( m_img_col.data, 255*(1-(0 < np.sum(m_img_col.mask ,axis=2,keepdims=True) ).astype(np.uint8) ),axis=2) )
axes[3].set_title("(desired) \n alpha channel set manually")
Here is an example image:
[update]:
some minor changes to code and images for better clarity...
I do not know if this is a feature not provided by matplotlib yet, but you can
just set all values to 255 where your mask is True:
m_img_col.data[m_img_col.mask]=255
In this way the invalid regions will be displayed as transparent

How can I make a draggable/adjustable ROI in OpenCV/Python?

Prior to performing my processing algorithm on an image, I need the user to click-draw a circle to create a clipping mask using the mouse. This mask will be used to remove areas of the image that will cause my algorithm to fail.
How can I allow the user to:
drag the ROI (to adjust x-y position on the image)
adjust the shape of the ROI (i.e. the size of the circle by dragging)
In the future I will need to use some feature detection to make the ROI choice, but for now I really need the user to be able to define the ROI in a way that is easy for them,
If you have scikit-image installed, you can use the following to do a rectangular selection (modifying the skimage code to do a circle instead would not be hard, though):
import matplotlib.pyplot as plt
from skimage import data
from skimage.viewer.canvastools import RectangleTool
f, ax = plt.subplots()
ax.imshow(data.camera(), interpolation='nearest', cmap='gray')
props = {'facecolor': '#000070',
'edgecolor': 'white',
'alpha': 0.3}
rect_tool = RectangleTool(ax, rect_props=props)
plt.show()
print("Final selection:")
rect_tool.callback_on_enter(rect_tool.extents)
You press enter to finalize the selection.
The piece of code given by Stefan must not be supported anymore (it fails when ax is passed to RectangleTool). RectangleTool only takes skimage viewer as argument. Here is a piece of code adapted from Stephan example and skimage documentation. It provides an interactive way for retrieving ROI coordinates.
from pylab import *
from skimage import data
from skimage.viewer.canvastools import RectangleTool
from skimage.viewer import ImageViewer
im = data.camera()
def get_rect_coord(extents):
global viewer,coord_list
coord_list.append(extents)
def get_ROI(im):
global viewer,coord_list
selecting=True
while selecting:
viewer = ImageViewer(im)
coord_list = []
rect_tool = RectangleTool(viewer, on_enter=get_rect_coord)
print "Draw your selections, press ENTER to validate one and close the window when you are finished"
viewer.show()
finished=raw_input('Is the selection correct? [y]/n: ')
if finished!='n':
selecting=False
return coord_list
a=get_ROI(im)
print a

Making image white space transparent, overlay onto imshow()

I have a plot of spatial data that I display with imshow().
I need to be able to overlay the crystal lattice that produced the data. I have a png
file of the lattice that loads as a black and white image.The parts of this image I want to
overlay are the black lines that are the lattice and not see the white background between the lines.
I'm thinking that I need to set the alphas for each background ( white ) pixel to transparent (0 ? ).
I'm so new to this that I don't really know how to ask this question.
EDIT:
import matplotlib.pyplot as plt
import numpy as np
lattice = plt.imread('path')
im = plt.imshow(data[0,:,:],vmin=v_min,vmax=v_max,extent=(0,32,0,32),interpolation='nearest',cmap='jet')
im2 = plt.imshow(lattice,extent=(0,32,0,32),cmap='gray')
#thinking of making a mask for the white background
mask = np.ma.masked_where( lattice < 1,lattice ) #confusion here b/c even tho theimage is gray scale in8, 0-255, the numpy array lattice 0-1.0 floats...?
With out your data, I can't test this, but something like
import matplotlib.pyplot as plt
import numpy as np
import copy
my_cmap = copy.copy(plt.cm.get_cmap('gray')) # get a copy of the gray color map
my_cmap.set_bad(alpha=0) # set how the colormap handles 'bad' values
lattice = plt.imread('path')
im = plt.imshow(data[0,:,:],vmin=v_min,vmax=v_max,extent=(0,32,0,32),interpolation='nearest',cmap='jet')
lattice[lattice< thresh] = np.nan # insert 'bad' values into your lattice (the white)
im2 = plt.imshow(lattice,extent=(0,32,0,32),cmap=my_cmap)
Alternately, you can hand imshow a NxMx4 np.array of RBGA values, that way you don't have to muck with the color map
im2 = np.zeros(lattice.shape + (4,))
im2[:, :, 3] = lattice # assuming lattice is already a bool array
imshow(im2)
The easy way is to simply use your image as a background rather than an overlay. Other than that you will need to use PIL or Python Image Magic bindings to convert the selected colour to transparent.
Don't forget you will probably also need to resize either your plot or your image so that they match in size.
Update:
If you follow the tutorial here with your image and then plot your data over it you should get what you need, note that the tutorial uses PIL so you will need that installed as well.

Drawing semi-transparent polygons in PIL

How do you draw semi-transparent polygons using the Python Imaging Library?
Can you draw the polygon on a separate RGBA image then use the Image.paste(image, box, mask) method?
Edit: This works.
from PIL import Image
from PIL import ImageDraw
back = Image.new('RGBA', (512,512), (255,0,0,0))
poly = Image.new('RGBA', (512,512))
pdraw = ImageDraw.Draw(poly)
pdraw.polygon([(128,128),(384,384),(128,384),(384,128)],
fill=(255,255,255,127),outline=(255,255,255,255))
back.paste(poly,mask=poly)
back.show()
http://effbot.org/imagingbook/image.htm#image-paste-method
I think #Nick T's answer is good, but you need to be careful when using his code as written with a very large background image, especially in the case that you may be annotating several polygons on said image. This is something I do when processing huge satellite images with some object detection code and annotating the detections using a transparent rectangle. To make the code efficient no matter the size of the background image, I make the following suggestion.
I would modify the solution to specify that the polygon image that you will paste be only as large as required to hold the polygon, not the same size as the back image. The coordinates of the polygon are specified with respect to the local bounding box, not the global image coordinates. Then you paste the polygon image at the offset in the larger background image.
import Image
import ImageDraw
img_size = (512,512)
poly_size = (256,256)
poly_offset = (128,128) #location in larger image
back = Image.new('RGBA', img_size, (255,0,0,0) )
poly = Image.new('RGBA', poly_size )
pdraw = ImageDraw.Draw(poly)
pdraw.polygon([ (0,0), (256,256), (0,256), (256,0)],
fill=(255,255,255,127), outline=(255,255,255,255))
back.paste(poly, poly_offset, mask=poly)
back.show()
Using the Image.paste(image, box, mask) method will convert the alpha channel in the pasted area of the background image into the corresponding transparency value of the polygon image.
The Image.alpha_composite(im1,im2) method utilizes the alpha channel of the "pasted" image, and will not turn the background transparent. However, this method again needs two equally sized images.

Categories

Resources