Image and acquisition I/O#

The quantem.widget readers return the dataset type of the machine, so the same code runs on a GPU workstation, a laptop without a GPU and an Intel Mac. The QuantEM.GPU I/O guide owns the detector storage, supported formats, metadata, backend, and exactness contracts of the GPU path. For a walkthrough of uint8/uint16, memory estimates and the image readers, start with IO/GPU; GPU selection and cleanup are in Memory management.

Read data on any machine#

from quantem.widget import Show2D, Show4DSTEM, io

ds = io.read_image(path)
arr = ds.array                      # the pixels; ds.sampling, ds.units and ds.name carry the rest
Show2D(ds)

data = io.read_4dstem("scan_master.h5")
Show4DSTEM(data)

Reader

quantem.gpu and a GPU

No GPU route ([cpu], or device="cpu")

Intel Mac, Windows on ARM

read_4dstem(path)

quantem.gpu.io.Dataset4dstemGPU, encoded on the GPU

quantem core Dataset4dstem, read densely into host memory

stand-in, read densely

read_image(path), read_images(folder)

quantem core Dataset2d

quantem core Dataset2d

stand-in

read_image_stack(folder)

quantem core Dataset3d

quantem core Dataset3d

stand-in

read_4dstem loads through quantem.gpu.io.load when quantem.gpu is installed and a CUDA or Metal GPU works; a list of paths returns one dataset per file. A .npy file that quantem.gpu cannot hold exactly (signed counts, float64 that float32 does not hold, a flat 3D array) is read densely instead, with one printed line. A dense read keeps every count exactly, prints one line with the time taken, and refuses rather than bins or crops above 80% of the available memory. Counts keep uint8 or uint16, and wider counts become uint16 when every count fits, on both routes. quantem core cannot install on an Intel Mac (torch 2.7, NumPy 2) or Windows on ARM (hdf5plugin 6); there the readers return the widget’s stand-in, with the same array, name, sampling (a float64 array), units (a list), metadata, shape, ndim and dtype as a quantem core dataset. A color PNG, JPEG or TIFF reads as an RgbImage with array, name, sampling and units, and a folder of color frames as an (N, H, W, 3) array: quantem core has no color-image dataset. Every widget accepts each of these, NumPy arrays and torch tensors on any device, and shows the same numbers for each.

On a GPU, a 4D-STEM file is a Dataset4dstemGPU, not a torch tensor:

  • counts are unsigned (uint16, uint32), and torch cannot add, multiply or take the max of those;

  • the file stays compressed in GPU memory (the 512 x 512 x 192 x 192 gold scan: 5.6 GiB, against 18 GiB as dense uint16);

  • quantem.gpu’s CUDA and Metal kernels decode and sum it exactly in one pass.

Indexing and read(scan_region=...) still return torch tensors.

Every 4D dataset has shape, dtype, ndim, sampling, units and metadata, and Show4DSTEM takes any of them, with scan_region= for part of the scan. A Dataset4dstemGPU reports an uncalibrated axis as None in sampling and units where a dense dataset has 1.0 and "pixels"; the widgets read both as an axis without a calibration. A dense dataset’s metadata names the file (source_path) and records the replacement of the detector pixels the master flags (hot_pixel_correction). read_4dstem, Show4DSTEM(path) and Show4DSTEM.from_folder replace them with the median of their unflagged 3x3 neighbours by default (the smallest larger window inside a flagged cluster, a .5 median rounded half to even, as quantem.gpu’s io.load does) and print how many; hot_pixel_correction="none" keeps the stored values and "zero" sets them to 0. A Dataset4dstemGPU holds a float64 file that float32 holds exactly as float32. A dense Dataset4dstem also has array (a NumPy array) and name; a Dataset4dstemGPU keeps its counts on the GPU, returns them as torch tensors from indexing (data[10, 12]) and read(scan_region=...), and has close().

Open an acquisition on the GPU and inspect a pattern#

from quantem.gpu import detector, io
from quantem.widget import Show2D, Show4DSTEM

data = io.load("scan_master.h5")   # what read_4dstem does on a GPU
Show4DSTEM(data)

data is a quantem.gpu.io.Dataset4dstemGPU, ANS encoded on the CUDA device or the Apple GPU at native detector sampling and count dtype: a 512 x 512 x 192 x 192 uint16 scan, 18 GiB as a dense array, occupies about 0.1 to 2 GiB. Keep it open while viewers or calculations use its encoded buffers. Array-style selection decodes only the selected region into a Torch tensor on the acquisition’s GPU; it does not unpack the complete acquisition:

Show2D(data[10, 12])

Expression

Selection

data[10, 12]

One diffraction pattern

data[8:12, 10:16]

A rectangular scan patch

data[10, 12, 64:128, 64:128]

A detector crop at one position

data[:, :, 95, 100]

One detector pixel across the scan

data.metadata

Scientific calibration and source metadata

Show2D([detector.bf(data), detector.adf(data)], labels=["BF", "ADF"])
io.save("scan.qem", data)

Call data.close() after the last use. Scripted jobs can use with io.load(path) as data: for automatic cleanup. A list of paths loads as a list of encoded acquisitions, one per file; Show4DSTEM(io.load(paths)) opens them as a comparison grid labelled by file, and Show4DSTEM(series[1]) opens one of them. io.load(path, backend="cuda", device=1) selects a CUDA device; a Mac loads onto MPS. See load for the function reference and the GPU guide for multi-device storage policies.

Read a scan region#

Reconstruction, denoise and ROI workflows read the rectangular scan region they need. read decodes that region into a Torch tensor on the acquisition’s GPU:

with io.load("scan_master.h5") as data:
    patch_t = data.read(scan_region=(160, 293, 234, 367))  # row_start, row_stop, col_start, col_stop
print(patch_t.shape)  # torch.Size([133, 133, 192, 192])

Stops are exclusive, and detector_region= bounds the detector pixels the same way. For a drift-corrected time series, derive scan_region from the shared specimen ROI, the frame shift and a small scan halo, then sample the final ROI from the local patch. The detector counts remain raw; drift stays as scan-position metadata.

Discover acquisitions#

from quantem.gpu import io
from quantem.widget import Show4DSTEM

files = io.discover("/data/session")
Show4DSTEM(io.load(files[0]))

io.discover(path, scan_shape=(512, 512)) keeps only matching acquisitions when a folder mixes scan sizes. io.inspect(path) reads readiness and calibration headers without decoding measurements. Use Show4DSTEM.from_folder(path) for a live acquisition viewer.

For named sample data, see Tutorial datasets. The IO/GPU notebook demonstrates a real Gold image session.

Read images and image stacks#

from quantem.widget import Show2D, Show3D, read_image, read_images, read_image_stack

image = read_image("overview.tif")
Show2D(image)
images = read_images("survey_images", workers=8)
Show2D(images)
stack = read_image_stack("frames", file_type="tif", workers=8)
Show3D(stack)

These format readers run on the host. Calibrated image inputs retain their sampling for viewer scale bars. This path is for individual images and image sequences, not compressed 4D-STEM detector acquisitions.

Share data and finished figures#

Use Contribute tutorial data for shared example datasets and HTML and file export for interactive figures. Acquisition export belongs to io.save, described in the GPU I/O guide; it is separate from exporting a viewer.

Function reference#

quantem.widget.read_4dstem(path, device: str = 'auto', *, scan_shape: tuple[int, int] | None = None, max_bytes: int | None = None, verbose: bool = True, hot_pixel_correction: str = 'median')#

Read a 4D-STEM file: a Dataset4dstemGPU on a GPU, a quantem core Dataset4dstem otherwise.

With quantem.gpu installed (quantem.widget[cuda] or [mps]) and a CUDA or Metal GPU working, quantem.gpu.io.load keeps the counts encoded on the GPU. Without that route the file is read whole into host memory (read_dense) and returned as a quantem core Dataset4dstem; on an Intel Mac or Windows on ARM, which cannot install quantem core, as the widget’s stand-in with the same array, name, sampling, units, metadata, shape and dtype. Show4DSTEM and the detector math give the same numbers for either. The dense dataset’s metadata names the file (source_path) and, when the master flags detector pixels, records their replacement (hot_pixel_correction: method, pixel count and coordinates).

Parameters:
  • path – *_master.h5, an HDF5 file with entry/data/data, .npy, or a list of them (a list of datasets is returned).

  • device – "auto" (quantem.gpu picks CUDA, then Metal, and prints which), "cuda", "cuda:N" or "mps" place an acquisition on that GPU; "cpu" reads densely into host memory even where quantem.gpu runs. A dense dataset stays in host memory; a widget computes on it where its own device= says.

  • scan_shape – (rows, cols) of a flat (positions, det_row, det_col) file; a square scan is inferred when omitted.

  • max_bytes – Largest dense array to allocate. Default: MEMORY_FRACTION of the available host memory.

  • verbose – Print the dense read’s one-line summary.

  • hot_pixel_correction – "median" (default), "zero" or "none": what becomes of the detector pixels the master flags, on either route; one line says how many were replaced or left as stored.

Raises:

MemoryError – When a dense array would exceed max_bytes. Nothing is reduced instead.

Examples

>>> from quantem.widget import Show4DSTEM, read_4dstem  
>>> data = read_4dstem("scan_master.h5")  
>>> Show4DSTEM(data)  
quantem.widget.read_image(path: str | Path)#

Return a single image from disk (grayscale or true-color RGB).

One reader for every survey-image format the lab produces:

  • .npy - raw array, no calibration.

  • .emd - Velox HAADF (image under Data/Image/<hash>/Data with a JSON metadata blob carrying the pixel size); falls back to the largest 2D dataset for non-Velox EMD layouts (e.g. a data/drift/data series).

  • .tif / .tiff / .png / .jpg / .bmp / .gif - via Pillow. Color PNG/JPEG/TIFF keep RGB (RgbImage with shape (H, W, 3)); they are not converted to a single gray channel. Pass the result to Show2D or Show3D to display true color.

  • .dm3 / .dm4 - Gatan, via ncempy, with the pixel size and unit the file records.

Grayscale results are a quantem core Dataset2d (on an Intel Mac or Windows on ARM, which cannot install quantem core, the widget’s stand-in with the same array, name, sampling, units and metadata). Color results are RgbImage. A multi-frame file gives its first frame and prints one line saying so. A Velox EMD keeps its metadata in metadata and its scan rotation in scan_rotation_deg.

Examples

>>> from quantem.widget import Show2D, io  
>>> Show2D(io.read_image("figure_rgb.png"))  # true color, not gray  
quantem.widget.read_images(folder: str | Path, *, workers: int = 1, progress: bool = False)#

Read every image in a folder into a list of Dataset2d.

The folder analog of read_image() for a mixed set of survey images - different formats and different sizes that cannot stack into one cube (use read_image_stack() for a folder of same-size frames). Files are sorted by name; every supported extension is read, anything else is skipped. Lets a gallery be one line: Show2D([d.array for d in io.read_images(folder)]).

Parameters:
  • folder (str or Path) – Folder containing supported 2D image files.

  • workers (int, default 1) – Thread count for reading many files. Use workers=8 for large folders of independent EMD/TIFF/PNG survey images.

  • progress (bool, default False) – Show a tqdm bar while reading.

quantem.widget.read_image_stack(path: str | Path, *, file_type: str | None = None, pattern: str | None = None, workers: int = 8, progress: bool = True)#

Decode a folder of image frames into a Dataset3d in parallel.

A directory of PNG/TIFF/EMD/DM/NPY frames - an in-situ time series, a tilt series, a reconstruction sweep - is read with a thread pool into one contiguous (N, H, W) float32 array, then wrapped so Show3D(read_image_stack(dir)) scrubs the frames with no extra arguments. Frames are sorted naturally (frame_2 before frame_10). Decode is threaded because PIL/tifffile release the GIL during the C decode, so N threads give near-linear speedup until I/O or memory bandwidth saturates; ~8 workers is optimal on most disks. When the first frame is a calibrated format (EMD/DM), its pixel sampling and units carry onto the stack’s spatial axes so Show3D draws a physical scale bar.

Parameters:
  • path (str or Path) – Folder containing the image frames.

  • file_type (str, optional) – Extension filter (e.g. "png", "tif"). When omitted every common image extension in the folder is taken.

  • pattern (str, optional) – Glob within the folder (e.g. "frame_*.png"); overrides file_type.

  • workers (int, default 8) – Thread count for parallel decompression.

  • progress (bool, default True) – Show a tqdm bar while decoding.

Returns:

Shape (N, H, W), dtype float32 (the widget’s stand-in where quantem core cannot install). Sampling defaults to pixels since a bare image folder carries no calibration. A folder of color frames returns an (N, H, W, 3) array: quantem core has no color-image dataset.

Return type:

Dataset3d