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_dataSPICEData.preprocess: built-in preprocessing accessorSPICEData.features: built-in feature-extraction accessorjuspice.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
)
[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)
[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)
[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()