"""data.py: different ProjectElementData classes"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import numpy as np
import properties
from .base import UidModel, ContentModel, ProjectElementData
from .serializers import array_serializer, array_deserializer
[docs]class ScalarArray(UidModel):
"""Class with unique ID and data array"""
array = properties.Array(
'Shared Scalar Array',
serializer=array_serializer,
deserializer=array_deserializer(('*',))
)
def __init__(self, array=None, **kwargs):
super(ScalarArray, self).__init__(**kwargs)
if array is not None:
self.array = array
def __len__(self):
return self.array.__len__()
def __getitem__(self, i):
return self.array.__getitem__(i)
[docs]class Vector2Array(ScalarArray):
"""Shared array of 2D vectors"""
array = properties.Vector2Array(
'Shared Vector2 Array',
serializer=array_serializer,
deserializer=array_deserializer(('*', 2))
)
[docs]class Vector3Array(ScalarArray):
"""Shared array of 3D vectors"""
array = properties.Vector3Array(
'Shared Vector3 Array',
serializer=array_serializer,
deserializer=array_deserializer(('*', 3))
)
[docs]class Int2Array(ScalarArray):
"""Shared n x 2 array of integers"""
array = properties.Array(
'Shared n x 2 Int Array',
dtype=int,
shape=('*', 2),
serializer=array_serializer,
deserializer=array_deserializer(('*', 2))
)
[docs]class Int3Array(ScalarArray):
"""Shared n x 3 array of integers"""
array = properties.Array(
'Shared n x 3 Int Array',
dtype=int,
shape=('*', 3),
serializer=array_serializer,
deserializer=array_deserializer(('*', 3))
)
[docs]class StringArray(ScalarArray):
"""Shared array of text strings"""
array = properties.List(
'Shared array of text strings',
prop=properties.String(''),
default=list,
)
[docs]class DateTimeArray(ScalarArray):
"""Shared array of DateTimes"""
array = properties.List(
'Shared array of DateTimes',
prop=properties.DateTime(''),
default=list,
)
[docs]class ColorArray(ScalarArray):
"""Shared array of Colors"""
array = properties.List(
'Shared array of Colors',
prop=properties.Color(''),
default=list,
)
[docs]class ScalarColormap(ContentModel):
"""Length-128 color gradient with min/max values, used with ScalarData"""
gradient = properties.Instance(
'length-128 ColorArray defining the gradient',
ColorArray
)
limits = properties.List(
'Data range associated with the gradient',
prop=properties.Float(''),
min_length=2,
max_length=2,
default=properties.undefined,
)
@properties.validator('gradient')
def _check_gradient_length(self, change): #pylint: disable=no-self-use
"""Ensure gradient is length-128"""
if len(change['value']) != 128:
raise ValueError('Colormap gradient must be length 128')
@properties.validator('limits')
def _check_limits_on_change(self, change): #pylint: disable=no-self-use
"""Ensure limits are valid"""
if change['value'][0] > change['value'][1]:
raise ValueError('Colormap limits[0] must be <= limits[1]')
@properties.validator
def _check_limits_on_validate(self):
"""Ensure limits are valid"""
self._check_limits_on_change({'value': self.limits})
[docs]class DateTimeColormap(ScalarColormap):
"""Length-128 color gradient with min/max values, used with DateTimeData"""
limits = properties.List(
'Data range associated with the gradient',
prop=properties.DateTime(''),
min_length=2,
max_length=2,
default=properties.undefined,
)
[docs]class ScalarData(ProjectElementData):
"""Data array with scalar values"""
array = properties.Instance(
'scalar values at locations on a mesh (see location parameter)',
ScalarArray
)
colormap = properties.Instance(
'colormap associated with the data',
ScalarColormap,
required=False
)
[docs]class Vector3Data(ProjectElementData):
"""Data array with 3D vectors"""
array = properties.Instance(
'3D vectors at locations on a mesh (see location parameter)',
Vector3Array
)
[docs]class Vector2Data(ProjectElementData):
"""Data array with 2D vectors"""
array = properties.Instance(
'2D vectors at locations on a mesh (see location parameter)',
Vector2Array
)
[docs]class ColorData(ProjectElementData):
"""Data array of RGB colors specified as three integers 0-255 or color
If n x 3 integers is provided, these will simply be clipped to values
between 0 and 255 inclusive; invalid colors will not error. This
allows fast array validation rather than slow element-by-element list
validation.
Other color formats may be used (ie String or Hex colors). However,
for large arrays, validation of these types will be slow.
"""
array = properties.Union(
'RGB color values at locations on a mesh (see location parameter)',
props=(
Int3Array,
ColorArray
)
)
@properties.validator('array')
def _clip_colors(self, change): #pylint: disable=no-self-use
"""This validation call fires immediately when array is set"""
if isinstance(change['value'], Int3Array):
change['value'].array = np.clip(change['value'].array, 0, 255)
[docs]class StringData(ProjectElementData):
"""Data array with text entries"""
array = properties.Instance(
'text at locations on a mesh (see location parameter)',
StringArray
)
[docs]class DateTimeData(ProjectElementData):
"""Data array with DateTime entries"""
array = properties.Instance(
'datetimes at locations on a mesh (see location parameter)',
DateTimeArray
)
colormap = properties.Instance(
'colormap associated with the data',
DateTimeColormap,
required=False
)
[docs]class Legend(ContentModel):
"""Legends to be used with DataMap indices"""
values = properties.Union(
'values for mapping indexed data',
props=(
ColorArray,
DateTimeArray,
StringArray,
ScalarArray
)
)
[docs]class MappedData(ProjectElementData):
"""Data array of indices linked to legend values or -1 for no data"""
array = properties.Instance(
'indices into 1 or more legends for locations on a mesh',
ScalarArray
)
legends = properties.List(
'legends into which the indices map',
Legend,
default=list,
)
@property
def indices(self):
"""Allows getting/setting array with more intuitive term indices"""
return self.array
@indices.setter
def indices(self, value):
self.array = value
def value_dict(self, i):
"""Return a dictionary of legend entries based on index"""
entry = {legend.name: legend.values[self.indices[i]] #pylint: disable=unsubscriptable-object
for legend in self.legends} #pylint: disable=not-an-iterable
return entry
@properties.validator('array')
def _validate_min_ind(self, change): #pylint: disable=no-self-use
"""This validation call fires immediately when indices is set"""
if change['value'].array.dtype.kind != 'i':
raise ValueError('DataMap indices must be integers')
if np.min(change['value'].array) < -1:
raise ValueError('DataMap indices must be >= -1')
@properties.validator
def _validate_indices(self):
"""This validation call fires on validate() after everything is set"""
if np.min(self.indices.array) < -1: #pylint: disable=no-member
raise ValueError(
'Indices of DataMap {} must be >= -1'.format(self.name)
)
for legend in self.legends: #pylint: disable=not-an-iterable
if np.max(self.indices.array) >= len(legend.values): #pylint: disable=no-member
raise ValueError(
'Indices of DataMap {dm} exceed number of available '
'entries in Legend {leg}'.format(
dm=self.name,
leg=legend.name
)
)
return True