The image processing field has recently seen quite a growth due to advances in neural networks and other useful machine learning algorithms. Image processing involves the processing of images which can be a range of operations like smoothing images, detecting edges, image segmentation, image filtering, etc. The image generated after image processing can be a final image or it can be an image that will be fed to further algorithms. Many times, we might need to add shapes to images in order to prepare them for some other task like adding circles around faces for face detection, adding rectangles around objects for object detection, etc. Manually doing such tasks can be very cumbersome. It can fasten the process if we can automate such tasks.

Python **scikit-image** library provides us with sub-module name **draw** which can help us add shapes (circles, rectangle, ellipses, etc.) of different types to our image. It has methods that can handle both grayscale and RGB images. As a part of this tutorial, we'll explain various methods available through **draw** sub-module with simple and easy-to-understand examples. We'll be adding shapes to both grayscale and RGB images for explanation purposes.

Below is a list of shapes that we'll discuss as a part of this tutorial.

- Line
- Circle Perimeter
- Disk/Filled Circle
- Filled Ellipse
- Ellipse Perimeter
- Filled Rectangle/Square
- Rectangle/Square Perimeter
- Filled Polygon
- Polygon Perimeter
- Bezier Curve
- Polygon Mask
- Set Color Of Given Coords
- Random Shapes

We'll start by importing the necessary modules and images that we'll be using for explanation purposes.

In [1]:

```
import skimage
from skimage import data, draw, io
import matplotlib.pyplot as plt
import numpy as np
```

Below we have read colored (RGB) JPEG image from disk using **io** module of **skimage** and printed its dimensions as well as data types. The second colored (RGB) image is of a coffee cup available as a part of **data** module of **skimage**.

If you are interested in learning about how to perform basic operations with images (reading/writing images, resize, rescale, rotate, etc) using **skimage**t then please feel free to check our tutorial on the same. It tries to explain things with simple examples.

Please make a note that scikit-image treats all images as a numpy array of integers (0-255) or floats (0.0-1.0). It performs all operations on the array.

In [2]:

```
dr_kalam = io.imread("dr_apj_kalam.jpeg")
coffee = skimage.data.coffee()
print("DR Kalam Image Shape : {}, Data Type : {}".format(dr_kalam.shape, dr_kalam.dtype))
print("Coffee Image Shape : {}, Data Type : {}".format(coffee.shape, coffee.dtype))
```

Below we have created a simple function that takes as input two images and displays them next to each other. We'll be reusing this function for showing different images.

In [ ]:

```
def display_images(img1, img2, cmap=None):
fig, ax = plt.subplots(nrows=1, ncols=2)
fig.set_figwidth(8)
fig.set_figheight(8)
ax[0].imshow(img1, cmap=cmap);
ax[1].imshow(img2, cmap=cmap);
display_images(coffee, dr_kalam)
```

Below we have loaded two gray scale images from the internet for explanation purposes.

In [4]:

```
puppy = io.imread("greyscale_2.jpg", as_gray=True)
girl = io.imread("greyscale_1.jpeg", as_gray=True)
print("Puppy Image Shape : {} , Data Type : {}".format(puppy.shape, puppy.dtype))
print("Girl Image Shape : {}, Data Type : {}".format(girl.shape, girl.dtype))
```

In [ ]:

```
display_images(puppy, girl, cmap="gray")
```

If you want to follow the tutorial with your images then you are free to do so but you'll have to make changes to method inputs for drawing shapes based on your image size.

The first shape that we'll explain in a simple line. The **skimage.draw** module provides us with a method named **line()** which lets us add lines to our image.

**line(r0,c0,r1,c1)**- This method takes as input coordinates of starting and ending points as row and columns of an image. It then returns two lists where the first list has row entries and the second list has column entries consisting of a line. These two lists can be used to index the numpy array holding image data.

NOTE

Please make a note that the majority of methods inside of **skimage.draw** module works with input in the form of row and column of an image. The row and column start at the top-left end of the image. All methods working with images require us to think as if we are working with a numpy array. Normally we think of coordinates in terms of X and Y which start at the bottom-left corner and that will give us wrong results with these methods. The matplotlib also works with coordinates in terms of X and Y. If we want to use the output of methods with matplotlib then we'll have to consider columns as X-axis values and rows as Y-axis values. Please don't worry if you don't get concepts from the theoretical explanations. We have explained below this with examples to make things clear.

Below we have created a line using **line()** method which starts at (row-100,column-100) and ends at (row-1200,column-800). We'll be using matplotlib for plotting images. We have then created a copy of the girl’s grayscale image. We have then added the value of **1** in each row and column combination provided by **line()** method. We can do this by simply **indexing** girl image which is a numpy array. At last, we have plotted a new modified image. The value of **1**

In [6]:

```
row, col = draw.line(100, 100, 1200, 800)
row,col
```

Out[6]:

In [ ]:

```
fig, ax = plt.subplots(1,1)
girl_copy = girl.copy()
girl_copy[row,col] = 1
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn a line to an image by using matplotlib. Please pay **ATTENTION** to how we have used **column** values as **X** values and **row** values as **Y** values for drawing the same line using matplotlib. This is the main difference between the coordinate system in numpy array and matplotlib that we need to keep in mind when working with the scikit-image module.

In [ ]:

```
fig, ax = plt.subplots(1,1)
ax.imshow(girl, cmap="gray");
ax.plot(col, row, "--r");
```

Below we have explained another example demonstrating usage of **line()** method on **RGB** image. We are drawing a line from coordinates (100,50) to (200,200). We have then added a line to the copy of the original image using numpy indexing. At last, we have plotted the image for showing changes.

Please pay **ATTENTION** that as this is **RGB** image, we have assigned an array of 3 numbers to the image array when indexing. If we provide only a single value then the same value will be assigned to all 3 channels. We need to provide a proper color combination using 3 RGB values.

In [ ]:

```
row, col = draw.line(100, 50, 200, 200)
fig, ax = plt.subplots(1,1)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col] = [255,255,0]
ax.imshow(dr_kalam_copy);
```

Below we have explained how we can draw a line on image without modifying image and using matplotlib.

In [ ]:

```
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam);
ax.plot(col, row, "--y");
```

As a part of this section, we'll explain how we can draw a circle using **circle_perimeter()** method of **skimage.draw** module.

**circle_perimeter(r,c,radius)**- This method takes as input center coordinate of circle and radius of circle in pixels.It returns two arrays of rows and columns which have coordinates of circle perimeter.

Below we have created a circle of 10 pixels at location (500,500) in the girl image. We have as usual created a copy of the image and modified the value at returned coordinates. We have then plotted the image using matplotlib to verify changes.

In [ ]:

```
row, col = draw.circle_perimeter(500,500, 150)
fig, ax = plt.subplots(1, 1)
girl_copy = girl.copy()
girl_copy[row, col] = 1
ax.imshow(girl_copy, cmap="gray");
```

Below we have created a circle in **RGB** image of pixel 50 at coordinates (150,150).

In [ ]:

```
row, col = draw.circle_perimeter(150,150, 50)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we'll explain how we can create a filled circle on an image using **disk()** method of **skimage.draw** module.

**disk(center, radius)**- This method takes as input center of the disk (as a tuple) as a first parameter and the radius of the disk as the second parameter. It then returns two lists that have coordinates of all points that fall within the circle specified using center and radius.

Below we have created a filled circle of radius 100 pixels at the center (150,250). We have then as usual plotted a modified image to show a circle in an image.

In [ ]:

```
row, col = draw.disk((150,250), 100)
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have explained how to add a filled circle to an RGB image.

In [ ]:

```
row, col = draw.disk((150,150), 50)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we'll explain how to create an ellipse using **ellipse()** method of **skimage.draw** module.

**ellipse(r,c,r_radius,c_radius)**- This method takes as input four values. The first two values are coordinates of the center of the ellipse. The next two values are radius row-wise and column-wise width of the ellipse.

Below we have created an ellipse at the center (150,250) in the girl image with a height of 60 pixels and a width of 120 pixels.

In [ ]:

```
row, col = draw.ellipse(150,250, 60, 120)
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have created an ellipse at the center (150,150) with a width of 15 pixels and a height of 50 pixels in the RGB image.

In [ ]:

```
row, col = draw.ellipse(150,150, 50, 15)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

Below we have explained how we can create an ellipse perimeter using **ellipse_perimeter()** method of **skimage.draw** module.

**ellipse_perimeter(r,c,r_radius,c_radius)**- This method works exactly like**ellipse()**method with only difference that it returns coordinates of only perimeter of ellipse and not of points inside it.

Below we have drawn an ellipse perimeter in the girl grayscale image.

In [ ]:

```
row, col = draw.ellipse_perimeter(150,250, 60, 120)
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn an ellipse perimeter in an RGB image for explanation purposes.

In [ ]:

```
row, col = draw.ellipse_perimeter(150,150, 50, 15)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we have explained how we can create a filled rectangle/square using **rectangle()** method of **skimage.draw** module.

**rectangle(start, end)**- This method takes as input two tuples where the first tuple is the start point of the rectangle and the second tuple is an endpoint of the rectangle. We can use the same method to create a square as well. Like all other**draw**module methods, it also returns two lists that have coordinates of a square.

Below we have drawn a rectangle between points (150,150) and (250,300) in our grayscale image.

In [ ]:

```
row, col = draw.rectangle(start=(150,150), end=(250, 300))
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn a rectangle between points (150,150) and (175,200) in our RGB image.

In [ ]:

```
row, col = draw.rectangle(start=(150,150), end=(175, 200))
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we have explained how we can create a rectangle perimeter using **rectangle_perimeter()** method of **skimage.draw** module.

**rectangle_perimeter(start, end)**- This method works exactly like**rectangle()**method with the only difference that it returns coordinates of the perimeter of the rectangle and not of points inside of it.

Below we have drawn a rectangle perimeter between coordinates (150,150) and (250,300) in our grayscale image.

In [ ]:

```
row, col = draw.rectangle_perimeter(start=(150,150), end=(250, 300))
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

In [ ]:

```
fig, ax = plt.subplots(1,1)
ax.imshow(girl, cmap="gray");
ax.plot(col, row, "--y");
```

Below we have drawn a rectangle perimeter between coordinates (150,150) and (175,200) in our RGB image.

In [ ]:

```
row, col = draw.rectangle_perimeter(start=(150,150), end=(175, 200))
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

In [ ]:

```
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam);
ax.plot(col, row, "--y");
```

As a part of this section, we have explained how we can create a polygon using **polygon()** method of **skimage.draw** module.

**polygon(r,c)**- This method accepts two lists where the first list is row values and the second list is column values specifying coordinates of a polygon that we want to draw.

Below we have drawn a triangle in our grayscale image using **polygon()** method.

In [ ]:

```
row, col = draw.polygon((100,200,800), (100,700,400))
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn a polygon with 4 points in our gray scale image.

In [ ]:

```
row, col = draw.polygon((100,200,800, 800), (100,700,400, 200))
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn a triangle in our RGB image.

In [ ]:

```
row, col = draw.polygon((100,150, 230), (100,130, 110))
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

Below we have drawn a polygon with 4 points in our RGB image.

In [ ]:

```
row, col = draw.polygon((100,150, 230, 230), (100,130, 110, 50))
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we have explained how we can draw polygon perimeter using **polygon_perimeter()** method of **skimage.draw** module.

**polygon_perimeter(r,c)**- This method works exactly like**polygon()**method with the only difference that it returns coordinates of the perimeter of the polygon and not of points inside of it.

Below we have explained the usage of the method on our grayscale and RGB image.

In [ ]:

```
row, col = draw.polygon_perimeter((100,200,800), (100,700,400))
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

In [ ]:

```
row, col = draw.polygon_perimeter((100,150, 230, 230), (100,130, 110, 50))
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we have explained how we can create a bezier curve shape on an image using **bezier_curve()** method of **skimage.draw** module. The bezier curves are used in the computer graphics field to create curves that appear smooth at almost all scales.

**bezier_curve(r0,c0,r1,c1,r2,c2,weight)**- This method takes as input six parameter and returns coordinates of bezier curve created using them.- The first two parameters (r0,c0) specifies the coordinates of the first control point of the curve.
- The next two parameters (r1,c1) specifies the coordinates of the middle control point of the curve.
- The next two parameters (r2,c2) specifies the coordinates of the last control point of the curve.
- The last
**weight**parameter accepts float value specifying middle control point weight. It's used to describe line tension.

Below we have drawn a bezier curve on a grayscale image for explanation purposes.

In [ ]:

```
row, col = draw.bezier_curve(100,800, 250,200, 500, 800, 2)
girl_copy = girl.copy()
girl_copy[row, col] = 1
fig, ax = plt.subplots(1,1)
ax.imshow(girl_copy, cmap="gray");
```

Below we have drawn a bezier curve on an RGB image for explanation purposes.

In [ ]:

```
row, col = draw.bezier_curve(50,175, 175,50, 200, 175, 2)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy[row, col, :] = [255, 255, 0] ## Yellow Color
fig, ax = plt.subplots(1,1)
ax.imshow(dr_kalam_copy);
```

As a part of this section, we'll explain how we can create a mask from polygon using **polygon2mask()** method of **skimage.draw** module.

**polygon2mask(image_shape,polygon)**- This method takes as input two parameters. The first parameter is an actual image shape and the second parameter is a list of coordinates specifying polygon. It then returns a numpy array of the same size as the image shape with values for polygon filled in.

Below we have explained how we can create a mask using **polygon2mask()** method. We have plotted the original grayscale image and mask next to each other. Then we have modified the image to add a mask to the image and plotted it as well in the next code cell.

In [ ]:

```
girl_mask = draw.polygon2mask(girl.shape, [(100,100),(200,700),(800,400), (800,200)])
fig, ax = plt.subplots(1,2)
ax[0].imshow(girl, cmap="gray");
ax[1].imshow(girl_mask, cmap="gray");
```

In [ ]:

```
girl_copy = girl.copy()
girl_copy[girl_mask] = 0
plt.imshow(girl_copy, cmap="gray");
```

As a part of this section, we have explained how we can change the color of the list of coordinates of an image using **set_color()** method of **skimage.draw** module.

**set_color(image,coords,color,alpha=1)**- This method takes as input 3 parameter and modifies input image based on given coordinates and color.- The first parameter is the actual image whose specified coordinate's color we want to change.
- The second input is a tuple of length 2 where the first value is a list of row values and the second value is a list of column values specifying coordinates that we want to modify.
- The third parameter is color value.

Below we have first created a bezier curve and stored its points in a variable. We have then given copied image, bezier curve coordinates, and color value of **1** (white) to **set_color()** method. The **set_color()** method will modify the color of coordinates specified by the bezier curve.

In [ ]:

```
bezier_curve = draw.bezier_curve(100,800, 250,200, 500, 800, 2)
girl_copy = girl.copy()
draw.set_color(girl_copy, bezier_curve, 1)
plt.imshow(girl_copy, cmap="gray");
```

Below we have drawn a polygon on an image using **set_color()** method.

In [ ]:

```
polygon = draw.polygon((100,200,800, 800), (100,700,400, 200))
girl_copy = girl.copy()
draw.set_color(girl_copy, polygon, 0)
plt.imshow(girl_copy, cmap="gray");
```

Below we have explained the usage of **set_color()** on an RGB image.

In [ ]:

```
bezier_curve = draw.bezier_curve(50,175, 175,50, 200, 175, 2)
dr_kalam_copy = dr_kalam.copy()
draw.set_color(dr_kalam_copy, bezier_curve, [0,255,0])
plt.imshow(dr_kalam_copy);
```

In [ ]:

```
polygon = draw.polygon((100,150, 230, 230), (100,130, 110, 50))
dr_kalam_copy = dr_kalam.copy()
draw.set_color(dr_kalam_copy, polygon, [0,255,0])
plt.imshow(dr_kalam_copy);
```

As a part of this section, we'll explain how we can add random shapes with random sizes, random locations, and random color to our image using **random_shapes()** method of **skimage.draw** module.

**random_shapes(image_shape,max_shapes,min_shapes=1,min_size=2,max_size=None,multichannel=True,shape=None,allow_overlap=False,random_seed=None)**- This method takes requires image shape and max shapes that we want to draw as input. It returns an image with shapes drawn into it based on image shape and labels for each shape included. The labels are a list of tuples where the first value is the label name and the second value is the coordinates of the bounding box surrounding the shape.- The
**max_shapes**parameter accepts an integer and specifies a maximum number of shapes to include. - The
**min_shapes**parameter accepts an integer and specifies the minimum number of shapes to include. The default is 1 shape at least. - The
**min_size**accepts an integer and specifies the minimum size of each shape. - The
**max_size**accepts an integer and specifies the maximum size of each shape. - The
**multichannel**parameter accepts a boolean value. If set to True then it returns shape in RGB image else in a grayscale image. - The
**shape**parameter accepts string values specifying shape type. The valid values are rectangle, circle, triangle, ellipse, and None. The default is set to**None**hence shape type is decided by a method. - The
**allow_overlap**method accepts boolean values specifying whether to allow shapes to overlap or not. The default is**False**which prevents overlap. - The
**random_seed**parameter accepts integer value specifying seed for randomness. It can be used for reproducibility. If we specify this parameter then it'll generate the same shapes at the same location each time we call the method.

- The

Below we have generated 3 random shapes for our grayscale image. We have then included logic to include those shapes in our image. We have then plotted images with shapes and shape image returned by a method.

In [ ]:

```
girl_random, labels = draw.random_shapes(girl.shape,
max_shapes=3,
min_size=5,
multichannel=False,
random_seed=1234)
girl_random = skimage.img_as_float(girl_random)
girl_copy = girl.copy()
girl_copy = np.where(girl_random == 1.0, girl_copy, girl_random)
fig,ax = plt.subplots(1,2)
ax[0].imshow(girl_copy, cmap="gray");
ax[1].imshow(girl_random, cmap="gray");
```

Below we have printed labels returned by a method.

In [40]:

```
labels
```

Out[40]:

Below we have explained the usage of a method to generate shapes on an RGB image. The shapes are not overlapping in this image.

In [ ]:

```
dr_kalam_random, labels = draw.random_shapes(dr_kalam.shape, max_shapes=3, min_size=5,
multichannel=True,
random_seed=1234)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy = np.where(dr_kalam_random==255, dr_kalam_copy, dr_kalam_random)
fig,ax = plt.subplots(1,2)
ax[0].imshow(dr_kalam_copy);
ax[1].imshow(dr_kalam_random);
```

In [42]:

```
labels
```

Out[42]:

Below we have explained another example specifying the usage of the method to create random shapes. We have included overlapping shapes this time.

In [ ]:

```
dr_kalam_random, labels = draw.random_shapes(dr_kalam.shape, max_shapes=3, min_size=5,
multichannel=True,
allow_overlap=True,
random_seed=1234)
dr_kalam_copy = dr_kalam.copy()
dr_kalam_copy = np.where(dr_kalam_random==255, dr_kalam_copy, dr_kalam_random)
fig,ax = plt.subplots(1,2)
ax[0].imshow(dr_kalam_copy);
ax[1].imshow(dr_kalam_random);
```

This ends our small tutorial explaining how we can use **draw** module of **skimage** library to draw shapes of various types and sizes in images. Please feel free to let us know your views in the comments section.

Sunny Solanki

Numba @vectorize Decorator: Convert Scaler Function to Universal Function (ufunc)

Dask DataFrames: Simple Guide to Work with Large Tabular Datasets

JAX - (Numpy + Automatic Gradients) on Accelerators (GPUs/TPUs)

Simple Guide to Style Display of Pandas DataFrames