Instantly share code, notes, and snippets.

Last active July 12, 2021 17:57
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')``` 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 - f
return [f - 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')``` 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 commented Oct 29, 2018

In Julia 0.6.4 and PyPlot.jl:

```function _extents(f)
delta = f - f
[f - 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```