<!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.mesh.meshes 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.mesh.meshes</code></h1>
</header>
<section id="section-intro">
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">from logging import error
from posixpath import supports_unicode_filenames
import warnings
import numpy as np
import vtk
from pymskt.image.main import apply_transform_retain_array
from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy
import pyacvd
import pyvista as pv
import SimpleITK as sitk
import os
import random
import string
# import pyfocusr # MAKE THIS AN OPTIONAL IMPORT?
import pymskt
from pymskt.mesh import createMesh
from pymskt.utils import safely_delete_tmp_file, copy_image_transform_to_mesh
from pymskt.image import read_nrrd, crop_bone_based_on_width
from pymskt.mesh.utils import vtk_deep_copy
from pymskt.mesh.meshTools import (gaussian_smooth_surface_scalars,
get_mesh_physical_point_coords,
get_cartilage_properties_at_points,
smooth_scalars_from_second_mesh_onto_base,
transfer_mesh_scalars_get_weighted_average_n_closest,
resample_surface
)
from pymskt.mesh.createMesh import create_surface_mesh
from pymskt.mesh.meshTransform import (SitkVtkTransformer,
get_versor_from_transform,
break_versor_into_center_rotate_translate_transforms,
apply_transform)
from pymskt.mesh.meshRegistration import non_rigidly_register, get_icp_transform
import pymskt.mesh.io as io
class Mesh:
"""
An object to contain surface meshes for musculoskeletal anatomy. Includes helper
functions to build surface meshes, to process them, and to save them.
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=5000
):
"""
Initialize Mesh class
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
"""
if type(mesh) in (str,): #accept path like objects?
print('mesh string passed, loading mesh from disk')
self._mesh = io.read_vtk(mesh)
else:
self._mesh = mesh
self._seg_image = seg_image
self._path_seg_image = path_seg_image
self._label_idx = label_idx
self._min_n_pixels = min_n_pixels
self._list_applied_transforms = []
def read_seg_image(self,
path_seg_image=None):
"""
Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
Parameters
----------
path_seg_image : str, optional
Path to the medical image file to be loaded in, by default None
Raises
------
Exception
If path_seg_image does not exist, exception is raised.
"""
# If passing new location/seg image name, then update variables.
if path_seg_image is not None:
self._path_seg_image = path_seg_image
# If seg image location / name exist, then load image else raise exception
if (self._path_seg_image is not None):
self._seg_image = sitk.ReadImage(self._path_seg_image)
else:
raise Exception('No file path (self._path_seg_image) provided.')
def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None):
"""
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
Raises
------
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
# allow assigning label idx during mesh creation step.
if label_idx is not None:
self._label_idx = label_idx
if self._seg_image is None:
self.read_seg_image()
# Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
if min_n_pixels is None: min_n_pixels = self._min_n_pixels
seg_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
if n_pixels_labelled < min_n_pixels:
raise Exception('The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}'.format(n_pixels_labelled,
marching_cubes_threshold))
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
self._mesh = create_surface_mesh(self._seg_image,
self._label_idx,
smooth_image_var,
loc_tmp_save='/tmp',
tmp_filename=tmp_filename,
mc_threshold=marching_cubes_threshold,
filter_binary_image=smooth_image
)
safely_delete_tmp_file('/tmp',
tmp_filename)
def save_mesh(self,
filepath):
"""
Save the surface mesh from this class to disk.
Parameters
----------
filepath : str
Location & filename to save the surface mesh (vtk.vtkPolyData) to.
"""
io.write_vtk(self._mesh, filepath)
def resample_surface(self,
subdivisions=2,
clusters=10000
):
"""
Resample a surface mesh using the ACVD algorithm:
Version used:
- https://github.com/pyvista/pyacvd
Original version w/ more references:
- https://github.com/valette/ACVD
Parameters
----------
subdivisions : int, optional
Subdivide the mesh to have more points before clustering, by default 2
Probably not necessary for very dense meshes.
clusters : int, optional
The number of clusters (points/vertices) to create during resampling
surafce, by default 10000
- This is not exact, might have slight differences.
"""
self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
def apply_transform_to_mesh(self,
transform=None,
transformer=None,
save_transform=True):
"""
Apply a transformation to the surface mesh.
Parameters
----------
transform : vtk.vtkTransform, optional
Transformation to apply to mesh, by default None
transformer : vtk.vtkTransformFilter, optional
Can supply transformFilter directly, by default None
save_transform : bool, optional
Should transform be saved to list of applied transforms, by default True
Raises
------
Exception
No `transform` or `transformer` supplied - have not transformation
to apply.
"""
if (transform is not None) & (transformer is None):
transformer = vtk.vtkTransformPolyDataFilter()
transformer.SetTransform(transform)
elif (transform is None) & (transformer is not None):
transform = transformer.GetTransform()
if transformer is not None:
transformer.SetInputData(self._mesh)
transformer.Update()
self._mesh = vtk_deep_copy(transformer.GetOutput())
if save_transform is True:
self._list_applied_transforms.append(transform)
else:
raise Exception('No transform or transformer provided')
def reverse_most_recent_transform(self):
"""
Function to undo the most recent transformation stored in self._list_applied_transforms
"""
transform = self._list_applied_transforms.pop()
transform.Inverse()
self.apply_transform_to_mesh(transform=transform, save_transform=False)
def reverse_all_transforms(self):
"""
Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
"""
while len(self._list_applied_transforms) > 0:
self.reverse_most_recent_transform()
def non_rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
**kwargs
):
"""
Function to perform non rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
Returns
-------
_type_
_description_
"""
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
# Get registered mesh (source to target)
source_transformed_to_target = non_rigidly_register(
target_mesh=target,
source_mesh=source,
**kwargs
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self._mesh = source_transformed_to_target
# curent mesh is target, or is source & want to return mesh, then return it.
if (as_source is False) or ((as_source is True) & (return_transformed_mesh is True)):
return source_transformed_to_target
def rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
return_transform=False,
max_n_iter=100,
n_landmarks=1000,
reg_mode='similarity'
):
"""
Function to perform rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
max_n_iter : int, optional
Maximum number of iterations to perform, by default 100
n_landmarks : int, optional
Number of landmarks to use in registration, by default 1000
reg_mode : str, optional
Mode of registration to use, by default 'similarity' (similarity, rigid, or affine)
Returns
-------
_type_
_description_
"""
if (return_transform is True) & (return_transformed_mesh is True):
raise Exception('Cannot return both transformed mesh and transform')
if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
other_mesh = other_mesh.mesh
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
icp_transform = get_icp_transform(
source=source,
target=target,
max_n_iter=max_n_iter,
n_landmarks=n_landmarks,
reg_mode=reg_mode
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self.apply_transform_to_mesh(transform=icp_transform)
if return_transformed_mesh is True:
return self._mesh
elif return_transform is True:
return icp_transform
# curent mesh is target, or is source & want to return mesh, then return it.
elif (as_source is False) & (return_transformed_mesh is True):
return apply_transform(source=source, transform=icp_transform)
else:
raise Exception('Nothing to return from rigid registration.')
def copy_scalars_from_other_mesh_to_currect(
self,
other_mesh,
new_scalars_name='scalars_from_other_mesh',
weighted_avg=True, # Use weighted average, otherwise guassian smooth transfer
n_closest=3,
sigma=1.,
idx_coords_to_smooth_base=None,
idx_coords_to_smooth_other=None,
set_non_smoothed_scalars_to_zero=True,
):
"""
Convenience function to enable easy transfer scalars from another mesh to the current.
Can use either a gaussian smoothing function, or transfer using nearest neighbours.
** This function requires that the `other_mesh` is non-rigidly registered to the surface
of the mesh inside of this class. Or rigidly registered but using the same anatomy that
VERY closely matches. Otherwise, the transfered scalars will be bad.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Mesh we want to copy
new_scalars_name : str, optional
What to name the scalars being transfered to this current mesh, by default 'scalars_from_other_mesh'
weighted_avg : bool, optional
Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
n_closest : int, optional
If `weighted_avg` True, the number of nearest neighbours to use, by default 3
sigma : float, optional
If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
idx_coords_to_smooth_base : list, optional
If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
idx_coords_to_smooth_other : list, optional
If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
set_non_smoothed_scalars_to_zero : bool, optional
Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
"""
if type(other_mesh) is Mesh:
other_mesh = other_mesh.mesh
elif type(other_mesh) is vtk.vtkPolyData:
pass
else:
raise TypeError(f'other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}')
if weighted_avg is True:
transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
self._mesh,
other_mesh,
n=n_closest
)
else:
transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
self._mesh,
other_mesh,
sigma=sigma,
idx_coords_to_smooth_base=idx_coords_to_smooth_base,
idx_coords_to_smooth_second=idx_coords_to_smooth_other,
set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
)
if (new_scalars_name is None) & (weighted_avg is True):
if transferred_scalars.shape[1] > 1:
n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
for idx, array_name in enumerate(array_names):
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
vtk_transferred_scalars.SetName(array_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
return
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
vtk_transferred_scalars.SetName(new_scalars_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
@property
def seg_image(self):
"""
Return the `_seg_image` object
Returns
-------
SimpleITK.Image
Segmentation image used to build the surface mesh
"""
return self._seg_image
@seg_image.setter
def seg_image(self, new_seg_image):
"""
Set the `_seg_image` of the class to be the inputted `new_seg_image`.
Parameters
----------
new_seg_image : SimpleITK.Image
New image to use for creating surface mesh. This can be used to provide image to
class if it was not provided during `__init__`
"""
self._seg_image = new_seg_image
@property
def mesh(self):
"""
Return the `_mesh` object
Returns
-------
vtk.vtkPolyData
The main mesh of this class.
"""
return self._mesh
@mesh.setter
def mesh(self, new_mesh):
"""
Set the `_mesh` of the class to be the inputted `new_mesh`
Parameters
----------
new_mesh : vtk.vtkPolyData
New mesh for this class - or a method to provide a mesh to the class
after `__init__` has already been run.
"""
self._mesh = new_mesh
@property
def point_coords(self):
"""
Convenience function to return the vertices (point coordinates) for the surface mesh.
Returns
-------
numpy.ndarray
Mx3 numpy array containing the x/y/z position of each vertex of the mesh.
"""
return get_mesh_physical_point_coords(self._mesh)
@point_coords.setter
def point_coords(self, new_point_coords):
"""
Convenience function to change/update the vertices/points locations
Parameters
----------
new_point_coords : numpy.ndarray
n_points X 3 numpy array to replace exisiting point coordinate locations
This can be used to easily/quickly update the x/y/z position of a set of points on a surface mesh.
The `new_point_coords` must include the same number of points as the mesh contains.
"""
orig_point_coords = get_mesh_physical_point_coords(self._mesh)
if new_point_coords.shape == orig_point_coords.shape:
self._mesh.GetPoints().SetData(numpy_to_vtk(new_point_coords))
@property
def path_seg_image(self):
"""
Convenience function to get the `path_seg_image`
Returns
-------
str
Path to the segmentation image
"""
return self._path_seg_image
@path_seg_image.setter
def path_seg_image(self, new_path_seg_image):
"""
Convenience function to set the `path_seg_image`
Parameters
----------
new_path_seg_image : str
String to where segmentation image that should be loaded is.
"""
self._path_seg_image = new_path_seg_image
@property
def label_idx(self):
"""
Convenience function to get `label_idx`
Returns
-------
int
Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh.
"""
return self._label_idx
@label_idx.setter
def label_idx(self, new_label_idx):
"""
Convenience function to set `label_idx`
Parameters
----------
new_label_idx : int
Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh.
"""
self._label_idx = new_label_idx
@property
def min_n_pixels(self):
"""
Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh.
Returns
-------
int
Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.
"""
return self._min_n_pixels
@min_n_pixels.setter
def min_n_pixels(self, new_min_n_pixels):
"""
Convenience function to set the minimum number of pixels for a segmentation region to be created as a mesh.
Parameters
----------
new_min_n_pixels : int
Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.
"""
self._min_n_pixels = new_min_n_pixels
@property
def list_applied_transforms(self):
"""
Convenience function to get the list of transformations that have been applied to this mesh.
Returns
-------
list
List of vtk.vtkTransform objects that have been applied to the current mesh.
"""
return self._list_applied_transforms
class CartilageMesh(Mesh):
"""
Class to create, store, and process cartilage meshes
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=1000
):
super().__init__(mesh=mesh,
seg_image=seg_image,
path_seg_image=path_seg_image,
label_idx=label_idx,
min_n_pixels=min_n_pixels)
class BoneMesh(Mesh):
"""
Class to create, store, and process bone meshes
Intention is that this class includes functions to process other data & assign it to the bone surface.
It might be possible that instead this class & a cartilage class or, this class and image data etc. are
provided to another function or class that does those analyses.
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
list_cartilage_meshes : list, optional
List object which contains 1+ `CartilageMesh` objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None
list_cartilage_labels : list, optional
List of `int` values that represent the different cartilage
regions of interest appropriate for a single bone, by default None
crop_percent : float, optional
Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0
bone : str, optional
String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
crop_percent : float
Percent of width to crop along long-axis of bone
bone : str
A string indicating what bone is being represented by this class.
list_cartilage_meshes : list
List of cartialge meshes assigned to this bone.
list_cartilage_labels : list
List of cartilage labels for the `_seg_image` that are associated
with this bone.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=5000,
list_cartilage_meshes=None,
list_cartilage_labels=None,
crop_percent=None,
bone='femur',
):
"""
Class initialization
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
list_cartilage_meshes : list, optional
List object which contains 1+ `CartilageMesh` objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None
list_cartilage_labels : list, optional
List of `int` values that represent the different cartilage
regions of interest appropriate for a single bone, by default None
crop_percent : float, optional
Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0
bone : str, optional
String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.
"""
self._crop_percent = crop_percent
self._bone = bone
self._list_cartilage_meshes = list_cartilage_meshes
self._list_cartilage_labels = list_cartilage_labels
super().__init__(mesh=mesh,
seg_image=seg_image,
path_seg_image=path_seg_image,
label_idx=label_idx,
min_n_pixels=min_n_pixels)
def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None,
crop_percent=None
):
"""
This is an extension of `Mesh.create_mesh` that enables cropping of bones.
Bones might need to be cropped (this isnt necessary for cartilage)
So, adding this functionality to the processing steps before the bone mesh is created.
All functionality, except for that relevant to `crop_percent` is the same as:
`Mesh.create_mesh`.
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
crop_percent : [type], optional
[description], by default None
Raises
------
Exception
If cropping & bone is not femur or tibia, then raise an error.
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
if self._seg_image is None:
self.read_seg_image()
# Bones might need to be cropped (this isnt necessary for cartilage)
# So, adding this functionality to the processing steps before the bone mesh is created
if crop_percent is not None:
self._crop_percent = crop_percent
if (self._crop_percent is not None) and (('femur' in self._bone) or ('tibia' in self._bone)):
if 'femur' in self._bone:
bone_crop_distal = True
elif 'tibia' in self._bone:
bone_crop_distal = False
else:
raise Exception('var bone should be "femur" or "tiba" got: {} instead'.format(self._bone))
self._seg_image = crop_bone_based_on_width(self._seg_image,
self._label_idx,
percent_width_to_crop_height=self._crop_percent,
bone_crop_distal=bone_crop_distal)
elif self._crop_percent is not None:
warnings.warn(f'Trying to crop bone, but {self._bone} specified and only bones `femur`',
'or `tibia` currently supported for cropping. If using another bone, consider',
'making a pull request. If cropping not desired, set `crop_percent=None`.'
)
super().create_mesh(smooth_image=smooth_image, smooth_image_var=smooth_image_var, marching_cubes_threshold=marching_cubes_threshold, label_idx=label_idx, min_n_pixels=min_n_pixels)
def create_cartilage_meshes(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5):
"""
Helper function to create the list of cartilage meshes from the list of cartilage
labels.
Parameters
----------
image_smooth_var_cart : float
Variance to smooth cartilage segmentations before finding surface using continuous
marching cubes.
marching_cubes_threshold : float
Threshold value to create cartilage surface at from segmentation images.
Notes
-----
?? Should this function just be everything inside the for loop and then that
function gets called somewhere else?
"""
self._list_cartilage_meshes = []
for cart_label_idx in self._list_cartilage_labels:
seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
if n_pixels_with_cart == 0:
warnings.warn(
f"Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!",
UserWarning
)
else:
cart_mesh = CartilageMesh(seg_image=self._seg_image,
label_idx=cart_label_idx)
cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
self._list_cartilage_meshes.append(cart_mesh)
def calc_cartilage_thickness(self,
list_cartilage_labels=None,
list_cartilage_meshes=None,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25
):
"""
Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
calcualte the cartilage thickness for each node on the bone surface.
Parameters
----------
list_cartilage_labels : list, optional
Cartilag labels to be used to create cartilage meshes (if they dont
exist), by default None
list_cartilage_meshes : list, optional
Cartilage meshes to be used for calculating cart thickness, by default None
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
Raises
------
Exception
No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
"""
# If new cartilage infor/labels are provided, then replace existing with these ones.
if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
# If no cartilage stuff provided, then cant do this function - raise exception.
if (self._list_cartilage_meshes is None) & (self._list_cartilage_labels is None):
raise Exception('No cartilage meshes or list of cartilage labels are provided! - These can be provided either to the class function `calc_cartilage_thickness` directly, or can be specified at the time of instantiating the `BoneMesh` class.')
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone.
thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
# iterate over meshes and add their thicknesses to the thicknesses list.
for cart_mesh in self._list_cartilage_meshes:
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
# seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
seg_vtk_image=None,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
thicknesses += node_data
# Assign the thickness scalars to the bone mesh surface.
thickness_scalars = numpy_to_vtk(thicknesses)
thickness_scalars.SetName('thickness (mm)')
self._mesh.GetPointData().SetScalars(thickness_scalars)
def assign_cartilage_regions(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
- Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
Parameters
----------
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
path_save_tmp_file = os.path.join('/tmp', tmp_filename)
# if self._bone == 'femur':
# new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
# fem_cart_label_idx=1)
# sitk.WriteImage(new_seg_image, path_save_tmp_file)
# else:
sitk.WriteImage(self._seg_image, path_save_tmp_file)
vtk_seg_reader = read_nrrd(path_save_tmp_file,
set_origin_zero=True
)
vtk_seg = vtk_seg_reader.GetOutput()
seg_transformer = SitkVtkTransformer(self._seg_image)
# Delete tmp files
safely_delete_tmp_file('/tmp',
tmp_filename)
self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# iterate over meshes and add their label (region)
for cart_mesh in self._list_cartilage_meshes:
cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
seg_vtk_image=vtk_seg,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
labels += node_data[1]
cart_mesh.reverse_all_transforms()
# Assign the label (region) scalars to the bone mesh surface.
label_scalars = numpy_to_vtk(labels)
label_scalars.SetName('labels')
self._mesh.GetPointData().AddArray(label_scalars)
self.reverse_all_transforms()
def calc_cartilage_t2(self,
path_t2_nrrd,
path_seg_to_t2_transform=None,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Apply cartilage T2 values to bone surface.
Parameters
----------
path_t2_nrrd : str
Path to nrrd image of T2 map to load / use.
path_seg_to_t2_transform : str, optional
Path to a transform file to be used for aligning T2 map with segmentations,
by default None
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
print('Not yet implemented')
# if self._list_cartilage_meshes is None:
# raise('Should calculate cartialge thickness before getting T2')
# # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
# # THOUGH, DOES THAT JUST COMPLICATE THINGS?
# if path_seg_transform is not None:
# # this is in case there is a transformation needed to align the segmentation with the
# # underlying T2 image
# seg_transform = sitk.ReadTransform(path_seg_transform)
# seg_image = apply_transform_retain_array(self._seg_image,
# seg_transform,
# interpolator=sitk.sitkNearestNeighbor)
# versor = get_versor_from_transform(seg_transform)
# center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
# # first apply negative of center of rotation to mesh
# self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# self._mesh.apply_transform_to_mesh(transform=center_transform)
# # Read t2 map (vtk format)
# vtk_t2map_reader = read_nrrd(path_t2_nrrd,
# set_origin_zero=True)
# vtk_t2map = vtk_t2map_reader.GetOutput()
# sitk_t2map = sitk.ReadImage(path_t2_nrrd)
# t2_transformer = SitkVtkTransformer(sitk_t2map)
# self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# t2 = np.zeros(self._mesh.GetNumberOfPoints())
# # iterate over meshes and add their t2 to the t2 list.
# for cart_mesh in self._list_cartilage_meshes:
# if path_seg_to_t2_transform is not None:
# # first apply negative of center of rotation to mesh
# cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# cart_mesh.apply_transform_to_mesh(transform=center_transform)
# cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# _, t2_data = get_cartilage_properties_at_points(self._mesh,
# cart_mesh._mesh,
# t2_vtk_image=vtk_t2map,
# ray_cast_length=ray_cast_length,
# percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
# )
# t2 += t2_data
# cart_mesh.reverse_all_transforms()
print('NOT DONE!!!')
def smooth_surface_scalars(self,
smooth_only_cartilage=True,
scalar_sigma=1.6986436005760381, # This is a FWHM = 4
scalar_array_name='thickness (mm)',
scalar_array_idx=None,
):
"""
Function to smooth the scalars with name `scalar_array_name` on the bone surface.
Parameters
----------
smooth_only_cartilage : bool, optional
Should we only smooth where there is cartialge & ignore everywhere else, by default True
scalar_sigma : float, optional
Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
default is based on a Full Width Half Maximum (FWHM) of 4mm.
scalar_array_name : str
Name of scalar array to smooth, default 'thickness (mm)'.
scalar_array_idx : int, optional
Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
"""
if smooth_only_cartilage is True:
loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray('thickness (mm)')) > 0.01)[0]
else:
loc_cartilage = None
self._mesh = gaussian_smooth_surface_scalars(self._mesh,
sigma=scalar_sigma,
idx_coords_to_smooth=loc_cartilage,
array_name=scalar_array_name,
array_idx=scalar_array_idx)
@property
def list_cartilage_meshes(self):
"""
Convenience function to get the list of cartilage meshes
Returns
-------
list
A list of `CartilageMesh` objects associated with this bone
"""
return self._list_cartilage_meshes
@list_cartilage_meshes.setter
def list_cartilage_meshes(self, new_list_cartilage_meshes):
"""
Convenience function to set the list of cartilage meshes
Parameters
----------
new_list_cartilage_meshes : list
A list of `CartilageMesh` objects associated with this bone
"""
if type(new_list_cartilage_meshes) is list:
for mesh in new_list_cartilage_meshes:
if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
raise TypeError('Item in `list_cartilage_meshes` is not a `CartilageMesh`')
elif type(new_list_cartilage_meshes) is pymskt.mesh.meshes.CartilageMesh:
new_list_cartilage_meshes = [new_list_cartilage_meshes,]
self._list_cartilage_meshes = new_list_cartilage_meshes
@property
def list_cartilage_labels(self):
"""
Convenience function to get the list of labels for cartilage tissues associated
with this bone.
Returns
-------
list
list of `int`s for the cartilage tissues associated with this bone.
"""
return self._list_cartilage_labels
@list_cartilage_labels.setter
def list_cartilage_labels(self, new_list_cartilage_labels):
"""
Convenience function to set the list of labels for cartilage tissues associated
with this bone
Parameters
----------
new_list_cartilage_labels : list
list of `int`s for the cartilage tissues associated with this bone.
"""
if type(new_list_cartilage_labels) == list:
for label in new_list_cartilage_labels:
if type(label) != int:
raise TypeError(f'Item in `list_cartilage_labels` is not a `int` - got {type(label)}')
elif type(new_list_cartilage_labels) == int:
new_list_cartilage_labels = [new_list_cartilage_labels,]
self._list_cartilage_labels = new_list_cartilage_labels
@property
def crop_percent(self):
"""
Convenience function to get the value that `crop_percent` is set to.
Returns
-------
float
Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.
"""
return self._crop_percent
@crop_percent.setter
def crop_percent(self, new_crop_percent):
"""
Convenience function to set the value that `crop_percent` is set to.
Parameters
----------
new_crop_percent : float
Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.
"""
if type(new_crop_percent) != float:
raise TypeError(f'New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`')
self._crop_percent = new_crop_percent
@property
def bone(self):
"""
Convenience function to get the name of the bone in this object.
Returns
-------
str
Name of the bone in this object - used to help identify how to crop the bone.
"""
return self._bone
@bone.setter
def bone(self, new_bone):
"""
Convenience function to set the name of the bone in this object.
Parameters
----------
new_bone : str
Name of the bone in this object - used to help identify how to crop the bone.
"""
if type(new_bone) != str:
raise TypeError(f'New bone provided is type {type(new_bone)} - expected `str`')
self._bone = new_bone
</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="pymskt.mesh.meshes.BoneMesh"><code class="flex name class">
<span>class <span class="ident">BoneMesh</span></span>
<span>(</span><span>mesh=None, seg_image=None, path_seg_image=None, label_idx=None, min_n_pixels=5000, list_cartilage_meshes=None, list_cartilage_labels=None, crop_percent=None, bone='femur')</span>
</code></dt>
<dd>
<div class="desc"><p>Class to create, store, and process bone meshes</p>
<p>Intention is that this class includes functions to process other data & assign it to the bone surface.
It might be possible that instead this class & a cartilage class or, this class and image data etc. are
provided to another function or class that does those analyses.</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
<dt><strong><code>list_cartilage_meshes</code></strong> : <code>list</code>, optional</dt>
<dd>List object which contains 1+ <code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code> objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None</dd>
<dt><strong><code>list_cartilage_labels</code></strong> : <code>list</code>, optional</dt>
<dd>List of <code>int</code> values that represent the different cartilage
regions of interest appropriate for a single bone, by default None</dd>
<dt><strong><code>crop_percent</code></strong> : <code>float</code>, optional</dt>
<dd>Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0</dd>
<dt><strong><code>bone</code></strong> : <code>str</code>, optional</dt>
<dd>String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.</dd>
</dl>
<h2 id="attributes">Attributes</h2>
<dl>
<dt><strong><code>_mesh</code></strong> : <code>vtk.vtkPolyData</code></dt>
<dd>Item passed from <strong>init</strong>, or created during life of class.
This is the main surface mesh of this class.</dd>
<dt><strong><code>_seg_image</code></strong> : <code>SimpleITK.Image</code></dt>
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code></dt>
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
and then creat surface mesh <code>_mesh</code></dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code></dt>
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code></dt>
<dd>Minimum number of pixels for an isolated island of a segmentation to be
retained</dd>
<dt><strong><code>list_applied_transforms</code></strong> : <code>list</code></dt>
<dd>A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.</dd>
<dt><strong><code>crop_percent</code></strong> : <code>float</code></dt>
<dd>Percent of width to crop along long-axis of bone</dd>
<dt><strong><code>bone</code></strong> : <code>str</code></dt>
<dd>A string indicating what bone is being represented by this class.</dd>
<dt><strong><code>list_cartilage_meshes</code></strong> : <code>list</code></dt>
<dd>List of cartialge meshes assigned to this bone.</dd>
<dt><strong><code>list_cartilage_labels</code></strong> : <code>list</code></dt>
<dd>List of cartilage labels for the <code>_seg_image</code> that are associated
with this bone.</dd>
</dl>
<h2 id="methods">Methods</h2>
<p>Class initialization</p>
<h2 id="parameters_1">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
<dt><strong><code>list_cartilage_meshes</code></strong> : <code>list</code>, optional</dt>
<dd>List object which contains 1+ <code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code> objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None</dd>
<dt><strong><code>list_cartilage_labels</code></strong> : <code>list</code>, optional</dt>
<dd>List of <code>int</code> values that represent the different cartilage
regions of interest appropriate for a single bone, by default None</dd>
<dt><strong><code>crop_percent</code></strong> : <code>float</code>, optional</dt>
<dd>Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0</dd>
<dt><strong><code>bone</code></strong> : <code>str</code>, optional</dt>
<dd>String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class BoneMesh(Mesh):
"""
Class to create, store, and process bone meshes
Intention is that this class includes functions to process other data & assign it to the bone surface.
It might be possible that instead this class & a cartilage class or, this class and image data etc. are
provided to another function or class that does those analyses.
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
list_cartilage_meshes : list, optional
List object which contains 1+ `CartilageMesh` objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None
list_cartilage_labels : list, optional
List of `int` values that represent the different cartilage
regions of interest appropriate for a single bone, by default None
crop_percent : float, optional
Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0
bone : str, optional
String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
crop_percent : float
Percent of width to crop along long-axis of bone
bone : str
A string indicating what bone is being represented by this class.
list_cartilage_meshes : list
List of cartialge meshes assigned to this bone.
list_cartilage_labels : list
List of cartilage labels for the `_seg_image` that are associated
with this bone.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=5000,
list_cartilage_meshes=None,
list_cartilage_labels=None,
crop_percent=None,
bone='femur',
):
"""
Class initialization
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
list_cartilage_meshes : list, optional
List object which contains 1+ `CartilageMesh` objects that wrap
a vtk.vtkPolyData surface mesh of cartilage, by default None
list_cartilage_labels : list, optional
List of `int` values that represent the different cartilage
regions of interest appropriate for a single bone, by default None
crop_percent : float, optional
Proportion value to crop long-axis of bone so it is proportional
to the width of the bone for standardization purposes, by default 1.0
bone : str, optional
String indicating what bone is being analyzed so that cropping
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
Patella is not an option because we do not need to crop for the patella.
"""
self._crop_percent = crop_percent
self._bone = bone
self._list_cartilage_meshes = list_cartilage_meshes
self._list_cartilage_labels = list_cartilage_labels
super().__init__(mesh=mesh,
seg_image=seg_image,
path_seg_image=path_seg_image,
label_idx=label_idx,
min_n_pixels=min_n_pixels)
def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None,
crop_percent=None
):
"""
This is an extension of `Mesh.create_mesh` that enables cropping of bones.
Bones might need to be cropped (this isnt necessary for cartilage)
So, adding this functionality to the processing steps before the bone mesh is created.
All functionality, except for that relevant to `crop_percent` is the same as:
`Mesh.create_mesh`.
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
crop_percent : [type], optional
[description], by default None
Raises
------
Exception
If cropping & bone is not femur or tibia, then raise an error.
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
if self._seg_image is None:
self.read_seg_image()
# Bones might need to be cropped (this isnt necessary for cartilage)
# So, adding this functionality to the processing steps before the bone mesh is created
if crop_percent is not None:
self._crop_percent = crop_percent
if (self._crop_percent is not None) and (('femur' in self._bone) or ('tibia' in self._bone)):
if 'femur' in self._bone:
bone_crop_distal = True
elif 'tibia' in self._bone:
bone_crop_distal = False
else:
raise Exception('var bone should be "femur" or "tiba" got: {} instead'.format(self._bone))
self._seg_image = crop_bone_based_on_width(self._seg_image,
self._label_idx,
percent_width_to_crop_height=self._crop_percent,
bone_crop_distal=bone_crop_distal)
elif self._crop_percent is not None:
warnings.warn(f'Trying to crop bone, but {self._bone} specified and only bones `femur`',
'or `tibia` currently supported for cropping. If using another bone, consider',
'making a pull request. If cropping not desired, set `crop_percent=None`.'
)
super().create_mesh(smooth_image=smooth_image, smooth_image_var=smooth_image_var, marching_cubes_threshold=marching_cubes_threshold, label_idx=label_idx, min_n_pixels=min_n_pixels)
def create_cartilage_meshes(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5):
"""
Helper function to create the list of cartilage meshes from the list of cartilage
labels.
Parameters
----------
image_smooth_var_cart : float
Variance to smooth cartilage segmentations before finding surface using continuous
marching cubes.
marching_cubes_threshold : float
Threshold value to create cartilage surface at from segmentation images.
Notes
-----
?? Should this function just be everything inside the for loop and then that
function gets called somewhere else?
"""
self._list_cartilage_meshes = []
for cart_label_idx in self._list_cartilage_labels:
seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
if n_pixels_with_cart == 0:
warnings.warn(
f"Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!",
UserWarning
)
else:
cart_mesh = CartilageMesh(seg_image=self._seg_image,
label_idx=cart_label_idx)
cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
self._list_cartilage_meshes.append(cart_mesh)
def calc_cartilage_thickness(self,
list_cartilage_labels=None,
list_cartilage_meshes=None,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25
):
"""
Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
calcualte the cartilage thickness for each node on the bone surface.
Parameters
----------
list_cartilage_labels : list, optional
Cartilag labels to be used to create cartilage meshes (if they dont
exist), by default None
list_cartilage_meshes : list, optional
Cartilage meshes to be used for calculating cart thickness, by default None
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
Raises
------
Exception
No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
"""
# If new cartilage infor/labels are provided, then replace existing with these ones.
if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
# If no cartilage stuff provided, then cant do this function - raise exception.
if (self._list_cartilage_meshes is None) & (self._list_cartilage_labels is None):
raise Exception('No cartilage meshes or list of cartilage labels are provided! - These can be provided either to the class function `calc_cartilage_thickness` directly, or can be specified at the time of instantiating the `BoneMesh` class.')
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone.
thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
# iterate over meshes and add their thicknesses to the thicknesses list.
for cart_mesh in self._list_cartilage_meshes:
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
# seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
seg_vtk_image=None,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
thicknesses += node_data
# Assign the thickness scalars to the bone mesh surface.
thickness_scalars = numpy_to_vtk(thicknesses)
thickness_scalars.SetName('thickness (mm)')
self._mesh.GetPointData().SetScalars(thickness_scalars)
def assign_cartilage_regions(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
- Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
Parameters
----------
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
path_save_tmp_file = os.path.join('/tmp', tmp_filename)
# if self._bone == 'femur':
# new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
# fem_cart_label_idx=1)
# sitk.WriteImage(new_seg_image, path_save_tmp_file)
# else:
sitk.WriteImage(self._seg_image, path_save_tmp_file)
vtk_seg_reader = read_nrrd(path_save_tmp_file,
set_origin_zero=True
)
vtk_seg = vtk_seg_reader.GetOutput()
seg_transformer = SitkVtkTransformer(self._seg_image)
# Delete tmp files
safely_delete_tmp_file('/tmp',
tmp_filename)
self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# iterate over meshes and add their label (region)
for cart_mesh in self._list_cartilage_meshes:
cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
seg_vtk_image=vtk_seg,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
labels += node_data[1]
cart_mesh.reverse_all_transforms()
# Assign the label (region) scalars to the bone mesh surface.
label_scalars = numpy_to_vtk(labels)
label_scalars.SetName('labels')
self._mesh.GetPointData().AddArray(label_scalars)
self.reverse_all_transforms()
def calc_cartilage_t2(self,
path_t2_nrrd,
path_seg_to_t2_transform=None,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Apply cartilage T2 values to bone surface.
Parameters
----------
path_t2_nrrd : str
Path to nrrd image of T2 map to load / use.
path_seg_to_t2_transform : str, optional
Path to a transform file to be used for aligning T2 map with segmentations,
by default None
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
print('Not yet implemented')
# if self._list_cartilage_meshes is None:
# raise('Should calculate cartialge thickness before getting T2')
# # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
# # THOUGH, DOES THAT JUST COMPLICATE THINGS?
# if path_seg_transform is not None:
# # this is in case there is a transformation needed to align the segmentation with the
# # underlying T2 image
# seg_transform = sitk.ReadTransform(path_seg_transform)
# seg_image = apply_transform_retain_array(self._seg_image,
# seg_transform,
# interpolator=sitk.sitkNearestNeighbor)
# versor = get_versor_from_transform(seg_transform)
# center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
# # first apply negative of center of rotation to mesh
# self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# self._mesh.apply_transform_to_mesh(transform=center_transform)
# # Read t2 map (vtk format)
# vtk_t2map_reader = read_nrrd(path_t2_nrrd,
# set_origin_zero=True)
# vtk_t2map = vtk_t2map_reader.GetOutput()
# sitk_t2map = sitk.ReadImage(path_t2_nrrd)
# t2_transformer = SitkVtkTransformer(sitk_t2map)
# self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# t2 = np.zeros(self._mesh.GetNumberOfPoints())
# # iterate over meshes and add their t2 to the t2 list.
# for cart_mesh in self._list_cartilage_meshes:
# if path_seg_to_t2_transform is not None:
# # first apply negative of center of rotation to mesh
# cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# cart_mesh.apply_transform_to_mesh(transform=center_transform)
# cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# _, t2_data = get_cartilage_properties_at_points(self._mesh,
# cart_mesh._mesh,
# t2_vtk_image=vtk_t2map,
# ray_cast_length=ray_cast_length,
# percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
# )
# t2 += t2_data
# cart_mesh.reverse_all_transforms()
print('NOT DONE!!!')
def smooth_surface_scalars(self,
smooth_only_cartilage=True,
scalar_sigma=1.6986436005760381, # This is a FWHM = 4
scalar_array_name='thickness (mm)',
scalar_array_idx=None,
):
"""
Function to smooth the scalars with name `scalar_array_name` on the bone surface.
Parameters
----------
smooth_only_cartilage : bool, optional
Should we only smooth where there is cartialge & ignore everywhere else, by default True
scalar_sigma : float, optional
Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
default is based on a Full Width Half Maximum (FWHM) of 4mm.
scalar_array_name : str
Name of scalar array to smooth, default 'thickness (mm)'.
scalar_array_idx : int, optional
Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
"""
if smooth_only_cartilage is True:
loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray('thickness (mm)')) > 0.01)[0]
else:
loc_cartilage = None
self._mesh = gaussian_smooth_surface_scalars(self._mesh,
sigma=scalar_sigma,
idx_coords_to_smooth=loc_cartilage,
array_name=scalar_array_name,
array_idx=scalar_array_idx)
@property
def list_cartilage_meshes(self):
"""
Convenience function to get the list of cartilage meshes
Returns
-------
list
A list of `CartilageMesh` objects associated with this bone
"""
return self._list_cartilage_meshes
@list_cartilage_meshes.setter
def list_cartilage_meshes(self, new_list_cartilage_meshes):
"""
Convenience function to set the list of cartilage meshes
Parameters
----------
new_list_cartilage_meshes : list
A list of `CartilageMesh` objects associated with this bone
"""
if type(new_list_cartilage_meshes) is list:
for mesh in new_list_cartilage_meshes:
if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
raise TypeError('Item in `list_cartilage_meshes` is not a `CartilageMesh`')
elif type(new_list_cartilage_meshes) is pymskt.mesh.meshes.CartilageMesh:
new_list_cartilage_meshes = [new_list_cartilage_meshes,]
self._list_cartilage_meshes = new_list_cartilage_meshes
@property
def list_cartilage_labels(self):
"""
Convenience function to get the list of labels for cartilage tissues associated
with this bone.
Returns
-------
list
list of `int`s for the cartilage tissues associated with this bone.
"""
return self._list_cartilage_labels
@list_cartilage_labels.setter
def list_cartilage_labels(self, new_list_cartilage_labels):
"""
Convenience function to set the list of labels for cartilage tissues associated
with this bone
Parameters
----------
new_list_cartilage_labels : list
list of `int`s for the cartilage tissues associated with this bone.
"""
if type(new_list_cartilage_labels) == list:
for label in new_list_cartilage_labels:
if type(label) != int:
raise TypeError(f'Item in `list_cartilage_labels` is not a `int` - got {type(label)}')
elif type(new_list_cartilage_labels) == int:
new_list_cartilage_labels = [new_list_cartilage_labels,]
self._list_cartilage_labels = new_list_cartilage_labels
@property
def crop_percent(self):
"""
Convenience function to get the value that `crop_percent` is set to.
Returns
-------
float
Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.
"""
return self._crop_percent
@crop_percent.setter
def crop_percent(self, new_crop_percent):
"""
Convenience function to set the value that `crop_percent` is set to.
Parameters
----------
new_crop_percent : float
Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.
"""
if type(new_crop_percent) != float:
raise TypeError(f'New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`')
self._crop_percent = new_crop_percent
@property
def bone(self):
"""
Convenience function to get the name of the bone in this object.
Returns
-------
str
Name of the bone in this object - used to help identify how to crop the bone.
"""
return self._bone
@bone.setter
def bone(self, new_bone):
"""
Convenience function to set the name of the bone in this object.
Parameters
----------
new_bone : str
Name of the bone in this object - used to help identify how to crop the bone.
"""
if type(new_bone) != str:
raise TypeError(f'New bone provided is type {type(new_bone)} - expected `str`')
self._bone = new_bone </code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></li>
</ul>
<h3>Instance variables</h3>
<dl>
<dt id="pymskt.mesh.meshes.BoneMesh.bone"><code class="name">var <span class="ident">bone</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the name of the bone in this object. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>str</code></dt>
<dd>Name of the bone in this object - used to help identify how to crop the bone.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def bone(self):
"""
Convenience function to get the name of the bone in this object.
Returns
-------
str
Name of the bone in this object - used to help identify how to crop the bone.
"""
return self._bone</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.crop_percent"><code class="name">var <span class="ident">crop_percent</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the value that <code>crop_percent</code> is set to. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>float</code></dt>
<dd>Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def crop_percent(self):
"""
Convenience function to get the value that `crop_percent` is set to.
Returns
-------
float
Floating point > 0.0 indicating how much of the length of the bone should be included
when cropping - expressed as a proportion of the width.
"""
return self._crop_percent</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.list_cartilage_labels"><code class="name">var <span class="ident">list_cartilage_labels</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the list of labels for cartilage tissues associated
with this bone. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>list</code></dt>
<dd>list of <code>int</code>s for the cartilage tissues associated with this bone.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def list_cartilage_labels(self):
"""
Convenience function to get the list of labels for cartilage tissues associated
with this bone.
Returns
-------
list
list of `int`s for the cartilage tissues associated with this bone.
"""
return self._list_cartilage_labels</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.list_cartilage_meshes"><code class="name">var <span class="ident">list_cartilage_meshes</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the list of cartilage meshes</p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>list</code></dt>
<dd>A list of <code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code> objects associated with this bone</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def list_cartilage_meshes(self):
"""
Convenience function to get the list of cartilage meshes
Returns
-------
list
A list of `CartilageMesh` objects associated with this bone
"""
return self._list_cartilage_meshes</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="pymskt.mesh.meshes.BoneMesh.assign_cartilage_regions"><code class="name flex">
<span>def <span class="ident">assign_cartilage_regions</span></span>(<span>self, image_smooth_var_cart=0.15625, marching_cubes_threshold=0.5, ray_cast_length=10.0, percent_ray_length_opposite_direction=0.25)</span>
</code></dt>
<dd>
<div class="desc"><p>Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
- Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>image_smooth_var_cart</code></strong> : <code>float</code>, optional</dt>
<dd>Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2</dd>
<dt><strong><code>marching_cubes_threshold</code></strong> : <code>float</code>, optional</dt>
<dd>Threshold to create bone surface at, by default 0.5</dd>
<dt><strong><code>ray_cast_length</code></strong> : <code>float</code>, optional</dt>
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0</dd>
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> : <code>float</code>, optional</dt>
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def assign_cartilage_regions(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
- Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
Parameters
----------
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
path_save_tmp_file = os.path.join('/tmp', tmp_filename)
# if self._bone == 'femur':
# new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
# fem_cart_label_idx=1)
# sitk.WriteImage(new_seg_image, path_save_tmp_file)
# else:
sitk.WriteImage(self._seg_image, path_save_tmp_file)
vtk_seg_reader = read_nrrd(path_save_tmp_file,
set_origin_zero=True
)
vtk_seg = vtk_seg_reader.GetOutput()
seg_transformer = SitkVtkTransformer(self._seg_image)
# Delete tmp files
safely_delete_tmp_file('/tmp',
tmp_filename)
self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# iterate over meshes and add their label (region)
for cart_mesh in self._list_cartilage_meshes:
cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
seg_vtk_image=vtk_seg,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
labels += node_data[1]
cart_mesh.reverse_all_transforms()
# Assign the label (region) scalars to the bone mesh surface.
label_scalars = numpy_to_vtk(labels)
label_scalars.SetName('labels')
self._mesh.GetPointData().AddArray(label_scalars)
self.reverse_all_transforms()</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.calc_cartilage_t2"><code class="name flex">
<span>def <span class="ident">calc_cartilage_t2</span></span>(<span>self, path_t2_nrrd, path_seg_to_t2_transform=None, ray_cast_length=10.0, percent_ray_length_opposite_direction=0.25)</span>
</code></dt>
<dd>
<div class="desc"><p>Apply cartilage T2 values to bone surface. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>path_t2_nrrd</code></strong> : <code>str</code></dt>
<dd>Path to nrrd image of T2 map to load / use.</dd>
<dt><strong><code>path_seg_to_t2_transform</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a transform file to be used for aligning T2 map with segmentations,
by default None</dd>
<dt><strong><code>ray_cast_length</code></strong> : <code>float</code>, optional</dt>
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0</dd>
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> : <code>float</code>, optional</dt>
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def calc_cartilage_t2(self,
path_t2_nrrd,
path_seg_to_t2_transform=None,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25):
"""
Apply cartilage T2 values to bone surface.
Parameters
----------
path_t2_nrrd : str
Path to nrrd image of T2 map to load / use.
path_seg_to_t2_transform : str, optional
Path to a transform file to be used for aligning T2 map with segmentations,
by default None
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
"""
print('Not yet implemented')
# if self._list_cartilage_meshes is None:
# raise('Should calculate cartialge thickness before getting T2')
# # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
# # THOUGH, DOES THAT JUST COMPLICATE THINGS?
# if path_seg_transform is not None:
# # this is in case there is a transformation needed to align the segmentation with the
# # underlying T2 image
# seg_transform = sitk.ReadTransform(path_seg_transform)
# seg_image = apply_transform_retain_array(self._seg_image,
# seg_transform,
# interpolator=sitk.sitkNearestNeighbor)
# versor = get_versor_from_transform(seg_transform)
# center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
# # first apply negative of center of rotation to mesh
# self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# self._mesh.apply_transform_to_mesh(transform=center_transform)
# # Read t2 map (vtk format)
# vtk_t2map_reader = read_nrrd(path_t2_nrrd,
# set_origin_zero=True)
# vtk_t2map = vtk_t2map_reader.GetOutput()
# sitk_t2map = sitk.ReadImage(path_t2_nrrd)
# t2_transformer = SitkVtkTransformer(sitk_t2map)
# self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# t2 = np.zeros(self._mesh.GetNumberOfPoints())
# # iterate over meshes and add their t2 to the t2 list.
# for cart_mesh in self._list_cartilage_meshes:
# if path_seg_to_t2_transform is not None:
# # first apply negative of center of rotation to mesh
# cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
# # now apply the transform (rotation then translation)
# cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
# cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
# #then undo the center of rotation
# cart_mesh.apply_transform_to_mesh(transform=center_transform)
# cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
# _, t2_data = get_cartilage_properties_at_points(self._mesh,
# cart_mesh._mesh,
# t2_vtk_image=vtk_t2map,
# ray_cast_length=ray_cast_length,
# percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
# )
# t2 += t2_data
# cart_mesh.reverse_all_transforms()
print('NOT DONE!!!')</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.calc_cartilage_thickness"><code class="name flex">
<span>def <span class="ident">calc_cartilage_thickness</span></span>(<span>self, list_cartilage_labels=None, list_cartilage_meshes=None, image_smooth_var_cart=0.15625, marching_cubes_threshold=0.5, ray_cast_length=10.0, percent_ray_length_opposite_direction=0.25)</span>
</code></dt>
<dd>
<div class="desc"><p>Using bone mesh (<code>_mesh</code>) and the list of cartilage meshes (<code>list_cartilage_meshes</code>)
calcualte the cartilage thickness for each node on the bone surface. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>list_cartilage_labels</code></strong> : <code>list</code>, optional</dt>
<dd>Cartilag labels to be used to create cartilage meshes (if they dont
exist), by default None</dd>
<dt><strong><code>list_cartilage_meshes</code></strong> : <code>list</code>, optional</dt>
<dd>Cartilage meshes to be used for calculating cart thickness, by default None</dd>
<dt><strong><code>image_smooth_var_cart</code></strong> : <code>float</code>, optional</dt>
<dd>Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2</dd>
<dt><strong><code>marching_cubes_threshold</code></strong> : <code>float</code>, optional</dt>
<dd>Threshold to create bone surface at, by default 0.5</dd>
<dt><strong><code>ray_cast_length</code></strong> : <code>float</code>, optional</dt>
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0</dd>
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> : <code>float</code>, optional</dt>
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
</dl>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>No cartilage available (either <code>list_cartilage_meshes</code> or <code>list_cartilage_labels</code>)</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def calc_cartilage_thickness(self,
list_cartilage_labels=None,
list_cartilage_meshes=None,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5,
ray_cast_length=10.0,
percent_ray_length_opposite_direction=0.25
):
"""
Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
calcualte the cartilage thickness for each node on the bone surface.
Parameters
----------
list_cartilage_labels : list, optional
Cartilag labels to be used to create cartilage meshes (if they dont
exist), by default None
list_cartilage_meshes : list, optional
Cartilage meshes to be used for calculating cart thickness, by default None
image_smooth_var_cart : float, optional
Variance of gaussian filter to be applied to binary cartilage masks,
by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold to create bone surface at, by default 0.5
ray_cast_length : float, optional
Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &
outter shell), by default 10.0
percent_ray_length_opposite_direction : float, optional
How far to project ray inside of the bone. This is done just in case the cartilage
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
Raises
------
Exception
No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
"""
# If new cartilage infor/labels are provided, then replace existing with these ones.
if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
# If no cartilage stuff provided, then cant do this function - raise exception.
if (self._list_cartilage_meshes is None) & (self._list_cartilage_labels is None):
raise Exception('No cartilage meshes or list of cartilage labels are provided! - These can be provided either to the class function `calc_cartilage_thickness` directly, or can be specified at the time of instantiating the `BoneMesh` class.')
# if cartilage meshes don't exist yet, then make them.
if self._list_cartilage_meshes is None:
self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
# pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone.
thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
# iterate over meshes and add their thicknesses to the thicknesses list.
for cart_mesh in self._list_cartilage_meshes:
node_data = get_cartilage_properties_at_points(self._mesh,
cart_mesh._mesh,
t2_vtk_image=None,
# seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
seg_vtk_image=None,
ray_cast_length=ray_cast_length,
percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
)
thicknesses += node_data
# Assign the thickness scalars to the bone mesh surface.
thickness_scalars = numpy_to_vtk(thicknesses)
thickness_scalars.SetName('thickness (mm)')
self._mesh.GetPointData().SetScalars(thickness_scalars)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.create_cartilage_meshes"><code class="name flex">
<span>def <span class="ident">create_cartilage_meshes</span></span>(<span>self, image_smooth_var_cart=0.15625, marching_cubes_threshold=0.5)</span>
</code></dt>
<dd>
<div class="desc"><p>Helper function to create the list of cartilage meshes from the list of cartilage
labels. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>image_smooth_var_cart</code></strong> : <code>float</code></dt>
<dd>Variance to smooth cartilage segmentations before finding surface using continuous
marching cubes.</dd>
<dt><strong><code>marching_cubes_threshold</code></strong> : <code>float</code></dt>
<dd>Threshold value to create cartilage surface at from segmentation images.</dd>
</dl>
<h2 id="notes">Notes</h2>
<p>?? Should this function just be everything inside the for loop and then that
function gets called somewhere else?</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def create_cartilage_meshes(self,
image_smooth_var_cart=0.3125 / 2,
marching_cubes_threshold=0.5):
"""
Helper function to create the list of cartilage meshes from the list of cartilage
labels.
Parameters
----------
image_smooth_var_cart : float
Variance to smooth cartilage segmentations before finding surface using continuous
marching cubes.
marching_cubes_threshold : float
Threshold value to create cartilage surface at from segmentation images.
Notes
-----
?? Should this function just be everything inside the for loop and then that
function gets called somewhere else?
"""
self._list_cartilage_meshes = []
for cart_label_idx in self._list_cartilage_labels:
seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
if n_pixels_with_cart == 0:
warnings.warn(
f"Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!",
UserWarning
)
else:
cart_mesh = CartilageMesh(seg_image=self._seg_image,
label_idx=cart_label_idx)
cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
marching_cubes_threshold=marching_cubes_threshold)
self._list_cartilage_meshes.append(cart_mesh)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.create_mesh"><code class="name flex">
<span>def <span class="ident">create_mesh</span></span>(<span>self, smooth_image=True, smooth_image_var=0.15625, marching_cubes_threshold=0.5, label_idx=None, min_n_pixels=None, crop_percent=None)</span>
</code></dt>
<dd>
<div class="desc"><p>This is an extension of <code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">Mesh.create_mesh()</a></code> that enables cropping of bones.
Bones might need to be cropped (this isnt necessary for cartilage)
So, adding this functionality to the processing steps before the bone mesh is created.</p>
<p>All functionality, except for that relevant to <code>crop_percent</code> is the same as:
<code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">Mesh.create_mesh()</a></code>. </p>
<p>Create a surface mesh from the classes <code>_seg_image</code>. If <code>_seg_image</code>
does not exist, then read it in using <code>read_seg_image</code>. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>smooth_image</code></strong> : <code>bool</code>, optional</dt>
<dd>Should the <code>_seg_image</code> be gaussian filtered, by default True</dd>
<dt><strong><code>smooth_image_var</code></strong> : <code>float</code>, optional</dt>
<dd>Variance of gaussian filter to apply to <code>_seg_image</code>, by default 0.3125/2</dd>
<dt><strong><code>marching_cubes_threshold</code></strong> : <code>float</code>, optional</dt>
<dd>Threshold contour level to create surface mesh on, by default 0.5</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label value / index to create mesh from, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None</dd>
<dt><strong><code>crop_percent</code></strong> : <code>[type]</code>, optional</dt>
<dd>[description], by default None</dd>
</dl>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>If cropping & bone is not femur or tibia, then raise an error.</dd>
<dt><code>Exception</code></dt>
<dd>If the total number of pixels segmentated (<code>n_pixels_labelled</code>) is
< <code>min_n_pixels</code> then there is no object in the image.</dd>
<dt><code>Exception</code></dt>
<dd>If no <code>_seg_image</code> and no <code>label_idx</code> then we don't know what tissue to create the
surface mesh from.</dd>
<dt><code>Exception</code></dt>
<dd>If no <code>_seg_image</code> or <code>path_seg_image</code> then we have no image to create mesh from.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None,
crop_percent=None
):
"""
This is an extension of `Mesh.create_mesh` that enables cropping of bones.
Bones might need to be cropped (this isnt necessary for cartilage)
So, adding this functionality to the processing steps before the bone mesh is created.
All functionality, except for that relevant to `crop_percent` is the same as:
`Mesh.create_mesh`.
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
crop_percent : [type], optional
[description], by default None
Raises
------
Exception
If cropping & bone is not femur or tibia, then raise an error.
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
if self._seg_image is None:
self.read_seg_image()
# Bones might need to be cropped (this isnt necessary for cartilage)
# So, adding this functionality to the processing steps before the bone mesh is created
if crop_percent is not None:
self._crop_percent = crop_percent
if (self._crop_percent is not None) and (('femur' in self._bone) or ('tibia' in self._bone)):
if 'femur' in self._bone:
bone_crop_distal = True
elif 'tibia' in self._bone:
bone_crop_distal = False
else:
raise Exception('var bone should be "femur" or "tiba" got: {} instead'.format(self._bone))
self._seg_image = crop_bone_based_on_width(self._seg_image,
self._label_idx,
percent_width_to_crop_height=self._crop_percent,
bone_crop_distal=bone_crop_distal)
elif self._crop_percent is not None:
warnings.warn(f'Trying to crop bone, but {self._bone} specified and only bones `femur`',
'or `tibia` currently supported for cropping. If using another bone, consider',
'making a pull request. If cropping not desired, set `crop_percent=None`.'
)
super().create_mesh(smooth_image=smooth_image, smooth_image_var=smooth_image_var, marching_cubes_threshold=marching_cubes_threshold, label_idx=label_idx, min_n_pixels=min_n_pixels)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.BoneMesh.smooth_surface_scalars"><code class="name flex">
<span>def <span class="ident">smooth_surface_scalars</span></span>(<span>self, smooth_only_cartilage=True, scalar_sigma=1.6986436005760381, scalar_array_name='thickness (mm)', scalar_array_idx=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Function to smooth the scalars with name <code>scalar_array_name</code> on the bone surface. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>smooth_only_cartilage</code></strong> : <code>bool</code>, optional</dt>
<dd>Should we only smooth where there is cartialge & ignore everywhere else, by default True</dd>
<dt><strong><code>scalar_sigma</code></strong> : <code>float</code>, optional</dt>
<dd>Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
default is based on a Full Width Half Maximum (FWHM) of 4mm.</dd>
<dt><strong><code>scalar_array_name</code></strong> : <code>str</code></dt>
<dd>Name of scalar array to smooth, default 'thickness (mm)'.</dd>
<dt><strong><code>scalar_array_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Index of the scalar array to smooth (alternative to using <code>scalar_array_name</code>) , by default None</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def smooth_surface_scalars(self,
smooth_only_cartilage=True,
scalar_sigma=1.6986436005760381, # This is a FWHM = 4
scalar_array_name='thickness (mm)',
scalar_array_idx=None,
):
"""
Function to smooth the scalars with name `scalar_array_name` on the bone surface.
Parameters
----------
smooth_only_cartilage : bool, optional
Should we only smooth where there is cartialge & ignore everywhere else, by default True
scalar_sigma : float, optional
Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
default is based on a Full Width Half Maximum (FWHM) of 4mm.
scalar_array_name : str
Name of scalar array to smooth, default 'thickness (mm)'.
scalar_array_idx : int, optional
Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
"""
if smooth_only_cartilage is True:
loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray('thickness (mm)')) > 0.01)[0]
else:
loc_cartilage = None
self._mesh = gaussian_smooth_surface_scalars(self._mesh,
sigma=scalar_sigma,
idx_coords_to_smooth=loc_cartilage,
array_name=scalar_array_name,
array_idx=scalar_array_idx)</code></pre>
</details>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></b></code>:
<ul class="hlist">
<li><code><a title="pymskt.mesh.meshes.Mesh.apply_transform_to_mesh" href="#pymskt.mesh.meshes.Mesh.apply_transform_to_mesh">apply_transform_to_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect" href="#pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect">copy_scalars_from_other_mesh_to_currect</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.list_applied_transforms" href="#pymskt.mesh.meshes.Mesh.list_applied_transforms">list_applied_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.min_n_pixels" href="#pymskt.mesh.meshes.Mesh.min_n_pixels">min_n_pixels</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.non_rigidly_register" href="#pymskt.mesh.meshes.Mesh.non_rigidly_register">non_rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.path_seg_image" href="#pymskt.mesh.meshes.Mesh.path_seg_image">path_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.read_seg_image" href="#pymskt.mesh.meshes.Mesh.read_seg_image">read_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_all_transforms" href="#pymskt.mesh.meshes.Mesh.reverse_all_transforms">reverse_all_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_most_recent_transform" href="#pymskt.mesh.meshes.Mesh.reverse_most_recent_transform">reverse_most_recent_transform</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
</ul>
</li>
</ul>
</dd>
<dt id="pymskt.mesh.meshes.CartilageMesh"><code class="flex name class">
<span>class <span class="ident">CartilageMesh</span></span>
<span>(</span><span>mesh=None, seg_image=None, path_seg_image=None, label_idx=None, min_n_pixels=1000)</span>
</code></dt>
<dd>
<div class="desc"><p>Class to create, store, and process cartilage meshes</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
</dl>
<h2 id="attributes">Attributes</h2>
<dl>
<dt><strong><code>_mesh</code></strong> : <code>vtk.vtkPolyData</code></dt>
<dd>Item passed from <strong>init</strong>, or created during life of class.
This is the main surface mesh of this class.</dd>
<dt><strong><code>_seg_image</code></strong> : <code>SimpleITK.Image</code></dt>
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code></dt>
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
and then creat surface mesh <code>_mesh</code></dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code></dt>
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code></dt>
<dd>Minimum number of pixels for an isolated island of a segmentation to be
retained</dd>
<dt><strong><code>list_applied_transforms</code></strong> : <code>list</code></dt>
<dd>A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.</dd>
</dl>
<h2 id="methods">Methods</h2>
<p>Initialize Mesh class</p>
<h2 id="parameters_1">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class CartilageMesh(Mesh):
"""
Class to create, store, and process cartilage meshes
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=1000
):
super().__init__(mesh=mesh,
seg_image=seg_image,
path_seg_image=path_seg_image,
label_idx=label_idx,
min_n_pixels=min_n_pixels)</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></li>
</ul>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></b></code>:
<ul class="hlist">
<li><code><a title="pymskt.mesh.meshes.Mesh.apply_transform_to_mesh" href="#pymskt.mesh.meshes.Mesh.apply_transform_to_mesh">apply_transform_to_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect" href="#pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect">copy_scalars_from_other_mesh_to_currect</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">create_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.list_applied_transforms" href="#pymskt.mesh.meshes.Mesh.list_applied_transforms">list_applied_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.min_n_pixels" href="#pymskt.mesh.meshes.Mesh.min_n_pixels">min_n_pixels</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.non_rigidly_register" href="#pymskt.mesh.meshes.Mesh.non_rigidly_register">non_rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.path_seg_image" href="#pymskt.mesh.meshes.Mesh.path_seg_image">path_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.read_seg_image" href="#pymskt.mesh.meshes.Mesh.read_seg_image">read_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_all_transforms" href="#pymskt.mesh.meshes.Mesh.reverse_all_transforms">reverse_all_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_most_recent_transform" href="#pymskt.mesh.meshes.Mesh.reverse_most_recent_transform">reverse_most_recent_transform</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
</ul>
</li>
</ul>
</dd>
<dt id="pymskt.mesh.meshes.Mesh"><code class="flex name class">
<span>class <span class="ident">Mesh</span></span>
<span>(</span><span>mesh=None, seg_image=None, path_seg_image=None, label_idx=None, min_n_pixels=5000)</span>
</code></dt>
<dd>
<div class="desc"><p>An object to contain surface meshes for musculoskeletal anatomy. Includes helper
functions to build surface meshes, to process them, and to save them. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
</dl>
<h2 id="attributes">Attributes</h2>
<dl>
<dt><strong><code>_mesh</code></strong> : <code>vtk.vtkPolyData</code></dt>
<dd>Item passed from <strong>init</strong>, or created during life of class.
This is the main surface mesh of this class.</dd>
<dt><strong><code>_seg_image</code></strong> : <code>SimpleITK.Image</code></dt>
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code></dt>
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
and then creat surface mesh <code>_mesh</code></dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code></dt>
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code></dt>
<dd>Minimum number of pixels for an isolated island of a segmentation to be
retained</dd>
<dt><strong><code>list_applied_transforms</code></strong> : <code>list</code></dt>
<dd>A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.</dd>
</dl>
<h2 id="methods">Methods</h2>
<p>Initialize Mesh class</p>
<h2 id="parameters_1">Parameters</h2>
<dl>
<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code>, optional</dt>
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
<dt><strong><code>seg_image</code></strong> : <code>SimpleITK.Image</code>, optional</dt>
<dd>Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None</dd>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to a medical image (.nrrd) to load and create mesh from,
by default None</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label of anatomy of interest, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>All islands smaller than this size are dropped, by default 5000</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Mesh:
"""
An object to contain surface meshes for musculoskeletal anatomy. Includes helper
functions to build surface meshes, to process them, and to save them.
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
Attributes
----------
_mesh : vtk.vtkPolyData
Item passed from __init__, or created during life of class.
This is the main surface mesh of this class.
_seg_image : SimpleITK.Image
Segmentation image that can be used to create mesh. This is optional.
path_seg_image : str
Path to medical image (.nrrd) that can be loaded to create `_seg_image`
and then creat surface mesh `_mesh`
label_idx : int
Integer of anatomy to create surface mesh from `_seg_image`
min_n_pixels : int
Minimum number of pixels for an isolated island of a segmentation to be
retained
list_applied_transforms : list
A list of transformations applied to a surface mesh.
This list allows for undoing of most recent transform, or undoing
all of them by iterating over the list in reverse.
Methods
----------
"""
def __init__(self,
mesh=None,
seg_image=None,
path_seg_image=None,
label_idx=None,
min_n_pixels=5000
):
"""
Initialize Mesh class
Parameters
----------
mesh : vtk.vtkPolyData, optional
vtkPolyData object that is basis of surface mesh, by default None
seg_image : SimpleITK.Image, optional
Segmentation image that can be used to create surface mesh - used
instead of mesh, by default None
path_seg_image : str, optional
Path to a medical image (.nrrd) to load and create mesh from,
by default None
label_idx : int, optional
Label of anatomy of interest, by default None
min_n_pixels : int, optional
All islands smaller than this size are dropped, by default 5000
"""
if type(mesh) in (str,): #accept path like objects?
print('mesh string passed, loading mesh from disk')
self._mesh = io.read_vtk(mesh)
else:
self._mesh = mesh
self._seg_image = seg_image
self._path_seg_image = path_seg_image
self._label_idx = label_idx
self._min_n_pixels = min_n_pixels
self._list_applied_transforms = []
def read_seg_image(self,
path_seg_image=None):
"""
Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
Parameters
----------
path_seg_image : str, optional
Path to the medical image file to be loaded in, by default None
Raises
------
Exception
If path_seg_image does not exist, exception is raised.
"""
# If passing new location/seg image name, then update variables.
if path_seg_image is not None:
self._path_seg_image = path_seg_image
# If seg image location / name exist, then load image else raise exception
if (self._path_seg_image is not None):
self._seg_image = sitk.ReadImage(self._path_seg_image)
else:
raise Exception('No file path (self._path_seg_image) provided.')
def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None):
"""
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
Raises
------
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
# allow assigning label idx during mesh creation step.
if label_idx is not None:
self._label_idx = label_idx
if self._seg_image is None:
self.read_seg_image()
# Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
if min_n_pixels is None: min_n_pixels = self._min_n_pixels
seg_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
if n_pixels_labelled < min_n_pixels:
raise Exception('The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}'.format(n_pixels_labelled,
marching_cubes_threshold))
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
self._mesh = create_surface_mesh(self._seg_image,
self._label_idx,
smooth_image_var,
loc_tmp_save='/tmp',
tmp_filename=tmp_filename,
mc_threshold=marching_cubes_threshold,
filter_binary_image=smooth_image
)
safely_delete_tmp_file('/tmp',
tmp_filename)
def save_mesh(self,
filepath):
"""
Save the surface mesh from this class to disk.
Parameters
----------
filepath : str
Location & filename to save the surface mesh (vtk.vtkPolyData) to.
"""
io.write_vtk(self._mesh, filepath)
def resample_surface(self,
subdivisions=2,
clusters=10000
):
"""
Resample a surface mesh using the ACVD algorithm:
Version used:
- https://github.com/pyvista/pyacvd
Original version w/ more references:
- https://github.com/valette/ACVD
Parameters
----------
subdivisions : int, optional
Subdivide the mesh to have more points before clustering, by default 2
Probably not necessary for very dense meshes.
clusters : int, optional
The number of clusters (points/vertices) to create during resampling
surafce, by default 10000
- This is not exact, might have slight differences.
"""
self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
def apply_transform_to_mesh(self,
transform=None,
transformer=None,
save_transform=True):
"""
Apply a transformation to the surface mesh.
Parameters
----------
transform : vtk.vtkTransform, optional
Transformation to apply to mesh, by default None
transformer : vtk.vtkTransformFilter, optional
Can supply transformFilter directly, by default None
save_transform : bool, optional
Should transform be saved to list of applied transforms, by default True
Raises
------
Exception
No `transform` or `transformer` supplied - have not transformation
to apply.
"""
if (transform is not None) & (transformer is None):
transformer = vtk.vtkTransformPolyDataFilter()
transformer.SetTransform(transform)
elif (transform is None) & (transformer is not None):
transform = transformer.GetTransform()
if transformer is not None:
transformer.SetInputData(self._mesh)
transformer.Update()
self._mesh = vtk_deep_copy(transformer.GetOutput())
if save_transform is True:
self._list_applied_transforms.append(transform)
else:
raise Exception('No transform or transformer provided')
def reverse_most_recent_transform(self):
"""
Function to undo the most recent transformation stored in self._list_applied_transforms
"""
transform = self._list_applied_transforms.pop()
transform.Inverse()
self.apply_transform_to_mesh(transform=transform, save_transform=False)
def reverse_all_transforms(self):
"""
Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
"""
while len(self._list_applied_transforms) > 0:
self.reverse_most_recent_transform()
def non_rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
**kwargs
):
"""
Function to perform non rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
Returns
-------
_type_
_description_
"""
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
# Get registered mesh (source to target)
source_transformed_to_target = non_rigidly_register(
target_mesh=target,
source_mesh=source,
**kwargs
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self._mesh = source_transformed_to_target
# curent mesh is target, or is source & want to return mesh, then return it.
if (as_source is False) or ((as_source is True) & (return_transformed_mesh is True)):
return source_transformed_to_target
def rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
return_transform=False,
max_n_iter=100,
n_landmarks=1000,
reg_mode='similarity'
):
"""
Function to perform rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
max_n_iter : int, optional
Maximum number of iterations to perform, by default 100
n_landmarks : int, optional
Number of landmarks to use in registration, by default 1000
reg_mode : str, optional
Mode of registration to use, by default 'similarity' (similarity, rigid, or affine)
Returns
-------
_type_
_description_
"""
if (return_transform is True) & (return_transformed_mesh is True):
raise Exception('Cannot return both transformed mesh and transform')
if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
other_mesh = other_mesh.mesh
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
icp_transform = get_icp_transform(
source=source,
target=target,
max_n_iter=max_n_iter,
n_landmarks=n_landmarks,
reg_mode=reg_mode
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self.apply_transform_to_mesh(transform=icp_transform)
if return_transformed_mesh is True:
return self._mesh
elif return_transform is True:
return icp_transform
# curent mesh is target, or is source & want to return mesh, then return it.
elif (as_source is False) & (return_transformed_mesh is True):
return apply_transform(source=source, transform=icp_transform)
else:
raise Exception('Nothing to return from rigid registration.')
def copy_scalars_from_other_mesh_to_currect(
self,
other_mesh,
new_scalars_name='scalars_from_other_mesh',
weighted_avg=True, # Use weighted average, otherwise guassian smooth transfer
n_closest=3,
sigma=1.,
idx_coords_to_smooth_base=None,
idx_coords_to_smooth_other=None,
set_non_smoothed_scalars_to_zero=True,
):
"""
Convenience function to enable easy transfer scalars from another mesh to the current.
Can use either a gaussian smoothing function, or transfer using nearest neighbours.
** This function requires that the `other_mesh` is non-rigidly registered to the surface
of the mesh inside of this class. Or rigidly registered but using the same anatomy that
VERY closely matches. Otherwise, the transfered scalars will be bad.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Mesh we want to copy
new_scalars_name : str, optional
What to name the scalars being transfered to this current mesh, by default 'scalars_from_other_mesh'
weighted_avg : bool, optional
Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
n_closest : int, optional
If `weighted_avg` True, the number of nearest neighbours to use, by default 3
sigma : float, optional
If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
idx_coords_to_smooth_base : list, optional
If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
idx_coords_to_smooth_other : list, optional
If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
set_non_smoothed_scalars_to_zero : bool, optional
Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
"""
if type(other_mesh) is Mesh:
other_mesh = other_mesh.mesh
elif type(other_mesh) is vtk.vtkPolyData:
pass
else:
raise TypeError(f'other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}')
if weighted_avg is True:
transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
self._mesh,
other_mesh,
n=n_closest
)
else:
transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
self._mesh,
other_mesh,
sigma=sigma,
idx_coords_to_smooth_base=idx_coords_to_smooth_base,
idx_coords_to_smooth_second=idx_coords_to_smooth_other,
set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
)
if (new_scalars_name is None) & (weighted_avg is True):
if transferred_scalars.shape[1] > 1:
n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
for idx, array_name in enumerate(array_names):
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
vtk_transferred_scalars.SetName(array_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
return
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
vtk_transferred_scalars.SetName(new_scalars_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
@property
def seg_image(self):
"""
Return the `_seg_image` object
Returns
-------
SimpleITK.Image
Segmentation image used to build the surface mesh
"""
return self._seg_image
@seg_image.setter
def seg_image(self, new_seg_image):
"""
Set the `_seg_image` of the class to be the inputted `new_seg_image`.
Parameters
----------
new_seg_image : SimpleITK.Image
New image to use for creating surface mesh. This can be used to provide image to
class if it was not provided during `__init__`
"""
self._seg_image = new_seg_image
@property
def mesh(self):
"""
Return the `_mesh` object
Returns
-------
vtk.vtkPolyData
The main mesh of this class.
"""
return self._mesh
@mesh.setter
def mesh(self, new_mesh):
"""
Set the `_mesh` of the class to be the inputted `new_mesh`
Parameters
----------
new_mesh : vtk.vtkPolyData
New mesh for this class - or a method to provide a mesh to the class
after `__init__` has already been run.
"""
self._mesh = new_mesh
@property
def point_coords(self):
"""
Convenience function to return the vertices (point coordinates) for the surface mesh.
Returns
-------
numpy.ndarray
Mx3 numpy array containing the x/y/z position of each vertex of the mesh.
"""
return get_mesh_physical_point_coords(self._mesh)
@point_coords.setter
def point_coords(self, new_point_coords):
"""
Convenience function to change/update the vertices/points locations
Parameters
----------
new_point_coords : numpy.ndarray
n_points X 3 numpy array to replace exisiting point coordinate locations
This can be used to easily/quickly update the x/y/z position of a set of points on a surface mesh.
The `new_point_coords` must include the same number of points as the mesh contains.
"""
orig_point_coords = get_mesh_physical_point_coords(self._mesh)
if new_point_coords.shape == orig_point_coords.shape:
self._mesh.GetPoints().SetData(numpy_to_vtk(new_point_coords))
@property
def path_seg_image(self):
"""
Convenience function to get the `path_seg_image`
Returns
-------
str
Path to the segmentation image
"""
return self._path_seg_image
@path_seg_image.setter
def path_seg_image(self, new_path_seg_image):
"""
Convenience function to set the `path_seg_image`
Parameters
----------
new_path_seg_image : str
String to where segmentation image that should be loaded is.
"""
self._path_seg_image = new_path_seg_image
@property
def label_idx(self):
"""
Convenience function to get `label_idx`
Returns
-------
int
Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh.
"""
return self._label_idx
@label_idx.setter
def label_idx(self, new_label_idx):
"""
Convenience function to set `label_idx`
Parameters
----------
new_label_idx : int
Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh.
"""
self._label_idx = new_label_idx
@property
def min_n_pixels(self):
"""
Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh.
Returns
-------
int
Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.
"""
return self._min_n_pixels
@min_n_pixels.setter
def min_n_pixels(self, new_min_n_pixels):
"""
Convenience function to set the minimum number of pixels for a segmentation region to be created as a mesh.
Parameters
----------
new_min_n_pixels : int
Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.
"""
self._min_n_pixels = new_min_n_pixels
@property
def list_applied_transforms(self):
"""
Convenience function to get the list of transformations that have been applied to this mesh.
Returns
-------
list
List of vtk.vtkTransform objects that have been applied to the current mesh.
"""
return self._list_applied_transforms</code></pre>
</details>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="pymskt.mesh.meshes.BoneMesh" href="#pymskt.mesh.meshes.BoneMesh">BoneMesh</a></li>
<li><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></li>
</ul>
<h3>Instance variables</h3>
<dl>
<dt id="pymskt.mesh.meshes.Mesh.label_idx"><code class="name">var <span class="ident">label_idx</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get <code>label_idx</code></p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>int</code></dt>
<dd>Integer indeicating the index/value of the tissues in <code>seg_image</code> associated with this mesh.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def label_idx(self):
"""
Convenience function to get `label_idx`
Returns
-------
int
Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh.
"""
return self._label_idx</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.list_applied_transforms"><code class="name">var <span class="ident">list_applied_transforms</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the list of transformations that have been applied to this mesh. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>list</code></dt>
<dd>List of vtk.vtkTransform objects that have been applied to the current mesh.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def list_applied_transforms(self):
"""
Convenience function to get the list of transformations that have been applied to this mesh.
Returns
-------
list
List of vtk.vtkTransform objects that have been applied to the current mesh.
"""
return self._list_applied_transforms</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.mesh"><code class="name">var <span class="ident">mesh</span></code></dt>
<dd>
<div class="desc"><p>Return the <code>_mesh</code> object</p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>vtk.vtkPolyData</code></dt>
<dd>The main mesh of this class.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def mesh(self):
"""
Return the `_mesh` object
Returns
-------
vtk.vtkPolyData
The main mesh of this class.
"""
return self._mesh</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.min_n_pixels"><code class="name">var <span class="ident">min_n_pixels</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>int</code></dt>
<dd>Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def min_n_pixels(self):
"""
Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh.
Returns
-------
int
Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.
"""
return self._min_n_pixels</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.path_seg_image"><code class="name">var <span class="ident">path_seg_image</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to get the <code>path_seg_image</code></p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>str</code></dt>
<dd>Path to the segmentation image</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def path_seg_image(self):
"""
Convenience function to get the `path_seg_image`
Returns
-------
str
Path to the segmentation image
"""
return self._path_seg_image</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.point_coords"><code class="name">var <span class="ident">point_coords</span></code></dt>
<dd>
<div class="desc"><p>Convenience function to return the vertices (point coordinates) for the surface mesh. </p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>numpy.ndarray</code></dt>
<dd>Mx3 numpy array containing the x/y/z position of each vertex of the mesh.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def point_coords(self):
"""
Convenience function to return the vertices (point coordinates) for the surface mesh.
Returns
-------
numpy.ndarray
Mx3 numpy array containing the x/y/z position of each vertex of the mesh.
"""
return get_mesh_physical_point_coords(self._mesh)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.seg_image"><code class="name">var <span class="ident">seg_image</span></code></dt>
<dd>
<div class="desc"><p>Return the <code>_seg_image</code> object</p>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>SimpleITK.Image</code></dt>
<dd>Segmentation image used to build the surface mesh</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def seg_image(self):
"""
Return the `_seg_image` object
Returns
-------
SimpleITK.Image
Segmentation image used to build the surface mesh
"""
return self._seg_image</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="pymskt.mesh.meshes.Mesh.apply_transform_to_mesh"><code class="name flex">
<span>def <span class="ident">apply_transform_to_mesh</span></span>(<span>self, transform=None, transformer=None, save_transform=True)</span>
</code></dt>
<dd>
<div class="desc"><p>Apply a transformation to the surface mesh. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>transform</code></strong> : <code>vtk.vtkTransform</code>, optional</dt>
<dd>Transformation to apply to mesh, by default None</dd>
<dt><strong><code>transformer</code></strong> : <code>vtk.vtkTransformFilter</code>, optional</dt>
<dd>Can supply transformFilter directly, by default None</dd>
<dt><strong><code>save_transform</code></strong> : <code>bool</code>, optional</dt>
<dd>Should transform be saved to list of applied transforms, by default True</dd>
</dl>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>No <code>transform</code> or <code>transformer</code> supplied - have not transformation
to apply.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def apply_transform_to_mesh(self,
transform=None,
transformer=None,
save_transform=True):
"""
Apply a transformation to the surface mesh.
Parameters
----------
transform : vtk.vtkTransform, optional
Transformation to apply to mesh, by default None
transformer : vtk.vtkTransformFilter, optional
Can supply transformFilter directly, by default None
save_transform : bool, optional
Should transform be saved to list of applied transforms, by default True
Raises
------
Exception
No `transform` or `transformer` supplied - have not transformation
to apply.
"""
if (transform is not None) & (transformer is None):
transformer = vtk.vtkTransformPolyDataFilter()
transformer.SetTransform(transform)
elif (transform is None) & (transformer is not None):
transform = transformer.GetTransform()
if transformer is not None:
transformer.SetInputData(self._mesh)
transformer.Update()
self._mesh = vtk_deep_copy(transformer.GetOutput())
if save_transform is True:
self._list_applied_transforms.append(transform)
else:
raise Exception('No transform or transformer provided')</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect"><code class="name flex">
<span>def <span class="ident">copy_scalars_from_other_mesh_to_currect</span></span>(<span>self, other_mesh, new_scalars_name='scalars_from_other_mesh', weighted_avg=True, n_closest=3, sigma=1.0, idx_coords_to_smooth_base=None, idx_coords_to_smooth_other=None, set_non_smoothed_scalars_to_zero=True)</span>
</code></dt>
<dd>
<div class="desc"><p>Convenience function to enable easy transfer scalars from another mesh to the current.
Can use either a gaussian smoothing function, or transfer using nearest neighbours. </p>
<p>** This function requires that the <code>other_mesh</code> is non-rigidly registered to the surface
of the mesh inside of this class. Or rigidly registered but using the same anatomy that
VERY closely matches. Otherwise, the transfered scalars will be bad.
</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>other_mesh</code></strong> : <code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
<dd>Mesh we want to copy</dd>
<dt><strong><code>new_scalars_name</code></strong> : <code>str</code>, optional</dt>
<dd> </dd>
<dt>What to name the scalars being transfered to this current mesh, by default 'scalars_from_other_mesh'</dt>
<dt><strong><code>weighted_avg</code></strong> : <code>bool</code>, optional</dt>
<dd>Should we use <code>weighted average</code> or <code>gaussian smooth</code> methods for transfer, by default True</dd>
<dt><strong><code>n_closest</code></strong> : <code>int</code>, optional</dt>
<dd>If <code>weighted_avg</code> True, the number of nearest neighbours to use, by default 3</dd>
<dt><strong><code>sigma</code></strong> : <code>float</code>, optional</dt>
<dd>If <code>weighted_avg</code> False, the standard deviation of gaussian kernel, by default 1.</dd>
<dt><strong><code>idx_coords_to_smooth_base</code></strong> : <code>list</code>, optional</dt>
<dd>If <code>weighted_avg</code> False, list of indices from current mesh to use in transfer, by default None</dd>
<dt><strong><code>idx_coords_to_smooth_other</code></strong> : <code>list</code>, optional</dt>
<dd>If <code>weighted_avg</code> False, list of indices from <code>other_mesh</code> to use in transfer, by default None</dd>
<dt><strong><code>set_non_smoothed_scalars_to_zero</code></strong> : <code>bool</code>, optional</dt>
<dd>Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def copy_scalars_from_other_mesh_to_currect(
self,
other_mesh,
new_scalars_name='scalars_from_other_mesh',
weighted_avg=True, # Use weighted average, otherwise guassian smooth transfer
n_closest=3,
sigma=1.,
idx_coords_to_smooth_base=None,
idx_coords_to_smooth_other=None,
set_non_smoothed_scalars_to_zero=True,
):
"""
Convenience function to enable easy transfer scalars from another mesh to the current.
Can use either a gaussian smoothing function, or transfer using nearest neighbours.
** This function requires that the `other_mesh` is non-rigidly registered to the surface
of the mesh inside of this class. Or rigidly registered but using the same anatomy that
VERY closely matches. Otherwise, the transfered scalars will be bad.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Mesh we want to copy
new_scalars_name : str, optional
What to name the scalars being transfered to this current mesh, by default 'scalars_from_other_mesh'
weighted_avg : bool, optional
Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
n_closest : int, optional
If `weighted_avg` True, the number of nearest neighbours to use, by default 3
sigma : float, optional
If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
idx_coords_to_smooth_base : list, optional
If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
idx_coords_to_smooth_other : list, optional
If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
set_non_smoothed_scalars_to_zero : bool, optional
Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
"""
if type(other_mesh) is Mesh:
other_mesh = other_mesh.mesh
elif type(other_mesh) is vtk.vtkPolyData:
pass
else:
raise TypeError(f'other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}')
if weighted_avg is True:
transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
self._mesh,
other_mesh,
n=n_closest
)
else:
transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
self._mesh,
other_mesh,
sigma=sigma,
idx_coords_to_smooth_base=idx_coords_to_smooth_base,
idx_coords_to_smooth_second=idx_coords_to_smooth_other,
set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
)
if (new_scalars_name is None) & (weighted_avg is True):
if transferred_scalars.shape[1] > 1:
n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
for idx, array_name in enumerate(array_names):
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
vtk_transferred_scalars.SetName(array_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
return
vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
vtk_transferred_scalars.SetName(new_scalars_name)
self._mesh.GetPointData().AddArray(vtk_transferred_scalars)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.create_mesh"><code class="name flex">
<span>def <span class="ident">create_mesh</span></span>(<span>self, smooth_image=True, smooth_image_var=0.15625, marching_cubes_threshold=0.5, label_idx=None, min_n_pixels=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Create a surface mesh from the classes <code>_seg_image</code>. If <code>_seg_image</code>
does not exist, then read it in using <code>read_seg_image</code>. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>smooth_image</code></strong> : <code>bool</code>, optional</dt>
<dd>Should the <code>_seg_image</code> be gaussian filtered, by default True</dd>
<dt><strong><code>smooth_image_var</code></strong> : <code>float</code>, optional</dt>
<dd>Variance of gaussian filter to apply to <code>_seg_image</code>, by default 0.3125/2</dd>
<dt><strong><code>marching_cubes_threshold</code></strong> : <code>float</code>, optional</dt>
<dd>Threshold contour level to create surface mesh on, by default 0.5</dd>
<dt><strong><code>label_idx</code></strong> : <code>int</code>, optional</dt>
<dd>Label value / index to create mesh from, by default None</dd>
<dt><strong><code>min_n_pixels</code></strong> : <code>int</code>, optional</dt>
<dd>Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None</dd>
</dl>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>If the total number of pixels segmentated (<code>n_pixels_labelled</code>) is
< <code>min_n_pixels</code> then there is no object in the image.</dd>
<dt><code>Exception</code></dt>
<dd>If no <code>_seg_image</code> and no <code>label_idx</code> then we don't know what tissue to create the
surface mesh from.</dd>
<dt><code>Exception</code></dt>
<dd>If no <code>_seg_image</code> or <code>path_seg_image</code> then we have no image to create mesh from.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def create_mesh(self,
smooth_image=True,
smooth_image_var=0.3125 / 2,
marching_cubes_threshold=0.5,
label_idx=None,
min_n_pixels=None):
"""
Create a surface mesh from the classes `_seg_image`. If `_seg_image`
does not exist, then read it in using `read_seg_image`.
Parameters
----------
smooth_image : bool, optional
Should the `_seg_image` be gaussian filtered, by default True
smooth_image_var : float, optional
Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
marching_cubes_threshold : float, optional
Threshold contour level to create surface mesh on, by default 0.5
label_idx : int, optional
Label value / index to create mesh from, by default None
min_n_pixels : int, optional
Minimum number of continuous pixels to include segmentation island
in the surface mesh creation, by default None
Raises
------
Exception
If the total number of pixels segmentated (`n_pixels_labelled`) is
< `min_n_pixels` then there is no object in the image.
Exception
If no `_seg_image` and no `label_idx` then we don't know what tissue to create the
surface mesh from.
Exception
If no `_seg_image` or `path_seg_image` then we have no image to create mesh from.
"""
# allow assigning label idx during mesh creation step.
if label_idx is not None:
self._label_idx = label_idx
if self._seg_image is None:
self.read_seg_image()
# Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
if min_n_pixels is None: min_n_pixels = self._min_n_pixels
seg_view = sitk.GetArrayViewFromImage(self._seg_image)
n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
if n_pixels_labelled < min_n_pixels:
raise Exception('The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}'.format(n_pixels_labelled,
marching_cubes_threshold))
tmp_filename = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.nrrd'
self._mesh = create_surface_mesh(self._seg_image,
self._label_idx,
smooth_image_var,
loc_tmp_save='/tmp',
tmp_filename=tmp_filename,
mc_threshold=marching_cubes_threshold,
filter_binary_image=smooth_image
)
safely_delete_tmp_file('/tmp',
tmp_filename)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.non_rigidly_register"><code class="name flex">
<span>def <span class="ident">non_rigidly_register</span></span>(<span>self, other_mesh, as_source=True, apply_transform_to_mesh=True, return_transformed_mesh=False, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Function to perform non rigid registration between this mesh and another mesh. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>other_mesh</code></strong> : <code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
<dd>Other mesh to use in registration process</dd>
<dt><strong><code>as_source</code></strong> : <code>bool</code>, optional</dt>
<dd>Should the current mesh (in this object) be the source or the target, by default True</dd>
<dt><strong><code>apply_transform_to_mesh</code></strong> : <code>bool</code>, optional</dt>
<dd>If as_source is True should we apply transformation to internal mesh, by default True</dd>
<dt><strong><code>return_transformed_mesh</code></strong> : <code>bool</code>, optional</dt>
<dd>Should we return the registered mesh, by default False</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>_type_</code></dt>
<dd><em>description</em></dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def non_rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
**kwargs
):
"""
Function to perform non rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
Returns
-------
_type_
_description_
"""
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
# Get registered mesh (source to target)
source_transformed_to_target = non_rigidly_register(
target_mesh=target,
source_mesh=source,
**kwargs
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self._mesh = source_transformed_to_target
# curent mesh is target, or is source & want to return mesh, then return it.
if (as_source is False) or ((as_source is True) & (return_transformed_mesh is True)):
return source_transformed_to_target</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.read_seg_image"><code class="name flex">
<span>def <span class="ident">read_seg_image</span></span>(<span>self, path_seg_image=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>path_seg_image</code></strong> : <code>str</code>, optional</dt>
<dd>Path to the medical image file to be loaded in, by default None</dd>
</dl>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>If path_seg_image does not exist, exception is raised.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def read_seg_image(self,
path_seg_image=None):
"""
Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
Parameters
----------
path_seg_image : str, optional
Path to the medical image file to be loaded in, by default None
Raises
------
Exception
If path_seg_image does not exist, exception is raised.
"""
# If passing new location/seg image name, then update variables.
if path_seg_image is not None:
self._path_seg_image = path_seg_image
# If seg image location / name exist, then load image else raise exception
if (self._path_seg_image is not None):
self._seg_image = sitk.ReadImage(self._path_seg_image)
else:
raise Exception('No file path (self._path_seg_image) provided.')</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.resample_surface"><code class="name flex">
<span>def <span class="ident">resample_surface</span></span>(<span>self, subdivisions=2, clusters=10000)</span>
</code></dt>
<dd>
<div class="desc"><p>Resample a surface mesh using the ACVD algorithm:
Version used:
- <a href="https://github.com/pyvista/pyacvd">https://github.com/pyvista/pyacvd</a>
Original version w/ more references:
- <a href="https://github.com/valette/ACVD">https://github.com/valette/ACVD</a></p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>subdivisions</code></strong> : <code>int</code>, optional</dt>
<dd>Subdivide the mesh to have more points before clustering, by default 2
Probably not necessary for very dense meshes.</dd>
<dt><strong><code>clusters</code></strong> : <code>int</code>, optional</dt>
<dd>The number of clusters (points/vertices) to create during resampling
surafce, by default 10000
- This is not exact, might have slight differences.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def resample_surface(self,
subdivisions=2,
clusters=10000
):
"""
Resample a surface mesh using the ACVD algorithm:
Version used:
- https://github.com/pyvista/pyacvd
Original version w/ more references:
- https://github.com/valette/ACVD
Parameters
----------
subdivisions : int, optional
Subdivide the mesh to have more points before clustering, by default 2
Probably not necessary for very dense meshes.
clusters : int, optional
The number of clusters (points/vertices) to create during resampling
surafce, by default 10000
- This is not exact, might have slight differences.
"""
self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.reverse_all_transforms"><code class="name flex">
<span>def <span class="ident">reverse_all_transforms</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def reverse_all_transforms(self):
"""
Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
"""
while len(self._list_applied_transforms) > 0:
self.reverse_most_recent_transform()</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.reverse_most_recent_transform"><code class="name flex">
<span>def <span class="ident">reverse_most_recent_transform</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Function to undo the most recent transformation stored in self._list_applied_transforms</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def reverse_most_recent_transform(self):
"""
Function to undo the most recent transformation stored in self._list_applied_transforms
"""
transform = self._list_applied_transforms.pop()
transform.Inverse()
self.apply_transform_to_mesh(transform=transform, save_transform=False)</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.rigidly_register"><code class="name flex">
<span>def <span class="ident">rigidly_register</span></span>(<span>self, other_mesh, as_source=True, apply_transform_to_mesh=True, return_transformed_mesh=False, return_transform=False, max_n_iter=100, n_landmarks=1000, reg_mode='similarity')</span>
</code></dt>
<dd>
<div class="desc"><p>Function to perform rigid registration between this mesh and another mesh. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>other_mesh</code></strong> : <code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
<dd>Other mesh to use in registration process</dd>
<dt><strong><code>as_source</code></strong> : <code>bool</code>, optional</dt>
<dd>Should the current mesh (in this object) be the source or the target, by default True</dd>
<dt><strong><code>apply_transform_to_mesh</code></strong> : <code>bool</code>, optional</dt>
<dd>If as_source is True should we apply transformation to internal mesh, by default True</dd>
<dt><strong><code>return_transformed_mesh</code></strong> : <code>bool</code>, optional</dt>
<dd>Should we return the registered mesh, by default False</dd>
<dt><strong><code>max_n_iter</code></strong> : <code>int</code>, optional</dt>
<dd>Maximum number of iterations to perform, by default 100</dd>
<dt><strong><code>n_landmarks</code></strong> : <code>int</code>, optional</dt>
<dd>Number of landmarks to use in registration, by default 1000</dd>
<dt><strong><code>reg_mode</code></strong> : <code>str</code>, optional</dt>
<dd>Mode of registration to use, by default 'similarity' (similarity, rigid, or affine)</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><code>_type_</code></dt>
<dd><em>description</em></dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def rigidly_register(
self,
other_mesh,
as_source=True,
apply_transform_to_mesh=True,
return_transformed_mesh=False,
return_transform=False,
max_n_iter=100,
n_landmarks=1000,
reg_mode='similarity'
):
"""
Function to perform rigid registration between this mesh and another mesh.
Parameters
----------
other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
Other mesh to use in registration process
as_source : bool, optional
Should the current mesh (in this object) be the source or the target, by default True
apply_transform_to_mesh : bool, optional
If as_source is True should we apply transformation to internal mesh, by default True
return_transformed_mesh : bool, optional
Should we return the registered mesh, by default False
max_n_iter : int, optional
Maximum number of iterations to perform, by default 100
n_landmarks : int, optional
Number of landmarks to use in registration, by default 1000
reg_mode : str, optional
Mode of registration to use, by default 'similarity' (similarity, rigid, or affine)
Returns
-------
_type_
_description_
"""
if (return_transform is True) & (return_transformed_mesh is True):
raise Exception('Cannot return both transformed mesh and transform')
if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
other_mesh = other_mesh.mesh
# Setup the source & target meshes based on `as_source``
if as_source is True:
source = self._mesh
target = other_mesh
elif as_source is False:
source = other_mesh
target = self._mesh
icp_transform = get_icp_transform(
source=source,
target=target,
max_n_iter=max_n_iter,
n_landmarks=n_landmarks,
reg_mode=reg_mode
)
# If current mesh is source & apply_transform_to_mesh is true then replace current mesh.
if (as_source is True) & (apply_transform_to_mesh is True):
self.apply_transform_to_mesh(transform=icp_transform)
if return_transformed_mesh is True:
return self._mesh
elif return_transform is True:
return icp_transform
# curent mesh is target, or is source & want to return mesh, then return it.
elif (as_source is False) & (return_transformed_mesh is True):
return apply_transform(source=source, transform=icp_transform)
else:
raise Exception('Nothing to return from rigid registration.')</code></pre>
</details>
</dd>
<dt id="pymskt.mesh.meshes.Mesh.save_mesh"><code class="name flex">
<span>def <span class="ident">save_mesh</span></span>(<span>self, filepath)</span>
</code></dt>
<dd>
<div class="desc"><p>Save the surface mesh from this class to disk. </p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>filepath</code></strong> : <code>str</code></dt>
<dd>Location & filename to save the surface mesh (vtk.vtkPolyData) to.</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def save_mesh(self,
filepath):
"""
Save the surface mesh from this class to disk.
Parameters
----------
filepath : str
Location & filename to save the surface mesh (vtk.vtkPolyData) to.
"""
io.write_vtk(self._mesh, filepath)</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</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.mesh" href="index.html">pymskt.mesh</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="pymskt.mesh.meshes.BoneMesh" href="#pymskt.mesh.meshes.BoneMesh">BoneMesh</a></code></h4>
<ul class="">
<li><code><a title="pymskt.mesh.meshes.BoneMesh.assign_cartilage_regions" href="#pymskt.mesh.meshes.BoneMesh.assign_cartilage_regions">assign_cartilage_regions</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.bone" href="#pymskt.mesh.meshes.BoneMesh.bone">bone</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.calc_cartilage_t2" href="#pymskt.mesh.meshes.BoneMesh.calc_cartilage_t2">calc_cartilage_t2</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.calc_cartilage_thickness" href="#pymskt.mesh.meshes.BoneMesh.calc_cartilage_thickness">calc_cartilage_thickness</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.create_cartilage_meshes" href="#pymskt.mesh.meshes.BoneMesh.create_cartilage_meshes">create_cartilage_meshes</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.create_mesh" href="#pymskt.mesh.meshes.BoneMesh.create_mesh">create_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.crop_percent" href="#pymskt.mesh.meshes.BoneMesh.crop_percent">crop_percent</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.list_cartilage_labels" href="#pymskt.mesh.meshes.BoneMesh.list_cartilage_labels">list_cartilage_labels</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.list_cartilage_meshes" href="#pymskt.mesh.meshes.BoneMesh.list_cartilage_meshes">list_cartilage_meshes</a></code></li>
<li><code><a title="pymskt.mesh.meshes.BoneMesh.smooth_surface_scalars" href="#pymskt.mesh.meshes.BoneMesh.smooth_surface_scalars">smooth_surface_scalars</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code></h4>
</li>
<li>
<h4><code><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></code></h4>
<ul class="">
<li><code><a title="pymskt.mesh.meshes.Mesh.apply_transform_to_mesh" href="#pymskt.mesh.meshes.Mesh.apply_transform_to_mesh">apply_transform_to_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect" href="#pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect">copy_scalars_from_other_mesh_to_currect</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">create_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.list_applied_transforms" href="#pymskt.mesh.meshes.Mesh.list_applied_transforms">list_applied_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.min_n_pixels" href="#pymskt.mesh.meshes.Mesh.min_n_pixels">min_n_pixels</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.non_rigidly_register" href="#pymskt.mesh.meshes.Mesh.non_rigidly_register">non_rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.path_seg_image" href="#pymskt.mesh.meshes.Mesh.path_seg_image">path_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.read_seg_image" href="#pymskt.mesh.meshes.Mesh.read_seg_image">read_seg_image</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_all_transforms" href="#pymskt.mesh.meshes.Mesh.reverse_all_transforms">reverse_all_transforms</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.reverse_most_recent_transform" href="#pymskt.mesh.meshes.Mesh.reverse_most_recent_transform">reverse_most_recent_transform</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
</ul>
</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>