import itertools
import os
from typing import Union
import numpy as np
import SimpleITK as sitk
[docs]def refrence_free_3D_resample(image, transformation=None,
interpolator=sitk.sitkBSpline,
default_value=0,
image_voxel_type=None,
spacing=None,
direction=None):
"""Do resampling without reference.
Args:
image: A SimpleITK image.
transformation: A transformation to be applied to the image. If None,
The identity transformation is used.
interpolator: The interpolator used for image interpolation after
applying transformation. The default is ``sitk.sitkBSpline``.
default_value: The default values used for voxel values. The default is
``0``.
image_voxel_type: The data type used for casting the resampled image.
If None, the voxel type of the ``image`` is used.
spacing: The spacing of the image after resampling.
direction: The direction of the image after resampling.
Returns:
sitk.Image: The resampled image.
"""
if transformation is None:
# The default is the identity transformation
transformation = sitk.Transform(image.GetDimension(), sitk.sitkIdentity)
if image_voxel_type is None:
image_voxel_type = image.GetPixelIDValue()
if spacing is None:
spacing = image.GetSpacing()
if direction is None:
direction = image.GetDirection()
extreme_indecies = list(itertools.product(*zip([0, 0, 0], image.GetSize())))
extreme_points = [image.TransformIndexToPhysicalPoint(idx)
for idx in extreme_indecies]
inv_transform = transformation.GetInverse()
# Calculate the coordinates of the inversed extream points
extreme_points_transformed = [inv_transform.TransformPoint(point)
for point in extreme_points]
min_values = np.array(extreme_points_transformed).min(axis=0)
max_values = np.array(extreme_points_transformed).max(axis=0)
# Minimal x,y, and coordinates are the new origin.
origin = min_values.tolist()
# Compute grid size based on the physical size and spacing.
size = [int((max_val - min_val) / s)
for min_val, max_val, s in zip(min_values, max_values, spacing)]
return sitk.Resample(image, size, transformation, interpolator,
outputOrigin=origin,
outputSpacing=spacing,
outputDirection=direction,
defaultPixelValue=default_value,
outputPixelType=image_voxel_type)
[docs]def referenced_3D_resample(image,
transformation=None,
interpolator=sitk.sitkBSpline,
default_value=0,
image_voxel_type=None,
reference=None):
"""Do resampling using a given reference.
Args:
image: A simpleITK image.
transformation: A transformation to be applied to the image. The
default is None, representing the identity transformation.
interpolator: The interpolator used for image interpolation after
applying transformation. If None, the default value is used. The
default value is ``sitk.sitkBSpline``.
default_value: The default value used for voxel values. The default
value is ``0``.
image_voxel_type: The data type used for casting the resampled image.
If None, the voxel type of the ``image`` is used as the voxel
type of the result.
reference: The image used as the reference for resampling. If None,
the image itself is used as the reference.
Returns:
sitk.Image: The resampled image.
"""
if transformation is None:
# The default is the identity transformation
transformation = sitk.Transform(image.GetDimension(), sitk.sitkIdentity)
if image_voxel_type is None:
image_voxel_type = image.GetPixelIDValue()
if reference is None:
return sitk.Resample(image, transformation, interpolator,
default_value, image_voxel_type)
return sitk.Resample(image, reference, transformation, interpolator,
default_value, image_voxel_type)
[docs]def image_equal(image1: sitk.Image, image2: sitk.Image, type_check=True,
tolerance=1e-6):
"""Check if two images are equal.
Data type, size, and content are used for comparison. Two image with the
L2 distance less than a tolerance value are considered equal.
Args:
image1: A SimpleITK image.
image2: A SimpleITK image.
type_check: True if data type is used for comparison.
tolerance: The threshold used for the acceptable deviation between the
euclidean distance of voxel values between the two images.
Returns:
bool: True if two images are equal; otherwise, False.
"""
if type_check is True:
# Check for equality of voxel types
if image1.GetPixelIDValue() != image2.GetPixelIDValue():
return False
if image1.GetDimension() != image2.GetDimension():
return False
if image1.GetSize() != image2.GetSize():
return False
# Check the equality of image spacings
if np.linalg.norm(np.array(image1.GetSpacing()) -
np.array(image2.GetSpacing())) > tolerance:
return False
# Check the equality of image origins
if np.linalg.norm(np.array(image1.GetOrigin()) -
np.array(image2.GetOrigin())) > tolerance:
return False
# Check the array equality
arry1 = sitk.GetArrayFromImage(image1)
arry2 = sitk.GetArrayFromImage(image2)
if np.linalg.norm(arry1 - arry2) > tolerance:
return False
return True
[docs]def read_image(image_path: Union[str, None]):
""" Read an image.
Args:
image_path: The path to the image file or the folder containing a DICOM image.
Returns:
sitk.Image: A SimpleITK Image.
Raises:
ValueError: if there exist more than one image series or if there is no
DICOM file in the provided ``path``.
"""
if not os.path.exists(image_path):
raise ValueError(f'Path does not exist: {image_path}')
if os.path.isdir(image_path):
reader = sitk.ImageSeriesReader()
series_IDs = reader.GetGDCMSeriesIDs(image_path)
if len(series_IDs) > 1:
msg = ('Only One image is allowed in a directory. There are '
f'{len(series_IDs)} Series IDs (images) in {image_path}.')
raise ValueError(msg)
if len(series_IDs) == 0:
msg = f'There are not dicom files in {image_path}.'
raise ValueError(msg)
series_id = series_IDs[0]
dicom_names = reader.GetGDCMSeriesFileNames(image_path, series_id)
reader.SetFileNames(dicom_names)
image = reader.Execute()
elif os.path.isfile(image_path):
image = sitk.ReadImage(image_path)
else:
raise ValueError(f'Invalid path: {image_path}')
return image
[docs]def get_stats(image):
"""Computes minimum, maximum, sum, mean, variance, and standard deviation of
an image.
Args:
image: A sitk.Image object.
Returns:
returns statistical values of type dictionary include keys 'min',
'max', 'mean', 'std', and 'var'.
"""
stat_fileter = sitk.StatisticsImageFilter()
stat_fileter.Execute(image)
image_info = {}
image_info.update({'min': stat_fileter.GetMinimum()})
image_info.update({'max': stat_fileter.GetMaximum()})
image_info.update({'mean': stat_fileter.GetMean()})
image_info.update({'std': stat_fileter.GetSigma()})
image_info.update({'var': stat_fileter.GetVariance()})
return image_info
[docs]def check_dimensions(image, mask):
"""Check if the size of the image and mask are equal.
Args:
image: A sitk.Image.
mask: A sitk.Image.
Raises:
ValueError: If image and mask are not None and their dimension are
not the same.
"""
if (mask is not None) and (image is not None):
if image.GetSize() != mask.GetSize():
img_size_message = ', '.join([str(x) for x in image.GetSize()])
msk_size_message = ', '.join([str(x) for x in mask.GetSize()])
msg = ('image and mask size should be equal,'
' but ({}) != ({}).'.format(img_size_message,
msk_size_message))
raise ValueError(msg)
[docs]class Label(object):
"""Label a binary image.
Each distinct connected component (segment) is assigned a unique label. The
segment labels start from 1 and are consecutive. The order of
label assignment is based on the the raster position of the
segments in the binary image.
Args:
fully_connected:
input_foreground_value:
output_background_value:
dtype: The data type for the label map. Options include:
* sitk.sitkUInt8
* sitk.sitkUInt16
* sitk.sitkUInt32
* sitk.sitkUInt64
"""
def __init__(self, fully_connected: bool = False,
input_foreground_value: int = 1,
output_background_value: int = 0,
dtype=sitk.sitkUInt8):
self.input_foreground_value = input_foreground_value
self.output_background_value = output_background_value
self.fully_connected = fully_connected
self.dtype = dtype
def __call__(self, binary_image):
mask = sitk.BinaryImageToLabelMap(
binary_image,
fullyConnected=self.fully_connected,
inputForegroundValue=self.input_foreground_value,
outputBackgroundValue=self.output_background_value)
mask = sitk.Cast(mask, pixelID=self.dtype)
return mask
def __repr__(self):
msg = ('{} (fully_connected={}, input_foreground_value={}, '
'output_background_value={}, dtype={}')
return msg.format(self.__class__.__name__,
self.fully_connected,
self.input_foreground_value,
self.output_background_value,
self.dtype)