--- a +++ b/docs/image/main.html @@ -0,0 +1,917 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> +<meta name="generator" content="pdoc 0.10.0" /> +<title>pymskt.image.main API documentation</title> +<meta name="description" content="" /> +<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin> +<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin> +<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin> +<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style> +<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style> +<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style> +<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script> +<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script> +</head> +<body> +<main> +<article id="content"> +<header> +<h1 class="title">Module <code>pymskt.image.main</code></h1> +</header> +<section id="section-intro"> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">from typing import Optional +import os +import vtk +import SimpleITK as sitk +import numpy as np +from vtk.util.numpy_support import numpy_to_vtk + + +def set_vtk_image_origin(vtk_image, new_origin=(0, 0, 0)): + """ + Reset the origin of a `vtk_image` + + Parameters + ---------- + vtk_image : vtk.image + VTK image that we want to change the origin of. + new_origin : tuple, optional + New origin to asign to `vtk_image`, by default (0, 0, 0) + + Returns + ------- + vtk.Filter + End of VTK filter pipeline after applying origin change. + """ + + change_origin = vtk.vtkImageChangeInformation() + change_origin.SetInputConnection(vtk_image.GetOutputPort()) + change_origin.SetOutputOrigin(new_origin) + change_origin.Update() + return change_origin + +def read_nrrd(path, set_origin_zero=False): + """ + Read NRRD image file into vtk. Enables usage of marching cubes + and other functions that work on image data. + + Parameters + ---------- + path : str + Path to `.nrrd` medical image to read in. + set_origin_zero : bool, optional + Bool to determine if origin should be set to zeros, by default False + + Returns + ------- + vtk.Filter + End of VTK filter pipeline. + """ + + image_reader = vtk.vtkNrrdReader() + image_reader.SetFileName(path) + image_reader.Update() + if set_origin_zero is True: + change_origin = set_vtk_image_origin(image_reader, new_origin=(0, 0, 0)) + return change_origin + elif set_origin_zero is False: + return image_reader + + +def set_seg_border_to_zeros(seg_image, + border_size=1): + """ + Utility function to ensure that all segmentations are "closed" after marching cubes. + If the segmentation extends to the edges of the image then the surface wont be closed + at the places it touches the edges. + + Parameters + ---------- + seg_image : SimpleITK.Image + Image of a segmentation. + border_size : int, optional + The size of the border to set around the edges of the 3D image, by default 1 + + Returns + ------- + SimpleITK.Image + The image with border set to 0 (background). + """ + + seg_array = sitk.GetArrayFromImage(seg_image) + new_seg_array = np.zeros_like(seg_array) + new_seg_array[border_size:-border_size, border_size:-border_size, border_size:-border_size] = seg_array[border_size:-border_size, border_size:-border_size, border_size:-border_size] + new_seg_image = sitk.GetImageFromArray(new_seg_array) + new_seg_image.CopyInformation(seg_image) + return new_seg_image + + +def smooth_image(image, label_idx, variance=1.0): + """ + Smooth a single label in a SimpleITK image. Used as pre-processing for + bones/cartilage before applying marching cubes. Helps obtain smooth surfaces. + + Parameters + ---------- + image : SimpleITK.Image + Image to be smoothed. + label_idx : int + Integer of the tissue of interest to be smoothed in the image. + variance : float, optional + The size of the smoothing, by default 1.0 + + Returns + ------- + SimpleITK.Image + Image of only the label (tissue) of interest after being smoothed. + """ + new_image = binarize_segmentation_image(image, label_idx) + + new_image = sitk.Cast(new_image, sitk.sitkFloat32) + + gauss_filter = sitk.DiscreteGaussianImageFilter() + gauss_filter.SetVariance(variance) + # gauss_filter.SetUseImageSpacingOn + gauss_filter.SetUseImageSpacing(True) + filtered_new_image = gauss_filter.Execute(new_image) + + return filtered_new_image + +def binarize_segmentation_image(seg_image, label_idx): + """ + Return segmentation that is only 0s/1s, with 1s where label_idx is + located in the image. + + Parameters + ---------- + seg_image : SimpleITK.Image + Segmentation image that contains data we want to binarize + label_idx : int + Integer/label that we want to extract (binarize) from the + `seg_image`. + + Returns + ------- + SimpleITK.Image + New segmentation image that is binarized. + """ + array = sitk.GetArrayFromImage(seg_image) + array_ = np.zeros_like(array) + array_[array == label_idx] = 1 + new_seg_image = sitk.GetImageFromArray(array_) + new_seg_image.CopyInformation(seg_image) + return new_seg_image + +def crop_bone_based_on_width(seg_image, + bone_idx, + np_med_lat_axis=0, + np_inf_sup_axis=1, + bone_crop_distal=True, + value_to_reassign=0, + percent_width_to_crop_height=1.0): + """ + Crop the bone labelmap of a SimpleITK.Image so that it is proportional to the + bones medial/lateral width. + + Parameters + ---------- + seg_image : SimpleITK.Image + Image to be cropped. + bone_idx : int + Label_index of the bone to be cropped. + np_med_lat_axis : int, optional + Medial/lateral axis, by default 0 + np_inf_sup_axis : int, optional + Inferior/superir axis, by default 1 + bone_crop_distal : bool, optional + Boolean of cropping should occur distal or proximally, by default True + value_to_reassign : int, optional + Value to replace bone label with, by default 0 + percent_width_to_crop_height : float, optional + Bone length as a proportion of width, by default 1.0 + + Returns + ------- + SimpleITK.Image + Image after bone is cropped as a proportion of the bone's width. + """ + seg_array = sitk.GetArrayFromImage(seg_image) + loc_bone = np.where(seg_array == bone_idx) + med_lat_width_bone_mm = (np.max(loc_bone[np_med_lat_axis]) - np.min(loc_bone[np_med_lat_axis])) * \ + seg_image.GetSpacing()[::-1][np_med_lat_axis] + inf_sup_crop_in_pixels = (med_lat_width_bone_mm / seg_image.GetSpacing()[::-1][ + np_inf_sup_axis]) * percent_width_to_crop_height + if bone_crop_distal is True: + bone_distal_idx = np.max(loc_bone[np_inf_sup_axis]) + bone_proximal_idx = bone_distal_idx - inf_sup_crop_in_pixels + if bone_proximal_idx < 1: + bone_proximal_idx = 1 + + elif bone_crop_distal is False: + bone_proximal_idx = np.min(loc_bone[np_inf_sup_axis]) + bone_distal_idx = bone_proximal_idx + inf_sup_crop_in_pixels + if bone_distal_idx > seg_array.shape[np_inf_sup_axis]: + bone_distal_idx = seg_array.shape[np_inf_sup_axis] - 1 + + max_inf_sup_idx = max(bone_distal_idx, bone_proximal_idx) + min_inf_sup_idx = min(bone_distal_idx, bone_proximal_idx) + + idx_bone_to_keep = np.where( + (loc_bone[np_inf_sup_axis] > min_inf_sup_idx) & (loc_bone[np_inf_sup_axis] < max_inf_sup_idx)) + loc_bone_to_remove = tuple([np.delete(x, idx_bone_to_keep) for x in loc_bone]) + + seg_array[loc_bone_to_remove] = value_to_reassign + + new_seg_image = sitk.GetImageFromArray(seg_array) + new_seg_image.CopyInformation(seg_image) + + return new_seg_image + +def apply_transform_retain_array(image, transform, interpolator=sitk.sitkNearestNeighbor): + """ + This function will move the actual image in space but keep the underlying array the same. + So, in x/y/z land the pixels are in a new location, but the actual underlying data array + is the same. + + Parameters + ---------- + image : SimpleITK.Image + Image to be transformed. + transform : SimpleITK.Transform + Transform to apply + interpolator : SimpleITK.Interpolator, optional + Interpolator type to use, by default sitk.sitkNearestNeighbor + + Returns + ------- + SimpleITK.Image + New image after applying the appropriate transform. + + Notes + ----- + I have a feeling that this is overkill. + """ + + inverse_transform = transform.GetInverse() + new_origin = inverse_transform.TransformPoint(image.GetOrigin()) + + new_x = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((image.GetSize()[0], 0, 0))) + new_y = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((0, image.GetSize()[1], 0))) + new_z = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((0, 0, image.GetSize()[2]))) + + # Create x-axis vector + new_x_vector = np.asarray(new_x) - np.asarray(new_origin) + new_x_vector /= np.sqrt(np.sum(np.square(new_x_vector))) + # Create y-axis vector + new_y_vector = np.asarray(new_y) - np.asarray(new_origin) + new_y_vector /= np.sqrt(np.sum(np.square(new_y_vector))) + # Create z-axis vector + new_z_vector = np.asarray(new_z) - np.asarray(new_origin) + new_z_vector /= np.sqrt(np.sum(np.square(new_z_vector))) + # New image size (shape) + new_size = image.GetSize() + # New image spacing + new_spacing = image.GetSpacing() + # Create 3x3 transformation matrix from the x/y/z unit vectors. + new_three_by_three = np.zeros((3,3)) + new_three_by_three[:,0] = new_x_vector + new_three_by_three[:,1] = new_y_vector + new_three_by_three[:,2] = new_z_vector + + new_image = sitk.Resample(image, + new_size, + transform, + interpolator, + new_origin, + new_spacing, + new_three_by_three.flatten().tolist()) + return new_image + +def create_vtk_image( + origin: Optional[int] = [0, 0, 0], + dimensions: Optional[list] = [20, 20, 20], + spacing: Optional[float] = [1., 1., 1.], + scalar: Optional[float] = 20., + data: Optional[np.ndarray] = None +): + """ + Function to create a 3D vtkimage from a numpy array + OR to create a uniform image (all same value) + + Parameters + ---------- + origin : Optional[int], optional + X/Y/Z origin of the image, by default [0, 0, 0] + dimensions : Optional[list], optional + Size of the image along each dimension, by default [20, 20, 20] + spacing : Optional[float], optional + Image spacing along each dimension, by default [1., 1., 1.] + scalar : Optional[float], optional + Scalar value to use for a uniform image, by default 20. + data : Optional[np.ndarray], optional + Data for a non-uniform image, by default None + """ + + if data is None: + data = np.ones(dimensions) * scalar + else: + if len(data.shape) == 3: + dimensions = data.shape + else: + dimensions = [1, 1, 1] + for idx, dim_size in enumerate(data.shape): + dimensions[idx] = dim_size + vtk_array = numpy_to_vtk(data.flatten(order='F')) + vtk_array.SetName('test') + + # points = vtk.vtkDoubleArray() + # points.SetName('test') + # points.SetNumberOfComponents(1) + # points.SetNumberOfTuples(np.product(dimensions)) + # for x in range(dimensions[0]): + # for y in range(dimensions[1]): + # for z in range(dimensions[2]): + # points.SetValue( + # (z * dimensions[0] * dimensions[1]) + (x * dimensions[1]) + y, + # array[x, y, z] + # ) + + vtk_image = vtk.vtkImageData() + vtk_image.SetOrigin(*origin) + vtk_image.SetDimensions(*dimensions) + vtk_image.SetSpacing(*spacing) + vtk_image.GetPointData().SetScalars(vtk_array) + +# + return vtk_image</code></pre> +</details> +</section> +<section> +</section> +<section> +</section> +<section> +<h2 class="section-title" id="header-functions">Functions</h2> +<dl> +<dt id="pymskt.image.main.apply_transform_retain_array"><code class="name flex"> +<span>def <span class="ident">apply_transform_retain_array</span></span>(<span>image, transform, interpolator=1)</span> +</code></dt> +<dd> +<div class="desc"><p>This function will move the actual image in space but keep the underlying array the same. +So, in x/y/z land the pixels are in a new location, but the actual underlying data array +is the same. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>image</code></strong> : <code>SimpleITK.Image</code></dt> +<dd>Image to be transformed.</dd> +<dt><strong><code>transform</code></strong> : <code>SimpleITK.Transform</code></dt> +<dd>Transform to apply</dd> +<dt><strong><code>interpolator</code></strong> : <code>SimpleITK.Interpolator</code>, optional</dt> +<dd>Interpolator type to use, by default sitk.sitkNearestNeighbor</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>SimpleITK.Image</code></dt> +<dd>New image after applying the appropriate transform.</dd> +</dl> +<h2 id="notes">Notes</h2> +<p>I have a feeling that this is overkill.</p></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def apply_transform_retain_array(image, transform, interpolator=sitk.sitkNearestNeighbor): + """ + This function will move the actual image in space but keep the underlying array the same. + So, in x/y/z land the pixels are in a new location, but the actual underlying data array + is the same. + + Parameters + ---------- + image : SimpleITK.Image + Image to be transformed. + transform : SimpleITK.Transform + Transform to apply + interpolator : SimpleITK.Interpolator, optional + Interpolator type to use, by default sitk.sitkNearestNeighbor + + Returns + ------- + SimpleITK.Image + New image after applying the appropriate transform. + + Notes + ----- + I have a feeling that this is overkill. + """ + + inverse_transform = transform.GetInverse() + new_origin = inverse_transform.TransformPoint(image.GetOrigin()) + + new_x = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((image.GetSize()[0], 0, 0))) + new_y = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((0, image.GetSize()[1], 0))) + new_z = inverse_transform.TransformPoint(image.TransformIndexToPhysicalPoint((0, 0, image.GetSize()[2]))) + + # Create x-axis vector + new_x_vector = np.asarray(new_x) - np.asarray(new_origin) + new_x_vector /= np.sqrt(np.sum(np.square(new_x_vector))) + # Create y-axis vector + new_y_vector = np.asarray(new_y) - np.asarray(new_origin) + new_y_vector /= np.sqrt(np.sum(np.square(new_y_vector))) + # Create z-axis vector + new_z_vector = np.asarray(new_z) - np.asarray(new_origin) + new_z_vector /= np.sqrt(np.sum(np.square(new_z_vector))) + # New image size (shape) + new_size = image.GetSize() + # New image spacing + new_spacing = image.GetSpacing() + # Create 3x3 transformation matrix from the x/y/z unit vectors. + new_three_by_three = np.zeros((3,3)) + new_three_by_three[:,0] = new_x_vector + new_three_by_three[:,1] = new_y_vector + new_three_by_three[:,2] = new_z_vector + + new_image = sitk.Resample(image, + new_size, + transform, + interpolator, + new_origin, + new_spacing, + new_three_by_three.flatten().tolist()) + return new_image</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.binarize_segmentation_image"><code class="name flex"> +<span>def <span class="ident">binarize_segmentation_image</span></span>(<span>seg_image, label_idx)</span> +</code></dt> +<dd> +<div class="desc"><p>Return segmentation that is only 0s/1s, with 1s where label_idx is +located in the image. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code></dt> +<dd>Segmentation image that contains data we want to binarize</dd> +<dt><strong><code>label_idx</code></strong> : <code>int</code></dt> +<dd>Integer/label that we want to extract (binarize) from the +<code>seg_image</code>.</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>SimpleITK.Image</code></dt> +<dd>New segmentation image that is binarized.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def binarize_segmentation_image(seg_image, label_idx): + """ + Return segmentation that is only 0s/1s, with 1s where label_idx is + located in the image. + + Parameters + ---------- + seg_image : SimpleITK.Image + Segmentation image that contains data we want to binarize + label_idx : int + Integer/label that we want to extract (binarize) from the + `seg_image`. + + Returns + ------- + SimpleITK.Image + New segmentation image that is binarized. + """ + array = sitk.GetArrayFromImage(seg_image) + array_ = np.zeros_like(array) + array_[array == label_idx] = 1 + new_seg_image = sitk.GetImageFromArray(array_) + new_seg_image.CopyInformation(seg_image) + return new_seg_image</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.create_vtk_image"><code class="name flex"> +<span>def <span class="ident">create_vtk_image</span></span>(<span>origin: Optional[int] = [0, 0, 0], dimensions: Optional[list] = [20, 20, 20], spacing: Optional[float] = [1.0, 1.0, 1.0], scalar: Optional[float] = 20.0, data: Optional[numpy.ndarray] = None)</span> +</code></dt> +<dd> +<div class="desc"><p>Function to create a 3D vtkimage from a numpy array +OR to create a uniform image (all same value)</p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>origin</code></strong> : <code>Optional[int]</code>, optional</dt> +<dd>X/Y/Z origin of the image, by default [0, 0, 0]</dd> +<dt><strong><code>dimensions</code></strong> : <code>Optional[list]</code>, optional</dt> +<dd>Size of the image along each dimension, by default [20, 20, 20]</dd> +<dt><strong><code>spacing</code></strong> : <code>Optional[float]</code>, optional</dt> +<dd>Image spacing along each dimension, by default [1., 1., 1.]</dd> +<dt><strong><code>scalar</code></strong> : <code>Optional[float]</code>, optional</dt> +<dd>Scalar value to use for a uniform image, by default 20.</dd> +<dt><strong><code>data</code></strong> : <code>Optional[np.ndarray]</code>, optional</dt> +<dd>Data for a non-uniform image, by default None</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def create_vtk_image( + origin: Optional[int] = [0, 0, 0], + dimensions: Optional[list] = [20, 20, 20], + spacing: Optional[float] = [1., 1., 1.], + scalar: Optional[float] = 20., + data: Optional[np.ndarray] = None +): + """ + Function to create a 3D vtkimage from a numpy array + OR to create a uniform image (all same value) + + Parameters + ---------- + origin : Optional[int], optional + X/Y/Z origin of the image, by default [0, 0, 0] + dimensions : Optional[list], optional + Size of the image along each dimension, by default [20, 20, 20] + spacing : Optional[float], optional + Image spacing along each dimension, by default [1., 1., 1.] + scalar : Optional[float], optional + Scalar value to use for a uniform image, by default 20. + data : Optional[np.ndarray], optional + Data for a non-uniform image, by default None + """ + + if data is None: + data = np.ones(dimensions) * scalar + else: + if len(data.shape) == 3: + dimensions = data.shape + else: + dimensions = [1, 1, 1] + for idx, dim_size in enumerate(data.shape): + dimensions[idx] = dim_size + vtk_array = numpy_to_vtk(data.flatten(order='F')) + vtk_array.SetName('test') + + # points = vtk.vtkDoubleArray() + # points.SetName('test') + # points.SetNumberOfComponents(1) + # points.SetNumberOfTuples(np.product(dimensions)) + # for x in range(dimensions[0]): + # for y in range(dimensions[1]): + # for z in range(dimensions[2]): + # points.SetValue( + # (z * dimensions[0] * dimensions[1]) + (x * dimensions[1]) + y, + # array[x, y, z] + # ) + + vtk_image = vtk.vtkImageData() + vtk_image.SetOrigin(*origin) + vtk_image.SetDimensions(*dimensions) + vtk_image.SetSpacing(*spacing) + vtk_image.GetPointData().SetScalars(vtk_array) + +# + return vtk_image</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.crop_bone_based_on_width"><code class="name flex"> +<span>def <span class="ident">crop_bone_based_on_width</span></span>(<span>seg_image, bone_idx, np_med_lat_axis=0, np_inf_sup_axis=1, bone_crop_distal=True, value_to_reassign=0, percent_width_to_crop_height=1.0)</span> +</code></dt> +<dd> +<div class="desc"><p>Crop the bone labelmap of a SimpleITK.Image so that it is proportional to the +bones medial/lateral width. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code></dt> +<dd>Image to be cropped.</dd> +<dt><strong><code>bone_idx</code></strong> : <code>int</code></dt> +<dd>Label_index of the bone to be cropped.</dd> +<dt><strong><code>np_med_lat_axis</code></strong> : <code>int</code>, optional</dt> +<dd>Medial/lateral axis, by default 0</dd> +<dt><strong><code>np_inf_sup_axis</code></strong> : <code>int</code>, optional</dt> +<dd>Inferior/superir axis, by default 1</dd> +<dt><strong><code>bone_crop_distal</code></strong> : <code>bool</code>, optional</dt> +<dd>Boolean of cropping should occur distal or proximally, by default True</dd> +<dt><strong><code>value_to_reassign</code></strong> : <code>int</code>, optional</dt> +<dd>Value to replace bone label with, by default 0</dd> +<dt><strong><code>percent_width_to_crop_height</code></strong> : <code>float</code>, optional</dt> +<dd>Bone length as a proportion of width, by default 1.0</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>SimpleITK.Image</code></dt> +<dd>Image after bone is cropped as a proportion of the bone's width.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def crop_bone_based_on_width(seg_image, + bone_idx, + np_med_lat_axis=0, + np_inf_sup_axis=1, + bone_crop_distal=True, + value_to_reassign=0, + percent_width_to_crop_height=1.0): + """ + Crop the bone labelmap of a SimpleITK.Image so that it is proportional to the + bones medial/lateral width. + + Parameters + ---------- + seg_image : SimpleITK.Image + Image to be cropped. + bone_idx : int + Label_index of the bone to be cropped. + np_med_lat_axis : int, optional + Medial/lateral axis, by default 0 + np_inf_sup_axis : int, optional + Inferior/superir axis, by default 1 + bone_crop_distal : bool, optional + Boolean of cropping should occur distal or proximally, by default True + value_to_reassign : int, optional + Value to replace bone label with, by default 0 + percent_width_to_crop_height : float, optional + Bone length as a proportion of width, by default 1.0 + + Returns + ------- + SimpleITK.Image + Image after bone is cropped as a proportion of the bone's width. + """ + seg_array = sitk.GetArrayFromImage(seg_image) + loc_bone = np.where(seg_array == bone_idx) + med_lat_width_bone_mm = (np.max(loc_bone[np_med_lat_axis]) - np.min(loc_bone[np_med_lat_axis])) * \ + seg_image.GetSpacing()[::-1][np_med_lat_axis] + inf_sup_crop_in_pixels = (med_lat_width_bone_mm / seg_image.GetSpacing()[::-1][ + np_inf_sup_axis]) * percent_width_to_crop_height + if bone_crop_distal is True: + bone_distal_idx = np.max(loc_bone[np_inf_sup_axis]) + bone_proximal_idx = bone_distal_idx - inf_sup_crop_in_pixels + if bone_proximal_idx < 1: + bone_proximal_idx = 1 + + elif bone_crop_distal is False: + bone_proximal_idx = np.min(loc_bone[np_inf_sup_axis]) + bone_distal_idx = bone_proximal_idx + inf_sup_crop_in_pixels + if bone_distal_idx > seg_array.shape[np_inf_sup_axis]: + bone_distal_idx = seg_array.shape[np_inf_sup_axis] - 1 + + max_inf_sup_idx = max(bone_distal_idx, bone_proximal_idx) + min_inf_sup_idx = min(bone_distal_idx, bone_proximal_idx) + + idx_bone_to_keep = np.where( + (loc_bone[np_inf_sup_axis] > min_inf_sup_idx) & (loc_bone[np_inf_sup_axis] < max_inf_sup_idx)) + loc_bone_to_remove = tuple([np.delete(x, idx_bone_to_keep) for x in loc_bone]) + + seg_array[loc_bone_to_remove] = value_to_reassign + + new_seg_image = sitk.GetImageFromArray(seg_array) + new_seg_image.CopyInformation(seg_image) + + return new_seg_image</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.read_nrrd"><code class="name flex"> +<span>def <span class="ident">read_nrrd</span></span>(<span>path, set_origin_zero=False)</span> +</code></dt> +<dd> +<div class="desc"><p>Read NRRD image file into vtk. Enables usage of marching cubes +and other functions that work on image data.</p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>path</code></strong> : <code>str</code></dt> +<dd>Path to <code>.nrrd</code> medical image to read in.</dd> +<dt><strong><code>set_origin_zero</code></strong> : <code>bool</code>, optional</dt> +<dd>Bool to determine if origin should be set to zeros, by default False</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>vtk.Filter</code></dt> +<dd>End of VTK filter pipeline.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def read_nrrd(path, set_origin_zero=False): + """ + Read NRRD image file into vtk. Enables usage of marching cubes + and other functions that work on image data. + + Parameters + ---------- + path : str + Path to `.nrrd` medical image to read in. + set_origin_zero : bool, optional + Bool to determine if origin should be set to zeros, by default False + + Returns + ------- + vtk.Filter + End of VTK filter pipeline. + """ + + image_reader = vtk.vtkNrrdReader() + image_reader.SetFileName(path) + image_reader.Update() + if set_origin_zero is True: + change_origin = set_vtk_image_origin(image_reader, new_origin=(0, 0, 0)) + return change_origin + elif set_origin_zero is False: + return image_reader</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.set_seg_border_to_zeros"><code class="name flex"> +<span>def <span class="ident">set_seg_border_to_zeros</span></span>(<span>seg_image, border_size=1)</span> +</code></dt> +<dd> +<div class="desc"><p>Utility function to ensure that all segmentations are "closed" after marching cubes. +If the segmentation extends to the edges of the image then the surface wont be closed +at the places it touches the edges. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code></dt> +<dd>Image of a segmentation.</dd> +<dt><strong><code>border_size</code></strong> : <code>int</code>, optional</dt> +<dd>The size of the border to set around the edges of the 3D image, by default 1</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>SimpleITK.Image</code></dt> +<dd>The image with border set to 0 (background).</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def set_seg_border_to_zeros(seg_image, + border_size=1): + """ + Utility function to ensure that all segmentations are "closed" after marching cubes. + If the segmentation extends to the edges of the image then the surface wont be closed + at the places it touches the edges. + + Parameters + ---------- + seg_image : SimpleITK.Image + Image of a segmentation. + border_size : int, optional + The size of the border to set around the edges of the 3D image, by default 1 + + Returns + ------- + SimpleITK.Image + The image with border set to 0 (background). + """ + + seg_array = sitk.GetArrayFromImage(seg_image) + new_seg_array = np.zeros_like(seg_array) + new_seg_array[border_size:-border_size, border_size:-border_size, border_size:-border_size] = seg_array[border_size:-border_size, border_size:-border_size, border_size:-border_size] + new_seg_image = sitk.GetImageFromArray(new_seg_array) + new_seg_image.CopyInformation(seg_image) + return new_seg_image</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.set_vtk_image_origin"><code class="name flex"> +<span>def <span class="ident">set_vtk_image_origin</span></span>(<span>vtk_image, new_origin=(0, 0, 0))</span> +</code></dt> +<dd> +<div class="desc"><p>Reset the origin of a <code>vtk_image</code></p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>vtk_image</code></strong> : <code>vtk.image</code></dt> +<dd>VTK image that we want to change the origin of.</dd> +<dt><strong><code>new_origin</code></strong> : <code>tuple</code>, optional</dt> +<dd>New origin to asign to <code>vtk_image</code>, by default (0, 0, 0)</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>vtk.Filter</code></dt> +<dd>End of VTK filter pipeline after applying origin change.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def set_vtk_image_origin(vtk_image, new_origin=(0, 0, 0)): + """ + Reset the origin of a `vtk_image` + + Parameters + ---------- + vtk_image : vtk.image + VTK image that we want to change the origin of. + new_origin : tuple, optional + New origin to asign to `vtk_image`, by default (0, 0, 0) + + Returns + ------- + vtk.Filter + End of VTK filter pipeline after applying origin change. + """ + + change_origin = vtk.vtkImageChangeInformation() + change_origin.SetInputConnection(vtk_image.GetOutputPort()) + change_origin.SetOutputOrigin(new_origin) + change_origin.Update() + return change_origin</code></pre> +</details> +</dd> +<dt id="pymskt.image.main.smooth_image"><code class="name flex"> +<span>def <span class="ident">smooth_image</span></span>(<span>image, label_idx, variance=1.0)</span> +</code></dt> +<dd> +<div class="desc"><p>Smooth a single label in a SimpleITK image. Used as pre-processing for +bones/cartilage before applying marching cubes. Helps obtain smooth surfaces. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>image</code></strong> : <code>SimpleITK.Image</code></dt> +<dd>Image to be smoothed.</dd> +<dt><strong><code>label_idx</code></strong> : <code>int</code></dt> +<dd>Integer of the tissue of interest to be smoothed in the image.</dd> +<dt><strong><code>variance</code></strong> : <code>float</code>, optional</dt> +<dd>The size of the smoothing, by default 1.0</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>SimpleITK.Image</code></dt> +<dd>Image of only the label (tissue) of interest after being smoothed.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def smooth_image(image, label_idx, variance=1.0): + """ + Smooth a single label in a SimpleITK image. Used as pre-processing for + bones/cartilage before applying marching cubes. Helps obtain smooth surfaces. + + Parameters + ---------- + image : SimpleITK.Image + Image to be smoothed. + label_idx : int + Integer of the tissue of interest to be smoothed in the image. + variance : float, optional + The size of the smoothing, by default 1.0 + + Returns + ------- + SimpleITK.Image + Image of only the label (tissue) of interest after being smoothed. + """ + new_image = binarize_segmentation_image(image, label_idx) + + new_image = sitk.Cast(new_image, sitk.sitkFloat32) + + gauss_filter = sitk.DiscreteGaussianImageFilter() + gauss_filter.SetVariance(variance) + # gauss_filter.SetUseImageSpacingOn + gauss_filter.SetUseImageSpacing(True) + filtered_new_image = gauss_filter.Execute(new_image) + + return filtered_new_image</code></pre> +</details> +</dd> +</dl> +</section> +<section> +</section> +</article> +<nav id="sidebar"> +<h1>Index</h1> +<div class="toc"> +<ul></ul> +</div> +<ul id="index"> +<li><h3>Super-module</h3> +<ul> +<li><code><a title="pymskt.image" href="index.html">pymskt.image</a></code></li> +</ul> +</li> +<li><h3><a href="#header-functions">Functions</a></h3> +<ul class=""> +<li><code><a title="pymskt.image.main.apply_transform_retain_array" href="#pymskt.image.main.apply_transform_retain_array">apply_transform_retain_array</a></code></li> +<li><code><a title="pymskt.image.main.binarize_segmentation_image" href="#pymskt.image.main.binarize_segmentation_image">binarize_segmentation_image</a></code></li> +<li><code><a title="pymskt.image.main.create_vtk_image" href="#pymskt.image.main.create_vtk_image">create_vtk_image</a></code></li> +<li><code><a title="pymskt.image.main.crop_bone_based_on_width" href="#pymskt.image.main.crop_bone_based_on_width">crop_bone_based_on_width</a></code></li> +<li><code><a title="pymskt.image.main.read_nrrd" href="#pymskt.image.main.read_nrrd">read_nrrd</a></code></li> +<li><code><a title="pymskt.image.main.set_seg_border_to_zeros" href="#pymskt.image.main.set_seg_border_to_zeros">set_seg_border_to_zeros</a></code></li> +<li><code><a title="pymskt.image.main.set_vtk_image_origin" href="#pymskt.image.main.set_vtk_image_origin">set_vtk_image_origin</a></code></li> +<li><code><a title="pymskt.image.main.smooth_image" href="#pymskt.image.main.smooth_image">smooth_image</a></code></li> +</ul> +</li> +</ul> +</nav> +</main> +<footer id="footer"> +<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p> +</footer> +</body> +</html> \ No newline at end of file