Source code for magni.afm.io._object_building

"""
..
    Copyright (c) 2014-2017, Magni developers.
    All rights reserved.
    See LICENSE.rst for further information.

Module providing functionality for building a hierarchical object-structure.

The reading of an .mi file is logically separated into four steps of which the
functionality provided by this module performs the second step.

Routine listings
----------------
build_object(params)
    Build a hierarchical object-structure from header parameters.

"""

from __future__ import division
from collections import OrderedDict as _OrderedDict

from magni.afm.types.spectroscopy import Chunk as _Chunk
from magni.afm.types.spectroscopy import Grid as _Grid
from magni.afm.types.spectroscopy import Point as _Point


[docs]def build_object(params): """ Build a hierarchical object-structure from header parameters. The hierarchical object-structure mimics that of `magni.afm.types`. Parameters ---------- params : list or tuple The header parameters. Returns ------- obj : object A hierarchical object-structure. See Also -------- magni.afm.io.read_mi_file : Function using the present function. Notes ----- This function splits the header parameters into file-related parameters and buffer-related parameters. """ groups = [[]] for name, value in params: if name == 'bufferLabel': groups.append([]) groups[-1].append((name, value)) groups[0].append(groups[-1].pop()) attrs = dict(groups[0]) for name in ('xPixels', 'yPixels'): if name not in attrs.keys(): raise IOError('The file must have a {!r} header parameter.' .format(name)) if not isinstance(attrs[name], int): raise IOError('The {!r} file header must have type {!r}.' .format(name, int)) buffers = [_build_buffer(attrs['fileType'], index, params_) for index, params_ in enumerate(groups[1:])] if len(buffers) == 0: raise IOError("The file must have at least one 'bufferLabel' header.") if attrs['fileType'].lower() == 'spectroscopy': _expand_buffers(buffers) obj = {'attrs': attrs, 'buffers': buffers} return _handle_format_inconsistency(obj)
[docs]def _build_buffer(file_type, index, params): """ Build a buffer-like object-structure from buffer parameters. For spectroscopy buffers, this function converts parameters which contain "sub-parameters" to objects with attributes. Parameters ---------- file_type : str The .mi file type. index : int The index of the buffer. params : list or tuple The buffer parameters. Returns ------- obj : object The buffer-like object-structure. See Also -------- build_object : Function using the present function. """ if file_type.lower() == 'image': buffer_ = {'attrs': dict(params)} else: # 'spectroscopy' allowed_params = ('chunk',) if index > 0 else ('grid', 'point') multiparams = {'grid': {'types': _Grid.params, 'min_len': 9}, 'point': {'types': _Point.params, 'min_len': 3}, 'chunk': {'types': _Chunk.params, 'min_len': 7}} # grid xDirection and yDirection are stored as integers multiparams['grid']['types'] = _OrderedDict(_Grid.params.items()) multiparams['grid']['types']['xDirection'] = int multiparams['grid']['types']['yDirection'] = int attrs = [] data = [] for name, value in params: if name in multiparams.keys(): if name not in allowed_params: raise IOError('Buffer #{!r} may not contain {!r} headers.' .format(index, name)) types = multiparams[name]['types'] min_len = multiparams[name]['min_len'] params = [] if len(value) < min_len: msg = ('Each {!r} buffer header must contain at least ' '{!r} values.') raise IOError(msg.format(name, len(types))) for value, param, type_ in zip(value, *zip(*types.items())): if not isinstance(value, type_): msg = ('The values of each {!r} buffer header must ' 'have types {!r}.') raise IOError(msg.format(name, types)) params.append((param, value)) data.append({'type': name, 'attrs': dict(params)}) else: attrs.append((name, value)) buffer_ = {'attrs': dict(attrs), 'data': data} return buffer_
[docs]def _expand_buffers(buffers): """ Expand the buffer-like object-structures. Grid parameters specify implicit point parameters. Furthermore, some chunk parameters may be implicitly specified. These implicit parameters should be made explicit. Parameters ---------- buffers : list or tuple The buffer-like object-structures. See Also -------- build_object : Function using the present function. _generate_grid_points : Make implicit point parameters explicit. _generate_implicit_chunks : Make implicit chunk parameters explicit. Notes ----- This function relies on the two functions, `_generate_grid_points` and `_generate_implicit_chunks` to make the implicit parameters explicit. """ indices = {'grid': 0, 'point': 0} for i, item in enumerate(buffers[0]['data']): if item['attrs']['index'] != indices[item['type']]: raise IOError('The {!r} buffer parameter indices are erroneous.' .format(item['type'])) if item['type'] == 'grid': try: attr = 'xDirection' item['attrs'][attr] = ('trace', 'retrace')[item['attrs'][attr]] attr = 'yDirection' item['attrs'][attr] = ('up', 'down')[item['attrs'][attr]] except IndexError: msg = "The {!r} of 'grid' buffer parameters must be in {!r}." raise IOError(msg.format(attr, (0, 1))) points = _generate_grid_points(item['attrs'], indices['point']) item['points'] = points indices['point'] = indices['point'] + len(points) * len(points[0]) indices[item['type']] = indices[item['type']] + 1 for buffer_ in buffers[1:]: buffer_['data'] = _generate_implicit_chunks(indices['point'], buffer_['data'])
[docs]def _generate_grid_points(attrs, index): """ Make implicit grid point parameters explicit. Parameters ---------- attrs : list or tuple The attributes of the grid. index : int The index of the first grid point. Returns ------- points : tuple The grid points. See Also -------- _expand_buffers : Function using the present function. """ points = [] spacing = attrs['pointSpacing'] x_start = attrs['xCenter'] - (attrs['xPoints'] - 1) / 2 * spacing y_start = attrs['yCenter'] - (attrs['yPoints'] - 1) / 2 * spacing x_dir = slice(None, None, 1 if attrs['xDirection'] == 'trace' else -1) y_dir = slice(None, None, 1 if attrs['yDirection'] == 'down' else -1) for y_index in range(attrs['yPoints']).__getitem__(y_dir): points.append([]) y = y_start + y_index * spacing for x_index in range(attrs['xPoints']).__getitem__(x_dir): x = x_start + x_index * spacing point = {'index': index, 'xCoordinate': x, 'yCoordinate': y} points[-1].append({'attrs': point}) index = index + 1 points[-1] = points[-1].__getitem__(x_dir) points = points.__getitem__(y_dir) return points
[docs]def _generate_implicit_chunks(npoints, explicit_chunks): """ Make implicit chunk parameters explicit. Parameters ---------- npoints : int The number of points. explicit_chunks : list The explicit chunks. Returns ------- chunks : list The chunks including both explicit and implicit chunks. See Also -------- _expand_buffer : Function using the present function. """ chunks = [] point_index = 0 point_chunks = [] stop_chunk = {'attrs': {'pointIndex': npoints, 'label': '_emptychunk_'}} for chunk in explicit_chunks + [stop_chunk]: index = chunk['attrs'].get('pointIndex', 0) for point_index in range(point_index + 1, index): for point_chunk in point_chunks: point_chunk = point_chunk.copy() point_chunk['attrs'] = point_chunk['attrs'].copy() point_chunk['attrs']['pointIndex'] = point_index chunks.append(point_chunk) if (chunk['attrs'].get('label', '').lower() == '_emptychunk_' and index <= npoints): break if point_index < index < npoints: point_index = point_index + 1 point_chunks = [] if index != point_index: raise IOError("The 'chunk' buffer parameter indices are " "erroneous.") chunks.append(chunk) point_chunks.append(chunk) return chunks
[docs]def _handle_format_inconsistency(obj): """ Handle format inconsistency. The inconsistency is the usage of string values for the 'trace' header parameter which is specified to have a boolean value. Parameters ---------- obj : object The hierarchical object-structure. Returns ------- obj : object The updated hierarchical object-structure. See Also -------- build_object : Function using the present function. """ if obj['attrs']['fileType'].lower() == 'image': for buffer_ in obj['buffers']: if ('trace' in buffer_['attrs'].keys() and isinstance(buffer_['attrs']['trace'], str)): if buffer_['attrs']['trace'].lower() == 'trace': buffer_['attrs']['trace'] = True elif buffer_['attrs']['trace'].lower() == 'retrace': buffer_['attrs']['trace'] = False return obj