def _check_single_elem(element): # Nested array annotations not allowed currently # If element is a list or a np.ndarray, it's not conform except if it's a quantity of # length 1 if isinstance(element, list) or (isinstance(element, np.ndarray) and not ( isinstance(element, pq.Quantity) and ( element.shape == () or element.shape == (1,)))): raise ValueError("Array annotations should only be 1-dimensional") if isinstance(element, dict): raise ValueError("Dictionaries are not supported as array annotations") # Perform regular check for elements of array or list _check_annotations(element)
def annotate(self, **annotations): """ Add an annotation to the sound :param annotations: comma-separated key-value pairs to be added to the sound objects annotations. All values are also stored in the database. Example: sound.annotate(type="sentence", transcript="This is a test") sound.annotations["type"] sound.annotations["transcript"] """ _check_annotations(annotations) self.annotations.update(annotations) self.manager.database.store_annotations(self.id, **annotations)
def test__check_annotations__dict(self): names = ['value%s' % i for i in range(len(self.values))] values = dict(zip(names, self.values)) _check_annotations(values)
def test__check_annotations__tuple(self): _check_annotations(tuple(self.values)) _check_annotations((self.values, self.values))
def test__check_annotations__list(self): _check_annotations(self.values)
def test__check_annotations__valid_dtypes(self): for value in self.values: _check_annotations(value)
def _normalize_array_annotations(value, length): """Check consistency of array annotations Recursively check that value is either an array or list containing only "simple" types (number, string, date/time) or is a dict of those. Args: :value: (np.ndarray, list or dict) value to be checked for consistency :length: (int) required length of the array annotation Returns: np.ndarray The array_annotations from value in correct form Raises: ValueError: In case value is not accepted as array_annotation(s) """ # First stage, resolve dict of annotations into single annotations if isinstance(value, dict): for key in value.keys(): if isinstance(value[key], dict): raise ValueError( "Nested dicts are not allowed as array annotations") value[key] = _normalize_array_annotations(value[key], length) elif value is None: raise ValueError("Array annotations must not be None") # If not array annotation, pass on to regular check and make it a list, that is checked again # This covers array annotations with length 1 elif not isinstance(value, (list, np.ndarray)) or (isinstance(value, pq.Quantity) and value.shape == ()): _check_annotations(value) value = _normalize_array_annotations(np.array([value]), length) # If array annotation, check for correct length, only single dimension and allowed data else: # Get length that is required for array annotations, which is equal to the length # of the object's data own_length = length # Escape check if empty array or list and just annotate an empty array (length 0) # This enables the user to easily create dummy array annotations that will be filled # with data later on if len(value) == 0: if not isinstance(value, np.ndarray): value = np.ndarray((0, )) val_length = own_length else: # Note: len(o) also works for np.ndarray, it then uses the first dimension, # which is exactly the desired behaviour here val_length = len(value) if not own_length == val_length: raise ValueError( "Incorrect length of array annotation: {} != {}".format( val_length, own_length)) # Local function used to check single elements of a list or an array # They must not be lists or arrays and fit the usual annotation data types def _check_single_elem(element): # Nested array annotations not allowed currently # If element is a list or a np.ndarray, it's not conform except if it's a quantity of # length 1 if isinstance(element, list) or ( isinstance(element, np.ndarray) and not (isinstance(element, pq.Quantity) and (element.shape == () or element.shape == (1, )))): raise ValueError( "Array annotations should only be 1-dimensional") if isinstance(element, dict): raise ValueError( "Dictionaries are not supported as array annotations") # Perform regular check for elements of array or list _check_annotations(element) # Arrays only need testing of single element to make sure the others are the same if isinstance(value, np.ndarray): # Type of first element is representative for all others # Thus just performing a check on the first element is enough # Even if it's a pq.Quantity, which can be scalar or array, this is still true # Because a np.ndarray cannot contain scalars and sequences simultaneously # If length of data is 0, then nothing needs to be checked if len(value): # Perform check on first element _check_single_elem(value[0]) return value # In case of list, it needs to be ensured that all data are of the same type else: # Conversion to numpy array makes all elements same type # Converts elements to most general type try: value = np.array(value) # Except when scalar and non-scalar values are mixed, this causes conversion to fail except ValueError as e: msg = str(e) if "setting an array element with a sequence." in msg: raise ValueError( "Scalar values and arrays/lists cannot be " "combined into a single array annotation") else: raise e # If most specialized data type that possibly fits all elements is object, # raise an Error with a telling error message, because this means the elements # are not compatible if value.dtype == object: raise ValueError( "Cannot convert list of incompatible types into a single" " array annotation") # Check the first element for correctness # If its type is correct for annotations, all others are correct as well # Note: Emtpy lists cannot reach this point _check_single_elem(value[0]) return value
def _normalize_array_annotations(value, length): """Check consistency of array annotations Recursively check that value is either an array or list containing only "simple" types (number, string, date/time) or is a dict of those. Args: :value: (np.ndarray, list or dict) value to be checked for consistency :length: (int) required length of the array annotation Returns: np.ndarray The array_annotations from value in correct form Raises: ValueError: In case value is not accepted as array_annotation(s) """ # First stage, resolve dict of annotations into single annotations if isinstance(value, dict): for key in value.keys(): if isinstance(value[key], dict): raise ValueError("Nested dicts are not allowed as array annotations") value[key] = _normalize_array_annotations(value[key], length) elif value is None: raise ValueError("Array annotations must not be None") # If not array annotation, pass on to regular check and make it a list, that is checked again # This covers array annotations with length 1 elif not isinstance(value, (list, np.ndarray)) or ( isinstance(value, pq.Quantity) and value.shape == ()): _check_annotations(value) value = _normalize_array_annotations(np.array([value]), length) # If array annotation, check for correct length, only single dimension and allowed data else: # Get length that is required for array annotations, which is equal to the length # of the object's data own_length = length # Escape check if empty array or list and just annotate an empty array (length 0) # This enables the user to easily create dummy array annotations that will be filled # with data later on if len(value) == 0: if not isinstance(value, np.ndarray): value = np.ndarray((0,)) val_length = own_length else: # Note: len(o) also works for np.ndarray, it then uses the first dimension, # which is exactly the desired behaviour here val_length = len(value) if not own_length == val_length: raise ValueError( "Incorrect length of array annotation: {} != {}".format(val_length, own_length)) # Local function used to check single elements of a list or an array # They must not be lists or arrays and fit the usual annotation data types def _check_single_elem(element): # Nested array annotations not allowed currently # If element is a list or a np.ndarray, it's not conform except if it's a quantity of # length 1 if isinstance(element, list) or (isinstance(element, np.ndarray) and not ( isinstance(element, pq.Quantity) and ( element.shape == () or element.shape == (1,)))): raise ValueError("Array annotations should only be 1-dimensional") if isinstance(element, dict): raise ValueError("Dictionaries are not supported as array annotations") # Perform regular check for elements of array or list _check_annotations(element) # Arrays only need testing of single element to make sure the others are the same if isinstance(value, np.ndarray): # Type of first element is representative for all others # Thus just performing a check on the first element is enough # Even if it's a pq.Quantity, which can be scalar or array, this is still true # Because a np.ndarray cannot contain scalars and sequences simultaneously # If length of data is 0, then nothing needs to be checked if len(value): # Perform check on first element _check_single_elem(value[0]) return value # In case of list, it needs to be ensured that all data are of the same type else: # Conversion to numpy array makes all elements same type # Converts elements to most general type try: value = np.array(value) # Except when scalar and non-scalar values are mixed, this causes conversion to fail except ValueError as e: msg = str(e) if "setting an array element with a sequence." in msg: raise ValueError("Scalar values and arrays/lists cannot be " "combined into a single array annotation") else: raise e # If most specialized data type that possibly fits all elements is object, # raise an Error with a telling error message, because this means the elements # are not compatible if value.dtype == object: raise ValueError("Cannot convert list of incompatible types into a single" " array annotation") # Check the first element for correctness # If its type is correct for annotations, all others are correct as well # Note: Emtpy lists cannot reach this point _check_single_elem(value[0]) return value