class Slice(_BaseSceneComponent): """Position and color information for a slice in the Scene""" _defaults = {'uid': 'slice'} position_a = properties.Vector3( 'Position of first plane', default=lambda: [0, 0, 0], ) position_b = properties.Vector3( 'Position of second plane; only required in section mode', default=[0, 0, 0], required=False, ) normal = properties.Vector3( 'Normal vector to the slice plane', default=lambda: [1., 0, 0], ) mode = properties.StringChoice( 'Slice mode', ['inactive', 'normal', 'reversed', 'section'], descriptions={ 'inactive': 'Slice is currently disabled', 'normal': 'Standard single slice slicing mode', 'reversed': 'Single slice with swapped normal', 'section': 'Mode with two parallel sliceplanes' }, default='inactive', ) color_a = properties.Color( 'Color of first slice', default=[0, 120, 255], serializer=spatial.mappings.to_hex, deserializer=spatial.mappings.from_hex, ) color_b = properties.Color( 'Color of second slice; only required in section mode', default=[253, 102, 0], required=False, serializer=spatial.mappings.to_hex, deserializer=spatial.mappings.from_hex, ) @properties.validator def _validate_section(self): """Validate that position_b and color_b are set in section mode""" if (self.mode == 'section' and (self.position_b is None or self.color_b is None)): raise properties.ValidationError( message='Section mode requires position_b and color_b', reason='invalid', prop='mode', instance=self, )
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
class ColorArray(ScalarArray): """Shared array of Colors""" array = properties.List( 'Shared array of Colors', prop=properties.Color(''), default=list, )
class HasOptionalUnion(properties.HasProperties): mybc = properties.Union( 'union of bool or color', props=[properties.Bool(''), properties.Color('')], required=False, )
class ValidateColorThenString(properties.HasProperties): values = properties.Union( 'Values', props=[ properties.List('', properties.Color('')), properties.List('', properties.String('')), ], )
class HasOptPropsUnion(properties.HasProperties): mybc = properties.Union( 'union of bool or color', props=[ properties.Bool('', required=False), properties.Color('', required=False), ], required=True, )
class _BaseAnnotation(_BaseSlideComponent): """Base class for all annotations""" uid = properties.String('Locally unique ID from client', required=False) position = properties.List( 'Location of annotation on slide plane', properties.Float(''), min_length=2, max_length=2, ) color = properties.Color('Annotation color')
class ColorOptions(Options): """Options related to resource display color""" color = properties.Color(doc='Solid color', default='random', required=False) opacity = properties.Float(doc='Opacity', default=1., min=0., max=1., required=False)
class OptionsSurfaceColor(OptionsColor): """Options for displayed surface color Identical to :class:`lfview.resources.spatial.options.OptionsColor` except it also includes solid back color. """ back = properties.Color( 'Back color of the surface, only used if data is unspecified', required=False, serializer=to_hex, deserializer=from_hex, )
class OptionsColor(_BaseOptionsData): """Options for displayed color You may specify a single solid color value for the entire Element or a variable colormap using data and mapping that evaluates to RGB color """ value = properties.Color( 'Single color value, only used if data is unspecified', required=False, serializer=to_hex, deserializer=from_hex, )
def test_color(self): class ColorOpts(properties.HasProperties): mycolor = properties.Color('My color') col = ColorOpts(mycolor='red') assert col.mycolor == (255, 0, 0) col.mycolor = 'darkred' assert col.mycolor == (139, 0, 0) col.mycolor = '#FFF' assert col.mycolor == (255, 255, 255) col.mycolor = [50, 50, 50] assert col.mycolor == (50, 50, 50) col.mycolor = 'random' assert len(col.mycolor) == 3 with self.assertRaises(ValueError): col.mycolor = 'SunburnRed' with self.assertRaises(ValueError): col.mycolor = '#00112233' with self.assertRaises(ValueError): col.mycolor = '#CDEFGH' with self.assertRaises(ValueError): col.mycolor = 5 with self.assertRaises(ValueError): col.mycolor = (1.3, 5.3, 100.6) with self.assertRaises(ValueError): col.mycolor = [5, 100] with self.assertRaises(ValueError): col.mycolor = [-10, 0, 0] col.mycolor = 'red' self.assertEqual(col.serialize(include_class=False), {'mycolor': [255, 0, 0]}) assert ColorOpts.deserialize( {'mycolor': [0, 10, 20]} ).mycolor == (0, 10, 20) assert properties.Color('').equal((0, 10, 20), (0, 10, 20)) assert not properties.Color('').equal((0, 10, 20), [0, 10, 20])
class TestModel(BaseModel): a_key = properties.String('A key') sub_instance = properties.Instance( 'Sub Instance', instance_class=BaseModel, ) list = properties.List( 'List', prop=properties.Instance('List Instance', instance_class=BaseModel), ) list_string = properties.List( 'List of Strings', prop=properties.String('String'), ) date = properties.DateTime('DateTime') color = properties.Color('Color') not_a_field = True
class Tag(BaseModel): tag = properties.String( 'The tag value.', required=True, ) slug = properties.String( 'Slugified version of the tag value.', ) count = properties.Integer( 'The number of times the tag is used.', ) color = properties.Color( 'The tag color.', ) created_at = properties.DateTime( 'UTC time when this tag was created.', ) modified_at = properties.DateTime( 'UTC time when this tag was modified.', )
class HasColorTuple(properties.HasProperties): ccc = properties.Tuple('tuple of colors', properties.Color(''), min_length=2, max_length=2)
class HasColor(properties.HasProperties): col = properties.Color('a color', default='random')
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, ) 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'], input_dtype=json.get('arrayType', None), )) if json.get('colormap'): data.colormap = json['colormap'] return data @classmethod def _build_from_omf(cls, omf_data): 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)) if omf_data.colormap: dlims = np.linspace(np.nanmin(omf_data.array.array), np.nanmax(omf_data.array.array), 257) dlims = (dlims[:-1] + dlims[1:]) / 2 omf_dlims = np.linspace( omf_data.colormap.limits[0], omf_data.colormap.limits[1], len(omf_data.colormap.gradient.array) + 1, ) omf_dlims = (omf_dlims[:-1] + omf_dlims[1:]) / 2 colormap = [] for dval in dlims: colormap += [ omf_data.colormap.gradient.array[np.argmin( np.abs(omf_dlims - dval))] ] data['data'].colormap = colormap return data def _to_omf(self): if self.order != 'c': raise ValueError('OMF data must be "c" order') import omf data = omf.ScalarData( name=self.title or '', description=self.description or '', array=omf.ScalarArray(self.array), ) if self.colormap: data.colormap = omf.ScalarColormap( gradient=omf.ColorArray(self.colormap, ), limits=[np.min(self.array), np.max(self.array)], ) return data
class HasBoolColor(properties.HasProperties): mybc = properties.Union( 'union of bool or color', props=[properties.Bool(''), properties.Color('')])
class HasColorSet(properties.HasProperties): ccc = properties.Set('set of colors', properties.Color(''), observe_mutations=om)
class ColorOpts(properties.HasProperties): mycolor = properties.Color('My color')
class MappingDiscrete(_BaseDataMapping): """Mapping of continuous data to discrete intervals These mappings are used to categorize continuous numeric data. Define the limits of the intervals by specifying end points, and specify if the end points are inclusive in the lower or upper bucket. Then assign values to each interval. .. code:: # values # # -- x - - - - -> # # -- x - - - o # # -- <- - - - - - o # # <------------|--------|------------> data # end_points """ SUB_TYPE = 'discrete' values = properties.Union( 'Values corresponding to intervals', props=[ properties.List( '', properties.Color('', serializer=to_hex, deserializer=from_hex), max_length=256, ), properties.List('', properties.Float(''), max_length=256), properties.List( '', ShortString('', max_length=300), max_length=256, ), ], ) end_points = properties.List( 'Data end values of discrete intervals; these also correspond to ' 'the start of the next interval. First start and final end are ' 'fixed at -inf and inf, respectively.', prop=properties.Float(''), max_length=255, ) end_inclusive = properties.List( 'True if corresponding end is inclusive for lower range and false if ' 'it is inclusive for upper range; must be specified for each interval', prop=properties.Boolean('', cast=True), max_length=255, ) visibility = properties.List( 'True if interval is visible; must be specified for each interval', prop=properties.Boolean('', cast=True), max_length=256, ) @properties.validator def _validate_lengths(self): """Validate lengths of values/end_points/end_inclusive/visibility""" if len(self.values) != len(self.visibility): raise properties.ValidationError( message='values and visibility must be equal length', reason='invalid', prop='visibility', instance=self, ) if len(self.values) != len(self.end_points) + 1: raise properties.ValidationError( message='values must be one longer than end points', reason='invalid', prop='end_points', instance=self, ) if len(self.values) != len(self.end_inclusive) + 1: raise properties.ValidationError( message='values must be one longer than end inclusive', reason='invalid', prop='end_inclusive', instance=self, ) @properties.validator('end_points') def _validate_increasing(self, change): """Ensure end_points are increasing""" if change['value'] is properties.undefined: return diffs = np.array(change['value'][1:]) - np.array(change['value'][:-1]) if not np.all(diffs >= 0): raise properties.ValidationError( message='end points must not decrease: {}'.format( change['value'] ), reason='invalid', prop='end_points', instance=self, )
class HasColorList(properties.HasProperties): ccc = properties.List('list of colors', properties.Color(''), observe_mutations=om)
class MappingCategory(_BaseDataMapping): """Mapping of integer index values to categories These mappings are used to define categories on :class:`spatial.resources.spatial.data.DataCategory` as well as color and other visual aspects. Data array values correspond to indices which then map to the values. If an array value is not present in indices, it is assumed there is no data at that location. .. code:: # values # # -- x # # -- x # # -- x # # | | | # indices """ SUB_TYPE = 'category' values = properties.Union( 'Values corresponding to indices', props=[ properties.List( '', properties.Color('', serializer=to_hex, deserializer=from_hex), max_length=256, ), properties.List('', properties.Float(''), max_length=256), properties.List( '', ShortString('', max_length=300), max_length=256, ), ], ) indices = properties.List( 'Array indices for values', properties.Integer('', min=0), max_length=256, ) visibility = properties.List( 'True if category is visible', prop=properties.Boolean('', cast=True), max_length=256, ) @properties.validator def _validate_lengths(self): """Validate lengths of values/indices/visibility""" if len(self.values) != len(self.indices): raise properties.ValidationError( message='values and indices must be equal length', reason='invalid', prop='indices', instance=self, ) if len(self.values) != len(self.visibility): raise properties.ValidationError( message='values and visibility must be equal length', reason='invalid', prop='visibility', instance=self, ) @properties.validator('indices') def _validate_indices_unique(self, change): """Ensure indices are unique""" if change['value'] is properties.undefined: return if len(change['value']) != len(set(change['value'])): raise properties.ValidationError( message='indices must be unique: {}'.format(change['value']), reason='invalid', prop='indices', instance=self, ) def to_omf(self, index_map): self.validate() new_values = [] if not self.values or isinstance(self.values[0], float): nan_value = np.nan elif isinstance(self.values[0], string_types): nan_value = '' else: nan_value = [255, 255, 255] for ind in index_map: try: new_values.append(self.values[self.indices.index(ind)]) except ValueError: new_values.append(nan_value) omf_legend = omf.Legend( name=self.name or '', description=self.description or '', values=new_values, ) return omf_legend
class HasFunnyDict(properties.HasProperties): mydict = properties.Dictionary('my dict', key_prop=properties.Color(''), value_prop=HasInt, observe_mutations=om)
class HasColorTuple(properties.HasProperties): ccc = properties.Tuple('tuple of colors', properties.Color(''))