Mad_plot_lib is a simple wrapper around matplotlib's pyplot, which I wrote for my own needs. Matplotlib is a great library, but I don't find it very pleasant to work with in terms of its API. It would be difficult to design a more user-friendly API for such a powerful library, but this wrapper is tailor-made and supports a basic subset of pyplot's functions. However, thanks to its permissive license and "header-only-ness", it can be easily incorporated into any project and modified to suit specific needs.
If you don't use advanced matplotlib features and just want to display a window with a series of images and/or graphs without spending too much time on this seemingly simple task, then mad_plot_lib might be for you.
Suppose we have a grayscale image, img_orig, and its blurred variant, img_edit, and two functions calc_histogram & calc_fft2d. We want to show a window with two rows: one for the original image and other for the edited image. Each row has 3 columns (let's call them "cells"): the image itself, its histogram, and its FFT spectrum with colorbar. We also want only histogram cells to have axis enabled, specific cmap for the image cells, the whole window should have a clearly visible heading, and we want to set a specific size for the window. This is how it should look like.
This is probably the standard pyplot approach to achieve that:
n_rows, n_cols = 2, 3
plt.figure(figsize=(12, 7))
plt.suptitle("cv2.blur(5,5)", fontsize=16, fontweight="bold")
for row_n, image in enumerate([img_orig, img_edit]):
plt.subplot(n_rows, n_cols, 1 + row_n * 3)
plt.imshow(image, cmap="gray")
plt.axis("off")
plt.subplot(n_rows, n_cols, 2 + row_n * 3)
plt.plot(calc_histogram(image))
plt.subplot(n_rows, n_cols, 3 + row_n * 3)
plt.imshow(calc_fft2d(image), cmap="jet")
plt.axis("off")
plt.colorbar()ββ¬Here, pyplot behaves like a state machine. This approach has its pros and cons, but is definitely verbose. For specifying cell number, we need a simple formula or a helper variable. We have to index a 2D grid of cells with one number starting from 1.
Another option is to use the "fig axs approach":
fig, axs = plt.subplots(2, 3, figsize=(12, 7))
fig.suptitle("cv2.blur(5,5)", fontsize=16, fontweight="bold")
for row_n, image in enumerate([img_orig, img_edit]):
axs[row_n, 0].imshow(image, cmap="gray")
axs[row_n, 0].axis("off")
axs[row_n, 1].plot(calc_histogram(image))
im = axs[row_n, 2].imshow(calc_fft2d(image), cmap="jet")
fig.colorbar(im, ax=axs[row_n, 2])
axs[row_n, 2].axis("off")ββ¬This approach behaves less like a state machine and is shorter. We can index 2D grid with two numbers starting from 0. Sadly I find it harder to work with and memorize. Some functions are different than the first approach, like what's up with the colorbar? I understand that this allows for more robust colorbar placement options, but when I just want to add a colorbar next to the corresponding image and move on, this is cumbersome.
Here's how it's done in mad_plot_lib:
mdpl_draw(plt, 2, 3, "cv2.blur(5,5)", (12, 7), [
[
MDPLCell(MDPLType.IMSHOW_GRAY, image, cmap="gray", hide_axes=True),
MDPLCell(MDPLType.PLOT, calc_histogram(image)),
MDPLCell(MDPLType.IMSHOW_GRAY, calc_fft2d(image), cmap="jet", show_colorbar=True, hide_axes=True)
]
for image in [img_orig, img_edit]
])ββ¬We use mdpl_draw function, which represents drawing of one whole window. Main star of the show is its last parameter, which takes a collection of collections: each inner collection represents one row, and each item in that collection represents one cell in that row. MDPLCell constructor sets everything for the particular cell: type of the cell (MDPLType enum) and its source are the only mandatory arguments, everything else can be set with optional keyword arguments.
This wrapper is very simple. We can't do any "rowspan" or "colspan", only a regular grid is supported. We can't set font size on heading, it's always going to be 16 bold. And so on. As already mentioned, this wrapper is useful in situations where we simply want to plot a few graphs and move on.
Download mad_plot_lib.py and add it to your project structure.
Import it.
Use it.
There are two ways of importing:
from matplotlib import pyplot as plt
from mad_plot_lib import mdpl_draw, MDPLType, MDPLCell
mdpl_draw(plt, 1, 1, None, None, [[MDPLCell(MDPLType.IMSHOW_BGR, image)]])or
from matplotlib import pyplot as plt
import mad_plot_lib as mdpl
mdpl.mdpl_draw(plt, 1, 1, None, None, [[mdpl.MDPLCell(mdpl.MDPLType.IMSHOW_BGR, image)]])-
Jump to:
-
Terminology:
- Cell represents a specific image/graph that is shown in a window.
- In this context, by window we mean a window displayed by the OS, that contains cells arranged in a regular grid. Each call of
mdpl_drawcreates its own window, similiar toplt.figure. - Row represents one line of cells in a given window. Each window must have at least one row, and each row must have at least one cell. The number of cells in each row of a given window is the same.
-
Module definitions:
mdpl_draw: function that displays a window with given cellsMDPLCell: class, whose constructor is used to specify a particular cellMDPLType: enum for determining cell type
mdpl_draw is a function for displaying a window with given cells. Its parameters are:
plt : # matplotlib.pyplot interface
n_rows : int
n_cols : int
figname : Optional[str]
figsize : Optional[Tuple[int, int]]
cells : List[List[MDPLCell]]pltβ matplotlib.pyplot interface that will be used to display a windown_rowsβ how many rows will the window have, positive integern_colsβ how many cells will each row have, positive integerfignameβ window header, can be set toNonefor no headerfigsizeβ explicit window size, tuple of two positive integers:(width, height), can be set toNonefor implicit sizecellsβ collection of collections: each inner collection represents one row, and each item in that collection represents one cell in that row viaMDPLCell
Examples (MDPLCell arguments are omitted for now):
# Window with one row and three columns, custom header, implicit window size
mdpl_draw(plt, 1, 3, "Window header", None, [
[
MDPLCell(...), # Row 1 Col 1
MDPLCell(...), # Row 1 Col 2
MDPLCell(...) # Row 1 Col 3
]
])
# Window with three rows and one column, no header, explicit window size
mdpl_draw(plt, 3, 1, None, (12, 7), [
[
MDPLCell(...) # Row 1 Col 1
],
[
MDPLCell(...) # Row 2 Col 1
],
[
MDPLCell(...) # Row 3 Col 1
]
])MDPLCell takes two mandatory arguments, where the first determines the type of the given cell, and the second contains the data to be rendered. For example, if we want to draw an RGB image stored in a variable image, we use MDPLCell(MDPLType.IMSHOW_RGB, image). Here is an overview of all available cell types:
MDPLType |
description | expected second argument of MDPLCell |
|---|---|---|
IMSHOW_BGR |
BGR image | array with shape (M, N, 3) |
IMSHOW_RGB |
RGB image | array with shape (M, N, 3) |
IMSHOW_GRAY |
grayscale image | array with shape (M, N) |
HISTOGRAM |
histogram of grayscale image | array with shape (M, N) |
PLOT |
2D line plot of Y versus X |
either tuple (X, Y) or just Y |
PLOT_3D_GRAY |
3D plot of grayscale image | array with shape (M, N) |
# kwarg expected type default val description
# ===== ============= =========== ===========
cmap : str : None # Colormap name for grayscale images.
do_dynamic_text : bool : True # Scale the text from `texts` when user resizes the window?
hide_axes : bool : False # Hide cell axes?
lines_horizontal : List[int | float | dict] : [] # Horizontal lines to be drawn on a cell.
lines_vertical : List[int | float | dict] : [] # Vertical lines to be drawn on a cell.
patches : List[matplotlib.patches] : [] # Matplotlib patches to be drawn on a cell.
show_colorbar : bool : False # Show colorbar? Used with grayscale images.
texts : List[matplotlib.text.Text] : [] # Texts to be drawn on a cell.
title : str : None # Title of the cell.Colormap name for grayscale images. If set to None, matplotlib's default will be used.
MDPLCell(MDPLType.IMSHOW_GRAY, image, cmap="jet", show_colorbar=True)Whether to dynamically scale the text from texts when user resizes the window. If set to False, text will be rendered in the same size regardless of the window size.
Whether to hide the cell axes.
MDPLCell(MDPLType.IMSHOW_BGR, image, hide_axes=True)Horizontal and vertical lines to be drawn on a cell. Argument is set to a collection where each item represents one line. Line can be represented with a number or a dictionary, that contains values for matplotlib's axhline/axvline function. Default appearance of the line (if only a number is provided) is a red colour with a thickness of 0.4.
# We want to visualise computed threshold value in image's histogram
ret, _ = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Use the default line appearance
MDPLCell(MDPLType.HISTOGRAM, image, lines_vertical=[ret])
# Or set custom appearance
MDPLCell(MDPLType.HISTOGRAM, image, lines_vertical=[{"x": ret, "color": "green", "lw": 1}])Matplotlib patches to be drawn on a cell.
from matplotlib import patches
from skimage.feature import blob_log
blobs = blob_log(255 - image, min_sigma=1, overlap=0.45)
_patches = [patches.Circle((b[1], b[0]), b[2] * sqrt(2), linewidth=2, color="magenta", fill=False) for b in blobs]
MDPLCell(MDPLType.IMSHOW_GRAY, image, patches=_patches, hide_axes=True)Whether to show a colorbar. Used with IMSHOW_GRAY.
Texts to be drawn on a cell.
from matplotlib.text import Text
MDPLCell(MDPLType.IMSHOW_BGR, image, texts=[
Text(5, 45, "Hello mdpl!", color="#00C153", fontweight="bold")
])Title of the cell, that is drawn above it.
MDPLCell(MDPLType.IMSHOW_BGR, image, title="Original image", hide_axes=True)When using matplotlib, we can "build" the resulting window in multiple steps, whereas in mad_plot_lib it must be set up with a single mdpl_draw command, which may seem like a problem. However, step-by-step window preparation can be achieved by preparing collections that represent individual cells in advance:
mdpl_row_1 = []
mdpl_row_2 = []
for image_orig in images:
image_edit = cv2.Canny(image_orig, 100, 256)
mdpl_row_1.append(MDPLCell(MDPLType.IMSHOW_GRAY, image_orig))
mdpl_row_2.append(MDPLCell(MDPLType.IMSHOW_GRAY, image_edit))
mdpl_draw(plt, 2, len(images), None, None, [mdpl_row_1, mdpl_row_2])