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

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:

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

Cutting a patch around a segment of a segmented image

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.

why is the histogram of Cr and Cb slightly changing when I am performing histogram matching in Y alone?

The code I used to match the histograms along with all the import statements:
import skimage.viewer
from skimage import data,io,color
from skimage import exposure
from skimage.exposure import match_histograms
ref1 = io.imread("drive/My Drive/Images_for_Adarsh/DSC_6139.JPG")
orig1 = io.imread("drive/My Drive/Images_for_Adarsh/DSC_6138.JPG")
ref = color.rgb2ycbcr(ref1)
orig = color.rgb2ycbcr(orig1)
f1 = match_histograms(orig, ref, multichannel=True)
f1[:,:,1] = orig[:,:,1]
f1[:,:,2] = orig[:,:,2]
f2 = color.ycbcr2rgb(f1)
The code I used to plot the histograms:
import numpy as np
import cv2
from matplotlib import pyplot as plt
from matplotlib.pyplot import figure
img1 = cv2.imread("drive/My Drive/Images_for_Adarsh/DSC_6176.JPG")
img2 = cv2.imread("drive/My Drive/Y Histogram_Match RESULTS/Y6176.JPG")
img1f = cv2.cvtColor(img1,cv2.COLOR_BGR2YCR_CB)
img2f = cv2.cvtColor(img2,cv2.COLOR_BGR2YCR_CB)
histr1 = cv2.calcHist([img1f],[0],mask,[256],[0,255])
histr2 = cv2.calcHist([img2f],[0],mask,[256],[0,255])
f = figure(num=None, figsize=(9, 5), dpi=400, facecolor='w', edgecolor='k')
plt.subplot(1 ,2 , 1)
plt.plot(histr1,color = 'pink')
plt.xlim([0,275])
plt.xlabel("Luminance")
plt.ylabel("No. of Pixels")
plt.subplot(1 ,2 , 2)
plt.plot(histr2,color = 'pink')
plt.xlim([0,275])
plt.xlabel("Luminance")
plt.ylabel("No. of Pixels")
a1 = f.add_subplot(121)
a2 = f.add_subplot(122)
f.suptitle("DSC_6176 -- LUMINANCE HISTOGRAM", fontsize=16)
a1.title.set_text('ORIGINAL')
a2.title.set_text('RESULT(after Y value Histogram Matching)')
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0.45, hspace=None)
plt.savefig("drive/My Drive/histograms Y Matching/Luminance6176.JPG",dpi = 400)
This is the Luminance histogram for the original image on the left and that of final result on the right
This is the Cb histogram for the original image on the left and that of final result on the right
This is the Cr histogram for the original image on the left and that of final result on the right
This is the input image
This is the reference image(I want the luminance histogram of the input image to this image)
Here, I am performing histogram matching on the luminance('Y' value in YCRCB). For this, I am performing histogram matching on all 3 channels, Y, Cr, and Cb, and then copying Cr and Cb from the original image and putting it in my final image. Hence, in effect, my code only modifies the luminance while Cr and Cb are unchanged. However, when I am plotting the histograms for my 3 channels, there is slight variation Cr and Cb channels. I am not able to understand this. Why is this happening? Please help! Thanks in advance!
I have added the Luminance(Y), Cb, and Cr histograms of the original image and result for your reference.

Plotting images side by side using matplotlib

I was wondering how I am able to plot images side by side using matplotlib for example something like this:
The closest I got is this:
This was produced by using this code:
f, axarr = plt.subplots(2,2)
axarr[0,0] = plt.imshow(image_datas[0])
axarr[0,1] = plt.imshow(image_datas[1])
axarr[1,0] = plt.imshow(image_datas[2])
axarr[1,1] = plt.imshow(image_datas[3])
But I can't seem to get the other images to show. I'm thinking that there must be a better way to do this as I would imagine trying to manage the indexes would be a pain. I have looked through the documentation although I have a feeling I may be look at the wrong one. Would anyone be able to provide me with an example or point me in the right direction?
EDIT:
See the answer from #duhaime if you want a function to automatically determine the grid size.
The problem you face is that you try to assign the return of imshow (which is an matplotlib.image.AxesImage to an existing axes object.
The correct way of plotting image data to the different axes in axarr would be
f, axarr = plt.subplots(2,2)
axarr[0,0].imshow(image_datas[0])
axarr[0,1].imshow(image_datas[1])
axarr[1,0].imshow(image_datas[2])
axarr[1,1].imshow(image_datas[3])
The concept is the same for all subplots, and in most cases the axes instance provide the same methods than the pyplot (plt) interface.
E.g. if ax is one of your subplot axes, for plotting a normal line plot you'd use ax.plot(..) instead of plt.plot(). This can actually be found exactly in the source from the page you link to.
One thing that I found quite helpful to use to print all images :
_, axs = plt.subplots(n_row, n_col, figsize=(12, 12))
axs = axs.flatten()
for img, ax in zip(imgs, axs):
ax.imshow(img)
plt.show()
You are plotting all your images on one axis. What you want ist to get a handle for each axis individually and plot your images there. Like so:
fig = plt.figure()
ax1 = fig.add_subplot(2,2,1)
ax1.imshow(...)
ax2 = fig.add_subplot(2,2,2)
ax2.imshow(...)
ax3 = fig.add_subplot(2,2,3)
ax3.imshow(...)
ax4 = fig.add_subplot(2,2,4)
ax4.imshow(...)
For more info have a look here: http://matplotlib.org/examples/pylab_examples/subplots_demo.html
For complex layouts, you should consider using gridspec: http://matplotlib.org/users/gridspec.html
If the images are in an array and you want to iterate through each element and print it, you can write the code as follows:
plt.figure(figsize=(10,10)) # specifying the overall grid size
for i in range(25):
plt.subplot(5,5,i+1) # the number of images in the grid is 5*5 (25)
plt.imshow(the_array[i])
plt.show()
Also note that I used subplot and not subplots. They're both different
Below is a complete function show_image_list() that displays images side-by-side in a grid. You can invoke the function with different arguments.
Pass in a list of images, where each image is a Numpy array. It will create a grid with 2 columns by default. It will also infer if each image is color or grayscale.
list_images = [img, gradx, grady, mag_binary, dir_binary]
show_image_list(list_images, figsize=(10, 10))
Pass in a list of images, a list of titles for each image, and other arguments.
show_image_list(list_images=[img, gradx, grady, mag_binary, dir_binary],
list_titles=['original', 'gradx', 'grady', 'mag_binary', 'dir_binary'],
num_cols=3,
figsize=(20, 10),
grid=False,
title_fontsize=20)
Here's the code:
import matplotlib.pyplot as plt
import numpy as np
def img_is_color(img):
if len(img.shape) == 3:
# Check the color channels to see if they're all the same.
c1, c2, c3 = img[:, : , 0], img[:, :, 1], img[:, :, 2]
if (c1 == c2).all() and (c2 == c3).all():
return True
return False
def show_image_list(list_images, list_titles=None, list_cmaps=None, grid=True, num_cols=2, figsize=(20, 10), title_fontsize=30):
'''
Shows a grid of images, where each image is a Numpy array. The images can be either
RGB or grayscale.
Parameters:
----------
images: list
List of the images to be displayed.
list_titles: list or None
Optional list of titles to be shown for each image.
list_cmaps: list or None
Optional list of cmap values for each image. If None, then cmap will be
automatically inferred.
grid: boolean
If True, show a grid over each image
num_cols: int
Number of columns to show.
figsize: tuple of width, height
Value to be passed to pyplot.figure()
title_fontsize: int
Value to be passed to set_title().
'''
assert isinstance(list_images, list)
assert len(list_images) > 0
assert isinstance(list_images[0], np.ndarray)
if list_titles is not None:
assert isinstance(list_titles, list)
assert len(list_images) == len(list_titles), '%d imgs != %d titles' % (len(list_images), len(list_titles))
if list_cmaps is not None:
assert isinstance(list_cmaps, list)
assert len(list_images) == len(list_cmaps), '%d imgs != %d cmaps' % (len(list_images), len(list_cmaps))
num_images = len(list_images)
num_cols = min(num_images, num_cols)
num_rows = int(num_images / num_cols) + (1 if num_images % num_cols != 0 else 0)
# Create a grid of subplots.
fig, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
# Create list of axes for easy iteration.
if isinstance(axes, np.ndarray):
list_axes = list(axes.flat)
else:
list_axes = [axes]
for i in range(num_images):
img = list_images[i]
title = list_titles[i] if list_titles is not None else 'Image %d' % (i)
cmap = list_cmaps[i] if list_cmaps is not None else (None if img_is_color(img) else 'gray')
list_axes[i].imshow(img, cmap=cmap)
list_axes[i].set_title(title, fontsize=title_fontsize)
list_axes[i].grid(grid)
for i in range(num_images, len(list_axes)):
list_axes[i].set_visible(False)
fig.tight_layout()
_ = plt.show()
As per matplotlib's suggestion for image grids:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure(figsize=(4., 4.))
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(2, 2), # creates 2x2 grid of axes
axes_pad=0.1, # pad between axes in inch.
)
for ax, im in zip(grid, image_data):
# Iterating over the grid returns the Axes.
ax.imshow(im)
plt.show()
I end up at this url about once a week. For those who want a little function that just plots a grid of images without hassle, here we go:
import matplotlib.pyplot as plt
import numpy as np
def plot_image_grid(images, ncols=None, cmap='gray'):
'''Plot a grid of images'''
if not ncols:
factors = [i for i in range(1, len(images)+1) if len(images) % i == 0]
ncols = factors[len(factors) // 2] if len(factors) else len(images) // 4 + 1
nrows = int(len(images) / ncols) + int(len(images) % ncols)
imgs = [images[i] if len(images) > i else None for i in range(nrows * ncols)]
f, axes = plt.subplots(nrows, ncols, figsize=(3*ncols, 2*nrows))
axes = axes.flatten()[:len(imgs)]
for img, ax in zip(imgs, axes.flatten()):
if np.any(img):
if len(img.shape) > 2 and img.shape[2] == 1:
img = img.squeeze()
ax.imshow(img, cmap=cmap)
# make 16 images with 60 height, 80 width, 3 color channels
images = np.random.rand(16, 60, 80, 3)
# plot them
plot_image_grid(images)
Sample code to visualize one random image from the dataset
def get_random_image(num):
path=os.path.join("/content/gdrive/MyDrive/dataset/",images[num])
image=cv2.imread(path)
return image
Call the function
images=os.listdir("/content/gdrive/MyDrive/dataset")
random_num=random.randint(0, len(images))
img=get_random_image(random_num)
plt.figure(figsize=(8,8))
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
Display cluster of random images from the given dataset
#Making a figure containing 16 images
lst=random.sample(range(0,len(images)), 16)
plt.figure(figsize=(12,12))
for index,value in enumerate(lst):
img=get_random_image(value)
img_resized=cv2.resize(img,(400,400))
#print(path)
plt.subplot(4,4,index+1)
plt.imshow(img_resized)
plt.axis('off')
plt.tight_layout()
plt.subplots_adjust(wspace=0, hspace=0)
#plt.savefig(f"Images/{lst[0]}.png")
plt.show()
Plotting images present in a dataset
Here rand gives a random index value which is used to select a random image present in the dataset and labels has the integer representation for every image type and labels_dict is a dictionary holding key val information
fig,ax = plt.subplots(5,5,figsize = (15,15))
ax = ax.ravel()
for i in range(25):
rand = np.random.randint(0,len(image_dataset))
image = image_dataset[rand]
ax[i].imshow(image,cmap = 'gray')
ax[i].set_title(labels_dict[labels[rand]])
plt.show()

Categories