Skip to content
Snippets Groups Projects
Commit 33d3b4f3 authored by Julius Steiglechner's avatar Julius Steiglechner
Browse files

Provide data functionality.

parent 04ed727a
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Module provides functionality to deal with DICOM files."""
import datetime as dt
from pathlib import Path
from typing import Tuple
import pydicom
def read_subjects_age_gender(dcm_path: Path) -> Tuple[int, str]:
"""
Read age and gender for a subject from a DCM file.
Parameters
----------
dcm_path : Path
Path to a DCM file.
Returns
-------
Tuple[int, str]
(age, gender).
"""
ds = pydicom.dcmread(dcm_path)
try:
age = int(ds["PatientAge"].value)
except AssertionError:
birth_date = ds["PatientBirthDate"].value
birth_date = dt.date(year=birth_date[:4],
month=birth_date[4:6],
day=birth_date[6:8])
acq_date = ds["AcquisitionDate"].value
acq_date = dt.date(year=acq_date[:4],
month=acq_date[4:6],
day=acq_date[6:8])
age = acq_date.year - birth_date.year
if acq_date.month < birth_date.month:
age -= 1
elif (
acq_date.month == birth_date.month
) and (acq_date.day < birth_date.day):
age -= 1
gender = ds["PatientSex"].value
return age, gender
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module provides functionality for handling ITK images.
Mainly, there are conversions to and from them.
"""
from typing import List, Tuple
import itk
import numpy as np
ITK_COORD_LPI: np.ndarray = np.array([-1, -1, 1], dtype=float)
def convert_affine_to_itk_params(
affine: np.ndarray,
) -> Tuple[List, List, itk.Matrix]:
"""
Get itk conform information from affine matrix.
Parameters
----------
affine : np.ndarray
Affine matrix.
Raises
------
ValueError
If affine spatial dimensionality is not 3.
Returns
-------
Tuple[List, List, itk.Matrix]
Affine space with itk conform values: spacing, origin, direction.
"""
if affine.shape[0] != 4:
raise ValueError(
f'Spatial dimensionality is not 3, but {affine.shape[0] - 1}.'
)
# Extract affine informations
affine_direction = affine[:3, :3]
affine_spacing: np.ndarray = np.linalg.norm(affine_direction, axis=0)
affine_origin = affine[:3, 3]
# Convert affine informations
affine_origin = affine_origin * ITK_COORD_LPI
affine_direction = affine_direction / affine_spacing
affine_direction = (affine_direction.T * ITK_COORD_LPI).T
# Return itk conform values
return (
affine_spacing.tolist(),
affine_origin.tolist(),
itk.matrix_from_array(affine_direction),
)
def convert_itk_params_to_affine(itk_img: itk.Image) -> np.ndarray:
"""
Extract an affine matrix from itk image.
Parameters
----------
itk_img : itk.Image
ITK image.
Raises
------
ValueError
If image dimensionality does not match.
Returns
-------
affine : np.ndarray
Affine matrix.
"""
if itk_img.GetImageDimension() != 3:
raise ValueError(
f'Image dimensionality isnot 3, but {itk_img.GetImageDimension()}.'
)
# Extract image information
affine_origin = itk_img.GetOrigin()
affine_spacing = itk_img.GetSpacing()
affine_direction = itk.array_from_matrix(itk_img.GetDirection())
# Convert affine information
affine_origin = affine_origin * ITK_COORD_LPI
affine_direction = affine_direction * affine_spacing
affine_direction = (affine_direction.T * ITK_COORD_LPI).T
# Conclude affine information
affine = np.eye(4, dtype=float)
affine[:3, :3] = affine_direction
affine[:3, 3] = affine_origin
return affine
def convert_nifti_array_to_itk(
img: np.ndarray,
affine: np.ndarray,
voxel_type: itk.itkCType = itk.F,
) -> itk.Image:
"""
Convert a physical 3D-image to an ITK Image.
Parameters
----------
img : np.ndarray
Input image effectivly in F ordering.
affine : np.ndarray
Affine matrix defining the physical cartesian space of image grid.
voxel_type : itk.itkCType, optional
Data type of voxels. The default is itk.F.
Raises
------
ValueError
If image dimensionality is not 3.
If affine spatial dimensionality is not 3.
Returns
-------
itk_img : itk.Image
Converted ITK image.
"""
if len(img.shape) != 3:
raise ValueError(
f'Image dimensionality is not 3, but {len(img.shape)}.')
if img.flags['C_CONTIGUOUS']:
# Is set to F-Odering so that shapes of np and ITK image match.
# Note, that NIfTI1 images are effectivly in F-Ordering.
img = np.asfortranarray(img)
affine_spacing, affine_origin, affine_direction = \
convert_affine_to_itk_params(affine=affine)
# Convert to ITK, data is copied
itk_img = itk.image_from_array(img)
itk_img = itk_img.astype(voxel_type)
# Set image informations
itk_img.SetOrigin(affine_origin)
itk_img.SetSpacing(affine_spacing)
itk_img.SetDirection(affine_direction)
return itk_img
def convert_itk_img_to_nifti_array(
itk_img: itk.Image,
dtype: np.dtype = float,
) -> Tuple[np.ndarray, np.ndarray]:
"""
Convert an ITK image to a Numpy array with a defined physical space.
Parameters
----------
itk_img : itk.Image
Image in ITK format.
dtype : np.dtype, optional
Voxel dtype. The default is float.
Returns
-------
img : TYPE
Image arrage in F-ordering.
affine : TYPE
Corresponding affine matrix.
"""
affine = convert_itk_params_to_affine(itk_img)
# Convert to array, data is copied.
# This will always be a C ordered array.
# Has to be transposed to match NIfTI1 convention of F-Ordering.
img = itk.array_from_image(itk_img).T
img = img.astype(dtype)
return img, affine
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Dec 19 11:44:28 2024
@author: jsteiglechner
"""
from typing import Tuple
import itk
import numpy as np
def extract_elastix_parameters(
transform_parameters: itk.ParameterObject,
) -> Tuple[np.ndarray, np.ndarray]:
"""
Extract parameters of elastix object to affine parameters.
Parameters
----------
transform_parameters : itk.ParameterObject
Parameters of operation.
Returns
-------
center : TYPE
center of rotation point.
elx_parameters : TYPE
transform parameters.
"""
parameter_map = transform_parameters.GetParameterMap(0)
center = np.array(
[float(p) for p in parameter_map['CenterOfRotationPoint']]
)
elx_parameters = np.array(
[float(p) for p in parameter_map['TransformParameters']]
)
return center, elx_parameters
def get_transformation_matrix(
itk_transform: itk.Transform,
) -> np.ndarray:
"""
Extract relevant a linear itk transform to generate the affine matrix.
Parameters
----------
itk_transform : itk.Transform
Linear transform.
Returns
-------
transformation_matrix : TYPE
Affine transformation matrix.
"""
affine_direction = itk.array_from_matrix(itk_transform.GetMatrix())
affine_origin = np.asarray(itk_transform.GetTranslation())
# Conclude affine information
transformation_matrix = np.eye(4, dtype=float)
transformation_matrix[:3, :3] = affine_direction
transformation_matrix[:3, 3] = affine_origin
return transformation_matrix
def build_euler_transformation(
center: np.ndarray,
transform: np.ndarray,
) -> itk.Euler3DTransform:
"""
Build an Euler transformation from elastix parameters.
Parameters
----------
center : np.ndarray
Center of rotation point in shape (dim, ).
transform : np.ndarray
Transform parameters in shape (dim * (dim+1), ).
Returns
-------
euler_transform : TYPE
Applicable Euler transform.
"""
euler_transform = itk.Euler3DTransform[itk.D].New()
fixed_parameters = itk.OptimizerParameters[itk.D](len(center))
for i, p in enumerate(center):
fixed_parameters[i] = p
euler_transform.SetFixedParameters(fixed_parameters)
itk_parameters = itk.OptimizerParameters[itk.D](len(transform))
for i, p in enumerate(transform):
itk_parameters[i] = p
euler_transform.SetParameters(itk_parameters)
return euler_transform
def build_affine_transformation(
center: np.ndarray,
transform: np.ndarray,
) -> itk.AffineTransform:
"""
Build an Affine transformation from elastix parameters.
Parameters
----------
center : np.ndarray
Center of rotation point in shape (dim, ).
transform : np.ndarray
Transform parameters in shape (dim * (dim+1), ).
Returns
-------
affine_transform : TYPE
Applicable affine transform.
"""
affine_transform = itk.AffineTransform[itk.D, len(center)].New()
fixed_parameters = itk.OptimizerParameters[itk.D](len(center))
for i, p in enumerate(center):
fixed_parameters[i] = p
affine_transform.SetFixedParameters(fixed_parameters)
itk_parameters = itk.OptimizerParameters[itk.D](len(transform))
for i, p in enumerate(transform):
itk_parameters[i] = p
affine_transform.SetParameters(itk_parameters)
return affine_transform
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module provides functionality for handling NIfTI images with nibabel.
Created on Fri Aug 4 14:19:10 2023
@author: jsteiglechner
"""
from pathlib import Path
from typing import Tuple, Union
import nibabel as nib
import numpy as np
def read_nii(filepath: Union[str, Path]) -> Tuple[np.ndarray, np.ndarray]:
"""
Read brain MRI and corresponding affine from .nii file.
Note that nibabel generates a fortran ordered array.
Parameters
----------
filepath : Union[str, Path]
Path to MRI.
Returns
-------
image : TYPE
Image data.
affine_matrix : TYPE
Corresponsing affine matrix.
"""
nifti = nib.load(filepath)
image = nifti.dataobj[:]
affine_matrix = nifti.affine
return image, affine_matrix
def save_nii(
image: np.ndarray,
affine_matrix: np.ndarray,
filepath: Union[str, Path],
dtype: np.dtype = float,
) -> None:
"""
Save brain MRI with affine matrix in NIfTI file format.
Parameters
----------
image : np.ndarray
Image data.
affine_matrix : np.ndarray
Corresponding affine matrix.
filepath : Union[str, Path]
Path where MRI should be saved.
dtype : np.dtype, optional
The type of the saved array. The default is float.
Returns
-------
None
DESCRIPTION.
"""
nifti = nib.Nifti1Image(image, affine_matrix, dtype=dtype)
nib.save(nifti, filepath)
def get_datatype(image: np.ndarray) -> np.dtype:
"""
Get datatype of image data.
Parameters
----------
image : np.ndarray
NIfTI image dataobj.
Returns
-------
datatype : np.dtype
numpy datatype.
"""
if np.all(np.equal(np.mod(image, 1), 0)):
if np.min(image) >= 0:
if np.max(image) <= (2**8 - 1):
datatype = np.uint8
elif np.max(image) <= (2**16 - 1):
datatype = np.uint16
else:
datatype = np.uint32
elif (np.min(image) > -2**15) and (np.max(image) < 2**15):
datatype = np.int16
else:
datatype = np.int32
else:
datatype = np.dtype('float')
return datatype
def convert_to_original_datatype(
image: np.ndarray,
datatype: np.dtype,
) -> np.ndarray:
"""
Convert an image to a certain original datatype.
Parameters
----------
image : np.ndarray
Image dataobject.
datatype : np.dtye
original datatype.
Returns
-------
image : TYPE
Converted image.
"""
if datatype == np.dtype('float'):
image = image.astype(float)
elif datatype == np.uint8:
image = nib.casting.float_to_int(image, 'uint8')
elif datatype == np.uint16:
image = nib.casting.float_to_int(image, 'uint16')
elif datatype == np.uint32:
image = nib.casting.float_to_int(image, 'uint32')
elif datatype == np.int16:
image = nib.casting.float_to_int(image, 'int16')
else:
image = nib.casting.float_to_int(image, 'int32')
return image
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment