From e7eb38b54ac6fda50e300f561b7524268a0f18de Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 11 Oct 2024 11:11:43 -0500 Subject: [PATCH 01/36] bump ver --- nrrd/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrrd/_version.py b/nrrd/_version.py index 1f356cc..1a72d32 100644 --- a/nrrd/_version.py +++ b/nrrd/_version.py @@ -1 +1 @@ -__version__ = '1.0.0' +__version__ = '1.1.0' From e4cc23d41d50aca2e9d63a032ac486c439f455e3 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 11 Oct 2024 11:50:47 -0500 Subject: [PATCH 02/36] Support array of None for formatting optional vector. Utilize np.asarray in relative formatters to ensure array is a numpy array. --- nrrd/formatters.py | 13 +++++++++++-- nrrd/tests/test_formatting.py | 6 +++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/nrrd/formatters.py b/nrrd/formatters.py index 53a6d3d..d860469 100644 --- a/nrrd/formatters.py +++ b/nrrd/formatters.py @@ -57,6 +57,7 @@ def format_vector(x: npt.NDArray) -> str: vector : :class:`str` String containing NRRD vector """ + x = np.asarray(x) return '(' + ','.join([format_number(y) for y in x]) + ')' @@ -80,10 +81,15 @@ def format_optional_vector(x: Optional[npt.NDArray]) -> str: vector : :class:`str` String containing NRRD vector """ + # If vector is None, return none + if x is None: + return 'none' + + x = np.asarray(x) - # If vector is None or all elements are NaN, then return none + # If all elements are None or NaN, then return none # Otherwise format the vector as normal - if x is None or np.all(np.isnan(x)): + if np.all(x == None) or np.all(np.isnan(x)): return 'none' else: return format_vector(x) @@ -131,6 +137,8 @@ def format_optional_matrix(x: Optional[npt.NDArray]) -> str: matrix : :class:`str` String containing NRRD matrix """ + # Convert to float dtype to convert None to NaN + x = np.asarray(x, dtype=float) return ' '.join([format_optional_vector(y) for y in x]) @@ -151,5 +159,6 @@ def format_number_list(x: npt.NDArray) -> str: list : :class:`str` String containing NRRD list """ + x = np.asarray(x) return ' '.join([format_number(y) for y in x]) diff --git a/nrrd/tests/test_formatting.py b/nrrd/tests/test_formatting.py index de4461f..f02084b 100644 --- a/nrrd/tests/test_formatting.py +++ b/nrrd/tests/test_formatting.py @@ -44,6 +44,7 @@ def test_format_optional_vector(self): self.assertEqual(nrrd.format_optional_vector(None), 'none') self.assertEqual(nrrd.format_optional_vector(np.array([np.nan, np.nan, np.nan])), 'none') self.assertEqual(nrrd.format_optional_vector([np.nan, np.nan, np.nan]), 'none') + self.assertEqual(nrrd.format_optional_vector([None, None, None]), 'none') def test_format_matrix(self): self.assertEqual(nrrd.format_matrix(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])), '(1,2,3) (4,5,6) (7,8,9)') @@ -81,7 +82,10 @@ def test_format_optional_matrix(self): 'none (1,2,3) (4,5,6) (7,8,9)') self.assertEqual(nrrd.format_optional_matrix(np.array([ [1, 2, 3], [np.nan, np.nan, np.nan], [4, 5, 6], [7, 8, 9]])), - '(1,2,3) none (4,5,6) (7,8,9)') + '(1,2,3) none (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_matrix(np.array([ + [None, None, None], [1, 2, 3], [4, 5, 6], [7, 8, 9]])), + 'none (1,2,3) (4,5,6) (7,8,9)') def test_format_number_list(self): self.assertEqual(nrrd.format_number_list([1, 2, 3]), '1 2 3') From ec55ae66d479da75bed178c0c7aa497bf3aa4ae7 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 11 Oct 2024 11:51:36 -0500 Subject: [PATCH 03/36] test impr --- nrrd/tests/test_formatting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nrrd/tests/test_formatting.py b/nrrd/tests/test_formatting.py index f02084b..f95680e 100644 --- a/nrrd/tests/test_formatting.py +++ b/nrrd/tests/test_formatting.py @@ -45,6 +45,7 @@ def test_format_optional_vector(self): self.assertEqual(nrrd.format_optional_vector(np.array([np.nan, np.nan, np.nan])), 'none') self.assertEqual(nrrd.format_optional_vector([np.nan, np.nan, np.nan]), 'none') self.assertEqual(nrrd.format_optional_vector([None, None, None]), 'none') + self.assertEqual(nrrd.format_optional_vector(np.array([None, None, None])), 'none') def test_format_matrix(self): self.assertEqual(nrrd.format_matrix(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])), '(1,2,3) (4,5,6) (7,8,9)') From 3a3b58680b5adc7d6bebb4887cf0fc9fc9b0204e Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 11 Oct 2024 11:57:52 -0500 Subject: [PATCH 04/36] Document new types `int vector list` and `double vector list` --- docs/source/background/datatypes.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/source/background/datatypes.rst b/docs/source/background/datatypes.rst index 75e08b6..3e4a466 100644 --- a/docs/source/background/datatypes.rst +++ b/docs/source/background/datatypes.rst @@ -72,6 +72,24 @@ double vector pynrrd will correctly handle vectors with or without spaces between the comma-delimiter. Saving the NRRD file back will remove all spaces between the comma-delimiters. +int vector list +~~~~~~~~~~~~~~~~~~ +:NRRD Syntax: (,,...,) (,,...,) ... (,,...,) +:NRRD Example: (1,0,0) (0,1,0) (0,0,1) +:Python Datatype: :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` +:Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([0, 0, 1])] + +All rows of the matrix are required, unlike that of the `double matrix`_. If some of the rows need to be 'none', then use a `double matrix`_ instead. The reason is that empty rows (i.e. containing 'none') are represented as a row of NaN's, and NaN's are only available for floating point numbers. + +double vector list +~~~~~~~~~~~~~~~~~~ +:NRRD Syntax: (,,...,) (,,...,) ... (,,...,) +:NRRD Example: (2.54, 1.3, 0.0) (3.14, 0.3, 3.3) none (0.05, -12.3, -3.3) +:Python Datatype: :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` +:Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] + +This datatype has the added feature where rows can be defined as empty by setting the vector as :code:`none`. In the NRRD specification, instead of the row, the :code:`none` keyword is used in it's place. This is represented in the Python NumPy array as a row of all NaN's. An example use case for this optional row matrix is for the 'space directions' field where one row may be empty because it is not a domain type. + int matrix ~~~~~~~~~~ :NRRD Syntax: (,,...,) (,,...,) ... (,,...,) From e8ff8a1beb6aa6abce6bff42f4762ffe5e279252 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 11 Oct 2024 12:03:26 -0500 Subject: [PATCH 05/36] WIP --- nrrd/parsers.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index e164bb3..2da5952 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -1,4 +1,4 @@ -from typing import Optional, Type, Union +from typing import List, Optional, Type, Union import numpy as np import numpy.typing as npt @@ -237,3 +237,93 @@ def parse_number_auto_dtype(x: str) -> Union[int, float]: value = int(value) return value + + +def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[npt.NDArray]: + """Parse NRRD vector list from string into (N,) :class:`numpy.ndarray`. + + TODO + + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on + the format. + + Parameters + ---------- + x : :class:`str` + String containing NRRD matrix + dtype : data-type, optional + Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If + :obj:`dtype` is :obj:`None`, then it will be automatically determined by checking any of the elements + for fractional numbers. If found, then the matrix will be converted to :class:`float`, otherwise :class:`int`. + Default is to automatically determine datatype. + + Returns + ------- + matrix : (M,N) :class:`numpy.ndarray` + Matrix that is parsed from the :obj:`x` string + """ + + # Split input by spaces, convert each row into a vector and stack them vertically to get a matrix + vector_list = [parse_vector(x, dtype=float) for x in x.split()] + + # Get the size of each row vector and then remove duplicate sizes + # There should be exactly one value in the matrix because all row sizes need to be the same + if len(np.unique([len(x) for x in vector_list])) != 1: + raise NRRDError('Matrix should have same number of elements in each row') + + # If using automatic datatype detection, then start by converting to float and determining if the number is whole + # Truncate to integer if dtype is int also + if dtype is None: + matrix_trunc = vector_list.astype(int) + + if np.all((vector_list - matrix_trunc) == 0): + vector_list = matrix_trunc + elif dtype == int: + vector_list = [x.astype(int) if x is not None else None for x in vector_list] + elif dtype != float: + raise NRRDError('dtype should be None for automatic type detection, float or int') + + return vector_list + + +def parse_optional_vector_list(x: str) -> List[Optional[npt.NDArray]]: + """Parse optional NRRD matrix from string into (M,N) :class:`numpy.ndarray` of :class:`float`. + + Function parses optional NRRD matrix from string into an (M,N) :class:`numpy.ndarray` of :class:`float`. This + function works the same as :meth:`parse_matrix` except if a row vector in the matrix is none, the resulting row in + the returned matrix will be all NaNs. + + See :ref:`background/datatypes:double matrix` for more information on the format. + + Parameters + ---------- + x : :class:`str` + String containing NRRD matrix + + Returns + ------- + matrix : (M,N) :class:`numpy.ndarray` of :class:`float` + Matrix that is parsed from the :obj:`x` string + """ + + # Split input by spaces to get each row and convert into a vector. The row can be 'none', in which case it will + # return None + vector_list = [parse_optional_vector(x, dtype=float) for x in x.split()] + + # Get the size of each row vector, 0 if None + sizes = np.array([0 if x is None else len(x) for x in vector_list]) + + # Get sizes of each row vector removing duplicate sizes + # Since each row vector should be same size, the unique sizes should return one value for the row size or it may + # return a second one (0) if there are None vectors + unique_sizes = np.unique(sizes) + + if len(unique_sizes) != 1 and (len(unique_sizes) != 2 or unique_sizes.min() != 0): + raise NRRDError('Matrix should have same number of elements in each row') + + # Create a vector row of NaN's that matches same size of remaining vector rows + # Stack the vector rows together to create matrix + nan_row = np.full((unique_sizes.max()), np.nan) + vector_list = np.vstack([nan_row if x is None else x for x in vector_list]) + + return vector_list From c39862bfe3d4ad5bbdb4f170117b816d37fa225c Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:05:35 -0500 Subject: [PATCH 06/36] Revert version bump --- nrrd/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrrd/_version.py b/nrrd/_version.py index 1a72d32..1f356cc 100644 --- a/nrrd/_version.py +++ b/nrrd/_version.py @@ -1 +1 @@ -__version__ = '1.1.0' +__version__ = '1.0.0' From 882b9998280e0e2e579b5ad0578a5df600d7446d Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:15:40 -0500 Subject: [PATCH 07/36] Update optional matrix tests --- nrrd/tests/test_formatting.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nrrd/tests/test_formatting.py b/nrrd/tests/test_formatting.py index f95680e..8375c2c 100644 --- a/nrrd/tests/test_formatting.py +++ b/nrrd/tests/test_formatting.py @@ -83,11 +83,21 @@ def test_format_optional_matrix(self): 'none (1,2,3) (4,5,6) (7,8,9)') self.assertEqual(nrrd.format_optional_matrix(np.array([ [1, 2, 3], [np.nan, np.nan, np.nan], [4, 5, 6], [7, 8, 9]])), - '(1,2,3) none (4,5,6) (7,8,9)') + '(1,2,3) none (4,5,6) (7,8,9)') self.assertEqual(nrrd.format_optional_matrix(np.array([ [None, None, None], [1, 2, 3], [4, 5, 6], [7, 8, 9]])), 'none (1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_matrix([ + [np.nan, np.nan, np.nan], [1, 2, 3], [4, 5, 6], [7, 8, 9]]), + 'none (1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_matrix([ + [1, 2, 3], [np.nan, np.nan, np.nan], [4, 5, 6], [7, 8, 9]]), + '(1,2,3) none (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_matrix([ + [None, None, None], [1, 2, 3], [4, 5, 6], [7, 8, 9]]), + 'none (1,2,3) (4,5,6) (7,8,9)') + def test_format_number_list(self): self.assertEqual(nrrd.format_number_list([1, 2, 3]), '1 2 3') self.assertEqual(nrrd.format_number_list([1., 2., 3.]), '1 2 3') From 8d4d1655b866c290c87bc532cef63f058499312c Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:44:15 -0500 Subject: [PATCH 08/36] Finalize vector list support --- nrrd/parsers.py | 84 +++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 2da5952..8b499a6 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -212,33 +212,6 @@ def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - return number_list -def parse_number_auto_dtype(x: str) -> Union[int, float]: - """Parse number from string with automatic type detection. - - Parses input string and converts to a number using automatic type detection. If the number contains any - fractional parts, then the number will be converted to float, otherwise the number will be converted to an int. - - See :ref:`background/datatypes:int` and :ref:`background/datatypes:double` for more information on the format. - - Parameters - ---------- - x : :class:`str` - String representation of number - - Returns - ------- - result : :class:`int` or :class:`float` - Number parsed from :obj:`x` string - """ - - value: Union[int, float] = float(x) - - if value.is_integer(): - value = int(value) - - return value - - def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[npt.NDArray]: """Parse NRRD vector list from string into (N,) :class:`numpy.ndarray`. @@ -269,26 +242,27 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - # Get the size of each row vector and then remove duplicate sizes # There should be exactly one value in the matrix because all row sizes need to be the same if len(np.unique([len(x) for x in vector_list])) != 1: - raise NRRDError('Matrix should have same number of elements in each row') + raise NRRDError('Vector list should have same number of elements in each row') # If using automatic datatype detection, then start by converting to float and determining if the number is whole # Truncate to integer if dtype is int also if dtype is None: - matrix_trunc = vector_list.astype(int) - - if np.all((vector_list - matrix_trunc) == 0): - vector_list = matrix_trunc + vector_list_trunc = [x.astype(int) for x in vector_list] + if np.all([np.array_equal(x, y) for x, y in zip(vector_list, vector_list_trunc)]): + vector_list = vector_list_trunc elif dtype == int: - vector_list = [x.astype(int) if x is not None else None for x in vector_list] + vector_list = [x.astype(int) for x in vector_list] elif dtype != float: raise NRRDError('dtype should be None for automatic type detection, float or int') return vector_list -def parse_optional_vector_list(x: str) -> List[Optional[npt.NDArray]]: +def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[Optional[npt.NDArray]]: """Parse optional NRRD matrix from string into (M,N) :class:`numpy.ndarray` of :class:`float`. + TODO + Function parses optional NRRD matrix from string into an (M,N) :class:`numpy.ndarray` of :class:`float`. This function works the same as :meth:`parse_matrix` except if a row vector in the matrix is none, the resulting row in the returned matrix will be all NaNs. @@ -321,9 +295,43 @@ def parse_optional_vector_list(x: str) -> List[Optional[npt.NDArray]]: if len(unique_sizes) != 1 and (len(unique_sizes) != 2 or unique_sizes.min() != 0): raise NRRDError('Matrix should have same number of elements in each row') - # Create a vector row of NaN's that matches same size of remaining vector rows - # Stack the vector rows together to create matrix - nan_row = np.full((unique_sizes.max()), np.nan) - vector_list = np.vstack([nan_row if x is None else x for x in vector_list]) + # If using automatic datatype detection, then start by converting to float and determining if the number is whole + # Truncate to integer if dtype is int also + if dtype is None: + vector_list_trunc = [x.astype(int) if x is not None else None for x in vector_list] + + if np.all([np.array_equal(x, y) for x, y in zip(vector_list, vector_list_trunc)]): + vector_list = vector_list_trunc + elif dtype == int: + vector_list = [x.astype(int) if x is not None else None for x in vector_list] + elif dtype != float: + raise NRRDError('dtype should be None for automatic type detection, float or int') return vector_list + + +def parse_number_auto_dtype(x: str) -> Union[int, float]: + """Parse number from string with automatic type detection. + + Parses input string and converts to a number using automatic type detection. If the number contains any + fractional parts, then the number will be converted to float, otherwise the number will be converted to an int. + + See :ref:`background/datatypes:int` and :ref:`background/datatypes:double` for more information on the format. + + Parameters + ---------- + x : :class:`str` + String representation of number + + Returns + ------- + result : :class:`int` or :class:`float` + Number parsed from :obj:`x` string + """ + + value: Union[int, float] = float(x) + + if value.is_integer(): + value = int(value) + + return value From 7a528a3ebb429753c8284b55bb8b8c96c3624f0c Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:47:08 -0500 Subject: [PATCH 09/36] Add vector list formatters --- nrrd/formatters.py | 54 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/nrrd/formatters.py b/nrrd/formatters.py index d860469..ff49ec4 100644 --- a/nrrd/formatters.py +++ b/nrrd/formatters.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import List, Optional, Union import numpy as np import numpy.typing as npt @@ -162,3 +162,55 @@ def format_number_list(x: npt.NDArray) -> str: x = np.asarray(x) return ' '.join([format_number(y) for y in x]) + + +def format_vector_list(x: List[npt.NDArray]) -> str: + """Format a (M,N) :class:`numpy.ndarray` into a NRRD matrix string + + TODO + + See :ref:`background/datatypes:int matrix` and :ref:`background/datatypes:double matrix` for more information on + the format. + + Parameters + ---------- + x : (M,N) :class:`numpy.ndarray` + Matrix to convert to NRRD vector string + + Returns + ------- + matrix : :class:`str` + String containing NRRD matrix + """ + + return ' '.join([format_vector(y) for y in x]) + + +def format_optional_vector_list(x: List[Optional[npt.NDArray]]) -> str: + """Format a (M,N) :class:`numpy.ndarray` of :class:`float` into a NRRD optional matrix string + + TODO + + Function converts a (M,N) :class:`numpy.ndarray` of :class:`float` into a string using the NRRD matrix format. For + any rows of the matrix that contain all NaNs for each element, the row will be replaced with a 'none' indicating + the row has no vector. + + See :ref:`background/datatypes:double matrix` for more information on the format. + + .. note:: + :obj:`x` must have a datatype of float because NaN's are only defined for floating point numbers. + + Parameters + ---------- + x : (M,N) :class:`numpy.ndarray` of :class:`float` + Matrix to convert to NRRD vector string + + Returns + ------- + matrix : :class:`str` + String containing NRRD matrix + """ + # Convert to float dtype to convert None to NaN + x = np.asarray(x, dtype=float) + + return ' '.join([format_optional_vector(y) for y in x]) \ No newline at end of file From 875540cfc718a9fbd71a234eb2d82661e4aff511 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:49:49 -0500 Subject: [PATCH 10/36] Add new types to docs --- docs/source/reference/formatting.rst | 4 +++- docs/source/reference/parsing.rst | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/formatting.rst b/docs/source/reference/formatting.rst index 1e83557..259b1e9 100644 --- a/docs/source/reference/formatting.rst +++ b/docs/source/reference/formatting.rst @@ -9,8 +9,10 @@ Formatting NRRD fields nrrd.format_optional_vector nrrd.format_matrix nrrd.format_optional_matrix + nrrd.format_vector_list + nrrd.format_optional_vector_list .. automodule:: nrrd - :members: format_number, format_number_list, format_vector, format_optional_vector, format_matrix, format_optional_matrix + :members: format_number, format_number_list, format_vector, format_optional_vector, format_matrix, format_optional_matrix, format_vector_list, format_optional_vector_list :undoc-members: :show-inheritance: diff --git a/docs/source/reference/parsing.rst b/docs/source/reference/parsing.rst index e324cd6..c89ba5b 100644 --- a/docs/source/reference/parsing.rst +++ b/docs/source/reference/parsing.rst @@ -9,8 +9,10 @@ Parsing NRRD fields nrrd.parse_optional_vector nrrd.parse_matrix nrrd.parse_optional_matrix + nrrd.parse_vector_list + nrrd.parse_optional_vector_list .. automodule:: nrrd - :members: parse_number_auto_dtype, parse_number_list, parse_vector, parse_optional_vector, parse_matrix, parse_optional_matrix + :members: parse_number_auto_dtype, parse_number_list, parse_vector, parse_optional_vector, parse_matrix, parse_optional_matrix, parse_vector_list, parse_optional_vector_list :undoc-members: :show-inheritance: From 7160918ae9daebc72a02a06fbfec06fa9cfbaea2 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:49:55 -0500 Subject: [PATCH 11/36] Add to init --- nrrd/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 6bf8b3e..09abe80 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -6,6 +6,7 @@ from nrrd.writer import write __all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix', - 'format_optional_matrix', 'format_optional_vector', 'format_vector', 'parse_matrix', - 'parse_number_auto_dtype', 'parse_number_list', 'parse_optional_matrix', 'parse_optional_vector', - 'parse_vector', 'NRRDFieldType', 'NRRDFieldMap', 'NRRDHeader', '__version__'] + 'format_optional_matrix', 'format_optional_vector', 'format_vector', 'format_vector_list', 'format_optional_vector_list', + 'parse_matrix', 'parse_number_auto_dtype', 'parse_number_list', 'parse_optional_matrix', + 'parse_optional_vector', 'parse_vector', 'parse_vector_list', 'parse_optional_vector_list', 'NRRDFieldType', + 'NRRDFieldMap', 'NRRDHeader', '__version__'] From 5665ee1793a254b78d54bba2593887c0fc1fe5d1 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 19:57:47 -0500 Subject: [PATCH 12/36] Support new datatypes int vector list & double vector list --- nrrd/reader.py | 6 ++++++ nrrd/writer.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/nrrd/reader.py b/nrrd/reader.py index b6a69b9..8e9ddf9 100644 --- a/nrrd/reader.py +++ b/nrrd/reader.py @@ -109,6 +109,8 @@ def _get_field_type(field: str, custom_field_map: Optional[NRRDFieldMap]) -> NRR elif field in ['measurement frame']: return 'double matrix' elif field in ['space directions']: + # TODO Addison + # return 'double vector list' return 'double matrix' else: if custom_field_map and field in custom_field_map: @@ -144,6 +146,10 @@ def _parse_field_value(value: str, field_type: NRRDFieldType) -> Any: # This is only valid for double matrices because the matrix is represented with NaN in the entire row # for none rows. NaN is only valid for floating point numbers return parse_optional_matrix(value) + elif field_type == 'int vector list': + return parse_optional_vector_list(value, dtype=int) + elif field_type == 'double vector list': + return parse_optional_vector_list(value, dtype=float) else: raise NRRDError(f'Invalid field type given: {field_type}') diff --git a/nrrd/writer.py b/nrrd/writer.py index e439409..52da6d4 100644 --- a/nrrd/writer.py +++ b/nrrd/writer.py @@ -97,6 +97,10 @@ def _format_field_value(value: Any, field_type: NRRDFieldType) -> str: return format_matrix(value) elif field_type == 'double matrix': return format_optional_matrix(value) + elif field_type == 'int vector list': + return format_optional_vector_list(value) + elif field_type == 'double vector list': + return format_optional_vector_list(value) else: raise NRRDError(f'Invalid field type given: {field_type}') From 4fa8a390d9354ffd1b3be0ef92c02796eacf0d71 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 20:02:48 -0500 Subject: [PATCH 13/36] Update formatters docs --- nrrd/formatters.py | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/nrrd/formatters.py b/nrrd/formatters.py index ff49ec4..2083456 100644 --- a/nrrd/formatters.py +++ b/nrrd/formatters.py @@ -165,50 +165,43 @@ def format_number_list(x: npt.NDArray) -> str: def format_vector_list(x: List[npt.NDArray]) -> str: - """Format a (M,N) :class:`numpy.ndarray` into a NRRD matrix string - - TODO + """Format a :class:`list` of (N,) :class:`numpy.ndarray` into a NRRD vector list string - See :ref:`background/datatypes:int matrix` and :ref:`background/datatypes:double matrix` for more information on + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on the format. Parameters ---------- - x : (M,N) :class:`numpy.ndarray` - Matrix to convert to NRRD vector string + x : :class:`list` of (N,) :class:`numpy.ndarray` + Vector list to convert to NRRD vector list string Returns ------- - matrix : :class:`str` - String containing NRRD matrix + vector_list : :class:`str` + String containing NRRD vector list """ return ' '.join([format_vector(y) for y in x]) def format_optional_vector_list(x: List[Optional[npt.NDArray]]) -> str: - """Format a (M,N) :class:`numpy.ndarray` of :class:`float` into a NRRD optional matrix string + """Format a :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None` into a NRRD optional vector list string - TODO + Function converts a :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None` into a string using + the NRRD vector list format. - Function converts a (M,N) :class:`numpy.ndarray` of :class:`float` into a string using the NRRD matrix format. For - any rows of the matrix that contain all NaNs for each element, the row will be replaced with a 'none' indicating - the row has no vector. - - See :ref:`background/datatypes:double matrix` for more information on the format. - - .. note:: - :obj:`x` must have a datatype of float because NaN's are only defined for floating point numbers. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on + the format. Parameters ---------- - x : (M,N) :class:`numpy.ndarray` of :class:`float` - Matrix to convert to NRRD vector string + x : :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None` + Vector list to convert to NRRD vector list string Returns ------- - matrix : :class:`str` - String containing NRRD matrix + vector_list : :class:`str` + String containing NRRD vector list """ # Convert to float dtype to convert None to NaN x = np.asarray(x, dtype=float) From 407e44d881dfaf46a8fcb29f0bc50325deaed262 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 20:04:48 -0500 Subject: [PATCH 14/36] Update parser docs --- nrrd/parsers.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 8b499a6..2d31736 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -213,9 +213,9 @@ def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[npt.NDArray]: - """Parse NRRD vector list from string into (N,) :class:`numpy.ndarray`. + """Parse NRRD vector list from string into a :class`list` of (N,) :class:`numpy.ndarray`. - TODO + Parses input string to convert it into a list of Numpy arrays using the NRRD vector list format. See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on the format. @@ -223,17 +223,17 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - Parameters ---------- x : :class:`str` - String containing NRRD matrix + String containing NRRD vector list dtype : data-type, optional - Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If - :obj:`dtype` is :obj:`None`, then it will be automatically determined by checking any of the elements - for fractional numbers. If found, then the matrix will be converted to :class:`float`, otherwise :class:`int`. + Datatype to use for the resulting Numpy arrays. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If + :obj:`dtype` is :obj:`None`, it will be automatically determined by checking any of the vector elements + for fractional numbers. If found, the vectors will be converted to :class:`float`, otherwise :class:`int`. Default is to automatically determine datatype. Returns ------- - matrix : (M,N) :class:`numpy.ndarray` - Matrix that is parsed from the :obj:`x` string + vector_list : :class:`list` of (N,) :class:`numpy.ndarray` + List of vectors that are parsed from the :obj:`x` string """ # Split input by spaces, convert each row into a vector and stack them vertically to get a matrix @@ -259,25 +259,24 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[Optional[npt.NDArray]]: - """Parse optional NRRD matrix from string into (M,N) :class:`numpy.ndarray` of :class:`float`. + """Parse optional NRRD vector list from string into :class`list` of (N,) :class:`numpy.ndarray` of :class:`float`. - TODO + Function parses optional NRRD vector list from string into a list of (N,) :class:`numpy.ndarray` or :obj:`None`. + This function works the same as :meth:`parse_vector_list` except if a row vector in the list is none, the resulting + row in the returned list will be :obj:`None`. - Function parses optional NRRD matrix from string into an (M,N) :class:`numpy.ndarray` of :class:`float`. This - function works the same as :meth:`parse_matrix` except if a row vector in the matrix is none, the resulting row in - the returned matrix will be all NaNs. - - See :ref:`background/datatypes:double matrix` for more information on the format. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on + the format. Parameters ---------- x : :class:`str` - String containing NRRD matrix + String containing NRRD vector list Returns ------- - matrix : (M,N) :class:`numpy.ndarray` of :class:`float` - Matrix that is parsed from the :obj:`x` string + vector_list : :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None` + List of vectors that is parsed from the :obj:`x` string """ # Split input by spaces to get each row and convert into a vector. The row can be 'none', in which case it will From fc710f8748145e6d49dfe57895133b4f9874a491 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 20:09:43 -0500 Subject: [PATCH 15/36] working on docs --- docs/source/background/datatypes.rst | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/source/background/datatypes.rst b/docs/source/background/datatypes.rst index 3e4a466..dcf2707 100644 --- a/docs/source/background/datatypes.rst +++ b/docs/source/background/datatypes.rst @@ -72,38 +72,38 @@ double vector pynrrd will correctly handle vectors with or without spaces between the comma-delimiter. Saving the NRRD file back will remove all spaces between the comma-delimiters. -int vector list -~~~~~~~~~~~~~~~~~~ +int matrix +~~~~~~~~~~ :NRRD Syntax: (,,...,) (,,...,) ... (,,...,) :NRRD Example: (1,0,0) (0,1,0) (0,0,1) -:Python Datatype: :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` -:Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([0, 0, 1])] +:Python Datatype: (M,N) :class:`numpy.ndarray` of :class:`int` +:Python Example: np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) All rows of the matrix are required, unlike that of the `double matrix`_. If some of the rows need to be 'none', then use a `double matrix`_ instead. The reason is that empty rows (i.e. containing 'none') are represented as a row of NaN's, and NaN's are only available for floating point numbers. -double vector list +double matrix ~~~~~~~~~~~~~~~~~~ :NRRD Syntax: (,,...,) (,,...,) ... (,,...,) :NRRD Example: (2.54, 1.3, 0.0) (3.14, 0.3, 3.3) none (0.05, -12.3, -3.3) -:Python Datatype: :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` -:Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] +:Python Datatype: (M,N) :class:`numpy.ndarray` of :class:`float` +:Python Example: np.array([[2.54, 1.3, 0.0], [3.14, 0.3, 3.3], [np.nan, np.nan, np.nan], [0.0, -12.3, -3.3]]) This datatype has the added feature where rows can be defined as empty by setting the vector as :code:`none`. In the NRRD specification, instead of the row, the :code:`none` keyword is used in it's place. This is represented in the Python NumPy array as a row of all NaN's. An example use case for this optional row matrix is for the 'space directions' field where one row may be empty because it is not a domain type. -int matrix -~~~~~~~~~~ +int vector list +~~~~~~~~~~~~~~~~~~ :NRRD Syntax: (,,...,) (,,...,) ... (,,...,) -:NRRD Example: (1,0,0) (0,1,0) (0,0,1) -:Python Datatype: (M,N) :class:`numpy.ndarray` of :class:`int` -:Python Example: np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) +:NRRD Example: (1,0,0) (0,1,0) none (0,0,1) +:Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` +:Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), None, np.array([0, 0, 1])] All rows of the matrix are required, unlike that of the `double matrix`_. If some of the rows need to be 'none', then use a `double matrix`_ instead. The reason is that empty rows (i.e. containing 'none') are represented as a row of NaN's, and NaN's are only available for floating point numbers. -double matrix +double vector list ~~~~~~~~~~~~~~~~~~ :NRRD Syntax: (,,...,) (,,...,) ... (,,...,) :NRRD Example: (2.54, 1.3, 0.0) (3.14, 0.3, 3.3) none (0.05, -12.3, -3.3) -:Python Datatype: (M,N) :class:`numpy.ndarray` of :class:`float` -:Python Example: np.array([[2.54, 1.3, 0.0], [3.14, 0.3, 3.3], [np.nan, np.nan, np.nan], [0.0, -12.3, -3.3]]) +:Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` +:Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] This datatype has the added feature where rows can be defined as empty by setting the vector as :code:`none`. In the NRRD specification, instead of the row, the :code:`none` keyword is used in it's place. This is represented in the Python NumPy array as a row of all NaN's. An example use case for this optional row matrix is for the 'space directions' field where one row may be empty because it is not a domain type. \ No newline at end of file From c48f3442fb06dd935583659bac20c84fb48c7fd2 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 20:09:52 -0500 Subject: [PATCH 16/36] bugfix --- nrrd/parsers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 2d31736..0465e2c 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -213,7 +213,7 @@ def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[npt.NDArray]: - """Parse NRRD vector list from string into a :class`list` of (N,) :class:`numpy.ndarray`. + """Parse NRRD vector list from string into a :class:`list` of (N,) :class:`numpy.ndarray`. Parses input string to convert it into a list of Numpy arrays using the NRRD vector list format. @@ -259,7 +259,7 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[Optional[npt.NDArray]]: - """Parse optional NRRD vector list from string into :class`list` of (N,) :class:`numpy.ndarray` of :class:`float`. + """Parse optional NRRD vector list from string into :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float`. Function parses optional NRRD vector list from string into a list of (N,) :class:`numpy.ndarray` or :obj:`None`. This function works the same as :meth:`parse_vector_list` except if a row vector in the list is none, the resulting From ad74d6109272e5cd8956f936c5987b291848443f Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Wed, 30 Oct 2024 20:14:05 -0500 Subject: [PATCH 17/36] update docs --- docs/source/background/datatypes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/background/datatypes.rst b/docs/source/background/datatypes.rst index dcf2707..3a6431a 100644 --- a/docs/source/background/datatypes.rst +++ b/docs/source/background/datatypes.rst @@ -97,7 +97,7 @@ int vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` :Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), None, np.array([0, 0, 1])] -All rows of the matrix are required, unlike that of the `double matrix`_. If some of the rows need to be 'none', then use a `double matrix`_ instead. The reason is that empty rows (i.e. containing 'none') are represented as a row of NaN's, and NaN's are only available for floating point numbers. +This datatype is similar to `int matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (,N) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. double vector list ~~~~~~~~~~~~~~~~~~ @@ -106,4 +106,4 @@ double vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` :Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] -This datatype has the added feature where rows can be defined as empty by setting the vector as :code:`none`. In the NRRD specification, instead of the row, the :code:`none` keyword is used in it's place. This is represented in the Python NumPy array as a row of all NaN's. An example use case for this optional row matrix is for the 'space directions' field where one row may be empty because it is not a domain type. \ No newline at end of file +This datatype is similar to `double matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (,N) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. \ No newline at end of file From ee8a885e623e41181fd2583c4381149b0c8816bb Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:31:12 -0500 Subject: [PATCH 18/36] Add format tets --- nrrd/tests/test_formatting.py | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/nrrd/tests/test_formatting.py b/nrrd/tests/test_formatting.py index 8375c2c..c97d9b1 100644 --- a/nrrd/tests/test_formatting.py +++ b/nrrd/tests/test_formatting.py @@ -107,6 +107,47 @@ def test_format_number_list(self): self.assertEqual(nrrd.format_number_list(np.array([1., 2., 3.])), '1 2 3') self.assertEqual(nrrd.format_number_list(np.array([1.2, 2., 3.2])), '1.2 2 3.2000000000000002') + def test_format_vector_list(self): + self.assertEqual(nrrd.format_vector_list([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_vector_list([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_vector_list([[1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]]), + '(1,2.2000000000000002,3.2999999999999998) (4.4000000000000004,5.5,6.5999999999999996) ' + '(7.7000000000000002,8.8000000000000007,9.9000000000000004)') + + self.assertEqual(nrrd.format_vector_list(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_vector_list(np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]])), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_vector_list(np.array([[1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]])), + '(1,2.2000000000000002,3.2999999999999998) (4.4000000000000004,5.5,6.5999999999999996) ' + '(7.7000000000000002,8.8000000000000007,9.9000000000000004)') + + def test_format_optional_vector_list(self): + self.assertEqual(nrrd.format_optional_vector_list([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]]), + '(1,2.2000000000000002,3.2999999999999998) (4.4000000000000004,5.5,6.5999999999999996) ' + '(7.7000000000000002,8.8000000000000007,9.9000000000000004)') + + self.assertEqual(nrrd.format_optional_vector_list([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]), + '(1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]]), + '(1,2.2000000000000002,3.2999999999999998) (4.4000000000000004,5.5,6.5999999999999996) ' + '(7.7000000000000002,8.8000000000000007,9.9000000000000004)') + + self.assertEqual(nrrd.format_optional_vector_list([[np.nan, np.nan, np.nan], [1, 2, 3], [4, 5, 6], [7, 8, 9]]), + 'none (1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[1, 2, 3], [np.nan, np.nan, np.nan], [4, 5, 6], [7, 8, 9]]), + '(1,2,3) none (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([[None, None, None], [1, 2, 3], [4, 5, 6], [7, 8, 9]]), + 'none (1,2,3) (4,5,6) (7,8,9)') + if __name__ == '__main__': unittest.main() From 14331bb5d88781830fd3e4034daa377cb1551938 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:39:31 -0500 Subject: [PATCH 19/36] Refactor tests --- nrrd/parsers.py | 2 +- nrrd/tests/test_parsing.py | 56 +++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 0465e2c..1248e8f 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -292,7 +292,7 @@ def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] unique_sizes = np.unique(sizes) if len(unique_sizes) != 1 and (len(unique_sizes) != 2 or unique_sizes.min() != 0): - raise NRRDError('Matrix should have same number of elements in each row') + raise NRRDError('Vector list should have same number of elements in each row') # If using automatic datatype detection, then start by converting to float and determining if the number is whole # Truncate to integer if dtype is int also diff --git a/nrrd/tests/test_parsing.py b/nrrd/tests/test_parsing.py index 2897bc7..66f6f03 100644 --- a/nrrd/tests/test_parsing.py +++ b/nrrd/tests/test_parsing.py @@ -10,7 +10,10 @@ def setUp(self): pass def assert_equal_with_datatype(self, desired, actual): - self.assertEqual(desired.dtype, np.array(actual[0]).dtype) + if isinstance(desired, list): + self.assertEqual(desired[0].dtype, np.array(actual[0]).dtype) + else: + self.assertEqual(desired.dtype, np.array(actual[0]).dtype) np.testing.assert_equal(desired, actual) def test_parse_vector(self): @@ -154,6 +157,57 @@ def test_parse_number_auto_dtype(self): self.assertEqual(nrrd.parse_number_auto_dtype('25'), 25) self.assertEqual(nrrd.parse_number_auto_dtype('25.125'), 25.125) + def test_parse_vector_list(self): + self.assert_equal_with_datatype( + nrrd.parse_vector_list('(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)'), + [[1.4726600000000003, 0, 0], [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) + + self.assert_equal_with_datatype( + nrrd.parse_vector_list('(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)', + dtype=float), + [[1.4726600000000003, 0, 0], [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) + + self.assert_equal_with_datatype( + nrrd.parse_vector_list('(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)', + dtype=int), [[1, 0, 0], [0, 1, 0], [0, 0, 4]]) + + self.assert_equal_with_datatype(nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)'), + [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + self.assert_equal_with_datatype(nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)', dtype=float), + [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) + self.assert_equal_with_datatype(nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)', dtype=int), + [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + with self.assertRaisesRegex(nrrd.NRRDError, 'Vector list should have same number of elements in each row'): + nrrd.parse_vector_list('(1,0,0,0) (0,1,0) (0,0,1)') + + with self.assertRaisesRegex(nrrd.NRRDError, 'dtype should be None for automatic type detection, float or int'): + nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)', dtype=np.uint8) + + def test_parse_optional_vector_list(self): + # TODO Help me + self.assert_equal_with_datatype(nrrd.parse_optional_vector_list( + '(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)'), + [[1.4726600000000003, 0, 0], [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) + + self.assert_equal_with_datatype(nrrd.parse_optional_vector_list('(1,0,0) (0,1,0) (0,0,1)'), + [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + self.assert_equal_with_datatype(nrrd.parse_optional_vector_list( + '(1.4726600000000003,-0,0) none (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)'), + [[1.4726600000000003, 0, 0], None, [0, 1.4726600000000003, 0], + [0, 0, 4.7619115092114601]]) + + self.assert_equal_with_datatype(nrrd.parse_optional_vector_list( + '(1.4726600000000003,-0,0) none (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)'), + [[1.4726600000000003, 0, 0], None, [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) + + with self.assertRaisesRegex(nrrd.NRRDError, 'Vector list should have same number of elements in each row'): + nrrd.parse_optional_vector_list('(1,0,0,0) (0,1,0) (0,0,1)') + + with self.assertRaisesRegex(nrrd.NRRDError, 'Vector list should have same number of elements in each row'): + nrrd.parse_optional_vector_list('none (1,0,0,0) (0,1,0) (0,0,1)') + if __name__ == '__main__': unittest.main() From c42464bf79bfa46aac4d359b276c81416d8fb3c2 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:40:58 -0500 Subject: [PATCH 20/36] x --- docs/source/background/datatypes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/background/datatypes.rst b/docs/source/background/datatypes.rst index 3a6431a..494eabf 100644 --- a/docs/source/background/datatypes.rst +++ b/docs/source/background/datatypes.rst @@ -97,7 +97,7 @@ int vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` :Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), None, np.array([0, 0, 1])] -This datatype is similar to `int matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (,N) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. +This datatype is similar to `int matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. double vector list ~~~~~~~~~~~~~~~~~~ @@ -106,4 +106,4 @@ double vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` :Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] -This datatype is similar to `double matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (,N) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. \ No newline at end of file +This datatype is similar to `double matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. \ No newline at end of file From 1a82c9236c4689f927450f25b55b3e82667dcdc3 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:44:30 -0500 Subject: [PATCH 21/36] Test --- nrrd/tests/test_parsing.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nrrd/tests/test_parsing.py b/nrrd/tests/test_parsing.py index 66f6f03..a3d2be0 100644 --- a/nrrd/tests/test_parsing.py +++ b/nrrd/tests/test_parsing.py @@ -184,8 +184,11 @@ def test_parse_vector_list(self): with self.assertRaisesRegex(nrrd.NRRDError, 'dtype should be None for automatic type detection, float or int'): nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)', dtype=np.uint8) + vector_list = nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)') + self.assertIsInstance(vector_list, list) + self.assertTrue(all(isinstance(vector, np.ndarray) for vector in vector_list)) + def test_parse_optional_vector_list(self): - # TODO Help me self.assert_equal_with_datatype(nrrd.parse_optional_vector_list( '(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)'), [[1.4726600000000003, 0, 0], [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) @@ -208,6 +211,9 @@ def test_parse_optional_vector_list(self): with self.assertRaisesRegex(nrrd.NRRDError, 'Vector list should have same number of elements in each row'): nrrd.parse_optional_vector_list('none (1,0,0,0) (0,1,0) (0,0,1)') + vector_list = nrrd.parse_optional_vector_list('(1,0,0) (0,1,0) none (0,0,1)') + self.assertIsInstance(vector_list, list) + self.assertTrue(all(vector is None or isinstance(vector, np.ndarray) for vector in vector_list)) if __name__ == '__main__': unittest.main() From 1a4fa123ac155ea253b9cb9705ab3fe6ed4a562b Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:48:37 -0500 Subject: [PATCH 22/36] minor docs fixes --- nrrd/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 1248e8f..550c9c4 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -236,7 +236,7 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - List of vectors that are parsed from the :obj:`x` string """ - # Split input by spaces, convert each row into a vector and stack them vertically to get a matrix + # Split input by spaces, convert each row into a vector vector_list = [parse_vector(x, dtype=float) for x in x.split()] # Get the size of each row vector and then remove duplicate sizes From a2c0940fd08c52a1fe8c4fac03d1723cf846980a Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:53:35 -0500 Subject: [PATCH 23/36] fix linter issues --- nrrd/__init__.py | 5 +++-- nrrd/formatters.py | 12 ++++++------ nrrd/parsers.py | 8 ++++---- nrrd/tests/test_parsing.py | 5 +++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 09abe80..fb28bfd 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -6,7 +6,8 @@ from nrrd.writer import write __all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix', - 'format_optional_matrix', 'format_optional_vector', 'format_vector', 'format_vector_list', 'format_optional_vector_list', - 'parse_matrix', 'parse_number_auto_dtype', 'parse_number_list', 'parse_optional_matrix', + 'format_optional_matrix', 'format_optional_vector', 'format_vector', 'format_vector_list', + 'format_optional_vector_list', 'parse_matrix', 'parse_number_auto_dtype', 'parse_number_list', + 'parse_optional_matrix', 'parse_optional_vector', 'parse_vector', 'parse_vector_list', 'parse_optional_vector_list', 'NRRDFieldType', 'NRRDFieldMap', 'NRRDHeader', '__version__'] diff --git a/nrrd/formatters.py b/nrrd/formatters.py index 2083456..04df561 100644 --- a/nrrd/formatters.py +++ b/nrrd/formatters.py @@ -89,7 +89,7 @@ def format_optional_vector(x: Optional[npt.NDArray]) -> str: # If all elements are None or NaN, then return none # Otherwise format the vector as normal - if np.all(x == None) or np.all(np.isnan(x)): + if np.all(x == None) or np.all(np.isnan(x)): # noqa: E711 return 'none' else: return format_vector(x) @@ -167,8 +167,8 @@ def format_number_list(x: npt.NDArray) -> str: def format_vector_list(x: List[npt.NDArray]) -> str: """Format a :class:`list` of (N,) :class:`numpy.ndarray` into a NRRD vector list string - See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on - the format. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more + information on the format. Parameters ---------- @@ -190,8 +190,8 @@ def format_optional_vector_list(x: List[Optional[npt.NDArray]]) -> str: Function converts a :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None` into a string using the NRRD vector list format. - See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on - the format. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more + information on the format. Parameters ---------- @@ -206,4 +206,4 @@ def format_optional_vector_list(x: List[Optional[npt.NDArray]]) -> str: # Convert to float dtype to convert None to NaN x = np.asarray(x, dtype=float) - return ' '.join([format_optional_vector(y) for y in x]) \ No newline at end of file + return ' '.join([format_optional_vector(y) for y in x]) diff --git a/nrrd/parsers.py b/nrrd/parsers.py index 550c9c4..01d654d 100644 --- a/nrrd/parsers.py +++ b/nrrd/parsers.py @@ -217,8 +217,8 @@ def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) - Parses input string to convert it into a list of Numpy arrays using the NRRD vector list format. - See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on - the format. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more + information on the format. Parameters ---------- @@ -265,8 +265,8 @@ def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] This function works the same as :meth:`parse_vector_list` except if a row vector in the list is none, the resulting row in the returned list will be :obj:`None`. - See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more information on - the format. + See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more + information on the format. Parameters ---------- diff --git a/nrrd/tests/test_parsing.py b/nrrd/tests/test_parsing.py index a3d2be0..ef53f93 100644 --- a/nrrd/tests/test_parsing.py +++ b/nrrd/tests/test_parsing.py @@ -164,12 +164,12 @@ def test_parse_vector_list(self): self.assert_equal_with_datatype( nrrd.parse_vector_list('(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)', - dtype=float), + dtype=float), [[1.4726600000000003, 0, 0], [0, 1.4726600000000003, 0], [0, 0, 4.7619115092114601]]) self.assert_equal_with_datatype( nrrd.parse_vector_list('(1.4726600000000003,-0,0) (-0,1.4726600000000003,-0) (0,-0,4.7619115092114601)', - dtype=int), [[1, 0, 0], [0, 1, 0], [0, 0, 4]]) + dtype=int), [[1, 0, 0], [0, 1, 0], [0, 0, 4]]) self.assert_equal_with_datatype(nrrd.parse_vector_list('(1,0,0) (0,1,0) (0,0,1)'), [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) @@ -215,5 +215,6 @@ def test_parse_optional_vector_list(self): self.assertIsInstance(vector_list, list) self.assertTrue(all(vector is None or isinstance(vector, np.ndarray) for vector in vector_list)) + if __name__ == '__main__': unittest.main() From 938aa9a5dc2a01c9392ef2b26beca15dc74423b7 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 15:59:58 -0500 Subject: [PATCH 24/36] Expm change for space directions type --- nrrd/__init__.py | 4 ++-- nrrd/reader.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index fb28bfd..3b8ca2b 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -1,7 +1,7 @@ from nrrd._version import __version__ from nrrd.formatters import * from nrrd.parsers import * -from nrrd.reader import read, read_data, read_header +from nrrd.reader import read, read_data, read_header, SPACE_DIRECTIONS_TYPE from nrrd.types import NRRDFieldMap, NRRDFieldType, NRRDHeader from nrrd.writer import write @@ -10,4 +10,4 @@ 'format_optional_vector_list', 'parse_matrix', 'parse_number_auto_dtype', 'parse_number_list', 'parse_optional_matrix', 'parse_optional_vector', 'parse_vector', 'parse_vector_list', 'parse_optional_vector_list', 'NRRDFieldType', - 'NRRDFieldMap', 'NRRDHeader', '__version__'] + 'NRRDFieldMap', 'NRRDHeader', 'SPACE_DIRECTIONS_TYPE', '__version__'] diff --git a/nrrd/reader.py b/nrrd/reader.py index 8e9ddf9..d830aa5 100644 --- a/nrrd/reader.py +++ b/nrrd/reader.py @@ -6,7 +6,7 @@ import warnings import zlib from collections import OrderedDict -from typing import IO, Any, AnyStr, Iterable, Tuple +from typing import IO, Any, AnyStr, Iterable, Literal, Tuple from nrrd.parsers import * from nrrd.types import IndexOrder, NRRDFieldMap, NRRDFieldType, NRRDHeader @@ -41,6 +41,30 @@ Duplicated fields are prohibited by the NRRD file specification. """ +SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' +"""Allow duplicate header fields when reading NRRD files + +TODO Addison + +When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as +:obj:`True` will instead show a warning. + +Example: + Reading a NRRD file with duplicated header field 'space' with field set to :obj:`False`. + + >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') + nrrd.errors.NRRDError: Duplicate header field: 'space' + + Set the field as :obj:`True` to receive a warning instead. + + >>> nrrd.reader.ALLOW_DUPLICATE_FIELD = True + >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') + UserWarning: Duplicate header field: 'space' warnings.warn(dup_message) + +Note: + Duplicated fields are prohibited by the NRRD file specification. +""" + _TYPEMAP_NRRD2NUMPY = { 'signed char': 'i1', 'int8': 'i1', @@ -109,9 +133,7 @@ def _get_field_type(field: str, custom_field_map: Optional[NRRDFieldMap]) -> NRR elif field in ['measurement frame']: return 'double matrix' elif field in ['space directions']: - # TODO Addison - # return 'double vector list' - return 'double matrix' + return SPACE_DIRECTIONS_TYPE else: if custom_field_map and field in custom_field_map: return custom_field_map[field] From a019001d2e56c55a7a17ee5ea9d4cd947b13db15 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 16:10:14 -0500 Subject: [PATCH 25/36] wip --- nrrd/__init__.py | 2 +- nrrd/reader.py | 2 +- nrrd/tests/test_reading.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 3b8ca2b..581155f 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -1,7 +1,7 @@ from nrrd._version import __version__ from nrrd.formatters import * from nrrd.parsers import * -from nrrd.reader import read, read_data, read_header, SPACE_DIRECTIONS_TYPE +from nrrd.reader import SPACE_DIRECTIONS_TYPE, read, read_data, read_header from nrrd.types import NRRDFieldMap, NRRDFieldType, NRRDHeader from nrrd.writer import write diff --git a/nrrd/reader.py b/nrrd/reader.py index d830aa5..801e43c 100644 --- a/nrrd/reader.py +++ b/nrrd/reader.py @@ -19,7 +19,7 @@ _NRRD_REQUIRED_FIELDS = ['dimension', 'type', 'encoding', 'sizes'] -ALLOW_DUPLICATE_FIELD = False +ALLOW_DUPLICATE_FIELD: bool = False """Allow duplicate header fields when reading NRRD files When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as diff --git a/nrrd/tests/test_reading.py b/nrrd/tests/test_reading.py index c0a6b68..d0ce479 100644 --- a/nrrd/tests/test_reading.py +++ b/nrrd/tests/test_reading.py @@ -501,6 +501,19 @@ def test(filename: str): with self.subTest(filename): test(filename) + def test_read_space_directions_list(self): + try: + # TODO This doesn't work + # nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + nrrd.reader.SPACE_DIRECTIONS_TYPE = 'double vector list' + + _, header = nrrd.read(RAW_4D_NRRD_FILE_PATH, index_order=self.index_order) + self.assertIsInstance(header['space directions'], list) + self.assertTrue(all(vector is None or isinstance(vector, np.ndarray) for vector in header['space directions'])) + np.testing.assert_equal(header['space directions'][0].dtype, np.float64) + finally: + nrrd.reader.SPACE_DIRECTIONS_TYPE = 'double matrix' + class TestReadingFunctionsFortran(Abstract.TestReadingFunctions): index_order = 'F' From de9abc3f5b669cbfc10226a4c47e30f0d1f0582d Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 16:17:21 -0500 Subject: [PATCH 26/36] fixes --- nrrd/__init__.py | 28 +++++++++++++++++++++++++++- nrrd/reader.py | 29 +++-------------------------- nrrd/tests/test_reading.py | 13 ++++++++----- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 581155f..968be6c 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -1,10 +1,36 @@ +from typing import Literal + from nrrd._version import __version__ from nrrd.formatters import * from nrrd.parsers import * -from nrrd.reader import SPACE_DIRECTIONS_TYPE, read, read_data, read_header +from nrrd.reader import read, read_data, read_header from nrrd.types import NRRDFieldMap, NRRDFieldType, NRRDHeader from nrrd.writer import write +SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' +"""Allow duplicate header fields when reading NRRD files + +TODO Addison + +When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as +:obj:`True` will instead show a warning. + +Example: + Reading a NRRD file with duplicated header field 'space' with field set to :obj:`False`. + + >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') + nrrd.errors.NRRDError: Duplicate header field: 'space' + + Set the field as :obj:`True` to receive a warning instead. + + >>> nrrd.reader.ALLOW_DUPLICATE_FIELD = True + >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') + UserWarning: Duplicate header field: 'space' warnings.warn(dup_message) + +Note: + Duplicated fields are prohibited by the NRRD file specification. +""" + __all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix', 'format_optional_matrix', 'format_optional_vector', 'format_vector', 'format_vector_list', 'format_optional_vector_list', 'parse_matrix', 'parse_number_auto_dtype', 'parse_number_list', diff --git a/nrrd/reader.py b/nrrd/reader.py index 801e43c..1dc757e 100644 --- a/nrrd/reader.py +++ b/nrrd/reader.py @@ -6,8 +6,9 @@ import warnings import zlib from collections import OrderedDict -from typing import IO, Any, AnyStr, Iterable, Literal, Tuple +from typing import IO, Any, AnyStr, Iterable, Tuple +import nrrd from nrrd.parsers import * from nrrd.types import IndexOrder, NRRDFieldMap, NRRDFieldType, NRRDHeader @@ -41,30 +42,6 @@ Duplicated fields are prohibited by the NRRD file specification. """ -SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' -"""Allow duplicate header fields when reading NRRD files - -TODO Addison - -When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as -:obj:`True` will instead show a warning. - -Example: - Reading a NRRD file with duplicated header field 'space' with field set to :obj:`False`. - - >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') - nrrd.errors.NRRDError: Duplicate header field: 'space' - - Set the field as :obj:`True` to receive a warning instead. - - >>> nrrd.reader.ALLOW_DUPLICATE_FIELD = True - >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') - UserWarning: Duplicate header field: 'space' warnings.warn(dup_message) - -Note: - Duplicated fields are prohibited by the NRRD file specification. -""" - _TYPEMAP_NRRD2NUMPY = { 'signed char': 'i1', 'int8': 'i1', @@ -133,7 +110,7 @@ def _get_field_type(field: str, custom_field_map: Optional[NRRDFieldMap]) -> NRR elif field in ['measurement frame']: return 'double matrix' elif field in ['space directions']: - return SPACE_DIRECTIONS_TYPE + return nrrd.SPACE_DIRECTIONS_TYPE else: if custom_field_map and field in custom_field_map: return custom_field_map[field] diff --git a/nrrd/tests/test_reading.py b/nrrd/tests/test_reading.py index d0ce479..ba3b9da 100644 --- a/nrrd/tests/test_reading.py +++ b/nrrd/tests/test_reading.py @@ -503,16 +503,19 @@ def test(filename: str): def test_read_space_directions_list(self): try: - # TODO This doesn't work - # nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' - nrrd.reader.SPACE_DIRECTIONS_TYPE = 'double vector list' + nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' _, header = nrrd.read(RAW_4D_NRRD_FILE_PATH, index_order=self.index_order) self.assertIsInstance(header['space directions'], list) - self.assertTrue(all(vector is None or isinstance(vector, np.ndarray) for vector in header['space directions'])) + self.assertTrue( + all(vector is None or isinstance(vector, np.ndarray) for vector in header['space directions'])) np.testing.assert_equal(header['space directions'][0].dtype, np.float64) + np.testing.assert_equal(header['space directions'], [np.array([1.5, 0., 0.]), + np.array([0., 1.5, 0.]), + np.array([0., 0., 1.]), + None]) finally: - nrrd.reader.SPACE_DIRECTIONS_TYPE = 'double matrix' + nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' class TestReadingFunctionsFortran(Abstract.TestReadingFunctions): From 7b6359da668677bbf4962fd1097d6bb83411855e Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 16:19:17 -0500 Subject: [PATCH 27/36] xxx --- nrrd/__init__.py | 5 +---- nrrd/tests/test_writing.py | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 968be6c..8322801 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -8,7 +8,7 @@ from nrrd.writer import write SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' -"""Allow duplicate header fields when reading NRRD files +"""Datatype to use for 'space directions' field when reading/writing NRRD files TODO Addison @@ -26,9 +26,6 @@ >>> nrrd.reader.ALLOW_DUPLICATE_FIELD = True >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') UserWarning: Duplicate header field: 'space' warnings.warn(dup_message) - -Note: - Duplicated fields are prohibited by the NRRD file specification. """ __all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix', diff --git a/nrrd/tests/test_writing.py b/nrrd/tests/test_writing.py index 9b3e842..e0337fa 100644 --- a/nrrd/tests/test_writing.py +++ b/nrrd/tests/test_writing.py @@ -478,6 +478,8 @@ def test_write_memory_file_handle(self): self.assertEqual(header.pop('sizes').all(), memory_header.pop('sizes').all()) self.assertSequenceEqual(header, memory_header) + # TODO Space direcitons tests + class TestWritingFunctionsFortran(Abstract.TestWritingFunctions): index_order = 'F' From d28d1994811c7e1d21a9b031a8e1476d0a0013cf Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 16:21:27 -0500 Subject: [PATCH 28/36] xxx --- nrrd/tests/test_writing.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/nrrd/tests/test_writing.py b/nrrd/tests/test_writing.py index e0337fa..a80ef60 100644 --- a/nrrd/tests/test_writing.py +++ b/nrrd/tests/test_writing.py @@ -478,7 +478,37 @@ def test_write_memory_file_handle(self): self.assertEqual(header.pop('sizes').all(), memory_header.pop('sizes').all()) self.assertSequenceEqual(header, memory_header) - # TODO Space direcitons tests + def test_space_directions_header(self): + space_directions_list = [np.array([1.5, 0., 0.]), + np.array([0., 1.5, 0.]), + np.array([0., 0., 1.]), + None] + space_directions_matrix = np.array([[1.5, 0., 0.], + [0., 1.5, 0.], + [0., 0., 1.], + [np.nan, np.nan, np.nan]]) + + # TODO Space directions tests + output_filename = os.path.join(self.temp_write_dir, 'testfile_ascii_3d.nrrd') + + x = np.arange(1, 28).reshape((3, 3, 3), order=self.index_order) + nrrd.write(output_filename, x, { + 'encoding': 'ascii', + 'units': ['mm', 'cm', 'in'], + 'space units': ['mm', 'cm', 'in'], + 'labels': ['X', 'Y', 'f(log(X, 10), Y)'], + }, index_order=self.index_order) + + with open(output_filename) as fh: + lines = fh.readlines() + + # Strip newline from end of line + lines = [line.rstrip() for line in lines] + + # Note the order of the lines dont matter, we just want to verify they are outputted correctly + self.assertTrue('units: "mm" "cm" "in"' in lines) + self.assertTrue('space units: "mm" "cm" "in"' in lines) + self.assertTrue('labels: "X" "Y" "f(log(X, 10), Y)"' in lines) class TestWritingFunctionsFortran(Abstract.TestWritingFunctions): From 42d30ffc0f9269fa3cc43cfc3ce0bd29d7aa70d0 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 19:52:43 -0500 Subject: [PATCH 29/36] various fixes --- nrrd/formatters.py | 2 - nrrd/tests/test_formatting.py | 2 + nrrd/tests/test_writing.py | 70 ++++++++++++++++++++++++++--------- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/nrrd/formatters.py b/nrrd/formatters.py index 04df561..3a1064d 100644 --- a/nrrd/formatters.py +++ b/nrrd/formatters.py @@ -203,7 +203,5 @@ def format_optional_vector_list(x: List[Optional[npt.NDArray]]) -> str: vector_list : :class:`str` String containing NRRD vector list """ - # Convert to float dtype to convert None to NaN - x = np.asarray(x, dtype=float) return ' '.join([format_optional_vector(y) for y in x]) diff --git a/nrrd/tests/test_formatting.py b/nrrd/tests/test_formatting.py index c97d9b1..97dad70 100644 --- a/nrrd/tests/test_formatting.py +++ b/nrrd/tests/test_formatting.py @@ -147,6 +147,8 @@ def test_format_optional_vector_list(self): '(1,2,3) none (4,5,6) (7,8,9)') self.assertEqual(nrrd.format_optional_vector_list([[None, None, None], [1, 2, 3], [4, 5, 6], [7, 8, 9]]), 'none (1,2,3) (4,5,6) (7,8,9)') + self.assertEqual(nrrd.format_optional_vector_list([None, [1, 2, 3], [4, 5, 6], [7, 8, 9]]), + 'none (1,2,3) (4,5,6) (7,8,9)') if __name__ == '__main__': diff --git a/nrrd/tests/test_writing.py b/nrrd/tests/test_writing.py index a80ef60..531e54b 100644 --- a/nrrd/tests/test_writing.py +++ b/nrrd/tests/test_writing.py @@ -488,27 +488,63 @@ def test_space_directions_header(self): [0., 0., 1.], [np.nan, np.nan, np.nan]]) - # TODO Space directions tests - output_filename = os.path.join(self.temp_write_dir, 'testfile_ascii_3d.nrrd') + with self.subTest('double matrix -> double matrix'): + nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' + test_output_filename = os.path.join(self.temp_write_dir, 'testfile_space_directions_matrix.nrrd') + nrrd.write(test_output_filename, self.data_input, {'space directions': space_directions_matrix}, + index_order=self.index_order) + _, header = nrrd.read(test_output_filename, index_order=self.index_order) + np.testing.assert_equal(header['space directions'], space_directions_matrix) - x = np.arange(1, 28).reshape((3, 3, 3), order=self.index_order) - nrrd.write(output_filename, x, { - 'encoding': 'ascii', - 'units': ['mm', 'cm', 'in'], - 'space units': ['mm', 'cm', 'in'], - 'labels': ['X', 'Y', 'f(log(X, 10), Y)'], - }, index_order=self.index_order) + with self.subTest('double matrix -> double vector list'): + nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' + test_output_filename = os.path.join(self.temp_write_dir, 'testfile_space_directions_matrix.nrrd') + nrrd.write(test_output_filename, self.data_input, {'space directions': space_directions_matrix}, + index_order=self.index_order) + nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + _, header = nrrd.read(test_output_filename, index_order=self.index_order) + np.testing.assert_equal(header['space directions'], space_directions_list) + + with self.subTest('double vector list'): + nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + test_output_filename = os.path.join(self.temp_write_dir, 'testfile_space_directions_list.nrrd') + nrrd.write(test_output_filename, self.data_input, {'space directions': space_directions_list}, + index_order=self.index_order) + _, header = nrrd.read(test_output_filename, index_order=self.index_order) + np.testing.assert_equal(header['space directions'], space_directions_list) - with open(output_filename) as fh: - lines = fh.readlines() + with self.subTest('double vector list -> double matrix'): + nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + test_output_filename = os.path.join(self.temp_write_dir, 'testfile_space_directions_list.nrrd') + nrrd.write(test_output_filename, self.data_input, {'space directions': space_directions_list}, + index_order=self.index_order) + nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' + _, header = nrrd.read(test_output_filename, index_order=self.index_order) + np.testing.assert_equal(header['space directions'], space_directions_matrix) - # Strip newline from end of line - lines = [line.rstrip() for line in lines] + # nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' - # Note the order of the lines dont matter, we just want to verify they are outputted correctly - self.assertTrue('units: "mm" "cm" "in"' in lines) - self.assertTrue('space units: "mm" "cm" "in"' in lines) - self.assertTrue('labels: "X" "Y" "f(log(X, 10), Y)"' in lines) + # # TODO Space directions tests + # output_filename = os.path.join(self.temp_write_dir, 'testfile_ascii_3d.nrrd') + + # x = np.arange(1, 28).reshape((3, 3, 3), order=self.index_order) + # nrrd.write(output_filename, x, { + # 'encoding': 'ascii', + # 'units': ['mm', 'cm', 'in'], + # 'space units': ['mm', 'cm', 'in'], + # 'labels': ['X', 'Y', 'f(log(X, 10), Y)'], + # }, index_order=self.index_order) + + # with open(output_filename) as fh: + # lines = fh.readlines() + + # # Strip newline from end of line + # lines = [line.rstrip() for line in lines] + + # # Note the order of the lines dont matter, we just want to verify they are outputted correctly + # self.assertTrue('units: "mm" "cm" "in"' in lines) + # self.assertTrue('space units: "mm" "cm" "in"' in lines) + # self.assertTrue('labels: "X" "Y" "f(log(X, 10), Y)"' in lines) class TestWritingFunctionsFortran(Abstract.TestWritingFunctions): From a9d3b3c8350c40d27941e710b22c93d30dac4652 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Thu, 31 Oct 2024 19:52:54 -0500 Subject: [PATCH 30/36] fixes --- nrrd/tests/test_writing.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/nrrd/tests/test_writing.py b/nrrd/tests/test_writing.py index 531e54b..ce7bb2e 100644 --- a/nrrd/tests/test_writing.py +++ b/nrrd/tests/test_writing.py @@ -522,30 +522,6 @@ def test_space_directions_header(self): _, header = nrrd.read(test_output_filename, index_order=self.index_order) np.testing.assert_equal(header['space directions'], space_directions_matrix) - # nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' - - # # TODO Space directions tests - # output_filename = os.path.join(self.temp_write_dir, 'testfile_ascii_3d.nrrd') - - # x = np.arange(1, 28).reshape((3, 3, 3), order=self.index_order) - # nrrd.write(output_filename, x, { - # 'encoding': 'ascii', - # 'units': ['mm', 'cm', 'in'], - # 'space units': ['mm', 'cm', 'in'], - # 'labels': ['X', 'Y', 'f(log(X, 10), Y)'], - # }, index_order=self.index_order) - - # with open(output_filename) as fh: - # lines = fh.readlines() - - # # Strip newline from end of line - # lines = [line.rstrip() for line in lines] - - # # Note the order of the lines dont matter, we just want to verify they are outputted correctly - # self.assertTrue('units: "mm" "cm" "in"' in lines) - # self.assertTrue('space units: "mm" "cm" "in"' in lines) - # self.assertTrue('labels: "X" "Y" "f(log(X, 10), Y)"' in lines) - class TestWritingFunctionsFortran(Abstract.TestWritingFunctions): index_order = 'F' From 379f267bf926d3db1f3f128649f539c92841d680 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 1 Nov 2024 05:41:04 -0500 Subject: [PATCH 31/36] x --- docs/source/background/datatypes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/background/datatypes.rst b/docs/source/background/datatypes.rst index 494eabf..0b13dfa 100644 --- a/docs/source/background/datatypes.rst +++ b/docs/source/background/datatypes.rst @@ -97,7 +97,7 @@ int vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`int` :Python Example: [np.array([1, 0, 0]), np.array([0, 1, 0]), None, np.array([0, 0, 1])] -This datatype is similar to `int matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. +This datatype is similar to `int matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this library. double vector list ~~~~~~~~~~~~~~~~~~ @@ -106,4 +106,4 @@ double vector list :Python Datatype: (M,N) :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float` :Python Example: [np.array([2.54, 1.3, 0.0]), np.array([3.14, 0.3, 3.3]), None, np.array([0.0, -12.3, -3.3])] -This datatype is similar to `double matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this Python library. \ No newline at end of file +This datatype is similar to `double matrix`_ except instead of returning a (M,N) :class:`numpy.ndarray`, it returns a list of (N,) :class:`numpy.ndarray`. Each row is optional and designated by :code:`none` in the NRRD specification and represented as :obj:`None` in this library. \ No newline at end of file From 5dc627e32ea34cdc8378d6d63cc68beb54e3b726 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Fri, 1 Nov 2024 05:48:49 -0500 Subject: [PATCH 32/36] asd --- nrrd/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 8322801..c2ef815 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -7,10 +7,25 @@ from nrrd.types import NRRDFieldMap, NRRDFieldType, NRRDHeader from nrrd.writer import write +# TODO Change to 'double vector list' in next major release SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' """Datatype to use for 'space directions' field when reading/writing NRRD files TODO Addison +The 'space directions' field can be represented in two different ways: as a matrix or as a list of vectors. + +The current default is to use a matrix, but it will be switched to a list of vectors in the next major release. + +The space directions field is defined per-axis where any of the axis can be 'none'. A matrix gives the false impression the s + +The default is to use a matrix, but it can be set to use a list of vectors by setting this variable to +:obj:`'double vector list'`. This is mostly useful for backwards compatibility with older versions of the `nrrd` library +which only supported the list of vectors representation. + +Example: + >>> nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + >>> nrrd.write('output.nrrd', data, {'space directions': [np.array([1.5, 0., 0.])]}, index_order='F') + When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as :obj:`True` will instead show a warning. From ea19dfea8a2ab6b8f1069d41d4d774f532220148 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Mon, 4 Nov 2024 20:32:23 -0600 Subject: [PATCH 33/36] Finalize --- docs/source/background/fields.rst | 2 +- docs/source/reference/reading.rst | 2 ++ nrrd/__init__.py | 40 ++++++++++++------------------- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/docs/source/background/fields.rst b/docs/source/background/fields.rst index c0ac23e..03209ee 100644 --- a/docs/source/background/fields.rst +++ b/docs/source/background/fields.rst @@ -33,7 +33,7 @@ centerings_ :ref:`background/datatypes:string list` space_ :ref:`background/datatypes:string` `space dimension`_ :ref:`background/datatypes:int` `space units`_ :ref:`background/datatypes:quoted string list` -`space directions`_ :ref:`background/datatypes:double matrix` +`space directions`_ :ref:`background/datatypes:double matrix` or :ref:`background/datatypes:double vector list` depending on :data:`nrrd.SPACE_DIRECTIONS_TYPE` `space origin`_ :ref:`background/datatypes:double vector` `measurement frame`_ :ref:`background/datatypes:int matrix` ======================== ============================================== diff --git a/docs/source/reference/reading.rst b/docs/source/reference/reading.rst index 9aca987..277a1ab 100644 --- a/docs/source/reference/reading.rst +++ b/docs/source/reference/reading.rst @@ -7,6 +7,7 @@ Reading NRRD files nrrd.read_header nrrd.read_data nrrd.reader.ALLOW_DUPLICATE_FIELD + nrrd.SPACE_DIRECTIONS_TYPE .. automodule:: nrrd :members: read, read_header, read_data @@ -14,3 +15,4 @@ Reading NRRD files :show-inheritance: .. autodata:: nrrd.reader.ALLOW_DUPLICATE_FIELD +.. autodata:: nrrd.SPACE_DIRECTIONS_TYPE diff --git a/nrrd/__init__.py b/nrrd/__init__.py index c2ef815..1b24156 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -11,36 +11,26 @@ SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' """Datatype to use for 'space directions' field when reading/writing NRRD files -TODO Addison -The 'space directions' field can be represented in two different ways: as a matrix or as a list of vectors. +The 'space directions' field can be represented in two different ways: as a matrix or as a list of vectors. Per the NRRD specification, the 'space directions' field is a per-axis definition that represents the direction and spacing of each axis. Non-spatial axes are represented as 'none'. -The current default is to use a matrix, but it will be switched to a list of vectors in the next major release. - -The space directions field is defined per-axis where any of the axis can be 'none'. A matrix gives the false impression the s - -The default is to use a matrix, but it can be set to use a list of vectors by setting this variable to -:obj:`'double vector list'`. This is mostly useful for backwards compatibility with older versions of the `nrrd` library -which only supported the list of vectors representation. +The current default is to return a matrix, where each non-spatial axis is represented as a row of `NaN` in the matrix. In the next major release, this default option will change to return a list of optional vectors, where each non-spatial axis is represented as `None`. Example: - >>> nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' - >>> nrrd.write('output.nrrd', data, {'space directions': [np.array([1.5, 0., 0.])]}, index_order='F') + Reading a NRRD file with space directions type set to 'double matrix' (the default). + >>> nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' + >>> data, header = nrrd.read('file.nrrd') + >>> print(header['space directions']) + [[1.5 0. 0. ] + [0. 1.5 0. ] + [0. 0. 1. ] + [nan nan nan]] + Reading a NRRD file with space directions type set to 'double vector list'. -When there are duplicated fields in a NRRD file header, pynrrd throws an error by default. Setting this field as -:obj:`True` will instead show a warning. - -Example: - Reading a NRRD file with duplicated header field 'space' with field set to :obj:`False`. - - >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') - nrrd.errors.NRRDError: Duplicate header field: 'space' - - Set the field as :obj:`True` to receive a warning instead. - - >>> nrrd.reader.ALLOW_DUPLICATE_FIELD = True - >>> filedata, fileheader = nrrd.read('filename_duplicatedheader.nrrd') - UserWarning: Duplicate header field: 'space' warnings.warn(dup_message) + >>> nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list' + >>> data, header = nrrd.read('file.nrrd') + >>> print(header['space directions']) + [array([1.5, 0. , 0. ]), array([0. , 1.5, 0. ]), array([0., 0., 1.]), None] """ __all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix', From 9bcee9d48516819f4a772df3260cb4906f2f77af Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Mon, 4 Nov 2024 20:33:08 -0600 Subject: [PATCH 34/36] lint fixes --- nrrd/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 1b24156..17fa2c2 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -11,9 +11,13 @@ SPACE_DIRECTIONS_TYPE: Literal['double matrix', 'double vector list'] = 'double matrix' """Datatype to use for 'space directions' field when reading/writing NRRD files -The 'space directions' field can be represented in two different ways: as a matrix or as a list of vectors. Per the NRRD specification, the 'space directions' field is a per-axis definition that represents the direction and spacing of each axis. Non-spatial axes are represented as 'none'. +The 'space directions' field can be represented in two different ways: as a matrix or as a list of vectors. Per the +NRRD specification, the 'space directions' field is a per-axis definition that represents the direction and spacing of +each axis. Non-spatial axes are represented as 'none'. -The current default is to return a matrix, where each non-spatial axis is represented as a row of `NaN` in the matrix. In the next major release, this default option will change to return a list of optional vectors, where each non-spatial axis is represented as `None`. +The current default is to return a matrix, where each non-spatial axis is represented as a row of `NaN` in the matrix. +In the next major release, this default option will change to return a list of optional vectors, where each non +spatial axis is represented as `None`. Example: Reading a NRRD file with space directions type set to 'double matrix' (the default). From ec93717a83191260d08f9db0bccaee9416f990c5 Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Mon, 4 Nov 2024 20:49:17 -0600 Subject: [PATCH 35/36] fix error --- nrrd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 17fa2c2..2defb88 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing_extensions import Literal from nrrd._version import __version__ from nrrd.formatters import * From e87663eee01494fe0a6f006252c32e6ef68f6a2b Mon Sep 17 00:00:00 2001 From: Addison Elliott Date: Tue, 5 Nov 2024 04:17:20 -0600 Subject: [PATCH 36/36] fix --- nrrd/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nrrd/__init__.py b/nrrd/__init__.py index 2defb88..d7f8dcf 100644 --- a/nrrd/__init__.py +++ b/nrrd/__init__.py @@ -21,6 +21,7 @@ Example: Reading a NRRD file with space directions type set to 'double matrix' (the default). + >>> nrrd.SPACE_DIRECTIONS_TYPE = 'double matrix' >>> data, header = nrrd.read('file.nrrd') >>> print(header['space directions'])