Source code for afmformats.formats.fmt_jpk.jpk_data

import numpy as np

from ...errors import FileFormatNotSupportedError


__all__ = ["JPK_COLUMNS",
           "JPK_SLOTS",
           "JPK_UNITS",
           "ReadJPKError",
           "find_column_dat",
           "load_dat_raw",
           "load_dat_unit",
           ]

#: Maps afmformats column names to JPK column names
JPK_COLUMNS = {
    "force": ["vDeflection"],
    "height (measured)": ["strainGaugeHeight", "capacitiveSensorHeight",
                          "measuredHeight"],
    "height (piezo)": ["height", "head-height"],
}

#: Maps afmformats column names to default JPK normalization slots
JPK_SLOTS = {
    "force": "force",
    "height (measured)": "nominal",
    "height (piezo)": "calibrated",
}

#: Maps afmformats column names to default JPK units
JPK_UNITS = {
    "force": "N",
    "height (measured)": "m",
    "height (piezo)": "m",
}


[docs]class ReadJPKError(FileFormatNotSupportedError): pass
[docs]def find_column_dat(loc_list, column): """Find a column in a list of strings Parameters ---------- loc_list: list of str The segment's data location list (within the archive), e.g. ['segments/0/channels/height.dat', 'segments/0/channels/vDeflection.dat', 'segments/0/channels/strainGaugeHeight.dat'] column: str afmformats column name :const:`afmformats.afm_data.known_columns` Returns ------- name: str Matched column name from :const:`JPK_COLUMNS`, e.g. "strainGaugeHeight" slot: str Default slot location from :const:`JPK_SLOTS` loc: str Matched data location in zip file, e.g. "segments/0/channels/strainGaugeHeight.dat" """ id_list = JPK_COLUMNS[column] col = None for dd in loc_list: cn = dd.rsplit("/")[-1].rsplit(".")[0] for vc in id_list: if vc == cn: col = vc break if col: break else: msg = "No data found for: {}".format(id_list) raise ReadJPKError(msg) slot = JPK_SLOTS[column] return col, slot, dd
def get_property(description, keys, properties): """Helper function to access data from property dictionaries Parameters ---------- description: str The human readable name of the property key. Used for error handling only to give the user an idea of what went wrong. keys: list of str List of potential property keys properties: dict Property dictionary metadata (see also :func:`JPKReader._get_index_segment_properties`) Raises ------ ReadJPKError if a key could not be found in `property_dict` """ for mk in keys: if mk in properties: mult = properties[mk] break else: raise ReadJPKError(f"Could not find property: '{description}'.\n" "Please make sure you are working with raw data " "and not with data exported from the JPK software.") return mult
[docs]def load_dat_raw(fd, name, properties): """Load data from binary JPK .dat files Parameters ---------- fd: file Open .dat file name: str Name of the data to read (required for scale conversions) (valid options are values in :const:`JPK_COLUMNS`) properties: dict Property dictionary metadata (see also :func:`JPKReader._get_index_segment_properties`) Returns ------- data: 1d ndarray A numpy array with the raw data. Notes ----- This method tries to correctly determine the data type of the binary data and scales it with the `data.encoder.scaling` values given in the header files. See Also -------- load_dat_unit: Includes conversion to useful units """ # Multiplier mult = get_property( description=f"{name} multiplier in {fd}", keys=[f"channel.{name}.data.encoder.scaling.multiplier", f"channel.{name}.encoder.scaling.multiplier"], properties=properties) # Offset off = get_property( description=f"{name} offset in {fd}", keys=[f"channel.{name}.data.encoder.scaling.offset", f"channel.{name}.encoder.scaling.offset"], properties=properties) # Data type enc = get_property( description=f"{name} offset in {fd}", keys=[f"channel.{name}.data.encoder.type", f"channel.{name}.encoder.type"], properties=properties) # determine encoder if enc == "signedshort": mydtype = np.dtype(">i2") elif enc == "unsignedshort": mydtype = np.dtype(">u2") elif enc == "signedinteger": mydtype = np.dtype(">i4") elif enc == "unsignedinteger": mydtype = np.dtype(">u4") elif enc == "signedlong": mydtype = np.dtype(">i8") else: raise NotImplementedError("Data file format '{}' not supported". format(enc)) data = np.frombuffer(fd.read(), dtype=mydtype) * mult + off return data
[docs]def load_dat_unit(fd, name, properties, slot="default"): """Load data from a JPK .dat file with a specific calibration slot Parameters ---------- fd: file Open .dat file name: str Name of the data to read (required for scale conversions) (valid options are values in :const:`JPK_COLUMNS`) properties: dict Property dictionary metadata (see also :func:`JPKReader._get_index_segment_properties`) slot: str The .dat files in the JPK measurement zip files come with different calibration slots. Valid values are - For the height of the piezo crystal during measurement (the piezo height is not as accurate as the measured height from the height sensor; the piezo movement is not linear): "height.dat": "volts", "nominal", "calibrated" - For the measured height of the cantilever: "strainGaugeHeight.dat": "volts", "nominal", "absolute" "measuredHeight.dat": "volts", "nominal", "absolute" "capacitiveSensorHeight": "volts", "nominal", "absolute" (they are all the same) - For the recorded cantilever deflection: "vDeflection.dat": "volts", "distance", "force" Returns ------- data: 1d ndarray A numpy array containing the scaled data. unit: str A string representing the metric unit of the data. name: str The name of the data column. Notes ----- The raw data (see `load_dat_raw`) is usually stored in "volts" and needs to be converted to e.g. "force" for "vDeflection" or "nominal" for "strainGaugeHeight". The conversion parameters (offset, multiplier) are stored in the header files and they are not stored separately for each slot, but the conversion parameters are stored relative to the slots. For instance, to compute the "force" slot from the raw "volts" data, one first needs to compute the "distance" slot. This conversion is taken care of by this method. This is an example header: channel.vDeflection.data.file.name=channels/vDeflection.dat channel.vDeflection.data.file.format=raw channel.vDeflection.data.type=short channel.vDeflection.data.encoder.type=signedshort channel.vDeflection.data.encoder.scaling.type=linear channel.vDeflection.data.encoder.scaling.style=offsetmultiplier channel.vDeflection.data.encoder.scaling.offset=-0.00728873489143207 channel.vDeflection.data.encoder.scaling.multiplier=3.0921021713588157E-4 channel.vDeflection.data.encoder.scaling.unit.type=metric-unit channel.vDeflection.data.encoder.scaling.unit.unit=V channel.vDeflection.channel.name=vDeflection channel.vDeflection.conversion-set.conversions.list=distance force channel.vDeflection.conversion-set.conversions.default=force channel.vDeflection.conversion-set.conversions.base=volts channel.vDeflection.conversion-set.conversion.volts.name=Volts channel.vDeflection.conversion-set.conversion.volts.defined=false channel.vDeflection.conversion-set.conversion.distance.name=Distance channel.vDeflection.conversion-set.conversion.distance.defined=true channel.vDeflection.conversion-set.conversion.distance.type=simple channel.vDeflection.conversion-set.conversion.distance.comment=Distance channel.vDeflection.conversion-set.conversion.distance.base-calibration-slot=volts channel.vDeflection.conversion-set.conversion.distance.calibration-slot=distance channel.vDeflection.conversion-set.conversion.distance.scaling.type=linear channel.vDeflection.conversion-set.conversion.distance.scaling.style=offsetmultiplier channel.vDeflection.conversion-set.conversion.distance.scaling.offset=0.0 channel.vDeflection.conversion-set.conversion.distance.scaling.multiplier=7.000143623002982E-8 channel.vDeflection.conversion-set.conversion.distance.scaling.unit.type=metric-unit channel.vDeflection.conversion-set.conversion.distance.scaling.unit.unit=m channel.vDeflection.conversion-set.conversion.force.name=Force channel.vDeflection.conversion-set.conversion.force.defined=true channel.vDeflection.conversion-set.conversion.force.type=simple channel.vDeflection.conversion-set.conversion.force.comment=Force channel.vDeflection.conversion-set.conversion.force.base-calibration-slot=distance channel.vDeflection.conversion-set.conversion.force.calibration-slot=force channel.vDeflection.conversion-set.conversion.force.scaling.type=linear channel.vDeflection.conversion-set.conversion.force.scaling.style=offsetmultiplier channel.vDeflection.conversion-set.conversion.force.scaling.offset=0.0 channel.vDeflection.conversion-set.conversion.force.scaling.multiplier=0.043493666407368466 channel.vDeflection.conversion-set.conversion.force.scaling.unit.type=metric-unit channel.vDeflection.conversion-set.conversion.force.scaling.unit.unit=N To convert from the raw "volts" data to force data, these steps are performed: - Convert from "volts" to "distance" first, because the "base-calibration-slot" for force is "distance". distance = volts*7.000143623002982E-8 + 0.0 - Convert from "distance" to "force": force = distance*0.043493666407368466 + 0.0 The multipliers shown above are the values for sensitivity and spring constant: sensitivity = 7.000143623002982E-8 m/V spring_constant = 0.043493666407368466 N/m """ data = load_dat_raw(fd, name=name, properties=properties) conv = f"channel.{name}.conversion-set" if slot == "default": slot = properties[f"{conv}.conversions.default"] # get base unit base = properties[f"{conv}.conversions.base"] # Now iterate through the conversion sets until we have the base converter. # A list of multipliers and offsets converters = [] curslot = slot while curslot != base: # Get current slot multipliers and offsets off = properties[f"{conv}.conversion.{curslot}.scaling.offset"] mult = properties[f"{conv}.conversion.{curslot}.scaling.multiplier"] converters.append([mult, off]) curslot = properties[ f"{conv}.conversion.{curslot}.base-calibration-slot" ] # Get raw data for c in converters[::-1]: data[:] = c[0] * data[:] + c[1] if base == slot: unit = properties[f"channel.{name}.data.encoder.scaling.unit.unit"] else: unit = get_property( description=f"scale conversion {name} for {slot} in {fd}", keys=[f"{conv}.conversion.{slot}.scaling.unit", f"{conv}.conversion.{slot}.scaling.unit.unit"], properties=properties) out_name = properties[f"{conv}.conversion.{slot}.name"] return data, unit, f"{name} ({out_name})"