"""
..
Copyright (c) 2014-2017, Magni developers.
All rights reserved.
See LICENSE.rst for further information.
Module providing the common functionality of the subpackage.
"""
from __future__ import division
from collections import OrderedDict as _OrderedDict
from magni.utils.types import ClassProperty as _classproperty
from magni.utils.types import ReadOnlyDict as _ReadOnlyDict
from magni.utils.validation import decorate_validation as _decorate_validation
from magni.utils.validation import validate_generic as _generic
from magni.utils.validation import validate_levels as _levels
[docs]class BaseClass(object):
"""
Base class of every `magni.afm.types` data class.
The present class validates the attributes passed to its constructor
against the allowed attributes of the class of the given instance and
exposes these attributes through a read-only dictionary property, `attrs`.
Furthermore, the present class exposes the allowed attributes of the class
of the given instance through a read-only dictionary static property,
`params`.
Parameters
----------
attrs : dict
The desired attributes of the instance.
Attributes
----------
attrs : magni.utils.types.ReadOnlyDict
The attributes of the instance.
params : magni.utils.types.ReadOnlyDict
The allowed attributes of the instance.
Examples
--------
An example could be the subclass, 'Person' which allows only the string
attribute, name:
>>> from magni.afm.types._util import BaseClass
>>> class Person(BaseClass):
... def __init__(self, attrs):
... BaseClass.__init__(self, attrs)
... _params = {'name': str}
This class can then be initiated with a name:
>>> person = Person({'name': 'Murphy'})
>>> print('{!r}'.format(person.attrs['name']))
'Murphy'
Any other attributes, than 'name', passed to the class are ignored:
>>> person = Person({'name': 'Murphy', 'age': 42})
>>> for name in person.attrs.keys():
... print('{!r}'.format(name))
'name'
"""
def __init__(self, attrs):
@_decorate_validation
def validate_input():
_generic('attrs', 'mapping')
params = self.params
for name in attrs.keys():
if name in params.keys():
_generic(('attrs', name), params[name])
validate_input()
names = self.params.keys()
attrs = {name: value for name, value in attrs.items() if name in names}
self._attrs = _ReadOnlyDict(attrs)
attrs = property(lambda self: self._attrs)
@_classproperty
def params(class_):
"""
Get the allowed attributes of the class (of the instance).
Which attributes are allowed is specified by the class and superclasses
(of the instance).
Returns
-------
params : magni.utils.types.ReadOnlyDict
The allowed attributes.
Notes
-----
The allowed attributes of the class (of the given instance) are found
by inspecting the class and its base classes for the static variable
'_params' and collecting these in a read-only dictionary.
"""
params = []
while class_ != BaseClass:
if hasattr(class_, '_params'):
params.extend(class_._params.items())
class_ = class_.__base__
return _ReadOnlyDict(params)
[docs]class File(BaseClass):
"""
Base class of the `magni.afm.types` file classes.
The present class specifies the allowed file-level attributes and exposes
the read-only property, buffers which all .mi files have. Furthermore, the
present class provides a method for accessing buffers by their
'bufferLabel' attribute.
Parameters
----------
attrs : list or tuple
The desired attributes of the file.
buffers : list or tuple
The buffers of the file.
Attributes
----------
buffers : tuple
The buffers of the file.
See Also
--------
BaseClass : Superclass of the present class.
Examples
--------
A subclass of the present class is implicitly instantiated when loading,
for example, the .mi file provided with the package:
>>> import os, magni
>>> path = magni.utils.split_path(magni.__path__[0])[0]
>>> path = path + 'examples' + os.sep + 'example.mi'
>>> if os.path.isfile(path):
... file_ = magni.afm.io.read_mi_file(path)
This file has a number of buffers which each has the 'bufferLabel'
attribute:
>>> if os.path.isfile(path):
... for buffer_ in file_.buffers[::2][:3]:
... label = buffer_.attrs['bufferLabel']
... print("Buffer with 'bufferLabel': {!r}".format(label))
... else:
... for label in ('Topography', 'Deflection', 'Friction'):
... print("Buffer with 'bufferLabel': {!r}".format(label))
Buffer with 'bufferLabel': 'Topography'
Buffer with 'bufferLabel': 'Deflection'
Buffer with 'bufferLabel': 'Friction'
If only, for example, buffers with 'bufferLabel' equal to 'Topography' are
desired, the method, `get_buffer` can be called:
>>> if os.path.isfile(path):
... buffers = len(file_.get_buffer('Topography'))
... print("Buffers with 'bufferLabel' == 'Topography': {}"
... .format(buffers))
... else:
... print("Buffers with 'bufferLabel' == 'Topography': 2")
Buffers with 'bufferLabel' == 'Topography': 2
"""
_params = _OrderedDict((('fileType', str),
('dateAcquired', str),
('xOffset', float),
('yOffset', float),
('xLength', float),
('yLength', float),
('data', str)))
def __init__(self, attrs, buffers):
@_decorate_validation
def validate_input():
_generic('attrs', 'mapping', has_keys=('fileType',))
_levels('buffers', (_generic(None, 'explicit collection'),
_generic(None, BaseClass)))
BaseClass.__init__(self, attrs)
validate_input()
self._buffers = tuple(buffers)
buffers = property(lambda self: self._buffers)
[docs] def get_buffer(self, label):
"""
Get the buffers that have the specified buffer label.
Parameters
----------
label : str
The desired buffer label of the buffer.
Returns
-------
buffers : list
The buffers that have the desired buffer label.
"""
@_decorate_validation
def validate_input():
_generic('label', 'string')
validate_input()
buffers = []
for buffer_ in self.buffers:
if ('bufferLabel' in buffer_.attrs.keys() and
buffer_.attrs['bufferLabel'] == label):
buffers.append(buffer_)
return buffers
[docs]class FileCollection(BaseClass):
"""
Data class for collections of File instances with identical settings.
The settings are the following attributes: 'fileType', 'mode', 'xPixels',
'yPixels', 'xOffset', 'yOffset', 'xLength', 'yLength', 'scanSpeed',
'acMac', 'acACMode', 'plotType'.
The present class exposes the files of the collection through a read-only
tuple property, `files` and the paths of these files through a read-only
tuple property, `paths`.
Parameters
----------
files : list or tuple
The files of the collection.
paths : list or tuple
The paths of the files of the collection.
Attributes
----------
files : tuple
The files of the collection.
paths : tuple
The paths of the files of the collection.
See Also
--------
BaseClass : Superclass of the present class.
Examples
--------
No example .mi file collection is distributed with `magni`.
"""
_params = _OrderedDict((('fileType', str),
('mode', str),
('xPixels', int),
('yPixels', int),
('xOffset', float),
('yOffset', float),
('xLength', float),
('yLength', float),
('scanSpeed', float),
('acMac', bool),
('acACMode', bool),
('plotType', str)))
def __init__(self, files, paths):
@_decorate_validation
def validate_input():
_levels('files', (_generic(None, 'explicit collection'),
_generic(None, File)))
if len(files) < 1:
msg = 'The value of >>len(files)<<, {!r}, must be > 0.'
raise ValueError(msg.format(len(files)))
attrs = [{name: value for name, value in file_.attrs.items()
if name in self._params} for file_ in files]
for i in range(1, len(files)):
if attrs[i] != attrs[0]:
raise ValueError('The values of >>files<< must have the '
'same settings.')
_levels('paths', (_generic(None, 'explicit collection',
len_=len(files)),
_generic(None, 'string')))
# validate before calling the superclass constructor to ensure that
# 'files[0].attrs' exists
validate_input()
BaseClass.__init__(self, files[0].attrs)
self._files = tuple(files)
self._paths = tuple(paths)
files = property(lambda self: self._files)
paths = property(lambda self: self._paths)