Source code for playnano.io.formats.read_asd

"""
Module to decode and load .asd high speed AFM data files into Python NumPy arrays.

Files containing multiple image frames are read together.
Converts the height data into nm from another metric unit (e.g. m).
"""

import logging
from pathlib import Path

import numpy as np
from AFMReader.asd import load_asd

from playnano.afm_stack import AFMImageStack
from playnano.utils.io_utils import convert_height_units_to_nm, guess_height_data_units

logger = logging.getLogger(__name__)


def _standardize_units_to_nm(image_stack: np.ndarray, channel: str) -> np.ndarray:
    """
    Convert height data to nanometers if the channel is topography ("TP").

    Attempts to guess the unit from data range; defaults to 'nm' on failure.

    Parameters
    ----------
    image_stack : np.ndarray
        AFM height data array (2D or 3D).
    channel : str
        Channel name; must be "TP" to trigger conversion.

    Returns
    -------
    None
    """
    try:
        height_unit = guess_height_data_units(image_stack)
        logger.info(f"Guessed that the height unit is {height_unit}")
    except Exception as e:
        height_unit = "nm"
        logger.warning(f"Failed to guess height unit, defaulting to 'nm': {e}")

    if channel == "TP":
        image_stack[:] = convert_height_units_to_nm(image_stack, height_unit)
    return image_stack


[docs] def load_asd_file(file_path: Path | str, channel: str) -> AFMImageStack: """ Load image stack from an .asd file scaled to nanometers. Parameters ---------- file_path : Path | str Path to the .asd file. channel : str Channel to extract. Returns ------- AFMImageStack Loaded AFM image stack with metadata and per-frame info. """ file_path = Path(file_path) # Read .asd data and header image_stack, pixel_size_nm, asd_metadata = load_asd(file_path, channel) image_stack = _standardize_units_to_nm(image_stack, channel) frame_time = asd_metadata["frame_time"] lines = asd_metadata["y_pixels"] num_frames = asd_metadata["num_frames"] line_rate = lines / (frame_time / 1000) # lines per second frame_interval = lines / line_rate # seconds per frame (lines / lines per second) timestamps = np.arange(num_frames) * frame_interval # Compose per-frame metadata list frame_metadata = [] for ts in timestamps: frame_metadata.append({"timestamp": ts, "line_rate": line_rate}) return AFMImageStack( data=image_stack, pixel_size_nm=pixel_size_nm, channel=channel, file_path=str(file_path), frame_metadata=frame_metadata, )