Source code for nrrd.parsers

from typing import Any, Optional, Type, Union

import nptyping as npt
import numpy as np
from typing_extensions import Literal

from nrrd.errors import NRRDError


[docs]def parse_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*'], Any]: """Parse NRRD vector from string into (N,) :class:`numpy.ndarray`. See :ref:`background/datatypes:int vector` and :ref:`background/datatypes:double vector` for more information on the format. Parameters ---------- x : :class:`str` String containing NRRD vector 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 vector elements for fractional numbers. If found, then the vector will be converted to :class:`float`, otherwise :class:`int`. Default is to automatically determine datatype. Returns ------- vector : (N,) :class:`numpy.ndarray` Vector that is parsed from the :obj:`x` string """ if x[0] != '(' or x[-1] != ')': raise NRRDError('Vector should be enclosed by parentheses.') # Always convert to float and then truncate to integer if desired # The reason why is parsing a floating point string to int will fail (i.e. int('25.1') will fail) vector = np.array([float(x) for x in x[1:-1].split(',')]) # 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_trunc = vector.astype(int) if np.all((vector - vector_trunc) == 0): vector = vector_trunc elif dtype == int: vector = vector.astype(int) elif dtype != float: raise NRRDError('dtype should be None for automatic type detection, float or int') return vector
[docs]def parse_optional_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> \ Optional[npt.NDArray[Literal['*'], Any]]: """Parse optional NRRD vector from string into (N,) :class:`numpy.ndarray` or :obj:`None`. Function parses optional NRRD vector from string into an (N,) :class:`numpy.ndarray`. This function works the same as :meth:`parse_vector` except if :obj:`x` is 'none', :obj:`vector` will be :obj:`None` See :ref:`background/datatypes:int vector` and :ref:`background/datatypes:double vector` for more information on the format. Parameters ---------- x : :class:`str` String containing NRRD vector or 'none' 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 vector elements for fractional numbers. If found, then the vector will be converted to :class:`float`, otherwise :class:`int`. Default is to automatically determine datatype. Returns ------- vector : (N,) :class:`numpy.ndarray` or :obj:`None` Vector that is parsed from the :obj:`x` string or :obj:`None` if :obj:`x` is 'none' """ if x == 'none': return None else: return parse_vector(x, dtype)
[docs]def parse_matrix(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*, *'], Any]: """Parse NRRD matrix from string into (M,N) :class:`numpy.ndarray`. See :ref:`background/datatypes:int matrix` and :ref:`background/datatypes:double matrix` 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 matrix = [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 matrix])) != 1: raise NRRDError('Matrix should have same number of elements in each row') matrix = np.vstack(matrix) # 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 = matrix.astype(int) if np.all((matrix - matrix_trunc) == 0): matrix = matrix_trunc elif dtype == int: matrix = matrix.astype(int) elif dtype != float: raise NRRDError('dtype should be None for automatic type detection, float or int') return matrix
[docs]def parse_optional_matrix(x: str) -> Optional[npt.NDArray[Literal['*, *'], Any]]: """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 matrix = [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 matrix]) # 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) matrix = np.vstack([nan_row if x is None else x for x in matrix]) return matrix
[docs]def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*'], Any]: """Parse NRRD number list from string into (N,) :class:`numpy.ndarray`. See :ref:`background/datatypes:int list` and :ref:`background/datatypes:double list` for more information on the format. Parameters ---------- x : :class:`str` String containing NRRD number 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 for fractional numbers. If found, then the string will be converted to :class:`float`, otherwise :class:`int`. Default is to automatically determine datatype. Returns ------- vector : (N,) :class:`numpy.ndarray` Vector that is parsed from the :obj:`x` string """ # Always convert to float and then perform truncation to integer if necessary number_list = np.array([float(x) for x in x.split()]) if dtype is None: number_list_trunc = number_list.astype(int) # If there is no difference between the truncated number list and the number list, then that means that the # number list was all integers and we can just return that if np.all((number_list - number_list_trunc) == 0): number_list = number_list_trunc elif dtype == int: number_list = number_list.astype(int) elif dtype != float: raise NRRDError('dtype should be None for automatic type detection, float or int') return number_list
[docs]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