"""
..
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