Pythonic Syntactic Sugar No description has been provided for this image

The Image Basics Notebook was straight forward and closely follows ITK's C++ interface.

Sugar is great it gives your energy to get things done faster! SimpleITK has applied a generous about of syntactic sugar to help get things done faster too.

In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rc("image", aspect="equal")
import SimpleITK as sitk

# Download data to work on
%run update_path_to_download_script
from downloaddata import fetch_data as fdata

Let us begin by developing a convenient method for displaying images in our notebooks.

In [2]:
img = sitk.GaussianSource(size=[64] * 2)
plt.imshow(sitk.GetArrayViewFromImage(img))
Out[2]:
<matplotlib.image.AxesImage at 0x1139279d0>
No description has been provided for this image
In [3]:
img = sitk.GaborSource(size=[64] * 2, frequency=0.03)
plt.imshow(sitk.GetArrayViewFromImage(img))
Out[3]:
<matplotlib.image.AxesImage at 0x1139dd150>
No description has been provided for this image
In [4]:
def myshow(img):
    nda = sitk.GetArrayViewFromImage(img)
    plt.imshow(nda)


myshow(img)
No description has been provided for this image

Multi-dimension slice indexing¶

If you are familiar with numpy, sliced index then this should be cake for the SimpleITK image. The Python standard slice interface for 1-D object:

Operation Result
d[i] i-th item of d, starting index 0
d[i:j] slice of d from i to j
d[i:j:k] slice of d from i to j with step k

With this convenient syntax many basic tasks can be easily done.

In [5]:
img[24, 24]
Out[5]:
0.048901304602622986

Cropping¶

In [6]:
myshow(img[16:48, :])
No description has been provided for this image
In [7]:
myshow(img[:, 16:-16])
No description has been provided for this image
In [8]:
myshow(img[:32, :32])
No description has been provided for this image

Flipping¶

In [9]:
img_corner = img[:32, :32]
myshow(img_corner)
No description has been provided for this image
In [10]:
myshow(img_corner[::-1, :])
No description has been provided for this image
In [11]:
myshow(
    sitk.Tile(
        img_corner,
        img_corner[::-1, ::],
        img_corner[::, ::-1],
        img_corner[::-1, ::-1],
        [2, 2],
    )
)
No description has been provided for this image

Slice Extraction¶

A 2D image can be extracted from a 3D one.

In [12]:
img = sitk.GaborSource(size=[64] * 3, frequency=0.05)

# Why does this produce an error?
myshow(img)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[12], line 4
      1 img = sitk.GaborSource(size=[64] * 3, frequency=0.05)
      3 # Why does this produce an error?
----> 4 myshow(img)

Cell In[4], line 3, in myshow(img)
      1 def myshow(img):
      2     nda = sitk.GetArrayViewFromImage(img)
----> 3     plt.imshow(nda)

File ~/toolkits/anaconda3/envs/sitkMaster3.11/lib/python3.11/site-packages/matplotlib/pyplot.py:3562, in imshow(X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, interpolation_stage, filternorm, filterrad, resample, url, data, **kwargs)
   3541 @_copy_docstring_and_deprecators(Axes.imshow)
   3542 def imshow(
   3543     X: ArrayLike | PIL.Image.Image,
   (...)
   3560     **kwargs,
   3561 ) -> AxesImage:
-> 3562     __ret = gca().imshow(
   3563         X,
   3564         cmap=cmap,
   3565         norm=norm,
   3566         aspect=aspect,
   3567         interpolation=interpolation,
   3568         alpha=alpha,
   3569         vmin=vmin,
   3570         vmax=vmax,
   3571         origin=origin,
   3572         extent=extent,
   3573         interpolation_stage=interpolation_stage,
   3574         filternorm=filternorm,
   3575         filterrad=filterrad,
   3576         resample=resample,
   3577         url=url,
   3578         **({"data": data} if data is not None else {}),
   3579         **kwargs,
   3580     )
   3581     sci(__ret)
   3582     return __ret

File ~/toolkits/anaconda3/envs/sitkMaster3.11/lib/python3.11/site-packages/matplotlib/__init__.py:1473, in _preprocess_data.<locals>.inner(ax, data, *args, **kwargs)
   1470 @functools.wraps(func)
   1471 def inner(ax, *args, data=None, **kwargs):
   1472     if data is None:
-> 1473         return func(
   1474             ax,
   1475             *map(sanitize_sequence, args),
   1476             **{k: sanitize_sequence(v) for k, v in kwargs.items()})
   1478     bound = new_sig.bind(ax, *args, **kwargs)
   1479     auto_label = (bound.arguments.get(label_namer)
   1480                   or bound.kwargs.get(label_namer))

File ~/toolkits/anaconda3/envs/sitkMaster3.11/lib/python3.11/site-packages/matplotlib/axes/_axes.py:5895, in Axes.imshow(self, X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, interpolation_stage, filternorm, filterrad, resample, url, **kwargs)
   5892 if aspect is not None:
   5893     self.set_aspect(aspect)
-> 5895 im.set_data(X)
   5896 im.set_alpha(alpha)
   5897 if im.get_clip_path() is None:
   5898     # image does not already have clipping set, clip to Axes patch

File ~/toolkits/anaconda3/envs/sitkMaster3.11/lib/python3.11/site-packages/matplotlib/image.py:729, in _ImageBase.set_data(self, A)
    727 if isinstance(A, PIL.Image.Image):
    728     A = pil_to_array(A)  # Needed e.g. to apply png palette.
--> 729 self._A = self._normalize_image_array(A)
    730 self._imcache = None
    731 self.stale = True

File ~/toolkits/anaconda3/envs/sitkMaster3.11/lib/python3.11/site-packages/matplotlib/image.py:697, in _ImageBase._normalize_image_array(A)
    695     A = A.squeeze(-1)  # If just (M, N, 1), assume scalar and apply colormap.
    696 if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]):
--> 697     raise TypeError(f"Invalid shape {A.shape} for image data")
    698 if A.ndim == 3:
    699     # If the input data has values outside the valid range (after
    700     # normalisation), we issue a warning and then clip X to the bounds
    701     # - otherwise casting wraps extreme values, hiding outliers and
    702     # making reliable interpretation impossible.
    703     high = 255 if np.issubdtype(A.dtype, np.integer) else 1

TypeError: Invalid shape (64, 64, 64) for image data
No description has been provided for this image
In [13]:
myshow(img[:, :, 32])
No description has been provided for this image
In [14]:
myshow(img[16, :, :])
No description has been provided for this image

Subsampling¶

In [15]:
myshow(img[:, ::3, 32])
No description has been provided for this image

Mathematical Operators¶

Most python mathematical operators are overloaded to call the SimpleITK filter which does that same operation on a per-pixel basis. They can operate on a two images or an image and a scalar.

If two images are used then both must have the same pixel type. The output image type is usually the same.

As these operators basically call ITK filter, which just use raw C++ operators, care must be taken to prevent overflow, and divide by zero etc.

Operators
+
-
*
/
//
**
In [16]:
img = sitk.ReadImage(fdata("cthead1.png"))
img = sitk.Cast(img, sitk.sitkFloat32)
myshow(img)
img[150, 150]
Fetching cthead1.png
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
Out[16]:
140.0
No description has been provided for this image
In [17]:
timg = img**2
myshow(timg)
timg[150, 150]
Out[17]:
19600.0
No description has been provided for this image

Division Operators¶

All three Python division operators are implemented __floordiv__, __truediv__, and __div__.

The true division's output is a double pixel type.

See PEP 238 to see why Python changed the division operator in Python 3.

Bitwise Logic Operators¶

Operators
&
|
^
~
In [18]:
img = sitk.ReadImage(fdata("cthead1.png"))
myshow(img)
Fetching cthead1.png
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
No description has been provided for this image

Comparative Operators¶

Operators
>
>=
<
<=
==

These comparative operators follow the same convention as the reset of SimpleITK for binary images. They have the pixel type of sitkUInt8 with values of 0 and 1.

In [19]:
img = sitk.ReadImage(fdata("cthead1.png"))
myshow(img)
Fetching cthead1.png
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
libpng warning: sCAL: invalid unit
No description has been provided for this image

Amazingly make common trivial tasks really trivial¶

In [20]:
myshow(img > 90)
No description has been provided for this image
In [21]:
myshow(img > 150)
No description has been provided for this image
In [22]:
myshow((img > 90) + (img > 150))
No description has been provided for this image

Masking¶

Set the values in an image according to a given mask.

In [23]:
# Create a grid and use as mask
grid_image = sitk.GridSource(
    outputPixelType=sitk.sitkUInt16,
    size=img.GetSize(),
    sigma=(0.1, 0.1),
    gridSpacing=(20.0, 20.0),
)
# zero out the values in the original image that correspond to
# the grid lines in the grid_image
img[grid_image == 0] = 0
myshow(img)
No description has been provided for this image