Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation (original) (raw)
volumeutils
¶
Utility functions for analyze-like formats
DtypeMapper() | Specialized mapper for numpy dtypes |
---|---|
Recoder(codes, fields, map_maker, ...) | class to return canonical code(s) from code or aliases |
apply_read_scaling(arr[, slope, inter]) | Apply scaling in slope and inter to array arr |
array_from_file(shape, in_dtype, infile[, ...]) | Get array from file with specified shape, dtype and file offset |
array_to_file(data, fileobj[, out_dtype, ...]) | Helper function for writing arrays to file objects |
best_write_scale_ftype(arr[, slope, inter, ...]) | Smallest float type to contain range of arr after scaling |
better_float_of(first, second[, default]) | Return more capable float type of first and second |
finite_range() | Get range (min, max) or range and flag (min, max, has_nan) from arr |
fname_ext_ul_case(fname) | fname with ext changed to upper / lower case if file exists |
int_scinter_ftype(ifmt[, slope, inter, default]) | float type containing int type ifmt * slope + inter |
make_dt_codes(codes_seqs) | Create full dt codes Recoder instance from datatype codes |
pretty_mapping(mapping[, getterfunc]) | Make pretty string from mapping |
rec2dict(rec) | Convert recarray to dictionary |
seek_tell(fileobj, offset[, write0]) | Seek in fileobj or check we're in the right place already |
shape_zoom_affine(shape, zooms[, x_flip]) | Get affine implied by given shape and zooms |
working_type(in_type[, slope, inter]) | Return array type from applying slope, inter to array of in_type |
write_zeros(fileobj, count[, block_size]) | 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 in codes
. 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 in codes
. 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 as self.fields
. Each S should be at least of the same length as self.fields
. After this call, if self.fields == ['field1', 'field2'], then ``self.field1[S[n]] == S[0]
for all n in 0..len(S) and self.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 initializing fields
argument of fields=(‘code’,), this will return the same as obj.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 with mode=mmap
. A mmap value of True gives the same behavior as mmap='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 0 - Write
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 interceptand divslope - so an array [np.nan]
with an intercept of 10 becomes [-10]
after conversion to integer out_dtype withnan2zero set. That is because you will likely apply divslope andintercept 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 scaling
Scaling that will be applied to arr
is (arr - inter) / slope
.
Note that slope
and inter
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
and inter
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:
recRecoder
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 with niistring
values if codes_seqs had sequences of length 4 instead of 3.
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
and key
where obj
is the passed mapping. If None, just use lambda 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_' 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 byshape).
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 withslope 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 typein_type.
write_zeros¶
nibabel.volumeutils.write_zeros(fileobj: io.IOBase, count: int, block_size: int = 8194) → None¶
Write count zero bytes to fileobj
Parameters:
fileobjfile-like object
with write
method
countint
number of bytes to write
block_sizeint, optional
largest continuous block to write.