Cutting a patch around a segment of a segmented image - python

I have an segmented image into superpixels as follows:
from skimage.data import astronaut
img = astronaut()
segments_slic = slic(img, n_segments=250, compactness=10, sigma=1,
start_label=1)
fig = plt.figure(figsize = (16,8));
plt.imshow(mark_boundaries(img, segments_slic))
And got the following image:
I wish to cut a patch around each superpixel. Consider, for example, the patch around the shining part of the helmet colored red:
If I want to take a close (manual) look at the segments using plt.imshow(segments_slic[425:459,346:371]), I get this patch around the segment:
The pixels with the specific superpixel labe streach on row 425:459 and on columns 346:371.
Currently, I am doing this:
patches = list()
for superpixel in np.unique(segments_slic ):
x_min = np.min(np.where(segments == 15)[0]);
x_max = np.max(np.where(segments == 15)[0]);
y_min = np.min(np.where(segments == 15)[1]);
y_max = np.max(np.where(segments == 15)[1]);
patches.append(I[x_min:x_max,y_min:y_max,:]);
Not sure if it is correct, though it seems to be fine. What is the best way to generate such a patch for each superpixel? Moreover, is it possible to set the pixels in the patch, which do not belong to the superpixel, to black?

You can use regionprops and access the patch coordinates via region.bbox as in
from skimage.data import astronaut
import matplotlib.pyplot as plt
from skimage.segmentation import slic
from skimage.segmentation import mark_boundaries
from skimage.measure import regionprops
import matplotlib.patches as mpatches
img = astronaut()
segments_slic = slic(img, n_segments=250, compactness=10, sigma=1, start_label=1)
fig, ax = plt.subplots(figsize=(16, 8))
ax.imshow(img)
for region in regionprops(segments_slic):
# draw rectangle around segmented coins
minr, minc, maxr, maxc = region.bbox
rect = mpatches.Rectangle((minc, minr), maxc - minc, maxr - minr,
fill=False, edgecolor='red', linewidth=2)
ax.add_patch(rect)
# access patch via img[minr:maxr, minc:maxc]
plt.imshow(mark_boundaries(img, segments_slic))
plt.show()
This results in
Example adapted from here.
EDIT: Furthermore, with region.image you get a mask of your region to set the others to black.

Related

Finding the Interface of two regions of a segmented image

I have a segmented (by watershed) image of two regions that share one boundary. How do I easily find the position of the pixels on the interface? I tried using hints from this answer but could not get it working. Here is my example code:
import matplotlib.pyplot as plt
from scipy import ndimage as ndi
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
from skimage import future
from skimage.measure import label, regionprops, regionprops_table
# Generate an initial image with two overlapping circles
x, y = np.indices((80, 80))
x1, y1, x2, y2 = 28, 28, 44, 52
r1, r2 = 16, 20
mask_circle1 = (x - x1)**2 + (y - y1)**2 < r1**2
mask_circle2 = (x - x2)**2 + (y - y2)**2 < r2**2
image = np.logical_or(mask_circle1, mask_circle2)
# Now we want to separate the two objects in image
# Generate the markers as local maxima of the distance to the background
distance = ndi.distance_transform_edt(image)
coords = peak_local_max(distance, footprint=np.ones((3, 3)), labels=image)
mask = np.zeros(distance.shape, dtype=bool)
mask[tuple(coords.T)] = True
markers, _ = ndi.label(mask)
labels = watershed(-distance, markers, mask=image)
fig, axes = plt.subplots(ncols=3, figsize=(9, 3), sharex=True, sharey=True)
ax = axes.ravel()
ax[0].imshow(image, cmap=plt.cm.gray)
ax[0].set_title('Overlapping objects')
ax[1].imshow(-distance, cmap=plt.cm.gray)
ax[1].set_title('Distances')
ax[2].imshow(labels, cmap=plt.cm.nipy_spectral)
ax[2].set_title('Separated objects')
for a in ax:
a.set_axis_off()
fig.tight_layout()
plt.show()
#---------------- find the interface pixels (either of the two interfaces) of these two objects -----------
rag = future.graph.RAG(labels)
rag.remove_node(0)
for region in regionprops(labels):
nlist=list(rag.neighbors(region.label))
print(nlist)
The nlist seems to be just a list containing one element 1: [1]. I was expecting position of pixels.
I do not have much experience in using the graph and RAG. It seems that rag creates a graph/network of the regions and has the information of which region is next to which one but I cannot extract that information in the form of the interface pixels. Thanks for any help.
Currently the RAG object doesn't keep track of all the regions and boundaries, though we hope to support that in the future. What you found is just the list of adjacent regions.
For now, if you only have two regions, it's not too expensive to do this manually:
from skimage.morphology import dilation
label1 = labels == 1
label2 = labels == 2
boundary = dilation(label1) & dilation(label2)

How to measure a text element in matplotlib

I need to lay out a table full of text boxes using matplotlib. It should be obvious how to do this: create a gridspec for the table members, fill in each element of the grid, take the maximum heights and widths of the elements in the grid, change the appropriate height and widths of the grid columns and rows. Easy peasy, right?
Wrong.
Everything works except the measurements of the items themselves. Matplotlib consistently returns the wrong size for each item. I believe that I have been able to track this down to not even being able to measure the size of a text path correctly:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatch
import matplotlib.text as mtext
import matplotlib.path as mpath
import matplotlib.patches as mpatches
fig, ax = plt.subplots(1, 1)
ax.set_axis_off()
text = '!?' * 16
size=36
## Buildand measure hidden text path
text_path=mtext.TextPath(
(0.0, 0.0),
text,
prop={'size' : size}
)
vertices = text_path.vertices
code = text_path.codes
min_x, min_y = np.min(
text_path.vertices[text_path.codes != mpath.Path.CLOSEPOLY], axis=0)
max_x, max_y = np.max(
text_path.vertices[text_path.codes != mpath.Path.CLOSEPOLY], axis=0)
## Transform measurement to graph units
transData = ax.transData.inverted()
((local_min_x, local_min_y),
(local_max_x, local_max_y)) = transData.transform(
((min_x, min_y), (max_x, max_y)))
## Draw a box which should enclose the path
x_offset = (local_max_x - local_max_y) / 2
y_offset = (local_max_y - local_min_y) / 2
local_min_x = 0.5 - x_offset
local_min_y = 0.5 - y_offset
local_max_x = 0.5 + x_offset
local_max_y = 0.5 + y_offset
path_data = [
(mpath.Path.MOVETO, (local_min_x, local_min_y)),
(mpath.Path.LINETO, (local_max_x, local_min_y)),
(mpath.Path.LINETO, (local_max_x, local_max_y)),
(mpath.Path.LINETO, (local_min_x, local_max_y)),
(mpath.Path.LINETO, (local_min_x, local_min_y)),
(mpath.Path.CLOSEPOLY, (local_min_x, local_min_y)),
]
codes, verts = zip(*path_data)
path = mpath.Path(verts, codes)
patch = mpatches.PathPatch(
path,
facecolor='white',
edgecolor='red',
linewidth=3)
ax.add_patch(patch)
## Draw the text itself
item_textbox = ax.text(
0.5, 0.5,
text,
bbox=dict(boxstyle='square',
fc='white',
ec='white',
alpha=0.0),
transform=ax.transAxes,
size=size,
horizontalalignment="center",
verticalalignment="center",
alpha=1.0)
plt.show()
Run this under Python 3.8
Expect: the red box to be the exact height and width of the text
Observe: the red box is the right height, but is most definitely not the right width.
There doesn't seem to be any way to do this directly, but there's a way to do it indirectly: instead of using a text box, use TextPath, transform it to Axis coordinates, and then use the differences between min and max on each coordinate. (See https://matplotlib.org/stable/gallery/text_labels_and_annotations/demo_text_path.html#sphx-glr-gallery-text-labels-and-annotations-demo-text-path-py for a sample implementation. This implementation has a significant bug -- it uses vertices and codes directly, which break in the case of a clipped text path.)

Python: Plotting three images in one to show before and after

I am trying to make an image like the one below which would be made from 3 equally sized arrays that get shown only partially. Is there some way to slice or overplot the three arrays to get a division like this one?
How about using Patches
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.lines as lines
import os
patch1 = ((0,0.3),(0,1),(0.5,1),(0.5,0.5))
patch2 = ((0.5,0.5),(0.5,1),(1,1),(1,0.3))
patch3 = ((0,0),(0,0.3),(0.5,0.5),(1,0.3),(1,0))
# Pictures from Win10 in WSL2
path = r"/mnt/c/Windows/Web/Wallpaper/Theme1"
img1 = plt.imread(os.path.join(path, "img1.jpg"))
img2 = plt.imread(os.path.join(path, "img2.jpg"))
img3 = plt.imread(os.path.join(path, "img3.jpg"))
fig, ax = plt.subplots()
poly1 = patches.Polygon(patch1, transform=ax.transAxes)
poly2 = patches.Polygon(patch2, transform=ax.transAxes)
poly3 = patches.Polygon(patch3, transform=ax.transAxes)
ip1 = ax.imshow(img1)
ip2 = ax.imshow(img2)
ip3 = ax.imshow(img3)
ip1.set_clip_path(poly1)
ip2.set_clip_path(poly2)
ip3.set_clip_path(poly3)
l1 = lines.Line2D((0, 0.5), (0.3, 0.5), color="w", transform=ax.transAxes)
l2 = lines.Line2D((0.5, 0.5), (0.5, 1), color="w", transform=ax.transAxes)
l3 = lines.Line2D((0.5, 1), (0.5, 0.3), color="w", transform=ax.transAxes)
l1.set_linewidth(5)
l2.set_linewidth(5)
l3.set_linewidth(5)
fig.add_artist(l1)
fig.add_artist(l2)
fig.add_artist(l3)
ax.axis('off')
plt.show()
I see there's a good answer here. but I came up with a code, and don't want to discard it.
I think a good way to get this is creating a list of mask using an auxiliar matrix with the angle of every pixel, then dividing the whole angle by the number of imgs and compose with those masks:
Imports and loading images (this is to show some result, there's no need to do some large block to load imgs) the imports are needed
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.pyrDown(cv2.imread("/home/ulises/stackof/composing/coffee.jpg")[:,:,::-1])
img2 = cv2.GaussianBlur(img,(7,7),2)
img3 = cv2.GaussianBlur(img,(23,23),7)
imgList = [img,img2,img3]
plt.imshow(np.hstack(imgList))
with the result [1]
Then a function that create the img with angles
def rad_img(W,H):
"""create a matrix with size WxH that contains in every
value the angle in radians to the center of the matrix W/2 ,H/2"""
return np.fromfunction(lambda x,y: np.pi-np.arctan2(y-W/2,x-H/2) ,(H,W))
wich results on something like [2]
Last a function that uses this and generate masks with the imgList and ploting the results:
def compose_images(imgList):
"asuming the imgs have the same shape, if not resize then to an unique.."
nImgs = len(imgList)
angle = 2*np.pi/nImgs
h,w = imgList[0].shape[:2]
radImg = rad_img(w,h)
outputImg = np.zeros((h,w,3),dtype=np.uint8)
for i in range(nImgs):
thisMask = (i*angle<=radImg) * (radImg<(i+1)*angle)
outputImg += imgList[i]*thisMask[:,:,None]
return outputImg
newImg = compose_images(imgList)
plt.figure()
plt.imshow(newImg)
this creates an image like this one:

Matplotlib RegularPolyCollection with static (data like) sizes?

Is it possible to create a RegularPolyCollection with static sizes?
I'd like to give the size in data units, not in screen units. Just like the offsetts.
The target is to have an image of a camera with 1440 hexagonal Pixels with a diameter of 9.5 mm.
It is possible to achieve this with looping over 1440 Polygons but i was not successfull creating it with a PolyCollection which has big advantages, for creating colormaps etc.
Here is the code i use to plot the 1440 hexagons with static size:
for c, x, y in zip(pixel_color, pixel_x, pixel_y):
ax.add_artist(
RegularPolygon(
xy=(x, y),
numVertices=6,
radius=4.75,
orientation=0.,
facecolor=c,
edgecolor=edgecolor,
linewidth=1.5,
)
)
And this code produces the same but with wrong and not static (in terms of data) sizes:
a = 1/np.sqrt(3) * 9.5
collection = RegularPolyCollection(
numsides=6,
rotation=0.,
sizes=np.ones(1440)*np.pi*a**2, # tarea of the surrounding circle
facecolors=pixel_colors,
edgecolors="g",
linewidth=np.ones(1440)*1.5,
offsets=np.transpose([pixel_x, pixel_y]),
transOffset=self.transData,
)
self.add_collection(collection)
How can I achieve the static sizes of the hexagons with the advantages of having a collection?
I recently had the same problem. The solution is to simply use PatchCollection instead of RegularPolyCollection. The disadvantage is, however, that you have instantiate every single patch manually. Below you'll find a code example that plots 10,000 regular hexagons on a regular grid.
# imports
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
from matplotlib.collections import PatchCollection
import numpy as np
# set up figure
fig, ax = plt.subplots(1)
# positions
pixel_x, pixel_y = np.indices((100, 100))
pixel_color = np.random.random_sample(30000).reshape(10000, 3)
dx = 4 # horizontal stride
dy = 5 # vertical stride
# set static radius
poly_radius = 2.5
# list to hold patches
patch_list = []
# creat the patches
for c, x, y in zip(pixel_color, pixel_x.flat, pixel_y.flat):
patch_list.append(
RegularPolygon(
xy=(x*dy, y*dy),
numVertices=6,
radius=poly_radius,
orientation=0.,
facecolor=c,
edgecolor='k'
)
)
pc = PatchCollection(patch_list, match_original=True)
ax.add_collection(pc)
ax.axis([-3, 480, -3, 480])
plt.show()
On my machine this code takes about 2.8 seconds to render everything.
If you'd like to use RegularPolyCollection, I've figured out how to set the sizes correctly. The main limitation is that the sizes depend on the axes transform, and so both the axes limits and the figure size need to be locked in before you calculate the sizes.
In the version below, the figure - and axis - also has to be square.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
sin60 = np.sin(np.pi/3)
fig, ax = plt.subplots()
fig.set_size_inches(8, 8)
ax.set_aspect(1)
ax.set_xlim(-1.5*sin60, +1.5*sin60)
ax.set_ylim(-1.5*sin60, +1.5*sin60)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
coords = [[-1/2, +sin60/2], [+1/2, +sin60/2], [0, -sin60/2]]
radius = .5/sin60
data_to_pixels = ax.transData.get_matrix()[0, 0]
pixels_to_points = 1/fig.get_dpi()*72.
size = np.pi*(data_to_pixels*pixels_to_points*radius)**2
hexes = mpl.collections.RegularPolyCollection(
numsides=6,
sizes=3*(size,),
offsets=coords,
edgecolors=3*('k',),
linewidths=1,
transOffset=ax.transData)
ax.add_collection(hexes)

Matplotlib transparent overlay & pdf transparency

Let's assume I have two numpy arrays (The ones I present are just examples):
import numpy as np
A = np.arange(144).reshape((12, 12))
np.random.shuffle(A)
B = np.ones((12,12))
B[0:10:4,:] = None
I want to plot A using imshow:
import matplotlib.pyplot as mplt
mplt.imshow(A, cmap = mplt.gray())
and overlay B so that the None areas are fully transparent and the one areas have an alpha of (e.g. alpha = 0.3.).
I already tried using something along the lines of:
mplt.imshow(B, cmap = mplt.get_cmap('Reds), alpha = 0.3)
but that does not work. Also tried to use masked arrays to create B, but cannot get my head around it. Any suggestions?
Thanks
EDIT:
I ended up using
my_red_cmap = mplt.cm.Reds
my_red_cmap.set_under(color="white", alpha="0")
which works like a charm (I tested Bill's solution as well, which also works perfectly).
If instead of None you use 0's for the transparent colors, you can take your favorite matplotlib colormap and add a transparent color at the beginning of it:
my_red_cmap = mplt.cm.Reds
my_red_cmap.set_under(color="white", alpha="0")
then you can just plot the array B with a global alpha of 0.3 whatever you want, using your custom color map, which will use a transparent white as its first value.
You can do the following:
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt
x = np.arange(100).reshape(10, 10)
y = np.arange(-50, 150, 2).reshape(10, 10)
y[y<x] = -100 # Set bad values
cmap1 = cm.gray
cmap2 = cm.Reds
cmap2.set_under((1, 1, 1, 0))
params = {'interpolation': 'nearest'}
plt.imshow(x, cmap=cmap1, **params)
plt.show()
plt.imshow(y, cmap=cmap2, **params)
plt.show()
plt.imshow(x, cmap=cmap1, **params)
plt.imshow(y, cmap=cmap2, vmin=0, **params) # vmin > -100
plt.show()

Categories