Skip to content

Commit

Permalink
Merge pull request #503 from jgrewe/range_dim
Browse files Browse the repository at this point in the history
Range dim
  • Loading branch information
achilleas-k authored Jun 13, 2021
2 parents d1a9f70 + a29fe7c commit 8dcb254
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 21 deletions.
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1.4.{build}
version: 1.5.{build}

image: Visual Studio 2017

Expand Down
41 changes: 36 additions & 5 deletions nixio/data_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,45 @@ def append_range_dimension(self, ticks=None, label=None, unit=None):
rdim.ticks = ticks
return rdim

def append_range_dimension_using_self(self, index=None):
"""
Convenience function to append a new RangeDimension to the list of
existing dimensions that uses the DataArray itself as provider for the ticks.
This is a replacement for
rdim = array.append_range_dimension()
rdim.link_data_array(self, index)
:param index: The slice of the DataArray that contains the tick
values. This must be a vector of the data. Defaults to None, which will be replaced by an index referring to the full first dimension.
:type: list of int
:returns: the newly created RangeDimension
:rtype: RangeDimension
"""
if index is None:
index = [0] * len(self.shape)
index[0] = -1
msg = RangeDimension._check_index(index)
if msg is not None:
raise ValueError(msg)

msg = RangeDimension._check_link_dimensionality(self, index)
if msg is not None:
raise IncompatibleDimensions(msg, "RangeDimension.append_range_dimension_using_self")

dim_index = len(self.dimensions) + 1
rdim = RangeDimension.create_new(self, dim_index, None)
rdim.link_data_array(self, index)
return rdim

def delete_dimensions(self):
"""
Delete all the dimension descriptors for this DataArray.
"""
dimgroup = self._h5group.open_group("dimensions")
ndims = len(dimgroup)
for idx in range(ndims):
del dimgroup[str(idx+1)]
del dimgroup[str(idx + 1)]
return True

def _dimension_count(self):
Expand All @@ -171,7 +202,7 @@ def iter_dimensions(self):
which returns the index starting from one and the dimensions.
"""
for idx, dim in enumerate(self.dimensions):
yield idx+1, dim
yield idx + 1, dim

@property
def dtype(self):
Expand Down Expand Up @@ -281,7 +312,7 @@ def get_slice(self, positions, extents=None, mode=DataSliceMode.Index):
"DataArray.get_slice"
)
if mode == DataSliceMode.Index:
slices = tuple(slice(p, p+e) for p, e in zip(positions, extents))
slices = tuple(slice(p, p + e) for p, e in zip(positions, extents))
return DataView(self, slices)
elif mode == DataSliceMode.Data:
return self._get_slice_bydim(positions, extents)
Expand All @@ -296,11 +327,11 @@ def _get_slice_bydim(self, positions, extents):
if dim.dimension_type in (DimensionType.Sample,
DimensionType.Range):
dpos.append(dim.index_of(pos))
dext.append(dim.index_of(pos+ext)-dpos[-1])
dext.append(dim.index_of(pos + ext) - dpos[-1])
elif dim.dimension_type == DimensionType.Set:
dpos.append(int(pos))
dext.append(int(ext))
slices = tuple(slice(p, p+e) for p, e in zip(dpos, dext))
slices = tuple(slice(p, p + e) for p, e in zip(dpos, dext))
return DataView(self, slices)

@property
Expand Down
78 changes: 63 additions & 15 deletions nixio/dimensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def create_new(cls, nixfile, nixparent, h5parent, dataobj, dotype, index):

@property
def id(self):
self._h5group.get_attr("entity_id")
return self._h5group.get_attr("entity_id")

@property
def file(self):
Expand Down Expand Up @@ -236,24 +236,37 @@ def _set_dimension_type(self, dimtype):
def index(self):
return self.dim_index

def link_data_array(self, data_array, index):
if len(data_array.data_extent) != len(index):
raise IncompatibleDimensions(
"Length of linked DataArray indices ({}) does not match"
"number of DataArray dimensions ({})".format(
len(data_array.data_extent), len(index)
),
"Dimension.link_data_array"
)

@staticmethod
def _check_index(index):
invalid_idx_msg = (
"Invalid linked DataArray index: "
"One of the values must be -1, indicating the relevant vector. "
"Negative indexing is not supported."
)
if index.count(-1) != 1 or sum(idx < 0 for idx in index) != 1:
# TODO: Add link to relevant docs
raise ValueError(invalid_idx_msg)
return invalid_idx_msg

return None

@staticmethod
def _check_link_dimensionality(data_array, index):
invalid_dim_msg = ("Length of linked DataArray indices ({}) does not match "
"number of DataArray dimensions ({})"
).format(len(data_array.data_extent), len(index))

if len(data_array.data_extent) != len(index):
return invalid_dim_msg
return None

def link_data_array(self, data_array, index):
msg = RangeDimension._check_link_dimensionality(data_array, index)
if msg is not None:
raise IncompatibleDimensions(msg, "Dimension.link_data_array")

msg = self._check_index(index)
if msg is not None:
raise ValueError(msg)

if self.has_link:
self.remove_link()
Expand Down Expand Up @@ -389,13 +402,12 @@ def axis(self, count, start=0):
:param start: positive integer, indicates the starting sample.
:returns: The created axis
:rtype: list
:rtype: tuple
"""
offset = self.offset if self.offset else 0.0
sample = self.sampling_interval
start_val = start * sample + offset
end_val = (start + count) * sample + offset
return tuple(np.arange(start_val, end_val, sample))
return tuple(np.arange(count) * sample + start_val)

@property
def label(self):
Expand Down Expand Up @@ -466,8 +478,44 @@ def create_new(cls, data_array, index, ticks):
newdim._h5group.write_data("ticks", ticks, dtype=DataType.Double)
return newdim

@property
def is_alias(self):
"""
In versions < 1.5 a RangeDimension that was referring to the DataArray it describes
was called an AliasRangeDimension. This has been made more flexible by allowing to link
to any DataArray or DataFrame.
For downward compatibility this method has been re-introduced in 1.5.1.
:returns: True, if this dimension links to a DataArray, False otherwise
:rtype: bool
"""
if self._h5group.has_data("ticks"):
return False
elif not self.has_link and len(self._h5group) > 0:
return True
elif self.has_link and self.dimension_link._data_object_type == "DataArray":
return True
return False

@property
def _redirgrp(self):
"""
If the dimension is an Alias Range dimension, this property returns
the H5Group of the linked DataArray. Otherwise, it returns the H5Group
representing the dimension.
"""
if self.is_alias:
gname = self._h5group.get_by_pos(0).name
return self._h5group.open_group(gname)
return self._h5group

@property
def ticks(self):
if self.is_alias and not self.has_link:
g = self._redirgrp
if g.has_data("data"):
ticks = g.get_data("data")
return tuple(ticks)
if self.has_link:
ticks = self.dimension_link.values
else:
Expand Down
22 changes: 22 additions & 0 deletions nixio/test/test_dimensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Redistribution and use in source and binary forms, with or without
# modification, are permitted under the terms of the BSD License. See
# LICENSE file in the root of the Project.
from nixio.exceptions.exceptions import IncompatibleDimensions
import os
import unittest
import numpy as np
Expand Down Expand Up @@ -388,6 +389,26 @@ def test_data_array_self_link_range_dimension(self):
assert da.dimensions[0].label == da.label
assert da.dimensions[0].unit == da.unit
assert np.all(da.dimensions[0].ticks == da[:])
assert rdim.is_alias

da.delete_dimensions()
da.append_range_dimension()
assert not da.dimensions[0].is_alias

da.delete_dimensions()
da.append_range_dimension_using_self()
assert len(da.dimensions) == 1
assert da.dimensions[0].is_alias

da.delete_dimensions()
with self.assertRaises(IncompatibleDimensions):
da.append_range_dimension_using_self([0, -1])
with self.assertRaises(ValueError):
da.append_range_dimension_using_self([-2])

da.append_range_dimension_using_self([-1])
assert len(da.dimensions) == 1
assert da.dimensions[0].is_alias

def test_data_array_self_link_set_dimension(self):
# The new way of making alias range dimension
Expand Down Expand Up @@ -432,6 +453,7 @@ def randtick():
tuple(v[2] for v in values))
assert self.range_dim.unit == df.units[2]
assert self.range_dim.label == df.column_names[2]
assert not self.range_dim.is_alias

def test_data_frame_set_link_dimension(self):
column_descriptions = OrderedDict([("name", nix.DataType.String),
Expand Down

0 comments on commit 8dcb254

Please sign in to comment.