Skip to content

Instantly share code, notes, and snippets.

@fasiha
Last active July 30, 2024 13:20
Show Gist options
  • Save fasiha/eff0763ca25777ec849ffead370dc907 to your computer and use it in GitHub Desktop.
Save fasiha/eff0763ca25777ec849ffead370dc907 to your computer and use it in GitHub Desktop.
How to visualize 2D arrays in Matplotlib/Python (like imagesc in Matlab)

Visualizing rectangular 2D arrays in Python and Matplotlib the way you do with Matlab’s imagesc

Say you have a very rectangular 2D array arr, whose columns and rows correspond to very specific sampling locations x and y. That is, the arr[i, j] entry corresponds to some measurement taken at x[j] and y[i].

Matlab’s imagesc shows you this quite meaningfully:

x = linspace(-100, -10, 10);
y = [-8 -3];
data = randn(numel(y), numel(x));

figure()
imagesc(x, y, data)
export_fig('matlab.png')

Matlab imagesc

The two left-most matrix elements’ y-positions are indeed at -8 and -3, as specified in y (although the effect is obscured because of all the extra tick marks). Each matrix element’s horizontal position falls exactly on a multiple of -10, as in the x vector.

Furthermore, the matrix is stretched to cover the figure window, causing non-square matrix elements—very valuable when you want to observe the behavior of the full dataset. A given rectangular matrix element (‘matel’?) has the same flat color over its extent.

In Python and Matplotlib, an image like this is a little harder to obtain, because by default, Matplotlib’s imshow forces square pixels. If you ask for rectangular pixels via aspect='auto', it interpolates the underlying array, so each matrix element has a blend of colors. Finally, if you specify extent=[xmin, xmax, ymin, ymax], it takes these limits to mean the farthest edges of the image shown, rather than to the center of the edge matrix elements—so matrix elements do not line up with the sampling locations in x and y.

Here’s how to fix all these issues:

import numpy as np
import matplotlib.pyplot as plt

def extents(f):
  delta = f[1] - f[0]
  return [f[0] - delta/2, f[-1] + delta/2]

x = np.linspace(-100, -10, 10)
y = np.array([-8, -3.0])
data = np.random.randn(y.size,x.size)

plt.imshow(data, aspect='auto', interpolation='none',
           extent=extents(x) + extents(y), origin='lower')
plt.savefig('py.png')

Matplotlib imshow

We write a custom extents function that takes a vector containing sampling locations, and converts it into a 2-tuple suitable for being used in imshow’s extent keyword argument. We call this extents function on both x and y before giving their combination to imshow. We also specify that no interpolation should happen, and request automatic aspect ratios for rectangular matrix elements. Finally, we request that the image be flipped vertically via origin='lower' (in Matlab, axis ij instead of axis xy).

With these tweaks, we get a visualization that with the same useful properties as imagesc:

  1. rectangular pixels,
  2. flat color over a single matrix element,
  3. x and y axes that correspond to specified sampling locations, and last but definitely not least,
  4. origin that matches these axes.
@fasiha
Copy link
Author

fasiha commented Oct 29, 2018

In Julia 0.6.4 and PyPlot.jl:

function _extents(f)
    delta = f[2] - f[1]
    [f[1] - delta / 2, f[end] + delta / 2]
end

"""
Wrapper for PyPlot's `imshow` to imitate Matlab-style IMAGESC.

`imagesc(z; x, y)` treats `z` as a 2D array to visualize, with `x` giving pixel coordinates
across a row and `y` giving pixel columns *down* a column.

Omitting `x` and/or `y` implies `1:size(z, 2)` and `1:size(z, 1)` respectively.

PyPlot will show the image using a carefully-constructed call to `PyPlot.imshow` where:
- the extent is carefully initialized so the plot's ticks line up exactly with `x` and `y`
- the origin is at the lower-left of the window
- the aspect ratio is fluid (uses the full window)
- no interpolation is applied.
"""
function imagesc(im; x=1:size(im, 2), y=1:size(im, 1))
    imshow(im, extent=vcat(_extents(x), _extents(y)), aspect="auto", origin="lower", interpolation="none")
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment