From 59c35949d2454a52ada61daf3e2c5151269b83c9 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Thu, 6 May 2021 20:35:02 +0100 Subject: [PATCH 1/7] DRAFT/NF Operator class This is a draft for community feedback. I would like to know 1. Are there header/affine check functions that already exist in nibabel that I can reuse? 2. more efficient way to add more operators? I have to admit I am not super familiar with a lot of tools related to class object that comes with python. --- nibabel/arrayops.py | 53 ++++++++++++++++++++++++++++++++++ nibabel/nifti1.py | 3 +- nibabel/tests/test_arrayops.py | 22 ++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 nibabel/arrayops.py create mode 100644 nibabel/tests/test_arrayops.py diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py new file mode 100644 index 0000000000..d13fea7691 --- /dev/null +++ b/nibabel/arrayops.py @@ -0,0 +1,53 @@ +import numpy as np +import operator + + +support_np_type = ( + np.int8, + np.int64, + np.float16, + np.float32, + np.float64, + np.complex128) + + +class OperableImage: + def _op(self, other, op): + """Apply operator to Nifti1Image. + + This is a draft and experiment. + + Parameters + ---------- + op : + Python operator. + """ + # Check header... need to have axes in the same places + if self.header.get_data_shape() != other.header.get_data_shape(): + raise ValueError("Two images should has the same shape.") + # Check affine + if (self.affine != other.affine).all(): + raise ValueError("Two images should has the same affine.") + # Check shape. Handle identical stuff for now. + if self.shape != other.shape: + raise ValueError("Two images should has the same shape.") + + # Check types? Problematic types will be caught by numpy, + # but might be cheaper to check before loading data. + # collect dtype + dtypes = [img.get_data_dtype().type for img in (self, other)] + # check allowed dtype based on the operator + if set(support_np_type).union(dtypes) == 0: + raise ValueError("Image contains illegal datatype for arithmatic.") + if op.__name__ in ["add", "sub", "mul", "div"]: + dataobj = op(np.asanyarray(self.dataobj), np.asanyarray(other.dataobj)) + if op.__name__ in ["and_", "or_"]: + self_ = self.dataobj.astype(bool) + other_ = other.dataobj.astype(bool) + dataobj = op(self_, other_).astype(int) + return self.__class__(dataobj, self.affine, self.header) + + def __add__(self, other): + return self._op(other, operator.__add__) + def __and__(self, other): + return self._op(other, operator.__and__) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 799377f282..8220b4d53e 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -26,6 +26,7 @@ from .spm99analyze import SpmAnalyzeHeader from .casting import have_binary128 from .pydicom_compat import have_dicom, pydicom as pdcm +from .arrayops import OperableImage # nifti1 flat header definition for Analyze-like first 348 bytes # first number in comments indicates offset in file header in bytes @@ -2011,7 +2012,7 @@ def as_reoriented(self, ornt): return img -class Nifti1Image(Nifti1Pair, SerializableImage): +class Nifti1Image(Nifti1Pair, SerializableImage, OperableImage): """ Class for single file NIfTI1 format image """ header_class = Nifti1Header diff --git a/nibabel/tests/test_arrayops.py b/nibabel/tests/test_arrayops.py new file mode 100644 index 0000000000..6a13d4510f --- /dev/null +++ b/nibabel/tests/test_arrayops.py @@ -0,0 +1,22 @@ +import numpy as np +from .. import Nifti1Image +from numpy.testing import assert_array_equal + + +def test_add(): + data1 = np.random.rand(5, 5, 2) + data2 = np.random.rand(5, 5, 2) + img1 = Nifti1Image(data1, np.eye(4)) + img2 = Nifti1Image(data2, np.eye(4)) + output = img1 + img2 + assert_array_equal(output.dataobj, data1 + data2) + output = img1 + img2 + img2 + assert_array_equal(output.dataobj, data1 + data2 + data2) + +def test_and(): + data1 = np.ones((5, 5, 2)) + data2 = np.zeros((5, 5, 2)) + img1 = Nifti1Image(data1, np.eye(4)) + img2 = Nifti1Image(data2, np.eye(4)) + output = img1 & img2 + assert_array_equal(output.dataobj, data2) \ No newline at end of file From 1cf09098075ecb67ba92ace38aab20aa92cdb7be Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Wed, 12 May 2021 21:33:41 +0100 Subject: [PATCH 2/7] ADD sensible arithmetic and logical operation --- nibabel/arrayops.py | 32 +++++++++++++++++++++++++------- nibabel/tests/test_arrayops.py | 34 ++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index d13fea7691..f665a4c0a0 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -1,6 +1,7 @@ import numpy as np import operator - +from functools import partial +from .orientations import aff2axcodes support_np_type = ( np.int8, @@ -22,15 +23,15 @@ def _op(self, other, op): op : Python operator. """ - # Check header... need to have axes in the same places - if self.header.get_data_shape() != other.header.get_data_shape(): - raise ValueError("Two images should has the same shape.") + # Check orientations are the same + if aff2axcodes(self.affine) != aff2axcodes(other.affine): + raise ValueError("Two images should have the same orientation") # Check affine if (self.affine != other.affine).all(): - raise ValueError("Two images should has the same affine.") + raise ValueError("Two images should have the same affine.") # Check shape. Handle identical stuff for now. if self.shape != other.shape: - raise ValueError("Two images should has the same shape.") + raise ValueError("Two images should have the same shape.") # Check types? Problematic types will be caught by numpy, # but might be cheaper to check before loading data. @@ -39,7 +40,8 @@ def _op(self, other, op): # check allowed dtype based on the operator if set(support_np_type).union(dtypes) == 0: raise ValueError("Image contains illegal datatype for arithmatic.") - if op.__name__ in ["add", "sub", "mul", "div"]: + + if op.__name__ in ["add", "sub", "mul", "truediv", "floordiv"]: dataobj = op(np.asanyarray(self.dataobj), np.asanyarray(other.dataobj)) if op.__name__ in ["and_", "or_"]: self_ = self.dataobj.astype(bool) @@ -49,5 +51,21 @@ def _op(self, other, op): def __add__(self, other): return self._op(other, operator.__add__) + + def __sub__(self, other): + return self._op(other, operator.__sub__) + + def __mul__(self, other): + return self._op(other, operator.__mul__) + + def __truediv__(self, other): + return self._op(other, operator.__truediv__) + + def __floordiv__(self, other): + return self._op(other, operator.__floordiv__) + def __and__(self, other): return self._op(other, operator.__and__) + + def __or__(self, other): + return self._op(other, operator.__or__) \ No newline at end of file diff --git a/nibabel/tests/test_arrayops.py b/nibabel/tests/test_arrayops.py index 6a13d4510f..5a03cf0cad 100644 --- a/nibabel/tests/test_arrayops.py +++ b/nibabel/tests/test_arrayops.py @@ -1,22 +1,40 @@ import numpy as np from .. import Nifti1Image from numpy.testing import assert_array_equal +import pytest - -def test_add(): +def test_operations(): data1 = np.random.rand(5, 5, 2) data2 = np.random.rand(5, 5, 2) + data1[0, 0, :] = 0 img1 = Nifti1Image(data1, np.eye(4)) img2 = Nifti1Image(data2, np.eye(4)) output = img1 + img2 assert_array_equal(output.dataobj, data1 + data2) + output = img1 + img2 + img2 assert_array_equal(output.dataobj, data1 + data2 + data2) -def test_and(): - data1 = np.ones((5, 5, 2)) - data2 = np.zeros((5, 5, 2)) - img1 = Nifti1Image(data1, np.eye(4)) - img2 = Nifti1Image(data2, np.eye(4)) + output = img1 - img2 + assert_array_equal(output.dataobj, data1 - data2) + + output = img1 * img2 + assert_array_equal(output.dataobj, data1 * data2) + + output = img1 / img2 + assert_array_equal(output.dataobj, data1 / data2) + + output = img1 // img2 + assert_array_equal(output.dataobj, data1 // data2) + + output = img2 / img1 + assert_array_equal(output.dataobj, data2 / data1) + + output = img2 // img1 + assert_array_equal(output.dataobj, data2 // data1) + output = img1 & img2 - assert_array_equal(output.dataobj, data2) \ No newline at end of file + assert_array_equal(output.dataobj, (data1.astype(bool) & data2.astype(bool)).astype(int)) + + output = img1 | img2 + assert_array_equal(output.dataobj, (data1.astype(bool) | data2.astype(bool)).astype(int)) \ No newline at end of file From 9f46fba7b99c9e9f362c9c14a6cc2b6a95123851 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Wed, 12 May 2021 21:40:59 +0100 Subject: [PATCH 3/7] Drafted soc string --- nibabel/arrayops.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index f665a4c0a0..7eccbc1560 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -1,8 +1,10 @@ -import numpy as np import operator -from functools import partial + +import numpy as np + from .orientations import aff2axcodes + support_np_type = ( np.int8, np.int64, @@ -16,7 +18,10 @@ class OperableImage: def _op(self, other, op): """Apply operator to Nifti1Image. - This is a draft and experiment. + Arithmetic and logical operation on Nifti image. + Currently support: +, -, *, /, //, &, | + The nifit image should contain the same header information and affine. + Images should be the same shape. Parameters ---------- @@ -39,7 +44,7 @@ def _op(self, other, op): dtypes = [img.get_data_dtype().type for img in (self, other)] # check allowed dtype based on the operator if set(support_np_type).union(dtypes) == 0: - raise ValueError("Image contains illegal datatype for arithmatic.") + raise ValueError("Image contains illegal datatype for Nifti1Image.") if op.__name__ in ["add", "sub", "mul", "truediv", "floordiv"]: dataobj = op(np.asanyarray(self.dataobj), np.asanyarray(other.dataobj)) @@ -68,4 +73,4 @@ def __and__(self, other): return self._op(other, operator.__and__) def __or__(self, other): - return self._op(other, operator.__or__) \ No newline at end of file + return self._op(other, operator.__or__) From b29b7838f4ad7da52db47c4caa7ed15b3c5f4e04 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Mon, 21 Jun 2021 12:16:19 +0100 Subject: [PATCH 4/7] ADD unary operator --- nibabel/arrayops.py | 92 +++++++++++++++++++++++----------- nibabel/tests/test_arrayops.py | 20 +++++++- 2 files changed, 82 insertions(+), 30 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index 7eccbc1560..da1926e51b 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -15,7 +15,7 @@ class OperableImage: - def _op(self, other, op): + def _binop(self, val, *, op): """Apply operator to Nifti1Image. Arithmetic and logical operation on Nifti image. @@ -28,49 +28,85 @@ def _op(self, other, op): op : Python operator. """ - # Check orientations are the same - if aff2axcodes(self.affine) != aff2axcodes(other.affine): - raise ValueError("Two images should have the same orientation") - # Check affine - if (self.affine != other.affine).all(): - raise ValueError("Two images should have the same affine.") - # Check shape. Handle identical stuff for now. - if self.shape != other.shape: - raise ValueError("Two images should have the same shape.") - - # Check types? Problematic types will be caught by numpy, - # but might be cheaper to check before loading data. - # collect dtype - dtypes = [img.get_data_dtype().type for img in (self, other)] - # check allowed dtype based on the operator - if set(support_np_type).union(dtypes) == 0: - raise ValueError("Image contains illegal datatype for Nifti1Image.") - + val = _input_validation(self, val) + # numerical operator should work work if op.__name__ in ["add", "sub", "mul", "truediv", "floordiv"]: - dataobj = op(np.asanyarray(self.dataobj), np.asanyarray(other.dataobj)) + dataobj = op(np.asanyarray(self.dataobj), val) if op.__name__ in ["and_", "or_"]: self_ = self.dataobj.astype(bool) - other_ = other.dataobj.astype(bool) + other_ = val.astype(bool) dataobj = op(self_, other_).astype(int) return self.__class__(dataobj, self.affine, self.header) + + def _unop(self, *, op): + """ + Parameters + ---------- + op : + Python operator. + """ + _type_check(self) + if op.__name__ in ["pos", "neg", "abs"]: + dataobj = op(np.asanyarray(self.dataobj)) + return self.__class__(dataobj, self.affine, self.header) + + def __add__(self, other): - return self._op(other, operator.__add__) + return self._binop(other, op=operator.__add__) def __sub__(self, other): - return self._op(other, operator.__sub__) + return self._binop(other, op=operator.__sub__) def __mul__(self, other): - return self._op(other, operator.__mul__) + return self._binop(other, op=operator.__mul__) def __truediv__(self, other): - return self._op(other, operator.__truediv__) + return self._binop(other, op=operator.__truediv__) def __floordiv__(self, other): - return self._op(other, operator.__floordiv__) + return self._binop(other, op=operator.__floordiv__) def __and__(self, other): - return self._op(other, operator.__and__) + return self._binop(other, op=operator.__and__) def __or__(self, other): - return self._op(other, operator.__or__) + return self._binop(other, op=operator.__or__) + + def __pos__(self): + return self._unop(op=operator.__pos__) + + def __neg__(self): + return self._unop(op=operator.__neg__) + + def __abs__(self): + return self._unop(op=operator.__abs__) + + + +def _input_validation(self, val): + """Check images orientation, affine, and shape muti-images operation.""" + _type_check(self) + if type(val) not in [float, int]: + # Check orientations are the same + if aff2axcodes(self.affine) != aff2axcodes(val.affine): + raise ValueError("Two images should have the same orientation") + # Check affine + if (self.affine != val.affine).all(): + raise ValueError("Two images should have the same affine.") + # Check shape. + if self.shape[:3] != val.shape[:3]: + raise ValueError("Two images should have the same shape except" + "the time dimension.") + + _type_check(val) + val = np.asanyarray(val.dataobj) + return val + +def _type_check(*args): + """Ensure image contains correct nifti data type.""" + # Check types + dtypes = [img.get_data_dtype().type for img in args] + # check allowed dtype based on the operator + if set(support_np_type).union(dtypes) == 0: + raise ValueError("Image contains illegal datatype for Nifti1Image.") diff --git a/nibabel/tests/test_arrayops.py b/nibabel/tests/test_arrayops.py index 5a03cf0cad..b9ff3f4842 100644 --- a/nibabel/tests/test_arrayops.py +++ b/nibabel/tests/test_arrayops.py @@ -3,12 +3,17 @@ from numpy.testing import assert_array_equal import pytest -def test_operations(): + +def test_binary_operations(): data1 = np.random.rand(5, 5, 2) data2 = np.random.rand(5, 5, 2) data1[0, 0, :] = 0 img1 = Nifti1Image(data1, np.eye(4)) img2 = Nifti1Image(data2, np.eye(4)) + + output = img1 + 2 + assert_array_equal(output.dataobj, data1 + 2) + output = img1 + img2 assert_array_equal(output.dataobj, data1 + data2) @@ -37,4 +42,15 @@ def test_operations(): assert_array_equal(output.dataobj, (data1.astype(bool) & data2.astype(bool)).astype(int)) output = img1 | img2 - assert_array_equal(output.dataobj, (data1.astype(bool) | data2.astype(bool)).astype(int)) \ No newline at end of file + assert_array_equal(output.dataobj, (data1.astype(bool) | data2.astype(bool)).astype(int)) + + +def test_unary_operations(): + data = np.random.rand(5, 5, 2) + img = Nifti1Image(data, np.eye(4)) + + output = -img + assert_array_equal(output.dataobj, -data) + + output = abs(img) + assert_array_equal(output.dataobj, abs(data)) From 9ded76a3db6a195d5e39ad5287174ba381d16213 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Mon, 21 Jun 2021 13:09:58 +0100 Subject: [PATCH 5/7] EHN allow 3D image projects along the 4th dimension --- nibabel/arrayops.py | 41 +++++++++++++++++++++++++--------- nibabel/tests/test_arrayops.py | 10 +++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index da1926e51b..dd3fc7c60d 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -28,15 +28,17 @@ def _binop(self, val, *, op): op : Python operator. """ - val = _input_validation(self, val) + affine, header = self.affine, self.header + self_, val_ = _input_validation(self, val) # numerical operator should work work + if op.__name__ in ["add", "sub", "mul", "truediv", "floordiv"]: - dataobj = op(np.asanyarray(self.dataobj), val) + dataobj = op(self_, val_) if op.__name__ in ["and_", "or_"]: - self_ = self.dataobj.astype(bool) - other_ = val.astype(bool) - dataobj = op(self_, other_).astype(int) - return self.__class__(dataobj, self.affine, self.header) + self_ = self_.astype(bool) + val_ = val_.astype(bool) + dataobj = op(self_, val_).astype(int) + return self.__class__(dataobj, affine, header) def _unop(self, *, op): @@ -87,7 +89,8 @@ def __abs__(self): def _input_validation(self, val): """Check images orientation, affine, and shape muti-images operation.""" _type_check(self) - if type(val) not in [float, int]: + if isinstance(val, self.__class__): + _type_check(val) # Check orientations are the same if aff2axcodes(self.affine) != aff2axcodes(val.affine): raise ValueError("Two images should have the same orientation") @@ -99,9 +102,27 @@ def _input_validation(self, val): raise ValueError("Two images should have the same shape except" "the time dimension.") - _type_check(val) - val = np.asanyarray(val.dataobj) - return val + # if 4th dim exist in a image, + # reshape the 3d image to ensure valid projection + ndims = (len(self.shape), len(val.shape)) + if 4 not in ndims: + self_ = np.asanyarray(self.dataobj) + val_ = np.asanyarray(val.dataobj) + return self_, val_ + + reference = None + imgs = [] + for ndim, img in zip(ndims, (self, val)): + img_ = np.asanyarray(img.dataobj) + if ndim == 3: + reference = tuple(list(img.shape) + [1]) + img_ = np.reshape(img_, reference) + imgs.append(img_) + return imgs + else: + self_ = np.asanyarray(self.dataobj) + val_ = val + return self_, val_ def _type_check(*args): """Ensure image contains correct nifti data type.""" diff --git a/nibabel/tests/test_arrayops.py b/nibabel/tests/test_arrayops.py index b9ff3f4842..15f61b6d6e 100644 --- a/nibabel/tests/test_arrayops.py +++ b/nibabel/tests/test_arrayops.py @@ -45,6 +45,16 @@ def test_binary_operations(): assert_array_equal(output.dataobj, (data1.astype(bool) | data2.astype(bool)).astype(int)) +def test_binary_operations_4d(): + data1 = np.random.rand(5, 5, 2, 3) + data2 = np.random.rand(5, 5, 2) + img1 = Nifti1Image(data1, np.eye(4)) + img2 = Nifti1Image(data2, np.eye(4)) + data2_ = np.reshape(data2, (5, 5, 2, 1)) + output = img1 * img2 + assert_array_equal(output.dataobj, data1 * data2_) + + def test_unary_operations(): data = np.random.rand(5, 5, 2) img = Nifti1Image(data, np.eye(4)) From 3f52ec2d6b38a0c84b49a2b60690c33ddde0d026 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Mon, 21 Jun 2021 13:12:29 +0100 Subject: [PATCH 6/7] LINT pep8 --- nibabel/arrayops.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index dd3fc7c60d..7eea29a095 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -40,7 +40,6 @@ def _binop(self, val, *, op): dataobj = op(self_, val_).astype(int) return self.__class__(dataobj, affine, header) - def _unop(self, *, op): """ Parameters @@ -53,7 +52,6 @@ def _unop(self, *, op): dataobj = op(np.asanyarray(self.dataobj)) return self.__class__(dataobj, self.affine, self.header) - def __add__(self, other): return self._binop(other, op=operator.__add__) @@ -85,7 +83,6 @@ def __abs__(self): return self._unop(op=operator.__abs__) - def _input_validation(self, val): """Check images orientation, affine, and shape muti-images operation.""" _type_check(self) @@ -100,7 +97,7 @@ def _input_validation(self, val): # Check shape. if self.shape[:3] != val.shape[:3]: raise ValueError("Two images should have the same shape except" - "the time dimension.") + "the time dimension.") # if 4th dim exist in a image, # reshape the 3d image to ensure valid projection @@ -124,6 +121,7 @@ def _input_validation(self, val): val_ = val return self_, val_ + def _type_check(*args): """Ensure image contains correct nifti data type.""" # Check types From fb3bcfc5190d9c2f37a0f722338db3535faadd78 Mon Sep 17 00:00:00 2001 From: Hao-Ting Wang Date: Mon, 21 Jun 2021 14:21:13 +0100 Subject: [PATCH 7/7] TEST improve error catching --- nibabel/arrayops.py | 41 +++++++++++++++++----------------- nibabel/tests/test_arrayops.py | 30 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/nibabel/arrayops.py b/nibabel/arrayops.py index 7eea29a095..591f2a22d7 100644 --- a/nibabel/arrayops.py +++ b/nibabel/arrayops.py @@ -3,15 +3,13 @@ import numpy as np from .orientations import aff2axcodes - - -support_np_type = ( - np.int8, - np.int64, - np.float16, - np.float32, - np.float64, - np.complex128) +# support_np_type = ( +# np.int8, +# np.int64, +# np.float16, +# np.float32, +# np.float64, +# np.complex128) class OperableImage: @@ -47,7 +45,7 @@ def _unop(self, *, op): op : Python operator. """ - _type_check(self) + # _type_check(self) if op.__name__ in ["pos", "neg", "abs"]: dataobj = op(np.asanyarray(self.dataobj)) return self.__class__(dataobj, self.affine, self.header) @@ -85,18 +83,19 @@ def __abs__(self): def _input_validation(self, val): """Check images orientation, affine, and shape muti-images operation.""" - _type_check(self) + # _type_check(self) if isinstance(val, self.__class__): - _type_check(val) + # _type_check(val) # Check orientations are the same if aff2axcodes(self.affine) != aff2axcodes(val.affine): raise ValueError("Two images should have the same orientation") # Check affine - if (self.affine != val.affine).all(): + if (self.affine != val.affine).any(): raise ValueError("Two images should have the same affine.") + # Check shape. if self.shape[:3] != val.shape[:3]: - raise ValueError("Two images should have the same shape except" + raise ValueError("Two images should have the same shape except " "the time dimension.") # if 4th dim exist in a image, @@ -122,10 +121,10 @@ def _input_validation(self, val): return self_, val_ -def _type_check(*args): - """Ensure image contains correct nifti data type.""" - # Check types - dtypes = [img.get_data_dtype().type for img in args] - # check allowed dtype based on the operator - if set(support_np_type).union(dtypes) == 0: - raise ValueError("Image contains illegal datatype for Nifti1Image.") +# def _type_check(*args): +# """Ensure image contains correct nifti data type.""" +# # Check types +# dtypes = [img.get_data_dtype().type for img in args] +# # check allowed dtype based on the operator +# if set(support_np_type).union(dtypes) == 0: +# raise ValueError("Image contains illegal datatype for Nifti1Image.") diff --git a/nibabel/tests/test_arrayops.py b/nibabel/tests/test_arrayops.py index 15f61b6d6e..be389c9e6f 100644 --- a/nibabel/tests/test_arrayops.py +++ b/nibabel/tests/test_arrayops.py @@ -51,16 +51,44 @@ def test_binary_operations_4d(): img1 = Nifti1Image(data1, np.eye(4)) img2 = Nifti1Image(data2, np.eye(4)) data2_ = np.reshape(data2, (5, 5, 2, 1)) + output = img1 * img2 assert_array_equal(output.dataobj, data1 * data2_) def test_unary_operations(): - data = np.random.rand(5, 5, 2) + data = np.random.rand(5, 5, 2) - 0.5 img = Nifti1Image(data, np.eye(4)) + output = +img + assert_array_equal(output.dataobj, +data) + output = -img assert_array_equal(output.dataobj, -data) output = abs(img) assert_array_equal(output.dataobj, abs(data)) + + +def test_error_catching(): + data1 = np.random.rand(5, 5, 1) + data2 = np.random.rand(5, 5, 2) + img1 = Nifti1Image(data1, np.eye(4)) + img2 = Nifti1Image(data2, np.eye(4)) + with pytest.raises(ValueError, match=r'should have the same shape'): + img1 + img2 + + data1 = np.random.rand(5, 5, 2) + data2 = np.random.rand(5, 5, 2) + img1 = Nifti1Image(data1, np.eye(4) * 2) + img2 = Nifti1Image(data2, np.eye(4)) + with pytest.raises(ValueError, match=r'should have the same affine'): + img1 + img2 + + data = np.random.rand(5, 5, 2) + aff1 = [[0,1,0,10],[-1,0,0,20],[0,0,1,30],[0,0,0,1]] + aff2 = np.eye(4) + img1 = Nifti1Image(data, aff1) + img2 = Nifti1Image(data, aff2) + with pytest.raises(ValueError, match=r'should have the same orientation'): + img1 + img2