Source code for steno3d.data

"""data.py contains resource data structures"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from json import dumps
import random
from six import string_types

import numpy as np
import properties

from .base import BaseData
from .props import array_serializer, array_download


[docs]class DataArray(BaseData): """Data array with unique values at every point in the mesh .. note: DataArray custom colormap is currently unsupported on steno3d.com """ _resource_class = 'array' array = properties.Array( doc='Data, unique values at every point in the mesh', shape=('*',), dtype=(float, int), serializer=array_serializer, deserializer=array_download(('*',), (float, int)), ) order = properties.StringChoice( doc='Data array order, for data on grid meshes', choices={ 'c': ('C-STYLE', 'NUMPY', 'ROW-MAJOR', 'ROW'), 'f': ('FORTRAN', 'MATLAB', 'COLUMN-MAJOR', 'COLUMN', 'COL') }, default='c', ) colormap = properties.List( doc='Colormap applied to data range or categories', prop=properties.Color(''), min_length=1, max_length=256, required=False, default=properties.undefined, ) def __init__(self, array=None, **kwargs): super(DataArray, self).__init__(**kwargs) if array is not None: self.array = array def _nbytes(self, arr=None): if arr is None or (isinstance(arr, string_types) and arr == 'array'): arr = self.array if isinstance(arr, np.ndarray): return arr.astype('f4').nbytes raise ValueError('DataArray cannot calculate the number of ' 'bytes of {}'.format(arr)) @properties.observer('array') def _reject_large_files(self, change): self._validate_file_size(change['name'], change['value']) @properties.validator def _validate_array(self): self._validate_file_size('array', self.array) return True def _get_dirty_data(self, force=False): datadict = super(DataArray, self)._get_dirty_data(force) dirty = self._dirty_props if 'order' in dirty or force: datadict['order'] = self.order if self.colormap and ('colormap' in dirty or force): datadict['colormap'] = dumps(self.colormap) return datadict def _get_dirty_files(self, force=False): files = super(DataArray, self)._get_dirty_files(force) dirty = self._dirty_props if 'array' in dirty or force: files['array'] = self._props['array'].serialize(self.array) return files @classmethod def _build_from_json(cls, json, **kwargs): data = DataArray( title=kwargs['title'], description=kwargs['description'], order=json['order'], array=cls._props['array'].deserialize( json['array'], ) ) if json.get('colormap'): data.colormap = json['colormap'] return data @classmethod def _build_from_omf(cls, omf_data): assert omf_data.__class__.__name__ in ('ScalarData', 'MappedData') data = dict( location='N' if omf_data.location == 'vertices' else 'CC', data=DataArray( title=omf_data.name, description=omf_data.description, array=omf_data.array.array ) ) return data
def index_serializer(data, **kwargs): """Serializes int indices as floats, where -1 is replaced with NaN""" data = data.astype(float) data = np.where(data == -1.0, np.nan, data) return array_serializer(data, **kwargs) class index_download(array_download): """Download index array as floats and convert to ints This replaces NaN values with -1 """ def __init__(self, shape): self.shape = shape self.dtype = (float,) def __call__(self, url, **kwargs): arr = super(index_download, self).__call__(url, **kwargs) arr = np.where(np.isnan(arr), -1, arr) arr = arr.astype(int) return arr
[docs]class DataCategory(DataArray): """Data array with indices and corresponding string categories For locations with no data, use -1 for index. If colormap is unspecified, colors will be randomized. """ _resource_class = 'category' array = properties.Array( doc='Category index values at every point in the mesh', shape=('*',), dtype=(int,), serializer=index_serializer, deserializer=index_download(('*',)), ) categories = properties.List( doc='List of string categories', prop=properties.String(''), min_length=1, max_length=256, required=False, default=properties.undefined, ) @properties.validator def _categories_and_colormap(self): if ( (self.categories and self.colormap) and len(self.categories) != len(self.colormap) ): raise ValueError('categories and colormap must be equal length') @properties.validator def _categories_and_array(self): if min(self.array) < -1: raise ValueError('array indices must be >= -1') if max(self.array) >= 256: raise ValueError('array indices must be < 256') if self.categories and max(self.array) >= len(self.categories): raise ValueError('array indices must be < len(categories)') if self.colormap and max(self.array) >= len(self.colormap): raise ValueError('array indices must be < len(colormap)') @properties.validator def _populate_colormap(self): if self.colormap: return self._categories_and_array() self.colormap = self._random_colormap() @properties.validator def _populate_categories(self): if self.categories: return self._categories_and_array() if self.categories: cat_len = len(self.categories) else: cat_len = max(self.array) + 1 self.categories = ['']*cat_len @properties.validator('array') def _array_gt_zero(self, change): if min(change['value']) < -1: raise ValueError('array indices must be >= -1') def _get_dirty_data(self, force=False): datadict = super(DataCategory, self)._get_dirty_data(force) dirty = self._dirty_props if 'categories' in dirty or force: datadict['categories'] = dumps(self.categories) return datadict @classmethod def _build_from_json(cls, json, **kwargs): data = DataCategory( title=kwargs['title'], description=kwargs['description'], order=json['order'], array=cls._props['array'].deserialize( json['array'], ), colormap=json['colormap'], categories=json['categories'], ) return data def _random_colormap(self): if self.categories: map_len = len(self.categories) elif self.array is not None: map_len = max(self.array) + 1 else: raise ValueError('categories or array indeces are required for ' 'random colormap') if map_len <= len(properties.basic.COLORS_20): return random.sample(properties.basic.COLORS_20, map_len) if map_len <= len(properties.basic.COLORS_NAMED): return random.sample(list(properties.basic.COLORS_NAMED), map_len) if map_len > 256**3: raise ValueError('max colormap length must be less than 256**3') map_ints = random.sample(range(256**3), map_len) def color_from_int(value): r = int(value % 256) g = int((value-r)/256 % 256) b = int(((value-r)/256-g)/256 % 256) return (r, g, b) return [color_from_int(value) for value in map_ints]
__all__ = ['DataArray', 'DataCategory']