Landing on the Red Puzzle

Collaging images from NASA's Perseverance Mars Rover

We wrote a quick ‘n dirty Python API for fetching, filtering and saving the images sent from Mars(!!!) by NASA’s Perseverance rover. This post shows a few different ways we used it to piece together the puzzle of the red planet (since NASA seems to like puzzles).

Some imports and a helper function to plot a grid of images:

In [1]:
import PIL
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d

from mars2020 import mars2020api

def display_image_grid(images: [mars2020api.ImageData], columns=3, width=15, height=8, max_images=30):
    if len(images) > max_images:
        print(f"Showing {max_images} of {len(images)} images")
        images = images[:max_images]
    height = max(height, int(len(images) / columns) * height)
    plt.figure(figsize=(width, height))
    for i, image in enumerate(images):
        plt.subplot(int(len(images) // columns + 1), columns, i + 1)

Fetch all of NASA’s Mars data (this just gets all the image metadata, the actual images are downloaded lazily when requested)

In [2]:
all_data = mars2020api.ImageDataCollection.fetch_all_mars2020_imagedata()


During the descent, the EDL_RDCAM camera continously took a ton of pictures that were perfect for collaging together.

In [3]:
images = [
    x for x in all_data.images 
    if x.camera_type.instrument == "EDL_RDCAM" 
    and not x.instrument_metadata.thumbnail # Not a thumbnail pic
    and x.instrument_metadata.filter_number == "E"
In [4]:

We used Photoshop’s Photomerge algorithm (had to subsample to a 100 images to keep Photoshop from crashing) to get this absolute beauty:

collage EDL_RDCAM Filter E

And similarly for filter_number = F:

collage EDL_RDCAM Filter F


NASA released a beautiful 360-degree panorama shot by the Mastcam-Z cameras on board. We tried to replicate this by getting the same images and running it through Photomerge again.

NASA’s claims to have used the GigaPan software for this but we couldn’t really get this to work probably because of the ordering of the images.

In [6]:
images = [
        for x in all_data.images
        if x.camera_type.instrument == "MCZ_LEFT" # MastCam Z - Left
        and not x.instrument_metadata.thumbnail # Not a thumbnail picture
        and x.instrument_metadata.filter_number == "F"
        and == 24 # Received on 24th Feb 2021
In [7]: