Source code for MEDiml.utils.imref

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Tuple, Union

import numpy as np


[docs] def intrinsicToWorld(R, xIntrinsic: float, yIntrinsic: float, zIntrinsic:float) -> Tuple[float, float, float]: """Convert from intrinsic to world coordinates. Args: R (imref3d): imref3d object (same functionality of MATLAB imref3d class) xIntrinsic (float): Coordinates along the x-dimension in the intrinsic coordinate system yIntrinsic (float): Coordinates along the y-dimension in the intrinsic coordinate system zIntrinsic (float): Coordinates along the z-dimension in the intrinsic coordinate system Returns: float: world coordinates """ return R.intrinsicToWorld(xIntrinsic=xIntrinsic, yIntrinsic=yIntrinsic, zIntrinsic=zIntrinsic)
[docs] def worldToIntrinsic(R, xWorld: float, yWorld: float, zWorld: float) -> Tuple[float, float, float] : """Convert from world coordinates to intrinsic. Args: R (imref3d): imref3d object (same functionality of MATLAB imref3d class) xWorld (float): Coordinates along the x-dimension in the intrinsic coordinate system yWorld (float): Coordinates along the y-dimension in the intrinsic coordinate system zWorld (float): Coordinates along the z-dimension in the intrinsic coordinate system Returns: _type_: intrinsic coordinates """ return R.worldToIntrinsic(xWorld=xWorld, yWorld=yWorld, zWorld=zWorld)
[docs] def sizes_match(R, A): """Compares whether the two imref3d objects have the same size. Args: R (imref3d): First imref3d object. A (imref3d): Second imref3d object. Returns: bool: True if ``R`` and ``A`` have the same size, and false if not. """ return np.all(R.imageSize == A.imageSize)
[docs] class imref3d: """This class mirrors the functionality of the matlab imref3d class An `imref3d object <https://www.mathworks.com/help/images/ref/imref3d.html>`_ stores the relationship between the intrinsic coordinates anchored to the columns, rows, and planes of a 3-D image and the spatial location of the same column, row, and plane locations in a world coordinate system. The image is sampled regularly in the planar world-x, world-y, and world-z coordinates of the coordinate system such that intrinsic-x, -y and -z values align with world-x, -y and -z values, respectively. The resolution in each dimension can be different. Args: ImageSize (ndarray, optional): Number of elements in each spatial dimension, specified as a 3-element positive row vector. PixelExtentInWorldX (float, optional): Size of a single pixel in the x-dimension measured in the world coordinate system. PixelExtentInWorldY (float, optional): Size of a single pixel in the y-dimension measured in the world coordinate system. PixelExtentInWorldZ (float, optional): Size of a single pixel in the z-dimension measured in the world coordinate system. xWorldLimits (ndarray, optional): Limits of image in world x, specified as a 2-element row vector, [xMin xMax]. yWorldLimits (ndarray, optional): Limits of image in world y, specified as a 2-element row vector, [yMin yMax]. zWorldLimits (ndarray, optional): Limits of image in world z, specified as a 2-element row vector, [zMin zMax]. Attributes: ImageSize (ndarray): Number of elements in each spatial dimension, specified as a 3-element positive row vector. PixelExtentInWorldX (float): Size of a single pixel in the x-dimension measured in the world coordinate system. PixelExtentInWorldY (float): Size of a single pixel in the y-dimension measured in the world coordinate system. PixelExtentInWorldZ (float): Size of a single pixel in the z-dimension measured in the world coordinate system. XIntrinsicLimits (ndarray): Limits of image in intrinsic units in the x-dimension, specified as a 2-element row vector [xMin xMax]. YIntrinsicLimits (ndarray): Limits of image in intrinsic units in the y-dimension, specified as a 2-element row vector [yMin yMax]. ZIntrinsicLimits (ndarray): Limits of image in intrinsic units in the z-dimension, specified as a 2-element row vector [zMin zMax]. ImageExtentInWorldX (float): Span of image in the x-dimension in the world coordinate system. ImageExtentInWorldY (float): Span of image in the y-dimension in the world coordinate system. ImageExtentInWorldZ (float): Span of image in the z-dimension in the world coordinate system. xWorldLimits (ndarray): Limits of image in world x, specified as a 2-element row vector, [xMin xMax]. yWorldLimits (ndarray): Limits of image in world y, specified as a 2-element row vector, [yMin yMax]. zWorldLimits (ndarray): Limits of image in world z, specified as a 2-element row vector, [zMin zMax]. """ def __init__(self, imageSize=None, pixelExtentInWorldX=1.0, pixelExtentInWorldY=1.0, pixelExtentInWorldZ=1.0, xWorldLimits=None, yWorldLimits=None, zWorldLimits=None) -> None: # Check if imageSize is an ndarray, and cast to ndarray otherwise self.ImageSize = self._parse_to_ndarray(x=imageSize, n=3) # Size of single voxels along axis in world coordinate system. # Equivalent to voxel spacing. self.PixelExtentInWorldX = pixelExtentInWorldX self.PixelExtentInWorldY = pixelExtentInWorldY self.PixelExtentInWorldZ = pixelExtentInWorldZ # Limits of the image in intrinsic coordinates # AZ: this differs from DICOM, which assumes that the origin lies # at the center of the first voxel. if imageSize is not None: self.XIntrinsicLimits = np.array([-0.5, imageSize[0]-0.5]) self.YIntrinsicLimits = np.array([-0.5, imageSize[1]-0.5]) self.ZIntrinsicLimits = np.array([-0.5, imageSize[2]-0.5]) else: self.XIntrinsicLimits = None self.YIntrinsicLimits = None self.ZIntrinsicLimits = None # Size of the image in world coordinates if imageSize is not None: self.ImageExtentInWorldX = imageSize[0] * pixelExtentInWorldX self.ImageExtentInWorldY = imageSize[1] * pixelExtentInWorldY self.ImageExtentInWorldZ = imageSize[2] * pixelExtentInWorldZ else: self.ImageExtentInWorldX = None self.ImageExtentInWorldY = None self.ImageExtentInWorldZ = None # Limits of the image in the world coordinates self.XWorldLimits = self._parse_to_ndarray(x=xWorldLimits, n=2) self.YWorldLimits = self._parse_to_ndarray(x=yWorldLimits, n=2) self.ZWorldLimits = self._parse_to_ndarray(x=zWorldLimits, n=2) if xWorldLimits is None and imageSize is not None: self.XWorldLimits = np.array([0.0, self.ImageExtentInWorldX]) if yWorldLimits is None and imageSize is not None: self.YWorldLimits = np.array([0.0, self.ImageExtentInWorldY]) if zWorldLimits is None and imageSize is not None: self.ZWorldLimits = np.array([0.0, self.ImageExtentInWorldZ])
[docs] def _parse_to_ndarray(self, x: np.iterable, n=None) -> np.ndarray: """Internal function to cast input to a numpy array. Args: x (iterable): Object that supports __iter__. n (int, optional): expected length. Returns: ndarray: iterable input as a numpy array. """ if x is not None: # Cast to ndarray if not isinstance(x, np.ndarray): x = np.array(x) # Check length if n is not None: if not len(x) == n: raise ValueError( "Length of array does not meet the expected length.", len(x), n) return x
[docs] def intrinsicToWorld(self, xIntrinsic: np.ndarray, yIntrinsic: np.ndarray, zIntrinsic: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """Convert from intrinsic to world coordinates. Args: xIntrinsic (ndarray): Coordinates along the x-dimension in the intrinsic coordinate system. yIntrinsic (ndarray): Coordinates along the y-dimension in the intrinsic coordinate system. zIntrinsic (ndarray): Coordinates along the z-dimension in the intrinsic coordinate system. Returns: Tuple[np.ndarray, np.ndarray, np.ndarray]: [xWorld, yWorld, zWorld] in world coordinate system. """ xWorld = (self.XWorldLimits[0] + 0.5*self.PixelExtentInWorldX) + \ xIntrinsic * self.PixelExtentInWorldX yWorld = (self.YWorldLimits[0] + 0.5*self.PixelExtentInWorldY) + \ yIntrinsic * self.PixelExtentInWorldY zWorld = (self.ZWorldLimits[0] + 0.5*self.PixelExtentInWorldZ) + \ zIntrinsic * self.PixelExtentInWorldZ return xWorld, yWorld, zWorld
[docs] def worldToIntrinsic(self, xWorld: np.ndarray, yWorld: np.ndarray, zWorld: np.ndarray)-> Union[np.ndarray, np.ndarray, np.ndarray]: """Converts from world coordinates to intrinsic coordinates. Args: xWorld (ndarray): Coordinates along the x-dimension in the world coordinate system. yWorld (ndarray): Coordinates along the y-dimension in the world coordinate system. zWorld (ndarray): Coordinates along the z-dimension in the world coordinate system. Returns: ndarray: [xIntrinsic,yIntrinsic,zIntrinsic] in intrinsic coordinate system. """ xIntrinsic = ( xWorld - (self.XWorldLimits[0] + 0.5*self.PixelExtentInWorldX)) / self.PixelExtentInWorldX yIntrinsic = ( yWorld - (self.YWorldLimits[0] + 0.5*self.PixelExtentInWorldY)) / self.PixelExtentInWorldY zIntrinsic = ( zWorld - (self.ZWorldLimits[0] + 0.5*self.PixelExtentInWorldZ)) / self.PixelExtentInWorldZ return xIntrinsic, yIntrinsic, zIntrinsic
[docs] def contains_point(self, xWorld: np.ndarray, yWorld: np.ndarray, zWorld: np.ndarray) -> np.ndarray: """Determines which points defined by ``xWorld``, ``yWorld`` and ``zWorld``. Args: xWorld (ndarray): Coordinates along the x-dimension in the world coordinate system. yWorld (ndarray): Coordinates along the y-dimension in the world coordinate system. zWorld (ndarray): Coordinates along the z-dimension in the world coordinate system. Returns: ndarray: boolean array for coordinate sets that are within the bounds of the image. """ xInside = np.logical_and( xWorld >= self.XWorldLimits[0], xWorld <= self.XWorldLimits[1]) yInside = np.logical_and( yWorld >= self.YWorldLimits[0], yWorld <= self.YWorldLimits[1]) zInside = np.logical_and( zWorld >= self.ZWorldLimits[0], zWorld <= self.ZWorldLimits[1]) return xInside + yInside + zInside == 3
[docs] def WorldLimits(self, axis=None, newValue=None) -> Union[np.ndarray, None]: """Sets the WorldLimits to the new value for the given ``axis``. If the newValue is None, the method returns the attribute value. Args: axis (str, optional): Specify the dimension, must be 'X', 'Y' or 'Z'. newValue (iterable, optional): New value for the WorldLimits attribute. Returns: ndarray: Limits of image in world along the axis-dimension. """ if newValue is None: # Get value if axis == "X": return self.XWorldLimits elif axis == "Y": return self.YWorldLimits elif axis == "Z": return self.ZWorldLimits else: # Set value if axis == "X": self.XWorldLimits = self._parse_to_ndarray(x=newValue, n=2) elif axis == "Y": self.YWorldLimits = self._parse_to_ndarray(x=newValue, n=2) elif axis == "Z": self.ZWorldLimits = self._parse_to_ndarray(x=newValue, n=2)
[docs] def PixelExtentInWorld(self, axis=None) -> Union[float, None]: """Returns the PixelExtentInWorld attribute value for the given ``axis``. Args: axis (str, optional): Specify the dimension, must be 'X', 'Y' or 'Z'. Returns: float: Size of a single pixel in the axis-dimension measured in the world coordinate system. """ if axis == "X": return self.PixelExtentInWorldX elif axis == "Y": return self.PixelExtentInWorldY elif axis == "Z": return self.PixelExtentInWorldZ
[docs] def IntrinsicLimits(self, axis=None) -> Union[np.ndarray, None]: """Returns the IntrinsicLimits attribute value for the given ``axis``. Args: axis (str, optional): Specify the dimension, must be 'X', 'Y' or 'Z'. Returns: ndarray: Limits of image in intrinsic units in the axis-dimension, specified as a 2-element row vector [xMin xMax]. """ if axis == "X": return self.XIntrinsicLimits elif axis == "Y": return self.YIntrinsicLimits elif axis == "Z": return self.ZIntrinsicLimits
[docs] def ImageExtentInWorld(self, axis=None) -> Union[float, None]: """Returns the ImageExtentInWorld attribute value for the given ``axis``. Args: axis (str, optional): Specify the dimension, must be 'X', 'Y' or 'Z'. Returns: ndarray: Span of image in the axis-dimension in the world coordinate system. """ if axis == "X": return self.ImageExtentInWorldX elif axis == "Y": return self.ImageExtentInWorldY elif axis == "Z": return self.ImageExtentInWorldZ