import pathlib
from .. import errors
from .. import meta
from .fmt_hdf5 import recipe_hdf5
from .fmt_igor import recipe_ibw
from .fmt_jpk import recipe_jpk_force, recipe_jpk_force_map, \
recipe_jpk_force_qi
from .fmt_tab import recipe_tab
from .fmt_ntmdt_txt import recipe_ntmdt_txt
from .fmt_workshop import recipe_workshop_single, recipe_workshop_map
from ..mod_force_distance import AFMForceDistance
from ..mod_creep_compliance import AFMCreepCompliance
__all__ = ["AFMFormatRecipe", "find_data", "get_recipe", "load_data",
"default_data_classes_by_modality", "formats_available",
"formats_by_suffix", "formats_by_modality", "supported_extensions"]
[docs]def find_data(path, modality=None):
"""Recursively find valid AFM data files
Parameters
----------
path: str or pathlib.Path
file or directory
modality: str
modality of the measurement ("force-distance")
Returns
-------
file_list: list of pathlib.Path
list of valid AFM data files
"""
path = pathlib.Path(path)
file_list = []
if path.is_dir():
# recurse into subdirectories
for pp in path.rglob("*"):
if pp.is_file():
file_list += find_data(pp, modality=modality)
else:
try:
get_recipe(path=path, modality=modality)
except errors.FileFormatNotSupportedError:
# not a valid file format
pass
else:
# valid file format
file_list.append(path)
return file_list
[docs]def get_recipe(path, modality=None):
"""Return the file format recipe for a given path
Parameters
----------
path: str or pathlib.Path
file or directory
modality: str
modality of the measurement (see :const:`IMAGING_MODALITIES`)
Returns
-------
recipe: AFMFormatRecipe
file format recipe
"""
path = pathlib.Path(path)
if path.suffix not in formats_by_suffix:
raise errors.FileFormatNotSupportedError(
f"No recipe for suffix '{path.suffix}' (file '{path}')!")
recipes = formats_by_suffix[path.suffix]
for rec in recipes:
if ((modality is None or modality in rec.modalities)
and rec.detect(path)):
break
else:
raise errors.FileFormatNotSupportedError(
f"Could not determine file format recipe for '{path}'!")
return rec
[docs]def load_data(path, meta_override=None, modality=None,
data_classes_by_modality=None, diskcache=False,
callback=None):
"""Load AFM data
Parameters
----------
path: str or pathlib.Path
Path to AFM data file
meta_override: dict
Metadata dictionary that overrides experimental metadata
modality: str
Which acquisition modality to use (e.g. "force-distance")
data_classes_by_modality: dict
Override the default AFMData class to use for managing the data
(see :data:`default_data_classes_by_modality`): This is e.g.
used by :ref:`nanite:index` to pass `Indentation` (which is a
subclass of the default `AFMForceDistance`) for handling
"force-indentation" data.
diskcache: bool
Whether to use caching (not implemented)
callback: callable
A method that accepts a float between 0 and 1
to externally track the process of loading the data
Returns
-------
afm_list: list of afmformats.afm_data.AFMData
List where each element is on AFMData curve
"""
if meta_override is None:
meta_override = {}
if data_classes_by_modality is None:
data_classes_by_modality = {}
path = pathlib.Path(path)
if path.suffix in formats_by_suffix:
afmdata = []
cur_recipe = get_recipe(path, modality=modality)
loader = cur_recipe.loader
if modality is None:
modality = cur_recipe.get_modality(path)
fix_modality = False
else:
fix_modality = True
if modality in data_classes_by_modality:
afm_data_class = data_classes_by_modality[modality]
else:
afm_data_class = default_data_classes_by_modality[modality]
for dd in loader(path, callback=callback,
meta_override=meta_override):
dd["metadata"]["format"] = "{} ({})".format(cur_recipe["maker"],
cur_recipe["descr"])
if fix_modality and dd["metadata"]["imaging mode"] != modality:
# The user explicitly requested this modality.
continue
ddi = afm_data_class(data=dd["data"],
metadata=dd["metadata"],
diskcache=diskcache)
afmdata.append(ddi)
else:
raise ValueError("Unsupported file extension: '{}'!".format(path))
return afmdata
def register_format(recipe):
"""Registers a file format from a recipe dictionary"""
afr = AFMFormatRecipe(recipe)
formats_available.append(afr)
# suffix
if afr.suffix not in formats_by_suffix:
formats_by_suffix[afr.suffix] = []
formats_by_suffix[afr.suffix].append(afr)
# modality
for modality in afr.modalities:
if modality not in formats_by_modality:
formats_by_modality[modality] = []
formats_by_modality[modality].append(afr)
# supported extensions
if afr.suffix not in supported_extensions: # avoid duplicates
supported_extensions.append(afr.suffix)
supported_extensions.sort()
#: dictionary with default data classes for each modality
default_data_classes_by_modality = {
"force-distance": AFMForceDistance,
"creep-compliance": AFMCreepCompliance,
}
#: available/supported file formats
formats_available = []
#: available file formats in a dictionary with suffix keys
formats_by_suffix = {}
#: available file formats in a dictionary for each modality
formats_by_modality = {}
#: list of supported extensions
supported_extensions = []
for _recipe in [
recipe_hdf5,
recipe_ibw,
recipe_jpk_force,
recipe_jpk_force_map,
recipe_jpk_force_qi,
recipe_ntmdt_txt,
recipe_tab,
recipe_workshop_map,
recipe_workshop_single,
]:
register_format(_recipe)