NumPy
PIL
Matplotlib
Image Processing
Colormap

How to convert a NumPy array to PIL image applying matplotlib colormap

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Converting numeric arrays into images is common in machine learning, geospatial analysis, and scientific dashboards. A raw NumPy array usually stores scalar values, not display-ready colors, so a colormap step is what makes the data interpretable. This guide shows a repeatable path from a two-dimensional NumPy array to a PIL image using Matplotlib.

Build a Safe Array-to-Image Pipeline

A reliable conversion has four explicit stages: normalize values, apply a colormap, scale to uint8, and create a PIL image. Keeping each stage explicit prevents accidental dtype or range bugs that produce washed-out or overly dark output.

The script below is runnable as-is. It generates synthetic data, applies viridis, and writes a PNG.

python
1import numpy as np
2from matplotlib import cm
3from PIL import Image
4
5# Create a sample float array
6h, w = 240, 320
7y = np.linspace(-3.0, 3.0, h)
8x = np.linspace(-4.0, 4.0, w)
9xx, yy = np.meshgrid(x, y)
10arr = np.sin(xx) * np.cos(yy) + 0.25 * np.random.default_rng(42).normal(size=(h, w))
11
12# Normalize to 0..1
13arr_min = arr.min()
14arr_max = arr.max()
15norm = (arr - arr_min) / (arr_max - arr_min + 1e-12)
16
17# Map with colormap, output is RGBA float in 0..1
18rgba = cm.get_cmap("viridis")(norm)
19
20# Convert to uint8 RGB for PIL
21rgb_uint8 = (rgba[:, :, :3] * 255).astype(np.uint8)
22img = Image.fromarray(rgb_uint8, mode="RGB")
23img.save("viridis-output.png")
24print("Saved viridis-output.png")

If your array already has a stable physical range, use that known range for normalization instead of per-image min and max. This keeps colors comparable across multiple frames or experiments.

Handle Outliers and Preserve Detail

Many datasets have a small number of extreme values. If you normalize directly with global min and max, those extremes can compress useful mid-range information into a tiny color band. A good fix is percentile clipping before normalization.

python
1import numpy as np
2from matplotlib import cm
3from PIL import Image
4
5
6def to_colormapped_pil(data, cmap_name="magma", p_low=2.0, p_high=98.0, alpha=False):
7    if data.ndim != 2:
8        raise ValueError("Expected a 2D array")
9
10    lo = np.percentile(data, p_low)
11    hi = np.percentile(data, p_high)
12    clipped = np.clip(data, lo, hi)
13
14    norm = (clipped - lo) / (hi - lo + 1e-12)
15    mapped = cm.get_cmap(cmap_name)(norm)
16
17    if alpha:
18        out = (mapped * 255).astype(np.uint8)
19        mode = "RGBA"
20    else:
21        out = (mapped[:, :, :3] * 255).astype(np.uint8)
22        mode = "RGB"
23
24    return Image.fromarray(out, mode=mode)
25
26
27if __name__ == "__main__":
28    rng = np.random.default_rng(7)
29    data = rng.normal(loc=10.0, scale=2.5, size=(256, 256))
30    data[8, 8] = 100.0
31
32    img = to_colormapped_pil(data, cmap_name="plasma")
33    img.save("plasma-output.png")
34    print("Saved plasma-output.png")

This function is practical in production because it centralizes policy decisions such as clipping and output mode. That consistency is useful for dashboards and model diagnostics.

Move Between PIL, NumPy, and Other Libraries

After conversion, PIL gives you simple save and resize operations, while NumPy remains ideal for numeric transforms. You can convert back with np.array(image) whenever downstream code expects an array. If you pass data into OpenCV, remember OpenCV defaults to BGR channel order, so convert deliberately to avoid color shifts.

When building reports, generate both raw grayscale and colorized outputs. Grayscale keeps numeric intuition for technical readers, while colormaps improve quick visual scanning for non-technical stakeholders.

Common Pitfalls

  • Skipping normalization and expecting different arrays to map to comparable colors.
  • Passing a three-dimensional array into a scalar colormap flow that expects height by width data.
  • Forgetting to cast to uint8 before creating an RGB PIL image.
  • Keeping the alpha channel by accident when the downstream pipeline expects plain RGB.
  • Normalizing each frame independently in a time series, which makes color meaning drift over time.

Summary

  • Use an explicit pipeline: normalize, map colors, scale to uint8, then create a PIL image.
  • Apply percentile clipping when outliers hide important structure.
  • Keep output mode intentional, especially when moving among PIL, NumPy, and OpenCV.
  • Use one reusable helper to keep image generation behavior stable across projects.
  • Validate array shape and dtype early to avoid hard-to-debug rendering artifacts.

Course illustration
Course illustration

All Rights Reserved.