volumeutils
¶
Utility functions for analyze-like formats
Specialized mapper for numpy dtypes |
|
|
class to return canonical code(s) from code or aliases |
|
Apply scaling in slope and inter to array arr |
|
Get array from file with specified shape, dtype and file offset |
|
Helper function for writing arrays to file objects |
|
Smallest float type to contain range of |
|
Return more capable float type of first and second |
Get range (min, max) or range and flag (min, max, has_nan) from arr |
|
|
fname with ext changed to upper / lower case if file exists |
|
float type containing int type ifmt * slope + inter |
|
Create full dt codes Recoder instance from datatype codes |
|
Make pretty string from mapping |
|
Convert recarray to dictionary |
|
Seek in fileobj or check we're in the right place already |
|
Get affine implied by given shape and zooms |
|
Return array type from applying slope, inter to array of in_type |
|
Write count zero bytes to fileobj |
DtypeMapper
¶
- class nibabel.volumeutils.DtypeMapper¶
Bases:
dict
[Hashable
,Hashable
]Specialized mapper for numpy dtypes
We pass this mapper into the Recoder class to deal with numpy dtype hashing.
The hashing problem is that dtypes that compare equal may not have the same hash. This is true for numpys up to the current at time of writing (1.6.0). For numpy 1.2.1 at least, even dtypes that look exactly the same in terms of fields don’t always have the same hash. This makes dtypes difficult to use as keys in a dictionary.
This class wraps a dictionary in order to implement a __getitem__ to deal with dtype hashing. If the key doesn’t appear to be in the mapping, and it is a dtype, we compare (using ==) all known dtype keys to the input key, and return any matching values for the matching key.
Recoder
¶
- class nibabel.volumeutils.Recoder(codes: ~typing.Sequence[~typing.Sequence[~typing.Hashable]], fields: ~typing.Sequence[str] = ('code',), map_maker: type[~typing.Mapping[~typing.Hashable, ~typing.Hashable]] = <class 'dict'>)¶
Bases:
object
class to return canonical code(s) from code or aliases
The concept is a lot easier to read in the implementation and tests than it is to explain, so…
>>> # If you have some codes, and several aliases, like this: >>> code1 = 1; aliases1=['one', 'first'] >>> code2 = 2; aliases2=['two', 'second'] >>> # You might want to do this: >>> codes = [[code1]+aliases1,[code2]+aliases2] >>> recodes = Recoder(codes) >>> recodes.code['one'] 1 >>> recodes.code['second'] 2 >>> recodes.code[2] 2 >>> # Or maybe you have a code, a label and some aliases >>> codes=((1,'label1','one', 'first'),(2,'label2','two')) >>> # you might want to get back the code or the label >>> recodes = Recoder(codes, fields=('code','label')) >>> recodes.code['first'] 1 >>> recodes.code['label1'] 1 >>> recodes.label[2] 'label2' >>> # For convenience, you can get the first entered name by >>> # indexing the object directly >>> recodes[2] 2
Create recoder object
codes
give a sequence of code, alias sequencesfields
are names by which the entries in these sequences can be accessed.By default
fields
gives the first column the name “code”. The first column is the vector of first entries in each of the sequences found incodes
. Thence you can get the equivalent first column value with ob.code[value], where value can be a first column value, or a value in any of the other columns in that sequence.You can give other columns names too, and access them in the same way - see the examples in the class docstring.
- Parameters:
- codessequence of sequences
Each sequence defines values (codes) that are equivalent
- fields{(‘code’,) string sequence}, optional
names by which elements in sequences can be accessed
- map_maker: callable, optional
constructor for dict-like objects used to store key value pairs. Default is
dict
.map_maker()
generates an empty mapping. The mapping need only implement__getitem__, __setitem__, keys, values
.
- __init__(codes: ~typing.Sequence[~typing.Sequence[~typing.Hashable]], fields: ~typing.Sequence[str] = ('code',), map_maker: type[~typing.Mapping[~typing.Hashable, ~typing.Hashable]] = <class 'dict'>)¶
Create recoder object
codes
give a sequence of code, alias sequencesfields
are names by which the entries in these sequences can be accessed.By default
fields
gives the first column the name “code”. The first column is the vector of first entries in each of the sequences found incodes
. Thence you can get the equivalent first column value with ob.code[value], where value can be a first column value, or a value in any of the other columns in that sequence.You can give other columns names too, and access them in the same way - see the examples in the class docstring.
- Parameters:
- codessequence of sequences
Each sequence defines values (codes) that are equivalent
- fields{(‘code’,) string sequence}, optional
names by which elements in sequences can be accessed
- map_maker: callable, optional
constructor for dict-like objects used to store key value pairs. Default is
dict
.map_maker()
generates an empty mapping. The mapping need only implement__getitem__, __setitem__, keys, values
.
- add_codes(code_syn_seqs: Sequence[Sequence[Hashable]]) None ¶
Add codes to object
- Parameters:
- code_syn_seqssequence
sequence of sequences, where each sequence
S = code_syn_seqs[n]
for n in 0..len(code_syn_seqs), is a sequence giving values in the same order asself.fields
. Each S should be at least of the same length asself.fields
. After this call, ifself.fields == ['field1', 'field2'], then ``self.field1[S[n]] == S[0]
for all n in 0..len(S) andself.field2[S[n]] == S[1]
for all n in 0..len(S).
Examples
>>> code_syn_seqs = ((2, 'two'), (1, 'one')) >>> rc = Recoder(code_syn_seqs) >>> rc.value_set() == set((1,2)) True >>> rc.add_codes(((3, 'three'), (1, 'first'))) >>> rc.value_set() == set((1,2,3)) True >>> print(rc.value_set()) # set is actually ordered OrderedSet([2, 1, 3])
- keys()¶
Return all available code and alias values
Returns same value as
obj.field1.keys()
and, with the default initializingfields
argument of fields=(‘code’,), this will return the same asobj.code.keys()
>>> codes = ((1, 'one'), (2, 'two'), (1, 'repeat value')) >>> k = Recoder(codes).keys() >>> set(k) == set([1, 2, 'one', 'repeat value', 'two']) True
- value_set(name: str | None = None) OrderedSet ¶
Return OrderedSet of possible returned values for column
By default, the column is the first column.
Returns same values as
set(obj.field1.values())
and, with the default initializing``fields`` argument of fields=(‘code’,), this will return the same asset(obj.code.values())
- Parameters:
- name{None, string}
Where default of none gives result for first column
- >>> codes = ((1, ‘one’), (2, ‘two’), (1, ‘repeat value’))
- >>> vs = Recoder(codes).value_set()
- >>> vs == set([1, 2]) # Sets are not ordered, hence this test
- True
- >>> rc = Recoder(codes, fields=(‘code’, ‘label’))
- >>> rc.value_set(‘label’) == set((‘one’, ‘two’, ‘repeat value’))
- True
apply_read_scaling¶
- nibabel.volumeutils.apply_read_scaling(arr: np.ndarray, slope: Scalar | None = None, inter: Scalar | None = None) np.ndarray ¶
Apply scaling in slope and inter to array arr
This is for loading the array from a file (as opposed to the reverse scaling when saving an array to file)
Return data will be
arr * slope + inter
. The trick is that we have to find a good precision to use for applying the scaling. The heuristic is that the data is always upcast to the higher of the types from arr, `slope, inter if slope and / or inter are not default values. If the dtype of arr is an integer, then we assume the data more or less fills the integer range, and upcast to a type such that the min, max ofarr.dtype
* scale + inter, will be finite.- Parameters:
- arrarray-like
- slopeNone or float, optional
slope value to apply to arr (
arr * slope + inter
). None corresponds to a value of 1.0- interNone or float, optional
intercept value to apply to arr (
arr * slope + inter
). None corresponds to a value of 0.0
- Returns:
- retarray
array with scaling applied. Maybe upcast in order to give room for the scaling. If scaling is default (1, 0), then ret may be arr
ret is arr
.
array_from_file¶
- nibabel.volumeutils.array_from_file(shape: tuple[int, ...], in_dtype: np.dtype[DT], infile: io.IOBase, offset: int = 0, order: ty.Literal['C', 'F'] = 'F', mmap: bool | ty.Literal['c', 'r', 'r+'] = True) npt.NDArray[DT] ¶
Get array from file with specified shape, dtype and file offset
- Parameters:
- shapesequence
sequence specifying output array shape
- in_dtypenumpy dtype
fully specified numpy dtype, including correct endianness
- infilefile-like
open file-like object implementing at least read() and seek()
- offsetint, optional
offset in bytes into infile to start reading array data. Default is 0
- order{‘F’, ‘C’} string
order in which to write data. Default is ‘F’ (fortran order).
- mmap{True, False, ‘c’, ‘r’, ‘r+’}
mmap controls the use of numpy memory mapping for reading data. If False, do not try numpy
memmap
for data array. If one of {‘c’, ‘r’, ‘r+’}, try numpy memmap withmode=mmap
. A mmap value of True gives the same behavior asmmap='c'
. If infile cannot be memory-mapped, ignore mmap value and read array from file.
- Returns:
- arrarray-like
array like object that can be sliced, containing data
Examples
>>> from io import BytesIO >>> bio = BytesIO() >>> arr = np.arange(6).reshape(1,2,3) >>> _ = bio.write(arr.tobytes('F')) # outputs int >>> arr2 = array_from_file((1,2,3), arr.dtype, bio) >>> np.all(arr == arr2) True >>> bio = BytesIO() >>> _ = bio.write(b' ' * 10) >>> _ = bio.write(arr.tobytes('F')) >>> arr2 = array_from_file((1,2,3), arr.dtype, bio, 10) >>> np.all(arr == arr2) True
array_to_file¶
- nibabel.volumeutils.array_to_file(data: npt.ArrayLike, fileobj: io.IOBase, out_dtype: np.dtype | None = None, offset: int = 0, intercept: Scalar = 0.0, divslope: Scalar | None = 1.0, mn: Scalar | None = None, mx: Scalar | None = None, order: ty.Literal['C', 'F'] = 'F', nan2zero: bool = True) None ¶
Helper function for writing arrays to file objects
Writes arrays as scaled by intercept and divslope, and clipped at (prescaling) mn minimum, and mx maximum.
Clip data array at min mn, max max where there are not None ->
clipped
(this is pre scale clipping)Scale
clipped
withclipped_scaled = (clipped - intercept) / divslope
Clip
clipped_scaled
to fit into range of out_dtype (post scale clipping) ->clipped_scaled_clipped
If converting to integer out_dtype and nan2zero is True, set NaN values in
clipped_scaled_clipped
to 0Write
clipped_scaled_clipped_n2z
to fileobj fileobj starting at offset offset in memory layout order
- Parameters:
- dataarray-like
array or array-like to write.
- fileobjfile-like
file-like object implementing
write
method.- out_dtypeNone or dtype, optional
dtype to write array as. Data array will be coerced to this dtype before writing. If None (default) then use input data type.
- offsetNone or int, optional
offset into fileobj at which to start writing data. Default is 0. None means start at current file position
- interceptscalar, optional
scalar to subtract from data, before dividing by
divslope
. Default is 0.0- divslopeNone or scalar, optional
scalefactor to divide data by before writing. Default is 1.0. If None, there is no valid data, we write zeros.
- mnscalar, optional
minimum threshold in (unscaled) data, such that all data below this value are set to this value. Default is None (no threshold). The typical use is to set -np.inf in the data to have this value (which might be the minimum non-finite value in the data).
- mxscalar, optional
maximum threshold in (unscaled) data, such that all data above this value are set to this value. Default is None (no threshold). The typical use is to set np.inf in the data to have this value (which might be the maximum non-finite value in the data).
- order{‘F’, ‘C’}, optional
memory order to write array. Default is ‘F’
- nan2zero{True, False}, optional
Whether to set NaN values to 0 when writing integer output. Defaults to True. If False, NaNs will be represented as numpy does when casting; this depends on the underlying C library and is undefined. In practice nan2zero == False might be a good choice when you completely sure there will be no NaNs in the data. This value ignored for float output types. NaNs are treated as zero before applying intercept and divslope - so an array
[np.nan]
with an intercept of 10 becomes[-10]
after conversion to integer out_dtype with nan2zero set. That is because you will likely apply divslope and intercept in reverse order when reading the data back, returning the zero you probably expected from the input NaN.
Examples
>>> from io import BytesIO >>> sio = BytesIO() >>> data = np.arange(10, dtype=np.float64) >>> array_to_file(data, sio, np.float64) >>> sio.getvalue() == data.tobytes('F') True >>> _ = sio.truncate(0); _ = sio.seek(0) # outputs 0 >>> array_to_file(data, sio, np.int16) >>> sio.getvalue() == data.astype(np.int16).tobytes() True >>> _ = sio.truncate(0); _ = sio.seek(0) >>> array_to_file(data.byteswap(), sio, np.float64) >>> sio.getvalue() == data.byteswap().tobytes('F') True >>> _ = sio.truncate(0); _ = sio.seek(0) >>> array_to_file(data, sio, np.float64, order='C') >>> sio.getvalue() == data.tobytes('C') True
best_write_scale_ftype¶
- nibabel.volumeutils.best_write_scale_ftype(arr: np.ndarray, slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0, default: type[np.number] = <class 'numpy.float32'>) type[np.floating] ¶
Smallest float type to contain range of
arr
after scalingScaling that will be applied to
arr
is(arr - inter) / slope
.Note that
slope
andinter
get promoted to 1D arrays for this purpose to avoid the numpy scalar casting rules, which prevent scalars upcasting the array.- Parameters:
- arrarray-like
array that will be scaled
- slopearray-like, optional
scalar such that output array will be
(arr - inter) / slope
.- interarray-like, optional
scalar such that output array will be
(arr - inter) / slope
- defaultnumpy type, optional
minimum float type to return
- Returns:
- ftypenumpy type
Best floating point type for scaling. If no floating point type prevents overflow, return the top floating point type. If the input array
arr
already contains inf values, return the greater of the input type and the default type.
Examples
>>> arr = np.array([0, 1, 2], dtype=np.int16) >>> best_write_scale_ftype(arr, 1, 0) is np.float32 True
Specify higher default return value
>>> best_write_scale_ftype(arr, 1, 0, default=np.float64) is np.float64 True
Even large values that don’t overflow don’t change output
>>> arr = np.array([0, np.finfo(np.float32).max], dtype=np.float32) >>> best_write_scale_ftype(arr, 1, 0) is np.float32 True
Scaling > 1 reduces output values, so no upcast needed
>>> best_write_scale_ftype(arr, np.float32(2), 0) is np.float32 True
Scaling < 1 increases values, so upcast may be needed (and is here)
>>> best_write_scale_ftype(arr, np.float32(0.5), 0) is np.float64 True
better_float_of¶
- nibabel.volumeutils.better_float_of(first: npt.DTypeLike, second: npt.DTypeLike, default: type[np.floating] = <class 'numpy.float32'>) type[np.floating] ¶
Return more capable float type of first and second
Return default if neither of first or second is a float
- Parameters:
- firstnumpy type specifier
Any valid input to np.dtype()`
- secondnumpy type specifier
Any valid input to np.dtype()`
- defaultnumpy type specifier, optional
Any valid input to np.dtype()`
- Returns:
- better_typenumpy type
More capable of first or second if both are floats; if only one is a float return that, otherwise return default.
Examples
>>> better_float_of(np.float32, np.float64) is np.float64 True >>> better_float_of(np.float32, 'i4') is np.float32 True >>> better_float_of('i2', 'u4') is np.float32 True >>> better_float_of('i2', 'u4', np.float64) is np.float64 True
finite_range¶
- nibabel.volumeutils.finite_range(arr: npt.ArrayLike, check_nan: Literal[False] = False) tuple[Scalar, Scalar] ¶
- nibabel.volumeutils.finite_range(arr: npt.ArrayLike, check_nan: Literal[True]) tuple[Scalar, Scalar, bool]
Get range (min, max) or range and flag (min, max, has_nan) from arr
- Parameters:
- arrarray-like
- check_nan{False, True}, optional
Whether to return third output, a bool signaling whether there are NaN values in arr
- Returns:
- mnscalar
minimum of values in (flattened) array
- mxscalar
maximum of values in (flattened) array
- has_nanbool
Returned if check_nan is True. has_nan is True if there are one or more NaN values in arr
Examples
>>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]]) >>> finite_range(a) (-1.0, 1.0) >>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]]) >>> finite_range(a, check_nan=True) (-1.0, 1.0, True) >>> a = np.array([[np.nan],[np.nan]]) >>> finite_range(a) == (np.inf, -np.inf) True >>> a = np.array([[-3, 0, 1],[2,-1,4]], dtype=int) >>> finite_range(a) (-3, 4) >>> a = np.array([[1, 0, 1],[2,3,4]], dtype=np.uint) >>> finite_range(a) (0, 4) >>> a = a + 1j >>> finite_range(a) (1j, (4+1j)) >>> a = np.zeros((2,), dtype=[('f1', 'i2')]) >>> finite_range(a) Traceback (most recent call last): ... TypeError: Can only handle numeric types
fname_ext_ul_case¶
- nibabel.volumeutils.fname_ext_ul_case(fname: str) str ¶
fname with ext changed to upper / lower case if file exists
Check for existence of fname. If it does exist, return unmodified. If it doesn’t, check for existence of fname with case changed from lower to upper, or upper to lower. Return this modified fname if it exists. Otherwise return fname unmodified
- Parameters:
- fnamestr
filename.
- Returns:
- mod_fnamestr
filename, maybe with extension of opposite case
int_scinter_ftype¶
- nibabel.volumeutils.int_scinter_ftype(ifmt: type[np.integer], slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0, default: type[np.floating] = <class 'numpy.float32'>) type[np.floating] ¶
float type containing int type ifmt * slope + inter
Return float type that can represent the max and the min of the ifmt type after multiplication with slope and addition of inter with something like
np.array([imin, imax], dtype=ifmt) * slope + inter
.Note that
slope
andinter
get promoted to 1D arrays for this purpose to avoid the numpy scalar casting rules, which prevent scalars upcasting the array.- Parameters:
- ifmtobject
numpy integer type (e.g. np.int32)
- slopefloat, optional
slope, default 1.0
- interfloat, optional
intercept, default 0.0
- default_outobject, optional
numpy floating point type, default is
np.float32
- Returns:
- ftypeobject
numpy floating point type
Notes
It is difficult to make floats overflow with just addition because the deltas are so large at the extremes of floating point. For example:
>>> arr = np.array([np.finfo(np.float32).max], dtype=np.float32) >>> res = arr + np.iinfo(np.int16).max >>> arr == res array([ True])
Examples
>>> int_scinter_ftype(np.int8, 1.0, 0.0) == np.float32 True >>> int_scinter_ftype(np.int8, 1e38, 0.0) == np.float64 True
make_dt_codes¶
- nibabel.volumeutils.make_dt_codes(codes_seqs: Sequence[Sequence]) Recoder ¶
Create full dt codes Recoder instance from datatype codes
Include created numpy dtype (from numpy type) and opposite endian numpy dtype
- Parameters:
- codes_seqssequence of sequences
contained sequences make be length 3 or 4, but must all be the same length. Elements are data type code, data type name, and numpy type (such as
np.float32
). The fourth element is the nifti string representation of the code (e.g. “NIFTI_TYPE_FLOAT32”)
- Returns:
- rec
Recoder
instance Recoder that, by default, returns
code
when indexed with any of the corresponding code, name, type, dtype, or swapped dtype. You can also index withniistring
values if codes_seqs had sequences of length 4 instead of 3.
- rec
pretty_mapping¶
- nibabel.volumeutils.pretty_mapping(mapping: ty.Mapping[K, V], getterfunc: ty.Callable[[ty.Mapping[K, V], K], V] | None = None) str ¶
Make pretty string from mapping
Adjusts text column to print values on basis of longest key. Probably only sensible if keys are mainly strings.
You can pass in a callable that does clever things to get the values out of the mapping, given the names. By default, we just use
__getitem__
- Parameters:
- mappingmapping
implementing iterator returning keys and .items()
- getterfuncNone or callable
callable taking two arguments,
obj
andkey
whereobj
is the passed mapping. If None, just uselambda obj, key: obj[key]
- Returns:
- strstring
Examples
>>> d = {'a key': 'a value'} >>> print(pretty_mapping(d)) a key : a value >>> class C: # to control ordering, show get_ method ... def __iter__(self): ... return iter(('short_field','longer_field')) ... def __getitem__(self, key): ... if key == 'short_field': ... return 0 ... if key == 'longer_field': ... return 'str' ... def get_longer_field(self): ... return 'method string' >>> def getter(obj, key): ... # Look for any 'get_<name>' methods ... try: ... return obj.__getattribute__('get_' + key)() ... except AttributeError: ... return obj[key] >>> print(pretty_mapping(C(), getter)) short_field : 0 longer_field : method string
rec2dict¶
- nibabel.volumeutils.rec2dict(rec: ndarray) dict[str, generic | ndarray] ¶
Convert recarray to dictionary
Also converts scalar values to scalars
- Parameters:
- recndarray
structured ndarray
- Returns:
- dctdict
dict with key, value pairs as for rec
Examples
>>> r = np.zeros((), dtype = [('x', 'i4'), ('s', 'S10')]) >>> d = rec2dict(r) >>> d == {'x': 0, 's': b''} True
seek_tell¶
- nibabel.volumeutils.seek_tell(fileobj: io.IOBase, offset: int, write0: bool = False) None ¶
Seek in fileobj or check we’re in the right place already
- Parameters:
- fileobjfile-like
object implementing
seek
and (if seek raises an OSError)tell
- offsetint
position in file to which to seek
- write0{False, True}, optional
If True, and standard seek fails, try to write zeros to the file to reach offset. This can be useful when writing bz2 files, that cannot do write seeks.
shape_zoom_affine¶
- nibabel.volumeutils.shape_zoom_affine(shape: Sequence[int] | ndarray, zooms: Sequence[float] | ndarray, x_flip: bool = True) ndarray ¶
Get affine implied by given shape and zooms
We get the translations from the center of the image (implied by shape).
- Parameters:
- shape(N,) array-like
shape of image data.
N
is the number of dimensions- zooms(N,) array-like
zooms (voxel sizes) of the image
- x_flip{True, False}
whether to flip the X row of the affine. Corresponds to radiological storage on disk.
- Returns:
- aff(4,4) array
affine giving correspondence of voxel coordinates to mm coordinates, taking the center of the image as origin
Examples
>>> shape = (3, 5, 7) >>> zooms = (3, 2, 1) >>> shape_zoom_affine((3, 5, 7), (3, 2, 1)) array([[-3., 0., 0., 3.], [ 0., 2., 0., -4.], [ 0., 0., 1., -3.], [ 0., 0., 0., 1.]]) >>> shape_zoom_affine((3, 5, 7), (3, 2, 1), False) array([[ 3., 0., 0., -3.], [ 0., 2., 0., -4.], [ 0., 0., 1., -3.], [ 0., 0., 0., 1.]])
working_type¶
- nibabel.volumeutils.working_type(in_type: npt.DTypeLike, slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0) type[np.number] ¶
Return array type from applying slope, inter to array of in_type
Numpy type that results from an array of type in_type being combined with slope and inter. It returns something like the dtype type of
((np.zeros((2,), dtype=in_type) - inter) / slope)
, but ignoring the actual values of slope and inter.Note that you would not necessarily get the same type by applying slope and inter the other way round. Also, you’ll see that the order in which slope and inter are applied is the opposite of the order in which they are passed.
- Parameters:
- in_typenumpy type specifier
Numpy type of input array. Any valid input for
np.dtype()
- slopescalar, optional
slope to apply to array. If 1.0 (default), ignore this value and its type.
- interscalar, optional
intercept to apply to array. If 0.0 (default), ignore this value and its type.
- Returns:
- wtype: numpy type
Numpy type resulting from applying inter and slope to array of type in_type.