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

Switch to unified view

a b/docs/mesh/utils.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.utils 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.utils</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">import vtk
30
import numpy as np
31
from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk
32
from pymskt.utils import sigma2fwhm
33
import pyvista as pv
34
import pymskt
35
36
37
# Some functions were originally based on the tutorial on ray casting in python + vtk 
38
# by Adamos Kyriakou @:
39
# https://pyscience.wordpress.com/2014/09/21/ray-casting-with-python-and-vtk-intersecting-linesrays-with-surface-meshes/
40
41
42
def is_hit(obb_tree, source, target):
43
    &#34;&#34;&#34;
44
    Return True if line intersects mesh (`obb_tree`). The line starts at `source` and ends at `target`.
45
46
    Parameters
47
    ----------
48
    obb_tree : vtk.vtkOBBTree
49
        OBBTree of a surface mesh. 
50
    source : list
51
        x/y/z position of starting point of ray (to find intersection)
52
    target : list
53
        x/y/z position of ending point of ray (to find intersection)
54
55
    Returns
56
    -------
57
    bool
58
        Telling if the line (source to target) intersects the obb_tree. 
59
    &#34;&#34;&#34;    
60
61
    code = obb_tree.IntersectWithLine(source, target, None, None)
62
    if code == 0:
63
        return False
64
    else: 
65
        return True
66
67
68
def get_intersect(obbTree, pSource, pTarget):
69
    &#34;&#34;&#34;
70
    Get intersecting points on the obbTree between a line from pSource to pTarget. 
71
72
    Parameters
73
    ----------
74
    obb_tree : vtk.vtkOBBTree
75
        OBBTree of a surface mesh. 
76
    pSource : list
77
        x/y/z position of starting point of ray (to find intersection)
78
    pTarget : list
79
        x/y/z position of ending point of ray (to find intersection)
80
81
    Returns
82
    -------
83
    tuple (list1, list2)
84
        list1 is of the intersection points
85
        list2 is the idx of the cells that were intersected. 
86
    &#34;&#34;&#34;    
87
    # Create an empty &#39;vtkPoints&#39; object to store the intersection point coordinates
88
    points = vtk.vtkPoints()
89
    # Create an empty &#39;vtkIdList&#39; object to store the ids of the cells that intersect
90
    # with the cast rays
91
    cell_ids = vtk.vtkIdList()
92
93
    # Perform intersection
94
    code = obbTree.IntersectWithLine(pSource, pTarget, points, cell_ids)
95
96
    # Get point-data
97
    point_data = points.GetData()
98
    # Get number of intersection points found
99
    n_points = point_data.GetNumberOfTuples()
100
    # Get number of intersected cell ids
101
    n_Ids = cell_ids.GetNumberOfIds()
102
103
    assert (n_points == n_Ids)
104
105
    # Loop through the found points and cells and store
106
    # them in lists
107
    points_inter = []
108
    cell_ids_inter = []
109
    for idx in range(n_points):
110
        points_inter.append(point_data.GetTuple3(idx))
111
        cell_ids_inter.append(cell_ids.GetId(idx))
112
113
    return points_inter, cell_ids_inter
114
115
116
def get_surface_normals(surface,
117
                        point_normals_on=True,
118
                        cell_normals_on=True):
119
    &#34;&#34;&#34;
120
    Get the surface normals of a mesh (`surface`
121
122
    Parameters
123
    ----------
124
    surface : vtk.vtkPolyData
125
        surface mesh to get normals from 
126
    point_normals_on : bool, optional
127
        Whether or not to get normals of points (vertices), by default True
128
    cell_normals_on : bool, optional
129
        Whether or not to get normals from cells (faces?), by default True
130
131
    Returns
132
    -------
133
    vtk.vtkPolyDataNormals
134
        Normval vectors for points/cells. 
135
    &#34;&#34;&#34;    
136
137
    normals = vtk.vtkPolyDataNormals()
138
    normals.SetInputData(surface)
139
140
    # Disable normal calculation at cell vertices
141
    if point_normals_on is True:
142
        normals.ComputePointNormalsOn()
143
    elif point_normals_on is False:
144
        normals.ComputePointNormalsOff()
145
    # Enable normal calculation at cell centers
146
    if cell_normals_on is True:
147
        normals.ComputeCellNormalsOn()
148
    elif cell_normals_on is False:
149
        normals.ComputeCellNormalsOff()
150
    # Disable splitting of sharp edges
151
    normals.SplittingOff()
152
    # Disable global flipping of normal orientation
153
    normals.FlipNormalsOff()
154
    # Enable automatic determination of correct normal orientation
155
    normals.AutoOrientNormalsOn()
156
    # Perform calculation
157
    normals.Update()
158
159
    return normals
160
161
162
def get_obb_surface(surface):
163
    &#34;&#34;&#34;
164
    Get vtk.vtkOBBTree for a surface mesh
165
    Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface.
166
167
    Parameters
168
    ----------
169
    surface : vtk.vtkPolyData
170
        The surface mesh to get an OBBTree for. 
171
172
    Returns
173
    -------
174
    vtk.vtkOBBTree
175
        The OBBTree to be used to find intersections for calculating cartilage thickness etc. 
176
    &#34;&#34;&#34;    
177
178
    obb = vtk.vtkOBBTree()
179
    obb.SetDataSet(surface)
180
    obb.BuildLocator()
181
    return obb
182
183
184
def vtk_deep_copy(mesh):
185
    &#34;&#34;&#34;
186
    &#34;Deep&#34; copy a vtk.vtkPolyData so that they are not connected in any way. 
187
188
    Parameters
189
    ----------
190
    mesh : vtk.vtkPolyData
191
        Mesh to copy. 
192
193
    Returns
194
    -------
195
    vtk.vtkPolyData
196
        Copy of the input mesh. 
197
    &#34;&#34;&#34;    
198
    new_mesh = vtk.vtkPolyData()
199
    new_mesh.DeepCopy(mesh)
200
    return new_mesh
201
202
def estimate_mesh_scalars_FWHMs(mesh, scalar_name=&#39;thickness_mm&#39;):
203
    &#34;&#34;&#34;
204
    Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. 
205
206
    Parameters
207
    ----------
208
    mesh : vtk.vtkPolyData
209
        Surface mesh to estimate FWHM of the scalars from. 
210
    scalar_name : str, optional
211
        Name of the scalars to calcualte FWHM for, by default &#39;thickness_mm&#39;
212
213
    Returns
214
    -------
215
    list
216
        List of the FWHM values. Assuming they are for X/Y/Z
217
    &#34;&#34;&#34;    
218
    gradient_filter = vtk.vtkGradientFilter()
219
    gradient_filter.SetInputData(mesh)
220
    gradient_filter.Update()
221
    gradient_mesh = vtk.vtkPolyData()
222
    gradient_mesh.DeepCopy(gradient_filter.GetOutput())
223
224
    scalars = vtk_to_numpy(mesh.GetPointData().GetScalars())
225
    location_non_zero = np.where(scalars != 0)
226
    gradient_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(&#39;Gradients&#39;))
227
    cartilage_gradients = gradient_scalars[location_non_zero, :][0]
228
229
    thickness_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(scalar_name))
230
    cartilage_thicknesses = thickness_scalars[location_non_zero]
231
232
    V0 = np.mean((cartilage_thicknesses - np.mean(cartilage_thicknesses)) ** 2)
233
    V1 = np.mean((cartilage_gradients - np.mean(cartilage_gradients)) ** 2, axis=0)
234
    sigma2s = -1 / (4 * np.log(1 - (V1 / (2 * V0))))
235
    sigmas = np.sqrt(sigma2s)
236
    FWHMs = [sigma2fwhm(x) for x in sigmas]
237
238
    return FWHMs
239
240
def get_surface_distance(surface_1, 
241
                         surface_2, 
242
                         return_RMS=True,
243
                         return_individual_distances=False):
244
245
    if (return_RMS is True) &amp; (return_individual_distances is True):
246
        raise Exception(&#39;Nothing to return - either return_RMS or return_individual_distances must be `True`&#39;)
247
248
    pt_locator = vtk.vtkPointLocator()
249
    pt_locator.SetDataSet(surface_2)
250
    pt_locator.AutomaticOn()
251
    pt_locator.BuildLocator()
252
    
253
    distances = np.zeros(surface_1.GetNumberOfPoints())
254
    
255
    for pt_idx in range(surface_1.GetNumberOfPoints()):
256
        point_1 = np.asarray(surface_1.GetPoint(pt_idx))
257
        pt_idx_2 = pt_locator.FindClosestPoint(point_1)
258
        point_2 = np.asarray(surface_2.GetPoint(pt_idx_2))
259
        distances[pt_idx] = np.sqrt(np.sum(np.square(point_2-point_1)))
260
    
261
    RMS = np.sqrt(np.mean(np.square(distances)))
262
    
263
    if return_individual_distances is True:
264
        if return_RMS is True:
265
            return RMS, distances
266
        else:
267
            return distances
268
    else:
269
        if return_RMS is True:
270
            return RMS
271
272
def get_symmetric_surface_distance(surface_1, surface_2):
273
    surf1_to_2_distances = get_surface_distance(surface_1, surface_2, return_RMS=False, return_individual_distances=True)
274
    surf2_to_1_distances = get_surface_distance(surface_2, surface_1, return_RMS=False, return_individual_distances=True)
275
276
    symmetric_distance = (np.sum(surf1_to_2_distances) + np.sum(surf2_to_1_distances)) / (len(surf1_to_2_distances) + len(surf2_to_1_distances))
277
278
    return symmetric_distance
279
280
class GIF:
281
    &#34;&#34;&#34;
282
    Class for generating GIF of surface meshes.
283
284
    Parameters
285
    ----------
286
    plotter : pyvista.Plotter
287
        Plotter to use for plotting.
288
    color: str, optional
289
        Color to use for object, by default &#39;orange&#39;
290
    show_edges: bool, optional
291
        Whether to show edges on mesh, by default True
292
    edge_color: str, optional
293
        Color to use for edges, by default &#39;black&#39;
294
    camera_position: list or string, optional
295
        Camera position to use, by default &#39;xz&#39;
296
    window_size: list, optional
297
        Window size to use for GIF, by default [3000, 4000]
298
    background_color: str, optional
299
        Background color to use, by default &#39;white&#39;
300
    path_save: str, optional
301
        Path to save GIF, by default &#39;~/Downloads/ssm.gif&#39;
302
    
303
    Attributes
304
    ----------
305
    _plotter : pyvista.Plotter
306
        Plotter to use for plotting.
307
    _color : str
308
        Color to use for object.
309
    _show_edges : bool
310
        Whether to show edges on mesh.
311
    _edge_color : str
312
        Color to use for edges.
313
    _camera_position : list or string
314
        Camera position to use.
315
    _window_size : list
316
        Window size to use for GIF.
317
    _background_color : str
318
        Background color to use.
319
    _path_save : str
320
        Path to save GIF.
321
    
322
    Methods
323
    -------
324
    add_mesh_frame(mesh)
325
        Add a mesh to the GIF.
326
    update_view()
327
        Update the view of the plotter.
328
    done()
329
        Close the plotter.
330
331
332
    &#34;&#34;&#34;
333
    def __init__(
334
        self,
335
        plotter=None,
336
        color=&#39;orange&#39;, 
337
        show_edges=True, 
338
        edge_color=&#39;black&#39;,
339
        camera_position=&#39;xz&#39;,
340
        window_size=[3000, 4000],
341
        background_color=&#39;white&#39;,
342
        path_save=&#39;~/Downloads/ssm.gif&#39;
343
    ):
344
        &#34;&#34;&#34;
345
        Initialize the GIF class.
346
347
        Parameters
348
        ----------
349
        plotter : pyvista.Plotter, optional
350
            Plotter to use for plotting, by default None
351
        color: str, optional
352
            Color to use for object, by default &#39;orange&#39;
353
        show_edges: bool, optional
354
            Whether to show edges on mesh, by default True
355
        edge_color: str, optional
356
            Color to use for edges, by default &#39;black&#39;
357
        camera_position: list or string, optional
358
            Camera position to use, by default &#39;xz&#39;
359
        window_size: list, optional
360
            Window size to use for GIF, by default [3000, 4000]
361
        background_color: str, optional
362
            Background color to use, by default &#39;white&#39;
363
        path_save: str, optional
364
            Path to save GIF, by default &#39;~/Downloads/ssm.gif&#39;
365
        
366
        &#34;&#34;&#34;
367
        if plotter is None:
368
            self._plotter = pv.Plotter(notebook=False, off_screen=True)
369
        else:
370
            self._plotter = plotter
371
        
372
        if path_save[-3:] != &#39;gif&#39;:
373
            raise Exception(&#39;path must be to a file ending with suffix `.gif`&#39;)
374
        
375
        self.counter = 0
376
        
377
        self._plotter.open_gif(path_save)
378
379
        self._color = color
380
        self._show_edges = show_edges
381
        self._edge_color = edge_color
382
        self._camera_position = camera_position
383
        self._window_size = window_size
384
        self._background_color = background_color
385
        self._path_save = path_save
386
    
387
    def update_view(
388
        self
389
    ):
390
        self._plotter.camera_position = self._camera_position
391
        self._plotter.window_size = self._window_size
392
        self._plotter.set_background(color=self._background_color)
393
    
394
    def add_mesh_frame(self, mesh):
395
        if type(mesh) in (list, tuple):
396
            actors = []
397
            for mesh_ in mesh:
398
                actors.append(self._plotter.add_mesh(
399
                    mesh_, 
400
                    render=False,
401
                    color=self._color, 
402
                    edge_color=self._edge_color, 
403
                    show_edges=self._show_edges
404
                ))
405
        else:
406
            actor = self._plotter.add_mesh(
407
                mesh, 
408
                render=False,
409
                color=self._color, 
410
                edge_color=self._edge_color, 
411
                show_edges=self._show_edges
412
            )
413
414
        if self.counter == 0:
415
            self.update_view()
416
        self._plotter.write_frame()
417
        
418
        if type(mesh) in (list, tuple):
419
            for actor in actors:
420
                self._plotter.remove_actor(actor)
421
        else:
422
            self._plotter.remove_actor(actor)
423
        self.counter += 1
424
    
425
    def done(self):
426
        self._plotter.close()
427
    
428
    @property
429
    def color(self):
430
        return self._color
431
    
432
    @color.setter
433
    def color(self, color):
434
        self._color = color
435
    
436
    @property
437
    def show_edges(self):
438
        return self._show_edges
439
    
440
    @show_edges.setter
441
    def show_edges(self, show_edges):
442
        self._show_edges = show_edges
443
    
444
    @property
445
    def edge_color(self):
446
        return self._edge_color
447
    
448
    @edge_color.setter
449
    def edge_color(self, edge_color):
450
        self._edge_color = edge_color
451
    
452
    @property
453
    def camera_position(self):
454
        return self._camera_position
455
    
456
    @camera_position.setter
457
    def camera_position(self, camera_position):
458
        self._camera_position = camera_position
459
    
460
    @property
461
    def window_size(self):
462
        return self._window_size
463
    
464
    @window_size.setter
465
    def window_size(self, window_size):
466
        self._window_size = window_size
467
    
468
    @property
469
    def background_color(self):
470
        return self._background_color
471
    
472
    @background_color.setter
473
    def background_color(self, background_color):
474
        self._background_color = background_color
475
    
476
    @property
477
    def path_save(self):
478
        return self._path_save
479
480
def get_arrow(
481
    direction,
482
    origin,
483
    scale=100,
484
    tip_length=0.25,
485
    tip_radius=0.1,
486
    tip_resolution=20, 
487
    shaft_radius=0.05,
488
    shaft_resolution=20,
489
):
490
491
    arrow = vtk.vtkArrowSource()
492
    arrow.SetTipLength(tip_length)
493
    arrow.SetTipRadius(tip_radius)
494
    arrow.SetTipResolution(tip_resolution)
495
    arrow.SetShaftRadius(shaft_radius)
496
    arrow.SetShaftResolution(shaft_resolution)
497
    arrow.Update()
498
499
    arrow = arrow.GetOutput()
500
    points = arrow.GetPoints().GetData()
501
    array = vtk_to_numpy(points)
502
    array *= scale
503
    arrow.GetPoints().SetData(numpy_to_vtk(array))
504
505
    normx = np.array(direction) / np.linalg.norm(direction)
506
    normz = np.cross(normx, [0, 1.0, 0.0001])
507
    normz /= np.linalg.norm(normz)
508
    normy = np.cross(normz, normx)
509
510
    four_by_four = np.identity(4)
511
    four_by_four[:3,0] = normx
512
    four_by_four[:3,1] = normy
513
    four_by_four[:3,2] = normz
514
    four_by_four[:3, 3] = origin
515
516
    transform = pymskt.mesh.meshTransform.create_transform(four_by_four)
517
    arrow = pymskt.mesh.meshTransform.apply_transform(arrow, transform)
518
    
519
    return arrow</code></pre>
520
</details>
521
</section>
522
<section>
523
</section>
524
<section>
525
</section>
526
<section>
527
<h2 class="section-title" id="header-functions">Functions</h2>
528
<dl>
529
<dt id="pymskt.mesh.utils.estimate_mesh_scalars_FWHMs"><code class="name flex">
530
<span>def <span class="ident">estimate_mesh_scalars_FWHMs</span></span>(<span>mesh, scalar_name='thickness_mm')</span>
531
</code></dt>
532
<dd>
533
<div class="desc"><p>Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. </p>
534
<h2 id="parameters">Parameters</h2>
535
<dl>
536
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
537
<dd>Surface mesh to estimate FWHM of the scalars from.</dd>
538
<dt><strong><code>scalar_name</code></strong> :&ensp;<code>str</code>, optional</dt>
539
<dd>Name of the scalars to calcualte FWHM for, by default 'thickness_mm'</dd>
540
</dl>
541
<h2 id="returns">Returns</h2>
542
<dl>
543
<dt><code>list</code></dt>
544
<dd>List of the FWHM values. Assuming they are for X/Y/Z</dd>
545
</dl></div>
546
<details class="source">
547
<summary>
548
<span>Expand source code</span>
549
</summary>
550
<pre><code class="python">def estimate_mesh_scalars_FWHMs(mesh, scalar_name=&#39;thickness_mm&#39;):
551
    &#34;&#34;&#34;
552
    Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. 
553
554
    Parameters
555
    ----------
556
    mesh : vtk.vtkPolyData
557
        Surface mesh to estimate FWHM of the scalars from. 
558
    scalar_name : str, optional
559
        Name of the scalars to calcualte FWHM for, by default &#39;thickness_mm&#39;
560
561
    Returns
562
    -------
563
    list
564
        List of the FWHM values. Assuming they are for X/Y/Z
565
    &#34;&#34;&#34;    
566
    gradient_filter = vtk.vtkGradientFilter()
567
    gradient_filter.SetInputData(mesh)
568
    gradient_filter.Update()
569
    gradient_mesh = vtk.vtkPolyData()
570
    gradient_mesh.DeepCopy(gradient_filter.GetOutput())
571
572
    scalars = vtk_to_numpy(mesh.GetPointData().GetScalars())
573
    location_non_zero = np.where(scalars != 0)
574
    gradient_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(&#39;Gradients&#39;))
575
    cartilage_gradients = gradient_scalars[location_non_zero, :][0]
576
577
    thickness_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(scalar_name))
578
    cartilage_thicknesses = thickness_scalars[location_non_zero]
579
580
    V0 = np.mean((cartilage_thicknesses - np.mean(cartilage_thicknesses)) ** 2)
581
    V1 = np.mean((cartilage_gradients - np.mean(cartilage_gradients)) ** 2, axis=0)
582
    sigma2s = -1 / (4 * np.log(1 - (V1 / (2 * V0))))
583
    sigmas = np.sqrt(sigma2s)
584
    FWHMs = [sigma2fwhm(x) for x in sigmas]
585
586
    return FWHMs</code></pre>
587
</details>
588
</dd>
589
<dt id="pymskt.mesh.utils.get_arrow"><code class="name flex">
590
<span>def <span class="ident">get_arrow</span></span>(<span>direction, origin, scale=100, tip_length=0.25, tip_radius=0.1, tip_resolution=20, shaft_radius=0.05, shaft_resolution=20)</span>
591
</code></dt>
592
<dd>
593
<div class="desc"></div>
594
<details class="source">
595
<summary>
596
<span>Expand source code</span>
597
</summary>
598
<pre><code class="python">def get_arrow(
599
    direction,
600
    origin,
601
    scale=100,
602
    tip_length=0.25,
603
    tip_radius=0.1,
604
    tip_resolution=20, 
605
    shaft_radius=0.05,
606
    shaft_resolution=20,
607
):
608
609
    arrow = vtk.vtkArrowSource()
610
    arrow.SetTipLength(tip_length)
611
    arrow.SetTipRadius(tip_radius)
612
    arrow.SetTipResolution(tip_resolution)
613
    arrow.SetShaftRadius(shaft_radius)
614
    arrow.SetShaftResolution(shaft_resolution)
615
    arrow.Update()
616
617
    arrow = arrow.GetOutput()
618
    points = arrow.GetPoints().GetData()
619
    array = vtk_to_numpy(points)
620
    array *= scale
621
    arrow.GetPoints().SetData(numpy_to_vtk(array))
622
623
    normx = np.array(direction) / np.linalg.norm(direction)
624
    normz = np.cross(normx, [0, 1.0, 0.0001])
625
    normz /= np.linalg.norm(normz)
626
    normy = np.cross(normz, normx)
627
628
    four_by_four = np.identity(4)
629
    four_by_four[:3,0] = normx
630
    four_by_four[:3,1] = normy
631
    four_by_four[:3,2] = normz
632
    four_by_four[:3, 3] = origin
633
634
    transform = pymskt.mesh.meshTransform.create_transform(four_by_four)
635
    arrow = pymskt.mesh.meshTransform.apply_transform(arrow, transform)
636
    
637
    return arrow</code></pre>
638
</details>
639
</dd>
640
<dt id="pymskt.mesh.utils.get_intersect"><code class="name flex">
641
<span>def <span class="ident">get_intersect</span></span>(<span>obbTree, pSource, pTarget)</span>
642
</code></dt>
643
<dd>
644
<div class="desc"><p>Get intersecting points on the obbTree between a line from pSource to pTarget. </p>
645
<h2 id="parameters">Parameters</h2>
646
<dl>
647
<dt><strong><code>obb_tree</code></strong> :&ensp;<code>vtk.vtkOBBTree</code></dt>
648
<dd>OBBTree of a surface mesh.</dd>
649
<dt><strong><code>pSource</code></strong> :&ensp;<code>list</code></dt>
650
<dd>x/y/z position of starting point of ray (to find intersection)</dd>
651
<dt><strong><code>pTarget</code></strong> :&ensp;<code>list</code></dt>
652
<dd>x/y/z position of ending point of ray (to find intersection)</dd>
653
</dl>
654
<h2 id="returns">Returns</h2>
655
<dl>
656
<dt><code>tuple (list1, list2)</code></dt>
657
<dd>list1 is of the intersection points
658
list2 is the idx of the cells that were intersected.</dd>
659
</dl></div>
660
<details class="source">
661
<summary>
662
<span>Expand source code</span>
663
</summary>
664
<pre><code class="python">def get_intersect(obbTree, pSource, pTarget):
665
    &#34;&#34;&#34;
666
    Get intersecting points on the obbTree between a line from pSource to pTarget. 
667
668
    Parameters
669
    ----------
670
    obb_tree : vtk.vtkOBBTree
671
        OBBTree of a surface mesh. 
672
    pSource : list
673
        x/y/z position of starting point of ray (to find intersection)
674
    pTarget : list
675
        x/y/z position of ending point of ray (to find intersection)
676
677
    Returns
678
    -------
679
    tuple (list1, list2)
680
        list1 is of the intersection points
681
        list2 is the idx of the cells that were intersected. 
682
    &#34;&#34;&#34;    
683
    # Create an empty &#39;vtkPoints&#39; object to store the intersection point coordinates
684
    points = vtk.vtkPoints()
685
    # Create an empty &#39;vtkIdList&#39; object to store the ids of the cells that intersect
686
    # with the cast rays
687
    cell_ids = vtk.vtkIdList()
688
689
    # Perform intersection
690
    code = obbTree.IntersectWithLine(pSource, pTarget, points, cell_ids)
691
692
    # Get point-data
693
    point_data = points.GetData()
694
    # Get number of intersection points found
695
    n_points = point_data.GetNumberOfTuples()
696
    # Get number of intersected cell ids
697
    n_Ids = cell_ids.GetNumberOfIds()
698
699
    assert (n_points == n_Ids)
700
701
    # Loop through the found points and cells and store
702
    # them in lists
703
    points_inter = []
704
    cell_ids_inter = []
705
    for idx in range(n_points):
706
        points_inter.append(point_data.GetTuple3(idx))
707
        cell_ids_inter.append(cell_ids.GetId(idx))
708
709
    return points_inter, cell_ids_inter</code></pre>
710
</details>
711
</dd>
712
<dt id="pymskt.mesh.utils.get_obb_surface"><code class="name flex">
713
<span>def <span class="ident">get_obb_surface</span></span>(<span>surface)</span>
714
</code></dt>
715
<dd>
716
<div class="desc"><p>Get vtk.vtkOBBTree for a surface mesh
717
Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface.</p>
718
<h2 id="parameters">Parameters</h2>
719
<dl>
720
<dt><strong><code>surface</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
721
<dd>The surface mesh to get an OBBTree for.</dd>
722
</dl>
723
<h2 id="returns">Returns</h2>
724
<dl>
725
<dt><code>vtk.vtkOBBTree</code></dt>
726
<dd>The OBBTree to be used to find intersections for calculating cartilage thickness etc.</dd>
727
</dl></div>
728
<details class="source">
729
<summary>
730
<span>Expand source code</span>
731
</summary>
732
<pre><code class="python">def get_obb_surface(surface):
733
    &#34;&#34;&#34;
734
    Get vtk.vtkOBBTree for a surface mesh
735
    Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface.
736
737
    Parameters
738
    ----------
739
    surface : vtk.vtkPolyData
740
        The surface mesh to get an OBBTree for. 
741
742
    Returns
743
    -------
744
    vtk.vtkOBBTree
745
        The OBBTree to be used to find intersections for calculating cartilage thickness etc. 
746
    &#34;&#34;&#34;    
747
748
    obb = vtk.vtkOBBTree()
749
    obb.SetDataSet(surface)
750
    obb.BuildLocator()
751
    return obb</code></pre>
752
</details>
753
</dd>
754
<dt id="pymskt.mesh.utils.get_surface_distance"><code class="name flex">
755
<span>def <span class="ident">get_surface_distance</span></span>(<span>surface_1, surface_2, return_RMS=True, return_individual_distances=False)</span>
756
</code></dt>
757
<dd>
758
<div class="desc"></div>
759
<details class="source">
760
<summary>
761
<span>Expand source code</span>
762
</summary>
763
<pre><code class="python">def get_surface_distance(surface_1, 
764
                         surface_2, 
765
                         return_RMS=True,
766
                         return_individual_distances=False):
767
768
    if (return_RMS is True) &amp; (return_individual_distances is True):
769
        raise Exception(&#39;Nothing to return - either return_RMS or return_individual_distances must be `True`&#39;)
770
771
    pt_locator = vtk.vtkPointLocator()
772
    pt_locator.SetDataSet(surface_2)
773
    pt_locator.AutomaticOn()
774
    pt_locator.BuildLocator()
775
    
776
    distances = np.zeros(surface_1.GetNumberOfPoints())
777
    
778
    for pt_idx in range(surface_1.GetNumberOfPoints()):
779
        point_1 = np.asarray(surface_1.GetPoint(pt_idx))
780
        pt_idx_2 = pt_locator.FindClosestPoint(point_1)
781
        point_2 = np.asarray(surface_2.GetPoint(pt_idx_2))
782
        distances[pt_idx] = np.sqrt(np.sum(np.square(point_2-point_1)))
783
    
784
    RMS = np.sqrt(np.mean(np.square(distances)))
785
    
786
    if return_individual_distances is True:
787
        if return_RMS is True:
788
            return RMS, distances
789
        else:
790
            return distances
791
    else:
792
        if return_RMS is True:
793
            return RMS</code></pre>
794
</details>
795
</dd>
796
<dt id="pymskt.mesh.utils.get_surface_normals"><code class="name flex">
797
<span>def <span class="ident">get_surface_normals</span></span>(<span>surface, point_normals_on=True, cell_normals_on=True)</span>
798
</code></dt>
799
<dd>
800
<div class="desc"><p>Get the surface normals of a mesh (<code>surface</code></p>
801
<h2 id="parameters">Parameters</h2>
802
<dl>
803
<dt><strong><code>surface</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
804
<dd>surface mesh to get normals from</dd>
805
<dt><strong><code>point_normals_on</code></strong> :&ensp;<code>bool</code>, optional</dt>
806
<dd>Whether or not to get normals of points (vertices), by default True</dd>
807
<dt><strong><code>cell_normals_on</code></strong> :&ensp;<code>bool</code>, optional</dt>
808
<dd>Whether or not to get normals from cells (faces?), by default True</dd>
809
</dl>
810
<h2 id="returns">Returns</h2>
811
<dl>
812
<dt><code>vtk.vtkPolyDataNormals</code></dt>
813
<dd>Normval vectors for points/cells.</dd>
814
</dl></div>
815
<details class="source">
816
<summary>
817
<span>Expand source code</span>
818
</summary>
819
<pre><code class="python">def get_surface_normals(surface,
820
                        point_normals_on=True,
821
                        cell_normals_on=True):
822
    &#34;&#34;&#34;
823
    Get the surface normals of a mesh (`surface`
824
825
    Parameters
826
    ----------
827
    surface : vtk.vtkPolyData
828
        surface mesh to get normals from 
829
    point_normals_on : bool, optional
830
        Whether or not to get normals of points (vertices), by default True
831
    cell_normals_on : bool, optional
832
        Whether or not to get normals from cells (faces?), by default True
833
834
    Returns
835
    -------
836
    vtk.vtkPolyDataNormals
837
        Normval vectors for points/cells. 
838
    &#34;&#34;&#34;    
839
840
    normals = vtk.vtkPolyDataNormals()
841
    normals.SetInputData(surface)
842
843
    # Disable normal calculation at cell vertices
844
    if point_normals_on is True:
845
        normals.ComputePointNormalsOn()
846
    elif point_normals_on is False:
847
        normals.ComputePointNormalsOff()
848
    # Enable normal calculation at cell centers
849
    if cell_normals_on is True:
850
        normals.ComputeCellNormalsOn()
851
    elif cell_normals_on is False:
852
        normals.ComputeCellNormalsOff()
853
    # Disable splitting of sharp edges
854
    normals.SplittingOff()
855
    # Disable global flipping of normal orientation
856
    normals.FlipNormalsOff()
857
    # Enable automatic determination of correct normal orientation
858
    normals.AutoOrientNormalsOn()
859
    # Perform calculation
860
    normals.Update()
861
862
    return normals</code></pre>
863
</details>
864
</dd>
865
<dt id="pymskt.mesh.utils.get_symmetric_surface_distance"><code class="name flex">
866
<span>def <span class="ident">get_symmetric_surface_distance</span></span>(<span>surface_1, surface_2)</span>
867
</code></dt>
868
<dd>
869
<div class="desc"></div>
870
<details class="source">
871
<summary>
872
<span>Expand source code</span>
873
</summary>
874
<pre><code class="python">def get_symmetric_surface_distance(surface_1, surface_2):
875
    surf1_to_2_distances = get_surface_distance(surface_1, surface_2, return_RMS=False, return_individual_distances=True)
876
    surf2_to_1_distances = get_surface_distance(surface_2, surface_1, return_RMS=False, return_individual_distances=True)
877
878
    symmetric_distance = (np.sum(surf1_to_2_distances) + np.sum(surf2_to_1_distances)) / (len(surf1_to_2_distances) + len(surf2_to_1_distances))
879
880
    return symmetric_distance</code></pre>
881
</details>
882
</dd>
883
<dt id="pymskt.mesh.utils.is_hit"><code class="name flex">
884
<span>def <span class="ident">is_hit</span></span>(<span>obb_tree, source, target)</span>
885
</code></dt>
886
<dd>
887
<div class="desc"><p>Return True if line intersects mesh (<code>obb_tree</code>). The line starts at <code>source</code> and ends at <code>target</code>.</p>
888
<h2 id="parameters">Parameters</h2>
889
<dl>
890
<dt><strong><code>obb_tree</code></strong> :&ensp;<code>vtk.vtkOBBTree</code></dt>
891
<dd>OBBTree of a surface mesh.</dd>
892
<dt><strong><code>source</code></strong> :&ensp;<code>list</code></dt>
893
<dd>x/y/z position of starting point of ray (to find intersection)</dd>
894
<dt><strong><code>target</code></strong> :&ensp;<code>list</code></dt>
895
<dd>x/y/z position of ending point of ray (to find intersection)</dd>
896
</dl>
897
<h2 id="returns">Returns</h2>
898
<dl>
899
<dt><code>bool</code></dt>
900
<dd>Telling if the line (source to target) intersects the obb_tree.</dd>
901
</dl></div>
902
<details class="source">
903
<summary>
904
<span>Expand source code</span>
905
</summary>
906
<pre><code class="python">def is_hit(obb_tree, source, target):
907
    &#34;&#34;&#34;
908
    Return True if line intersects mesh (`obb_tree`). The line starts at `source` and ends at `target`.
909
910
    Parameters
911
    ----------
912
    obb_tree : vtk.vtkOBBTree
913
        OBBTree of a surface mesh. 
914
    source : list
915
        x/y/z position of starting point of ray (to find intersection)
916
    target : list
917
        x/y/z position of ending point of ray (to find intersection)
918
919
    Returns
920
    -------
921
    bool
922
        Telling if the line (source to target) intersects the obb_tree. 
923
    &#34;&#34;&#34;    
924
925
    code = obb_tree.IntersectWithLine(source, target, None, None)
926
    if code == 0:
927
        return False
928
    else: 
929
        return True</code></pre>
930
</details>
931
</dd>
932
<dt id="pymskt.mesh.utils.vtk_deep_copy"><code class="name flex">
933
<span>def <span class="ident">vtk_deep_copy</span></span>(<span>mesh)</span>
934
</code></dt>
935
<dd>
936
<div class="desc"><p>"Deep" copy a vtk.vtkPolyData so that they are not connected in any way. </p>
937
<h2 id="parameters">Parameters</h2>
938
<dl>
939
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
940
<dd>Mesh to copy.</dd>
941
</dl>
942
<h2 id="returns">Returns</h2>
943
<dl>
944
<dt><code>vtk.vtkPolyData</code></dt>
945
<dd>Copy of the input mesh.</dd>
946
</dl></div>
947
<details class="source">
948
<summary>
949
<span>Expand source code</span>
950
</summary>
951
<pre><code class="python">def vtk_deep_copy(mesh):
952
    &#34;&#34;&#34;
953
    &#34;Deep&#34; copy a vtk.vtkPolyData so that they are not connected in any way. 
954
955
    Parameters
956
    ----------
957
    mesh : vtk.vtkPolyData
958
        Mesh to copy. 
959
960
    Returns
961
    -------
962
    vtk.vtkPolyData
963
        Copy of the input mesh. 
964
    &#34;&#34;&#34;    
965
    new_mesh = vtk.vtkPolyData()
966
    new_mesh.DeepCopy(mesh)
967
    return new_mesh</code></pre>
968
</details>
969
</dd>
970
</dl>
971
</section>
972
<section>
973
<h2 class="section-title" id="header-classes">Classes</h2>
974
<dl>
975
<dt id="pymskt.mesh.utils.GIF"><code class="flex name class">
976
<span>class <span class="ident">GIF</span></span>
977
<span>(</span><span>plotter=None, color='orange', show_edges=True, edge_color='black', camera_position='xz', window_size=[3000, 4000], background_color='white', path_save='~/Downloads/ssm.gif')</span>
978
</code></dt>
979
<dd>
980
<div class="desc"><p>Class for generating GIF of surface meshes.</p>
981
<h2 id="parameters">Parameters</h2>
982
<dl>
983
<dt><strong><code>plotter</code></strong> :&ensp;<code>pyvista.Plotter</code></dt>
984
<dd>Plotter to use for plotting.</dd>
985
<dt><strong><code>color</code></strong> :&ensp;<code>str</code>, optional</dt>
986
<dd>Color to use for object, by default 'orange'</dd>
987
<dt><strong><code>show_edges</code></strong> :&ensp;<code>bool</code>, optional</dt>
988
<dd>Whether to show edges on mesh, by default True</dd>
989
<dt><strong><code>edge_color</code></strong> :&ensp;<code>str</code>, optional</dt>
990
<dd>Color to use for edges, by default 'black'</dd>
991
<dt><strong><code>camera_position</code></strong> :&ensp;<code>list</code> or <code>string</code>, optional</dt>
992
<dd>Camera position to use, by default 'xz'</dd>
993
<dt><strong><code>window_size</code></strong> :&ensp;<code>list</code>, optional</dt>
994
<dd>Window size to use for GIF, by default [3000, 4000]</dd>
995
<dt><strong><code>background_color</code></strong> :&ensp;<code>str</code>, optional</dt>
996
<dd>Background color to use, by default 'white'</dd>
997
<dt><strong><code>path_save</code></strong> :&ensp;<code>str</code>, optional</dt>
998
<dd>Path to save GIF, by default '~/Downloads/ssm.gif'</dd>
999
</dl>
1000
<h2 id="attributes">Attributes</h2>
1001
<dl>
1002
<dt><strong><code>_plotter</code></strong> :&ensp;<code>pyvista.Plotter</code></dt>
1003
<dd>Plotter to use for plotting.</dd>
1004
<dt><strong><code>_color</code></strong> :&ensp;<code>str</code></dt>
1005
<dd>Color to use for object.</dd>
1006
<dt><strong><code>_show_edges</code></strong> :&ensp;<code>bool</code></dt>
1007
<dd>Whether to show edges on mesh.</dd>
1008
<dt><strong><code>_edge_color</code></strong> :&ensp;<code>str</code></dt>
1009
<dd>Color to use for edges.</dd>
1010
<dt><strong><code>_camera_position</code></strong> :&ensp;<code>list</code> or <code>string</code></dt>
1011
<dd>Camera position to use.</dd>
1012
<dt><strong><code>_window_size</code></strong> :&ensp;<code>list</code></dt>
1013
<dd>Window size to use for GIF.</dd>
1014
<dt><strong><code>_background_color</code></strong> :&ensp;<code>str</code></dt>
1015
<dd>Background color to use.</dd>
1016
<dt><strong><code>_path_save</code></strong> :&ensp;<code>str</code></dt>
1017
<dd>Path to save GIF.</dd>
1018
</dl>
1019
<h2 id="methods">Methods</h2>
1020
<p>add_mesh_frame(mesh)
1021
Add a mesh to the GIF.
1022
update_view()
1023
Update the view of the plotter.
1024
done()
1025
Close the plotter.</p>
1026
<p>Initialize the GIF class.</p>
1027
<h2 id="parameters_1">Parameters</h2>
1028
<dl>
1029
<dt><strong><code>plotter</code></strong> :&ensp;<code>pyvista.Plotter</code>, optional</dt>
1030
<dd>Plotter to use for plotting, by default None</dd>
1031
<dt><strong><code>color</code></strong> :&ensp;<code>str</code>, optional</dt>
1032
<dd>Color to use for object, by default 'orange'</dd>
1033
<dt><strong><code>show_edges</code></strong> :&ensp;<code>bool</code>, optional</dt>
1034
<dd>Whether to show edges on mesh, by default True</dd>
1035
<dt><strong><code>edge_color</code></strong> :&ensp;<code>str</code>, optional</dt>
1036
<dd>Color to use for edges, by default 'black'</dd>
1037
<dt><strong><code>camera_position</code></strong> :&ensp;<code>list</code> or <code>string</code>, optional</dt>
1038
<dd>Camera position to use, by default 'xz'</dd>
1039
<dt><strong><code>window_size</code></strong> :&ensp;<code>list</code>, optional</dt>
1040
<dd>Window size to use for GIF, by default [3000, 4000]</dd>
1041
<dt><strong><code>background_color</code></strong> :&ensp;<code>str</code>, optional</dt>
1042
<dd>Background color to use, by default 'white'</dd>
1043
<dt><strong><code>path_save</code></strong> :&ensp;<code>str</code>, optional</dt>
1044
<dd>Path to save GIF, by default '~/Downloads/ssm.gif'</dd>
1045
</dl></div>
1046
<details class="source">
1047
<summary>
1048
<span>Expand source code</span>
1049
</summary>
1050
<pre><code class="python">class GIF:
1051
    &#34;&#34;&#34;
1052
    Class for generating GIF of surface meshes.
1053
1054
    Parameters
1055
    ----------
1056
    plotter : pyvista.Plotter
1057
        Plotter to use for plotting.
1058
    color: str, optional
1059
        Color to use for object, by default &#39;orange&#39;
1060
    show_edges: bool, optional
1061
        Whether to show edges on mesh, by default True
1062
    edge_color: str, optional
1063
        Color to use for edges, by default &#39;black&#39;
1064
    camera_position: list or string, optional
1065
        Camera position to use, by default &#39;xz&#39;
1066
    window_size: list, optional
1067
        Window size to use for GIF, by default [3000, 4000]
1068
    background_color: str, optional
1069
        Background color to use, by default &#39;white&#39;
1070
    path_save: str, optional
1071
        Path to save GIF, by default &#39;~/Downloads/ssm.gif&#39;
1072
    
1073
    Attributes
1074
    ----------
1075
    _plotter : pyvista.Plotter
1076
        Plotter to use for plotting.
1077
    _color : str
1078
        Color to use for object.
1079
    _show_edges : bool
1080
        Whether to show edges on mesh.
1081
    _edge_color : str
1082
        Color to use for edges.
1083
    _camera_position : list or string
1084
        Camera position to use.
1085
    _window_size : list
1086
        Window size to use for GIF.
1087
    _background_color : str
1088
        Background color to use.
1089
    _path_save : str
1090
        Path to save GIF.
1091
    
1092
    Methods
1093
    -------
1094
    add_mesh_frame(mesh)
1095
        Add a mesh to the GIF.
1096
    update_view()
1097
        Update the view of the plotter.
1098
    done()
1099
        Close the plotter.
1100
1101
1102
    &#34;&#34;&#34;
1103
    def __init__(
1104
        self,
1105
        plotter=None,
1106
        color=&#39;orange&#39;, 
1107
        show_edges=True, 
1108
        edge_color=&#39;black&#39;,
1109
        camera_position=&#39;xz&#39;,
1110
        window_size=[3000, 4000],
1111
        background_color=&#39;white&#39;,
1112
        path_save=&#39;~/Downloads/ssm.gif&#39;
1113
    ):
1114
        &#34;&#34;&#34;
1115
        Initialize the GIF class.
1116
1117
        Parameters
1118
        ----------
1119
        plotter : pyvista.Plotter, optional
1120
            Plotter to use for plotting, by default None
1121
        color: str, optional
1122
            Color to use for object, by default &#39;orange&#39;
1123
        show_edges: bool, optional
1124
            Whether to show edges on mesh, by default True
1125
        edge_color: str, optional
1126
            Color to use for edges, by default &#39;black&#39;
1127
        camera_position: list or string, optional
1128
            Camera position to use, by default &#39;xz&#39;
1129
        window_size: list, optional
1130
            Window size to use for GIF, by default [3000, 4000]
1131
        background_color: str, optional
1132
            Background color to use, by default &#39;white&#39;
1133
        path_save: str, optional
1134
            Path to save GIF, by default &#39;~/Downloads/ssm.gif&#39;
1135
        
1136
        &#34;&#34;&#34;
1137
        if plotter is None:
1138
            self._plotter = pv.Plotter(notebook=False, off_screen=True)
1139
        else:
1140
            self._plotter = plotter
1141
        
1142
        if path_save[-3:] != &#39;gif&#39;:
1143
            raise Exception(&#39;path must be to a file ending with suffix `.gif`&#39;)
1144
        
1145
        self.counter = 0
1146
        
1147
        self._plotter.open_gif(path_save)
1148
1149
        self._color = color
1150
        self._show_edges = show_edges
1151
        self._edge_color = edge_color
1152
        self._camera_position = camera_position
1153
        self._window_size = window_size
1154
        self._background_color = background_color
1155
        self._path_save = path_save
1156
    
1157
    def update_view(
1158
        self
1159
    ):
1160
        self._plotter.camera_position = self._camera_position
1161
        self._plotter.window_size = self._window_size
1162
        self._plotter.set_background(color=self._background_color)
1163
    
1164
    def add_mesh_frame(self, mesh):
1165
        if type(mesh) in (list, tuple):
1166
            actors = []
1167
            for mesh_ in mesh:
1168
                actors.append(self._plotter.add_mesh(
1169
                    mesh_, 
1170
                    render=False,
1171
                    color=self._color, 
1172
                    edge_color=self._edge_color, 
1173
                    show_edges=self._show_edges
1174
                ))
1175
        else:
1176
            actor = self._plotter.add_mesh(
1177
                mesh, 
1178
                render=False,
1179
                color=self._color, 
1180
                edge_color=self._edge_color, 
1181
                show_edges=self._show_edges
1182
            )
1183
1184
        if self.counter == 0:
1185
            self.update_view()
1186
        self._plotter.write_frame()
1187
        
1188
        if type(mesh) in (list, tuple):
1189
            for actor in actors:
1190
                self._plotter.remove_actor(actor)
1191
        else:
1192
            self._plotter.remove_actor(actor)
1193
        self.counter += 1
1194
    
1195
    def done(self):
1196
        self._plotter.close()
1197
    
1198
    @property
1199
    def color(self):
1200
        return self._color
1201
    
1202
    @color.setter
1203
    def color(self, color):
1204
        self._color = color
1205
    
1206
    @property
1207
    def show_edges(self):
1208
        return self._show_edges
1209
    
1210
    @show_edges.setter
1211
    def show_edges(self, show_edges):
1212
        self._show_edges = show_edges
1213
    
1214
    @property
1215
    def edge_color(self):
1216
        return self._edge_color
1217
    
1218
    @edge_color.setter
1219
    def edge_color(self, edge_color):
1220
        self._edge_color = edge_color
1221
    
1222
    @property
1223
    def camera_position(self):
1224
        return self._camera_position
1225
    
1226
    @camera_position.setter
1227
    def camera_position(self, camera_position):
1228
        self._camera_position = camera_position
1229
    
1230
    @property
1231
    def window_size(self):
1232
        return self._window_size
1233
    
1234
    @window_size.setter
1235
    def window_size(self, window_size):
1236
        self._window_size = window_size
1237
    
1238
    @property
1239
    def background_color(self):
1240
        return self._background_color
1241
    
1242
    @background_color.setter
1243
    def background_color(self, background_color):
1244
        self._background_color = background_color
1245
    
1246
    @property
1247
    def path_save(self):
1248
        return self._path_save</code></pre>
1249
</details>
1250
<h3>Instance variables</h3>
1251
<dl>
1252
<dt id="pymskt.mesh.utils.GIF.background_color"><code class="name">var <span class="ident">background_color</span></code></dt>
1253
<dd>
1254
<div class="desc"></div>
1255
<details class="source">
1256
<summary>
1257
<span>Expand source code</span>
1258
</summary>
1259
<pre><code class="python">@property
1260
def background_color(self):
1261
    return self._background_color</code></pre>
1262
</details>
1263
</dd>
1264
<dt id="pymskt.mesh.utils.GIF.camera_position"><code class="name">var <span class="ident">camera_position</span></code></dt>
1265
<dd>
1266
<div class="desc"></div>
1267
<details class="source">
1268
<summary>
1269
<span>Expand source code</span>
1270
</summary>
1271
<pre><code class="python">@property
1272
def camera_position(self):
1273
    return self._camera_position</code></pre>
1274
</details>
1275
</dd>
1276
<dt id="pymskt.mesh.utils.GIF.color"><code class="name">var <span class="ident">color</span></code></dt>
1277
<dd>
1278
<div class="desc"></div>
1279
<details class="source">
1280
<summary>
1281
<span>Expand source code</span>
1282
</summary>
1283
<pre><code class="python">@property
1284
def color(self):
1285
    return self._color</code></pre>
1286
</details>
1287
</dd>
1288
<dt id="pymskt.mesh.utils.GIF.edge_color"><code class="name">var <span class="ident">edge_color</span></code></dt>
1289
<dd>
1290
<div class="desc"></div>
1291
<details class="source">
1292
<summary>
1293
<span>Expand source code</span>
1294
</summary>
1295
<pre><code class="python">@property
1296
def edge_color(self):
1297
    return self._edge_color</code></pre>
1298
</details>
1299
</dd>
1300
<dt id="pymskt.mesh.utils.GIF.path_save"><code class="name">var <span class="ident">path_save</span></code></dt>
1301
<dd>
1302
<div class="desc"></div>
1303
<details class="source">
1304
<summary>
1305
<span>Expand source code</span>
1306
</summary>
1307
<pre><code class="python">@property
1308
def path_save(self):
1309
    return self._path_save</code></pre>
1310
</details>
1311
</dd>
1312
<dt id="pymskt.mesh.utils.GIF.show_edges"><code class="name">var <span class="ident">show_edges</span></code></dt>
1313
<dd>
1314
<div class="desc"></div>
1315
<details class="source">
1316
<summary>
1317
<span>Expand source code</span>
1318
</summary>
1319
<pre><code class="python">@property
1320
def show_edges(self):
1321
    return self._show_edges</code></pre>
1322
</details>
1323
</dd>
1324
<dt id="pymskt.mesh.utils.GIF.window_size"><code class="name">var <span class="ident">window_size</span></code></dt>
1325
<dd>
1326
<div class="desc"></div>
1327
<details class="source">
1328
<summary>
1329
<span>Expand source code</span>
1330
</summary>
1331
<pre><code class="python">@property
1332
def window_size(self):
1333
    return self._window_size</code></pre>
1334
</details>
1335
</dd>
1336
</dl>
1337
<h3>Methods</h3>
1338
<dl>
1339
<dt id="pymskt.mesh.utils.GIF.add_mesh_frame"><code class="name flex">
1340
<span>def <span class="ident">add_mesh_frame</span></span>(<span>self, mesh)</span>
1341
</code></dt>
1342
<dd>
1343
<div class="desc"></div>
1344
<details class="source">
1345
<summary>
1346
<span>Expand source code</span>
1347
</summary>
1348
<pre><code class="python">def add_mesh_frame(self, mesh):
1349
    if type(mesh) in (list, tuple):
1350
        actors = []
1351
        for mesh_ in mesh:
1352
            actors.append(self._plotter.add_mesh(
1353
                mesh_, 
1354
                render=False,
1355
                color=self._color, 
1356
                edge_color=self._edge_color, 
1357
                show_edges=self._show_edges
1358
            ))
1359
    else:
1360
        actor = self._plotter.add_mesh(
1361
            mesh, 
1362
            render=False,
1363
            color=self._color, 
1364
            edge_color=self._edge_color, 
1365
            show_edges=self._show_edges
1366
        )
1367
1368
    if self.counter == 0:
1369
        self.update_view()
1370
    self._plotter.write_frame()
1371
    
1372
    if type(mesh) in (list, tuple):
1373
        for actor in actors:
1374
            self._plotter.remove_actor(actor)
1375
    else:
1376
        self._plotter.remove_actor(actor)
1377
    self.counter += 1</code></pre>
1378
</details>
1379
</dd>
1380
<dt id="pymskt.mesh.utils.GIF.done"><code class="name flex">
1381
<span>def <span class="ident">done</span></span>(<span>self)</span>
1382
</code></dt>
1383
<dd>
1384
<div class="desc"></div>
1385
<details class="source">
1386
<summary>
1387
<span>Expand source code</span>
1388
</summary>
1389
<pre><code class="python">def done(self):
1390
    self._plotter.close()</code></pre>
1391
</details>
1392
</dd>
1393
<dt id="pymskt.mesh.utils.GIF.update_view"><code class="name flex">
1394
<span>def <span class="ident">update_view</span></span>(<span>self)</span>
1395
</code></dt>
1396
<dd>
1397
<div class="desc"></div>
1398
<details class="source">
1399
<summary>
1400
<span>Expand source code</span>
1401
</summary>
1402
<pre><code class="python">def update_view(
1403
    self
1404
):
1405
    self._plotter.camera_position = self._camera_position
1406
    self._plotter.window_size = self._window_size
1407
    self._plotter.set_background(color=self._background_color)</code></pre>
1408
</details>
1409
</dd>
1410
</dl>
1411
</dd>
1412
</dl>
1413
</section>
1414
</article>
1415
<nav id="sidebar">
1416
<h1>Index</h1>
1417
<div class="toc">
1418
<ul></ul>
1419
</div>
1420
<ul id="index">
1421
<li><h3>Super-module</h3>
1422
<ul>
1423
<li><code><a title="pymskt.mesh" href="index.html">pymskt.mesh</a></code></li>
1424
</ul>
1425
</li>
1426
<li><h3><a href="#header-functions">Functions</a></h3>
1427
<ul class="">
1428
<li><code><a title="pymskt.mesh.utils.estimate_mesh_scalars_FWHMs" href="#pymskt.mesh.utils.estimate_mesh_scalars_FWHMs">estimate_mesh_scalars_FWHMs</a></code></li>
1429
<li><code><a title="pymskt.mesh.utils.get_arrow" href="#pymskt.mesh.utils.get_arrow">get_arrow</a></code></li>
1430
<li><code><a title="pymskt.mesh.utils.get_intersect" href="#pymskt.mesh.utils.get_intersect">get_intersect</a></code></li>
1431
<li><code><a title="pymskt.mesh.utils.get_obb_surface" href="#pymskt.mesh.utils.get_obb_surface">get_obb_surface</a></code></li>
1432
<li><code><a title="pymskt.mesh.utils.get_surface_distance" href="#pymskt.mesh.utils.get_surface_distance">get_surface_distance</a></code></li>
1433
<li><code><a title="pymskt.mesh.utils.get_surface_normals" href="#pymskt.mesh.utils.get_surface_normals">get_surface_normals</a></code></li>
1434
<li><code><a title="pymskt.mesh.utils.get_symmetric_surface_distance" href="#pymskt.mesh.utils.get_symmetric_surface_distance">get_symmetric_surface_distance</a></code></li>
1435
<li><code><a title="pymskt.mesh.utils.is_hit" href="#pymskt.mesh.utils.is_hit">is_hit</a></code></li>
1436
<li><code><a title="pymskt.mesh.utils.vtk_deep_copy" href="#pymskt.mesh.utils.vtk_deep_copy">vtk_deep_copy</a></code></li>
1437
</ul>
1438
</li>
1439
<li><h3><a href="#header-classes">Classes</a></h3>
1440
<ul>
1441
<li>
1442
<h4><code><a title="pymskt.mesh.utils.GIF" href="#pymskt.mesh.utils.GIF">GIF</a></code></h4>
1443
<ul class="two-column">
1444
<li><code><a title="pymskt.mesh.utils.GIF.add_mesh_frame" href="#pymskt.mesh.utils.GIF.add_mesh_frame">add_mesh_frame</a></code></li>
1445
<li><code><a title="pymskt.mesh.utils.GIF.background_color" href="#pymskt.mesh.utils.GIF.background_color">background_color</a></code></li>
1446
<li><code><a title="pymskt.mesh.utils.GIF.camera_position" href="#pymskt.mesh.utils.GIF.camera_position">camera_position</a></code></li>
1447
<li><code><a title="pymskt.mesh.utils.GIF.color" href="#pymskt.mesh.utils.GIF.color">color</a></code></li>
1448
<li><code><a title="pymskt.mesh.utils.GIF.done" href="#pymskt.mesh.utils.GIF.done">done</a></code></li>
1449
<li><code><a title="pymskt.mesh.utils.GIF.edge_color" href="#pymskt.mesh.utils.GIF.edge_color">edge_color</a></code></li>
1450
<li><code><a title="pymskt.mesh.utils.GIF.path_save" href="#pymskt.mesh.utils.GIF.path_save">path_save</a></code></li>
1451
<li><code><a title="pymskt.mesh.utils.GIF.show_edges" href="#pymskt.mesh.utils.GIF.show_edges">show_edges</a></code></li>
1452
<li><code><a title="pymskt.mesh.utils.GIF.update_view" href="#pymskt.mesh.utils.GIF.update_view">update_view</a></code></li>
1453
<li><code><a title="pymskt.mesh.utils.GIF.window_size" href="#pymskt.mesh.utils.GIF.window_size">window_size</a></code></li>
1454
</ul>
1455
</li>
1456
</ul>
1457
</li>
1458
</ul>
1459
</nav>
1460
</main>
1461
<footer id="footer">
1462
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
1463
</footer>
1464
</body>
1465
</html>