K3 DM4 and saved .qem copies#
Calibrated DigitalMicrograph DM4 acquisitions can be opened directly on CUDA,
Python MPS and native Swift/Metal. The loader selects the unique four-dimensional
uint8/uint16 image, excluding survey images. Scan and detector axes retain their
native (row, column) order and calibration. Encoding uses bounded scan windows;
no scan binning, detector binning, crop or intensity scaling is introduced.
The shared native acquisition layout protocol v1.1
defines K3 identification, exact tag paths, normalized fields, units and snapshot
metadata placement. K3 is identified from the camera model, not a filename;
generic DM4 remains generic. In Live4DSTEM, Save Compressed Copy… creates a
.qem copy; reopening retains the K3 identity, recorded acquisition processing,
scan sampling, reciprocal sampling and voltage. No original file is modified.
Save once and reopen#
from quantem.gpu import io
with io.load("STEM SI.dm4", backend="mps") as acquisition:
io.save("acquisition.qem", acquisition, format="quantem", backend="mps")
with io.load("acquisition.qem", backend="mps") as acquisition:
print(acquisition.shape, acquisition.dtype)
Use backend="cuda" on NVIDIA. Both backends write and read the same
quantem.qem container with codec runtime-column-rans-spatial-v2.
Reopening restores encoded bytes and spatial indexes directly, with header and
body SHA-256 verification; it does not re-encode the original acquisition.
Saving refuses to overwrite an existing destination. The original DM4 is not
needed for reopening a complete snapshot.
The only accepted saved-copy extension is .qem. These source changes need a
matching development revision; they are not a claim about an older PyPI release.
Python DM4 metadata reading needs the dm extra (ncempy); native Swift does not.
Automatic virtual images#
from quantem.gpu import detector, io
with io.load("acquisition.qem") as data:
bf = detector.bf(data)
adf = detector.adf(data)
The disk is fitted automatically. Within the with block, replace the ADF
call with detector.adf(data, inner=180, outer=360, unit="px") when those are
the desired detector-pixel limits. Use the
static detector preview to inspect the selection.
Exact integer products for integrations#
The ordinary convenience functions return float32 NumPy images. The advanced session below preserves large integer sums as uint64; it uses an explicitly chosen center rather than the automatic disk fit.
import numpy as np
from quantem.gpu import detector, io
with io.load("acquisition.qem", backend="mps") as acquisition:
session = detector.prepare(acquisition)
try:
row, column = np.indices(acquisition.shape[2:])
center = (np.array(acquisition.shape[2:]) - 1) / 2
squared_radius = (row - center[0])**2 + (column - center[1])**2
adf = session.masked_sum_exact(
(squared_radius >= 180**2) & (squared_radius <= 360**2)
)
finally:
session.close()
Stored detector validity applies to products, while raw diffraction reads retain file counts. The Metal implementation uses exact packed 8/32-pixel spatial sums for mask interiors and ANS decoding for boundary residuals. Small mask changes use signed delta updates. Python MPS sums use uint64, including carry and borrow across the uint32 boundary. Native viewer product storage is uint32; camera loaders reject uint16 detector geometries whose possible sums exceed that range and direct the caller to the Python MPS path.
Native macOS integration#
NativeDM4Source inspects native DM4 metadata. NativeANSSnapshot inspects the
saved container and verifies its checksums before GPU consumption.
MetalRuntimeANSResidentSource.load(camera:device:) and
load(snapshot:device:) create the same native resident owner.
MetalRuntimeANSSeries owns the reusable diffraction and detector output buffers.
Call release() on the series and releaseResidentStorage() on its source when
finished. The UI owns selection, scheduling and presentation; the package owns
IO, codecs and scientific kernels.
The Live4DSTEM macOS integration discovers HDF5, DM4 and saved .qem copies in
mixed folders. Finder/Open With can open .dm4 and .qem documents directly.
HDF5 keeps its existing original/packed loading routes and correction policy.
Reproduce correctness and performance#
On an Apple Silicon Mac, build the existing metal-runtime-ans-benchmark product
and run it with --camera /absolute/path/acquisition.qem or a DM4 path.
K3_AUDIT_DM4=/absolute/path/original.dm4 additionally compares every decoded
count with native file bytes. This exhaustive audit is separate from timing a
single interactive request. K3_BENCH_TRIALS controls translated-mask trials.
The focused tests are NativeCameraSourceTests and
tests/hardware/mps/test_camera_spatial_ans.py; CUDA interchange tests are in
tests/contracts/io/test_digitalmicrograph.py with QUANTEM_TEST_CUDA=1.
Record the exact revision, hardware, file shape/dtype, cache state, first query,
steady-state queries and physical presentation separately. A kernel completing
within 8.33 ms does not establish 120 Hz on a 60 Hz display. Large arbitrary masks,
first-use compilation and unrelated GPU work can have different costs.
For the bounded native workflow check, run:
K3_REOPEN_TRIALS=5 bash scripts/check_metal_camera.sh original.dm4 saved.qem
# Also encode, save and verify every retained native metadata field:
bash scripts/check_metal_camera.sh original.dm4 saved.qem new-copy.qem
The first command reports header-through-resident wall time, including body checksum verification and GPU setup. Repeated loads reuse filesystem pages; these are not cold-storage or UI-presented-frame measurements. The check compares seven original diffraction frames and BF/ADF/DF values at those scan positions for four translated masks. It is not an exhaustive every-count audit. Raw DM4 loading additionally reads and encodes the original volume and must be timed separately; a fast compressed reopen is not a one-second original-load claim.
Native Swift writers can call source.saveSnapshot(to: destination) on a live,
exclusively owned resident with spatial indexes. To create those indexes during
original HDF5 loading, pass includeSpatialIndex: true to
MetalRuntimeANSResidentSource.load(source:device:...). DM4 residents already
include them. Run encoding and saving on a worker, keep the owner alive until
completion, and use shouldCancel for cooperative cancellation. Saving keeps
existing destinations and publishes completed files atomically. The saved
source_metadata retains native metadata; this path preserves stored counts
without median correction.