Thanks to visit codestin.com
Credit goes to github.com

Skip to content

πŸ‡ΊπŸ‡³ Simple wrapper around matplotlib's pyplot

License

Notifications You must be signed in to change notification settings

RDMCz/mad_plot_lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

mad_plot_lib

What is this?

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.

Pitch (quick example)

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.

Getting started

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

Reference

  • 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_draw creates its own window, similiar to plt.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 cells
    • MDPLCell: class, whose constructor is used to specify a particular cell
    • MDPLType: enum for determining cell type

mdpl_draw

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 window
  • n_rows – how many rows will the window have, positive integer
  • n_cols – how many cells will each row have, positive integer
  • figname – window header, can be set to None for no header
  • figsize – explicit window size, tuple of two positive integers: (width, height), can be set to None for implicit size
  • cells – collection of collections: each inner collection represents one row, and each item in that collection represents one cell in that row via MDPLCell

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 and MDPLType

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)

MDPLCell keyword arguments

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

cmap

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)

do_dynamic_text

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.

hide_axes

Whether to hide the cell axes.

MDPLCell(MDPLType.IMSHOW_BGR, image, hide_axes=True)

lines_horizontal and lines_vertical

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}])

patches

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)

show_colorbar

Whether to show a colorbar. Used with IMSHOW_GRAY.

texts

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

Title of the cell, that is drawn above it.

MDPLCell(MDPLType.IMSHOW_BGR, image, title="Original image", hide_axes=True)

Tips

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

About

πŸ‡ΊπŸ‡³ Simple wrapper around matplotlib's pyplot

Resources

License

Stars

Watchers

Forks

Contributors

Languages