Diff of /docs/mesh/meshes.html [000000] .. [9173ee]

Switch to side-by-side view

--- a
+++ b/docs/mesh/meshes.html
@@ -0,0 +1,4444 @@
+<!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:
+    &#34;&#34;&#34;
+    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
+    ----------
+
+    &#34;&#34;&#34;    
+    def __init__(self,
+                 mesh=None,
+                 seg_image=None,
+                 path_seg_image=None,
+                 label_idx=None,
+                 min_n_pixels=5000
+                 ):
+        &#34;&#34;&#34;
+        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
+        &#34;&#34;&#34;      
+        if type(mesh) in (str,): #accept path like objects?  
+            print(&#39;mesh string passed, loading mesh from disk&#39;)
+            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):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        # 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(&#39;No file path (self._path_seg_image) provided.&#39;)
+    
+    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):
+        &#34;&#34;&#34;
+        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
+            &lt; `min_n_pixels` then there is no object in the image.  
+        Exception
+            If no `_seg_image` and no `label_idx` then we don&#39;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. 
+        &#34;&#34;&#34;        
+        # 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 &lt; min_n_pixels:
+            raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
+                                                                                                                               marching_cubes_threshold))
+        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+        self._mesh = create_surface_mesh(self._seg_image,
+                                         self._label_idx,
+                                         smooth_image_var,
+                                         loc_tmp_save=&#39;/tmp&#39;,
+                                         tmp_filename=tmp_filename,
+                                         mc_threshold=marching_cubes_threshold,
+                                         filter_binary_image=smooth_image
+                                         )
+        safely_delete_tmp_file(&#39;/tmp&#39;,
+                               tmp_filename)
+    
+    def save_mesh(self,
+                  filepath):
+        &#34;&#34;&#34;
+        Save the surface mesh from this class to disk. 
+
+        Parameters
+        ----------
+        filepath : str
+            Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
+        &#34;&#34;&#34;        
+        io.write_vtk(self._mesh, filepath)
+
+    def resample_surface(self,
+                         subdivisions=2,
+                         clusters=10000
+                         ):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
+
+    def apply_transform_to_mesh(self,
+                                transform=None,
+                                transformer=None,
+                                save_transform=True):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        if (transform is not None) &amp; (transformer is None):
+            transformer = vtk.vtkTransformPolyDataFilter()
+            transformer.SetTransform(transform)
+
+        elif (transform is None) &amp; (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(&#39;No transform or transformer provided&#39;)
+
+    def reverse_most_recent_transform(self):
+        &#34;&#34;&#34;
+        Function to undo the most recent transformation stored in self._list_applied_transforms
+        &#34;&#34;&#34;
+        transform = self._list_applied_transforms.pop()
+        transform.Inverse()
+        self.apply_transform_to_mesh(transform=transform, save_transform=False)
+
+    def reverse_all_transforms(self):
+        &#34;&#34;&#34;
+        Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
+        &#34;&#34;&#34;
+        while len(self._list_applied_transforms) &gt; 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
+    ):  
+        &#34;&#34;&#34;
+        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_
+        &#34;&#34;&#34;
+        # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+        if (as_source is True) &amp; (apply_transform_to_mesh is True):
+            self._mesh = source_transformed_to_target
+        
+        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
+        if (as_source is False) or ((as_source is True) &amp; (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=&#39;similarity&#39;
+
+    ):
+        &#34;&#34;&#34;
+        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 &#39;similarity&#39; (similarity, rigid, or affine)
+
+        Returns
+        -------
+        _type_
+            _description_
+        &#34;&#34;&#34;
+
+        if (return_transform is True) &amp; (return_transformed_mesh is True):
+            raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
+
+        if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
+            other_mesh = other_mesh.mesh
+
+        # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+        if (as_source is True) &amp; (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 &amp; want to return mesh, then return it.  
+        elif (as_source is False) &amp; (return_transformed_mesh is True):
+            return apply_transform(source=source, transform=icp_transform)
+
+        else:
+            raise Exception(&#39;Nothing to return from rigid registration.&#39;)
+
+    def copy_scalars_from_other_mesh_to_currect(
+        self,
+        other_mesh,
+        new_scalars_name=&#39;scalars_from_other_mesh&#39;,
+        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,
+    ):
+        &#34;&#34;&#34;
+        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 &#39;scalars_from_other_mesh&#39;
+        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
+        &#34;&#34;&#34;
+        if type(other_mesh) is Mesh:
+            other_mesh = other_mesh.mesh
+        elif type(other_mesh) is vtk.vtkPolyData:
+            pass
+        else:
+            raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
+
+        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) &amp; (weighted_avg is True):
+            if transferred_scalars.shape[1] &gt; 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):
+        &#34;&#34;&#34;
+        Return the `_seg_image` object
+
+        Returns
+        -------
+        SimpleITK.Image
+            Segmentation image used to build the surface mesh
+        &#34;&#34;&#34;        
+        return self._seg_image
+
+    @seg_image.setter
+    def seg_image(self, new_seg_image):
+        &#34;&#34;&#34;
+        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__`
+        &#34;&#34;&#34;        
+        self._seg_image = new_seg_image
+
+    @property
+    def mesh(self):
+        &#34;&#34;&#34;
+        Return the `_mesh` object
+
+        Returns
+        -------
+        vtk.vtkPolyData
+            The main mesh of this class. 
+        &#34;&#34;&#34;        
+        return self._mesh
+
+    @mesh.setter
+    def mesh(self, new_mesh):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._mesh = new_mesh
+    
+    @property
+    def point_coords(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return get_mesh_physical_point_coords(self._mesh)
+    
+    @point_coords.setter
+    def point_coords(self, new_point_coords):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        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):
+        &#34;&#34;&#34;
+        Convenience function to get the `path_seg_image`
+
+        Returns
+        -------
+        str
+            Path to the segmentation image
+        &#34;&#34;&#34;        
+        return self._path_seg_image
+    
+    @path_seg_image.setter
+    def path_seg_image(self, new_path_seg_image):
+        &#34;&#34;&#34;
+        Convenience function to set the `path_seg_image`
+
+        Parameters
+        ----------
+        new_path_seg_image : str
+            String to where segmentation image that should be loaded is. 
+        &#34;&#34;&#34;        
+        self._path_seg_image = new_path_seg_image
+    
+    @property
+    def label_idx(self):
+        &#34;&#34;&#34;
+        Convenience function to get `label_idx`
+
+        Returns
+        -------
+        int
+            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
+        &#34;&#34;&#34;        
+        return self._label_idx
+    
+    @label_idx.setter
+    def label_idx(self, new_label_idx):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._label_idx = new_label_idx
+
+    @property
+    def min_n_pixels(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._min_n_pixels
+    
+    @min_n_pixels.setter
+    def min_n_pixels(self, new_min_n_pixels):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._min_n_pixels = new_min_n_pixels
+
+    @property
+    def list_applied_transforms(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._list_applied_transforms
+
+
+class CartilageMesh(Mesh):
+    &#34;&#34;&#34;
+    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
+    ----------
+
+    &#34;&#34;&#34;
+
+    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):
+    &#34;&#34;&#34;
+    Class to create, store, and process bone meshes
+
+    Intention is that this class includes functions to process other data &amp; assign it to the bone surface.
+    It might be possible that instead this class &amp; 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. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
+        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
+    ----------
+
+    &#34;&#34;&#34;
+
+    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=&#39;femur&#39;,
+                 ):
+        &#34;&#34;&#34;
+        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. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
+            Patella is not an option because we do not need to crop for the patella. 
+        &#34;&#34;&#34;        
+        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
+                    ):
+        &#34;&#34;&#34;
+        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 &amp; bone is not femur or tibia, then raise an error. 
+        Exception
+            If the total number of pixels segmentated (`n_pixels_labelled`) is
+            &lt; `min_n_pixels` then there is no object in the image.  
+        Exception
+            If no `_seg_image` and no `label_idx` then we don&#39;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. 
+        &#34;&#34;&#34;     
+        
+        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 ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
+            if &#39;femur&#39; in self._bone:
+                bone_crop_distal = True
+            elif &#39;tibia&#39; in self._bone:
+                bone_crop_distal = False
+            else:
+                raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.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&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
+                          &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
+                          &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
+                )
+        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):
+        &#34;&#34;&#34;
+        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? 
+        &#34;&#34;&#34;        
+
+        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&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
+                    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
+                                 ):
+        &#34;&#34;&#34;
+        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 &amp;
+            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`)
+        &#34;&#34;&#34;        
+        # 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) &amp; (self._list_cartilage_labels is None):
+            raise Exception(&#39;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.&#39;)
+
+        # if cartilage meshes don&#39;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(&#39;thickness (mm)&#39;)
+        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):
+        &#34;&#34;&#34;
+        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 &amp;
+            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
+        &#34;&#34;&#34;        
+        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+        path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
+        # if self._bone == &#39;femur&#39;:
+        #     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(&#39;/tmp&#39;,
+                               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&#39;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(&#39;labels&#39;)
+        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):
+        &#34;&#34;&#34;
+        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 &amp;
+            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
+        &#34;&#34;&#34;        
+        print(&#39;Not yet implemented&#39;)
+        # if self._list_cartilage_meshes is None:
+        #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
+        #     # 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(&#39;NOT DONE!!!&#39;)
+        
+
+
+    def smooth_surface_scalars(self,
+                               smooth_only_cartilage=True,
+                               scalar_sigma=1.6986436005760381,  # This is a FWHM = 4
+                               scalar_array_name=&#39;thickness (mm)&#39;,
+                               scalar_array_idx=None,
+                               ):
+                               
+        &#34;&#34;&#34;
+        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 &amp; 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 &#39;thickness (mm)&#39;.
+        scalar_array_idx : int, optional
+            Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
+        &#34;&#34;&#34;
+        if smooth_only_cartilage is True:
+            loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 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):
+        &#34;&#34;&#34;
+        Convenience function to get the list of cartilage meshes
+
+        Returns
+        -------
+        list
+            A list of `CartilageMesh` objects associated with this bone
+        &#34;&#34;&#34;        
+        return self._list_cartilage_meshes
+    
+    @list_cartilage_meshes.setter
+    def list_cartilage_meshes(self, new_list_cartilage_meshes):
+        &#34;&#34;&#34;
+        Convenience function to set the list of cartilage meshes
+
+        Parameters
+        ----------
+        new_list_cartilage_meshes : list
+            A list of `CartilageMesh` objects associated with this bone
+        &#34;&#34;&#34;
+        if type(new_list_cartilage_meshes) is list:
+            for mesh in new_list_cartilage_meshes:
+                if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
+                    raise TypeError(&#39;Item in `list_cartilage_meshes` is not a `CartilageMesh`&#39;)
+        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):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._list_cartilage_labels
+    
+    @list_cartilage_labels.setter
+    def list_cartilage_labels(self, new_list_cartilage_labels):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        if type(new_list_cartilage_labels) == list:
+            for label in new_list_cartilage_labels:
+                if type(label) != int:
+                    raise TypeError(f&#39;Item in `list_cartilage_labels` is not a `int` - got {type(label)}&#39;)
+        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):
+        &#34;&#34;&#34;
+        Convenience function to get the value that `crop_percent` is set to. 
+
+        Returns
+        -------
+        float
+            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
+            when cropping - expressed as a proportion of the width. 
+        &#34;&#34;&#34;        
+        return self._crop_percent
+    
+    @crop_percent.setter
+    def crop_percent(self, new_crop_percent):
+        &#34;&#34;&#34;
+        Convenience function to set the value that `crop_percent` is set to. 
+
+        Parameters
+        ----------
+        new_crop_percent : float
+            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
+            when cropping - expressed as a proportion of the width. 
+        &#34;&#34;&#34;
+        if type(new_crop_percent) != float:
+            raise TypeError(f&#39;New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`&#39;)
+        self._crop_percent = new_crop_percent
+    
+    @property
+    def bone(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._bone
+
+    @bone.setter
+    def bone(self, new_bone):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        if type(new_bone) != str:
+            raise TypeError(f&#39;New bone provided is type {type(new_bone)} - expected `str`&#39;)   
+        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 &amp; assign it to the bone surface.
+It might be possible that instead this class &amp; 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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>float</code></dt>
+<dd>Percent of width to crop along long-axis of bone</dd>
+<dt><strong><code>bone</code></strong> :&ensp;<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> :&ensp;<code>list</code></dt>
+<dd>List of cartialge meshes assigned to this bone.</dd>
+<dt><strong><code>list_cartilage_labels</code></strong> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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):
+    &#34;&#34;&#34;
+    Class to create, store, and process bone meshes
+
+    Intention is that this class includes functions to process other data &amp; assign it to the bone surface.
+    It might be possible that instead this class &amp; 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. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
+        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
+    ----------
+
+    &#34;&#34;&#34;
+
+    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=&#39;femur&#39;,
+                 ):
+        &#34;&#34;&#34;
+        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. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
+            Patella is not an option because we do not need to crop for the patella. 
+        &#34;&#34;&#34;        
+        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
+                    ):
+        &#34;&#34;&#34;
+        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 &amp; bone is not femur or tibia, then raise an error. 
+        Exception
+            If the total number of pixels segmentated (`n_pixels_labelled`) is
+            &lt; `min_n_pixels` then there is no object in the image.  
+        Exception
+            If no `_seg_image` and no `label_idx` then we don&#39;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. 
+        &#34;&#34;&#34;     
+        
+        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 ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
+            if &#39;femur&#39; in self._bone:
+                bone_crop_distal = True
+            elif &#39;tibia&#39; in self._bone:
+                bone_crop_distal = False
+            else:
+                raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.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&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
+                          &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
+                          &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
+                )
+        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):
+        &#34;&#34;&#34;
+        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? 
+        &#34;&#34;&#34;        
+
+        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&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
+                    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
+                                 ):
+        &#34;&#34;&#34;
+        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 &amp;
+            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`)
+        &#34;&#34;&#34;        
+        # 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) &amp; (self._list_cartilage_labels is None):
+            raise Exception(&#39;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.&#39;)
+
+        # if cartilage meshes don&#39;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(&#39;thickness (mm)&#39;)
+        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):
+        &#34;&#34;&#34;
+        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 &amp;
+            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
+        &#34;&#34;&#34;        
+        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+        path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
+        # if self._bone == &#39;femur&#39;:
+        #     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(&#39;/tmp&#39;,
+                               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&#39;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(&#39;labels&#39;)
+        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):
+        &#34;&#34;&#34;
+        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 &amp;
+            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
+        &#34;&#34;&#34;        
+        print(&#39;Not yet implemented&#39;)
+        # if self._list_cartilage_meshes is None:
+        #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
+        #     # 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(&#39;NOT DONE!!!&#39;)
+        
+
+
+    def smooth_surface_scalars(self,
+                               smooth_only_cartilage=True,
+                               scalar_sigma=1.6986436005760381,  # This is a FWHM = 4
+                               scalar_array_name=&#39;thickness (mm)&#39;,
+                               scalar_array_idx=None,
+                               ):
+                               
+        &#34;&#34;&#34;
+        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 &amp; 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 &#39;thickness (mm)&#39;.
+        scalar_array_idx : int, optional
+            Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
+        &#34;&#34;&#34;
+        if smooth_only_cartilage is True:
+            loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 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):
+        &#34;&#34;&#34;
+        Convenience function to get the list of cartilage meshes
+
+        Returns
+        -------
+        list
+            A list of `CartilageMesh` objects associated with this bone
+        &#34;&#34;&#34;        
+        return self._list_cartilage_meshes
+    
+    @list_cartilage_meshes.setter
+    def list_cartilage_meshes(self, new_list_cartilage_meshes):
+        &#34;&#34;&#34;
+        Convenience function to set the list of cartilage meshes
+
+        Parameters
+        ----------
+        new_list_cartilage_meshes : list
+            A list of `CartilageMesh` objects associated with this bone
+        &#34;&#34;&#34;
+        if type(new_list_cartilage_meshes) is list:
+            for mesh in new_list_cartilage_meshes:
+                if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
+                    raise TypeError(&#39;Item in `list_cartilage_meshes` is not a `CartilageMesh`&#39;)
+        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):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._list_cartilage_labels
+    
+    @list_cartilage_labels.setter
+    def list_cartilage_labels(self, new_list_cartilage_labels):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        if type(new_list_cartilage_labels) == list:
+            for label in new_list_cartilage_labels:
+                if type(label) != int:
+                    raise TypeError(f&#39;Item in `list_cartilage_labels` is not a `int` - got {type(label)}&#39;)
+        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):
+        &#34;&#34;&#34;
+        Convenience function to get the value that `crop_percent` is set to. 
+
+        Returns
+        -------
+        float
+            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
+            when cropping - expressed as a proportion of the width. 
+        &#34;&#34;&#34;        
+        return self._crop_percent
+    
+    @crop_percent.setter
+    def crop_percent(self, new_crop_percent):
+        &#34;&#34;&#34;
+        Convenience function to set the value that `crop_percent` is set to. 
+
+        Parameters
+        ----------
+        new_crop_percent : float
+            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
+            when cropping - expressed as a proportion of the width. 
+        &#34;&#34;&#34;
+        if type(new_crop_percent) != float:
+            raise TypeError(f&#39;New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`&#39;)
+        self._crop_percent = new_crop_percent
+    
+    @property
+    def bone(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._bone
+
+    @bone.setter
+    def bone(self, new_bone):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        if type(new_bone) != str:
+            raise TypeError(f&#39;New bone provided is type {type(new_bone)} - expected `str`&#39;)   
+        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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    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 &gt; 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):
+    &#34;&#34;&#34;
+    Convenience function to get the value that `crop_percent` is set to. 
+
+    Returns
+    -------
+    float
+        Floating point &gt; 0.0 indicating how much of the length of the bone should be included
+        when cropping - expressed as a proportion of the width. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    Convenience function to get the list of cartilage meshes
+
+    Returns
+    -------
+    list
+        A list of `CartilageMesh` objects associated with this bone
+    &#34;&#34;&#34;        
+    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> :&ensp;<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> :&ensp;<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> :&ensp;<code>float</code>, optional</dt>
+<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
+outter shell), by default 10.0</dd>
+<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<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):
+    &#34;&#34;&#34;
+    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 &amp;
+        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
+    &#34;&#34;&#34;        
+    tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+    path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
+    # if self._bone == &#39;femur&#39;:
+    #     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(&#39;/tmp&#39;,
+                           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&#39;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(&#39;labels&#39;)
+    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> :&ensp;<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> :&ensp;<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> :&ensp;<code>float</code>, optional</dt>
+<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
+outter shell), by default 10.0</dd>
+<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<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):
+    &#34;&#34;&#34;
+    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 &amp;
+        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
+    &#34;&#34;&#34;        
+    print(&#39;Not yet implemented&#39;)
+    # if self._list_cartilage_meshes is None:
+    #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
+    #     # 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(&#39;NOT DONE!!!&#39;)</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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>float</code>, optional</dt>
+<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
+outter shell), by default 10.0</dd>
+<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<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
+                             ):
+    &#34;&#34;&#34;
+    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 &amp;
+        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`)
+    &#34;&#34;&#34;        
+    # 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) &amp; (self._list_cartilage_labels is None):
+        raise Exception(&#39;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.&#39;)
+
+    # if cartilage meshes don&#39;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(&#39;thickness (mm)&#39;)
+    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> :&ensp;<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> :&ensp;<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):
+    &#34;&#34;&#34;
+    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? 
+    &#34;&#34;&#34;        
+
+    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&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
+                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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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 &amp; 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
+&lt; <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
+                ):
+    &#34;&#34;&#34;
+    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 &amp; bone is not femur or tibia, then raise an error. 
+    Exception
+        If the total number of pixels segmentated (`n_pixels_labelled`) is
+        &lt; `min_n_pixels` then there is no object in the image.  
+    Exception
+        If no `_seg_image` and no `label_idx` then we don&#39;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. 
+    &#34;&#34;&#34;     
+    
+    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 ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
+        if &#39;femur&#39; in self._bone:
+            bone_crop_distal = True
+        elif &#39;tibia&#39; in self._bone:
+            bone_crop_distal = False
+        else:
+            raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.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&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
+                      &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
+                      &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
+            )
+    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> :&ensp;<code>bool</code>, optional</dt>
+<dd>Should we only smooth where there is cartialge &amp; ignore everywhere else, by default True</dd>
+<dt><strong><code>scalar_sigma</code></strong> :&ensp;<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> :&ensp;<code>str</code></dt>
+<dd>Name of scalar array to smooth, default 'thickness (mm)'.</dd>
+<dt><strong><code>scalar_array_idx</code></strong> :&ensp;<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=&#39;thickness (mm)&#39;,
+                           scalar_array_idx=None,
+                           ):
+                           
+    &#34;&#34;&#34;
+    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 &amp; 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 &#39;thickness (mm)&#39;.
+    scalar_array_idx : int, optional
+        Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
+    &#34;&#34;&#34;
+    if smooth_only_cartilage is True:
+        loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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):
+    &#34;&#34;&#34;
+    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
+    ----------
+
+    &#34;&#34;&#34;
+
+    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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>int</code>, optional</dt>
+<dd>Label of anatomy of interest, by default None</dd>
+<dt><strong><code>min_n_pixels</code></strong> :&ensp;<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:
+    &#34;&#34;&#34;
+    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
+    ----------
+
+    &#34;&#34;&#34;    
+    def __init__(self,
+                 mesh=None,
+                 seg_image=None,
+                 path_seg_image=None,
+                 label_idx=None,
+                 min_n_pixels=5000
+                 ):
+        &#34;&#34;&#34;
+        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
+        &#34;&#34;&#34;      
+        if type(mesh) in (str,): #accept path like objects?  
+            print(&#39;mesh string passed, loading mesh from disk&#39;)
+            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):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        # 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(&#39;No file path (self._path_seg_image) provided.&#39;)
+    
+    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):
+        &#34;&#34;&#34;
+        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
+            &lt; `min_n_pixels` then there is no object in the image.  
+        Exception
+            If no `_seg_image` and no `label_idx` then we don&#39;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. 
+        &#34;&#34;&#34;        
+        # 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 &lt; min_n_pixels:
+            raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
+                                                                                                                               marching_cubes_threshold))
+        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+        self._mesh = create_surface_mesh(self._seg_image,
+                                         self._label_idx,
+                                         smooth_image_var,
+                                         loc_tmp_save=&#39;/tmp&#39;,
+                                         tmp_filename=tmp_filename,
+                                         mc_threshold=marching_cubes_threshold,
+                                         filter_binary_image=smooth_image
+                                         )
+        safely_delete_tmp_file(&#39;/tmp&#39;,
+                               tmp_filename)
+    
+    def save_mesh(self,
+                  filepath):
+        &#34;&#34;&#34;
+        Save the surface mesh from this class to disk. 
+
+        Parameters
+        ----------
+        filepath : str
+            Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
+        &#34;&#34;&#34;        
+        io.write_vtk(self._mesh, filepath)
+
+    def resample_surface(self,
+                         subdivisions=2,
+                         clusters=10000
+                         ):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;
+        self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
+
+    def apply_transform_to_mesh(self,
+                                transform=None,
+                                transformer=None,
+                                save_transform=True):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        if (transform is not None) &amp; (transformer is None):
+            transformer = vtk.vtkTransformPolyDataFilter()
+            transformer.SetTransform(transform)
+
+        elif (transform is None) &amp; (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(&#39;No transform or transformer provided&#39;)
+
+    def reverse_most_recent_transform(self):
+        &#34;&#34;&#34;
+        Function to undo the most recent transformation stored in self._list_applied_transforms
+        &#34;&#34;&#34;
+        transform = self._list_applied_transforms.pop()
+        transform.Inverse()
+        self.apply_transform_to_mesh(transform=transform, save_transform=False)
+
+    def reverse_all_transforms(self):
+        &#34;&#34;&#34;
+        Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
+        &#34;&#34;&#34;
+        while len(self._list_applied_transforms) &gt; 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
+    ):  
+        &#34;&#34;&#34;
+        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_
+        &#34;&#34;&#34;
+        # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+        if (as_source is True) &amp; (apply_transform_to_mesh is True):
+            self._mesh = source_transformed_to_target
+        
+        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
+        if (as_source is False) or ((as_source is True) &amp; (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=&#39;similarity&#39;
+
+    ):
+        &#34;&#34;&#34;
+        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 &#39;similarity&#39; (similarity, rigid, or affine)
+
+        Returns
+        -------
+        _type_
+            _description_
+        &#34;&#34;&#34;
+
+        if (return_transform is True) &amp; (return_transformed_mesh is True):
+            raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
+
+        if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
+            other_mesh = other_mesh.mesh
+
+        # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+        if (as_source is True) &amp; (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 &amp; want to return mesh, then return it.  
+        elif (as_source is False) &amp; (return_transformed_mesh is True):
+            return apply_transform(source=source, transform=icp_transform)
+
+        else:
+            raise Exception(&#39;Nothing to return from rigid registration.&#39;)
+
+    def copy_scalars_from_other_mesh_to_currect(
+        self,
+        other_mesh,
+        new_scalars_name=&#39;scalars_from_other_mesh&#39;,
+        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,
+    ):
+        &#34;&#34;&#34;
+        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 &#39;scalars_from_other_mesh&#39;
+        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
+        &#34;&#34;&#34;
+        if type(other_mesh) is Mesh:
+            other_mesh = other_mesh.mesh
+        elif type(other_mesh) is vtk.vtkPolyData:
+            pass
+        else:
+            raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
+
+        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) &amp; (weighted_avg is True):
+            if transferred_scalars.shape[1] &gt; 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):
+        &#34;&#34;&#34;
+        Return the `_seg_image` object
+
+        Returns
+        -------
+        SimpleITK.Image
+            Segmentation image used to build the surface mesh
+        &#34;&#34;&#34;        
+        return self._seg_image
+
+    @seg_image.setter
+    def seg_image(self, new_seg_image):
+        &#34;&#34;&#34;
+        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__`
+        &#34;&#34;&#34;        
+        self._seg_image = new_seg_image
+
+    @property
+    def mesh(self):
+        &#34;&#34;&#34;
+        Return the `_mesh` object
+
+        Returns
+        -------
+        vtk.vtkPolyData
+            The main mesh of this class. 
+        &#34;&#34;&#34;        
+        return self._mesh
+
+    @mesh.setter
+    def mesh(self, new_mesh):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._mesh = new_mesh
+    
+    @property
+    def point_coords(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return get_mesh_physical_point_coords(self._mesh)
+    
+    @point_coords.setter
+    def point_coords(self, new_point_coords):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        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):
+        &#34;&#34;&#34;
+        Convenience function to get the `path_seg_image`
+
+        Returns
+        -------
+        str
+            Path to the segmentation image
+        &#34;&#34;&#34;        
+        return self._path_seg_image
+    
+    @path_seg_image.setter
+    def path_seg_image(self, new_path_seg_image):
+        &#34;&#34;&#34;
+        Convenience function to set the `path_seg_image`
+
+        Parameters
+        ----------
+        new_path_seg_image : str
+            String to where segmentation image that should be loaded is. 
+        &#34;&#34;&#34;        
+        self._path_seg_image = new_path_seg_image
+    
+    @property
+    def label_idx(self):
+        &#34;&#34;&#34;
+        Convenience function to get `label_idx`
+
+        Returns
+        -------
+        int
+            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
+        &#34;&#34;&#34;        
+        return self._label_idx
+    
+    @label_idx.setter
+    def label_idx(self, new_label_idx):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._label_idx = new_label_idx
+
+    @property
+    def min_n_pixels(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        return self._min_n_pixels
+    
+    @min_n_pixels.setter
+    def min_n_pixels(self, new_min_n_pixels):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        self._min_n_pixels = new_min_n_pixels
+
+    @property
+    def list_applied_transforms(self):
+        &#34;&#34;&#34;
+        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. 
+        &#34;&#34;&#34;        
+        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):
+    &#34;&#34;&#34;
+    Convenience function to get `label_idx`
+
+    Returns
+    -------
+    int
+        Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    Return the `_mesh` object
+
+    Returns
+    -------
+    vtk.vtkPolyData
+        The main mesh of this class. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    Convenience function to get the `path_seg_image`
+
+    Returns
+    -------
+    str
+        Path to the segmentation image
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    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):
+    &#34;&#34;&#34;
+    Return the `_seg_image` object
+
+    Returns
+    -------
+    SimpleITK.Image
+        Segmentation image used to build the surface mesh
+    &#34;&#34;&#34;        
+    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> :&ensp;<code>vtk.vtkTransform</code>, optional</dt>
+<dd>Transformation to apply to mesh, by default None</dd>
+<dt><strong><code>transformer</code></strong> :&ensp;<code>vtk.vtkTransformFilter</code>, optional</dt>
+<dd>Can supply transformFilter directly, by default None</dd>
+<dt><strong><code>save_transform</code></strong> :&ensp;<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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    if (transform is not None) &amp; (transformer is None):
+        transformer = vtk.vtkTransformPolyDataFilter()
+        transformer.SetTransform(transform)
+
+    elif (transform is None) &amp; (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(&#39;No transform or transformer provided&#39;)</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> :&ensp;<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> :&ensp;<code>str</code>, optional</dt>
+<dd>&nbsp;</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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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=&#39;scalars_from_other_mesh&#39;,
+    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,
+):
+    &#34;&#34;&#34;
+    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 &#39;scalars_from_other_mesh&#39;
+    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
+    &#34;&#34;&#34;
+    if type(other_mesh) is Mesh:
+        other_mesh = other_mesh.mesh
+    elif type(other_mesh) is vtk.vtkPolyData:
+        pass
+    else:
+        raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
+
+    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) &amp; (weighted_avg is True):
+        if transferred_scalars.shape[1] &gt; 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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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
+&lt; <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):
+    &#34;&#34;&#34;
+    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
+        &lt; `min_n_pixels` then there is no object in the image.  
+    Exception
+        If no `_seg_image` and no `label_idx` then we don&#39;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. 
+    &#34;&#34;&#34;        
+    # 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 &lt; min_n_pixels:
+        raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
+                                                                                                                           marching_cubes_threshold))
+    tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
+    self._mesh = create_surface_mesh(self._seg_image,
+                                     self._label_idx,
+                                     smooth_image_var,
+                                     loc_tmp_save=&#39;/tmp&#39;,
+                                     tmp_filename=tmp_filename,
+                                     mc_threshold=marching_cubes_threshold,
+                                     filter_binary_image=smooth_image
+                                     )
+    safely_delete_tmp_file(&#39;/tmp&#39;,
+                           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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<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
+):  
+    &#34;&#34;&#34;
+    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_
+    &#34;&#34;&#34;
+    # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+    if (as_source is True) &amp; (apply_transform_to_mesh is True):
+        self._mesh = source_transformed_to_target
+    
+    # curent mesh is target, or is source &amp; want to return mesh, then return it.  
+    if (as_source is False) or ((as_source is True) &amp; (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> :&ensp;<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):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;        
+    # 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(&#39;No file path (self._path_seg_image) provided.&#39;)</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> :&ensp;<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> :&ensp;<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
+                     ):
+    &#34;&#34;&#34;
+    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. 
+    &#34;&#34;&#34;
+    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):
+    &#34;&#34;&#34;
+    Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
+    &#34;&#34;&#34;
+    while len(self._list_applied_transforms) &gt; 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):
+    &#34;&#34;&#34;
+    Function to undo the most recent transformation stored in self._list_applied_transforms
+    &#34;&#34;&#34;
+    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> :&ensp;<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> :&ensp;<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> :&ensp;<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> :&ensp;<code>bool</code>, optional</dt>
+<dd>Should we return the registered mesh, by default False</dd>
+<dt><strong><code>max_n_iter</code></strong> :&ensp;<code>int</code>, optional</dt>
+<dd>Maximum number of iterations to perform, by default 100</dd>
+<dt><strong><code>n_landmarks</code></strong> :&ensp;<code>int</code>, optional</dt>
+<dd>Number of landmarks to use in registration, by default 1000</dd>
+<dt><strong><code>reg_mode</code></strong> :&ensp;<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=&#39;similarity&#39;
+
+):
+    &#34;&#34;&#34;
+    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 &#39;similarity&#39; (similarity, rigid, or affine)
+
+    Returns
+    -------
+    _type_
+        _description_
+    &#34;&#34;&#34;
+
+    if (return_transform is True) &amp; (return_transformed_mesh is True):
+        raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
+
+    if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
+        other_mesh = other_mesh.mesh
+
+    # Setup the source &amp; 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 &amp; apply_transform_to_mesh is true then replace current mesh. 
+    if (as_source is True) &amp; (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 &amp; want to return mesh, then return it.  
+    elif (as_source is False) &amp; (return_transformed_mesh is True):
+        return apply_transform(source=source, transform=icp_transform)
+
+    else:
+        raise Exception(&#39;Nothing to return from rigid registration.&#39;)</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> :&ensp;<code>str</code></dt>
+<dd>Location &amp; 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):
+    &#34;&#34;&#34;
+    Save the surface mesh from this class to disk. 
+
+    Parameters
+    ----------
+    filepath : str
+        Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
+    &#34;&#34;&#34;        
+    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>
\ No newline at end of file