Source code for omf.base

"""base.py: OMF Project and base classes for its components"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from collections import OrderedDict
import datetime

import properties


[docs]class UidModel(properties.HasProperties): """UidModel is a HasProperties object with uid""" _REGISTRY = OrderedDict() uid = properties.Uuid( 'Unique identifier', serializer=lambda val, **kwargs: None, deserializer=lambda val, **kwargs: None ) date_created = properties.GettableProperty( 'Date project was created', default=datetime.datetime.utcnow, serializer=properties.DateTime.to_json, deserializer=lambda val, **kwargs: None ) date_modified = properties.GettableProperty( 'Date project was modified', default=datetime.datetime.utcnow, serializer=properties.DateTime.to_json, deserializer=lambda val, **kwargs: None ) @properties.observer(properties.everything) def _modify(self, _): """Update date_modified whenever anything changes""" self._backend['date_modified'] = datetime.datetime.utcnow() @properties.validator def _update_date_modified(self): """Update date_modified if any contained UidModel has been modified""" for val in self._backend.values(): if ( isinstance(val, UidModel) and val.date_modified > self.date_modified ): self._backend['date_modified'] = val.date_modified def serialize(self, include_class=True, registry=None, #pylint: disable=arguments-differ skip_validation=False, **kwargs): """Serialize nested UidModels to a flat dictionary with pointers""" if registry is None: if not skip_validation: self.validate() registry = dict() root = True else: root = False if str(self.uid) not in registry: registry.update({ str(self.uid): super(UidModel, self).serialize( include_class, registry=registry, **kwargs ) }) if root: return registry return str(self.uid) @classmethod def deserialize(cls, uid, trusted=True, registry=None, **kwargs): #pylint: disable=arguments-differ """Deserialize nested UidModels from flat pointer dictionary""" if registry is None: raise ValueError('no registry provided') if uid not in registry: raise ValueError('uid not found: {}'.format(uid)) if not isinstance(registry[uid], UidModel): date_created = registry[uid]['date_created'] date_modified = registry[uid]['date_modified'] kwargs.update({'verbose': False}) new_model = super(UidModel, cls).deserialize( value=registry[uid], registry=registry, trusted=trusted, **kwargs ) new_model._backend.update({ 'uid': properties.Uuid.from_json(uid), 'date_created': properties.DateTime.from_json(date_created), 'date_modified': properties.DateTime.from_json(date_modified) }) registry.update({uid: new_model}) return registry[uid]
[docs]class ContentModel(UidModel): """ContentModel is a UidModel with title and description""" name = properties.String( 'Title', default='' ) description = properties.String( 'Description', default='' )
[docs]class ProjectElementData(ContentModel): """Data array with values at specific locations on the mesh""" location = properties.StringChoice( 'Location of the data on mesh', choices=('vertices', 'segments', 'faces', 'cells') ) @property def array(self): """Data subclasses should override array with their data array""" raise ValueError('Cannot access array of base ProjectElementData')
class ProjectElementGeometry(UidModel): """Base class for all ProjectElement meshes""" _valid_locations = None origin = properties.Vector3( 'Origin of the Mesh relative to origin of the Project', default=[0., 0., 0.] ) def location_length(self, location): """Return correct data length based on location""" raise NotImplementedError() @property def num_nodes(self): """get number of nodes""" raise NotImplementedError() @property def num_cells(self): """get number of cells""" raise NotImplementedError()
[docs]class ProjectElement(ContentModel): """Base ProjectElement class for OMF file ProjectElement subclasses must define their mesh. ProjectElements include PointSet, LineSet, Surface, and Volume """ data = properties.List( 'Data defined on the element', prop=ProjectElementData, required=False, default=list, ) color = properties.Color( 'Solid color', default='random' ) geometry = None @properties.validator def _validate_data(self): """Check if element is built correctly""" assert self.geometry is not None, 'ProjectElement must have a mesh' for i, dat in enumerate(self.data): if dat.location not in self.geometry._valid_locations: #pylint: disable=protected-access raise ValueError( 'Invalid location {loc} - valid values: {locs}'.format( loc=dat.location, locs=', '.join(self.geometry._valid_locations) #pylint: disable=protected-access ) ) valid_length = self.geometry.location_length(dat.location) if len(dat.array) != valid_length: raise ValueError( 'data[{index}] length {datalen} does not match ' '{loc} length {meshlen}'.format( index=i, datalen=len(dat.array), loc=dat.location, meshlen=valid_length ) ) return True
[docs]class Project(ContentModel): """OMF Project for serializing to .omf file""" author = properties.String( 'Author', default='' ) revision = properties.String( 'Revision', default='' ) date = properties.DateTime( 'Date associated with the project data', required=False ) units = properties.String( 'Spatial units of project', default='' ) elements = properties.List( 'Project Elements', prop=ProjectElement, default=list, ) origin = properties.Vector3( 'Origin point for all elements in the project', default=[0., 0., 0.] )