Note

This page was generated from a Jupyter notebook. You can download the notebook from the notebooks/ directory in the repository.

Hyperspectral Images Manipulation

Author: Riccardo Finotello riccardo.finotello@cea.fr

This notebook explores the HSIMars class to manipulate and display the hyperspectral images (HSI) of Mars exploration.

[1]:
%load_ext autoreload
%autoreload 2
[2]:
from hsimars import HSIMars

First Steps

We first need to instantiate the HSIMars class:

[3]:
hsi = HSIMars(
    hdr_path="../data/HC_frt0000580c_07_if164j_ter3.hdr",
    annotations="../data/HC_ground_truth.mat",
)

We then proceed with the extraction of the HSI:

[4]:
img = hsi.get_img()
type(img)
[4]:
hsimars.hsi.HSIMarsImageData

The object is a namedtuple containing several attributes, which can be accessed using the usual Python interface img.<attr>:

  • hsi: the HSI data,

  • wavelength: the list of wavelengths (units: nm),

  • shape: the shape of the image,

  • height: the height of the image,

  • width: the width of the image,

  • channels: the number of channels in the image,

  • dtype: the data type of the HSI.

[5]:
print("img.hsi -->", type(img.hsi))
print("img.wavelength -->", type(img.wavelength))
print("img.shape -->", type(img.shape))
print("img.height -->", type(img.height))
print("img.width -->", type(img.width))
print("img.channels -->", type(img.channels))
print("img.dtype -->", type(img.dtype))
img.hsi --> <class 'numpy.ndarray'>
img.wavelength --> <class 'numpy.ndarray'>
img.shape --> <class 'tuple'>
img.height --> <class 'int'>
img.width --> <class 'int'>
img.channels --> <class 'int'>
img.dtype --> <class 'str'>

We can also recover the annotations using the same principle:

[6]:
ann = hsi.get_annotations()
type(ann)
[6]:
hsimars.hsi.HSIMarsAnnotationData

The output is another namedtuple, containing the following attributes:

  • labels: the annotation data,

  • shape: the shape of the image,

  • height: the height of the image,

  • width: the width of the image,

  • dtype: the data type of the annotation.

[7]:
print("ann.labels -->", type(ann.labels))
print("ann.shape -->", type(ann.shape))
print("ann.height -->", type(ann.height))
print("ann.width -->", type(ann.width))
print("ann.dtype -->", type(ann.dtype))
ann.labels --> <class 'numpy.ndarray'>
ann.shape --> <class 'tuple'>
ann.height --> <class 'int'>
ann.width --> <class 'int'>
ann.dtype --> <class 'str'>

If you prefer, you can also call both of these outputs at the same time:

[8]:
img, ann = hsi.data()
type(img), type(ann)
[8]:
(hsimars.hsi.HSIMarsImageData, hsimars.hsi.HSIMarsAnnotationData)

Visualisation

You can also visualise the annotations and the image (in fake colours) using opencv (a new window will open, close it with “q”):

[9]:
hsi.display_hsi()

You can visualise the annotations (close the new window with “q”):

[10]:
hsi.display_annotations()

Or you can display all of the information at once (close again with “q”):

[11]:
hsi.display()

We can also plot a particular spectrum:

[12]:
hsi.plot_spectra((128, 145))
../_images/notebooks_hsimars_22_0.png
[13]:
hsi.plot_spectra([[128, 145], [129, 146], [128, 146], [129, 145]], bands=True)
../_images/notebooks_hsimars_23_0.png
[14]:
hsi.plot_spectra(
    [[128, 145], [129, 146], [128, 146], [129, 145]],
    convex_hull=True,
    bands=True,
)
../_images/notebooks_hsimars_24_0.png

Or even the histogram of a particular band:

[15]:
hsi.plot_histogram(band=146)
../_images/notebooks_hsimars_26_0.png
[16]:
hsi.plot_histogram(band=1493.0)
../_images/notebooks_hsimars_27_0.png

Working with Label Names

The annotation data includes a label_names dictionary that maps numerical labels to human-readable mineral names:

[17]:
# Get annotation data
img_data, ann_data = hsi.data()

# Display the label names mapping
print("Available mineral classes:")
for label_id, mineral_name in sorted(ann_data.label_names.items()):
    print(f"  {label_id}: {mineral_name}")
Available mineral classes:
  1: Analcime
  2: Plagioclase
  3: Prehnite
  4: High-Ca Pyroxene
  5: Serpentine
  6: Margarite
[18]:
# Count pixels for each mineral class
import numpy as np

unique_labels = np.unique(ann_data.labels)
unique_labels = unique_labels[unique_labels > 0]  # Exclude background

print("\nClass distribution:")
for label_id in sorted(unique_labels):
    count = np.sum(ann_data.labels == label_id)
    mineral_name = ann_data.label_names.get(
        label_id, f"Unknown (ID: {label_id})"
    )
    percentage = (count / ann_data.labels.size) * 100
    print(f"  {mineral_name:25s} - {count:6d} pixels ({percentage:5.2f}%)")

Class distribution:
  Analcime                  -    940 pixels ( 0.37%)
  Plagioclase               -   1472 pixels ( 0.59%)
  Prehnite                  -   1560 pixels ( 0.62%)
  High-Ca Pyroxene          -   6963 pixels ( 2.78%)
  Serpentine                -   8697 pixels ( 3.47%)
  Margarite                 -    458 pixels ( 0.18%)