draw grid lines over an image in matplotlib - matplotlib

How can I draw regular grid lines over a tiff image?
I want to draw regular square grids for each interval (say 100 by 100 pixels) over the image and save that with the drawings. I also need to overlay each grid id as '1','2',...at the middle of each grid box.

You will need the python imaging library (PIL) installed. (See here https://pypi.python.org/pypi/PIL). See these answers for examples of ways to install PIL: answer 1, answer 2
Right, with that installed, the following code should do what you ask for:
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
try:
from PIL import Image
except ImportError:
import Image
# Open image file
image = Image.open('myImage.tiff')
my_dpi=300.
# Set up figure
fig=plt.figure(figsize=(float(image.size[0])/my_dpi,float(image.size[1])/my_dpi),dpi=my_dpi)
ax=fig.add_subplot(111)
# Remove whitespace from around the image
fig.subplots_adjust(left=0,right=1,bottom=0,top=1)
# Set the gridding interval: here we use the major tick interval
myInterval=100.
loc = plticker.MultipleLocator(base=myInterval)
ax.xaxis.set_major_locator(loc)
ax.yaxis.set_major_locator(loc)
# Add the grid
ax.grid(which='major', axis='both', linestyle='-')
# Add the image
ax.imshow(image)
# Find number of gridsquares in x and y direction
nx=abs(int(float(ax.get_xlim()[1]-ax.get_xlim()[0])/float(myInterval)))
ny=abs(int(float(ax.get_ylim()[1]-ax.get_ylim()[0])/float(myInterval)))
# Add some labels to the gridsquares
for j in range(ny):
y=myInterval/2+j*myInterval
for i in range(nx):
x=myInterval/2.+float(i)*myInterval
ax.text(x,y,'{:d}'.format(i+j*nx),color='w',ha='center',va='center')
# Save the figure
fig.savefig('myImageGrid.tiff',dpi=my_dpi)
Which, if used on the grace_hopper.png example file, produces the following output:

This can be done effectively in two lines by looping over the image data at your grid intervals. Using the canonical image from the SIPI database as an example
import pylab as plt
# Load the image
img = plt.imread("lena512color.tiff")
# Grid lines at these intervals (in pixels)
# dx and dy can be different
dx, dy = 100,100
# Custom (rgb) grid color
grid_color = [0,0,0]
# Modify the image to include the grid
img[:,::dy,:] = grid_color
img[::dx,:,:] = grid_color
# Show the result
plt.imshow(img)
plt.show()
The answer by #tom may be more robust as it works with the matplotlib library. I'll leave this example up for its simplicity.

Let me just leave it here
def draw_grid(image, line_space=20):
H, W = image.shape
image[0:H:line_space] = 1
image[:, 0:W:line_space] = 1

Related

Using a subfigure as textbody fails on tight layout with a lot of text

Using a subfigure as textbody fails on tight layout with a lot of text. As seen in the provided example, the bound of a figure are overreached by one subfigure, as if the text was not wrapped.
import pandas as pd
from matplotlib import pyplot as plt
# Paramters for A4 Paper
fullheight = 11.69
fullwidth = 8.27
# how the subfigures dive the space
fig0_factor = 0.7
fig1_factor = 0.3
# generate the figure
fig = plt.figure(constrained_layout = True, figsize=(fullwidth, fullheight)) #
# generate 2 subfigures
subfigs = fig.subfigures(2,height_ratios=[fig0_factor,fig1_factor])
# fill the fist subfigure
axes = subfigs[0].subplots(nrows=1)
# some plot
ax = plt.plot([0,0],[-1,1], lw=1, c='b', figure=subfigs[0])
# some text
subfigs[0].text(0.55, 0.00001, 'Figure 1', ha='center', va='center', rotation='horizontal',weight='bold')
# fill the second subfigure
text_ax = subfigs[1].subplots(1)
# make its background transparent
subfigs[1].patch.set_alpha(0.0)
# remove the axis, not removing it makes no difference regarding the problem
text_ax.set_axis_off()
# generate some text
message = ['word']*50 # 50 is enough to make the problem visable, my usecase has a way longer text
message = ' '.join(message)
# fill in the Text and wrap it
text_ax.text(0.00001, 0.8, message, horizontalalignment='left', verticalalignment='top',size=7, wrap=True)
# call tight layout
# this is neccecary for the whole plot, that is not shown for simplicity reasons
# explaination: subfigure one would be an array of subplots with different scales and descriptions,
# ever changing depending on the data that is to be plotted, so tight_layout mittigates
# a lot of tideos formatting
fig.tight_layout()
Please notice the figures right edge reaches for the whole panel, as seen in the second image
# omitting the wrap, it is clear why the figure is out of bound: somehow the layout gets its information from the unwrapped text
text_ax.text(0.00001, 0.8, message, horizontalalignment='left', verticalalignment='top',size=7, wrap=False)
Is there another way to have the text rendered in the subplot with it correctly noticing the wrapped bounds instead of the unwrapped bounds?
If you modify your code above to remove fig.tight_layout() and to take the text out of the layout then everything works as expected.
# fill in the Text and wrap it
thetext = text_ax.text(0.00001, 0.8, message, horizontalalignment='left',
verticalalignment='top',size=7, wrap=True)
thetext.set_in_layout(False)
There should be nothing that tight_layout does that layout='constrained' cannot do, and constrained layout is much more flexible.
BTW, you might consider subfigs[0].supxlabel() instead of the text command for "Figure 1"

How to apply random rotation to multiple images with the same rotation angle

I am trying to do some preprocessing to my input. I have three input images (X1, X2, X3). I want to augment the data and apply tf.keras.layers.experimental.preprocessing.RandomRotation() on every element (X1, X2, X3). I am hoping all the images in the same element are rotated with the same angle. How can I do that?
A related post: How to apply random image geometric transformation simultaneously to multiple images?
Any other solutions besides calling tf.stack() and tf.split()?
try this
import scipy.misc
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
img1_path=r'c:\temp\people\test\savory\001.jpg' # path to first image
img2_path=r'c:\temp\people\test\savory\002.jpg' # path to second image
img3_path=r'c:\temp\people\test\savory\003.jpg' # path to third image
path_list=[img1_path, img2_path, img3_path] # list of image paths
degree=np.random.random() * 360 # get random angle between 0 to 360 degrees
print (' rotation angle in counter clockwise direction = ', degree)
plt.figure(figsize=(12,12))
rotated_img_list=[] # create empty list to store rotated images
for i,path in enumerate(path_list):
img= plt.imread(path) # read in the image
rotated_img = ndimage.rotate(img, degree) # rotate the image counter clockwise by degree
rotated_img_list.append(rotated_img) # appended rotated image to the list
plt.subplot(1, 3, i + 1)
plt.imshow(rotated_img) # show the ith rotated image
name=str(i) #label the image
plt.title(name, color='white', fontsize=16)
plt.axis('off')
plt.show()
In your case you probably have the images in a list so don't both with reading them in. I showed an example for reading in and rotating 3 different images by the same random angle and displayed the results.

Tensorflow how to sample large number of textures from small dataset of large images

I have 100 large-ish (1000x1000) images which I want to use as a training data set for a texture analysis system. I want to randomly generate texture swatches of about 200x200. What is the best way to do this? I would prefer to not preprocess all of the swatches so that each epoch is trained with slightly different swatches.
My initial (naive?) implementation included preprocessing layers in the model that do random crops on the image and just do a ton of epochs to accommodate the small number of large pictures, however after about ~400 epochs TF would crash without exception (it would just exit).
I now find myself coding a data generator (tf.keras.utils.Sequence) that will return a batch of swatches on request, but I feel like I'm reinventing the wheel and it is getting clunky - making me think this can't be the best way.
What is the best way to handle such a situation where you have a somewhat small dataset that you dynamically create more samples from?
I have written a function that will segment an image. Code is below
import cv2
def image_segment( image_path, img_resize, crop_size):
image_list=[]
img=cv2.imread(image_path)
img=cv2.resize(img, img_resize)
shape=img.shape
xsteps =int( shape[0]/crop_size[0])
ysteps = int( shape[1]/crop_size[1])
print (xsteps, ysteps)
for i in range (xsteps):
for j in range (ysteps):
x= i * crop_size[0]
xend=x + crop_size[0]
y= j * crop_size[1]
yend = y + crop_size[1]
cropped_image = cropped_image=img[x: xend, y: yend]
image_list.append(cropped_image)
return image_list
below is an example of use
# This code provides input to the image_segment function
image_path=r'c:\temp\landscape.jpg' # location of image
width=1000 # width to resize input image
height= 800 # height to resize input image
image_resize=( width, height) # specify original image (width, height)
crop_width=200 # width of desired cropped images
crop_height=400 # height of desired cropped images
# Note to get full set of cropped images width/cropped_width and height/cropped_height should be integer values
crop_size=(crop_height, crop_width)
images=image_segment(image_path, image_resize, crop_size) # call the function
The code below will display the resized input image and the resultant cropped images
# this code will display the resized input image and the resultant cropped images
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
img=cv2.imread(image_path) # read in the image
input_resized_image=cv2.resize(img, image_resize) # resize the image
imshow(input_resized_image) # show the resized input image
r=len(images)
plt.figure(figsize=(20, 20))
for i in range(r):
plt.subplot(5, 5, i + 1)
image=images # scale images between 0 and 1 becaue pre-processor set them between -1 and +1
plt.imshow(image[i])
class_name=str(i)
plt.title(class_name, color='green', fontsize=16)
plt.axis('off')
plt.show()

Geopandas reduce legend size (and remove white space below map)

I would like to know how to change the legend automatically generated by Geopandas. Mostly I would like to reduce its size because it's quite big on the generated image. The legend seems to take all the available space.
Additional question, do you know how to remove the empty space below my map ? I've tried with
pad_inches = 0, bbox_inches='tight'
but I still have an empty space below the map.
Thanks for your help.
This works for me:
some_geodataframe.plot(..., legend=True, legend_kwds={'shrink': 0.3})
Other options here: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.colorbar.html
To show how to get proper size of a colorbar legend accompanying a map created by geopandas' plot() method I use the built-in 'naturalearth_lowres' dataset.
The working code is as follows.
import matplotlib.pyplot as plt
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world = world[(world.name != "Antarctica") & (world.name != "Fr. S. Antarctic Lands")] # exclude 2 no-man lands
plot as usual, grab the axes 'ax' returned by the plot
colormap = "copper_r" # add _r to reverse the colormap
ax = world.plot(column='pop_est', cmap=colormap, \
figsize=[12,9], \
vmin=min(world.pop_est), vmax=max(world.pop_est))
map marginal/face deco
ax.set_title('World Population')
ax.grid()
colorbar will be created by ...
fig = ax.get_figure()
# add colorbar axes to the figure
# here, need trial-and-error to get [l,b,w,h] right
# l:left, b:bottom, w:width, h:height; in normalized unit (0-1)
cbax = fig.add_axes([0.95, 0.3, 0.03, 0.39])
cbax.set_title('Population')
sm = plt.cm.ScalarMappable(cmap=colormap, \
norm=plt.Normalize(vmin=min(world.pop_est), vmax=max(world.pop_est)))
at this stage, 'cbax' is just a blank axes, with un needed labels on x and y axes blank-out the array of the scalar mappable 'sm'
sm._A = []
draw colorbar into 'cbax'
fig.colorbar(sm, cax=cbax, format="%d")
# dont use: plt.tight_layout()
plt.show()
Read the comments in the code for useful info.
The resulting plot:

Graphing matplotlib with Python code in a R Markdown document

Is it possible to use Python matplotlib code to draw graph in RStudio?
e.g. below Python matplotlib code:
import numpy as np
import matplotlib.pyplot as plt
n = 256
X = np.linspace(-np.pi,np.pi,n,endpoint=True)
Y = np.sin(2*X)
plt.plot (X, Y+1, color='blue', alpha=1.00)
plt.plot (X, Y-1, color='blue', alpha=1.00)
plt.show()
Output graph will be:
Then I need to write a R Markdown to include these code and generate graph automatically after knitting the markdown.
install.packages('devtools') first, get install_github function
install_github("rstudio/reticulate") install the dev version of reticulate
in r markdown doc, use code below to enable the function.
```{r setup, include=FALSE}
library(knitr)
library(reticulate)
knitr::knit_engines$set(python = reticulate::eng_python)
```
Try it , you will get what you want and don't need to save any image.
One possible solution is save the plot as a image, then load the file to markdown.
### Call python code sample
```{r,engine='python'}
import numpy as np
import matplotlib.pyplot as plt
n = 256
X = np.linspace(-np.pi,np.pi,n,endpoint=True)
Y = np.sin(2*X)
fig, ax = plt.subplots( nrows=1, ncols=1 )
ax.plot (X, Y+1, color='blue', alpha=1.00)
ax.plot (X, Y-1, color='blue', alpha=1.00)
#plt.show()
fig.savefig('foo.png', bbox_inches='tight')
print "finished"
```
Output image:
![output](foo.png)
#### The End
Output:
You can do that with reticulate, but most time in trying to follow a tutorial in doing that you may encounter some technicalities that weren't sufficiently explained.
My answer is a little late but I hope it's a thorough walkthrough of doing it the right way - not rendering it and then loading it as a png but have the python code executed more "natively".
Step 1: Configure Python from RStudio
You want to insert an R chunk, and run the following code to configure the path to the version of Python you want to use. The default python that comes shipped with most OS is usually the outdated python 2 and is not where you install your packages. That is the reason why it's important to do this, to make sure Rstudio will use the specified python instance where your matplotlib library (and the other libraries you will be using for that project) can be found:
library(reticulate)
# change the following to point to the desired path on your system
use_python('/Users/Samuel/anaconda3/bin/python')
# prints the python configuration
py_config()
You should expect to see that your session is configured with the settings you specified:
python: /Users/Samuel/anaconda3/bin/python
libpython: /Users/Samuel/anaconda3/lib/libpython3.6m.dylib
pythonhome: /Users/Samuel/anaconda3:/Users/Samuel/anaconda3
version: 3.6.3 |Anaconda custom (64-bit)| (default, Oct 6 2017, 12:04:38) [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
numpy: /Users/Samuel/anaconda3/lib/python3.6/site-packages/numpy
numpy_version: 1.15.2
python versions found:
/Users/Samuel/anaconda3/bin/python
/usr/bin/python
/usr/local/bin/python
/usr/local/bin/python3
/Users/Samuel/.virtualenvs/r-tensorflow/bin/python
Step 2: The familiar plt.show
Add a Python chunk (not R!) in your R Markdown document (see attached screenshot) and you can now write native Python code. This means that the familiar plt.show() and plt.imshow() will work without any extra work. It will be rendered and can be compiled into HTML / PDF using knitr.
This will work:
plt.imshow(my_image, cmap='gray')
Or a more elaborated example:
import numpy as np
import matplotlib.pyplot as plt
import os
import cv2
DATADIR = '/Users/Samuel/Datasets/PetImages'
CATEGORIES = ['Dog', 'Cat']
for category in CATEGORIES:
path = os.path.join(DATADIR, category) # path to cat or dog dir
for img in os.listdir(path):
img_array = cv2.imread(os.path.join(path,img), cv2.IMREAD_GRAYSCALE)
plt.imshow(img_array, cmap='gray')
plt.show()
break
break
Output:
Step 3: Knit to HTML / PDF / Word etc
Proceed to knit as usual. The end product is a beautifully formatted document done in Python code using R Markdown. RStudio has come a long way and I'm surprised the level of support it has for Python code isn't more known so hoping anyone that stumbled upon this answer will find it informative and learned something new.
I have been working with reticulate and R Markdown and you should specify your virtual environment. For example my R Markdown starts as follows:
{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, cache.lazy = FALSE)
library(reticulate)
use_condaenv('pytorch') ## yes, you can run pytorch and tensor flow too
Then you can work in either language. So, for plotting with matplotlib, I have found that you need the PyQt5 module to make it all run smoothly. The following makes a nice plot inside R Markdown - it's a separate chunk.
{python plot}
import PyQt5
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
data = pd.read_csv('Subscriptions.csv',index_col='Date', parse_dates=True)
# make the nice plot
# set the figure size
fig = plt.figure(figsize = (15,10))
# the series
ax1 = fig.add_subplot(211)
ax1.plot(data.index.values, data.Opens, color = 'green', label = 'Opens')
# plot the legend for the first plot
ax1.legend(loc = 'upper right', fontsize = 14)
plt.ylabel('Opens', fontsize=16)
# Hide the top x axis
ax1.axes.get_xaxis().set_visible(False)
####### NOW PLOT THE OTHER SERIES ON A SINGLE PLOT
# plot 212 is the MI series
# plot series
ax2 = fig.add_subplot(212)
ax2.plot(data.index.values, data.Joiners, color = 'orange', label = 'Joiners')
# plot the legend for the second plot
ax2.legend(loc = 'upper right', fontsize = 14)
# set the fontsize for the bottom plot
plt.ylabel('Joiners', fontsize=16)
plt.tight_layout()
plt.show()
You get the following from this:
I don't have the reputation points to add a comment, but Bryan's answer above was the only one to work for me. Adding plt.tight_layout() made the difference. I added that line to the following simple code and the plot displayed.
{python evaluate}
plt.scatter(X_train, y_train, color = 'gray')
plt.plot(X_train, regresssion_model_sklearn.predict(X_train), color = 'red')
plt.ylabel('Salary')
plt.xlabel('Number of Years of Experience')
plt.title('Salary vs. Years of Experience')
plt.tight_layout()
plt.show()