Loading a TEM Image (.dm3)

Transmission electron microscopes (TEM) [4] often save images in Gatan’s .dm3 format. The file also stores the real size of a pixel, so structures can later be measured in nanometres.

This notebook loads a TEM image of an LATP solid-electrolyte sample and checks that it works with the standard JuSPICE tools.

JuSPICE Modules and Classes

  • juspice.io: SPICEData, load_data, save_data

  • SPICEData.preprocess: built-in preprocessing accessor

  • SPICEData.features: built-in feature-extraction accessor

  • juspice.tracking: Tracker

[1]:
from juspice.tracking import Tracker

tracker = Tracker(include_metadata=True, notes='EM (.dm3) io basics')

tracker.recording_start()
/Users/amir/GIT_repositories/juspice_pre_release/venvs/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
[2]:
# --------- Block 0: Setup ----------

# Standard library
from pathlib import Path
import sys
import os

# Third-party
import numpy as np
import matplotlib.pyplot as plt

# JuSPICE
from juspice.io import SPICEData, load_data, save_data

# Paths
try:
    notebook_dir = Path(__file__).resolve().parent
except Exception:
    notebook_dir = Path.cwd()

cur = notebook_dir
repo_root = None
for _ in range(6):
    if (cur / 'juspice').exists() or (cur / 'pyproject.toml').exists():
        repo_root = cur
        break
    if cur.parent == cur:
        break
    cur = cur.parent
if repo_root is None:
    repo_root = notebook_dir
if str(repo_root) not in sys.path:
    sys.path.insert(0, str(repo_root))

[3]:
tracker.recording_stop()

Load the image

load_data() reads the file with ncempy [55] and puts the pixel values in spice.data and the pixel size in spice.metadata: here about 0.54 nm per pixel.

[4]:
tracker.recording_start()
[5]:
# --------- Block 1: Load ----------

dm3_path = os.path.join(repo_root, 'Sample_data', 'em', 'LMP_LATP_19500X_0005.dm3')
print(f'TEM file path: {dm3_path}')

# `spice` is just a variable name for the SPICEData instance load_data()
# returns here — any name would work; we use `spice` throughout these
# notebooks as an intuitive nod to JuSPICE / SPICEData.
spice = load_data(dm3_path)
print(f'data_type:  {spice.data_type}')
print(f'data.shape: {spice.data.shape}')
print(f'data.dtype: {spice.data.dtype}')
print(f'pixel_size: {spice.metadata["pixel_size"]}')
print(f'pixel_unit: {spice.metadata["pixel_unit"]}')

TEM file path: /Users/amir/GIT_repositories/juspice_pre_release/Sample_data/em/LMP_LATP_19500X_0005.dm3
data_type:  em_dm3
data.shape: (2048, 2048)
data.dtype: int32
pixel_size: [0.5436682105064392, 0.5436682105064392]
pixel_unit: ['nm', 'nm']
[6]:
tracker.recording_stop()

The raw image and its brightness histogram:

[7]:
tracker.recording_start()
[8]:
# --------- Block 2: Visualize ----------

spice.preprocess.show_image(
    title='LMP_LATP TEM Image (raw)', show_colorbar=True, show_histogram=True
)
../../_images/notebooks_io_io_dm3_10_0.png
[9]:
tracker.recording_stop()

Check that the standard tools work

normalize() rescales the values to 0–1, as for any PNG or TIFF image.

[10]:
tracker.recording_start()
[11]:
# --------- Block 3: Normalize via spice.preprocess ----------

spice.preprocess.normalize()
print(f'Size of the normalized image: {spice.data.shape}')
spice.preprocess.show_image(
    title='LMP_LATP TEM Image (normalized)', show_colorbar=True, show_histogram=True
)
Size of the normalized image: (2048, 2048)
../../_images/notebooks_io_io_dm3_14_1.png
[12]:
tracker.recording_stop()

The Sobel magnitude [6] is large where brightness changes sharply, so edges appear bright in the right panel.

[13]:
tracker.recording_start()
[14]:
# --------- Block 4: Feature extraction via spice.features ----------

feature_spice = spice.features.extract(feature_names='sobel_magnitude')
print(f'feature_spice.data.shape: {feature_spice.data.shape}')

fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].imshow(spice.data, cmap='gray')
ax[0].set_title('Normalized TEM Image')
ax[0].axis('off')
ax[1].imshow(feature_spice.data[:, :, 0], cmap='gray')
ax[1].set_title('Sobel Magnitude')
ax[1].axis('off')
plt.tight_layout()
plt.show()

feature_spice.data.shape: (2048, 2048, 1)
../../_images/notebooks_io_io_dm3_18_1.png
[15]:
tracker.recording_stop()

Save and review

Saving writes the data, its metadata, and a script that recreates it.

[16]:
tracker.recording_start()
[17]:
# --------- Block 5: Save ----------
# This folder holds several notebooks, so save_data()'s automatic
# notebook-name detection would be ambiguous when run outside a live
# Jupyter session (e.g. via nbconvert) — pass output_stem explicitly
# to always land on this notebook's own files. See
# docs/repository_structure.rst.
stem = 'io_dm3'
save_data(
    spice,
    output_stem=str(notebook_dir / stem),
)
print(f'Data:           {os.path.join(notebook_dir, stem + ".npy")}')
print(f'JSON sidecar:   {os.path.join(notebook_dir, stem + ".json")}')
print(f'History script: {os.path.join(notebook_dir, stem + "_history.py")}')

Data:           /Users/amir/GIT_repositories/juspice_pre_release/notebooks/io/io_dm3.npy
JSON sidecar:   /Users/amir/GIT_repositories/juspice_pre_release/notebooks/io/io_dm3.json
History script: /Users/amir/GIT_repositories/juspice_pre_release/notebooks/io/io_dm3_history.py
[18]:
tracker.recording_stop()

The history lists every step as runnable code.

[19]:
tracker.recording_start()
[20]:
# --------- Block 6: Inspect human-readable history ----------

readable_lines = spice.history.to_lines()
print('Reconstructed pipeline for this SPICEData object:')
print('\n'.join(readable_lines))

Reconstructed pipeline for this SPICEData object:
import juspice
spice = juspice.io.load_data('/Users/amir/GIT_repositories/juspice_pre_release/Sample_data/em/LMP_LATP_19500X_0005.dm3')
spice.preprocess.normalize()
feature_spice = spice.features.extract(feature_names=['sobel_magnitude'], n_features=1)
juspice.io.save_data(spice)
[21]:
tracker.recording_stop()