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

Switch to unified view

a b/docs/mesh/meshes.html
1
<!doctype html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
6
<meta name="generator" content="pdoc 0.10.0" />
7
<title>pymskt.mesh.meshes API documentation</title>
8
<meta name="description" content="" />
9
<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>
10
<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>
11
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
12
<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>
13
<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>
14
<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>
15
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
16
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
17
</head>
18
<body>
19
<main>
20
<article id="content">
21
<header>
22
<h1 class="title">Module <code>pymskt.mesh.meshes</code></h1>
23
</header>
24
<section id="section-intro">
25
<details class="source">
26
<summary>
27
<span>Expand source code</span>
28
</summary>
29
<pre><code class="python">from logging import error
30
from posixpath import supports_unicode_filenames
31
import warnings
32
import numpy as np
33
import vtk
34
from pymskt.image.main import apply_transform_retain_array
35
from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy
36
import pyacvd
37
import pyvista as pv
38
import SimpleITK as sitk
39
import os
40
import random
41
import string
42
# import pyfocusr     # MAKE THIS AN OPTIONAL IMPORT? 
43
44
import pymskt
45
from pymskt.mesh import createMesh
46
from pymskt.utils import safely_delete_tmp_file, copy_image_transform_to_mesh
47
from pymskt.image import read_nrrd, crop_bone_based_on_width
48
from pymskt.mesh.utils import vtk_deep_copy
49
from pymskt.mesh.meshTools import (gaussian_smooth_surface_scalars, 
50
                                   get_mesh_physical_point_coords, 
51
                                   get_cartilage_properties_at_points,
52
                                   smooth_scalars_from_second_mesh_onto_base,
53
                                   transfer_mesh_scalars_get_weighted_average_n_closest,
54
                                   resample_surface
55
                                   )
56
from pymskt.mesh.createMesh import create_surface_mesh
57
from pymskt.mesh.meshTransform import (SitkVtkTransformer, 
58
                                       get_versor_from_transform, 
59
                                       break_versor_into_center_rotate_translate_transforms,
60
                                       apply_transform)
61
from pymskt.mesh.meshRegistration import non_rigidly_register, get_icp_transform
62
import pymskt.mesh.io as io
63
64
class Mesh:
65
    &#34;&#34;&#34;
66
    An object to contain surface meshes for musculoskeletal anatomy. Includes helper
67
    functions to build surface meshes, to process them, and to save them. 
68
69
    Parameters
70
    ----------
71
    mesh : vtk.vtkPolyData, optional
72
        vtkPolyData object that is basis of surface mesh, by default None
73
    seg_image : SimpleITK.Image, optional
74
        Segmentation image that can be used to create surface mesh - used 
75
        instead of mesh, by default None
76
    path_seg_image : str, optional
77
        Path to a medical image (.nrrd) to load and create mesh from, 
78
        by default None
79
    label_idx : int, optional
80
        Label of anatomy of interest, by default None
81
    min_n_pixels : int, optional
82
        All islands smaller than this size are dropped, by default 5000
83
84
85
    Attributes
86
    ----------
87
    _mesh : vtk.vtkPolyData
88
        Item passed from __init__, or created during life of class. 
89
        This is the main surface mesh of this class. 
90
    _seg_image : SimpleITK.Image
91
        Segmentation image that can be used to create mesh. This is optional.
92
    path_seg_image : str
93
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
94
        and then creat surface mesh `_mesh` 
95
    label_idx : int
96
        Integer of anatomy to create surface mesh from `_seg_image`
97
    min_n_pixels : int
98
        Minimum number of pixels for an isolated island of a segmentation to be
99
        retained
100
    list_applied_transforms : list
101
        A list of transformations applied to a surface mesh. 
102
        This list allows for undoing of most recent transform, or undoing
103
        all of them by iterating over the list in reverse. 
104
105
    Methods
106
    ----------
107
108
    &#34;&#34;&#34;    
109
    def __init__(self,
110
                 mesh=None,
111
                 seg_image=None,
112
                 path_seg_image=None,
113
                 label_idx=None,
114
                 min_n_pixels=5000
115
                 ):
116
        &#34;&#34;&#34;
117
        Initialize Mesh class
118
119
        Parameters
120
        ----------
121
        mesh : vtk.vtkPolyData, optional
122
            vtkPolyData object that is basis of surface mesh, by default None
123
        seg_image : SimpleITK.Image, optional
124
            Segmentation image that can be used to create surface mesh - used 
125
            instead of mesh, by default None
126
        path_seg_image : str, optional
127
            Path to a medical image (.nrrd) to load and create mesh from, 
128
            by default None
129
        label_idx : int, optional
130
            Label of anatomy of interest, by default None
131
        min_n_pixels : int, optional
132
            All islands smaller than this size are dropped, by default 5000
133
        &#34;&#34;&#34;      
134
        if type(mesh) in (str,): #accept path like objects?  
135
            print(&#39;mesh string passed, loading mesh from disk&#39;)
136
            self._mesh = io.read_vtk(mesh)
137
        else:
138
            self._mesh = mesh
139
        self._seg_image = seg_image
140
        self._path_seg_image = path_seg_image
141
        self._label_idx = label_idx
142
        self._min_n_pixels = min_n_pixels
143
144
        self._list_applied_transforms = []
145
146
    def read_seg_image(self,
147
                       path_seg_image=None):
148
        &#34;&#34;&#34;
149
        Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
150
151
        Parameters
152
        ----------
153
        path_seg_image : str, optional
154
            Path to the medical image file to be loaded in, by default None
155
156
        Raises
157
        ------
158
        Exception
159
            If path_seg_image does not exist, exception is raised. 
160
        &#34;&#34;&#34;        
161
        # If passing new location/seg image name, then update variables. 
162
        if path_seg_image is not None:
163
            self._path_seg_image = path_seg_image
164
        
165
        # If seg image location / name exist, then load image else raise exception
166
        if (self._path_seg_image is not None):
167
            self._seg_image = sitk.ReadImage(self._path_seg_image)
168
        else:
169
            raise Exception(&#39;No file path (self._path_seg_image) provided.&#39;)
170
    
171
    def create_mesh(self,
172
                    smooth_image=True,
173
                    smooth_image_var=0.3125 / 2,
174
                    marching_cubes_threshold=0.5,
175
                    label_idx=None,
176
                    min_n_pixels=None):
177
        &#34;&#34;&#34;
178
        Create a surface mesh from the classes `_seg_image`. If `_seg_image`
179
        does not exist, then read it in using `read_seg_image`. 
180
181
        Parameters
182
        ----------
183
        smooth_image : bool, optional
184
            Should the `_seg_image` be gaussian filtered, by default True
185
        smooth_image_var : float, optional
186
            Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
187
        marching_cubes_threshold : float, optional
188
            Threshold contour level to create surface mesh on, by default 0.5
189
        label_idx : int, optional
190
            Label value / index to create mesh from, by default None
191
        min_n_pixels : int, optional
192
            Minimum number of continuous pixels to include segmentation island
193
            in the surface mesh creation, by default None
194
195
        Raises
196
        ------
197
        Exception
198
            If the total number of pixels segmentated (`n_pixels_labelled`) is
199
            &lt; `min_n_pixels` then there is no object in the image.  
200
        Exception
201
            If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
202
            surface mesh from. 
203
        Exception
204
            If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
205
        &#34;&#34;&#34;        
206
        # allow assigning label idx during mesh creation step. 
207
        if label_idx is not None:
208
            self._label_idx = label_idx
209
        
210
        if self._seg_image is None:
211
            self.read_seg_image()
212
        
213
        # Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
214
        if min_n_pixels is None: min_n_pixels = self._min_n_pixels
215
        seg_view = sitk.GetArrayViewFromImage(self._seg_image)
216
        n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
217
218
        if n_pixels_labelled &lt; min_n_pixels:
219
            raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
220
                                                                                                                               marching_cubes_threshold))
221
        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
222
        self._mesh = create_surface_mesh(self._seg_image,
223
                                         self._label_idx,
224
                                         smooth_image_var,
225
                                         loc_tmp_save=&#39;/tmp&#39;,
226
                                         tmp_filename=tmp_filename,
227
                                         mc_threshold=marching_cubes_threshold,
228
                                         filter_binary_image=smooth_image
229
                                         )
230
        safely_delete_tmp_file(&#39;/tmp&#39;,
231
                               tmp_filename)
232
    
233
    def save_mesh(self,
234
                  filepath):
235
        &#34;&#34;&#34;
236
        Save the surface mesh from this class to disk. 
237
238
        Parameters
239
        ----------
240
        filepath : str
241
            Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
242
        &#34;&#34;&#34;        
243
        io.write_vtk(self._mesh, filepath)
244
245
    def resample_surface(self,
246
                         subdivisions=2,
247
                         clusters=10000
248
                         ):
249
        &#34;&#34;&#34;
250
        Resample a surface mesh using the ACVD algorithm: 
251
        Version used: 
252
        - https://github.com/pyvista/pyacvd
253
        Original version w/ more references: 
254
        - https://github.com/valette/ACVD
255
256
        Parameters
257
        ----------
258
        subdivisions : int, optional
259
            Subdivide the mesh to have more points before clustering, by default 2
260
            Probably not necessary for very dense meshes.
261
        clusters : int, optional
262
            The number of clusters (points/vertices) to create during resampling 
263
            surafce, by default 10000
264
            - This is not exact, might have slight differences. 
265
        &#34;&#34;&#34;
266
        self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
267
268
    def apply_transform_to_mesh(self,
269
                                transform=None,
270
                                transformer=None,
271
                                save_transform=True):
272
        &#34;&#34;&#34;
273
        Apply a transformation to the surface mesh. 
274
275
        Parameters
276
        ----------
277
        transform : vtk.vtkTransform, optional
278
            Transformation to apply to mesh, by default None
279
        transformer : vtk.vtkTransformFilter, optional
280
            Can supply transformFilter directly, by default None
281
        save_transform : bool, optional
282
            Should transform be saved to list of applied transforms, by default True
283
284
        Raises
285
        ------
286
        Exception
287
            No `transform` or `transformer` supplied - have not transformation
288
            to apply. 
289
        &#34;&#34;&#34;        
290
        if (transform is not None) &amp; (transformer is None):
291
            transformer = vtk.vtkTransformPolyDataFilter()
292
            transformer.SetTransform(transform)
293
294
        elif (transform is None) &amp; (transformer is not None):
295
            transform = transformer.GetTransform()
296
297
        if transformer is not None:
298
            transformer.SetInputData(self._mesh)
299
            transformer.Update()
300
            self._mesh = vtk_deep_copy(transformer.GetOutput())
301
302
            if save_transform is True:
303
                self._list_applied_transforms.append(transform)
304
                
305
        else:
306
            raise Exception(&#39;No transform or transformer provided&#39;)
307
308
    def reverse_most_recent_transform(self):
309
        &#34;&#34;&#34;
310
        Function to undo the most recent transformation stored in self._list_applied_transforms
311
        &#34;&#34;&#34;
312
        transform = self._list_applied_transforms.pop()
313
        transform.Inverse()
314
        self.apply_transform_to_mesh(transform=transform, save_transform=False)
315
316
    def reverse_all_transforms(self):
317
        &#34;&#34;&#34;
318
        Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
319
        &#34;&#34;&#34;
320
        while len(self._list_applied_transforms) &gt; 0:
321
            self.reverse_most_recent_transform()
322
    
323
    def non_rigidly_register(
324
        self,
325
        other_mesh,
326
        as_source=True,
327
        apply_transform_to_mesh=True,
328
        return_transformed_mesh=False,
329
        **kwargs
330
    ):  
331
        &#34;&#34;&#34;
332
        Function to perform non rigid registration between this mesh and another mesh. 
333
334
        Parameters
335
        ----------
336
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
337
            Other mesh to use in registration process
338
        as_source : bool, optional
339
            Should the current mesh (in this object) be the source or the target, by default True
340
        apply_transform_to_mesh : bool, optional
341
            If as_source is True should we apply transformation to internal mesh, by default True
342
        return_transformed_mesh : bool, optional
343
            Should we return the registered mesh, by default False
344
345
        Returns
346
        -------
347
        _type_
348
            _description_
349
        &#34;&#34;&#34;
350
        # Setup the source &amp; target meshes based on `as_source``
351
        if as_source is True:
352
            source = self._mesh
353
            target = other_mesh
354
        elif as_source is False:
355
            source = other_mesh
356
            target = self._mesh
357
358
        # Get registered mesh (source to target)
359
        source_transformed_to_target = non_rigidly_register(
360
                target_mesh=target,
361
                source_mesh=source,
362
                **kwargs
363
            ) 
364
365
        # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
366
        if (as_source is True) &amp; (apply_transform_to_mesh is True):
367
            self._mesh = source_transformed_to_target
368
        
369
        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
370
        if (as_source is False) or ((as_source is True) &amp; (return_transformed_mesh is True)):
371
            return source_transformed_to_target
372
373
    def rigidly_register(
374
        self,
375
        other_mesh,
376
        as_source=True,
377
        apply_transform_to_mesh=True,
378
        return_transformed_mesh=False,
379
        return_transform=False,
380
        max_n_iter=100,
381
        n_landmarks=1000,
382
        reg_mode=&#39;similarity&#39;
383
384
    ):
385
        &#34;&#34;&#34;
386
        Function to perform rigid registration between this mesh and another mesh. 
387
388
        Parameters
389
        ----------
390
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
391
            Other mesh to use in registration process
392
        as_source : bool, optional
393
            Should the current mesh (in this object) be the source or the target, by default True
394
        apply_transform_to_mesh : bool, optional
395
            If as_source is True should we apply transformation to internal mesh, by default True
396
        return_transformed_mesh : bool, optional
397
            Should we return the registered mesh, by default False
398
        max_n_iter : int, optional
399
            Maximum number of iterations to perform, by default 100
400
        n_landmarks : int, optional
401
            Number of landmarks to use in registration, by default 1000
402
        reg_mode : str, optional
403
            Mode of registration to use, by default &#39;similarity&#39; (similarity, rigid, or affine)
404
405
        Returns
406
        -------
407
        _type_
408
            _description_
409
        &#34;&#34;&#34;
410
411
        if (return_transform is True) &amp; (return_transformed_mesh is True):
412
            raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
413
414
        if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
415
            other_mesh = other_mesh.mesh
416
417
        # Setup the source &amp; target meshes based on `as_source``
418
        if as_source is True:
419
            source = self._mesh
420
            target = other_mesh
421
        elif as_source is False:
422
            source = other_mesh
423
            target = self._mesh
424
        
425
        icp_transform = get_icp_transform(
426
            source=source,
427
            target=target,
428
            max_n_iter=max_n_iter,
429
            n_landmarks=n_landmarks,
430
            reg_mode=reg_mode
431
        )
432
433
        # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
434
        if (as_source is True) &amp; (apply_transform_to_mesh is True):
435
            self.apply_transform_to_mesh(transform=icp_transform)
436
437
            if return_transformed_mesh is True:
438
                return self._mesh
439
            
440
            elif return_transform is True:
441
                return icp_transform
442
        
443
        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
444
        elif (as_source is False) &amp; (return_transformed_mesh is True):
445
            return apply_transform(source=source, transform=icp_transform)
446
447
        else:
448
            raise Exception(&#39;Nothing to return from rigid registration.&#39;)
449
450
    def copy_scalars_from_other_mesh_to_currect(
451
        self,
452
        other_mesh,
453
        new_scalars_name=&#39;scalars_from_other_mesh&#39;,
454
        weighted_avg=True,                  # Use weighted average, otherwise guassian smooth transfer
455
        n_closest=3,
456
        sigma=1.,
457
        idx_coords_to_smooth_base=None,
458
        idx_coords_to_smooth_other=None,
459
        set_non_smoothed_scalars_to_zero=True,
460
    ):
461
        &#34;&#34;&#34;
462
        Convenience function to enable easy transfer scalars from another mesh to the current. 
463
        Can use either a gaussian smoothing function, or transfer using nearest neighbours. 
464
465
        ** This function requires that the `other_mesh` is non-rigidly registered to the surface
466
            of the mesh inside of this class. Or rigidly registered but using the same anatomy that
467
            VERY closely matches. Otherwise, the transfered scalars will be bad.  
468
469
        Parameters
470
        ----------
471
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
472
            Mesh we want to copy 
473
        new_scalars_name : str, optional
474
           What to name the scalars being transfered to this current mesh, by default &#39;scalars_from_other_mesh&#39;
475
        weighted_avg : bool, optional
476
            Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
477
        n_closest : int, optional
478
            If `weighted_avg` True, the number of nearest neighbours to use, by default 3
479
        sigma : float, optional
480
            If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
481
        idx_coords_to_smooth_base : list, optional
482
            If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
483
        idx_coords_to_smooth_other : list, optional
484
            If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
485
        set_non_smoothed_scalars_to_zero : bool, optional
486
            Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
487
        &#34;&#34;&#34;
488
        if type(other_mesh) is Mesh:
489
            other_mesh = other_mesh.mesh
490
        elif type(other_mesh) is vtk.vtkPolyData:
491
            pass
492
        else:
493
            raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
494
495
        if weighted_avg is True:
496
            transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
497
                self._mesh,
498
                other_mesh,
499
                n=n_closest
500
            )
501
        else:
502
            transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
503
                self._mesh,
504
                other_mesh,
505
                sigma=sigma,
506
                idx_coords_to_smooth_base=idx_coords_to_smooth_base,
507
                idx_coords_to_smooth_second=idx_coords_to_smooth_other,
508
                set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
509
            )
510
        if (new_scalars_name is None) &amp; (weighted_avg is True):
511
            if transferred_scalars.shape[1] &gt; 1:
512
                n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
513
                array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
514
                for idx, array_name in enumerate(array_names):
515
                    vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
516
                    vtk_transferred_scalars.SetName(array_name)
517
                    self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
518
                return
519
520
        vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
521
        vtk_transferred_scalars.SetName(new_scalars_name)
522
        self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
523
524
    @property
525
    def seg_image(self):
526
        &#34;&#34;&#34;
527
        Return the `_seg_image` object
528
529
        Returns
530
        -------
531
        SimpleITK.Image
532
            Segmentation image used to build the surface mesh
533
        &#34;&#34;&#34;        
534
        return self._seg_image
535
536
    @seg_image.setter
537
    def seg_image(self, new_seg_image):
538
        &#34;&#34;&#34;
539
        Set the `_seg_image` of the class to be the inputted `new_seg_image`. 
540
541
        Parameters
542
        ----------
543
        new_seg_image : SimpleITK.Image
544
            New image to use for creating surface mesh. This can be used to provide image to
545
            class if it was not provided during `__init__`
546
        &#34;&#34;&#34;        
547
        self._seg_image = new_seg_image
548
549
    @property
550
    def mesh(self):
551
        &#34;&#34;&#34;
552
        Return the `_mesh` object
553
554
        Returns
555
        -------
556
        vtk.vtkPolyData
557
            The main mesh of this class. 
558
        &#34;&#34;&#34;        
559
        return self._mesh
560
561
    @mesh.setter
562
    def mesh(self, new_mesh):
563
        &#34;&#34;&#34;
564
        Set the `_mesh` of the class to be the inputted `new_mesh`
565
566
        Parameters
567
        ----------
568
        new_mesh : vtk.vtkPolyData
569
            New mesh for this class - or a method to provide a mesh to the class
570
            after `__init__` has already been run. 
571
        &#34;&#34;&#34;        
572
        self._mesh = new_mesh
573
    
574
    @property
575
    def point_coords(self):
576
        &#34;&#34;&#34;
577
        Convenience function to return the vertices (point coordinates) for the surface mesh. 
578
579
        Returns
580
        -------
581
        numpy.ndarray
582
            Mx3 numpy array containing the x/y/z position of each vertex of the mesh. 
583
        &#34;&#34;&#34;        
584
        return get_mesh_physical_point_coords(self._mesh)
585
    
586
    @point_coords.setter
587
    def point_coords(self, new_point_coords):
588
        &#34;&#34;&#34;
589
        Convenience function to change/update the vertices/points locations
590
591
        Parameters
592
        ----------
593
        new_point_coords : numpy.ndarray
594
            n_points X 3 numpy array to replace exisiting point coordinate locations
595
            This can be used to easily/quickly update the x/y/z position of a set of points on a surface mesh. 
596
            The `new_point_coords` must include the same number of points as the mesh contains. 
597
        &#34;&#34;&#34;        
598
        orig_point_coords = get_mesh_physical_point_coords(self._mesh)
599
        if new_point_coords.shape == orig_point_coords.shape:
600
            self._mesh.GetPoints().SetData(numpy_to_vtk(new_point_coords))
601
602
    
603
    @property
604
    def path_seg_image(self):
605
        &#34;&#34;&#34;
606
        Convenience function to get the `path_seg_image`
607
608
        Returns
609
        -------
610
        str
611
            Path to the segmentation image
612
        &#34;&#34;&#34;        
613
        return self._path_seg_image
614
    
615
    @path_seg_image.setter
616
    def path_seg_image(self, new_path_seg_image):
617
        &#34;&#34;&#34;
618
        Convenience function to set the `path_seg_image`
619
620
        Parameters
621
        ----------
622
        new_path_seg_image : str
623
            String to where segmentation image that should be loaded is. 
624
        &#34;&#34;&#34;        
625
        self._path_seg_image = new_path_seg_image
626
    
627
    @property
628
    def label_idx(self):
629
        &#34;&#34;&#34;
630
        Convenience function to get `label_idx`
631
632
        Returns
633
        -------
634
        int
635
            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
636
        &#34;&#34;&#34;        
637
        return self._label_idx
638
    
639
    @label_idx.setter
640
    def label_idx(self, new_label_idx):
641
        &#34;&#34;&#34;
642
        Convenience function to set `label_idx`
643
644
        Parameters
645
        ----------
646
        new_label_idx : int
647
            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
648
        &#34;&#34;&#34;        
649
        self._label_idx = new_label_idx
650
651
    @property
652
    def min_n_pixels(self):
653
        &#34;&#34;&#34;
654
        Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh. 
655
656
        Returns
657
        -------
658
        int
659
            Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised. 
660
        &#34;&#34;&#34;        
661
        return self._min_n_pixels
662
    
663
    @min_n_pixels.setter
664
    def min_n_pixels(self, new_min_n_pixels):
665
        &#34;&#34;&#34;
666
        Convenience function to set the minimum number of pixels for a segmentation region to be created as a mesh. 
667
668
        Parameters
669
        ----------
670
        new_min_n_pixels : int
671
            Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised. 
672
        &#34;&#34;&#34;        
673
        self._min_n_pixels = new_min_n_pixels
674
675
    @property
676
    def list_applied_transforms(self):
677
        &#34;&#34;&#34;
678
        Convenience function to get the list of transformations that have been applied to this mesh. 
679
680
        Returns
681
        -------
682
        list
683
            List of vtk.vtkTransform objects that have been applied to the current mesh. 
684
        &#34;&#34;&#34;        
685
        return self._list_applied_transforms
686
687
688
class CartilageMesh(Mesh):
689
    &#34;&#34;&#34;
690
    Class to create, store, and process cartilage meshes
691
692
    Parameters
693
    ----------
694
    mesh : vtk.vtkPolyData, optional
695
        vtkPolyData object that is basis of surface mesh, by default None
696
    seg_image : SimpleITK.Image, optional
697
        Segmentation image that can be used to create surface mesh - used 
698
        instead of mesh, by default None
699
    path_seg_image : str, optional
700
        Path to a medical image (.nrrd) to load and create mesh from, 
701
        by default None
702
    label_idx : int, optional
703
        Label of anatomy of interest, by default None
704
    min_n_pixels : int, optional
705
        All islands smaller than this size are dropped, by default 5000
706
707
708
    Attributes
709
    ----------
710
    _mesh : vtk.vtkPolyData
711
        Item passed from __init__, or created during life of class. 
712
        This is the main surface mesh of this class. 
713
    _seg_image : SimpleITK.Image
714
        Segmentation image that can be used to create mesh. This is optional.
715
    path_seg_image : str
716
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
717
        and then creat surface mesh `_mesh` 
718
    label_idx : int
719
        Integer of anatomy to create surface mesh from `_seg_image`
720
    min_n_pixels : int
721
        Minimum number of pixels for an isolated island of a segmentation to be
722
        retained
723
    list_applied_transforms : list
724
        A list of transformations applied to a surface mesh. 
725
        This list allows for undoing of most recent transform, or undoing
726
        all of them by iterating over the list in reverse. 
727
728
    Methods
729
    ----------
730
731
    &#34;&#34;&#34;
732
733
    def __init__(self,
734
                 mesh=None,
735
                 seg_image=None,
736
                 path_seg_image=None,
737
                 label_idx=None,
738
                 min_n_pixels=1000
739
                 ):
740
        super().__init__(mesh=mesh,
741
                         seg_image=seg_image,
742
                         path_seg_image=path_seg_image,
743
                         label_idx=label_idx,
744
                         min_n_pixels=min_n_pixels)
745
746
747
class BoneMesh(Mesh):
748
    &#34;&#34;&#34;
749
    Class to create, store, and process bone meshes
750
751
    Intention is that this class includes functions to process other data &amp; assign it to the bone surface.
752
    It might be possible that instead this class &amp; a cartilage class or, this class and image data etc. are
753
    provided to another function or class that does those analyses.
754
755
    Parameters
756
    ----------
757
    mesh : vtk.vtkPolyData, optional
758
        vtkPolyData object that is basis of surface mesh, by default None
759
    seg_image : SimpleITK.Image, optional
760
        Segmentation image that can be used to create surface mesh - used 
761
        instead of mesh, by default None
762
    path_seg_image : str, optional
763
        Path to a medical image (.nrrd) to load and create mesh from, 
764
        by default None
765
    label_idx : int, optional
766
        Label of anatomy of interest, by default None
767
    min_n_pixels : int, optional
768
        All islands smaller than this size are dropped, by default 5000
769
    list_cartilage_meshes : list, optional
770
        List object which contains 1+ `CartilageMesh` objects that wrap
771
        a vtk.vtkPolyData surface mesh of cartilage, by default None
772
    list_cartilage_labels : list, optional
773
        List of `int` values that represent the different cartilage
774
        regions of interest appropriate for a single bone, by default None
775
    crop_percent : float, optional
776
        Proportion value to crop long-axis of bone so it is proportional
777
        to the width of the bone for standardization purposes, by default 1.0
778
    bone : str, optional
779
        String indicating what bone is being analyzed so that cropping
780
        can be applied appropriatey. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
781
        Patella is not an option because we do not need to crop for the patella. 
782
783
784
    Attributes
785
    ----------
786
    _mesh : vtk.vtkPolyData
787
        Item passed from __init__, or created during life of class. 
788
        This is the main surface mesh of this class. 
789
    _seg_image : SimpleITK.Image
790
        Segmentation image that can be used to create mesh. This is optional.
791
    path_seg_image : str
792
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
793
        and then creat surface mesh `_mesh` 
794
    label_idx : int
795
        Integer of anatomy to create surface mesh from `_seg_image`
796
    min_n_pixels : int
797
        Minimum number of pixels for an isolated island of a segmentation to be
798
        retained
799
    list_applied_transforms : list
800
        A list of transformations applied to a surface mesh. 
801
        This list allows for undoing of most recent transform, or undoing
802
        all of them by iterating over the list in reverse.
803
    crop_percent : float
804
        Percent of width to crop along long-axis of bone
805
    bone : str
806
        A string indicating what bone is being represented by this class. 
807
    list_cartilage_meshes : list
808
        List of cartialge meshes assigned to this bone. 
809
    list_cartilage_labels : list
810
        List of cartilage labels for the `_seg_image` that are associated
811
        with this bone. 
812
813
    Methods
814
    ----------
815
816
    &#34;&#34;&#34;
817
818
    def __init__(self,
819
                 mesh=None,
820
                 seg_image=None,
821
                 path_seg_image=None,
822
                 label_idx=None,
823
                 min_n_pixels=5000,
824
                 list_cartilage_meshes=None,
825
                 list_cartilage_labels=None,
826
                 crop_percent=None,
827
                 bone=&#39;femur&#39;,
828
                 ):
829
        &#34;&#34;&#34;
830
        Class initialization
831
832
        Parameters
833
        ----------
834
        mesh : vtk.vtkPolyData, optional
835
            vtkPolyData object that is basis of surface mesh, by default None
836
        seg_image : SimpleITK.Image, optional
837
            Segmentation image that can be used to create surface mesh - used 
838
            instead of mesh, by default None
839
        path_seg_image : str, optional
840
            Path to a medical image (.nrrd) to load and create mesh from, 
841
            by default None
842
        label_idx : int, optional
843
            Label of anatomy of interest, by default None
844
        min_n_pixels : int, optional
845
            All islands smaller than this size are dropped, by default 5000
846
        list_cartilage_meshes : list, optional
847
            List object which contains 1+ `CartilageMesh` objects that wrap
848
            a vtk.vtkPolyData surface mesh of cartilage, by default None
849
        list_cartilage_labels : list, optional
850
            List of `int` values that represent the different cartilage
851
            regions of interest appropriate for a single bone, by default None
852
        crop_percent : float, optional
853
            Proportion value to crop long-axis of bone so it is proportional
854
            to the width of the bone for standardization purposes, by default 1.0
855
        bone : str, optional
856
            String indicating what bone is being analyzed so that cropping
857
            can be applied appropriatey. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
858
            Patella is not an option because we do not need to crop for the patella. 
859
        &#34;&#34;&#34;        
860
        self._crop_percent = crop_percent
861
        self._bone = bone
862
        self._list_cartilage_meshes = list_cartilage_meshes
863
        self._list_cartilage_labels = list_cartilage_labels
864
865
        super().__init__(mesh=mesh,
866
                         seg_image=seg_image,
867
                         path_seg_image=path_seg_image,
868
                         label_idx=label_idx,
869
                         min_n_pixels=min_n_pixels)
870
871
        
872
    def create_mesh(self, 
873
                    smooth_image=True, 
874
                    smooth_image_var=0.3125 / 2, 
875
                    marching_cubes_threshold=0.5, 
876
                    label_idx=None, 
877
                    min_n_pixels=None,
878
                    crop_percent=None
879
                    ):
880
        &#34;&#34;&#34;
881
        This is an extension of `Mesh.create_mesh` that enables cropping of bones. 
882
        Bones might need to be cropped (this isnt necessary for cartilage)
883
        So, adding this functionality to the processing steps before the bone mesh is created.
884
885
        All functionality, except for that relevant to `crop_percent` is the same as:
886
        `Mesh.create_mesh`. 
887
        
888
        Create a surface mesh from the classes `_seg_image`. If `_seg_image`
889
        does not exist, then read it in using `read_seg_image`. 
890
891
        Parameters
892
        ----------
893
        smooth_image : bool, optional
894
            Should the `_seg_image` be gaussian filtered, by default True
895
        smooth_image_var : float, optional
896
            Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
897
        marching_cubes_threshold : float, optional
898
            Threshold contour level to create surface mesh on, by default 0.5
899
        label_idx : int, optional
900
            Label value / index to create mesh from, by default None
901
        min_n_pixels : int, optional
902
            Minimum number of continuous pixels to include segmentation island
903
            in the surface mesh creation, by default None
904
        crop_percent : [type], optional
905
            [description], by default None
906
907
        Raises
908
        ------
909
        Exception
910
            If cropping &amp; bone is not femur or tibia, then raise an error. 
911
        Exception
912
            If the total number of pixels segmentated (`n_pixels_labelled`) is
913
            &lt; `min_n_pixels` then there is no object in the image.  
914
        Exception
915
            If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
916
            surface mesh from. 
917
        Exception
918
            If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
919
        &#34;&#34;&#34;     
920
        
921
        if self._seg_image is None:
922
            self.read_seg_image()
923
        
924
        # Bones might need to be cropped (this isnt necessary for cartilage)
925
        # So, adding this functionality to the processing steps before the bone mesh is created
926
        if crop_percent is not None:
927
            self._crop_percent = crop_percent
928
        if (self._crop_percent is not None) and ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
929
            if &#39;femur&#39; in self._bone:
930
                bone_crop_distal = True
931
            elif &#39;tibia&#39; in self._bone:
932
                bone_crop_distal = False
933
            else:
934
                raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.format(self._bone))
935
936
            self._seg_image = crop_bone_based_on_width(self._seg_image,
937
                                                       self._label_idx,
938
                                                       percent_width_to_crop_height=self._crop_percent,
939
                                                       bone_crop_distal=bone_crop_distal)
940
        elif self._crop_percent is not None:
941
            warnings.warn(f&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
942
                          &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
943
                          &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
944
                )
945
        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)
946
947
    def create_cartilage_meshes(self,
948
                                image_smooth_var_cart=0.3125 / 2,
949
                                marching_cubes_threshold=0.5):
950
        &#34;&#34;&#34;
951
        Helper function to create the list of cartilage meshes from the list of cartilage
952
        labels. 
953
954
        Parameters
955
        ----------
956
        image_smooth_var_cart : float
957
            Variance to smooth cartilage segmentations before finding surface using continuous
958
            marching cubes.             
959
        marching_cubes_threshold : float
960
            Threshold value to create cartilage surface at from segmentation images. 
961
962
        Notes
963
        -----
964
        ?? Should this function just be everything inside the for loop and then that 
965
        function gets called somewhere else? 
966
        &#34;&#34;&#34;        
967
968
        self._list_cartilage_meshes = []
969
        for cart_label_idx in self._list_cartilage_labels:
970
            seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
971
            n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
972
            if n_pixels_with_cart == 0:
973
                warnings.warn(
974
                    f&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
975
                    UserWarning
976
                )
977
            else:
978
                cart_mesh = CartilageMesh(seg_image=self._seg_image,
979
                                            label_idx=cart_label_idx)
980
                cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
981
                                        marching_cubes_threshold=marching_cubes_threshold)
982
                self._list_cartilage_meshes.append(cart_mesh)
983
984
985
    def calc_cartilage_thickness(self,
986
                                 list_cartilage_labels=None,
987
                                 list_cartilage_meshes=None,
988
                                 image_smooth_var_cart=0.3125 / 2,
989
                                 marching_cubes_threshold=0.5,
990
                                 ray_cast_length=10.0,
991
                                 percent_ray_length_opposite_direction=0.25
992
                                 ):
993
        &#34;&#34;&#34;
994
        Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
995
        calcualte the cartilage thickness for each node on the bone surface. 
996
997
        Parameters
998
        ----------
999
        list_cartilage_labels : list, optional
1000
            Cartilag labels to be used to create cartilage meshes (if they dont
1001
            exist), by default None
1002
        list_cartilage_meshes : list, optional
1003
            Cartilage meshes to be used for calculating cart thickness, by default None
1004
        image_smooth_var_cart : float, optional
1005
            Variance of gaussian filter to be applied to binary cartilage masks, 
1006
            by default 0.3125/2
1007
        marching_cubes_threshold : float, optional
1008
            Threshold to create bone surface at, by default 0.5
1009
        ray_cast_length : float, optional
1010
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1011
            outter shell), by default 10.0
1012
        percent_ray_length_opposite_direction : float, optional
1013
            How far to project ray inside of the bone. This is done just in case the cartilage
1014
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1015
1016
        Raises
1017
        ------
1018
        Exception
1019
            No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
1020
        &#34;&#34;&#34;        
1021
        # If new cartilage infor/labels are provided, then replace existing with these ones. 
1022
        if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
1023
        if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
1024
1025
        # If no cartilage stuff provided, then cant do this function - raise exception. 
1026
        if (self._list_cartilage_meshes is None) &amp; (self._list_cartilage_labels is None):
1027
            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;)
1028
1029
        # if cartilage meshes don&#39;t exist yet, then make them. 
1030
        if self._list_cartilage_meshes is None:
1031
            self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
1032
                                         marching_cubes_threshold=marching_cubes_threshold)
1033
        
1034
        # pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone. 
1035
        thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
1036
        
1037
        # iterate over meshes and add their thicknesses to the thicknesses list. 
1038
        for cart_mesh in self._list_cartilage_meshes:
1039
            node_data = get_cartilage_properties_at_points(self._mesh,
1040
                                                           cart_mesh._mesh,
1041
                                                           t2_vtk_image=None,
1042
                                                           #   seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
1043
                                                           seg_vtk_image=None,
1044
                                                           ray_cast_length=ray_cast_length,
1045
                                                           percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1046
                                                           )
1047
            thicknesses += node_data
1048
        
1049
        # Assign the thickness scalars to the bone mesh surface. 
1050
        thickness_scalars = numpy_to_vtk(thicknesses)
1051
        thickness_scalars.SetName(&#39;thickness (mm)&#39;)
1052
        self._mesh.GetPointData().SetScalars(thickness_scalars)
1053
    
1054
    def assign_cartilage_regions(self,
1055
                                 image_smooth_var_cart=0.3125 / 2,
1056
                                 marching_cubes_threshold=0.5,
1057
                                 ray_cast_length=10.0,
1058
                                 percent_ray_length_opposite_direction=0.25):
1059
        &#34;&#34;&#34;
1060
        Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
1061
        - Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
1062
1063
        Parameters
1064
        ----------
1065
        image_smooth_var_cart : float, optional
1066
            Variance of gaussian filter to be applied to binary cartilage masks, 
1067
            by default 0.3125/2
1068
        marching_cubes_threshold : float, optional
1069
            Threshold to create bone surface at, by default 0.5
1070
        ray_cast_length : float, optional
1071
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1072
            outter shell), by default 10.0
1073
        percent_ray_length_opposite_direction : float, optional
1074
            How far to project ray inside of the bone. This is done just in case the cartilage
1075
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1076
        &#34;&#34;&#34;        
1077
        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
1078
        path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
1079
        # if self._bone == &#39;femur&#39;:
1080
        #     new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
1081
        #                                                                    fem_cart_label_idx=1)
1082
        #     sitk.WriteImage(new_seg_image, path_save_tmp_file)
1083
        # else:
1084
        sitk.WriteImage(self._seg_image, path_save_tmp_file)
1085
        vtk_seg_reader = read_nrrd(path_save_tmp_file,
1086
                                   set_origin_zero=True
1087
                                   )
1088
        vtk_seg = vtk_seg_reader.GetOutput()
1089
1090
        seg_transformer = SitkVtkTransformer(self._seg_image)
1091
1092
        # Delete tmp files
1093
        safely_delete_tmp_file(&#39;/tmp&#39;,
1094
                               tmp_filename)
1095
        
1096
        self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
1097
        labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
1098
1099
        # if cartilage meshes don&#39;t exist yet, then make them. 
1100
        if self._list_cartilage_meshes is None:
1101
            self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
1102
                                         marching_cubes_threshold=marching_cubes_threshold)
1103
        
1104
        # iterate over meshes and add their label (region) 
1105
        for cart_mesh in self._list_cartilage_meshes:
1106
            cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
1107
            node_data = get_cartilage_properties_at_points(self._mesh,
1108
                                                           cart_mesh._mesh,
1109
                                                           t2_vtk_image=None,
1110
                                                           seg_vtk_image=vtk_seg,
1111
                                                           ray_cast_length=ray_cast_length,
1112
                                                           percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1113
                                                           )
1114
            labels += node_data[1]
1115
            cart_mesh.reverse_all_transforms()
1116
1117
        # Assign the label (region) scalars to the bone mesh surface. 
1118
        label_scalars = numpy_to_vtk(labels)
1119
        label_scalars.SetName(&#39;labels&#39;)
1120
        self._mesh.GetPointData().AddArray(label_scalars)
1121
1122
        self.reverse_all_transforms()
1123
1124
    def calc_cartilage_t2(self,
1125
                          path_t2_nrrd,
1126
                          path_seg_to_t2_transform=None,
1127
                          ray_cast_length=10.0,
1128
                          percent_ray_length_opposite_direction=0.25):
1129
        &#34;&#34;&#34;
1130
        Apply cartilage T2 values to bone surface. 
1131
1132
        Parameters
1133
        ----------
1134
        path_t2_nrrd : str
1135
            Path to nrrd image of T2 map to load / use. 
1136
        path_seg_to_t2_transform : str, optional
1137
            Path to a transform file to be used for aligning T2 map with segmentations, 
1138
            by default None
1139
        ray_cast_length : float, optional
1140
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1141
            outter shell), by default 10.0
1142
        percent_ray_length_opposite_direction : float, optional
1143
            How far to project ray inside of the bone. This is done just in case the cartilage
1144
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1145
        &#34;&#34;&#34;        
1146
        print(&#39;Not yet implemented&#39;)
1147
        # if self._list_cartilage_meshes is None:
1148
        #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
1149
        #     # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
1150
        #     # THOUGH, DOES THAT JUST COMPLICATE THINGS? 
1151
1152
        # if path_seg_transform is not None:
1153
        #     # this is in case there is a transformation needed to align the segmentation with the
1154
        #     # underlying T2 image
1155
        #     seg_transform = sitk.ReadTransform(path_seg_transform)
1156
        #     seg_image = apply_transform_retain_array(self._seg_image,
1157
        #                                              seg_transform,
1158
        #                                              interpolator=sitk.sitkNearestNeighbor)
1159
            
1160
            
1161
        #     versor = get_versor_from_transform(seg_transform)
1162
        #     center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
1163
        #     # first apply negative of center of rotation to mesh
1164
        #     self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
1165
        #     # now apply the transform (rotation then translation)
1166
        #     self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
1167
        #     self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
1168
        #     #then undo the center of rotation
1169
        #     self._mesh.apply_transform_to_mesh(transform=center_transform)
1170
1171
        # # Read t2 map (vtk format)
1172
        # vtk_t2map_reader = read_nrrd(path_t2_nrrd,
1173
        #                              set_origin_zero=True)
1174
        # vtk_t2map = vtk_t2map_reader.GetOutput()
1175
        # sitk_t2map = sitk.ReadImage(path_t2_nrrd)
1176
        # t2_transformer = SitkVtkTransformer(sitk_t2map)
1177
1178
        # self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
1179
1180
        # t2 = np.zeros(self._mesh.GetNumberOfPoints())
1181
        # # iterate over meshes and add their t2 to the t2 list. 
1182
        # for cart_mesh in self._list_cartilage_meshes:
1183
        #     if path_seg_to_t2_transform is not None:
1184
        #         # first apply negative of center of rotation to mesh
1185
        #         cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
1186
        #         # now apply the transform (rotation then translation)
1187
        #         cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
1188
        #         cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
1189
        #         #then undo the center of rotation
1190
        #         cart_mesh.apply_transform_to_mesh(transform=center_transform)
1191
1192
        #     cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
1193
        #     _, t2_data = get_cartilage_properties_at_points(self._mesh,
1194
        #                                                     cart_mesh._mesh,
1195
        #                                                     t2_vtk_image=vtk_t2map,
1196
        #                                                     ray_cast_length=ray_cast_length,
1197
        #                                                     percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1198
        #                                                     )
1199
        #     t2 += t2_data
1200
        #     cart_mesh.reverse_all_transforms()
1201
        print(&#39;NOT DONE!!!&#39;)
1202
        
1203
1204
1205
    def smooth_surface_scalars(self,
1206
                               smooth_only_cartilage=True,
1207
                               scalar_sigma=1.6986436005760381,  # This is a FWHM = 4
1208
                               scalar_array_name=&#39;thickness (mm)&#39;,
1209
                               scalar_array_idx=None,
1210
                               ):
1211
                               
1212
        &#34;&#34;&#34;
1213
        Function to smooth the scalars with name `scalar_array_name` on the bone surface. 
1214
1215
        Parameters
1216
        ----------
1217
        smooth_only_cartilage : bool, optional
1218
            Should we only smooth where there is cartialge &amp; ignore everywhere else, by default True
1219
        scalar_sigma : float, optional
1220
            Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
1221
            default is based on a Full Width Half Maximum (FWHM) of 4mm. 
1222
        scalar_array_name : str
1223
            Name of scalar array to smooth, default &#39;thickness (mm)&#39;.
1224
        scalar_array_idx : int, optional
1225
            Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
1226
        &#34;&#34;&#34;
1227
        if smooth_only_cartilage is True:
1228
            loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 0.01)[0]
1229
        else:
1230
            loc_cartilage = None
1231
        self._mesh = gaussian_smooth_surface_scalars(self._mesh,
1232
                                                     sigma=scalar_sigma,
1233
                                                     idx_coords_to_smooth=loc_cartilage,
1234
                                                     array_name=scalar_array_name,
1235
                                                     array_idx=scalar_array_idx)
1236
1237
    @property
1238
    def list_cartilage_meshes(self):
1239
        &#34;&#34;&#34;
1240
        Convenience function to get the list of cartilage meshes
1241
1242
        Returns
1243
        -------
1244
        list
1245
            A list of `CartilageMesh` objects associated with this bone
1246
        &#34;&#34;&#34;        
1247
        return self._list_cartilage_meshes
1248
    
1249
    @list_cartilage_meshes.setter
1250
    def list_cartilage_meshes(self, new_list_cartilage_meshes):
1251
        &#34;&#34;&#34;
1252
        Convenience function to set the list of cartilage meshes
1253
1254
        Parameters
1255
        ----------
1256
        new_list_cartilage_meshes : list
1257
            A list of `CartilageMesh` objects associated with this bone
1258
        &#34;&#34;&#34;
1259
        if type(new_list_cartilage_meshes) is list:
1260
            for mesh in new_list_cartilage_meshes:
1261
                if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
1262
                    raise TypeError(&#39;Item in `list_cartilage_meshes` is not a `CartilageMesh`&#39;)
1263
        elif type(new_list_cartilage_meshes) is pymskt.mesh.meshes.CartilageMesh:
1264
            new_list_cartilage_meshes = [new_list_cartilage_meshes,]
1265
        self._list_cartilage_meshes = new_list_cartilage_meshes
1266
    
1267
    @property
1268
    def list_cartilage_labels(self):
1269
        &#34;&#34;&#34;
1270
        Convenience function to get the list of labels for cartilage tissues associated
1271
        with this bone. 
1272
1273
        Returns
1274
        -------
1275
        list
1276
            list of `int`s for the cartilage tissues associated with this bone. 
1277
        &#34;&#34;&#34;        
1278
        return self._list_cartilage_labels
1279
    
1280
    @list_cartilage_labels.setter
1281
    def list_cartilage_labels(self, new_list_cartilage_labels):
1282
        &#34;&#34;&#34;
1283
        Convenience function to set the list of labels for cartilage tissues associated
1284
        with this bone
1285
1286
        Parameters
1287
        ----------
1288
        new_list_cartilage_labels : list
1289
            list of `int`s for the cartilage tissues associated with this bone. 
1290
        &#34;&#34;&#34;
1291
        if type(new_list_cartilage_labels) == list:
1292
            for label in new_list_cartilage_labels:
1293
                if type(label) != int:
1294
                    raise TypeError(f&#39;Item in `list_cartilage_labels` is not a `int` - got {type(label)}&#39;)
1295
        elif type(new_list_cartilage_labels) == int:
1296
            new_list_cartilage_labels = [new_list_cartilage_labels,]
1297
        self._list_cartilage_labels = new_list_cartilage_labels
1298
    
1299
    @property
1300
    def crop_percent(self):
1301
        &#34;&#34;&#34;
1302
        Convenience function to get the value that `crop_percent` is set to. 
1303
1304
        Returns
1305
        -------
1306
        float
1307
            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
1308
            when cropping - expressed as a proportion of the width. 
1309
        &#34;&#34;&#34;        
1310
        return self._crop_percent
1311
    
1312
    @crop_percent.setter
1313
    def crop_percent(self, new_crop_percent):
1314
        &#34;&#34;&#34;
1315
        Convenience function to set the value that `crop_percent` is set to. 
1316
1317
        Parameters
1318
        ----------
1319
        new_crop_percent : float
1320
            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
1321
            when cropping - expressed as a proportion of the width. 
1322
        &#34;&#34;&#34;
1323
        if type(new_crop_percent) != float:
1324
            raise TypeError(f&#39;New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`&#39;)
1325
        self._crop_percent = new_crop_percent
1326
    
1327
    @property
1328
    def bone(self):
1329
        &#34;&#34;&#34;
1330
        Convenience function to get the name of the bone in this object. 
1331
1332
        Returns
1333
        -------
1334
        str
1335
            Name of the bone in this object - used to help identify how to crop the bone. 
1336
        &#34;&#34;&#34;        
1337
        return self._bone
1338
1339
    @bone.setter
1340
    def bone(self, new_bone):
1341
        &#34;&#34;&#34;
1342
        Convenience function to set the name of the bone in this object.         
1343
1344
        Parameters
1345
        ----------
1346
        new_bone : str
1347
            Name of the bone in this object - used to help identify how to crop the bone. 
1348
        &#34;&#34;&#34;
1349
        if type(new_bone) != str:
1350
            raise TypeError(f&#39;New bone provided is type {type(new_bone)} - expected `str`&#39;)   
1351
        self._bone = new_bone   
1352
        
1353
1354
1355
                     </code></pre>
1356
</details>
1357
</section>
1358
<section>
1359
</section>
1360
<section>
1361
</section>
1362
<section>
1363
</section>
1364
<section>
1365
<h2 class="section-title" id="header-classes">Classes</h2>
1366
<dl>
1367
<dt id="pymskt.mesh.meshes.BoneMesh"><code class="flex name class">
1368
<span>class <span class="ident">BoneMesh</span></span>
1369
<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>
1370
</code></dt>
1371
<dd>
1372
<div class="desc"><p>Class to create, store, and process bone meshes</p>
1373
<p>Intention is that this class includes functions to process other data &amp; assign it to the bone surface.
1374
It might be possible that instead this class &amp; a cartilage class or, this class and image data etc. are
1375
provided to another function or class that does those analyses.</p>
1376
<h2 id="parameters">Parameters</h2>
1377
<dl>
1378
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
1379
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
1380
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
1381
<dd>Segmentation image that can be used to create surface mesh - used
1382
instead of mesh, by default None</dd>
1383
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
1384
<dd>Path to a medical image (.nrrd) to load and create mesh from,
1385
by default None</dd>
1386
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
1387
<dd>Label of anatomy of interest, by default None</dd>
1388
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
1389
<dd>All islands smaller than this size are dropped, by default 5000</dd>
1390
<dt><strong><code>list_cartilage_meshes</code></strong> :&ensp;<code>list</code>, optional</dt>
1391
<dd>List object which contains 1+ <code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code> objects that wrap
1392
a vtk.vtkPolyData surface mesh of cartilage, by default None</dd>
1393
<dt><strong><code>list_cartilage_labels</code></strong> :&ensp;<code>list</code>, optional</dt>
1394
<dd>List of <code>int</code> values that represent the different cartilage
1395
regions of interest appropriate for a single bone, by default None</dd>
1396
<dt><strong><code>crop_percent</code></strong> :&ensp;<code>float</code>, optional</dt>
1397
<dd>Proportion value to crop long-axis of bone so it is proportional
1398
to the width of the bone for standardization purposes, by default 1.0</dd>
1399
<dt><strong><code>bone</code></strong> :&ensp;<code>str</code>, optional</dt>
1400
<dd>String indicating what bone is being analyzed so that cropping
1401
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
1402
Patella is not an option because we do not need to crop for the patella.</dd>
1403
</dl>
1404
<h2 id="attributes">Attributes</h2>
1405
<dl>
1406
<dt><strong><code>_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1407
<dd>Item passed from <strong>init</strong>, or created during life of class.
1408
This is the main surface mesh of this class.</dd>
1409
<dt><strong><code>_seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code></dt>
1410
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
1411
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code></dt>
1412
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
1413
and then creat surface mesh <code>_mesh</code></dd>
1414
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code></dt>
1415
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
1416
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code></dt>
1417
<dd>Minimum number of pixels for an isolated island of a segmentation to be
1418
retained</dd>
1419
<dt><strong><code>list_applied_transforms</code></strong> :&ensp;<code>list</code></dt>
1420
<dd>A list of transformations applied to a surface mesh.
1421
This list allows for undoing of most recent transform, or undoing
1422
all of them by iterating over the list in reverse.</dd>
1423
<dt><strong><code>crop_percent</code></strong> :&ensp;<code>float</code></dt>
1424
<dd>Percent of width to crop along long-axis of bone</dd>
1425
<dt><strong><code>bone</code></strong> :&ensp;<code>str</code></dt>
1426
<dd>A string indicating what bone is being represented by this class.</dd>
1427
<dt><strong><code>list_cartilage_meshes</code></strong> :&ensp;<code>list</code></dt>
1428
<dd>List of cartialge meshes assigned to this bone.</dd>
1429
<dt><strong><code>list_cartilage_labels</code></strong> :&ensp;<code>list</code></dt>
1430
<dd>List of cartilage labels for the <code>_seg_image</code> that are associated
1431
with this bone.</dd>
1432
</dl>
1433
<h2 id="methods">Methods</h2>
1434
<p>Class initialization</p>
1435
<h2 id="parameters_1">Parameters</h2>
1436
<dl>
1437
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
1438
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
1439
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
1440
<dd>Segmentation image that can be used to create surface mesh - used
1441
instead of mesh, by default None</dd>
1442
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
1443
<dd>Path to a medical image (.nrrd) to load and create mesh from,
1444
by default None</dd>
1445
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
1446
<dd>Label of anatomy of interest, by default None</dd>
1447
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
1448
<dd>All islands smaller than this size are dropped, by default 5000</dd>
1449
<dt><strong><code>list_cartilage_meshes</code></strong> :&ensp;<code>list</code>, optional</dt>
1450
<dd>List object which contains 1+ <code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code> objects that wrap
1451
a vtk.vtkPolyData surface mesh of cartilage, by default None</dd>
1452
<dt><strong><code>list_cartilage_labels</code></strong> :&ensp;<code>list</code>, optional</dt>
1453
<dd>List of <code>int</code> values that represent the different cartilage
1454
regions of interest appropriate for a single bone, by default None</dd>
1455
<dt><strong><code>crop_percent</code></strong> :&ensp;<code>float</code>, optional</dt>
1456
<dd>Proportion value to crop long-axis of bone so it is proportional
1457
to the width of the bone for standardization purposes, by default 1.0</dd>
1458
<dt><strong><code>bone</code></strong> :&ensp;<code>str</code>, optional</dt>
1459
<dd>String indicating what bone is being analyzed so that cropping
1460
can be applied appropriatey. {'femur', 'tibia'}, by default 'femur'.
1461
Patella is not an option because we do not need to crop for the patella.</dd>
1462
</dl></div>
1463
<details class="source">
1464
<summary>
1465
<span>Expand source code</span>
1466
</summary>
1467
<pre><code class="python">class BoneMesh(Mesh):
1468
    &#34;&#34;&#34;
1469
    Class to create, store, and process bone meshes
1470
1471
    Intention is that this class includes functions to process other data &amp; assign it to the bone surface.
1472
    It might be possible that instead this class &amp; a cartilage class or, this class and image data etc. are
1473
    provided to another function or class that does those analyses.
1474
1475
    Parameters
1476
    ----------
1477
    mesh : vtk.vtkPolyData, optional
1478
        vtkPolyData object that is basis of surface mesh, by default None
1479
    seg_image : SimpleITK.Image, optional
1480
        Segmentation image that can be used to create surface mesh - used 
1481
        instead of mesh, by default None
1482
    path_seg_image : str, optional
1483
        Path to a medical image (.nrrd) to load and create mesh from, 
1484
        by default None
1485
    label_idx : int, optional
1486
        Label of anatomy of interest, by default None
1487
    min_n_pixels : int, optional
1488
        All islands smaller than this size are dropped, by default 5000
1489
    list_cartilage_meshes : list, optional
1490
        List object which contains 1+ `CartilageMesh` objects that wrap
1491
        a vtk.vtkPolyData surface mesh of cartilage, by default None
1492
    list_cartilage_labels : list, optional
1493
        List of `int` values that represent the different cartilage
1494
        regions of interest appropriate for a single bone, by default None
1495
    crop_percent : float, optional
1496
        Proportion value to crop long-axis of bone so it is proportional
1497
        to the width of the bone for standardization purposes, by default 1.0
1498
    bone : str, optional
1499
        String indicating what bone is being analyzed so that cropping
1500
        can be applied appropriatey. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
1501
        Patella is not an option because we do not need to crop for the patella. 
1502
1503
1504
    Attributes
1505
    ----------
1506
    _mesh : vtk.vtkPolyData
1507
        Item passed from __init__, or created during life of class. 
1508
        This is the main surface mesh of this class. 
1509
    _seg_image : SimpleITK.Image
1510
        Segmentation image that can be used to create mesh. This is optional.
1511
    path_seg_image : str
1512
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
1513
        and then creat surface mesh `_mesh` 
1514
    label_idx : int
1515
        Integer of anatomy to create surface mesh from `_seg_image`
1516
    min_n_pixels : int
1517
        Minimum number of pixels for an isolated island of a segmentation to be
1518
        retained
1519
    list_applied_transforms : list
1520
        A list of transformations applied to a surface mesh. 
1521
        This list allows for undoing of most recent transform, or undoing
1522
        all of them by iterating over the list in reverse.
1523
    crop_percent : float
1524
        Percent of width to crop along long-axis of bone
1525
    bone : str
1526
        A string indicating what bone is being represented by this class. 
1527
    list_cartilage_meshes : list
1528
        List of cartialge meshes assigned to this bone. 
1529
    list_cartilage_labels : list
1530
        List of cartilage labels for the `_seg_image` that are associated
1531
        with this bone. 
1532
1533
    Methods
1534
    ----------
1535
1536
    &#34;&#34;&#34;
1537
1538
    def __init__(self,
1539
                 mesh=None,
1540
                 seg_image=None,
1541
                 path_seg_image=None,
1542
                 label_idx=None,
1543
                 min_n_pixels=5000,
1544
                 list_cartilage_meshes=None,
1545
                 list_cartilage_labels=None,
1546
                 crop_percent=None,
1547
                 bone=&#39;femur&#39;,
1548
                 ):
1549
        &#34;&#34;&#34;
1550
        Class initialization
1551
1552
        Parameters
1553
        ----------
1554
        mesh : vtk.vtkPolyData, optional
1555
            vtkPolyData object that is basis of surface mesh, by default None
1556
        seg_image : SimpleITK.Image, optional
1557
            Segmentation image that can be used to create surface mesh - used 
1558
            instead of mesh, by default None
1559
        path_seg_image : str, optional
1560
            Path to a medical image (.nrrd) to load and create mesh from, 
1561
            by default None
1562
        label_idx : int, optional
1563
            Label of anatomy of interest, by default None
1564
        min_n_pixels : int, optional
1565
            All islands smaller than this size are dropped, by default 5000
1566
        list_cartilage_meshes : list, optional
1567
            List object which contains 1+ `CartilageMesh` objects that wrap
1568
            a vtk.vtkPolyData surface mesh of cartilage, by default None
1569
        list_cartilage_labels : list, optional
1570
            List of `int` values that represent the different cartilage
1571
            regions of interest appropriate for a single bone, by default None
1572
        crop_percent : float, optional
1573
            Proportion value to crop long-axis of bone so it is proportional
1574
            to the width of the bone for standardization purposes, by default 1.0
1575
        bone : str, optional
1576
            String indicating what bone is being analyzed so that cropping
1577
            can be applied appropriatey. {&#39;femur&#39;, &#39;tibia&#39;}, by default &#39;femur&#39;.
1578
            Patella is not an option because we do not need to crop for the patella. 
1579
        &#34;&#34;&#34;        
1580
        self._crop_percent = crop_percent
1581
        self._bone = bone
1582
        self._list_cartilage_meshes = list_cartilage_meshes
1583
        self._list_cartilage_labels = list_cartilage_labels
1584
1585
        super().__init__(mesh=mesh,
1586
                         seg_image=seg_image,
1587
                         path_seg_image=path_seg_image,
1588
                         label_idx=label_idx,
1589
                         min_n_pixels=min_n_pixels)
1590
1591
        
1592
    def create_mesh(self, 
1593
                    smooth_image=True, 
1594
                    smooth_image_var=0.3125 / 2, 
1595
                    marching_cubes_threshold=0.5, 
1596
                    label_idx=None, 
1597
                    min_n_pixels=None,
1598
                    crop_percent=None
1599
                    ):
1600
        &#34;&#34;&#34;
1601
        This is an extension of `Mesh.create_mesh` that enables cropping of bones. 
1602
        Bones might need to be cropped (this isnt necessary for cartilage)
1603
        So, adding this functionality to the processing steps before the bone mesh is created.
1604
1605
        All functionality, except for that relevant to `crop_percent` is the same as:
1606
        `Mesh.create_mesh`. 
1607
        
1608
        Create a surface mesh from the classes `_seg_image`. If `_seg_image`
1609
        does not exist, then read it in using `read_seg_image`. 
1610
1611
        Parameters
1612
        ----------
1613
        smooth_image : bool, optional
1614
            Should the `_seg_image` be gaussian filtered, by default True
1615
        smooth_image_var : float, optional
1616
            Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
1617
        marching_cubes_threshold : float, optional
1618
            Threshold contour level to create surface mesh on, by default 0.5
1619
        label_idx : int, optional
1620
            Label value / index to create mesh from, by default None
1621
        min_n_pixels : int, optional
1622
            Minimum number of continuous pixels to include segmentation island
1623
            in the surface mesh creation, by default None
1624
        crop_percent : [type], optional
1625
            [description], by default None
1626
1627
        Raises
1628
        ------
1629
        Exception
1630
            If cropping &amp; bone is not femur or tibia, then raise an error. 
1631
        Exception
1632
            If the total number of pixels segmentated (`n_pixels_labelled`) is
1633
            &lt; `min_n_pixels` then there is no object in the image.  
1634
        Exception
1635
            If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
1636
            surface mesh from. 
1637
        Exception
1638
            If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
1639
        &#34;&#34;&#34;     
1640
        
1641
        if self._seg_image is None:
1642
            self.read_seg_image()
1643
        
1644
        # Bones might need to be cropped (this isnt necessary for cartilage)
1645
        # So, adding this functionality to the processing steps before the bone mesh is created
1646
        if crop_percent is not None:
1647
            self._crop_percent = crop_percent
1648
        if (self._crop_percent is not None) and ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
1649
            if &#39;femur&#39; in self._bone:
1650
                bone_crop_distal = True
1651
            elif &#39;tibia&#39; in self._bone:
1652
                bone_crop_distal = False
1653
            else:
1654
                raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.format(self._bone))
1655
1656
            self._seg_image = crop_bone_based_on_width(self._seg_image,
1657
                                                       self._label_idx,
1658
                                                       percent_width_to_crop_height=self._crop_percent,
1659
                                                       bone_crop_distal=bone_crop_distal)
1660
        elif self._crop_percent is not None:
1661
            warnings.warn(f&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
1662
                          &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
1663
                          &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
1664
                )
1665
        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)
1666
1667
    def create_cartilage_meshes(self,
1668
                                image_smooth_var_cart=0.3125 / 2,
1669
                                marching_cubes_threshold=0.5):
1670
        &#34;&#34;&#34;
1671
        Helper function to create the list of cartilage meshes from the list of cartilage
1672
        labels. 
1673
1674
        Parameters
1675
        ----------
1676
        image_smooth_var_cart : float
1677
            Variance to smooth cartilage segmentations before finding surface using continuous
1678
            marching cubes.             
1679
        marching_cubes_threshold : float
1680
            Threshold value to create cartilage surface at from segmentation images. 
1681
1682
        Notes
1683
        -----
1684
        ?? Should this function just be everything inside the for loop and then that 
1685
        function gets called somewhere else? 
1686
        &#34;&#34;&#34;        
1687
1688
        self._list_cartilage_meshes = []
1689
        for cart_label_idx in self._list_cartilage_labels:
1690
            seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
1691
            n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
1692
            if n_pixels_with_cart == 0:
1693
                warnings.warn(
1694
                    f&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
1695
                    UserWarning
1696
                )
1697
            else:
1698
                cart_mesh = CartilageMesh(seg_image=self._seg_image,
1699
                                            label_idx=cart_label_idx)
1700
                cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
1701
                                        marching_cubes_threshold=marching_cubes_threshold)
1702
                self._list_cartilage_meshes.append(cart_mesh)
1703
1704
1705
    def calc_cartilage_thickness(self,
1706
                                 list_cartilage_labels=None,
1707
                                 list_cartilage_meshes=None,
1708
                                 image_smooth_var_cart=0.3125 / 2,
1709
                                 marching_cubes_threshold=0.5,
1710
                                 ray_cast_length=10.0,
1711
                                 percent_ray_length_opposite_direction=0.25
1712
                                 ):
1713
        &#34;&#34;&#34;
1714
        Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
1715
        calcualte the cartilage thickness for each node on the bone surface. 
1716
1717
        Parameters
1718
        ----------
1719
        list_cartilage_labels : list, optional
1720
            Cartilag labels to be used to create cartilage meshes (if they dont
1721
            exist), by default None
1722
        list_cartilage_meshes : list, optional
1723
            Cartilage meshes to be used for calculating cart thickness, by default None
1724
        image_smooth_var_cart : float, optional
1725
            Variance of gaussian filter to be applied to binary cartilage masks, 
1726
            by default 0.3125/2
1727
        marching_cubes_threshold : float, optional
1728
            Threshold to create bone surface at, by default 0.5
1729
        ray_cast_length : float, optional
1730
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1731
            outter shell), by default 10.0
1732
        percent_ray_length_opposite_direction : float, optional
1733
            How far to project ray inside of the bone. This is done just in case the cartilage
1734
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1735
1736
        Raises
1737
        ------
1738
        Exception
1739
            No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
1740
        &#34;&#34;&#34;        
1741
        # If new cartilage infor/labels are provided, then replace existing with these ones. 
1742
        if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
1743
        if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
1744
1745
        # If no cartilage stuff provided, then cant do this function - raise exception. 
1746
        if (self._list_cartilage_meshes is None) &amp; (self._list_cartilage_labels is None):
1747
            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;)
1748
1749
        # if cartilage meshes don&#39;t exist yet, then make them. 
1750
        if self._list_cartilage_meshes is None:
1751
            self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
1752
                                         marching_cubes_threshold=marching_cubes_threshold)
1753
        
1754
        # pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone. 
1755
        thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
1756
        
1757
        # iterate over meshes and add their thicknesses to the thicknesses list. 
1758
        for cart_mesh in self._list_cartilage_meshes:
1759
            node_data = get_cartilage_properties_at_points(self._mesh,
1760
                                                           cart_mesh._mesh,
1761
                                                           t2_vtk_image=None,
1762
                                                           #   seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
1763
                                                           seg_vtk_image=None,
1764
                                                           ray_cast_length=ray_cast_length,
1765
                                                           percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1766
                                                           )
1767
            thicknesses += node_data
1768
        
1769
        # Assign the thickness scalars to the bone mesh surface. 
1770
        thickness_scalars = numpy_to_vtk(thicknesses)
1771
        thickness_scalars.SetName(&#39;thickness (mm)&#39;)
1772
        self._mesh.GetPointData().SetScalars(thickness_scalars)
1773
    
1774
    def assign_cartilage_regions(self,
1775
                                 image_smooth_var_cart=0.3125 / 2,
1776
                                 marching_cubes_threshold=0.5,
1777
                                 ray_cast_length=10.0,
1778
                                 percent_ray_length_opposite_direction=0.25):
1779
        &#34;&#34;&#34;
1780
        Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
1781
        - Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
1782
1783
        Parameters
1784
        ----------
1785
        image_smooth_var_cart : float, optional
1786
            Variance of gaussian filter to be applied to binary cartilage masks, 
1787
            by default 0.3125/2
1788
        marching_cubes_threshold : float, optional
1789
            Threshold to create bone surface at, by default 0.5
1790
        ray_cast_length : float, optional
1791
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1792
            outter shell), by default 10.0
1793
        percent_ray_length_opposite_direction : float, optional
1794
            How far to project ray inside of the bone. This is done just in case the cartilage
1795
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1796
        &#34;&#34;&#34;        
1797
        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
1798
        path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
1799
        # if self._bone == &#39;femur&#39;:
1800
        #     new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
1801
        #                                                                    fem_cart_label_idx=1)
1802
        #     sitk.WriteImage(new_seg_image, path_save_tmp_file)
1803
        # else:
1804
        sitk.WriteImage(self._seg_image, path_save_tmp_file)
1805
        vtk_seg_reader = read_nrrd(path_save_tmp_file,
1806
                                   set_origin_zero=True
1807
                                   )
1808
        vtk_seg = vtk_seg_reader.GetOutput()
1809
1810
        seg_transformer = SitkVtkTransformer(self._seg_image)
1811
1812
        # Delete tmp files
1813
        safely_delete_tmp_file(&#39;/tmp&#39;,
1814
                               tmp_filename)
1815
        
1816
        self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
1817
        labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
1818
1819
        # if cartilage meshes don&#39;t exist yet, then make them. 
1820
        if self._list_cartilage_meshes is None:
1821
            self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
1822
                                         marching_cubes_threshold=marching_cubes_threshold)
1823
        
1824
        # iterate over meshes and add their label (region) 
1825
        for cart_mesh in self._list_cartilage_meshes:
1826
            cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
1827
            node_data = get_cartilage_properties_at_points(self._mesh,
1828
                                                           cart_mesh._mesh,
1829
                                                           t2_vtk_image=None,
1830
                                                           seg_vtk_image=vtk_seg,
1831
                                                           ray_cast_length=ray_cast_length,
1832
                                                           percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1833
                                                           )
1834
            labels += node_data[1]
1835
            cart_mesh.reverse_all_transforms()
1836
1837
        # Assign the label (region) scalars to the bone mesh surface. 
1838
        label_scalars = numpy_to_vtk(labels)
1839
        label_scalars.SetName(&#39;labels&#39;)
1840
        self._mesh.GetPointData().AddArray(label_scalars)
1841
1842
        self.reverse_all_transforms()
1843
1844
    def calc_cartilage_t2(self,
1845
                          path_t2_nrrd,
1846
                          path_seg_to_t2_transform=None,
1847
                          ray_cast_length=10.0,
1848
                          percent_ray_length_opposite_direction=0.25):
1849
        &#34;&#34;&#34;
1850
        Apply cartilage T2 values to bone surface. 
1851
1852
        Parameters
1853
        ----------
1854
        path_t2_nrrd : str
1855
            Path to nrrd image of T2 map to load / use. 
1856
        path_seg_to_t2_transform : str, optional
1857
            Path to a transform file to be used for aligning T2 map with segmentations, 
1858
            by default None
1859
        ray_cast_length : float, optional
1860
            Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1861
            outter shell), by default 10.0
1862
        percent_ray_length_opposite_direction : float, optional
1863
            How far to project ray inside of the bone. This is done just in case the cartilage
1864
            surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1865
        &#34;&#34;&#34;        
1866
        print(&#39;Not yet implemented&#39;)
1867
        # if self._list_cartilage_meshes is None:
1868
        #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
1869
        #     # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
1870
        #     # THOUGH, DOES THAT JUST COMPLICATE THINGS? 
1871
1872
        # if path_seg_transform is not None:
1873
        #     # this is in case there is a transformation needed to align the segmentation with the
1874
        #     # underlying T2 image
1875
        #     seg_transform = sitk.ReadTransform(path_seg_transform)
1876
        #     seg_image = apply_transform_retain_array(self._seg_image,
1877
        #                                              seg_transform,
1878
        #                                              interpolator=sitk.sitkNearestNeighbor)
1879
            
1880
            
1881
        #     versor = get_versor_from_transform(seg_transform)
1882
        #     center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
1883
        #     # first apply negative of center of rotation to mesh
1884
        #     self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
1885
        #     # now apply the transform (rotation then translation)
1886
        #     self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
1887
        #     self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
1888
        #     #then undo the center of rotation
1889
        #     self._mesh.apply_transform_to_mesh(transform=center_transform)
1890
1891
        # # Read t2 map (vtk format)
1892
        # vtk_t2map_reader = read_nrrd(path_t2_nrrd,
1893
        #                              set_origin_zero=True)
1894
        # vtk_t2map = vtk_t2map_reader.GetOutput()
1895
        # sitk_t2map = sitk.ReadImage(path_t2_nrrd)
1896
        # t2_transformer = SitkVtkTransformer(sitk_t2map)
1897
1898
        # self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
1899
1900
        # t2 = np.zeros(self._mesh.GetNumberOfPoints())
1901
        # # iterate over meshes and add their t2 to the t2 list. 
1902
        # for cart_mesh in self._list_cartilage_meshes:
1903
        #     if path_seg_to_t2_transform is not None:
1904
        #         # first apply negative of center of rotation to mesh
1905
        #         cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
1906
        #         # now apply the transform (rotation then translation)
1907
        #         cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
1908
        #         cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
1909
        #         #then undo the center of rotation
1910
        #         cart_mesh.apply_transform_to_mesh(transform=center_transform)
1911
1912
        #     cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
1913
        #     _, t2_data = get_cartilage_properties_at_points(self._mesh,
1914
        #                                                     cart_mesh._mesh,
1915
        #                                                     t2_vtk_image=vtk_t2map,
1916
        #                                                     ray_cast_length=ray_cast_length,
1917
        #                                                     percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
1918
        #                                                     )
1919
        #     t2 += t2_data
1920
        #     cart_mesh.reverse_all_transforms()
1921
        print(&#39;NOT DONE!!!&#39;)
1922
        
1923
1924
1925
    def smooth_surface_scalars(self,
1926
                               smooth_only_cartilage=True,
1927
                               scalar_sigma=1.6986436005760381,  # This is a FWHM = 4
1928
                               scalar_array_name=&#39;thickness (mm)&#39;,
1929
                               scalar_array_idx=None,
1930
                               ):
1931
                               
1932
        &#34;&#34;&#34;
1933
        Function to smooth the scalars with name `scalar_array_name` on the bone surface. 
1934
1935
        Parameters
1936
        ----------
1937
        smooth_only_cartilage : bool, optional
1938
            Should we only smooth where there is cartialge &amp; ignore everywhere else, by default True
1939
        scalar_sigma : float, optional
1940
            Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
1941
            default is based on a Full Width Half Maximum (FWHM) of 4mm. 
1942
        scalar_array_name : str
1943
            Name of scalar array to smooth, default &#39;thickness (mm)&#39;.
1944
        scalar_array_idx : int, optional
1945
            Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
1946
        &#34;&#34;&#34;
1947
        if smooth_only_cartilage is True:
1948
            loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 0.01)[0]
1949
        else:
1950
            loc_cartilage = None
1951
        self._mesh = gaussian_smooth_surface_scalars(self._mesh,
1952
                                                     sigma=scalar_sigma,
1953
                                                     idx_coords_to_smooth=loc_cartilage,
1954
                                                     array_name=scalar_array_name,
1955
                                                     array_idx=scalar_array_idx)
1956
1957
    @property
1958
    def list_cartilage_meshes(self):
1959
        &#34;&#34;&#34;
1960
        Convenience function to get the list of cartilage meshes
1961
1962
        Returns
1963
        -------
1964
        list
1965
            A list of `CartilageMesh` objects associated with this bone
1966
        &#34;&#34;&#34;        
1967
        return self._list_cartilage_meshes
1968
    
1969
    @list_cartilage_meshes.setter
1970
    def list_cartilage_meshes(self, new_list_cartilage_meshes):
1971
        &#34;&#34;&#34;
1972
        Convenience function to set the list of cartilage meshes
1973
1974
        Parameters
1975
        ----------
1976
        new_list_cartilage_meshes : list
1977
            A list of `CartilageMesh` objects associated with this bone
1978
        &#34;&#34;&#34;
1979
        if type(new_list_cartilage_meshes) is list:
1980
            for mesh in new_list_cartilage_meshes:
1981
                if type(mesh) != pymskt.mesh.meshes.CartilageMesh:
1982
                    raise TypeError(&#39;Item in `list_cartilage_meshes` is not a `CartilageMesh`&#39;)
1983
        elif type(new_list_cartilage_meshes) is pymskt.mesh.meshes.CartilageMesh:
1984
            new_list_cartilage_meshes = [new_list_cartilage_meshes,]
1985
        self._list_cartilage_meshes = new_list_cartilage_meshes
1986
    
1987
    @property
1988
    def list_cartilage_labels(self):
1989
        &#34;&#34;&#34;
1990
        Convenience function to get the list of labels for cartilage tissues associated
1991
        with this bone. 
1992
1993
        Returns
1994
        -------
1995
        list
1996
            list of `int`s for the cartilage tissues associated with this bone. 
1997
        &#34;&#34;&#34;        
1998
        return self._list_cartilage_labels
1999
    
2000
    @list_cartilage_labels.setter
2001
    def list_cartilage_labels(self, new_list_cartilage_labels):
2002
        &#34;&#34;&#34;
2003
        Convenience function to set the list of labels for cartilage tissues associated
2004
        with this bone
2005
2006
        Parameters
2007
        ----------
2008
        new_list_cartilage_labels : list
2009
            list of `int`s for the cartilage tissues associated with this bone. 
2010
        &#34;&#34;&#34;
2011
        if type(new_list_cartilage_labels) == list:
2012
            for label in new_list_cartilage_labels:
2013
                if type(label) != int:
2014
                    raise TypeError(f&#39;Item in `list_cartilage_labels` is not a `int` - got {type(label)}&#39;)
2015
        elif type(new_list_cartilage_labels) == int:
2016
            new_list_cartilage_labels = [new_list_cartilage_labels,]
2017
        self._list_cartilage_labels = new_list_cartilage_labels
2018
    
2019
    @property
2020
    def crop_percent(self):
2021
        &#34;&#34;&#34;
2022
        Convenience function to get the value that `crop_percent` is set to. 
2023
2024
        Returns
2025
        -------
2026
        float
2027
            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
2028
            when cropping - expressed as a proportion of the width. 
2029
        &#34;&#34;&#34;        
2030
        return self._crop_percent
2031
    
2032
    @crop_percent.setter
2033
    def crop_percent(self, new_crop_percent):
2034
        &#34;&#34;&#34;
2035
        Convenience function to set the value that `crop_percent` is set to. 
2036
2037
        Parameters
2038
        ----------
2039
        new_crop_percent : float
2040
            Floating point &gt; 0.0 indicating how much of the length of the bone should be included
2041
            when cropping - expressed as a proportion of the width. 
2042
        &#34;&#34;&#34;
2043
        if type(new_crop_percent) != float:
2044
            raise TypeError(f&#39;New `crop_percent` provided is type {type(new_crop_percent)} - expected `float`&#39;)
2045
        self._crop_percent = new_crop_percent
2046
    
2047
    @property
2048
    def bone(self):
2049
        &#34;&#34;&#34;
2050
        Convenience function to get the name of the bone in this object. 
2051
2052
        Returns
2053
        -------
2054
        str
2055
            Name of the bone in this object - used to help identify how to crop the bone. 
2056
        &#34;&#34;&#34;        
2057
        return self._bone
2058
2059
    @bone.setter
2060
    def bone(self, new_bone):
2061
        &#34;&#34;&#34;
2062
        Convenience function to set the name of the bone in this object.         
2063
2064
        Parameters
2065
        ----------
2066
        new_bone : str
2067
            Name of the bone in this object - used to help identify how to crop the bone. 
2068
        &#34;&#34;&#34;
2069
        if type(new_bone) != str:
2070
            raise TypeError(f&#39;New bone provided is type {type(new_bone)} - expected `str`&#39;)   
2071
        self._bone = new_bone   </code></pre>
2072
</details>
2073
<h3>Ancestors</h3>
2074
<ul class="hlist">
2075
<li><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></li>
2076
</ul>
2077
<h3>Instance variables</h3>
2078
<dl>
2079
<dt id="pymskt.mesh.meshes.BoneMesh.bone"><code class="name">var <span class="ident">bone</span></code></dt>
2080
<dd>
2081
<div class="desc"><p>Convenience function to get the name of the bone in this object. </p>
2082
<h2 id="returns">Returns</h2>
2083
<dl>
2084
<dt><code>str</code></dt>
2085
<dd>Name of the bone in this object - used to help identify how to crop the bone.</dd>
2086
</dl></div>
2087
<details class="source">
2088
<summary>
2089
<span>Expand source code</span>
2090
</summary>
2091
<pre><code class="python">@property
2092
def bone(self):
2093
    &#34;&#34;&#34;
2094
    Convenience function to get the name of the bone in this object. 
2095
2096
    Returns
2097
    -------
2098
    str
2099
        Name of the bone in this object - used to help identify how to crop the bone. 
2100
    &#34;&#34;&#34;        
2101
    return self._bone</code></pre>
2102
</details>
2103
</dd>
2104
<dt id="pymskt.mesh.meshes.BoneMesh.crop_percent"><code class="name">var <span class="ident">crop_percent</span></code></dt>
2105
<dd>
2106
<div class="desc"><p>Convenience function to get the value that <code>crop_percent</code> is set to. </p>
2107
<h2 id="returns">Returns</h2>
2108
<dl>
2109
<dt><code>float</code></dt>
2110
<dd>Floating point &gt; 0.0 indicating how much of the length of the bone should be included
2111
when cropping - expressed as a proportion of the width.</dd>
2112
</dl></div>
2113
<details class="source">
2114
<summary>
2115
<span>Expand source code</span>
2116
</summary>
2117
<pre><code class="python">@property
2118
def crop_percent(self):
2119
    &#34;&#34;&#34;
2120
    Convenience function to get the value that `crop_percent` is set to. 
2121
2122
    Returns
2123
    -------
2124
    float
2125
        Floating point &gt; 0.0 indicating how much of the length of the bone should be included
2126
        when cropping - expressed as a proportion of the width. 
2127
    &#34;&#34;&#34;        
2128
    return self._crop_percent</code></pre>
2129
</details>
2130
</dd>
2131
<dt id="pymskt.mesh.meshes.BoneMesh.list_cartilage_labels"><code class="name">var <span class="ident">list_cartilage_labels</span></code></dt>
2132
<dd>
2133
<div class="desc"><p>Convenience function to get the list of labels for cartilage tissues associated
2134
with this bone. </p>
2135
<h2 id="returns">Returns</h2>
2136
<dl>
2137
<dt><code>list</code></dt>
2138
<dd>list of <code>int</code>s for the cartilage tissues associated with this bone.</dd>
2139
</dl></div>
2140
<details class="source">
2141
<summary>
2142
<span>Expand source code</span>
2143
</summary>
2144
<pre><code class="python">@property
2145
def list_cartilage_labels(self):
2146
    &#34;&#34;&#34;
2147
    Convenience function to get the list of labels for cartilage tissues associated
2148
    with this bone. 
2149
2150
    Returns
2151
    -------
2152
    list
2153
        list of `int`s for the cartilage tissues associated with this bone. 
2154
    &#34;&#34;&#34;        
2155
    return self._list_cartilage_labels</code></pre>
2156
</details>
2157
</dd>
2158
<dt id="pymskt.mesh.meshes.BoneMesh.list_cartilage_meshes"><code class="name">var <span class="ident">list_cartilage_meshes</span></code></dt>
2159
<dd>
2160
<div class="desc"><p>Convenience function to get the list of cartilage meshes</p>
2161
<h2 id="returns">Returns</h2>
2162
<dl>
2163
<dt><code>list</code></dt>
2164
<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>
2165
</dl></div>
2166
<details class="source">
2167
<summary>
2168
<span>Expand source code</span>
2169
</summary>
2170
<pre><code class="python">@property
2171
def list_cartilage_meshes(self):
2172
    &#34;&#34;&#34;
2173
    Convenience function to get the list of cartilage meshes
2174
2175
    Returns
2176
    -------
2177
    list
2178
        A list of `CartilageMesh` objects associated with this bone
2179
    &#34;&#34;&#34;        
2180
    return self._list_cartilage_meshes</code></pre>
2181
</details>
2182
</dd>
2183
</dl>
2184
<h3>Methods</h3>
2185
<dl>
2186
<dt id="pymskt.mesh.meshes.BoneMesh.assign_cartilage_regions"><code class="name flex">
2187
<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>
2188
</code></dt>
2189
<dd>
2190
<div class="desc"><p>Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
2191
- Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)</p>
2192
<h2 id="parameters">Parameters</h2>
2193
<dl>
2194
<dt><strong><code>image_smooth_var_cart</code></strong> :&ensp;<code>float</code>, optional</dt>
2195
<dd>Variance of gaussian filter to be applied to binary cartilage masks,
2196
by default 0.3125/2</dd>
2197
<dt><strong><code>marching_cubes_threshold</code></strong> :&ensp;<code>float</code>, optional</dt>
2198
<dd>Threshold to create bone surface at, by default 0.5</dd>
2199
<dt><strong><code>ray_cast_length</code></strong> :&ensp;<code>float</code>, optional</dt>
2200
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2201
outter shell), by default 10.0</dd>
2202
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<code>float</code>, optional</dt>
2203
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
2204
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
2205
</dl></div>
2206
<details class="source">
2207
<summary>
2208
<span>Expand source code</span>
2209
</summary>
2210
<pre><code class="python">def assign_cartilage_regions(self,
2211
                             image_smooth_var_cart=0.3125 / 2,
2212
                             marching_cubes_threshold=0.5,
2213
                             ray_cast_length=10.0,
2214
                             percent_ray_length_opposite_direction=0.25):
2215
    &#34;&#34;&#34;
2216
    Assign cartilage regions to the bone surface (e.g. medial/lateral tibial cartilage)
2217
    - Can also be used for femur sub-regions (anterior, medial weight-bearing, etc.)
2218
2219
    Parameters
2220
    ----------
2221
    image_smooth_var_cart : float, optional
2222
        Variance of gaussian filter to be applied to binary cartilage masks, 
2223
        by default 0.3125/2
2224
    marching_cubes_threshold : float, optional
2225
        Threshold to create bone surface at, by default 0.5
2226
    ray_cast_length : float, optional
2227
        Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2228
        outter shell), by default 10.0
2229
    percent_ray_length_opposite_direction : float, optional
2230
        How far to project ray inside of the bone. This is done just in case the cartilage
2231
        surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
2232
    &#34;&#34;&#34;        
2233
    tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
2234
    path_save_tmp_file = os.path.join(&#39;/tmp&#39;, tmp_filename)
2235
    # if self._bone == &#39;femur&#39;:
2236
    #     new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
2237
    #                                                                    fem_cart_label_idx=1)
2238
    #     sitk.WriteImage(new_seg_image, path_save_tmp_file)
2239
    # else:
2240
    sitk.WriteImage(self._seg_image, path_save_tmp_file)
2241
    vtk_seg_reader = read_nrrd(path_save_tmp_file,
2242
                               set_origin_zero=True
2243
                               )
2244
    vtk_seg = vtk_seg_reader.GetOutput()
2245
2246
    seg_transformer = SitkVtkTransformer(self._seg_image)
2247
2248
    # Delete tmp files
2249
    safely_delete_tmp_file(&#39;/tmp&#39;,
2250
                           tmp_filename)
2251
    
2252
    self.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
2253
    labels = np.zeros(self._mesh.GetNumberOfPoints(), dtype=np.int)
2254
2255
    # if cartilage meshes don&#39;t exist yet, then make them. 
2256
    if self._list_cartilage_meshes is None:
2257
        self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
2258
                                     marching_cubes_threshold=marching_cubes_threshold)
2259
    
2260
    # iterate over meshes and add their label (region) 
2261
    for cart_mesh in self._list_cartilage_meshes:
2262
        cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
2263
        node_data = get_cartilage_properties_at_points(self._mesh,
2264
                                                       cart_mesh._mesh,
2265
                                                       t2_vtk_image=None,
2266
                                                       seg_vtk_image=vtk_seg,
2267
                                                       ray_cast_length=ray_cast_length,
2268
                                                       percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
2269
                                                       )
2270
        labels += node_data[1]
2271
        cart_mesh.reverse_all_transforms()
2272
2273
    # Assign the label (region) scalars to the bone mesh surface. 
2274
    label_scalars = numpy_to_vtk(labels)
2275
    label_scalars.SetName(&#39;labels&#39;)
2276
    self._mesh.GetPointData().AddArray(label_scalars)
2277
2278
    self.reverse_all_transforms()</code></pre>
2279
</details>
2280
</dd>
2281
<dt id="pymskt.mesh.meshes.BoneMesh.calc_cartilage_t2"><code class="name flex">
2282
<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>
2283
</code></dt>
2284
<dd>
2285
<div class="desc"><p>Apply cartilage T2 values to bone surface. </p>
2286
<h2 id="parameters">Parameters</h2>
2287
<dl>
2288
<dt><strong><code>path_t2_nrrd</code></strong> :&ensp;<code>str</code></dt>
2289
<dd>Path to nrrd image of T2 map to load / use.</dd>
2290
<dt><strong><code>path_seg_to_t2_transform</code></strong> :&ensp;<code>str</code>, optional</dt>
2291
<dd>Path to a transform file to be used for aligning T2 map with segmentations,
2292
by default None</dd>
2293
<dt><strong><code>ray_cast_length</code></strong> :&ensp;<code>float</code>, optional</dt>
2294
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2295
outter shell), by default 10.0</dd>
2296
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<code>float</code>, optional</dt>
2297
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
2298
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
2299
</dl></div>
2300
<details class="source">
2301
<summary>
2302
<span>Expand source code</span>
2303
</summary>
2304
<pre><code class="python">def calc_cartilage_t2(self,
2305
                      path_t2_nrrd,
2306
                      path_seg_to_t2_transform=None,
2307
                      ray_cast_length=10.0,
2308
                      percent_ray_length_opposite_direction=0.25):
2309
    &#34;&#34;&#34;
2310
    Apply cartilage T2 values to bone surface. 
2311
2312
    Parameters
2313
    ----------
2314
    path_t2_nrrd : str
2315
        Path to nrrd image of T2 map to load / use. 
2316
    path_seg_to_t2_transform : str, optional
2317
        Path to a transform file to be used for aligning T2 map with segmentations, 
2318
        by default None
2319
    ray_cast_length : float, optional
2320
        Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2321
        outter shell), by default 10.0
2322
    percent_ray_length_opposite_direction : float, optional
2323
        How far to project ray inside of the bone. This is done just in case the cartilage
2324
        surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
2325
    &#34;&#34;&#34;        
2326
    print(&#39;Not yet implemented&#39;)
2327
    # if self._list_cartilage_meshes is None:
2328
    #     raise(&#39;Should calculate cartialge thickness before getting T2&#39;)
2329
    #     # ALTERNATIVELY - COULD ALLOW PASSING OF CARTILAGE REGIONS IN HERE
2330
    #     # THOUGH, DOES THAT JUST COMPLICATE THINGS? 
2331
2332
    # if path_seg_transform is not None:
2333
    #     # this is in case there is a transformation needed to align the segmentation with the
2334
    #     # underlying T2 image
2335
    #     seg_transform = sitk.ReadTransform(path_seg_transform)
2336
    #     seg_image = apply_transform_retain_array(self._seg_image,
2337
    #                                              seg_transform,
2338
    #                                              interpolator=sitk.sitkNearestNeighbor)
2339
        
2340
        
2341
    #     versor = get_versor_from_transform(seg_transform)
2342
    #     center_transform, rotate_transform, translate_transform = break_versor_into_center_rotate_translate_transforms(versor)
2343
    #     # first apply negative of center of rotation to mesh
2344
    #     self._mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
2345
    #     # now apply the transform (rotation then translation)
2346
    #     self._mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
2347
    #     self._mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
2348
    #     #then undo the center of rotation
2349
    #     self._mesh.apply_transform_to_mesh(transform=center_transform)
2350
2351
    # # Read t2 map (vtk format)
2352
    # vtk_t2map_reader = read_nrrd(path_t2_nrrd,
2353
    #                              set_origin_zero=True)
2354
    # vtk_t2map = vtk_t2map_reader.GetOutput()
2355
    # sitk_t2map = sitk.ReadImage(path_t2_nrrd)
2356
    # t2_transformer = SitkVtkTransformer(sitk_t2map)
2357
2358
    # self._mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
2359
2360
    # t2 = np.zeros(self._mesh.GetNumberOfPoints())
2361
    # # iterate over meshes and add their t2 to the t2 list. 
2362
    # for cart_mesh in self._list_cartilage_meshes:
2363
    #     if path_seg_to_t2_transform is not None:
2364
    #         # first apply negative of center of rotation to mesh
2365
    #         cart_mesh.apply_transform_to_mesh(transform=center_transform.GetInverse())
2366
    #         # now apply the transform (rotation then translation)
2367
    #         cart_mesh.apply_transform_to_mesh(transform=rotate_transform.GetInverse())
2368
    #         cart_mesh.apply_transform_to_mesh(transform=translate_transform.GetInverse())
2369
    #         #then undo the center of rotation
2370
    #         cart_mesh.apply_transform_to_mesh(transform=center_transform)
2371
2372
    #     cart_mesh.apply_transform_to_mesh(transform=t2_transformer.get_inverse_transform())
2373
    #     _, t2_data = get_cartilage_properties_at_points(self._mesh,
2374
    #                                                     cart_mesh._mesh,
2375
    #                                                     t2_vtk_image=vtk_t2map,
2376
    #                                                     ray_cast_length=ray_cast_length,
2377
    #                                                     percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
2378
    #                                                     )
2379
    #     t2 += t2_data
2380
    #     cart_mesh.reverse_all_transforms()
2381
    print(&#39;NOT DONE!!!&#39;)</code></pre>
2382
</details>
2383
</dd>
2384
<dt id="pymskt.mesh.meshes.BoneMesh.calc_cartilage_thickness"><code class="name flex">
2385
<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>
2386
</code></dt>
2387
<dd>
2388
<div class="desc"><p>Using bone mesh (<code>_mesh</code>) and the list of cartilage meshes (<code>list_cartilage_meshes</code>)
2389
calcualte the cartilage thickness for each node on the bone surface. </p>
2390
<h2 id="parameters">Parameters</h2>
2391
<dl>
2392
<dt><strong><code>list_cartilage_labels</code></strong> :&ensp;<code>list</code>, optional</dt>
2393
<dd>Cartilag labels to be used to create cartilage meshes (if they dont
2394
exist), by default None</dd>
2395
<dt><strong><code>list_cartilage_meshes</code></strong> :&ensp;<code>list</code>, optional</dt>
2396
<dd>Cartilage meshes to be used for calculating cart thickness, by default None</dd>
2397
<dt><strong><code>image_smooth_var_cart</code></strong> :&ensp;<code>float</code>, optional</dt>
2398
<dd>Variance of gaussian filter to be applied to binary cartilage masks,
2399
by default 0.3125/2</dd>
2400
<dt><strong><code>marching_cubes_threshold</code></strong> :&ensp;<code>float</code>, optional</dt>
2401
<dd>Threshold to create bone surface at, by default 0.5</dd>
2402
<dt><strong><code>ray_cast_length</code></strong> :&ensp;<code>float</code>, optional</dt>
2403
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2404
outter shell), by default 10.0</dd>
2405
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<code>float</code>, optional</dt>
2406
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
2407
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
2408
</dl>
2409
<h2 id="raises">Raises</h2>
2410
<dl>
2411
<dt><code>Exception</code></dt>
2412
<dd>No cartilage available (either <code>list_cartilage_meshes</code> or <code>list_cartilage_labels</code>)</dd>
2413
</dl></div>
2414
<details class="source">
2415
<summary>
2416
<span>Expand source code</span>
2417
</summary>
2418
<pre><code class="python">def calc_cartilage_thickness(self,
2419
                             list_cartilage_labels=None,
2420
                             list_cartilage_meshes=None,
2421
                             image_smooth_var_cart=0.3125 / 2,
2422
                             marching_cubes_threshold=0.5,
2423
                             ray_cast_length=10.0,
2424
                             percent_ray_length_opposite_direction=0.25
2425
                             ):
2426
    &#34;&#34;&#34;
2427
    Using bone mesh (`_mesh`) and the list of cartilage meshes (`list_cartilage_meshes`)
2428
    calcualte the cartilage thickness for each node on the bone surface. 
2429
2430
    Parameters
2431
    ----------
2432
    list_cartilage_labels : list, optional
2433
        Cartilag labels to be used to create cartilage meshes (if they dont
2434
        exist), by default None
2435
    list_cartilage_meshes : list, optional
2436
        Cartilage meshes to be used for calculating cart thickness, by default None
2437
    image_smooth_var_cart : float, optional
2438
        Variance of gaussian filter to be applied to binary cartilage masks, 
2439
        by default 0.3125/2
2440
    marching_cubes_threshold : float, optional
2441
        Threshold to create bone surface at, by default 0.5
2442
    ray_cast_length : float, optional
2443
        Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
2444
        outter shell), by default 10.0
2445
    percent_ray_length_opposite_direction : float, optional
2446
        How far to project ray inside of the bone. This is done just in case the cartilage
2447
        surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
2448
2449
    Raises
2450
    ------
2451
    Exception
2452
        No cartilage available (either `list_cartilage_meshes` or `list_cartilage_labels`)
2453
    &#34;&#34;&#34;        
2454
    # If new cartilage infor/labels are provided, then replace existing with these ones. 
2455
    if list_cartilage_meshes is not None: self._list_cartilage_meshes = list_cartilage_meshes
2456
    if list_cartilage_labels is not None: self._list_cartilage_labels = list_cartilage_labels
2457
2458
    # If no cartilage stuff provided, then cant do this function - raise exception. 
2459
    if (self._list_cartilage_meshes is None) &amp; (self._list_cartilage_labels is None):
2460
        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;)
2461
2462
    # if cartilage meshes don&#39;t exist yet, then make them. 
2463
    if self._list_cartilage_meshes is None:
2464
        self.create_cartilage_meshes(image_smooth_var_cart=image_smooth_var_cart,
2465
                                     marching_cubes_threshold=marching_cubes_threshold)
2466
    
2467
    # pre-allocate empty thicknesses so that as labels are iterated over, they can all be appended to the same bone. 
2468
    thicknesses = np.zeros(self._mesh.GetNumberOfPoints())
2469
    
2470
    # iterate over meshes and add their thicknesses to the thicknesses list. 
2471
    for cart_mesh in self._list_cartilage_meshes:
2472
        node_data = get_cartilage_properties_at_points(self._mesh,
2473
                                                       cart_mesh._mesh,
2474
                                                       t2_vtk_image=None,
2475
                                                       #   seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
2476
                                                       seg_vtk_image=None,
2477
                                                       ray_cast_length=ray_cast_length,
2478
                                                       percent_ray_length_opposite_direction=percent_ray_length_opposite_direction
2479
                                                       )
2480
        thicknesses += node_data
2481
    
2482
    # Assign the thickness scalars to the bone mesh surface. 
2483
    thickness_scalars = numpy_to_vtk(thicknesses)
2484
    thickness_scalars.SetName(&#39;thickness (mm)&#39;)
2485
    self._mesh.GetPointData().SetScalars(thickness_scalars)</code></pre>
2486
</details>
2487
</dd>
2488
<dt id="pymskt.mesh.meshes.BoneMesh.create_cartilage_meshes"><code class="name flex">
2489
<span>def <span class="ident">create_cartilage_meshes</span></span>(<span>self, image_smooth_var_cart=0.15625, marching_cubes_threshold=0.5)</span>
2490
</code></dt>
2491
<dd>
2492
<div class="desc"><p>Helper function to create the list of cartilage meshes from the list of cartilage
2493
labels. </p>
2494
<h2 id="parameters">Parameters</h2>
2495
<dl>
2496
<dt><strong><code>image_smooth_var_cart</code></strong> :&ensp;<code>float</code></dt>
2497
<dd>Variance to smooth cartilage segmentations before finding surface using continuous
2498
marching cubes.</dd>
2499
<dt><strong><code>marching_cubes_threshold</code></strong> :&ensp;<code>float</code></dt>
2500
<dd>Threshold value to create cartilage surface at from segmentation images.</dd>
2501
</dl>
2502
<h2 id="notes">Notes</h2>
2503
<p>?? Should this function just be everything inside the for loop and then that
2504
function gets called somewhere else?</p></div>
2505
<details class="source">
2506
<summary>
2507
<span>Expand source code</span>
2508
</summary>
2509
<pre><code class="python">def create_cartilage_meshes(self,
2510
                            image_smooth_var_cart=0.3125 / 2,
2511
                            marching_cubes_threshold=0.5):
2512
    &#34;&#34;&#34;
2513
    Helper function to create the list of cartilage meshes from the list of cartilage
2514
    labels. 
2515
2516
    Parameters
2517
    ----------
2518
    image_smooth_var_cart : float
2519
        Variance to smooth cartilage segmentations before finding surface using continuous
2520
        marching cubes.             
2521
    marching_cubes_threshold : float
2522
        Threshold value to create cartilage surface at from segmentation images. 
2523
2524
    Notes
2525
    -----
2526
    ?? Should this function just be everything inside the for loop and then that 
2527
    function gets called somewhere else? 
2528
    &#34;&#34;&#34;        
2529
2530
    self._list_cartilage_meshes = []
2531
    for cart_label_idx in self._list_cartilage_labels:
2532
        seg_array_view = sitk.GetArrayViewFromImage(self._seg_image)
2533
        n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
2534
        if n_pixels_with_cart == 0:
2535
            warnings.warn(
2536
                f&#34;Not analyzing cartilage for label {cart_label_idx} because it doesnt have any pixels!&#34;,
2537
                UserWarning
2538
            )
2539
        else:
2540
            cart_mesh = CartilageMesh(seg_image=self._seg_image,
2541
                                        label_idx=cart_label_idx)
2542
            cart_mesh.create_mesh(smooth_image_var=image_smooth_var_cart,
2543
                                    marching_cubes_threshold=marching_cubes_threshold)
2544
            self._list_cartilage_meshes.append(cart_mesh)</code></pre>
2545
</details>
2546
</dd>
2547
<dt id="pymskt.mesh.meshes.BoneMesh.create_mesh"><code class="name flex">
2548
<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>
2549
</code></dt>
2550
<dd>
2551
<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.
2552
Bones might need to be cropped (this isnt necessary for cartilage)
2553
So, adding this functionality to the processing steps before the bone mesh is created.</p>
2554
<p>All functionality, except for that relevant to <code>crop_percent</code> is the same as:
2555
<code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">Mesh.create_mesh()</a></code>. </p>
2556
<p>Create a surface mesh from the classes <code>_seg_image</code>. If <code>_seg_image</code>
2557
does not exist, then read it in using <code>read_seg_image</code>. </p>
2558
<h2 id="parameters">Parameters</h2>
2559
<dl>
2560
<dt><strong><code>smooth_image</code></strong> :&ensp;<code>bool</code>, optional</dt>
2561
<dd>Should the <code>_seg_image</code> be gaussian filtered, by default True</dd>
2562
<dt><strong><code>smooth_image_var</code></strong> :&ensp;<code>float</code>, optional</dt>
2563
<dd>Variance of gaussian filter to apply to <code>_seg_image</code>, by default 0.3125/2</dd>
2564
<dt><strong><code>marching_cubes_threshold</code></strong> :&ensp;<code>float</code>, optional</dt>
2565
<dd>Threshold contour level to create surface mesh on, by default 0.5</dd>
2566
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2567
<dd>Label value / index to create mesh from, by default None</dd>
2568
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
2569
<dd>Minimum number of continuous pixels to include segmentation island
2570
in the surface mesh creation, by default None</dd>
2571
<dt><strong><code>crop_percent</code></strong> :&ensp;<code>[type]</code>, optional</dt>
2572
<dd>[description], by default None</dd>
2573
</dl>
2574
<h2 id="raises">Raises</h2>
2575
<dl>
2576
<dt><code>Exception</code></dt>
2577
<dd>If cropping &amp; bone is not femur or tibia, then raise an error.</dd>
2578
<dt><code>Exception</code></dt>
2579
<dd>If the total number of pixels segmentated (<code>n_pixels_labelled</code>) is
2580
&lt; <code>min_n_pixels</code> then there is no object in the image.</dd>
2581
<dt><code>Exception</code></dt>
2582
<dd>If no <code>_seg_image</code> and no <code>label_idx</code> then we don't know what tissue to create the
2583
surface mesh from.</dd>
2584
<dt><code>Exception</code></dt>
2585
<dd>If no <code>_seg_image</code> or <code>path_seg_image</code> then we have no image to create mesh from.</dd>
2586
</dl></div>
2587
<details class="source">
2588
<summary>
2589
<span>Expand source code</span>
2590
</summary>
2591
<pre><code class="python">def create_mesh(self, 
2592
                smooth_image=True, 
2593
                smooth_image_var=0.3125 / 2, 
2594
                marching_cubes_threshold=0.5, 
2595
                label_idx=None, 
2596
                min_n_pixels=None,
2597
                crop_percent=None
2598
                ):
2599
    &#34;&#34;&#34;
2600
    This is an extension of `Mesh.create_mesh` that enables cropping of bones. 
2601
    Bones might need to be cropped (this isnt necessary for cartilage)
2602
    So, adding this functionality to the processing steps before the bone mesh is created.
2603
2604
    All functionality, except for that relevant to `crop_percent` is the same as:
2605
    `Mesh.create_mesh`. 
2606
    
2607
    Create a surface mesh from the classes `_seg_image`. If `_seg_image`
2608
    does not exist, then read it in using `read_seg_image`. 
2609
2610
    Parameters
2611
    ----------
2612
    smooth_image : bool, optional
2613
        Should the `_seg_image` be gaussian filtered, by default True
2614
    smooth_image_var : float, optional
2615
        Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
2616
    marching_cubes_threshold : float, optional
2617
        Threshold contour level to create surface mesh on, by default 0.5
2618
    label_idx : int, optional
2619
        Label value / index to create mesh from, by default None
2620
    min_n_pixels : int, optional
2621
        Minimum number of continuous pixels to include segmentation island
2622
        in the surface mesh creation, by default None
2623
    crop_percent : [type], optional
2624
        [description], by default None
2625
2626
    Raises
2627
    ------
2628
    Exception
2629
        If cropping &amp; bone is not femur or tibia, then raise an error. 
2630
    Exception
2631
        If the total number of pixels segmentated (`n_pixels_labelled`) is
2632
        &lt; `min_n_pixels` then there is no object in the image.  
2633
    Exception
2634
        If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
2635
        surface mesh from. 
2636
    Exception
2637
        If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
2638
    &#34;&#34;&#34;     
2639
    
2640
    if self._seg_image is None:
2641
        self.read_seg_image()
2642
    
2643
    # Bones might need to be cropped (this isnt necessary for cartilage)
2644
    # So, adding this functionality to the processing steps before the bone mesh is created
2645
    if crop_percent is not None:
2646
        self._crop_percent = crop_percent
2647
    if (self._crop_percent is not None) and ((&#39;femur&#39; in self._bone) or (&#39;tibia&#39; in self._bone)):
2648
        if &#39;femur&#39; in self._bone:
2649
            bone_crop_distal = True
2650
        elif &#39;tibia&#39; in self._bone:
2651
            bone_crop_distal = False
2652
        else:
2653
            raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.format(self._bone))
2654
2655
        self._seg_image = crop_bone_based_on_width(self._seg_image,
2656
                                                   self._label_idx,
2657
                                                   percent_width_to_crop_height=self._crop_percent,
2658
                                                   bone_crop_distal=bone_crop_distal)
2659
    elif self._crop_percent is not None:
2660
        warnings.warn(f&#39;Trying to crop bone, but {self._bone} specified and only bones `femur`&#39;,
2661
                      &#39;or `tibia` currently supported for cropping. If using another bone, consider&#39;,
2662
                      &#39;making a pull request. If cropping not desired, set `crop_percent=None`.&#39;
2663
            )
2664
    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>
2665
</details>
2666
</dd>
2667
<dt id="pymskt.mesh.meshes.BoneMesh.smooth_surface_scalars"><code class="name flex">
2668
<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>
2669
</code></dt>
2670
<dd>
2671
<div class="desc"><p>Function to smooth the scalars with name <code>scalar_array_name</code> on the bone surface. </p>
2672
<h2 id="parameters">Parameters</h2>
2673
<dl>
2674
<dt><strong><code>smooth_only_cartilage</code></strong> :&ensp;<code>bool</code>, optional</dt>
2675
<dd>Should we only smooth where there is cartialge &amp; ignore everywhere else, by default True</dd>
2676
<dt><strong><code>scalar_sigma</code></strong> :&ensp;<code>float</code>, optional</dt>
2677
<dd>Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
2678
default is based on a Full Width Half Maximum (FWHM) of 4mm.</dd>
2679
<dt><strong><code>scalar_array_name</code></strong> :&ensp;<code>str</code></dt>
2680
<dd>Name of scalar array to smooth, default 'thickness (mm)'.</dd>
2681
<dt><strong><code>scalar_array_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2682
<dd>Index of the scalar array to smooth (alternative to using <code>scalar_array_name</code>) , by default None</dd>
2683
</dl></div>
2684
<details class="source">
2685
<summary>
2686
<span>Expand source code</span>
2687
</summary>
2688
<pre><code class="python">def smooth_surface_scalars(self,
2689
                           smooth_only_cartilage=True,
2690
                           scalar_sigma=1.6986436005760381,  # This is a FWHM = 4
2691
                           scalar_array_name=&#39;thickness (mm)&#39;,
2692
                           scalar_array_idx=None,
2693
                           ):
2694
                           
2695
    &#34;&#34;&#34;
2696
    Function to smooth the scalars with name `scalar_array_name` on the bone surface. 
2697
2698
    Parameters
2699
    ----------
2700
    smooth_only_cartilage : bool, optional
2701
        Should we only smooth where there is cartialge &amp; ignore everywhere else, by default True
2702
    scalar_sigma : float, optional
2703
        Smoothing sigma (standard deviation or sqrt(variance)) for gaussian filter, by default 1.6986436005760381
2704
        default is based on a Full Width Half Maximum (FWHM) of 4mm. 
2705
    scalar_array_name : str
2706
        Name of scalar array to smooth, default &#39;thickness (mm)&#39;.
2707
    scalar_array_idx : int, optional
2708
        Index of the scalar array to smooth (alternative to using `scalar_array_name`) , by default None
2709
    &#34;&#34;&#34;
2710
    if smooth_only_cartilage is True:
2711
        loc_cartilage = np.where(vtk_to_numpy(self._mesh.GetPointData().GetArray(&#39;thickness (mm)&#39;)) &gt; 0.01)[0]
2712
    else:
2713
        loc_cartilage = None
2714
    self._mesh = gaussian_smooth_surface_scalars(self._mesh,
2715
                                                 sigma=scalar_sigma,
2716
                                                 idx_coords_to_smooth=loc_cartilage,
2717
                                                 array_name=scalar_array_name,
2718
                                                 array_idx=scalar_array_idx)</code></pre>
2719
</details>
2720
</dd>
2721
</dl>
2722
<h3>Inherited members</h3>
2723
<ul class="hlist">
2724
<li><code><b><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></b></code>:
2725
<ul class="hlist">
2726
<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>
2727
<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>
2728
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
2729
<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>
2730
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
2731
<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>
2732
<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>
2733
<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>
2734
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
2735
<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>
2736
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
2737
<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>
2738
<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>
2739
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
2740
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
2741
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
2742
</ul>
2743
</li>
2744
</ul>
2745
</dd>
2746
<dt id="pymskt.mesh.meshes.CartilageMesh"><code class="flex name class">
2747
<span>class <span class="ident">CartilageMesh</span></span>
2748
<span>(</span><span>mesh=None, seg_image=None, path_seg_image=None, label_idx=None, min_n_pixels=1000)</span>
2749
</code></dt>
2750
<dd>
2751
<div class="desc"><p>Class to create, store, and process cartilage meshes</p>
2752
<h2 id="parameters">Parameters</h2>
2753
<dl>
2754
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
2755
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
2756
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
2757
<dd>Segmentation image that can be used to create surface mesh - used
2758
instead of mesh, by default None</dd>
2759
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
2760
<dd>Path to a medical image (.nrrd) to load and create mesh from,
2761
by default None</dd>
2762
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2763
<dd>Label of anatomy of interest, by default None</dd>
2764
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
2765
<dd>All islands smaller than this size are dropped, by default 5000</dd>
2766
</dl>
2767
<h2 id="attributes">Attributes</h2>
2768
<dl>
2769
<dt><strong><code>_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
2770
<dd>Item passed from <strong>init</strong>, or created during life of class.
2771
This is the main surface mesh of this class.</dd>
2772
<dt><strong><code>_seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code></dt>
2773
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
2774
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code></dt>
2775
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
2776
and then creat surface mesh <code>_mesh</code></dd>
2777
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code></dt>
2778
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
2779
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code></dt>
2780
<dd>Minimum number of pixels for an isolated island of a segmentation to be
2781
retained</dd>
2782
<dt><strong><code>list_applied_transforms</code></strong> :&ensp;<code>list</code></dt>
2783
<dd>A list of transformations applied to a surface mesh.
2784
This list allows for undoing of most recent transform, or undoing
2785
all of them by iterating over the list in reverse.</dd>
2786
</dl>
2787
<h2 id="methods">Methods</h2>
2788
<p>Initialize Mesh class</p>
2789
<h2 id="parameters_1">Parameters</h2>
2790
<dl>
2791
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
2792
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
2793
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
2794
<dd>Segmentation image that can be used to create surface mesh - used
2795
instead of mesh, by default None</dd>
2796
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
2797
<dd>Path to a medical image (.nrrd) to load and create mesh from,
2798
by default None</dd>
2799
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2800
<dd>Label of anatomy of interest, by default None</dd>
2801
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
2802
<dd>All islands smaller than this size are dropped, by default 5000</dd>
2803
</dl></div>
2804
<details class="source">
2805
<summary>
2806
<span>Expand source code</span>
2807
</summary>
2808
<pre><code class="python">class CartilageMesh(Mesh):
2809
    &#34;&#34;&#34;
2810
    Class to create, store, and process cartilage meshes
2811
2812
    Parameters
2813
    ----------
2814
    mesh : vtk.vtkPolyData, optional
2815
        vtkPolyData object that is basis of surface mesh, by default None
2816
    seg_image : SimpleITK.Image, optional
2817
        Segmentation image that can be used to create surface mesh - used 
2818
        instead of mesh, by default None
2819
    path_seg_image : str, optional
2820
        Path to a medical image (.nrrd) to load and create mesh from, 
2821
        by default None
2822
    label_idx : int, optional
2823
        Label of anatomy of interest, by default None
2824
    min_n_pixels : int, optional
2825
        All islands smaller than this size are dropped, by default 5000
2826
2827
2828
    Attributes
2829
    ----------
2830
    _mesh : vtk.vtkPolyData
2831
        Item passed from __init__, or created during life of class. 
2832
        This is the main surface mesh of this class. 
2833
    _seg_image : SimpleITK.Image
2834
        Segmentation image that can be used to create mesh. This is optional.
2835
    path_seg_image : str
2836
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
2837
        and then creat surface mesh `_mesh` 
2838
    label_idx : int
2839
        Integer of anatomy to create surface mesh from `_seg_image`
2840
    min_n_pixels : int
2841
        Minimum number of pixels for an isolated island of a segmentation to be
2842
        retained
2843
    list_applied_transforms : list
2844
        A list of transformations applied to a surface mesh. 
2845
        This list allows for undoing of most recent transform, or undoing
2846
        all of them by iterating over the list in reverse. 
2847
2848
    Methods
2849
    ----------
2850
2851
    &#34;&#34;&#34;
2852
2853
    def __init__(self,
2854
                 mesh=None,
2855
                 seg_image=None,
2856
                 path_seg_image=None,
2857
                 label_idx=None,
2858
                 min_n_pixels=1000
2859
                 ):
2860
        super().__init__(mesh=mesh,
2861
                         seg_image=seg_image,
2862
                         path_seg_image=path_seg_image,
2863
                         label_idx=label_idx,
2864
                         min_n_pixels=min_n_pixels)</code></pre>
2865
</details>
2866
<h3>Ancestors</h3>
2867
<ul class="hlist">
2868
<li><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></li>
2869
</ul>
2870
<h3>Inherited members</h3>
2871
<ul class="hlist">
2872
<li><code><b><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></b></code>:
2873
<ul class="hlist">
2874
<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>
2875
<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>
2876
<li><code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">create_mesh</a></code></li>
2877
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
2878
<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>
2879
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
2880
<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>
2881
<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>
2882
<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>
2883
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
2884
<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>
2885
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
2886
<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>
2887
<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>
2888
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
2889
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
2890
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
2891
</ul>
2892
</li>
2893
</ul>
2894
</dd>
2895
<dt id="pymskt.mesh.meshes.Mesh"><code class="flex name class">
2896
<span>class <span class="ident">Mesh</span></span>
2897
<span>(</span><span>mesh=None, seg_image=None, path_seg_image=None, label_idx=None, min_n_pixels=5000)</span>
2898
</code></dt>
2899
<dd>
2900
<div class="desc"><p>An object to contain surface meshes for musculoskeletal anatomy. Includes helper
2901
functions to build surface meshes, to process them, and to save them. </p>
2902
<h2 id="parameters">Parameters</h2>
2903
<dl>
2904
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
2905
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
2906
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
2907
<dd>Segmentation image that can be used to create surface mesh - used
2908
instead of mesh, by default None</dd>
2909
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
2910
<dd>Path to a medical image (.nrrd) to load and create mesh from,
2911
by default None</dd>
2912
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2913
<dd>Label of anatomy of interest, by default None</dd>
2914
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
2915
<dd>All islands smaller than this size are dropped, by default 5000</dd>
2916
</dl>
2917
<h2 id="attributes">Attributes</h2>
2918
<dl>
2919
<dt><strong><code>_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
2920
<dd>Item passed from <strong>init</strong>, or created during life of class.
2921
This is the main surface mesh of this class.</dd>
2922
<dt><strong><code>_seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code></dt>
2923
<dd>Segmentation image that can be used to create mesh. This is optional.</dd>
2924
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code></dt>
2925
<dd>Path to medical image (.nrrd) that can be loaded to create <code>_seg_image</code>
2926
and then creat surface mesh <code>_mesh</code></dd>
2927
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code></dt>
2928
<dd>Integer of anatomy to create surface mesh from <code>_seg_image</code></dd>
2929
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code></dt>
2930
<dd>Minimum number of pixels for an isolated island of a segmentation to be
2931
retained</dd>
2932
<dt><strong><code>list_applied_transforms</code></strong> :&ensp;<code>list</code></dt>
2933
<dd>A list of transformations applied to a surface mesh.
2934
This list allows for undoing of most recent transform, or undoing
2935
all of them by iterating over the list in reverse.</dd>
2936
</dl>
2937
<h2 id="methods">Methods</h2>
2938
<p>Initialize Mesh class</p>
2939
<h2 id="parameters_1">Parameters</h2>
2940
<dl>
2941
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code>, optional</dt>
2942
<dd>vtkPolyData object that is basis of surface mesh, by default None</dd>
2943
<dt><strong><code>seg_image</code></strong> :&ensp;<code>SimpleITK.Image</code>, optional</dt>
2944
<dd>Segmentation image that can be used to create surface mesh - used
2945
instead of mesh, by default None</dd>
2946
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
2947
<dd>Path to a medical image (.nrrd) to load and create mesh from,
2948
by default None</dd>
2949
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
2950
<dd>Label of anatomy of interest, by default None</dd>
2951
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
2952
<dd>All islands smaller than this size are dropped, by default 5000</dd>
2953
</dl></div>
2954
<details class="source">
2955
<summary>
2956
<span>Expand source code</span>
2957
</summary>
2958
<pre><code class="python">class Mesh:
2959
    &#34;&#34;&#34;
2960
    An object to contain surface meshes for musculoskeletal anatomy. Includes helper
2961
    functions to build surface meshes, to process them, and to save them. 
2962
2963
    Parameters
2964
    ----------
2965
    mesh : vtk.vtkPolyData, optional
2966
        vtkPolyData object that is basis of surface mesh, by default None
2967
    seg_image : SimpleITK.Image, optional
2968
        Segmentation image that can be used to create surface mesh - used 
2969
        instead of mesh, by default None
2970
    path_seg_image : str, optional
2971
        Path to a medical image (.nrrd) to load and create mesh from, 
2972
        by default None
2973
    label_idx : int, optional
2974
        Label of anatomy of interest, by default None
2975
    min_n_pixels : int, optional
2976
        All islands smaller than this size are dropped, by default 5000
2977
2978
2979
    Attributes
2980
    ----------
2981
    _mesh : vtk.vtkPolyData
2982
        Item passed from __init__, or created during life of class. 
2983
        This is the main surface mesh of this class. 
2984
    _seg_image : SimpleITK.Image
2985
        Segmentation image that can be used to create mesh. This is optional.
2986
    path_seg_image : str
2987
        Path to medical image (.nrrd) that can be loaded to create `_seg_image`
2988
        and then creat surface mesh `_mesh` 
2989
    label_idx : int
2990
        Integer of anatomy to create surface mesh from `_seg_image`
2991
    min_n_pixels : int
2992
        Minimum number of pixels for an isolated island of a segmentation to be
2993
        retained
2994
    list_applied_transforms : list
2995
        A list of transformations applied to a surface mesh. 
2996
        This list allows for undoing of most recent transform, or undoing
2997
        all of them by iterating over the list in reverse. 
2998
2999
    Methods
3000
    ----------
3001
3002
    &#34;&#34;&#34;    
3003
    def __init__(self,
3004
                 mesh=None,
3005
                 seg_image=None,
3006
                 path_seg_image=None,
3007
                 label_idx=None,
3008
                 min_n_pixels=5000
3009
                 ):
3010
        &#34;&#34;&#34;
3011
        Initialize Mesh class
3012
3013
        Parameters
3014
        ----------
3015
        mesh : vtk.vtkPolyData, optional
3016
            vtkPolyData object that is basis of surface mesh, by default None
3017
        seg_image : SimpleITK.Image, optional
3018
            Segmentation image that can be used to create surface mesh - used 
3019
            instead of mesh, by default None
3020
        path_seg_image : str, optional
3021
            Path to a medical image (.nrrd) to load and create mesh from, 
3022
            by default None
3023
        label_idx : int, optional
3024
            Label of anatomy of interest, by default None
3025
        min_n_pixels : int, optional
3026
            All islands smaller than this size are dropped, by default 5000
3027
        &#34;&#34;&#34;      
3028
        if type(mesh) in (str,): #accept path like objects?  
3029
            print(&#39;mesh string passed, loading mesh from disk&#39;)
3030
            self._mesh = io.read_vtk(mesh)
3031
        else:
3032
            self._mesh = mesh
3033
        self._seg_image = seg_image
3034
        self._path_seg_image = path_seg_image
3035
        self._label_idx = label_idx
3036
        self._min_n_pixels = min_n_pixels
3037
3038
        self._list_applied_transforms = []
3039
3040
    def read_seg_image(self,
3041
                       path_seg_image=None):
3042
        &#34;&#34;&#34;
3043
        Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
3044
3045
        Parameters
3046
        ----------
3047
        path_seg_image : str, optional
3048
            Path to the medical image file to be loaded in, by default None
3049
3050
        Raises
3051
        ------
3052
        Exception
3053
            If path_seg_image does not exist, exception is raised. 
3054
        &#34;&#34;&#34;        
3055
        # If passing new location/seg image name, then update variables. 
3056
        if path_seg_image is not None:
3057
            self._path_seg_image = path_seg_image
3058
        
3059
        # If seg image location / name exist, then load image else raise exception
3060
        if (self._path_seg_image is not None):
3061
            self._seg_image = sitk.ReadImage(self._path_seg_image)
3062
        else:
3063
            raise Exception(&#39;No file path (self._path_seg_image) provided.&#39;)
3064
    
3065
    def create_mesh(self,
3066
                    smooth_image=True,
3067
                    smooth_image_var=0.3125 / 2,
3068
                    marching_cubes_threshold=0.5,
3069
                    label_idx=None,
3070
                    min_n_pixels=None):
3071
        &#34;&#34;&#34;
3072
        Create a surface mesh from the classes `_seg_image`. If `_seg_image`
3073
        does not exist, then read it in using `read_seg_image`. 
3074
3075
        Parameters
3076
        ----------
3077
        smooth_image : bool, optional
3078
            Should the `_seg_image` be gaussian filtered, by default True
3079
        smooth_image_var : float, optional
3080
            Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
3081
        marching_cubes_threshold : float, optional
3082
            Threshold contour level to create surface mesh on, by default 0.5
3083
        label_idx : int, optional
3084
            Label value / index to create mesh from, by default None
3085
        min_n_pixels : int, optional
3086
            Minimum number of continuous pixels to include segmentation island
3087
            in the surface mesh creation, by default None
3088
3089
        Raises
3090
        ------
3091
        Exception
3092
            If the total number of pixels segmentated (`n_pixels_labelled`) is
3093
            &lt; `min_n_pixels` then there is no object in the image.  
3094
        Exception
3095
            If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
3096
            surface mesh from. 
3097
        Exception
3098
            If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
3099
        &#34;&#34;&#34;        
3100
        # allow assigning label idx during mesh creation step. 
3101
        if label_idx is not None:
3102
            self._label_idx = label_idx
3103
        
3104
        if self._seg_image is None:
3105
            self.read_seg_image()
3106
        
3107
        # Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
3108
        if min_n_pixels is None: min_n_pixels = self._min_n_pixels
3109
        seg_view = sitk.GetArrayViewFromImage(self._seg_image)
3110
        n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
3111
3112
        if n_pixels_labelled &lt; min_n_pixels:
3113
            raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
3114
                                                                                                                               marching_cubes_threshold))
3115
        tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
3116
        self._mesh = create_surface_mesh(self._seg_image,
3117
                                         self._label_idx,
3118
                                         smooth_image_var,
3119
                                         loc_tmp_save=&#39;/tmp&#39;,
3120
                                         tmp_filename=tmp_filename,
3121
                                         mc_threshold=marching_cubes_threshold,
3122
                                         filter_binary_image=smooth_image
3123
                                         )
3124
        safely_delete_tmp_file(&#39;/tmp&#39;,
3125
                               tmp_filename)
3126
    
3127
    def save_mesh(self,
3128
                  filepath):
3129
        &#34;&#34;&#34;
3130
        Save the surface mesh from this class to disk. 
3131
3132
        Parameters
3133
        ----------
3134
        filepath : str
3135
            Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
3136
        &#34;&#34;&#34;        
3137
        io.write_vtk(self._mesh, filepath)
3138
3139
    def resample_surface(self,
3140
                         subdivisions=2,
3141
                         clusters=10000
3142
                         ):
3143
        &#34;&#34;&#34;
3144
        Resample a surface mesh using the ACVD algorithm: 
3145
        Version used: 
3146
        - https://github.com/pyvista/pyacvd
3147
        Original version w/ more references: 
3148
        - https://github.com/valette/ACVD
3149
3150
        Parameters
3151
        ----------
3152
        subdivisions : int, optional
3153
            Subdivide the mesh to have more points before clustering, by default 2
3154
            Probably not necessary for very dense meshes.
3155
        clusters : int, optional
3156
            The number of clusters (points/vertices) to create during resampling 
3157
            surafce, by default 10000
3158
            - This is not exact, might have slight differences. 
3159
        &#34;&#34;&#34;
3160
        self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)
3161
3162
    def apply_transform_to_mesh(self,
3163
                                transform=None,
3164
                                transformer=None,
3165
                                save_transform=True):
3166
        &#34;&#34;&#34;
3167
        Apply a transformation to the surface mesh. 
3168
3169
        Parameters
3170
        ----------
3171
        transform : vtk.vtkTransform, optional
3172
            Transformation to apply to mesh, by default None
3173
        transformer : vtk.vtkTransformFilter, optional
3174
            Can supply transformFilter directly, by default None
3175
        save_transform : bool, optional
3176
            Should transform be saved to list of applied transforms, by default True
3177
3178
        Raises
3179
        ------
3180
        Exception
3181
            No `transform` or `transformer` supplied - have not transformation
3182
            to apply. 
3183
        &#34;&#34;&#34;        
3184
        if (transform is not None) &amp; (transformer is None):
3185
            transformer = vtk.vtkTransformPolyDataFilter()
3186
            transformer.SetTransform(transform)
3187
3188
        elif (transform is None) &amp; (transformer is not None):
3189
            transform = transformer.GetTransform()
3190
3191
        if transformer is not None:
3192
            transformer.SetInputData(self._mesh)
3193
            transformer.Update()
3194
            self._mesh = vtk_deep_copy(transformer.GetOutput())
3195
3196
            if save_transform is True:
3197
                self._list_applied_transforms.append(transform)
3198
                
3199
        else:
3200
            raise Exception(&#39;No transform or transformer provided&#39;)
3201
3202
    def reverse_most_recent_transform(self):
3203
        &#34;&#34;&#34;
3204
        Function to undo the most recent transformation stored in self._list_applied_transforms
3205
        &#34;&#34;&#34;
3206
        transform = self._list_applied_transforms.pop()
3207
        transform.Inverse()
3208
        self.apply_transform_to_mesh(transform=transform, save_transform=False)
3209
3210
    def reverse_all_transforms(self):
3211
        &#34;&#34;&#34;
3212
        Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
3213
        &#34;&#34;&#34;
3214
        while len(self._list_applied_transforms) &gt; 0:
3215
            self.reverse_most_recent_transform()
3216
    
3217
    def non_rigidly_register(
3218
        self,
3219
        other_mesh,
3220
        as_source=True,
3221
        apply_transform_to_mesh=True,
3222
        return_transformed_mesh=False,
3223
        **kwargs
3224
    ):  
3225
        &#34;&#34;&#34;
3226
        Function to perform non rigid registration between this mesh and another mesh. 
3227
3228
        Parameters
3229
        ----------
3230
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
3231
            Other mesh to use in registration process
3232
        as_source : bool, optional
3233
            Should the current mesh (in this object) be the source or the target, by default True
3234
        apply_transform_to_mesh : bool, optional
3235
            If as_source is True should we apply transformation to internal mesh, by default True
3236
        return_transformed_mesh : bool, optional
3237
            Should we return the registered mesh, by default False
3238
3239
        Returns
3240
        -------
3241
        _type_
3242
            _description_
3243
        &#34;&#34;&#34;
3244
        # Setup the source &amp; target meshes based on `as_source``
3245
        if as_source is True:
3246
            source = self._mesh
3247
            target = other_mesh
3248
        elif as_source is False:
3249
            source = other_mesh
3250
            target = self._mesh
3251
3252
        # Get registered mesh (source to target)
3253
        source_transformed_to_target = non_rigidly_register(
3254
                target_mesh=target,
3255
                source_mesh=source,
3256
                **kwargs
3257
            ) 
3258
3259
        # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
3260
        if (as_source is True) &amp; (apply_transform_to_mesh is True):
3261
            self._mesh = source_transformed_to_target
3262
        
3263
        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
3264
        if (as_source is False) or ((as_source is True) &amp; (return_transformed_mesh is True)):
3265
            return source_transformed_to_target
3266
3267
    def rigidly_register(
3268
        self,
3269
        other_mesh,
3270
        as_source=True,
3271
        apply_transform_to_mesh=True,
3272
        return_transformed_mesh=False,
3273
        return_transform=False,
3274
        max_n_iter=100,
3275
        n_landmarks=1000,
3276
        reg_mode=&#39;similarity&#39;
3277
3278
    ):
3279
        &#34;&#34;&#34;
3280
        Function to perform rigid registration between this mesh and another mesh. 
3281
3282
        Parameters
3283
        ----------
3284
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
3285
            Other mesh to use in registration process
3286
        as_source : bool, optional
3287
            Should the current mesh (in this object) be the source or the target, by default True
3288
        apply_transform_to_mesh : bool, optional
3289
            If as_source is True should we apply transformation to internal mesh, by default True
3290
        return_transformed_mesh : bool, optional
3291
            Should we return the registered mesh, by default False
3292
        max_n_iter : int, optional
3293
            Maximum number of iterations to perform, by default 100
3294
        n_landmarks : int, optional
3295
            Number of landmarks to use in registration, by default 1000
3296
        reg_mode : str, optional
3297
            Mode of registration to use, by default &#39;similarity&#39; (similarity, rigid, or affine)
3298
3299
        Returns
3300
        -------
3301
        _type_
3302
            _description_
3303
        &#34;&#34;&#34;
3304
3305
        if (return_transform is True) &amp; (return_transformed_mesh is True):
3306
            raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
3307
3308
        if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
3309
            other_mesh = other_mesh.mesh
3310
3311
        # Setup the source &amp; target meshes based on `as_source``
3312
        if as_source is True:
3313
            source = self._mesh
3314
            target = other_mesh
3315
        elif as_source is False:
3316
            source = other_mesh
3317
            target = self._mesh
3318
        
3319
        icp_transform = get_icp_transform(
3320
            source=source,
3321
            target=target,
3322
            max_n_iter=max_n_iter,
3323
            n_landmarks=n_landmarks,
3324
            reg_mode=reg_mode
3325
        )
3326
3327
        # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
3328
        if (as_source is True) &amp; (apply_transform_to_mesh is True):
3329
            self.apply_transform_to_mesh(transform=icp_transform)
3330
3331
            if return_transformed_mesh is True:
3332
                return self._mesh
3333
            
3334
            elif return_transform is True:
3335
                return icp_transform
3336
        
3337
        # curent mesh is target, or is source &amp; want to return mesh, then return it.  
3338
        elif (as_source is False) &amp; (return_transformed_mesh is True):
3339
            return apply_transform(source=source, transform=icp_transform)
3340
3341
        else:
3342
            raise Exception(&#39;Nothing to return from rigid registration.&#39;)
3343
3344
    def copy_scalars_from_other_mesh_to_currect(
3345
        self,
3346
        other_mesh,
3347
        new_scalars_name=&#39;scalars_from_other_mesh&#39;,
3348
        weighted_avg=True,                  # Use weighted average, otherwise guassian smooth transfer
3349
        n_closest=3,
3350
        sigma=1.,
3351
        idx_coords_to_smooth_base=None,
3352
        idx_coords_to_smooth_other=None,
3353
        set_non_smoothed_scalars_to_zero=True,
3354
    ):
3355
        &#34;&#34;&#34;
3356
        Convenience function to enable easy transfer scalars from another mesh to the current. 
3357
        Can use either a gaussian smoothing function, or transfer using nearest neighbours. 
3358
3359
        ** This function requires that the `other_mesh` is non-rigidly registered to the surface
3360
            of the mesh inside of this class. Or rigidly registered but using the same anatomy that
3361
            VERY closely matches. Otherwise, the transfered scalars will be bad.  
3362
3363
        Parameters
3364
        ----------
3365
        other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
3366
            Mesh we want to copy 
3367
        new_scalars_name : str, optional
3368
           What to name the scalars being transfered to this current mesh, by default &#39;scalars_from_other_mesh&#39;
3369
        weighted_avg : bool, optional
3370
            Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
3371
        n_closest : int, optional
3372
            If `weighted_avg` True, the number of nearest neighbours to use, by default 3
3373
        sigma : float, optional
3374
            If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
3375
        idx_coords_to_smooth_base : list, optional
3376
            If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
3377
        idx_coords_to_smooth_other : list, optional
3378
            If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
3379
        set_non_smoothed_scalars_to_zero : bool, optional
3380
            Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
3381
        &#34;&#34;&#34;
3382
        if type(other_mesh) is Mesh:
3383
            other_mesh = other_mesh.mesh
3384
        elif type(other_mesh) is vtk.vtkPolyData:
3385
            pass
3386
        else:
3387
            raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
3388
3389
        if weighted_avg is True:
3390
            transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
3391
                self._mesh,
3392
                other_mesh,
3393
                n=n_closest
3394
            )
3395
        else:
3396
            transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
3397
                self._mesh,
3398
                other_mesh,
3399
                sigma=sigma,
3400
                idx_coords_to_smooth_base=idx_coords_to_smooth_base,
3401
                idx_coords_to_smooth_second=idx_coords_to_smooth_other,
3402
                set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
3403
            )
3404
        if (new_scalars_name is None) &amp; (weighted_avg is True):
3405
            if transferred_scalars.shape[1] &gt; 1:
3406
                n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
3407
                array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
3408
                for idx, array_name in enumerate(array_names):
3409
                    vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
3410
                    vtk_transferred_scalars.SetName(array_name)
3411
                    self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
3412
                return
3413
3414
        vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
3415
        vtk_transferred_scalars.SetName(new_scalars_name)
3416
        self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
3417
3418
    @property
3419
    def seg_image(self):
3420
        &#34;&#34;&#34;
3421
        Return the `_seg_image` object
3422
3423
        Returns
3424
        -------
3425
        SimpleITK.Image
3426
            Segmentation image used to build the surface mesh
3427
        &#34;&#34;&#34;        
3428
        return self._seg_image
3429
3430
    @seg_image.setter
3431
    def seg_image(self, new_seg_image):
3432
        &#34;&#34;&#34;
3433
        Set the `_seg_image` of the class to be the inputted `new_seg_image`. 
3434
3435
        Parameters
3436
        ----------
3437
        new_seg_image : SimpleITK.Image
3438
            New image to use for creating surface mesh. This can be used to provide image to
3439
            class if it was not provided during `__init__`
3440
        &#34;&#34;&#34;        
3441
        self._seg_image = new_seg_image
3442
3443
    @property
3444
    def mesh(self):
3445
        &#34;&#34;&#34;
3446
        Return the `_mesh` object
3447
3448
        Returns
3449
        -------
3450
        vtk.vtkPolyData
3451
            The main mesh of this class. 
3452
        &#34;&#34;&#34;        
3453
        return self._mesh
3454
3455
    @mesh.setter
3456
    def mesh(self, new_mesh):
3457
        &#34;&#34;&#34;
3458
        Set the `_mesh` of the class to be the inputted `new_mesh`
3459
3460
        Parameters
3461
        ----------
3462
        new_mesh : vtk.vtkPolyData
3463
            New mesh for this class - or a method to provide a mesh to the class
3464
            after `__init__` has already been run. 
3465
        &#34;&#34;&#34;        
3466
        self._mesh = new_mesh
3467
    
3468
    @property
3469
    def point_coords(self):
3470
        &#34;&#34;&#34;
3471
        Convenience function to return the vertices (point coordinates) for the surface mesh. 
3472
3473
        Returns
3474
        -------
3475
        numpy.ndarray
3476
            Mx3 numpy array containing the x/y/z position of each vertex of the mesh. 
3477
        &#34;&#34;&#34;        
3478
        return get_mesh_physical_point_coords(self._mesh)
3479
    
3480
    @point_coords.setter
3481
    def point_coords(self, new_point_coords):
3482
        &#34;&#34;&#34;
3483
        Convenience function to change/update the vertices/points locations
3484
3485
        Parameters
3486
        ----------
3487
        new_point_coords : numpy.ndarray
3488
            n_points X 3 numpy array to replace exisiting point coordinate locations
3489
            This can be used to easily/quickly update the x/y/z position of a set of points on a surface mesh. 
3490
            The `new_point_coords` must include the same number of points as the mesh contains. 
3491
        &#34;&#34;&#34;        
3492
        orig_point_coords = get_mesh_physical_point_coords(self._mesh)
3493
        if new_point_coords.shape == orig_point_coords.shape:
3494
            self._mesh.GetPoints().SetData(numpy_to_vtk(new_point_coords))
3495
3496
    
3497
    @property
3498
    def path_seg_image(self):
3499
        &#34;&#34;&#34;
3500
        Convenience function to get the `path_seg_image`
3501
3502
        Returns
3503
        -------
3504
        str
3505
            Path to the segmentation image
3506
        &#34;&#34;&#34;        
3507
        return self._path_seg_image
3508
    
3509
    @path_seg_image.setter
3510
    def path_seg_image(self, new_path_seg_image):
3511
        &#34;&#34;&#34;
3512
        Convenience function to set the `path_seg_image`
3513
3514
        Parameters
3515
        ----------
3516
        new_path_seg_image : str
3517
            String to where segmentation image that should be loaded is. 
3518
        &#34;&#34;&#34;        
3519
        self._path_seg_image = new_path_seg_image
3520
    
3521
    @property
3522
    def label_idx(self):
3523
        &#34;&#34;&#34;
3524
        Convenience function to get `label_idx`
3525
3526
        Returns
3527
        -------
3528
        int
3529
            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
3530
        &#34;&#34;&#34;        
3531
        return self._label_idx
3532
    
3533
    @label_idx.setter
3534
    def label_idx(self, new_label_idx):
3535
        &#34;&#34;&#34;
3536
        Convenience function to set `label_idx`
3537
3538
        Parameters
3539
        ----------
3540
        new_label_idx : int
3541
            Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
3542
        &#34;&#34;&#34;        
3543
        self._label_idx = new_label_idx
3544
3545
    @property
3546
    def min_n_pixels(self):
3547
        &#34;&#34;&#34;
3548
        Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh. 
3549
3550
        Returns
3551
        -------
3552
        int
3553
            Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised. 
3554
        &#34;&#34;&#34;        
3555
        return self._min_n_pixels
3556
    
3557
    @min_n_pixels.setter
3558
    def min_n_pixels(self, new_min_n_pixels):
3559
        &#34;&#34;&#34;
3560
        Convenience function to set the minimum number of pixels for a segmentation region to be created as a mesh. 
3561
3562
        Parameters
3563
        ----------
3564
        new_min_n_pixels : int
3565
            Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised. 
3566
        &#34;&#34;&#34;        
3567
        self._min_n_pixels = new_min_n_pixels
3568
3569
    @property
3570
    def list_applied_transforms(self):
3571
        &#34;&#34;&#34;
3572
        Convenience function to get the list of transformations that have been applied to this mesh. 
3573
3574
        Returns
3575
        -------
3576
        list
3577
            List of vtk.vtkTransform objects that have been applied to the current mesh. 
3578
        &#34;&#34;&#34;        
3579
        return self._list_applied_transforms</code></pre>
3580
</details>
3581
<h3>Subclasses</h3>
3582
<ul class="hlist">
3583
<li><a title="pymskt.mesh.meshes.BoneMesh" href="#pymskt.mesh.meshes.BoneMesh">BoneMesh</a></li>
3584
<li><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></li>
3585
</ul>
3586
<h3>Instance variables</h3>
3587
<dl>
3588
<dt id="pymskt.mesh.meshes.Mesh.label_idx"><code class="name">var <span class="ident">label_idx</span></code></dt>
3589
<dd>
3590
<div class="desc"><p>Convenience function to get <code>label_idx</code></p>
3591
<h2 id="returns">Returns</h2>
3592
<dl>
3593
<dt><code>int</code></dt>
3594
<dd>Integer indeicating the index/value of the tissues in <code>seg_image</code> associated with this mesh.</dd>
3595
</dl></div>
3596
<details class="source">
3597
<summary>
3598
<span>Expand source code</span>
3599
</summary>
3600
<pre><code class="python">@property
3601
def label_idx(self):
3602
    &#34;&#34;&#34;
3603
    Convenience function to get `label_idx`
3604
3605
    Returns
3606
    -------
3607
    int
3608
        Integer indeicating the index/value of the tissues in `seg_image` associated with this mesh. 
3609
    &#34;&#34;&#34;        
3610
    return self._label_idx</code></pre>
3611
</details>
3612
</dd>
3613
<dt id="pymskt.mesh.meshes.Mesh.list_applied_transforms"><code class="name">var <span class="ident">list_applied_transforms</span></code></dt>
3614
<dd>
3615
<div class="desc"><p>Convenience function to get the list of transformations that have been applied to this mesh. </p>
3616
<h2 id="returns">Returns</h2>
3617
<dl>
3618
<dt><code>list</code></dt>
3619
<dd>List of vtk.vtkTransform objects that have been applied to the current mesh.</dd>
3620
</dl></div>
3621
<details class="source">
3622
<summary>
3623
<span>Expand source code</span>
3624
</summary>
3625
<pre><code class="python">@property
3626
def list_applied_transforms(self):
3627
    &#34;&#34;&#34;
3628
    Convenience function to get the list of transformations that have been applied to this mesh. 
3629
3630
    Returns
3631
    -------
3632
    list
3633
        List of vtk.vtkTransform objects that have been applied to the current mesh. 
3634
    &#34;&#34;&#34;        
3635
    return self._list_applied_transforms</code></pre>
3636
</details>
3637
</dd>
3638
<dt id="pymskt.mesh.meshes.Mesh.mesh"><code class="name">var <span class="ident">mesh</span></code></dt>
3639
<dd>
3640
<div class="desc"><p>Return the <code>_mesh</code> object</p>
3641
<h2 id="returns">Returns</h2>
3642
<dl>
3643
<dt><code>vtk.vtkPolyData</code></dt>
3644
<dd>The main mesh of this class.</dd>
3645
</dl></div>
3646
<details class="source">
3647
<summary>
3648
<span>Expand source code</span>
3649
</summary>
3650
<pre><code class="python">@property
3651
def mesh(self):
3652
    &#34;&#34;&#34;
3653
    Return the `_mesh` object
3654
3655
    Returns
3656
    -------
3657
    vtk.vtkPolyData
3658
        The main mesh of this class. 
3659
    &#34;&#34;&#34;        
3660
    return self._mesh</code></pre>
3661
</details>
3662
</dd>
3663
<dt id="pymskt.mesh.meshes.Mesh.min_n_pixels"><code class="name">var <span class="ident">min_n_pixels</span></code></dt>
3664
<dd>
3665
<div class="desc"><p>Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh. </p>
3666
<h2 id="returns">Returns</h2>
3667
<dl>
3668
<dt><code>int</code></dt>
3669
<dd>Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised.</dd>
3670
</dl></div>
3671
<details class="source">
3672
<summary>
3673
<span>Expand source code</span>
3674
</summary>
3675
<pre><code class="python">@property
3676
def min_n_pixels(self):
3677
    &#34;&#34;&#34;
3678
    Convenience function to get the minimum number of pixels for a segmentation region to be created as a mesh. 
3679
3680
    Returns
3681
    -------
3682
    int
3683
        Minimum number of pixels needed to create a mesh. Less than this and it will be skipped / error raised. 
3684
    &#34;&#34;&#34;        
3685
    return self._min_n_pixels</code></pre>
3686
</details>
3687
</dd>
3688
<dt id="pymskt.mesh.meshes.Mesh.path_seg_image"><code class="name">var <span class="ident">path_seg_image</span></code></dt>
3689
<dd>
3690
<div class="desc"><p>Convenience function to get the <code>path_seg_image</code></p>
3691
<h2 id="returns">Returns</h2>
3692
<dl>
3693
<dt><code>str</code></dt>
3694
<dd>Path to the segmentation image</dd>
3695
</dl></div>
3696
<details class="source">
3697
<summary>
3698
<span>Expand source code</span>
3699
</summary>
3700
<pre><code class="python">@property
3701
def path_seg_image(self):
3702
    &#34;&#34;&#34;
3703
    Convenience function to get the `path_seg_image`
3704
3705
    Returns
3706
    -------
3707
    str
3708
        Path to the segmentation image
3709
    &#34;&#34;&#34;        
3710
    return self._path_seg_image</code></pre>
3711
</details>
3712
</dd>
3713
<dt id="pymskt.mesh.meshes.Mesh.point_coords"><code class="name">var <span class="ident">point_coords</span></code></dt>
3714
<dd>
3715
<div class="desc"><p>Convenience function to return the vertices (point coordinates) for the surface mesh. </p>
3716
<h2 id="returns">Returns</h2>
3717
<dl>
3718
<dt><code>numpy.ndarray</code></dt>
3719
<dd>Mx3 numpy array containing the x/y/z position of each vertex of the mesh.</dd>
3720
</dl></div>
3721
<details class="source">
3722
<summary>
3723
<span>Expand source code</span>
3724
</summary>
3725
<pre><code class="python">@property
3726
def point_coords(self):
3727
    &#34;&#34;&#34;
3728
    Convenience function to return the vertices (point coordinates) for the surface mesh. 
3729
3730
    Returns
3731
    -------
3732
    numpy.ndarray
3733
        Mx3 numpy array containing the x/y/z position of each vertex of the mesh. 
3734
    &#34;&#34;&#34;        
3735
    return get_mesh_physical_point_coords(self._mesh)</code></pre>
3736
</details>
3737
</dd>
3738
<dt id="pymskt.mesh.meshes.Mesh.seg_image"><code class="name">var <span class="ident">seg_image</span></code></dt>
3739
<dd>
3740
<div class="desc"><p>Return the <code>_seg_image</code> object</p>
3741
<h2 id="returns">Returns</h2>
3742
<dl>
3743
<dt><code>SimpleITK.Image</code></dt>
3744
<dd>Segmentation image used to build the surface mesh</dd>
3745
</dl></div>
3746
<details class="source">
3747
<summary>
3748
<span>Expand source code</span>
3749
</summary>
3750
<pre><code class="python">@property
3751
def seg_image(self):
3752
    &#34;&#34;&#34;
3753
    Return the `_seg_image` object
3754
3755
    Returns
3756
    -------
3757
    SimpleITK.Image
3758
        Segmentation image used to build the surface mesh
3759
    &#34;&#34;&#34;        
3760
    return self._seg_image</code></pre>
3761
</details>
3762
</dd>
3763
</dl>
3764
<h3>Methods</h3>
3765
<dl>
3766
<dt id="pymskt.mesh.meshes.Mesh.apply_transform_to_mesh"><code class="name flex">
3767
<span>def <span class="ident">apply_transform_to_mesh</span></span>(<span>self, transform=None, transformer=None, save_transform=True)</span>
3768
</code></dt>
3769
<dd>
3770
<div class="desc"><p>Apply a transformation to the surface mesh. </p>
3771
<h2 id="parameters">Parameters</h2>
3772
<dl>
3773
<dt><strong><code>transform</code></strong> :&ensp;<code>vtk.vtkTransform</code>, optional</dt>
3774
<dd>Transformation to apply to mesh, by default None</dd>
3775
<dt><strong><code>transformer</code></strong> :&ensp;<code>vtk.vtkTransformFilter</code>, optional</dt>
3776
<dd>Can supply transformFilter directly, by default None</dd>
3777
<dt><strong><code>save_transform</code></strong> :&ensp;<code>bool</code>, optional</dt>
3778
<dd>Should transform be saved to list of applied transforms, by default True</dd>
3779
</dl>
3780
<h2 id="raises">Raises</h2>
3781
<dl>
3782
<dt><code>Exception</code></dt>
3783
<dd>No <code>transform</code> or <code>transformer</code> supplied - have not transformation
3784
to apply.</dd>
3785
</dl></div>
3786
<details class="source">
3787
<summary>
3788
<span>Expand source code</span>
3789
</summary>
3790
<pre><code class="python">def apply_transform_to_mesh(self,
3791
                            transform=None,
3792
                            transformer=None,
3793
                            save_transform=True):
3794
    &#34;&#34;&#34;
3795
    Apply a transformation to the surface mesh. 
3796
3797
    Parameters
3798
    ----------
3799
    transform : vtk.vtkTransform, optional
3800
        Transformation to apply to mesh, by default None
3801
    transformer : vtk.vtkTransformFilter, optional
3802
        Can supply transformFilter directly, by default None
3803
    save_transform : bool, optional
3804
        Should transform be saved to list of applied transforms, by default True
3805
3806
    Raises
3807
    ------
3808
    Exception
3809
        No `transform` or `transformer` supplied - have not transformation
3810
        to apply. 
3811
    &#34;&#34;&#34;        
3812
    if (transform is not None) &amp; (transformer is None):
3813
        transformer = vtk.vtkTransformPolyDataFilter()
3814
        transformer.SetTransform(transform)
3815
3816
    elif (transform is None) &amp; (transformer is not None):
3817
        transform = transformer.GetTransform()
3818
3819
    if transformer is not None:
3820
        transformer.SetInputData(self._mesh)
3821
        transformer.Update()
3822
        self._mesh = vtk_deep_copy(transformer.GetOutput())
3823
3824
        if save_transform is True:
3825
            self._list_applied_transforms.append(transform)
3826
            
3827
    else:
3828
        raise Exception(&#39;No transform or transformer provided&#39;)</code></pre>
3829
</details>
3830
</dd>
3831
<dt id="pymskt.mesh.meshes.Mesh.copy_scalars_from_other_mesh_to_currect"><code class="name flex">
3832
<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>
3833
</code></dt>
3834
<dd>
3835
<div class="desc"><p>Convenience function to enable easy transfer scalars from another mesh to the current.
3836
Can use either a gaussian smoothing function, or transfer using nearest neighbours. </p>
3837
<p>** This function requires that the <code>other_mesh</code> is non-rigidly registered to the surface
3838
of the mesh inside of this class. Or rigidly registered but using the same anatomy that
3839
VERY closely matches. Otherwise, the transfered scalars will be bad.
3840
</p>
3841
<h2 id="parameters">Parameters</h2>
3842
<dl>
3843
<dt><strong><code>other_mesh</code></strong> :&ensp;<code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
3844
<dd>Mesh we want to copy</dd>
3845
<dt><strong><code>new_scalars_name</code></strong> :&ensp;<code>str</code>, optional</dt>
3846
<dd>&nbsp;</dd>
3847
<dt>What to name the scalars being transfered to this current mesh, by default 'scalars_from_other_mesh'</dt>
3848
<dt><strong><code>weighted_avg</code></strong> :&ensp;<code>bool</code>, optional</dt>
3849
<dd>Should we use <code>weighted average</code> or <code>gaussian smooth</code> methods for transfer, by default True</dd>
3850
<dt><strong><code>n_closest</code></strong> :&ensp;<code>int</code>, optional</dt>
3851
<dd>If <code>weighted_avg</code> True, the number of nearest neighbours to use, by default 3</dd>
3852
<dt><strong><code>sigma</code></strong> :&ensp;<code>float</code>, optional</dt>
3853
<dd>If <code>weighted_avg</code> False, the standard deviation of gaussian kernel, by default 1.</dd>
3854
<dt><strong><code>idx_coords_to_smooth_base</code></strong> :&ensp;<code>list</code>, optional</dt>
3855
<dd>If <code>weighted_avg</code> False, list of indices from current mesh to use in transfer, by default None</dd>
3856
<dt><strong><code>idx_coords_to_smooth_other</code></strong> :&ensp;<code>list</code>, optional</dt>
3857
<dd>If <code>weighted_avg</code> False, list of indices from <code>other_mesh</code> to use in transfer, by default None</dd>
3858
<dt><strong><code>set_non_smoothed_scalars_to_zero</code></strong> :&ensp;<code>bool</code>, optional</dt>
3859
<dd>Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True</dd>
3860
</dl></div>
3861
<details class="source">
3862
<summary>
3863
<span>Expand source code</span>
3864
</summary>
3865
<pre><code class="python">def copy_scalars_from_other_mesh_to_currect(
3866
    self,
3867
    other_mesh,
3868
    new_scalars_name=&#39;scalars_from_other_mesh&#39;,
3869
    weighted_avg=True,                  # Use weighted average, otherwise guassian smooth transfer
3870
    n_closest=3,
3871
    sigma=1.,
3872
    idx_coords_to_smooth_base=None,
3873
    idx_coords_to_smooth_other=None,
3874
    set_non_smoothed_scalars_to_zero=True,
3875
):
3876
    &#34;&#34;&#34;
3877
    Convenience function to enable easy transfer scalars from another mesh to the current. 
3878
    Can use either a gaussian smoothing function, or transfer using nearest neighbours. 
3879
3880
    ** This function requires that the `other_mesh` is non-rigidly registered to the surface
3881
        of the mesh inside of this class. Or rigidly registered but using the same anatomy that
3882
        VERY closely matches. Otherwise, the transfered scalars will be bad.  
3883
3884
    Parameters
3885
    ----------
3886
    other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
3887
        Mesh we want to copy 
3888
    new_scalars_name : str, optional
3889
       What to name the scalars being transfered to this current mesh, by default &#39;scalars_from_other_mesh&#39;
3890
    weighted_avg : bool, optional
3891
        Should we use `weighted average` or `gaussian smooth` methods for transfer, by default True
3892
    n_closest : int, optional
3893
        If `weighted_avg` True, the number of nearest neighbours to use, by default 3
3894
    sigma : float, optional
3895
        If `weighted_avg` False, the standard deviation of gaussian kernel, by default 1.
3896
    idx_coords_to_smooth_base : list, optional
3897
        If `weighted_avg` False, list of indices from current mesh to use in transfer, by default None
3898
    idx_coords_to_smooth_other : list, optional
3899
        If `weighted_avg` False, list of indices from `other_mesh` to use in transfer, by default None
3900
    set_non_smoothed_scalars_to_zero : bool, optional
3901
        Should all other indices (not included in idx_coords_to_smooth_other) be set to 0, by default True
3902
    &#34;&#34;&#34;
3903
    if type(other_mesh) is Mesh:
3904
        other_mesh = other_mesh.mesh
3905
    elif type(other_mesh) is vtk.vtkPolyData:
3906
        pass
3907
    else:
3908
        raise TypeError(f&#39;other_mesh must be type `pymskt.mesh.Mesh` or `vtk.vtkPolyData` and received: {type(other_mesh)}&#39;)
3909
3910
    if weighted_avg is True:
3911
        transferred_scalars = transfer_mesh_scalars_get_weighted_average_n_closest(
3912
            self._mesh,
3913
            other_mesh,
3914
            n=n_closest
3915
        )
3916
    else:
3917
        transferred_scalars = smooth_scalars_from_second_mesh_onto_base(
3918
            self._mesh,
3919
            other_mesh,
3920
            sigma=sigma,
3921
            idx_coords_to_smooth_base=idx_coords_to_smooth_base,
3922
            idx_coords_to_smooth_second=idx_coords_to_smooth_other,
3923
            set_non_smoothed_scalars_to_zero=set_non_smoothed_scalars_to_zero
3924
        )
3925
    if (new_scalars_name is None) &amp; (weighted_avg is True):
3926
        if transferred_scalars.shape[1] &gt; 1:
3927
            n_arrays = other_mesh.GetPointData().GetNumberOfArrays()
3928
            array_names = [other_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
3929
            for idx, array_name in enumerate(array_names):
3930
                vtk_transferred_scalars = numpy_to_vtk(transferred_scalars[:,idx])
3931
                vtk_transferred_scalars.SetName(array_name)
3932
                self._mesh.GetPointData().AddArray(vtk_transferred_scalars)
3933
            return
3934
3935
    vtk_transferred_scalars = numpy_to_vtk(transferred_scalars)
3936
    vtk_transferred_scalars.SetName(new_scalars_name)
3937
    self._mesh.GetPointData().AddArray(vtk_transferred_scalars)</code></pre>
3938
</details>
3939
</dd>
3940
<dt id="pymskt.mesh.meshes.Mesh.create_mesh"><code class="name flex">
3941
<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>
3942
</code></dt>
3943
<dd>
3944
<div class="desc"><p>Create a surface mesh from the classes <code>_seg_image</code>. If <code>_seg_image</code>
3945
does not exist, then read it in using <code>read_seg_image</code>. </p>
3946
<h2 id="parameters">Parameters</h2>
3947
<dl>
3948
<dt><strong><code>smooth_image</code></strong> :&ensp;<code>bool</code>, optional</dt>
3949
<dd>Should the <code>_seg_image</code> be gaussian filtered, by default True</dd>
3950
<dt><strong><code>smooth_image_var</code></strong> :&ensp;<code>float</code>, optional</dt>
3951
<dd>Variance of gaussian filter to apply to <code>_seg_image</code>, by default 0.3125/2</dd>
3952
<dt><strong><code>marching_cubes_threshold</code></strong> :&ensp;<code>float</code>, optional</dt>
3953
<dd>Threshold contour level to create surface mesh on, by default 0.5</dd>
3954
<dt><strong><code>label_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
3955
<dd>Label value / index to create mesh from, by default None</dd>
3956
<dt><strong><code>min_n_pixels</code></strong> :&ensp;<code>int</code>, optional</dt>
3957
<dd>Minimum number of continuous pixels to include segmentation island
3958
in the surface mesh creation, by default None</dd>
3959
</dl>
3960
<h2 id="raises">Raises</h2>
3961
<dl>
3962
<dt><code>Exception</code></dt>
3963
<dd>If the total number of pixels segmentated (<code>n_pixels_labelled</code>) is
3964
&lt; <code>min_n_pixels</code> then there is no object in the image.</dd>
3965
<dt><code>Exception</code></dt>
3966
<dd>If no <code>_seg_image</code> and no <code>label_idx</code> then we don't know what tissue to create the
3967
surface mesh from.</dd>
3968
<dt><code>Exception</code></dt>
3969
<dd>If no <code>_seg_image</code> or <code>path_seg_image</code> then we have no image to create mesh from.</dd>
3970
</dl></div>
3971
<details class="source">
3972
<summary>
3973
<span>Expand source code</span>
3974
</summary>
3975
<pre><code class="python">def create_mesh(self,
3976
                smooth_image=True,
3977
                smooth_image_var=0.3125 / 2,
3978
                marching_cubes_threshold=0.5,
3979
                label_idx=None,
3980
                min_n_pixels=None):
3981
    &#34;&#34;&#34;
3982
    Create a surface mesh from the classes `_seg_image`. If `_seg_image`
3983
    does not exist, then read it in using `read_seg_image`. 
3984
3985
    Parameters
3986
    ----------
3987
    smooth_image : bool, optional
3988
        Should the `_seg_image` be gaussian filtered, by default True
3989
    smooth_image_var : float, optional
3990
        Variance of gaussian filter to apply to `_seg_image`, by default 0.3125/2
3991
    marching_cubes_threshold : float, optional
3992
        Threshold contour level to create surface mesh on, by default 0.5
3993
    label_idx : int, optional
3994
        Label value / index to create mesh from, by default None
3995
    min_n_pixels : int, optional
3996
        Minimum number of continuous pixels to include segmentation island
3997
        in the surface mesh creation, by default None
3998
3999
    Raises
4000
    ------
4001
    Exception
4002
        If the total number of pixels segmentated (`n_pixels_labelled`) is
4003
        &lt; `min_n_pixels` then there is no object in the image.  
4004
    Exception
4005
        If no `_seg_image` and no `label_idx` then we don&#39;t know what tissue to create the 
4006
        surface mesh from. 
4007
    Exception
4008
        If no `_seg_image` or `path_seg_image` then we have no image to create mesh from. 
4009
    &#34;&#34;&#34;        
4010
    # allow assigning label idx during mesh creation step. 
4011
    if label_idx is not None:
4012
        self._label_idx = label_idx
4013
    
4014
    if self._seg_image is None:
4015
        self.read_seg_image()
4016
    
4017
    # Ensure the image has a certain number of pixels with the label of interest, otherwise there might be an issue.
4018
    if min_n_pixels is None: min_n_pixels = self._min_n_pixels
4019
    seg_view = sitk.GetArrayViewFromImage(self._seg_image)
4020
    n_pixels_labelled = sum(seg_view[seg_view == self._label_idx])
4021
4022
    if n_pixels_labelled &lt; min_n_pixels:
4023
        raise Exception(&#39;The mesh does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
4024
                                                                                                                           marching_cubes_threshold))
4025
    tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
4026
    self._mesh = create_surface_mesh(self._seg_image,
4027
                                     self._label_idx,
4028
                                     smooth_image_var,
4029
                                     loc_tmp_save=&#39;/tmp&#39;,
4030
                                     tmp_filename=tmp_filename,
4031
                                     mc_threshold=marching_cubes_threshold,
4032
                                     filter_binary_image=smooth_image
4033
                                     )
4034
    safely_delete_tmp_file(&#39;/tmp&#39;,
4035
                           tmp_filename)</code></pre>
4036
</details>
4037
</dd>
4038
<dt id="pymskt.mesh.meshes.Mesh.non_rigidly_register"><code class="name flex">
4039
<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>
4040
</code></dt>
4041
<dd>
4042
<div class="desc"><p>Function to perform non rigid registration between this mesh and another mesh. </p>
4043
<h2 id="parameters">Parameters</h2>
4044
<dl>
4045
<dt><strong><code>other_mesh</code></strong> :&ensp;<code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
4046
<dd>Other mesh to use in registration process</dd>
4047
<dt><strong><code>as_source</code></strong> :&ensp;<code>bool</code>, optional</dt>
4048
<dd>Should the current mesh (in this object) be the source or the target, by default True</dd>
4049
<dt><strong><code>apply_transform_to_mesh</code></strong> :&ensp;<code>bool</code>, optional</dt>
4050
<dd>If as_source is True should we apply transformation to internal mesh, by default True</dd>
4051
<dt><strong><code>return_transformed_mesh</code></strong> :&ensp;<code>bool</code>, optional</dt>
4052
<dd>Should we return the registered mesh, by default False</dd>
4053
</dl>
4054
<h2 id="returns">Returns</h2>
4055
<dl>
4056
<dt><code>_type_</code></dt>
4057
<dd><em>description</em></dd>
4058
</dl></div>
4059
<details class="source">
4060
<summary>
4061
<span>Expand source code</span>
4062
</summary>
4063
<pre><code class="python">def non_rigidly_register(
4064
    self,
4065
    other_mesh,
4066
    as_source=True,
4067
    apply_transform_to_mesh=True,
4068
    return_transformed_mesh=False,
4069
    **kwargs
4070
):  
4071
    &#34;&#34;&#34;
4072
    Function to perform non rigid registration between this mesh and another mesh. 
4073
4074
    Parameters
4075
    ----------
4076
    other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
4077
        Other mesh to use in registration process
4078
    as_source : bool, optional
4079
        Should the current mesh (in this object) be the source or the target, by default True
4080
    apply_transform_to_mesh : bool, optional
4081
        If as_source is True should we apply transformation to internal mesh, by default True
4082
    return_transformed_mesh : bool, optional
4083
        Should we return the registered mesh, by default False
4084
4085
    Returns
4086
    -------
4087
    _type_
4088
        _description_
4089
    &#34;&#34;&#34;
4090
    # Setup the source &amp; target meshes based on `as_source``
4091
    if as_source is True:
4092
        source = self._mesh
4093
        target = other_mesh
4094
    elif as_source is False:
4095
        source = other_mesh
4096
        target = self._mesh
4097
4098
    # Get registered mesh (source to target)
4099
    source_transformed_to_target = non_rigidly_register(
4100
            target_mesh=target,
4101
            source_mesh=source,
4102
            **kwargs
4103
        ) 
4104
4105
    # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
4106
    if (as_source is True) &amp; (apply_transform_to_mesh is True):
4107
        self._mesh = source_transformed_to_target
4108
    
4109
    # curent mesh is target, or is source &amp; want to return mesh, then return it.  
4110
    if (as_source is False) or ((as_source is True) &amp; (return_transformed_mesh is True)):
4111
        return source_transformed_to_target</code></pre>
4112
</details>
4113
</dd>
4114
<dt id="pymskt.mesh.meshes.Mesh.read_seg_image"><code class="name flex">
4115
<span>def <span class="ident">read_seg_image</span></span>(<span>self, path_seg_image=None)</span>
4116
</code></dt>
4117
<dd>
4118
<div class="desc"><p>Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)</p>
4119
<h2 id="parameters">Parameters</h2>
4120
<dl>
4121
<dt><strong><code>path_seg_image</code></strong> :&ensp;<code>str</code>, optional</dt>
4122
<dd>Path to the medical image file to be loaded in, by default None</dd>
4123
</dl>
4124
<h2 id="raises">Raises</h2>
4125
<dl>
4126
<dt><code>Exception</code></dt>
4127
<dd>If path_seg_image does not exist, exception is raised.</dd>
4128
</dl></div>
4129
<details class="source">
4130
<summary>
4131
<span>Expand source code</span>
4132
</summary>
4133
<pre><code class="python">def read_seg_image(self,
4134
                   path_seg_image=None):
4135
    &#34;&#34;&#34;
4136
    Read segmentation image from disk. Must be a single file (e.g., nrrd, 3D dicom)
4137
4138
    Parameters
4139
    ----------
4140
    path_seg_image : str, optional
4141
        Path to the medical image file to be loaded in, by default None
4142
4143
    Raises
4144
    ------
4145
    Exception
4146
        If path_seg_image does not exist, exception is raised. 
4147
    &#34;&#34;&#34;        
4148
    # If passing new location/seg image name, then update variables. 
4149
    if path_seg_image is not None:
4150
        self._path_seg_image = path_seg_image
4151
    
4152
    # If seg image location / name exist, then load image else raise exception
4153
    if (self._path_seg_image is not None):
4154
        self._seg_image = sitk.ReadImage(self._path_seg_image)
4155
    else:
4156
        raise Exception(&#39;No file path (self._path_seg_image) provided.&#39;)</code></pre>
4157
</details>
4158
</dd>
4159
<dt id="pymskt.mesh.meshes.Mesh.resample_surface"><code class="name flex">
4160
<span>def <span class="ident">resample_surface</span></span>(<span>self, subdivisions=2, clusters=10000)</span>
4161
</code></dt>
4162
<dd>
4163
<div class="desc"><p>Resample a surface mesh using the ACVD algorithm:
4164
Version used:
4165
- <a href="https://github.com/pyvista/pyacvd">https://github.com/pyvista/pyacvd</a>
4166
Original version w/ more references:
4167
- <a href="https://github.com/valette/ACVD">https://github.com/valette/ACVD</a></p>
4168
<h2 id="parameters">Parameters</h2>
4169
<dl>
4170
<dt><strong><code>subdivisions</code></strong> :&ensp;<code>int</code>, optional</dt>
4171
<dd>Subdivide the mesh to have more points before clustering, by default 2
4172
Probably not necessary for very dense meshes.</dd>
4173
<dt><strong><code>clusters</code></strong> :&ensp;<code>int</code>, optional</dt>
4174
<dd>The number of clusters (points/vertices) to create during resampling
4175
surafce, by default 10000
4176
- This is not exact, might have slight differences.</dd>
4177
</dl></div>
4178
<details class="source">
4179
<summary>
4180
<span>Expand source code</span>
4181
</summary>
4182
<pre><code class="python">def resample_surface(self,
4183
                     subdivisions=2,
4184
                     clusters=10000
4185
                     ):
4186
    &#34;&#34;&#34;
4187
    Resample a surface mesh using the ACVD algorithm: 
4188
    Version used: 
4189
    - https://github.com/pyvista/pyacvd
4190
    Original version w/ more references: 
4191
    - https://github.com/valette/ACVD
4192
4193
    Parameters
4194
    ----------
4195
    subdivisions : int, optional
4196
        Subdivide the mesh to have more points before clustering, by default 2
4197
        Probably not necessary for very dense meshes.
4198
    clusters : int, optional
4199
        The number of clusters (points/vertices) to create during resampling 
4200
        surafce, by default 10000
4201
        - This is not exact, might have slight differences. 
4202
    &#34;&#34;&#34;
4203
    self._mesh = resample_surface(self._mesh, subdivisions=subdivisions, clusters=clusters)</code></pre>
4204
</details>
4205
</dd>
4206
<dt id="pymskt.mesh.meshes.Mesh.reverse_all_transforms"><code class="name flex">
4207
<span>def <span class="ident">reverse_all_transforms</span></span>(<span>self)</span>
4208
</code></dt>
4209
<dd>
4210
<div class="desc"><p>Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.</p></div>
4211
<details class="source">
4212
<summary>
4213
<span>Expand source code</span>
4214
</summary>
4215
<pre><code class="python">def reverse_all_transforms(self):
4216
    &#34;&#34;&#34;
4217
    Function to iterate over all of the self._list_applied_transforms (in reverse order) and undo them.
4218
    &#34;&#34;&#34;
4219
    while len(self._list_applied_transforms) &gt; 0:
4220
        self.reverse_most_recent_transform()</code></pre>
4221
</details>
4222
</dd>
4223
<dt id="pymskt.mesh.meshes.Mesh.reverse_most_recent_transform"><code class="name flex">
4224
<span>def <span class="ident">reverse_most_recent_transform</span></span>(<span>self)</span>
4225
</code></dt>
4226
<dd>
4227
<div class="desc"><p>Function to undo the most recent transformation stored in self._list_applied_transforms</p></div>
4228
<details class="source">
4229
<summary>
4230
<span>Expand source code</span>
4231
</summary>
4232
<pre><code class="python">def reverse_most_recent_transform(self):
4233
    &#34;&#34;&#34;
4234
    Function to undo the most recent transformation stored in self._list_applied_transforms
4235
    &#34;&#34;&#34;
4236
    transform = self._list_applied_transforms.pop()
4237
    transform.Inverse()
4238
    self.apply_transform_to_mesh(transform=transform, save_transform=False)</code></pre>
4239
</details>
4240
</dd>
4241
<dt id="pymskt.mesh.meshes.Mesh.rigidly_register"><code class="name flex">
4242
<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>
4243
</code></dt>
4244
<dd>
4245
<div class="desc"><p>Function to perform rigid registration between this mesh and another mesh. </p>
4246
<h2 id="parameters">Parameters</h2>
4247
<dl>
4248
<dt><strong><code>other_mesh</code></strong> :&ensp;<code>pymskt.mesh.Mesh</code> or <code>vtk.vtkPolyData</code></dt>
4249
<dd>Other mesh to use in registration process</dd>
4250
<dt><strong><code>as_source</code></strong> :&ensp;<code>bool</code>, optional</dt>
4251
<dd>Should the current mesh (in this object) be the source or the target, by default True</dd>
4252
<dt><strong><code>apply_transform_to_mesh</code></strong> :&ensp;<code>bool</code>, optional</dt>
4253
<dd>If as_source is True should we apply transformation to internal mesh, by default True</dd>
4254
<dt><strong><code>return_transformed_mesh</code></strong> :&ensp;<code>bool</code>, optional</dt>
4255
<dd>Should we return the registered mesh, by default False</dd>
4256
<dt><strong><code>max_n_iter</code></strong> :&ensp;<code>int</code>, optional</dt>
4257
<dd>Maximum number of iterations to perform, by default 100</dd>
4258
<dt><strong><code>n_landmarks</code></strong> :&ensp;<code>int</code>, optional</dt>
4259
<dd>Number of landmarks to use in registration, by default 1000</dd>
4260
<dt><strong><code>reg_mode</code></strong> :&ensp;<code>str</code>, optional</dt>
4261
<dd>Mode of registration to use, by default 'similarity' (similarity, rigid, or affine)</dd>
4262
</dl>
4263
<h2 id="returns">Returns</h2>
4264
<dl>
4265
<dt><code>_type_</code></dt>
4266
<dd><em>description</em></dd>
4267
</dl></div>
4268
<details class="source">
4269
<summary>
4270
<span>Expand source code</span>
4271
</summary>
4272
<pre><code class="python">def rigidly_register(
4273
    self,
4274
    other_mesh,
4275
    as_source=True,
4276
    apply_transform_to_mesh=True,
4277
    return_transformed_mesh=False,
4278
    return_transform=False,
4279
    max_n_iter=100,
4280
    n_landmarks=1000,
4281
    reg_mode=&#39;similarity&#39;
4282
4283
):
4284
    &#34;&#34;&#34;
4285
    Function to perform rigid registration between this mesh and another mesh. 
4286
4287
    Parameters
4288
    ----------
4289
    other_mesh : pymskt.mesh.Mesh or vtk.vtkPolyData
4290
        Other mesh to use in registration process
4291
    as_source : bool, optional
4292
        Should the current mesh (in this object) be the source or the target, by default True
4293
    apply_transform_to_mesh : bool, optional
4294
        If as_source is True should we apply transformation to internal mesh, by default True
4295
    return_transformed_mesh : bool, optional
4296
        Should we return the registered mesh, by default False
4297
    max_n_iter : int, optional
4298
        Maximum number of iterations to perform, by default 100
4299
    n_landmarks : int, optional
4300
        Number of landmarks to use in registration, by default 1000
4301
    reg_mode : str, optional
4302
        Mode of registration to use, by default &#39;similarity&#39; (similarity, rigid, or affine)
4303
4304
    Returns
4305
    -------
4306
    _type_
4307
        _description_
4308
    &#34;&#34;&#34;
4309
4310
    if (return_transform is True) &amp; (return_transformed_mesh is True):
4311
        raise Exception(&#39;Cannot return both transformed mesh and transform&#39;)
4312
4313
    if type(other_mesh) in (pymskt.mesh.meshes.BoneMesh, pymskt.mesh.meshes.Mesh):
4314
        other_mesh = other_mesh.mesh
4315
4316
    # Setup the source &amp; target meshes based on `as_source``
4317
    if as_source is True:
4318
        source = self._mesh
4319
        target = other_mesh
4320
    elif as_source is False:
4321
        source = other_mesh
4322
        target = self._mesh
4323
    
4324
    icp_transform = get_icp_transform(
4325
        source=source,
4326
        target=target,
4327
        max_n_iter=max_n_iter,
4328
        n_landmarks=n_landmarks,
4329
        reg_mode=reg_mode
4330
    )
4331
4332
    # If current mesh is source &amp; apply_transform_to_mesh is true then replace current mesh. 
4333
    if (as_source is True) &amp; (apply_transform_to_mesh is True):
4334
        self.apply_transform_to_mesh(transform=icp_transform)
4335
4336
        if return_transformed_mesh is True:
4337
            return self._mesh
4338
        
4339
        elif return_transform is True:
4340
            return icp_transform
4341
    
4342
    # curent mesh is target, or is source &amp; want to return mesh, then return it.  
4343
    elif (as_source is False) &amp; (return_transformed_mesh is True):
4344
        return apply_transform(source=source, transform=icp_transform)
4345
4346
    else:
4347
        raise Exception(&#39;Nothing to return from rigid registration.&#39;)</code></pre>
4348
</details>
4349
</dd>
4350
<dt id="pymskt.mesh.meshes.Mesh.save_mesh"><code class="name flex">
4351
<span>def <span class="ident">save_mesh</span></span>(<span>self, filepath)</span>
4352
</code></dt>
4353
<dd>
4354
<div class="desc"><p>Save the surface mesh from this class to disk. </p>
4355
<h2 id="parameters">Parameters</h2>
4356
<dl>
4357
<dt><strong><code>filepath</code></strong> :&ensp;<code>str</code></dt>
4358
<dd>Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to.</dd>
4359
</dl></div>
4360
<details class="source">
4361
<summary>
4362
<span>Expand source code</span>
4363
</summary>
4364
<pre><code class="python">def save_mesh(self,
4365
              filepath):
4366
    &#34;&#34;&#34;
4367
    Save the surface mesh from this class to disk. 
4368
4369
    Parameters
4370
    ----------
4371
    filepath : str
4372
        Location &amp; filename to save the surface mesh (vtk.vtkPolyData) to. 
4373
    &#34;&#34;&#34;        
4374
    io.write_vtk(self._mesh, filepath)</code></pre>
4375
</details>
4376
</dd>
4377
</dl>
4378
</dd>
4379
</dl>
4380
</section>
4381
</article>
4382
<nav id="sidebar">
4383
<h1>Index</h1>
4384
<div class="toc">
4385
<ul></ul>
4386
</div>
4387
<ul id="index">
4388
<li><h3>Super-module</h3>
4389
<ul>
4390
<li><code><a title="pymskt.mesh" href="index.html">pymskt.mesh</a></code></li>
4391
</ul>
4392
</li>
4393
<li><h3><a href="#header-classes">Classes</a></h3>
4394
<ul>
4395
<li>
4396
<h4><code><a title="pymskt.mesh.meshes.BoneMesh" href="#pymskt.mesh.meshes.BoneMesh">BoneMesh</a></code></h4>
4397
<ul class="">
4398
<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>
4399
<li><code><a title="pymskt.mesh.meshes.BoneMesh.bone" href="#pymskt.mesh.meshes.BoneMesh.bone">bone</a></code></li>
4400
<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>
4401
<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>
4402
<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>
4403
<li><code><a title="pymskt.mesh.meshes.BoneMesh.create_mesh" href="#pymskt.mesh.meshes.BoneMesh.create_mesh">create_mesh</a></code></li>
4404
<li><code><a title="pymskt.mesh.meshes.BoneMesh.crop_percent" href="#pymskt.mesh.meshes.BoneMesh.crop_percent">crop_percent</a></code></li>
4405
<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>
4406
<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>
4407
<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>
4408
</ul>
4409
</li>
4410
<li>
4411
<h4><code><a title="pymskt.mesh.meshes.CartilageMesh" href="#pymskt.mesh.meshes.CartilageMesh">CartilageMesh</a></code></h4>
4412
</li>
4413
<li>
4414
<h4><code><a title="pymskt.mesh.meshes.Mesh" href="#pymskt.mesh.meshes.Mesh">Mesh</a></code></h4>
4415
<ul class="">
4416
<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>
4417
<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>
4418
<li><code><a title="pymskt.mesh.meshes.Mesh.create_mesh" href="#pymskt.mesh.meshes.Mesh.create_mesh">create_mesh</a></code></li>
4419
<li><code><a title="pymskt.mesh.meshes.Mesh.label_idx" href="#pymskt.mesh.meshes.Mesh.label_idx">label_idx</a></code></li>
4420
<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>
4421
<li><code><a title="pymskt.mesh.meshes.Mesh.mesh" href="#pymskt.mesh.meshes.Mesh.mesh">mesh</a></code></li>
4422
<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>
4423
<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>
4424
<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>
4425
<li><code><a title="pymskt.mesh.meshes.Mesh.point_coords" href="#pymskt.mesh.meshes.Mesh.point_coords">point_coords</a></code></li>
4426
<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>
4427
<li><code><a title="pymskt.mesh.meshes.Mesh.resample_surface" href="#pymskt.mesh.meshes.Mesh.resample_surface">resample_surface</a></code></li>
4428
<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>
4429
<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>
4430
<li><code><a title="pymskt.mesh.meshes.Mesh.rigidly_register" href="#pymskt.mesh.meshes.Mesh.rigidly_register">rigidly_register</a></code></li>
4431
<li><code><a title="pymskt.mesh.meshes.Mesh.save_mesh" href="#pymskt.mesh.meshes.Mesh.save_mesh">save_mesh</a></code></li>
4432
<li><code><a title="pymskt.mesh.meshes.Mesh.seg_image" href="#pymskt.mesh.meshes.Mesh.seg_image">seg_image</a></code></li>
4433
</ul>
4434
</li>
4435
</ul>
4436
</li>
4437
</ul>
4438
</nav>
4439
</main>
4440
<footer id="footer">
4441
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
4442
</footer>
4443
</body>
4444
</html>