Source code for afmformats.formats.fmt_workshop.ws_map

import io
import pathlib
import warnings
import zipfile

import numpy as np

from .ws_single import AFMWorkshopFormatWarning, load_csv

__all__ = ["load_map"]


[docs]def load_map(path, callback=None, meta_override=None): """Load a set of zipped csv AFM workshop data If you are recording quantitative force-maps (i.e. multiple curves on an x-y-grid) with AFM workshop setups, then you might have realized that you get *multiple* .csv files (one file per indentation) instead of *one* file that contains all the data (as you might be accustomed to from other manufacturers). Since afmformats expects one file per measurement, it would not be straight forward to obtain a properly enumerated quantitative imaging group. This function offers a workaround - it loads a zip archive created from the the .csv files. The files are structured like this:: Force-Distance Curve File Format: 3 Date: Wednesday, August 1, 2018 Time: 1:07:47 PM Mode: Mapping Point: 16 X, um: 27.250000 Y, um: 27.250000 Extend Z-Sense(nm),Extend T-B(V),Retract Z-Sense(nm),Retract T-B(V) 13777.9288,0.6875,14167.9288,1.0917 13778.9288,0.6874,14166.9288,1.0722 13779.9288,0.6876,14165.9288,1.0693 13780.9288,0.6877,14164.9288,1.0824 13781.9288,0.6875,14163.9288,1.0989 ... Please make sure that the ``Point`` is enumerated from 1 onwards (and matches the alphanumerical order of the files in the archive) and that ``Mode`` is ``Mapping``. The ``X`` and ``Y`` coordinates can be used by e.g. PyJibe to display QMap data on a grid. Parameters ---------- path: str or pathlib.Path path to zip file containing AFM workshop .csv files callback: callable function for progress tracking; must accept a float in [0, 1] as an argument. meta_override: dict if specified, contains key-value pairs of metadata that are used when loading the files (see :data:`afmformats.meta.META_FIELDS`) """ datasets = [] with zipfile.ZipFile(path) as arc: names = sorted(arc.namelist()) for ii, name in enumerate(names): with arc.open(name, "r") as fd: tfd = io.TextIOWrapper(fd, encoding="utf-8") dd = load_csv( tfd, # recurse into callback with None as default callback=lambda x: callback((ii + x) / len(names)) if callback is not None else None, meta_override=meta_override, mode="mapping") dd[0]["metadata"]["path"] = pathlib.Path(path) cur_enum = dd[0]["metadata"]["enum"] if cur_enum != ii + 1: warnings.warn("Dataset 'Point' enumeration mismatch for " f"'{name}' in '{path}' (expected {ii + 1}, " f"got {cur_enum})!", AFMWorkshopFormatWarning) datasets += dd # Populate missing grid metadata xvals = list(set([ad["metadata"]["position x"] for ad in datasets])) yvals = list(set([ad["metadata"]["position y"] for ad in datasets])) mdgrid = { "grid center x": np.mean(xvals), "grid center y": np.mean(yvals), "grid shape x": len(xvals), "grid shape y": len(yvals), # grid size in um includes boundaries of pixels "grid size x": np.ptp(xvals)*(1 + 1/(len(xvals)-1)), "grid size y": np.ptp(yvals)*(1 + 1/(len(yvals)-1)), } # Update with new metadata (note that grid index x/y is populated via # MetaData._autocomplete_grid_metadata) [ad["metadata"].update(mdgrid) for ad in datasets] return datasets