1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
# -*- coding: utf-8 -*-
"""
Objects that represent dimensions or axes of scientific datasets
Created on Thu Jul 7 21:14:25 2020
@author: Gerd Duscher, Suhas Somnath
"""
from __future__ import division, print_function, unicode_literals, \
absolute_import
from warnings import warn
import sys
import numpy as np
from enum import Enum
from sidpy.base.string_utils import validate_single_string_arg
import copy
__all__ = ['Dimension', 'DimensionType']
if sys.version_info.major == 3:
unicode = str
class DimensionType(Enum):
"""
Physical type of Dimension object. This information will be used for
visualization and processing purposes.
"""
UNKNOWN = -1
SPATIAL = 1
RECIPROCAL = 2
SPECTRAL = 3
TEMPORAL = 4
CHANNEL = 5
POINT_CLOUD = 6
class Dimension(np.ndarray):
"""
"""
def __new__(cls, values, name='none', quantity='generic', units='generic',
dimension_type=DimensionType.UNKNOWN, *args, **kwargs):
"""
Parameters
----------
name : str or unicode
Name of the dimension. For example 'X'
quantity : str or unicode
Quantity for this dimension. For example: 'Length'
units : str or unicode
Units for this dimension. For example: 'um'
values : array-like or int
Values over which this dimension was varied. A linearly increasing
set of values will be generated if an integer is provided instead
of an array.
dimension_type : str or sidpy.sid.dimension.DimensionType
For example: 'spectral', 'spatial', 'reciprocal', 'channel', or 'UNKNOWN',
'time', 'frame', 'reciprocal'
This will determine how the data are visualized. 'spatial' are
image dimensions. 'spectral' indicate spectroscopy data dimensions.
Attributes
----------
self.name : str
Name of the dimension
self.quantity : str
Physical quantity. E.g. - current
self.units : str
Physical units. E.g. - amperes
self.dimension_type : enum
Type of dimension. E.g. - Spectral, Spatial, etc.
self.values : array-like
Values over which this dimension was varied
"""
if isinstance(values, int):
if values < 1:
raise TypeError("When specifying the size of a Dimension, "
"values should at be integers > 1")
values = np.arange(values)
elif len(np.array(values)) < 1:
raise TypeError("When specifying values over which a parameter is "
"varied, values should not be an empty array")
if np.array(values).ndim != 1:
raise ValueError('Dimension can only be 1 dimensional')
new_dim = np.asarray(values, dtype=float).view(cls)
new_dim._name = validate_single_string_arg(name, 'name')
new_dim.quantity = quantity
new_dim.units = units
new_dim.dimension_type = dimension_type
return new_dim
def __array_finalize__(self, obj):
# see InfoArray.__array_finalize__ for comments
if obj is None:
return
self._name = validate_single_string_arg(getattr(obj, '_name', 'generic'), 'name')
self.quantity = getattr(obj, '_quantity', 'generic')
self.units = getattr(obj, '_units', 'generic')
self.dimension_type = getattr(obj, '_dimension_type', 'UNKNOWN')
def __array_wrap__(self, out_arr, context=None):
# just call the parent
super(Dimension, self).__array_wrap__(self, out_arr, context)
# return correct values
return out_arr
def __repr__(self):
return '{}: {} ({}) of size {}'.format(self.name, self.quantity, self.units, self.shape)
def __str__(self):
return '{}: {} ({}) of size {}'.format(self.name, self.quantity, self.units, self.shape)
# def __copy__(self):
# new_dim = Dimension(np.array(self), name=self.name, quantity=self.quantity, units=self.units)
# new_dim.dimension_type = self.dimension_type
# return new_dim
def __copy__(self):
# Create a new instance of Dimension
new_instance = Dimension(
copy.copy(self.values),
copy.copy(self.name),
copy.copy(self.quantity),
copy.copy(self.units),
copy.copy(self.dimension_type)
)
return new_instance
def __deepcopy__(self, memo):
# For now this is what chatGPT came up with and it does not break any tests
# Create a new instance of Dimension
new_instance = Dimension(
copy.deepcopy(self.values, memo),
copy.deepcopy(self.name, memo),
copy.deepcopy(self.quantity, memo),
copy.deepcopy(self.units, memo),
copy.deepcopy(self.dimension_type, memo)
)
return new_instance
# TODO: Implement equality
# TODO: Find out how to get rid of this
def copy(self):
# Not sure why __copy__() would not be called by itself
new_dim = self.__copy__()
return new_dim
@property
def info(self):
return '{} - {} ({}): {}'.format(self.name, self.quantity, self.units,
self.values)
@property
def name(self):
return self._name
@name.setter
def name(self, value):
raise AttributeError("Cannot change the name of the dimension. "
"If the dimension is associated with the dataset, please try "
"dataset.rename_dimension")
# # self._name = validate_single_string_arg(value, 'name')
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, value):
self._quantity = validate_single_string_arg(value, 'quantity')
@property
def units(self):
return self._units
@units.setter
def units(self, value):
self._units = validate_single_string_arg(value, 'units')
@property
def dimension_type(self):
return self._dimension_type
@dimension_type.setter
def dimension_type(self, value):
if isinstance(value, DimensionType):
self._dimension_type = value
else:
dimension_type = validate_single_string_arg(value, 'dimension_type')
if dimension_type.upper() in [member.name for member in DimensionType]:
self._dimension_type = DimensionType[dimension_type.upper()]
elif dimension_type.lower() in ['frame', 'time', 'stack']:
self._dimension_type = DimensionType.TEMPORAL
else:
self._dimension_type = DimensionType.UNKNOWN
warn('Supported dimension types for plotting are only: {}'
''.format([member.name for member in DimensionType]))
warn('Setting DimensionType to UNKNOWN')
@property
def values(self):
return np.array(self)
# @values.setter
# def values(self, value):
# isinstance(np.ndarray)
def __eq__(self, other):
if not isinstance(other, Dimension):
return False
if self.name != other.name:
return False
if self.units != other.units:
return False
if self.quantity != other.quantity:
return False
if len(self.values) != len(other):
return False
if not (np.array(self) == np.array(other)).all():
return False
if not (self.values == other.values).all():
return False
return True
|