IO/GPU#

Open in Colab

Use this page when you have microscope data on disk and want to open it. Every reader below runs on a real gold HAADF microscope session from the public bobleesj/quantem-data Hugging Face dataset, so you can see exactly what each IO path returns and how the viewers render it.

For what the dtypes mean, how much memory a loaded acquisition takes, GPU selection, and freeing memory afterward, see Memory management.

Tip

Run this exact notebook with the Colab badge above, or View or download this notebook on GitHub. For finished results, use HTML and file export to export interactive HTML or share a trusted notebook with widget state.

from quantem.widget.datasets import gold_session

folder = gold_session()  # downloads and caches the real session folder
sorted(p.name for p in folder.glob("*.emd"))[:5]
Gold image session: 26 EMD files
['0001 - HAADF 15Mx gold FOV 01 0deg.emd',
 '0002 - HAADF 15Mx gold FOV 01 90deg.emd',
 '0003 - HAADF 15Mx gold FOV 02 0deg.emd',
 '0004 - HAADF 15Mx gold FOV 02 90deg.emd',
 '0005 - HAADF 15Mx gold FOV 03 0deg.emd']

Inspect before loading#

List the cached session files, then load the selected images with the readers below. For a growing session, use Show2D.from_folder or Show3D.from_folder.

sorted(path.name for path in folder.glob("*.emd"))
['0001 - HAADF 15Mx gold FOV 01 0deg.emd',
 '0002 - HAADF 15Mx gold FOV 01 90deg.emd',
 '0003 - HAADF 15Mx gold FOV 02 0deg.emd',
 '0004 - HAADF 15Mx gold FOV 02 90deg.emd',
 '0005 - HAADF 15Mx gold FOV 03 0deg.emd',
 '0006 - HAADF 15Mx gold FOV 03 90deg.emd',
 '0007 - HAADF 15Mx gold FOV 04 0deg.emd',
 '0008 - HAADF 15Mx gold FOV 04 90deg.emd',
 '0009 - HAADF 15Mx gold FOV 05 0deg.emd',
 '0010 - HAADF 15Mx gold FOV 05 90deg.emd',
 '0011 - HAADF 15Mx gold FOV 06 0deg.emd',
 '0012 - HAADF 15Mx gold FOV 06 90deg.emd',
 '0013 - HAADF 15Mx gold FOV 07 0deg.emd',
 '0014 - HAADF 15Mx gold FOV 07 90deg.emd',
 '0015 - HAADF 15Mx gold FOV 08 0deg.emd',
 '0016 - HAADF 15Mx gold FOV 08 90deg.emd',
 '0017 - HAADF 15Mx gold FOV 09 0deg.emd',
 '0018 - HAADF 15Mx gold FOV 09 90deg.emd',
 '0019 - HAADF 15Mx gold FOV 10 0deg.emd',
 '0020 - HAADF 15Mx gold FOV 10 90deg.emd',
 '0021 - HAADF 3.7Mx gold overview 01.emd',
 '0022 - HAADF 3.7Mx gold overview 02.emd',
 '0023 - HAADF 3.7Mx gold overview 03.emd',
 '0024 - HAADF 3.7Mx gold overview 04.emd',
 '0025 - HAADF 3.7Mx gold overview 05.emd',
 '0026 - HAADF 3.7Mx gold overview 06.emd']

For 4D-STEM master files, discover collects the candidate HDF5 masters before loading (this session folder has survey images only, so run this on your own acquisition folder):

from quantem.gpu.io import discover

masters = discover("/data/session", scan_shape=(512, 512))

Two different IO paths#

Use the 4D-STEM loader for detector stacks, and use the image readers for ordinary survey images. They are intentionally different:

data on disk

reader

what is accelerated

4D-STEM HDF5 master files

load(...)

detector decompression on CUDA or Metal into encoded GPU storage at native detector sampling; bounded read(...) regions as Torch tensors

HAADF/ADF/BF survey images, PNG, TIFF, JPEG, GIF, DM, NPY

read_image(...), read_images(...)

CPU file decoding through the format library, optional threaded batch reads, then explicit GPU transfer if you need Torch/CuPy

folders of same-size image frames

read_image_stack(...)

threaded CPU decoding, preallocated stack, then optional GPU transfer

PNG, TIFF, and EMD survey-image decoding is not a WebGPU operation and is not decoded by the browser. It happens in Python. That is still the right path for single images and 50-60 survey files because the files are independent and can be read in parallel. Move the resulting arrays to the GPU only when you are about to compute on them (see Memory management).

Survey images and frame folders#

Open a HAADF or EMD survey image#

For a single 2D microscope image, use read_image. It returns a calibrated Dataset2d when the file carries metadata, so Show2D draws the physical scale bar automatically. This is a real 3.7Mx gold overview from the session:

from quantem.widget import Show2D, read_image

overview = sorted(folder.glob("*overview*"))[0]
haadf = read_image(overview)  # Velox EMD HAADF -> calibrated Dataset2d
Show2D(haadf, display_bin=4)
  Display bin 4×: 768×768 → 192×192 (0 MB preview; full-res detail streams on zoom)

The same reader works for common image files:

image = read_image("overview.tif")    # also .png, .jpg, .bmp, .gif, .dm3, .dm4, .npy
Show2D(image)

Use this path for HAADF, ADF, BF, overview images, diffraction snapshots saved as images, and quick previews. It avoids the 4D-STEM decompression path entirely, so loading is immediate for normal image sizes.

Open many EMD survey images#

For a folder of independent microscope images, use read_images(folder, workers=8): one call reads every image in the folder concurrently and returns a list of calibrated Dataset2d (it runs on the full session in the profiling cell below). Any list of datasets renders as a gallery - here are three of the real 3.7Mx overview images. The docs widget uses display_bin=4 so the published HTML stays small; remove it when you need pixel-level inspection:

from quantem.widget import Show2D, read_image

overviews = [read_image(p) for p in sorted(folder.glob("*overview*"))[:3]]
Show2D(overviews, display_bin=4)
  Display bin 4×: 768×768 → 192×192 (0 MB preview; full-res detail streams on zoom)

This is the best fit for 50-60 HAADF/ADF/BF survey images that may be different sizes or formats. If every frame is the same size and you want a time/depth slider, read_image_stack decodes the folder in parallel into one scrubbable stack - and when the frames are a calibrated format (EMD/DM), the pixel sampling carries onto the stack so Show3D draws the scale bar. Two frames and display_bin=8 keep this docs page lightweight:

from quantem.widget import Show3D, read_image_stack

# Two fields of view keep this docs page light; drop the [1-2] for all ten.
stack = read_image_stack(folder, pattern="*15Mx* 0[1-2] 0deg*", workers=8)
Show3D(stack, display_bin=8)
Decoding 2 frames: 100%|██████████| 1/1 [00:00<?, ?frame/s]
                                                           
  Display bin 8×: 512×512 → 64×64

The same call handles PNG, TIFF, and NPY frame folders (an in-situ sequence, tilt series, or denoising sweep):

stack = read_image_stack("frames", file_type="tif", workers=8)
stack = read_image_stack("frames", pattern="frame_*.png", workers=8)

Keep the files in their original integer dtype on disk. The reader converts the viewer stack to float32, which is the right display/processing dtype for most image stacks and avoids accidental float64 memory growth.

Profile your own image folder#

Before changing file formats, measure the folder you actually have. This times the real session folder on the machine that built this page:

import time
from quantem.widget import read_images

for workers in (1, 8):
    t0 = time.perf_counter()
    images = read_images(folder, workers=workers)
    dt = time.perf_counter() - t0
    pixels = sum(ds.array.size for ds in images)
    print(f"workers={workers}: {len(images)} images, {pixels / 1e6:.0f} MP in {dt:.2f} s ({pixels / dt / 1e6:.0f} MP/s)")
workers=1: 26 images, 9 MP in 0.21 s (42 MP/s)
workers=8: 26 images, 9 MP in 0.25 s (36 MP/s)

On many image folders the parallel path is faster; on slow network storage, the disk can become the limit. For 50-60 full 4k survey images, prefer EMD or TIFF when you control the export format. PNG is fine for screenshots and compact sharing, but it is not the fastest format for high-throughput analysis.

Open a diffraction image or diffraction stack#

Use ShowDiffraction for a single diffraction pattern or a small stack of patterns:

from quantem.widget import ShowDiffraction, read_image, read_image_stack

dp = read_image("diffraction.tif")
ShowDiffraction(dp.array)

dp_stack = read_image_stack("diffraction_frames", file_type="tif")
ShowDiffraction(dp_stack.array)

This is separate from Show4DSTEM: use ShowDiffraction when the data is just one detector image or a detector-image stack, and use Show4DSTEM(load(...)) when you have a scan grid with a diffraction pattern at every probe position.

4D-STEM detector stacks#

For your own acquisitions, load opens one or more *_master.h5 files on CUDA or Apple Silicon. Each acquisition stays encoded on the GPU at native detector sampling and count dtype: a 512 x 512 x 192 x 192 uint16 scan, 18 GiB as a dense array, takes about 0.1 to 2 GiB.

from quantem.gpu.io import discover, load
from quantem.widget import Show4DSTEM

loaded = load("scan_001_master.h5", verbose=True)
Show4DSTEM(loaded)

# compare a whole session: one acquisition per master, one virtual image per file
masters = discover("/data/session", scan_shape=(512, 512))
Show4DSTEM(load(masters))

# decode only the scan region an analysis needs, as a Torch tensor
patch_t = loaded.read(scan_region=(0, 128, 0, 128))

To check VRAM before and after the load, and to free it when you are done, see Memory management.

The public tutorial dataset ships a real binned gold 4D-STEM scan, so the same viewer runs here on real detector counts:

from quantem.widget import Show4DSTEM
from quantem.widget.datasets import show4dstem_gold

stem_dataset = show4dstem_gold(size="small")
Show4DSTEM(stem_dataset, dp_scale_mode="linear")
Source: gold_128_npy_bin8 from Hugging Face
Full stack: (128, 128, 24, 24) uint16
Preview: (32, 32, 24, 24), scan stride 4
Sampling: [8.   8.   3.68 3.68] ['A', 'A', 'mrad', 'mrad']
Processing: real-space 4x4 block average from gold_512_npy_bin8; detector remains bin8

What should I choose?#

  1. Exact detector counts, browsing, or reconstruction on CUDA/MPS: load(path) keeps every detector pixel and count.

  2. Need a smaller array for an analysis: read(scan_region=...) a bounded region instead of the whole scan.

  3. Sharing a compact browser copy: choose dtype="uint8" and any detector bin in the export (export_html or quantem show4dstem ... --html --bin N --dtype uint8) and label it.

  4. Making a terminal WebGPU viewer: use quantem show4dstem ... --backend webgpu --html --bin 1 for native detector sampling.

  5. Making a tutorial or HTML preview: use a visibly reduced copy and say how it was reduced.

Do not silently crop, bin, or change dtype in a tutorial. Put the reduction in the code so another scientist can see exactly what happened.