diff --git a/connectomics/common/array.py b/connectomics/common/array.py index eb625fb..fcfce2a 100644 --- a/connectomics/common/array.py +++ b/connectomics/common/array.py @@ -25,9 +25,8 @@ from collections import abc import numbers -from typing import Any, List, Tuple, TypeVar, Type, Union +from typing import Any, List, Tuple, TypeVar, Union -from connectomics.common import array_mixins import numpy as np import numpy.typing as npt @@ -43,12 +42,11 @@ IndexExpOrPointLookups = Union[ArbitrarySlice, PointLookups] CanonicalSliceOrPointLookups = Union[CanonicalSlice, PointLookups] -ArrayLike = Union[npt.ArrayLike, 'ImmutableArray', 'MutableArray'] +ArrayLike = npt.ArrayLike Tuple3f = Tuple[float, float, float] Tuple3i = Tuple[int, int, int] Tuple4i = Tuple[int, int, int, int] -ArrayLike3d = Union[npt.ArrayLike, 'ImmutableArray', 'MutableArray', Tuple3f, - Tuple3i] +ArrayLike3d = Union[npt.ArrayLike, Tuple3f, Tuple3i] def is_point_lookup(ind: IndexExpOrPointLookups) -> bool: @@ -120,75 +118,12 @@ def process_slice_ind(slice_ind: ArbitrarySlice, limit: int) -> slice: return slice(*slice_ind.indices(limit)) -# TODO(timblakely): Make these typed by using Generic[T] -class ImmutableArray(array_mixins.ImmutableArrayMixin, np.ndarray): - """Strongly typed, immutable NumPy NDArray.""" - - def __new__(cls: Type['ImmutableArray'], - input_array: 'ArrayLike', - *args, - zero_copy=False, - **kwargs) -> 'ImmutableArray': - if zero_copy: - obj = np.asanyarray(input_array, *args, **kwargs).view(cls) - else: - obj = np.array(input_array, *args, **kwargs).view(cls) - obj.flags.writeable = False - return obj - - def __init__(self, *args, zero_copy=False, **kwargs): - # Needed for mixin construction. - super().__init__() # pylint: disable=no-value-for-parameter - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - out = kwargs.get('out', ()) - - # Defer to the implementation of the ufunc on unwrapped values. - inputs = tuple(_to_np_compatible(x) for x in inputs) - if out: - kwargs['out'] = tuple(_to_np_compatible(x) for x in out) - result = getattr(ufunc, method)(*inputs, **kwargs) - - if method == 'at': - # no return value - return None - - return MutableArray(result) - - def copy(self, *args, **kwargs) -> 'MutableArray': - return MutableArray(np.asarray(self).copy(*args, **kwargs)) - - def __str__(self): - return np.ndarray.__repr__(self) - - -class MutableArray(array_mixins.MutableArrayMixin, ImmutableArray): - """Strongly typed mutable version of np.ndarray.""" - - def __new__(cls: Type['MutableArray'], - input_array: 'ArrayLike', - *args, - zero_copy=False, - **kwargs) -> 'MutableArray': - if zero_copy: - obj = np.asanyarray(input_array, *args, **kwargs).view(cls) - else: - obj = np.array(input_array, *args, **kwargs).view(cls) - obj.flags.writeable = True - return obj - - def is_arraylike(obj): # Technically sequences, but this is intended to check for numeric sequences. if isinstance(obj, str) or isinstance(obj, bytes): return False - return isinstance(obj, abc.Sequence) or isinstance( - obj, np.ndarray) or isinstance(obj, ImmutableArray) or isinstance( - obj, MutableArray) + return isinstance(obj, abc.Sequence) or isinstance(obj, np.ndarray) def _to_np_compatible(array_like) -> np.ndarray: - if isinstance(array_like, ImmutableArray) or isinstance( - array_like, MutableArray): - return np.asarray(array_like) - return array_like + return np.asarray(array_like) diff --git a/connectomics/common/array_mixins.py b/connectomics/common/array_mixins.py deleted file mode 100644 index 43c3418..0000000 --- a/connectomics/common/array_mixins.py +++ /dev/null @@ -1,264 +0,0 @@ -# coding=utf-8 -# Copyright 2022 The Google Research Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Mutability mixins for numpy numeric and indexing.""" - -from typing import Any - -import numpy as np - -# Avoid circular dependency -array_mixins = Any - - -class InPlaceNumericOpsDisabled: - """Type marker indicating that in-place numeric methods have been disabled.""" - pass - - -class IndexingDisabled: - """Type marker indicating that indexing has been disabled.""" - pass - - -def _disables_array_ufunc(obj): - """True when __array_ufunc__ is set to None.""" - try: - return obj.__array_ufunc__ is None - except AttributeError: - return False - - -def _maybe_run_ufunc(ufunc, self, other, reflexive=False): - if _disables_array_ufunc(other): - return NotImplemented - if reflexive: - return ufunc(other, self) - return ufunc(self, other) - - -class ImmutableArrayMixin: - """Mixin that only enables immutable numeric methods.""" - - def __init__(self, *args, **kwargs): # pylint: disable=useless-super-delegation - super().__init__(*args, **kwargs) - - # Comparisons - - def __lt__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.less(self, other) - - def __le__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.less_equal(self, other) - - def __eq__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.equal(self, other) - - def __ne__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.not_equal(self, other) - - def __gt__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.greater(self, other) - - def __ge__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.greater_equal(self, other) - - # Numeric - - def __add__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.add, self, other) - - def __radd__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.add, self, other, reflexive=True) - - __iadd__: InPlaceNumericOpsDisabled - - def __sub__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.subtract, self, other) - - def __rsub__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.subtract, self, other, reflexive=True) - - __isub__: InPlaceNumericOpsDisabled - - def __mul__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.multiply, self, other) - - def __rmul__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.multiply, self, other, reflexive=True) - - __imul__: InPlaceNumericOpsDisabled - - def __matmul__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.matmul, self, other) - - def __rmatmul__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.matmul, self, other, reflexive=True) - - __imatmul__: InPlaceNumericOpsDisabled - - def __truediv__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.true_divide, self, other) - - def __rtruediv__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.true_divide, self, other, reflexive=True) - - __itruediv__: InPlaceNumericOpsDisabled - - def __floordiv__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.floor_divide, self, other) - - def __rfloordiv__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.floor_divide, self, other, reflexive=True) - - __ifloordiv__: InPlaceNumericOpsDisabled - - def __mod__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.remainder, self, other) - - def __rmod__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.remainder, self, other, reflexive=True) - - __imod__: InPlaceNumericOpsDisabled - - def __divmod__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.divmod, self, other) - - def __rdivmod__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.divmod, self, other, reflexive=True) - - def __pow__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.power, self, other) - - def __rpow__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.power, self, other, reflexive=True) - - __ipow__: InPlaceNumericOpsDisabled - - def __lshift__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.left_shift, self, other) - - def __rlshift__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.left_shift, self, other, reflexive=True) - - __ilshift__: InPlaceNumericOpsDisabled - - def __rshift__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.right_shift, self, other) - - def __rrshift__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.right_shift, self, other, reflexive=True) - - __irshift__: InPlaceNumericOpsDisabled - - def __and__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.bitwise_and, self, other) - - def __rand__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.bitwise_and, self, other, reflexive=True) - - __iand__: InPlaceNumericOpsDisabled - - def __xor__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.bitwise_xor, self, other) - - def __rxor__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.bitwise_xor, self, other, reflexive=True) - - __ixor__: InPlaceNumericOpsDisabled - - def __or__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc(np.core.umath.bitwise_or, self, other) - - def __ror__(self, other) -> 'array_mixins.MutableArray': - return _maybe_run_ufunc( - np.core.umath.bitwise_or, self, other, reflexive=True) - - __ior__: InPlaceNumericOpsDisabled - - # Unary - - def __neg__(self) -> 'array_mixins.MutableArray': - return np.core.umath.negative(self) - - def __pos__(self) -> 'array_mixins.MutableArray': - return np.core.umath.positive(self) - - def __abs__(self) -> 'array_mixins.MutableArray': - return np.core.umath.absolute(self) - - def __invert__(self) -> 'array_mixins.MutableArray': - return np.core.umath.invert(self) - - # Disable mutable indexing - __setitem__: IndexingDisabled - - def __array_wrap__(self, out_arr, context=None): - pass - - -class MutableArrayMixin: - """Mixin that enables in-place numeric and indexing methods.""" - - def __init__(self, *args, **kwargs): # pylint: disable=useless-super-delegation - super().__init__(*args, **kwargs) - - def __iadd__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.add(self, other, out=(self,)) - - def __isub__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.subtract(self, other, out=(self,)) - - def __imul__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.multiply(self, other, out=(self,)) - - def __imatmul__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.matmul(self, other, out=(self,)) - - def __itruediv__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.true_divide(self, other, out=(self,)) - - def __ifloordiv__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.floor_divide(self, other, out=(self,)) - - def __imod__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.remainder(self, other, out=(self,)) - - def __ipow__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.power(self, other, out=(self,)) - - def __ilshift__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.left_shift(self, other, out=(self,)) - - def __irshift__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.right_shift(self, other, out=(self,)) - - def __iand__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.bitwise_and(self, other, out=(self,)) - - def __ixor__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.bitwise_xor(self, other, out=(self,)) - - def __ior__(self, other) -> 'array_mixins.MutableArray': - return np.core.umath.bitwise_or(self, other, out=(self,)) - - def __setitem__(self, index, obj): - return np.asarray(self).__setitem__(index, obj) diff --git a/connectomics/common/array_pytype_test.py b/connectomics/common/array_pytype_test.py deleted file mode 100644 index 29068db..0000000 --- a/connectomics/common/array_pytype_test.py +++ /dev/null @@ -1,107 +0,0 @@ -# coding=utf-8 -# Copyright 2022 The Google Research Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Untyped tests for array. - -Since we're using PyType to enforce mutability constraints ahead-of-time, these -tests ensure that if the type system was somehow disabled there would be a -corresponding runtime error for immutable types. -""" - -from typing import Any # pylint: disable=unused-import - -from absl.testing import absltest -from connectomics.common import array # type: Any - -ImmutableArray = array.ImmutableArray - - -class ImmutableArrayRuntimeErrorTest(absltest.TestCase): - - def test_inplace_add(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a += [4, 5, 6] - - def test_inplace_sub(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a -= [4, 5, 6] - - def test_inplace_mul(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a *= [4, 5, 6] - - def test_inplace_matmul(self): - a = ImmutableArray([1, 2, 3]) - # In-place matmul is not yet supported, so ensure that this TypeErrors for - # now. Will need to be updated if Python3 ever supports in-place matmul - # operations. - with self.assertRaises((TypeError, ValueError)): - a @= [4, 5, 6] - - def test_inplace_truediv(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a /= [4, 5, 6] - - def test_inplace_floordiv(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a //= [4, 5, 6] - - def test_inplace_mod(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a %= [4, 5, 6] - - def test_inplace_pow(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a **= [4, 5, 6] - - def test_inplace_lshift(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a <<= [4, 5, 6] - - def test_inplace_rshift(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a >>= [4, 5, 6] - - def test_inplace_and(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a &= [4, 5, 6] - - def test_inplace_xor(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a ^= [4, 5, 6] - - def test_inplace_or(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a |= [4, 5, 6] - - def test_indexing(self): - a = ImmutableArray([1, 2, 3]) - with self.assertRaises(ValueError): - a[1] = 7 - - -if __name__ == '__main__': - absltest.main() diff --git a/connectomics/common/array_test.py b/connectomics/common/array_test.py index f9fe37e..65718bf 100644 --- a/connectomics/common/array_test.py +++ b/connectomics/common/array_test.py @@ -18,241 +18,6 @@ from connectomics.common import array import numpy as np -ImmutableArray = array.ImmutableArray -MutableArray = array.MutableArray - - -class ImmutableArrayTest(absltest.TestCase): - - def assertIsTypedCorrectly(self, arr): - self.assertIsInstance(arr, MutableArray) - self.assertIsInstance(arr, ImmutableArray) - self.assertIsInstance(arr, np.ndarray) - - def test_construction(self): - a = ImmutableArray([1, 2, 3]) - np.testing.assert_array_equal([1, 2, 3], a) - - # Copy by default, same as np.array - b = ImmutableArray(a) - self.assertFalse(np.may_share_memory(a, b)) - - # Ensure zero-copy is possible - c = ImmutableArray(a, zero_copy=True) - self.assertTrue(np.may_share_memory(a, c)) - - # Ensure we can downcast - m = MutableArray([4, 5, 6]) - a = ImmutableArray(m) - np.testing.assert_array_equal([4, 5, 6], a) - self.assertFalse(np.may_share_memory(a, b)) - - a = ImmutableArray(m, zero_copy=True) - np.testing.assert_array_equal([4, 5, 6], a) - self.assertTrue(np.may_share_memory(a, m)) - - def test_dtype(self): - a = ImmutableArray([1, 2, 3], dtype=int) - np.testing.assert_array_equal([1, 2, 3], a) - self.assertEqual(int, a.dtype) - - a = ImmutableArray([1., 2.4, 3.8], dtype=float) - np.testing.assert_array_equal([1., 2.4, 3.8], a) - self.assertEqual(float, a.dtype) - - def test_tolist(self): - a = ImmutableArray([1, 2, 3]) - self.assertIsInstance(a.tolist(), list) - self.assertEqual([1, 2, 3], a.tolist()) - - def test_indexing(self): - a = ImmutableArray([1, 2, 3]) - self.assertEqual(1, a[0]) - self.assertEqual([1, 2, 3], list(a[:3])) - - def test_numpy_ufuncs(self): - a = ImmutableArray([1, 2, 3]) - - out = a + [1, 2, 3] - self.assertIsTypedCorrectly(out) - self.assertEqual([2, 4, 6], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a - [4, -1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([-3, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a / [2, 2, 2] - self.assertIsTypedCorrectly(out) - self.assertEqual([0.5, 1, 1.5], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(float, out.dtype) - - out = a // [2, 2, 2] - self.assertIsTypedCorrectly(out) - self.assertEqual([0, 1, 1], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(int, out.dtype) - - out = a * [3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3, 4, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(int, out.dtype) - - out = a * [3., 2.1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3., 4.2, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(float, out.dtype) - - out = a & [3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([1, 2, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a | [2, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a ^ [3, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([2, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a << [7, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([128, 4, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a >> [2, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([0, 1, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a**[3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([1, 4, 1], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - def test_copy(self): - a = ImmutableArray([1, 2, 3]) - - b = a.copy() - self.assertIsTypedCorrectly(b) - self.assertIsNot(a, b) - self.assertFalse(np.may_share_memory(a, b)) - np.testing.assert_array_equal(a, b) - self.assertEqual([1, 2, 3], a.tolist()) - self.assertEqual([1, 2, 3], b.tolist()) - - b += [1, 2, 3] - self.assertEqual([1, 2, 3], a.tolist()) - self.assertEqual([2, 4, 6], b.tolist()) - - def test_reflexive_ufuncs(self): - a = ImmutableArray([17, 22, 38]) - other = np.array([2, 3, 4]) - np.testing.assert_array_equal(a + other, other + a) - - np.testing.assert_array_equal([15, 19, 34], a - other) - np.testing.assert_array_equal([-15, -19, -34], other - a) - - np.testing.assert_array_equal(other * a, a * other) - - self.assertSequenceAlmostEqual([8.5, 7.3333333, 9.5], a / other) - self.assertSequenceAlmostEqual([0.11764705, 0.136363636, 0.10526315789], - other / a) - - -class MutableArrayTest(absltest.TestCase): - - def assertIsTypedCorrectly(self, arr): - self.assertIsInstance(arr, MutableArray) - self.assertIsInstance(arr, ImmutableArray) - self.assertIsInstance(arr, np.ndarray) - - def test_construction(self): - a = MutableArray([1, 2, 3]) - np.testing.assert_array_equal([1, 2, 3], a) - - # Copy by default, same as np.array - b = MutableArray(a) - self.assertFalse(np.may_share_memory(a, b)) - - # Ensure zero-copy is possible - c = MutableArray(a, zero_copy=True) - self.assertTrue(np.may_share_memory(a, c)) - - def test_numpy_ufuncs(self): - a = MutableArray([1, 2, 3]) - - out = a + [1, 2, 3] - self.assertIsTypedCorrectly(out) - self.assertEqual([2, 4, 6], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a - [4, -1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([-3, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a / [2, 2, 2] - self.assertIsTypedCorrectly(out) - self.assertEqual([0.5, 1, 1.5], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(float, out.dtype) - - out = a // [2, 2, 2] - self.assertIsTypedCorrectly(out) - self.assertEqual([0, 1, 1], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(int, out.dtype) - - out = a * [3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3, 4, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(int, out.dtype) - - out = a * [3., 2.1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3., 4.2, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - self.assertEqual(float, out.dtype) - - out = a & [3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([1, 2, 0], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a | [2, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([3, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a ^ [3, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([2, 3, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a << [7, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([128, 4, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a >> [2, 1, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([0, 1, 3], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - - out = a**[3, 2, 0] - self.assertIsTypedCorrectly(out) - self.assertEqual([1, 4, 1], out.tolist()) - self.assertFalse(np.may_share_memory(out, a)) - class TestNormalizeIndex(absltest.TestCase): diff --git a/connectomics/common/bounding_box.py b/connectomics/common/bounding_box.py index 3a25389..8dd4276 100644 --- a/connectomics/common/bounding_box.py +++ b/connectomics/common/bounding_box.py @@ -95,13 +95,13 @@ def __init__( f'Got {start=} and {end=}.') if array.is_arraylike(start): - start = np.array(start) + start = np.asarray(start) if array.is_arraylike(end): - end = np.array(end) + end = np.asarray(end) if size is not None: if array.is_arraylike(size): - size = np.array(size) + size = np.asarray(size) else: param = start if start is not None else end size = np.ones_like(param) * size @@ -185,32 +185,39 @@ def _as_ndarray(self: S, seq: Sequence[float]) -> np.ndarray: """ return np.asarray(seq) - def _as_immutablearray(self: S, seq: Sequence[float]) -> array.ImmutableArray: - return array.ImmutableArray(self._as_ndarray(seq)) - @property def rank(self: S) -> int: return len(self.start) @property - def start(self: S) -> array.ImmutableArray: - return self._as_immutablearray(self._start) + def start(self: S) -> np.ndarray: + start = np.asarray(self._start) + start.setflags(write=False) + return start @property - def end(self: S) -> array.ImmutableArray: - return self._as_immutablearray(self.start + self.size) + def end(self: S) -> np.ndarray: + end = self.start + self.size + end.setflags(write=False) + return end @property - def size(self: S) -> array.ImmutableArray: - return self._as_immutablearray(self._size) + def size(self: S) -> np.ndarray: + size = np.asarray(self._size) + size.setflags(write=False) + return size @property - def is_border_start(self: S) -> array.ImmutableArray: - return array.ImmutableArray(np.asarray(self._is_border_start)) + def is_border_start(self: S) -> np.ndarray: + is_start = np.asarray(self._is_border_start) + is_start.setflags(write=False) + return is_start @property - def is_border_end(self: S) -> array.ImmutableArray: - return array.ImmutableArray(np.asarray(self._is_border_end)) + def is_border_end(self: S) -> np.ndarray: + is_end = np.asarray(self._is_border_end) + is_end.setflags(write=False) + return is_end def scale(self: S, scale_factor: FloatSequence) -> S: """Returns a new BoundingBox, scaled relative to this one. diff --git a/connectomics/common/box_generator.py b/connectomics/common/box_generator.py index a52fdbc..d52d492 100644 --- a/connectomics/common/box_generator.py +++ b/connectomics/common/box_generator.py @@ -23,7 +23,6 @@ import json from typing import List, Optional, Sequence, Tuple, Iterable, TypeVar, Union -from connectomics.common import array from connectomics.common import bounding_box import dataclasses_json import numpy as np @@ -42,11 +41,11 @@ def num_boxes(self) -> int: raise NotImplementedError() @property - def box_size(self) -> array.ImmutableArray: + def box_size(self) -> np.ndarray: raise NotImplementedError() @property - def box_overlap(self) -> array.ImmutableArray: + def box_overlap(self) -> np.ndarray: raise NotImplementedError() def generate(self, index: int) -> IndexedBoundingBox: @@ -197,16 +196,16 @@ def output(self) -> bounding_box.BoundingBox: return self._output @property - def box_overlap(self) -> array.ImmutableArray: - return array.ImmutableArray(self._box_overlap) + def box_overlap(self) -> np.ndarray: + return np.asarray(self._box_overlap) @property - def box_size(self) -> array.ImmutableArray: - return array.ImmutableArray(self._box_size) + def box_size(self) -> np.ndarray: + return np.asarray(self._box_size) @property - def box_stride(self) -> array.ImmutableArray: - return array.ImmutableArray(self._box_stride) + def box_stride(self) -> np.ndarray: + return np.asarray(self._box_stride) @property def num_boxes(self) -> int: @@ -217,7 +216,6 @@ def squeeze(self) -> List[int]: return self._squeeze @property - # def start(self) -> array.ImmutableArray: def start(self) -> np.ndarray: return self._generate(0)[1].start @@ -231,7 +229,6 @@ def boxes(self) -> Iterable[bounding_box.BoundingBoxBase]: yield self.generate(i)[1] @property - # def boxes_per_dim(self) -> array.ImmutableArray: def boxes_per_dim(self) -> np.ndarray: return self._output.size @@ -565,11 +562,11 @@ def tag_border_locations( return self.generators[generator_index].tag_border_locations(index) @property - def box_size(self) -> array.ImmutableArray: + def box_size(self) -> np.ndarray: return self.generators[0].box_size @property - def box_overlap(self) -> array.ImmutableArray: + def box_overlap(self) -> np.ndarray: return self.generators[0].box_overlap diff --git a/connectomics/volume/subvolume.py b/connectomics/volume/subvolume.py index 49e85d4..4281aba 100644 --- a/connectomics/volume/subvolume.py +++ b/connectomics/volume/subvolume.py @@ -146,12 +146,12 @@ def shape(self) -> tuple[int, int, int, int]: return self._data.shape @property - def start(self) -> array.ImmutableArray: + def start(self) -> np.ndarray: """Starting corner of the bounding box.""" return self._bbox.start @property - def size(self) -> array.ImmutableArray: + def size(self) -> np.ndarray: """3d size of the subvolume.""" return self._bbox.size diff --git a/connectomics/volume/subvolume_processor.py b/connectomics/volume/subvolume_processor.py index b325498..3ad7ebe 100644 --- a/connectomics/volume/subvolume_processor.py +++ b/connectomics/volume/subvolume_processor.py @@ -30,8 +30,6 @@ import dataclasses_json import numpy as np -ImmutableArray = array.ImmutableArray -MutableArray = array.MutableArray Subvolume = subvolume.Subvolume SuggestedXyz = collections.namedtuple('SuggestedXyz', 'x y z') TupleOrSuggestedXyz = Union['XyzTuple', SuggestedXyz] # pylint: disable=invalid-name @@ -122,10 +120,9 @@ class SubvolumeProcessor: # Effective subvolume/overlap configuration as set by the framework within # which this processor is being executed. This might include, e.g. user # overrides supplied via command-line arguments. - _context: tuple[ImmutableArray, ImmutableArray] - _subvol_size: ImmutableArray - _overlap: ImmutableArray - + _context: tuple[np.ndarray, np.ndarray] + _subvol_size: np.ndarray + _overlap: np.ndarray # Whether the output of this processor will be cropped for subvolumes that # are adjacent to the input bounding box(es). crop_at_borders = True @@ -158,8 +155,8 @@ def name_parts(self) -> Tuple[str]: """ return type(self).__name__, - def pixelsize(self, input_psize: array.ArrayLike3d) -> ImmutableArray: - return ImmutableArray(input_psize) + def pixelsize(self, input_psize: array.ArrayLike3d) -> np.ndarray: + return np.asarray(input_psize) def num_channels(self, input_channels: int) -> int: return input_channels @@ -206,10 +203,10 @@ def overlap(self) -> TupleOrSuggestedXyz: def set_effective_subvol_and_overlap(self, subvol_size: array.ArrayLike3d, overlap: array.ArrayLike3d): """Assign the effective subvolume and overlap.""" - self._subvol_size = array.ImmutableArray(subvol_size) - self._overlap = array.ImmutableArray(overlap) + self._subvol_size = np.asarray(subvol_size) + self._overlap = np.asarray(overlap) if np.all(self.overlap() == self._overlap): - self._context = tuple([array.ImmutableArray(c) for c in self.context()]) + self._context = tuple([np.asarray(c) for c in self.context()]) else: pre = self._overlap // 2 post = self._overlap - pre