a b/docs/mesh/meshTools.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.meshTools 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.meshTools</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 os
30
import time
31
from turtle import distance
32
33
import vtk
34
from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk
35
36
import SimpleITK as sitk
37
import pyacvd
38
import pyvista as pv
39
40
import numpy as np
41
42
from pymskt.utils import n2l, l2n, safely_delete_tmp_file
43
from pymskt.mesh.utils import is_hit, get_intersect, get_surface_normals, get_obb_surface
44
import pymskt.image as pybtimage
45
import pymskt.mesh.createMesh as createMesh 
46
import pymskt.mesh.meshTransform as meshTransform
47
from pymskt.cython_functions import gaussian_kernel
48
49
epsilon = 1e-7
50
51
class ProbeVtkImageDataAlongLine:
52
    &#34;&#34;&#34;
53
    Class to find values along a line. This is used to get things like the mean T2 value normal
54
    to a bones surface &amp; within the cartialge region. This is done by defining a line in a
55
    particualar location. 
56
57
    Parameters
58
    ----------
59
    line_resolution : float
60
        How many points to create along the line. 
61
    vtk_image : vtk.vtkImageData
62
        Image read into vtk so that we can apply the probe to it. 
63
    save_data_in_class : bool, optional
64
        Whether or not to save data along the line(s) to the class, by default True
65
    save_mean : bool, optional
66
        Whether the mean value should be saved along the line, by default False
67
    save_std : bool, optional
68
        Whether the standard deviation of the data along the line should be
69
        saved, by default False
70
    save_most_common : bool, optional
71
        Whether the mode (most common) value should be saved used for identifying cartilage
72
        regions on the bone surface, by default False
73
    filler : int, optional
74
        What value should be placed at locations where we don&#39;t have a value
75
        (e.g., where we don&#39;t have T2 values), by default 0
76
    non_zero_only : bool, optional
77
        Only save non-zero values along the line, by default True
78
        This is done becuase zeros are normally regions of error (e.g.
79
        poor T2 relaxation fit) and thus would artifically reduce the outcome
80
        along the line. 
81
    
82
    
83
    Attributes
84
    ----------
85
    save_mean : bool
86
        Whether the mean value should be saved along the line, by default False
87
    save_std : bool
88
        Whether the standard deviation of the data along the line should be
89
        saved, by default False
90
    save_most_common : bool 
91
        Whether the mode (most common) value should be saved used for identifying cartilage
92
        regions on the bone surface, by default False
93
    filler : float
94
        What value should be placed at locations where we don&#39;t have a value
95
        (e.g., where we don&#39;t have T2 values), by default 0
96
    non_zero_only : bool 
97
        Only save non-zero values along the line, by default True
98
        This is done becuase zeros are normally regions of error (e.g.
99
        poor T2 relaxation fit) and thus would artifically reduce the outcome
100
        along the line. 
101
    line : vtk.vtkLineSource
102
        Line to put into `probe_filter` and to determine mean/std/common values for. 
103
    probe_filter : vtk.vtkProbeFilter
104
        Filter to use to get the image data along the line. 
105
    _mean_data : list
106
        List of the mean values for each vertex / line projected
107
    _std_data : list
108
        List of standard deviation of each vertex / line projected
109
    _most_common_data : list
110
        List of most common data of each vertex / line projected
111
    
112
    Methods
113
    -------
114
115
116
    &#34;&#34;&#34;    
117
    def __init__(self,
118
                 line_resolution,
119
                 vtk_image,
120
                 save_data_in_class=True,
121
                 save_mean=False,
122
                 save_std=False,
123
                 save_most_common=False,
124
                 save_max=False,
125
                 filler=0,
126
                 non_zero_only=True,
127
                 data_categorical=False
128
                 ):
129
        &#34;&#34;&#34;[summary]
130
131
        Parameters
132
        ----------
133
        line_resolution : float
134
            How many points to create along the line. 
135
        vtk_image : vtk.vtkImageData
136
            Image read into vtk so that we can apply the probe to it. 
137
        save_data_in_class : bool, optional
138
            Whether or not to save data along the line(s) to the class, by default True
139
        save_mean : bool, optional
140
            Whether the mean value should be saved along the line, by default False
141
        save_std : bool, optional
142
            Whether the standard deviation of the data along the line should be
143
            saved, by default False
144
        save_most_common : bool, optional
145
            Whether the mode (most common) value should be saved used for identifying cartilage
146
            regions on the bone surface, by default False
147
        save_max : bool, optional
148
            Whether the max value should be saved along the line, be default False
149
        filler : int, optional
150
            What value should be placed at locations where we don&#39;t have a value
151
            (e.g., where we don&#39;t have T2 values), by default 0
152
        non_zero_only : bool, optional
153
            Only save non-zero values along the line, by default True
154
            This is done becuase zeros are normally regions of error (e.g.
155
            poor T2 relaxation fit) and thus would artifically reduce the outcome
156
            along the line.
157
        data_categorical : bool, optional
158
            Specify whether or not the data is categorical to determine the interpolation
159
            method that should be used. 
160
        &#34;&#34;&#34;        
161
        self.save_mean = save_mean
162
        self.save_std = save_std
163
        self.save_most_common = save_most_common
164
        self.save_max = save_max
165
        self.filler = filler
166
        self.non_zero_only = non_zero_only
167
168
        self.line = vtk.vtkLineSource()
169
        self.line.SetResolution(line_resolution)
170
171
        self.probe_filter = vtk.vtkProbeFilter()
172
        self.probe_filter.SetSourceData(vtk_image)
173
        if data_categorical is True:
174
            self.probe_filter.CategoricalDataOn()
175
176
        if save_data_in_class is True:
177
            if self.save_mean is True:
178
                self._mean_data = []
179
            if self.save_std is True:
180
                self._std_data = []
181
            if self.save_most_common is True:
182
                self._most_common_data = []
183
            if self.save_max is True:
184
                self._max_data = []
185
186
    def get_data_along_line(self,
187
                            start_pt,
188
                            end_pt):
189
        &#34;&#34;&#34;
190
        Function to get scalar values along a line between `start_pt` and `end_pt`. 
191
192
        Parameters
193
        ----------
194
        start_pt : list
195
            List of the x,y,z position of the starting point in the line. 
196
        end_pt : list
197
            List of the x,y,z position of the ending point in the line. 
198
199
        Returns
200
        -------
201
        numpy.ndarray
202
            numpy array of scalar values obtained along the line.
203
        &#34;&#34;&#34;        
204
        self.line.SetPoint1(start_pt)
205
        self.line.SetPoint2(end_pt)
206
207
        self.probe_filter.SetInputConnection(self.line.GetOutputPort())
208
        self.probe_filter.Update()
209
        scalars = vtk_to_numpy(self.probe_filter.GetOutput().GetPointData().GetScalars())
210
211
        if self.non_zero_only is True:
212
            scalars = scalars[scalars != 0]
213
214
        return scalars
215
216
    def save_data_along_line(self,
217
                             start_pt,
218
                             end_pt):
219
        &#34;&#34;&#34;
220
        Save the appropriate outcomes to a growing list. 
221
222
        Parameters
223
        ----------
224
        start_pt : list
225
            List of the x,y,z position of the starting point in the line. 
226
        end_pt : list
227
            List of the x,y,z position of the ending point in the line. 
228
        &#34;&#34;&#34;        
229
        scalars = self.get_data_along_line(start_pt, end_pt)
230
        if len(scalars) &gt; 0:
231
            if self.save_mean is True:
232
                self._mean_data.append(np.mean(scalars))
233
            if self.save_std is True:
234
                self._std_data.append(np.std(scalars, ddof=1))
235
            if self.save_most_common is True:
236
                # most_common is for getting segmentations and trying to assign a bone region
237
                # to be a cartilage ROI. This is becuase there might be a normal vector that
238
                # cross &gt; 1 cartilage region (e.g., weight-bearing vs anterior fem cartilage)
239
                self._most_common_data.append(np.bincount(scalars).argmax())
240
            if self.save_max is True:
241
                self._max_data.append(np.max(scalars))
242
        else:
243
            self.append_filler()
244
245
    def append_filler(self):
246
        &#34;&#34;&#34;
247
        Add filler value to the requisite lists (_mean_data, _std_data, etc.) as 
248
        appropriate. 
249
        &#34;&#34;&#34;        
250
        if self.save_mean is True:
251
            self._mean_data.append(self.filler)
252
        if self.save_std is True:
253
            self._std_data.append(self.filler)
254
        if self.save_most_common is True:
255
            self._most_common_data.append(self.filler)
256
        if self.save_max is True:
257
            self._max_data.append(self.filler)
258
259
    @property
260
    def mean_data(self):
261
        &#34;&#34;&#34;
262
        Return the `_mean_data`
263
264
        Returns
265
        -------
266
        list
267
            List of mean values along each line tested. 
268
        &#34;&#34;&#34;        
269
        if self.save_mean is True:
270
            return self._mean_data
271
        else:
272
            return None
273
274
    @property
275
    def std_data(self):
276
        &#34;&#34;&#34;
277
        Return the `_std_data`
278
279
        Returns
280
        -------
281
        list
282
            List of the std values along each line tested. 
283
        &#34;&#34;&#34;        
284
        if self.save_std is True:
285
            return self._std_data
286
        else:
287
            return None
288
289
    @property
290
    def most_common_data(self):
291
        &#34;&#34;&#34;
292
        Return the `_most_common_data`
293
294
        Returns
295
        -------
296
        list
297
            List of the most common value for each line tested. 
298
        &#34;&#34;&#34;        
299
        if self.save_most_common is True:
300
            return self._most_common_data
301
        else:
302
            return None
303
    
304
    @property
305
    def max_data(self):
306
        &#34;&#34;&#34;
307
        Return the `_max_data`
308
309
        Returns
310
        -------
311
        list
312
            List of the most common value for each line tested. 
313
        &#34;&#34;&#34;        
314
        if self.save_max is True:
315
            return self._max_data
316
        else:
317
            return None
318
319
320
def get_cartilage_properties_at_points(surface_bone,
321
                                       surface_cartilage,
322
                                       t2_vtk_image=None,
323
                                       seg_vtk_image=None,
324
                                       ray_cast_length=20.,
325
                                       percent_ray_length_opposite_direction=0.25,
326
                                       no_thickness_filler=0.,
327
                                       no_t2_filler=0.,
328
                                       no_seg_filler=0,
329
                                       line_resolution=100):  # Could be nan??
330
    &#34;&#34;&#34;
331
    Extract cartilage outcomes (T2 &amp; thickness) at all points on bone surface. 
332
333
    Parameters
334
    ----------
335
    surface_bone : BoneMesh
336
        Bone mesh containing vtk.vtkPolyData - get outcomes for nodes (vertices) on
337
        this mesh
338
    surface_cartilage : CartilageMesh
339
        Cartilage mesh containing vtk.vtkPolyData - for obtaining cartilage outcomes.
340
    t2_vtk_image : vtk.vtkImageData, optional
341
        vtk object that contains our Cartilage T2 data, by default None
342
    seg_vtk_image : vtk.vtkImageData, optional
343
        vtk object that contains the segmentation mask(s) to help assign
344
        labels to bone surface (e.g., most common), by default None
345
    ray_cast_length : float, optional
346
        Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
347
        outter shell), by default 20.0
348
    percent_ray_length_opposite_direction : float, optional
349
        How far to project ray inside of the bone. This is done just in case the cartilage
350
        surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
351
    no_thickness_filler : float, optional
352
        Value to use instead of thickness (if no cartilage), by default 0.
353
    no_t2_filler : float, optional
354
        Value to use instead of T2 (if no cartilage), by default 0.
355
    no_seg_filler : int, optional
356
        Value to use if no segmentation label available (because no cartilage?), by default 0
357
    line_resolution : int, optional
358
        Number of points to have along line, by default 100
359
360
    Returns
361
    -------
362
    list
363
        Will return list of data for:
364
            Cartilage thickness
365
            Mean T2 at each point on bone
366
            Most common cartilage label at each point on bone (normal to surface).
367
    &#34;&#34;&#34;    
368
369
    normals = get_surface_normals(surface_bone)
370
    points = surface_bone.GetPoints()
371
    obb_cartilage = get_obb_surface(surface_cartilage)
372
    point_normals = normals.GetOutput().GetPointData().GetNormals()
373
374
    thickness_data = []
375
    if (t2_vtk_image is not None) or (seg_vtk_image is not None):
376
        # if T2 data, or a segmentation image is provided, then setup Probe tool to
377
        # get T2 values and/or cartilage ROI for each bone vertex. 
378
        line = vtk.vtkLineSource()
379
        line.SetResolution(line_resolution)
380
381
        if t2_vtk_image is not None:
382
            t2_data_probe = ProbeVtkImageDataAlongLine(line_resolution,
383
                                                  t2_vtk_image,
384
                                                  save_mean=True,
385
                                                  filler=no_t2_filler)
386
        if seg_vtk_image is not None:
387
            seg_data_probe = ProbeVtkImageDataAlongLine(line_resolution,
388
                                                   seg_vtk_image,
389
                                                   save_most_common=True,
390
                                                   filler=no_seg_filler,
391
                                                   data_categorical=True)
392
    # Loop through all points
393
    for idx in range(points.GetNumberOfPoints()):
394
        point = points.GetPoint(idx)
395
        normal = point_normals.GetTuple(idx)
396
397
        end_point_ray = n2l(l2n(point) + ray_cast_length*l2n(normal))
398
        start_point_ray = n2l(l2n(point) + ray_cast_length*percent_ray_length_opposite_direction*(-l2n(normal)))
399
400
        # Check if there are any intersections for the given ray
401
        if is_hit(obb_cartilage, start_point_ray, end_point_ray):  # intersections were found
402
            # Retrieve coordinates of intersection points and intersected cell ids
403
            points_intersect, cell_ids_intersect = get_intersect(obb_cartilage, start_point_ray, end_point_ray)
404
    #         points
405
            if len(points_intersect) == 2:
406
                thickness_data.append(np.sqrt(np.sum(np.square(l2n(points_intersect[0]) - l2n(points_intersect[1])))))
407
                if t2_vtk_image is not None:
408
                    t2_data_probe.save_data_along_line(start_pt=points_intersect[0],
409
                                                       end_pt=points_intersect[1])
410
                if seg_vtk_image is not None:
411
                    seg_data_probe.save_data_along_line(start_pt=points_intersect[0],
412
                                                        end_pt=points_intersect[1])
413
414
            else:
415
                thickness_data.append(no_thickness_filler)
416
                if t2_vtk_image is not None:
417
                    t2_data_probe.append_filler()
418
                if seg_vtk_image is not None:
419
                    seg_data_probe.append_filler()
420
        else:
421
            thickness_data.append(no_thickness_filler)
422
            if t2_vtk_image is not None:
423
                t2_data_probe.append_filler()
424
            if seg_vtk_image is not None:
425
                seg_data_probe.append_filler()
426
427
    if (t2_vtk_image is None) &amp; (seg_vtk_image is None):
428
        return np.asarray(thickness_data, dtype=np.float)
429
    elif (t2_vtk_image is not None) &amp; (seg_vtk_image is not None):
430
        return (np.asarray(thickness_data, dtype=np.float),
431
                np.asarray(t2_data_probe.mean_data, dtype=np.float),
432
                np.asarray(seg_data_probe.most_common_data, dtype=np.int)
433
                )
434
    elif t2_vtk_image is not None:
435
        return (np.asarray(thickness_data, dtype=np.float),
436
                np.asarray(t2_data_probe.mean_data, dtype=np.float)
437
                )
438
    elif seg_vtk_image is not None:
439
        return (np.asarray(thickness_data, dtype=np.float),
440
                np.asarray(seg_data_probe.most_common_data, dtype=np.int)
441
                )
442
443
def set_mesh_physical_point_coords(mesh, new_points):
444
    &#34;&#34;&#34;
445
    Convenience function to update the x/y/z point coords of a mesh
446
447
    Nothing is returned becuase the mesh object is updated in-place. 
448
449
    Parameters
450
    ----------
451
    mesh : vtk.vtkPolyData
452
        Mesh object we want to update the point coordinates for
453
    new_points : np.ndarray
454
        Numpy array shaped n_points x 3. These are the new point coordinates that
455
        we want to update the mesh to have. 
456
457
    &#34;&#34;&#34;
458
    orig_point_coords = get_mesh_physical_point_coords(mesh)
459
    if new_points.shape == orig_point_coords.shape:
460
        mesh.GetPoints().SetData(numpy_to_vtk(new_points))
461
462
463
def get_mesh_physical_point_coords(mesh):
464
    &#34;&#34;&#34;
465
    Get a numpy array of the x/y/z location of each point (vertex) on the `mesh`.
466
467
    Parameters
468
    ----------
469
    mesh : 
470
        [description]
471
472
    Returns
473
    -------
474
    numpy.ndarray
475
        n_points x 3 array describing the x/y/z position of each point. 
476
    
477
    Notes
478
    -----
479
    Below is the original method used to retrieve the point coordinates. 
480
    
481
    point_coordinates = np.zeros((mesh.GetNumberOfPoints(), 3))
482
    for pt_idx in range(mesh.GetNumberOfPoints()):
483
        point_coordinates[pt_idx, :] = mesh.GetPoint(pt_idx)
484
    &#34;&#34;&#34;    
485
    
486
    point_coordinates = vtk_to_numpy(mesh.GetPoints().GetData())
487
    return point_coordinates
488
489
def smooth_scalars_from_second_mesh_onto_base(base_mesh,
490
                                              second_mesh,
491
                                              sigma=1.,
492
                                              idx_coords_to_smooth_base=None,
493
                                              idx_coords_to_smooth_second=None,
494
                                              set_non_smoothed_scalars_to_zero=True
495
                                              ):  # sigma is equal to fwhm=2 (1mm in each direction)
496
    &#34;&#34;&#34;
497
    Function to copy surface scalars from one mesh to another. This is done in a &#34;smoothing&#34; fashioon
498
    to get a weighted-average of the closest point - this is because the points on the 2 meshes won&#39;t
499
    be coincident with one another. The weighted average is done using a gaussian smoothing.
500
501
    Parameters
502
    ----------
503
    base_mesh : vtk.vtkPolyData
504
        The base mesh to smooth the scalars from `second_mesh` onto. 
505
    second_mesh : vtk.vtkPolyData
506
        The mesh with the scalar values that we want to pass onto the `base_mesh`.
507
    sigma : float, optional
508
        Sigma (standard deviation) of gaussian filter to apply to scalars, by default 1.
509
    idx_coords_to_smooth_base : list, optional
510
        List of the indices of nodes that are of interest for transferring (typically cartilage), 
511
        by default None
512
    idx_coords_to_smooth_second : list, optional
513
        List of the indices of the nodes that are of interest on the second mesh, by default None
514
    set_non_smoothed_scalars_to_zero : bool, optional
515
        Whether or not to set all notes that are not smoothed to zero, by default True
516
517
    Returns
518
    -------
519
    numpy.ndarray
520
        An array of the scalar values for each node on the base mesh that includes the scalar values
521
        transfered (smoothed) from the secondary mesh. 
522
    &#34;&#34;&#34;    
523
    base_mesh_pts = get_mesh_physical_point_coords(base_mesh)
524
    if idx_coords_to_smooth_base is not None:
525
        base_mesh_pts = base_mesh_pts[idx_coords_to_smooth_base, :]
526
    second_mesh_pts = get_mesh_physical_point_coords(second_mesh)
527
    if idx_coords_to_smooth_second is not None:
528
        second_mesh_pts = second_mesh_pts[idx_coords_to_smooth_second, :]
529
    gauss_kernel = gaussian_kernel(base_mesh_pts, second_mesh_pts, sigma=sigma)
530
    second_mesh_scalars = np.copy(vtk_to_numpy(second_mesh.GetPointData().GetScalars()))
531
    if idx_coords_to_smooth_second is not None:
532
        # If sub-sampled second mesh - then only give the scalars from those sub-sampled points on mesh.
533
        second_mesh_scalars = second_mesh_scalars[idx_coords_to_smooth_second]
534
535
    smoothed_scalars_on_base = np.sum(gauss_kernel * second_mesh_scalars, axis=1)
536
537
    if idx_coords_to_smooth_base is not None:
538
        # if sub-sampled baseline mesh (only want to set cartilage to certain points/vertices), then
539
        # set the calculated smoothed scalars to only those nodes (and leave all other nodes the same as they were
540
        # originally.
541
        if set_non_smoothed_scalars_to_zero is True:
542
            base_mesh_scalars = np.zeros(base_mesh.GetNumberOfPoints())
543
        else:
544
            base_mesh_scalars = np.copy(vtk_to_numpy(base_mesh.GetPointData().GetScalars()))
545
        base_mesh_scalars[idx_coords_to_smooth_base] = smoothed_scalars_on_base
546
        return base_mesh_scalars
547
548
    else:
549
        return smoothed_scalars_on_base
550
551
552
def transfer_mesh_scalars_get_weighted_average_n_closest(new_mesh, old_mesh, n=3):
553
    &#34;&#34;&#34;
554
    Transfer scalars from old_mesh to new_mesh using the weighted-average of the `n` closest
555
    nodes/points/vertices. Similar but not exactly the same as `smooth_scalars_from_second_mesh_onto_base`
556
    
557
    This function is ideally used for things like transferring cartilage thickness values from one mesh to another 
558
    after they have been registered together. This is necessary for things like performing statistical analyses or
559
    getting aggregate statistics. 
560
561
    Parameters
562
    ----------
563
    new_mesh : vtk.vtkPolyData
564
        The new mesh that we want to transfer scalar values onto. Also `base_mesh` from
565
        `smooth_scalars_from_second_mesh_onto_base` 
566
    old_mesh : vtk.vtkPolyData
567
        The mesh that we want to transfer scalars from. Also called `second_mesh` from 
568
        `smooth_scalars_from_second_mesh_onto_base`
569
    n : int, optional
570
        The number of closest nodes that we want to get weighed average of, by default 3
571
572
    Returns
573
    -------
574
    numpy.ndarray
575
        An array of the scalar values for each node on the `new_mesh` that includes the scalar values
576
        transfered (smoothed) from the `old_mesh`. 
577
    &#34;&#34;&#34;    
578
579
    kDTree = vtk.vtkKdTreePointLocator()
580
    kDTree.SetDataSet(old_mesh)
581
    kDTree.BuildLocator()
582
583
    n_arrays = old_mesh.GetPointData().GetNumberOfArrays()
584
    array_names = [old_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
585
    new_scalars = np.zeros((new_mesh.GetNumberOfPoints(), n_arrays))
586
    scalars_old_mesh = [np.copy(vtk_to_numpy(old_mesh.GetPointData().GetArray(array_name))) for array_name in array_names]
587
    # print(&#39;len scalars_old_mesh&#39;, len(scalars_old_mesh))
588
    # scalars_old_mesh = np.copy(vtk_to_numpy(old_mesh.GetPointData().GetScalars()))
589
    for new_mesh_pt_idx in range(new_mesh.GetNumberOfPoints()):
590
        point = new_mesh.GetPoint(new_mesh_pt_idx)
591
        closest_ids = vtk.vtkIdList()
592
        kDTree.FindClosestNPoints(n, point, closest_ids)
593
594
        list_scalars = []
595
        distance_weighting = []
596
        for closest_pts_idx in range(closest_ids.GetNumberOfIds()):
597
            pt_idx = closest_ids.GetId(closest_pts_idx)
598
            _point = old_mesh.GetPoint(pt_idx)
599
            list_scalars.append([scalars[pt_idx] for scalars in scalars_old_mesh])
600
            distance_weighting.append(1 / np.sqrt(np.sum(np.square(np.asarray(point) - np.asarray(_point) + epsilon))))
601
    
602
        total_distance = np.sum(distance_weighting)
603
        # print(&#39;list_scalars&#39;, list_scalars)
604
        # print(&#39;distance_weighting&#39;, distance_weighting)
605
        # print(&#39;total_distance&#39;, total_distance)
606
        normalized_value = np.sum(np.asarray(list_scalars) * np.expand_dims(np.asarray(distance_weighting), axis=1),
607
                                  axis=0) / total_distance
608
        # print(&#39;new_mesh_pt_idx&#39;, new_mesh_pt_idx)
609
        # print(&#39;normalized_value&#39;, normalized_value)
610
        # print(&#39;new_scalars shape&#39;, new_scalars.shape)
611
        new_scalars[new_mesh_pt_idx, :] = normalized_value
612
    return new_scalars
613
614
def get_smoothed_scalars(mesh, max_dist=2.0, order=2, gaussian=False):
615
    &#34;&#34;&#34;
616
    perform smoothing of scalars on the nodes of a surface mesh. 
617
    return the smoothed values of the nodes so they can be used as necessary. 
618
    (e.g. to replace originals or something else)
619
    Smoothing is done for all data within `max_dist` and uses a simple weighted average based on
620
    the distance to the power of `order`. Default is squared distance (`order=2`)
621
622
    Parameters
623
    ----------
624
    mesh : vtk.vtkPolyData
625
        Surface mesh that we want to smooth scalars of. 
626
    max_dist : float, optional
627
        Maximum distance of nodes that we want to smooth (mm), by default 2.0
628
    order : int, optional
629
        Order of the polynomial used for weighting other nodes within `max_dist`, by default 2
630
    gaussian : bool, optional
631
        Should this use a gaussian smoothing, or weighted average, by default False
632
633
    Returns
634
    -------
635
    numpy.ndarray
636
        An array of the scalar values for each node on the `mesh` after they have been smoothed. 
637
    &#34;&#34;&#34;    
638
639
    kDTree = vtk.vtkKdTreePointLocator()
640
    kDTree.SetDataSet(mesh)
641
    kDTree.BuildLocator()
642
643
    thickness_smoothed = np.zeros(mesh.GetNumberOfPoints())
644
    scalars = l2n(mesh.GetPointData().GetScalars())
645
    for idx in range(mesh.GetNumberOfPoints()):
646
        if scalars[idx] &gt;0:  # don&#39;t smooth nodes with thickness == 0 (or negative? if that were to happen)
647
            point = mesh.GetPoint(idx)
648
            closest_ids = vtk.vtkIdList()
649
            kDTree.FindPointsWithinRadius(max_dist, point, closest_ids) # This will return a value ( 0 or 1). Can use that for debudding.
650
651
            list_scalars = []
652
            list_distances = []
653
            for closest_pt_idx in range(closest_ids.GetNumberOfIds()):
654
                pt_idx = closest_ids.GetId(closest_pt_idx)
655
                _point = mesh.GetPoint(pt_idx)
656
                list_scalars.append(scalars[pt_idx])
657
                list_distances.append(np.sqrt(np.sum(np.square(np.asarray(point) - np.asarray(_point) + epsilon))))
658
659
            distances_weighted = (max_dist - np.asarray(list_distances))**order
660
            scalars_weights = distances_weighted * np.asarray(list_scalars)
661
            normalized_value = np.sum(scalars_weights) / np.sum(distances_weighted)
662
            thickness_smoothed[idx] = normalized_value
663
    return thickness_smoothed
664
665
def gaussian_smooth_surface_scalars(mesh, sigma=1., idx_coords_to_smooth=None, array_name=&#39;thickness (mm)&#39;, array_idx=None):
666
    &#34;&#34;&#34;
667
    The following is another function to smooth the scalar values on the surface of a mesh. 
668
    This one performs a gaussian smoothing using the supplied sigma and only smooths based on 
669
    the input `idx_coords_to_smooth`. If no `idx_coords_to_smooth` is provided, then all of the
670
    points are smoothed. `idx_coords_to_smooth` should be a list of indices of points to include. 
671
672
    e.g., coords_to_smooth = np.where(vtk_to_numpy(mesh.GetPointData().GetScalars())&gt;0.01)[0]
673
    This would give only coordinates where the scalar values of the mesh are &gt;0.01. This example is
674
    useful for cartilage where we might only want to smooth in locations that we have cartilage and
675
    ignore the other areas. 
676
677
    Parameters
678
    ----------
679
    mesh : vtk.vtkPolyData
680
        This is a surface mesh of that we want to smooth the scalars of. 
681
    sigma : float, optional
682
        The standard deviation of the gaussian filter to apply, by default 1.
683
    idx_coords_to_smooth : list, optional
684
        List of the indices of the vertices (points) that we want to include in the
685
        smoothing. For example, we can only smooth values that are cartialge and ignore
686
        all non-cartilage points, by default None
687
    array_name : str, optional
688
        Name of the scalar array that we want to smooth/filter, by default &#39;thickness (mm)&#39;
689
    array_idx : int, optional
690
        The index of the scalar array that we want to smooth/filter - this is an alternative
691
        option to `array_name`, by default None
692
693
    Returns
694
    -------
695
    vtk.vtkPolyData
696
        Return the original mesh for which the scalars have been smoothed. However, this is not
697
        necessary becuase if the original mesh still exists then it should have been updated
698
        during the course of the pipeline. 
699
    &#34;&#34;&#34;    
700
701
    point_coordinates = get_mesh_physical_point_coords(mesh)
702
    if idx_coords_to_smooth is not None:
703
        point_coordinates = point_coordinates[idx_coords_to_smooth, :]
704
    kernel = gaussian_kernel(point_coordinates, point_coordinates, sigma=sigma)
705
706
    original_array = mesh.GetPointData().GetArray(array_idx if array_idx is not None else array_name)
707
    original_scalars = np.copy(vtk_to_numpy(original_array))
708
709
    if idx_coords_to_smooth is not None:
710
        smoothed_scalars = np.sum(kernel * original_scalars[idx_coords_to_smooth],
711
                                    axis=1)
712
        original_scalars[idx_coords_to_smooth] = smoothed_scalars
713
        smoothed_scalars = original_scalars
714
    else:
715
        smoothed_scalars = np.sum(kernel * original_scalars, axis=1)
716
717
    smoothed_scalars = numpy_to_vtk(smoothed_scalars)
718
    smoothed_scalars.SetName(original_array.GetName())
719
    original_array.DeepCopy(smoothed_scalars) # Assign the scalars back to the original mesh
720
721
    # return the mesh object - however, if the original is not deleted, it should be smoothed
722
    # appropriately. 
723
    return mesh
724
725
def resample_surface(mesh, subdivisions=2, clusters=10000):
726
    &#34;&#34;&#34;
727
    Resample a surface mesh using the ACVD algorithm: 
728
    Version used: 
729
    - https://github.com/pyvista/pyacvd
730
    Original version w/ more references: 
731
    - https://github.com/valette/ACVD
732
733
    Parameters
734
    ----------
735
    mesh : vtk.vtkPolyData
736
        Polydata mesh to be re-sampled. 
737
    subdivisions : int, optional
738
        Subdivide the mesh to have more points before clustering, by default 2
739
        Probably not necessary for very dense meshes.
740
    clusters : int, optional
741
        The number of clusters (points/vertices) to create during resampling 
742
        surafce, by default 10000
743
        - This is not exact, might have slight differences.
744
    
745
        Returns
746
    -------
747
    vtk.vtkPolyData :
748
        Return the resampled mesh. This will be a pyvista version of the vtk mesh
749
        but this is usable in all vtk function so it is not an issue. 
750
        
751
752
    &#34;&#34;&#34;        
753
    pv_smooth_mesh = pv.wrap(mesh)
754
    clus = pyacvd.Clustering(pv_smooth_mesh)
755
    clus.subdivide(subdivisions)
756
    clus.cluster(clusters)
757
    mesh = clus.create_mesh()
758
759
    return mesh
760
### THE FOLLOWING IS AN OLD/ORIGINAL VERSION OF THIS THAT SMOOTHED ALL ARRAYS ATTACHED TO MESH
761
# def gaussian_smooth_surface_scalars(mesh, sigma=(1,), idx_coords_to_smooth=None):
762
#     &#34;&#34;&#34;
763
#     The following is another function to smooth the scalar values on the surface of a mesh. 
764
#     This one performs a gaussian smoothing using the supplied sigma and only smooths based on 
765
#     the input `idx_coords_to_smooth`. If no `idx_coords_to_smooth` is provided, then all of the
766
#     points are smoothed. `idx_coords_to_smooth` should be a list of indices of points to include. 
767
768
#     e.g., coords_to_smooth = np.where(vtk_to_numpy(mesh.GetPointData().GetScalars())&gt;0.01)[0]
769
#     This would give only coordinates where the scalar values of the mesh are &gt;0.01. This example is
770
#     useful for cartilage where we might only want to smooth in locations that we have cartilage and
771
#     ignore the other areas. 
772
773
#     &#34;&#34;&#34;
774
#     point_coordinates = get_mesh_physical_point_coords(mesh)
775
#     if idx_coords_to_smooth is not None:
776
#         point_coordinates = point_coordinates[idx_coords_to_smooth, :]
777
#     kernels = []
778
#     if isinstance(sigma, (list, tuple)):
779
#         for sig in sigma:
780
#             kernels.append(gaussian_kernel(point_coordinates, point_coordinates, sigma=sig))
781
#     elif isinstance(sigma, (float, int)):
782
#         kernels.append(gaussian_kernel(point_coordinates, point_coordinates, sigma=sigma))
783
784
#     n_arrays = mesh.GetPointData().GetNumberOfArrays()
785
#     if n_arrays &gt; len(kernels):
786
#         if len(kernels) == 1:
787
#             kernels = [kernels[0] for x in range(n_arrays)]
788
#     for array_idx in range(n_arrays):
789
#         original_array = mesh.GetPointData().GetArray(array_idx)
790
#         original_scalars = np.copy(vtk_to_numpy(original_array))
791
792
#         if idx_coords_to_smooth is not None:
793
#             smoothed_scalars = np.sum(kernels[array_idx] * original_scalars[idx_coords_to_smooth],
794
#                                       axis=1)
795
#             original_scalars[idx_coords_to_smooth] = smoothed_scalars
796
#             smoothed_scalars = original_scalars
797
#         else:
798
#             smoothed_scalars = np.sum(kernels[array_idx] * original_scalars, axis=1)
799
800
#         smoothed_scalars = numpy_to_vtk(smoothed_scalars)
801
#         smoothed_scalars.SetName(original_array.GetName())
802
#         original_array.DeepCopy(smoothed_scalars)
803
804
#     return mesh
805
806
# def get_smoothed_cartilage_thickness_values(loc_nrrd_images,
807
#                                             seg_image_name,
808
#                                             bone_label,
809
#                                             list_cart_labels,
810
#                                             image_smooth_var=1.0,
811
#                                             smooth_cart=False,
812
#                                             image_smooth_var_cart=1.0,
813
#                                             ray_cast_length=10.,
814
#                                             percent_ray_len_opposite_dir=0.2,
815
#                                             smooth_surface_scalars=True,
816
#                                             smooth_only_cartilage_values=True,
817
#                                             scalar_gauss_sigma=1.6986436005760381,  # This is a FWHM = 4
818
#                                             bone_pyacvd_subdivisions=2,
819
#                                             bone_pyacvd_clusters=20000,
820
#                                             crop_bones=False,
821
#                                             crop_percent=0.7,
822
#                                             bone=None,
823
#                                             loc_t2_map_nrrd=None,
824
#                                             t2_map_filename=None,
825
#                                             t2_smooth_sigma_multiple_of_thick=3,
826
#                                             assign_seg_label_to_bone=False,
827
#                                             mc_threshold=0.5,
828
#                                             bone_label_threshold=5000,
829
#                                             path_to_seg_transform=None,
830
#                                             reverse_seg_transform=True,
831
#                                             verbose=False):
832
#     &#34;&#34;&#34;
833
834
#     :param loc_nrrd_images:
835
#     :param seg_image_name:
836
#     :param bone_label:
837
#     :param list_cart_labels:
838
#     :param image_smooth_var:
839
#     :param loc_tmp_save:
840
#     :param tmp_bone_filename:
841
#     :param smooth_cart:
842
#     :param image_smooth_var_cart:
843
#     :param tmp_cart_filename:
844
#     :param ray_cast_length:
845
#     :param percent_ray_len_opposite_dir:
846
#     :param smooth_surface_scalars:
847
#     :param smooth_surface_scalars_gauss:
848
#     :param smooth_only_cartilage_values:
849
#     :param scalar_gauss_sigma:
850
#     :param scalar_smooth_max_dist:
851
#     :param scalar_smooth_order:
852
#     :param bone_pyacvd_subdivisions:
853
#     :param bone_pyacvd_clusters:
854
#     :param crop_bones:
855
#     :param crop_percent:
856
#     :param bone:
857
#     :param tmp_cropped_image_filename:
858
#     :param loc_t2_map_nrrd:.
859
#     :param t2_map_filename:
860
#     :param t2_smooth_sigma_multiple_of_thick:
861
#     :param assign_seg_label_to_bone:
862
#     :param multiple_cart_labels_separate:
863
#     :param mc_threshold:
864
#     :return:
865
866
#     Notes:
867
#     multiple_cart_labels_separate REMOVED from the function.
868
#     &#34;&#34;&#34;
869
#     # Read segmentation image
870
#     seg_image = sitk.ReadImage(os.path.join(loc_nrrd_images, seg_image_name))
871
#     seg_image = set_seg_border_to_zeros(seg_image, border_size=1)
872
873
#     seg_view = sitk.GetArrayViewFromImage(seg_image)
874
#     n_pixels_labelled = sum(seg_view[seg_view == bone_label])
875
876
#     if n_pixels_labelled &lt; bone_label_threshold:
877
#         raise Exception(&#39;The bone does not exist in this segmentation!, only {} pixels detected, threshold # is {}&#39;.format(n_pixels_labelled, 
878
#                                                                                                                            bone_label_threshold))
879
    
880
#     # Read segmentation in vtk format if going to assign labels to surface.
881
#     # Also, if femur break it up into its parts.
882
#     if assign_seg_label_to_bone is True:
883
#         tmp_filename = &#39;&#39;.join(random.choice(string.ascii_lowercase) for i in range(10)) + &#39;.nrrd&#39;
884
#         if bone == &#39;femur&#39;:
885
#             new_seg_image = qc.get_knee_segmentation_with_femur_subregions(seg_image,
886
#                                                                            fem_cart_label_idx=1)
887
#             sitk.WriteImage(new_seg_image, os.path.join(&#39;/tmp&#39;, tmp_filename))
888
#         else:
889
#             sitk.WriteImage(seg_image, os.path.join(&#39;/tmp&#39;, tmp_filename))
890
#         vtk_seg_reader = read_nrrd(&#39;/tmp&#39;,
891
#                                    tmp_filename,
892
#                                    set_origin_zero=True
893
#                                    )
894
#         vtk_seg = vtk_seg_reader.GetOutput()
895
896
#         seg_transformer = SitkVtkTransformer(seg_image)
897
898
#         # Delete tmp files
899
#         safely_delete_tmp_file(&#39;/tmp&#39;,
900
#                                tmp_filename)
901
902
#     # Crop the bones if that&#39;s an option/thing.
903
#     if crop_bones is True:
904
#         if &#39;femur&#39; in bone:
905
#             bone_crop_distal = True
906
#         elif &#39;tibia&#39; in bone:
907
#             bone_crop_distal = False
908
#         else:
909
#             raise Exception(&#39;var bone should be &#34;femur&#34; or &#34;tiba&#34; got: {} instead&#39;.format(bone))
910
911
#         seg_image = crop_bone_based_on_width(seg_image,
912
#                                              bone_label,
913
#                                              percent_width_to_crop_height=crop_percent,
914
#                                              bone_crop_distal=bone_crop_distal)
915
916
#     if verbose is True:
917
#         tic = time.time()
918
919
#     # Create bone mesh/smooth/resample surface points.
920
#     ns_bone_mesh = BoneMesh(seg_image=seg_image,
921
#                             label_idx=bone_label)
922
#     if verbose is True:
923
#         print(&#39;Loaded mesh&#39;)
924
#     ns_bone_mesh.create_mesh(smooth_image=True,
925
#                              smooth_image_var=image_smooth_var,
926
#                              marching_cubes_threshold=mc_threshold
927
#                              )
928
#     if verbose is True:
929
#        print(&#39;Smoothed bone surface&#39;)
930
#     ns_bone_mesh.resample_surface(subdivisions=bone_pyacvd_subdivisions,
931
#                                   clusters=bone_pyacvd_clusters)
932
#     if verbose is True:
933
#        print(&#39;Resampled surface&#39;)
934
#     n_bone_points = ns_bone_mesh._mesh.GetNumberOfPoints()
935
936
#     if verbose is True:
937
#         toc = time.time()
938
#         print(&#39;Creating bone mesh took: {}&#39;.format(toc - tic))
939
#         tic = time.time()
940
941
#     # Pre-allocate empty arrays for t2/labels if they are being placed on surface.
942
#     if assign_seg_label_to_bone is True:
943
#         # Apply inverse transform to get it into the space of the image.
944
#         # This is easier than the reverse function.
945
#         if assign_seg_label_to_bone is True:
946
#             ns_bone_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
947
948
#             labels = np.zeros(n_bone_points, dtype=np.int)
949
950
#     thicknesses = np.zeros(n_bone_points, dtype=np.float)
951
#     if verbose is True:
952
#        print(&#39;Number bone mesh points: {}&#39;.format(n_bone_points))
953
954
#     # Iterate over cartilage labels
955
#     # Create mesh &amp; store thickness + cartilage label + t2 in arrays
956
#     for cart_label_idx in list_cart_labels:
957
#         # Test to see if this particular cartilage label even exists in the label :P
958
#         # This is important for people that may have no cartilage (of a particular type)
959
#         seg_array_view = sitk.GetArrayViewFromImage(seg_image)
960
#         n_pixels_with_cart = np.sum(seg_array_view == cart_label_idx)
961
#         if n_pixels_with_cart == 0:
962
#             print(&#34;Not analyzing cartilage for label {} because it doesnt have any pixels!&#34;.format(cart_label_idx))
963
#             continue
964
965
#         ns_cart_mesh = CartilageMesh(seg_image=seg_image,
966
#                                      label_idx=cart_label_idx)
967
#         ns_cart_mesh.create_mesh(smooth_image=smooth_cart,
968
#                                  smooth_image_var=image_smooth_var_cart,
969
#                                  marching_cubes_threshold=mc_threshold)
970
971
#         # Perform Thickness &amp; label simultaneously. 
972
973
#         if assign_seg_label_to_bone is True:
974
#             ns_cart_mesh.apply_transform_to_mesh(transform=seg_transformer.get_inverse_transform())
975
976
#         node_data = get_cartilage_properties_at_points(ns_bone_mesh._mesh,
977
#                                                        ns_cart_mesh._mesh,
978
#                                                        t2_vtk_image=None,
979
#                                                        seg_vtk_image=vtk_seg if assign_seg_label_to_bone is True else None,
980
#                                                        ray_cast_length=ray_cast_length,
981
#                                                        percent_ray_length_opposite_direction=percent_ray_len_opposite_dir
982
#                                                        )
983
#         if assign_seg_label_to_bone is False:
984
#             thicknesses += node_data
985
#         else:
986
#             thicknesses += node_data[0]
987
#             labels += node_data[1]
988
989
#         if verbose is True:
990
#             print(&#39;Cartilage label: {}&#39;.format(cart_label_idx))
991
#             print(&#39;Mean thicknesses (all): {}&#39;.format(np.mean(thicknesses)))
992
993
#     if verbose is True:
994
#         toc = time.time()
995
#         print(&#39;Calculating all thicknesses: {}&#39;.format(toc - tic))
996
#         tic = time.time()
997
998
#     # Assign thickness &amp; T2 data (if it exists) to bone surface.
999
#     thickness_scalars = numpy_to_vtk(thicknesses)
1000
#     thickness_scalars.SetName(&#39;thickness (mm)&#39;)
1001
#     ns_bone_mesh._mesh.GetPointData().SetScalars(thickness_scalars)
1002
    
1003
#     # Smooth surface scalars
1004
#     if smooth_surface_scalars is True:
1005
#         if smooth_only_cartilage_values is True:
1006
#             loc_cartilage = np.where(vtk_to_numpy(ns_bone_mesh._mesh.GetPointData().GetScalars())&gt;0.01)[0]
1007
#             ns_bone_mesh.mesh = gaussian_smooth_surface_scalars(ns_bone_mesh.mesh,
1008
#                                                                 sigma=scalar_gauss_sigma,
1009
#                                                                 idx_coords_to_smooth=loc_cartilage)
1010
#         else:
1011
#             ns_bone_mesh.mesh = gaussian_smooth_surface_scalars(ns_bone_mesh.mesh, sigma=scalar_gauss_sigma)
1012
1013
#         if verbose is True:
1014
#             toc = time.time()
1015
#             print(&#39;Smoothing scalars took: {}&#39;.format(toc - tic))
1016
1017
#     # Add the label values to the bone after smoothing is finished.
1018
#     if assign_seg_label_to_bone is True:
1019
#         label_scalars = numpy_to_vtk(labels)
1020
#         label_scalars.SetName(&#39;Cartilage Region&#39;)
1021
#         ns_bone_mesh._mesh.GetPointData().AddArray(label_scalars)
1022
1023
#     if assign_seg_label_to_bone is True:
1024
#         # Transform bone back to the position it was in before rotating it (for the t2 analysis)
1025
#         ns_bone_mesh.reverse_all_transforms()
1026
1027
#     return ns_bone_mesh.mesh</code></pre>
1028
</details>
1029
</section>
1030
<section>
1031
</section>
1032
<section>
1033
</section>
1034
<section>
1035
<h2 class="section-title" id="header-functions">Functions</h2>
1036
<dl>
1037
<dt id="pymskt.mesh.meshTools.gaussian_smooth_surface_scalars"><code class="name flex">
1038
<span>def <span class="ident">gaussian_smooth_surface_scalars</span></span>(<span>mesh, sigma=1.0, idx_coords_to_smooth=None, array_name='thickness (mm)', array_idx=None)</span>
1039
</code></dt>
1040
<dd>
1041
<div class="desc"><p>The following is another function to smooth the scalar values on the surface of a mesh.
1042
This one performs a gaussian smoothing using the supplied sigma and only smooths based on
1043
the input <code>idx_coords_to_smooth</code>. If no <code>idx_coords_to_smooth</code> is provided, then all of the
1044
points are smoothed. <code>idx_coords_to_smooth</code> should be a list of indices of points to include. </p>
1045
<p>e.g., coords_to_smooth = np.where(vtk_to_numpy(mesh.GetPointData().GetScalars())&gt;0.01)[0]
1046
This would give only coordinates where the scalar values of the mesh are &gt;0.01. This example is
1047
useful for cartilage where we might only want to smooth in locations that we have cartilage and
1048
ignore the other areas. </p>
1049
<h2 id="parameters">Parameters</h2>
1050
<dl>
1051
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1052
<dd>This is a surface mesh of that we want to smooth the scalars of.</dd>
1053
<dt><strong><code>sigma</code></strong> :&ensp;<code>float</code>, optional</dt>
1054
<dd>The standard deviation of the gaussian filter to apply, by default 1.</dd>
1055
<dt><strong><code>idx_coords_to_smooth</code></strong> :&ensp;<code>list</code>, optional</dt>
1056
<dd>List of the indices of the vertices (points) that we want to include in the
1057
smoothing. For example, we can only smooth values that are cartialge and ignore
1058
all non-cartilage points, by default None</dd>
1059
<dt><strong><code>array_name</code></strong> :&ensp;<code>str</code>, optional</dt>
1060
<dd>Name of the scalar array that we want to smooth/filter, by default 'thickness (mm)'</dd>
1061
<dt><strong><code>array_idx</code></strong> :&ensp;<code>int</code>, optional</dt>
1062
<dd>The index of the scalar array that we want to smooth/filter - this is an alternative
1063
option to <code>array_name</code>, by default None</dd>
1064
</dl>
1065
<h2 id="returns">Returns</h2>
1066
<dl>
1067
<dt><code>vtk.vtkPolyData</code></dt>
1068
<dd>Return the original mesh for which the scalars have been smoothed. However, this is not
1069
necessary becuase if the original mesh still exists then it should have been updated
1070
during the course of the pipeline.</dd>
1071
</dl></div>
1072
<details class="source">
1073
<summary>
1074
<span>Expand source code</span>
1075
</summary>
1076
<pre><code class="python">def gaussian_smooth_surface_scalars(mesh, sigma=1., idx_coords_to_smooth=None, array_name=&#39;thickness (mm)&#39;, array_idx=None):
1077
    &#34;&#34;&#34;
1078
    The following is another function to smooth the scalar values on the surface of a mesh. 
1079
    This one performs a gaussian smoothing using the supplied sigma and only smooths based on 
1080
    the input `idx_coords_to_smooth`. If no `idx_coords_to_smooth` is provided, then all of the
1081
    points are smoothed. `idx_coords_to_smooth` should be a list of indices of points to include. 
1082
1083
    e.g., coords_to_smooth = np.where(vtk_to_numpy(mesh.GetPointData().GetScalars())&gt;0.01)[0]
1084
    This would give only coordinates where the scalar values of the mesh are &gt;0.01. This example is
1085
    useful for cartilage where we might only want to smooth in locations that we have cartilage and
1086
    ignore the other areas. 
1087
1088
    Parameters
1089
    ----------
1090
    mesh : vtk.vtkPolyData
1091
        This is a surface mesh of that we want to smooth the scalars of. 
1092
    sigma : float, optional
1093
        The standard deviation of the gaussian filter to apply, by default 1.
1094
    idx_coords_to_smooth : list, optional
1095
        List of the indices of the vertices (points) that we want to include in the
1096
        smoothing. For example, we can only smooth values that are cartialge and ignore
1097
        all non-cartilage points, by default None
1098
    array_name : str, optional
1099
        Name of the scalar array that we want to smooth/filter, by default &#39;thickness (mm)&#39;
1100
    array_idx : int, optional
1101
        The index of the scalar array that we want to smooth/filter - this is an alternative
1102
        option to `array_name`, by default None
1103
1104
    Returns
1105
    -------
1106
    vtk.vtkPolyData
1107
        Return the original mesh for which the scalars have been smoothed. However, this is not
1108
        necessary becuase if the original mesh still exists then it should have been updated
1109
        during the course of the pipeline. 
1110
    &#34;&#34;&#34;    
1111
1112
    point_coordinates = get_mesh_physical_point_coords(mesh)
1113
    if idx_coords_to_smooth is not None:
1114
        point_coordinates = point_coordinates[idx_coords_to_smooth, :]
1115
    kernel = gaussian_kernel(point_coordinates, point_coordinates, sigma=sigma)
1116
1117
    original_array = mesh.GetPointData().GetArray(array_idx if array_idx is not None else array_name)
1118
    original_scalars = np.copy(vtk_to_numpy(original_array))
1119
1120
    if idx_coords_to_smooth is not None:
1121
        smoothed_scalars = np.sum(kernel * original_scalars[idx_coords_to_smooth],
1122
                                    axis=1)
1123
        original_scalars[idx_coords_to_smooth] = smoothed_scalars
1124
        smoothed_scalars = original_scalars
1125
    else:
1126
        smoothed_scalars = np.sum(kernel * original_scalars, axis=1)
1127
1128
    smoothed_scalars = numpy_to_vtk(smoothed_scalars)
1129
    smoothed_scalars.SetName(original_array.GetName())
1130
    original_array.DeepCopy(smoothed_scalars) # Assign the scalars back to the original mesh
1131
1132
    # return the mesh object - however, if the original is not deleted, it should be smoothed
1133
    # appropriately. 
1134
    return mesh</code></pre>
1135
</details>
1136
</dd>
1137
<dt id="pymskt.mesh.meshTools.get_cartilage_properties_at_points"><code class="name flex">
1138
<span>def <span class="ident">get_cartilage_properties_at_points</span></span>(<span>surface_bone, surface_cartilage, t2_vtk_image=None, seg_vtk_image=None, ray_cast_length=20.0, percent_ray_length_opposite_direction=0.25, no_thickness_filler=0.0, no_t2_filler=0.0, no_seg_filler=0, line_resolution=100)</span>
1139
</code></dt>
1140
<dd>
1141
<div class="desc"><p>Extract cartilage outcomes (T2 &amp; thickness) at all points on bone surface. </p>
1142
<h2 id="parameters">Parameters</h2>
1143
<dl>
1144
<dt><strong><code>surface_bone</code></strong> :&ensp;<code>BoneMesh</code></dt>
1145
<dd>Bone mesh containing vtk.vtkPolyData - get outcomes for nodes (vertices) on
1146
this mesh</dd>
1147
<dt><strong><code>surface_cartilage</code></strong> :&ensp;<code>CartilageMesh</code></dt>
1148
<dd>Cartilage mesh containing vtk.vtkPolyData - for obtaining cartilage outcomes.</dd>
1149
<dt><strong><code>t2_vtk_image</code></strong> :&ensp;<code>vtk.vtkImageData</code>, optional</dt>
1150
<dd>vtk object that contains our Cartilage T2 data, by default None</dd>
1151
<dt><strong><code>seg_vtk_image</code></strong> :&ensp;<code>vtk.vtkImageData</code>, optional</dt>
1152
<dd>vtk object that contains the segmentation mask(s) to help assign
1153
labels to bone surface (e.g., most common), by default None</dd>
1154
<dt><strong><code>ray_cast_length</code></strong> :&ensp;<code>float</code>, optional</dt>
1155
<dd>Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1156
outter shell), by default 20.0</dd>
1157
<dt><strong><code>percent_ray_length_opposite_direction</code></strong> :&ensp;<code>float</code>, optional</dt>
1158
<dd>How far to project ray inside of the bone. This is done just in case the cartilage
1159
surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25</dd>
1160
<dt><strong><code>no_thickness_filler</code></strong> :&ensp;<code>float</code>, optional</dt>
1161
<dd>Value to use instead of thickness (if no cartilage), by default 0.</dd>
1162
<dt><strong><code>no_t2_filler</code></strong> :&ensp;<code>float</code>, optional</dt>
1163
<dd>Value to use instead of T2 (if no cartilage), by default 0.</dd>
1164
<dt><strong><code>no_seg_filler</code></strong> :&ensp;<code>int</code>, optional</dt>
1165
<dd>Value to use if no segmentation label available (because no cartilage?), by default 0</dd>
1166
<dt><strong><code>line_resolution</code></strong> :&ensp;<code>int</code>, optional</dt>
1167
<dd>Number of points to have along line, by default 100</dd>
1168
</dl>
1169
<h2 id="returns">Returns</h2>
1170
<dl>
1171
<dt><code>list</code></dt>
1172
<dd>Will return list of data for:
1173
Cartilage thickness
1174
Mean T2 at each point on bone
1175
Most common cartilage label at each point on bone (normal to surface).</dd>
1176
</dl></div>
1177
<details class="source">
1178
<summary>
1179
<span>Expand source code</span>
1180
</summary>
1181
<pre><code class="python">def get_cartilage_properties_at_points(surface_bone,
1182
                                       surface_cartilage,
1183
                                       t2_vtk_image=None,
1184
                                       seg_vtk_image=None,
1185
                                       ray_cast_length=20.,
1186
                                       percent_ray_length_opposite_direction=0.25,
1187
                                       no_thickness_filler=0.,
1188
                                       no_t2_filler=0.,
1189
                                       no_seg_filler=0,
1190
                                       line_resolution=100):  # Could be nan??
1191
    &#34;&#34;&#34;
1192
    Extract cartilage outcomes (T2 &amp; thickness) at all points on bone surface. 
1193
1194
    Parameters
1195
    ----------
1196
    surface_bone : BoneMesh
1197
        Bone mesh containing vtk.vtkPolyData - get outcomes for nodes (vertices) on
1198
        this mesh
1199
    surface_cartilage : CartilageMesh
1200
        Cartilage mesh containing vtk.vtkPolyData - for obtaining cartilage outcomes.
1201
    t2_vtk_image : vtk.vtkImageData, optional
1202
        vtk object that contains our Cartilage T2 data, by default None
1203
    seg_vtk_image : vtk.vtkImageData, optional
1204
        vtk object that contains the segmentation mask(s) to help assign
1205
        labels to bone surface (e.g., most common), by default None
1206
    ray_cast_length : float, optional
1207
        Length (mm) of ray to cast from bone surface when trying to find cartilage (inner &amp;
1208
        outter shell), by default 20.0
1209
    percent_ray_length_opposite_direction : float, optional
1210
        How far to project ray inside of the bone. This is done just in case the cartilage
1211
        surface ends up slightly inside of (or coincident with) the bone surface, by default 0.25
1212
    no_thickness_filler : float, optional
1213
        Value to use instead of thickness (if no cartilage), by default 0.
1214
    no_t2_filler : float, optional
1215
        Value to use instead of T2 (if no cartilage), by default 0.
1216
    no_seg_filler : int, optional
1217
        Value to use if no segmentation label available (because no cartilage?), by default 0
1218
    line_resolution : int, optional
1219
        Number of points to have along line, by default 100
1220
1221
    Returns
1222
    -------
1223
    list
1224
        Will return list of data for:
1225
            Cartilage thickness
1226
            Mean T2 at each point on bone
1227
            Most common cartilage label at each point on bone (normal to surface).
1228
    &#34;&#34;&#34;    
1229
1230
    normals = get_surface_normals(surface_bone)
1231
    points = surface_bone.GetPoints()
1232
    obb_cartilage = get_obb_surface(surface_cartilage)
1233
    point_normals = normals.GetOutput().GetPointData().GetNormals()
1234
1235
    thickness_data = []
1236
    if (t2_vtk_image is not None) or (seg_vtk_image is not None):
1237
        # if T2 data, or a segmentation image is provided, then setup Probe tool to
1238
        # get T2 values and/or cartilage ROI for each bone vertex. 
1239
        line = vtk.vtkLineSource()
1240
        line.SetResolution(line_resolution)
1241
1242
        if t2_vtk_image is not None:
1243
            t2_data_probe = ProbeVtkImageDataAlongLine(line_resolution,
1244
                                                  t2_vtk_image,
1245
                                                  save_mean=True,
1246
                                                  filler=no_t2_filler)
1247
        if seg_vtk_image is not None:
1248
            seg_data_probe = ProbeVtkImageDataAlongLine(line_resolution,
1249
                                                   seg_vtk_image,
1250
                                                   save_most_common=True,
1251
                                                   filler=no_seg_filler,
1252
                                                   data_categorical=True)
1253
    # Loop through all points
1254
    for idx in range(points.GetNumberOfPoints()):
1255
        point = points.GetPoint(idx)
1256
        normal = point_normals.GetTuple(idx)
1257
1258
        end_point_ray = n2l(l2n(point) + ray_cast_length*l2n(normal))
1259
        start_point_ray = n2l(l2n(point) + ray_cast_length*percent_ray_length_opposite_direction*(-l2n(normal)))
1260
1261
        # Check if there are any intersections for the given ray
1262
        if is_hit(obb_cartilage, start_point_ray, end_point_ray):  # intersections were found
1263
            # Retrieve coordinates of intersection points and intersected cell ids
1264
            points_intersect, cell_ids_intersect = get_intersect(obb_cartilage, start_point_ray, end_point_ray)
1265
    #         points
1266
            if len(points_intersect) == 2:
1267
                thickness_data.append(np.sqrt(np.sum(np.square(l2n(points_intersect[0]) - l2n(points_intersect[1])))))
1268
                if t2_vtk_image is not None:
1269
                    t2_data_probe.save_data_along_line(start_pt=points_intersect[0],
1270
                                                       end_pt=points_intersect[1])
1271
                if seg_vtk_image is not None:
1272
                    seg_data_probe.save_data_along_line(start_pt=points_intersect[0],
1273
                                                        end_pt=points_intersect[1])
1274
1275
            else:
1276
                thickness_data.append(no_thickness_filler)
1277
                if t2_vtk_image is not None:
1278
                    t2_data_probe.append_filler()
1279
                if seg_vtk_image is not None:
1280
                    seg_data_probe.append_filler()
1281
        else:
1282
            thickness_data.append(no_thickness_filler)
1283
            if t2_vtk_image is not None:
1284
                t2_data_probe.append_filler()
1285
            if seg_vtk_image is not None:
1286
                seg_data_probe.append_filler()
1287
1288
    if (t2_vtk_image is None) &amp; (seg_vtk_image is None):
1289
        return np.asarray(thickness_data, dtype=np.float)
1290
    elif (t2_vtk_image is not None) &amp; (seg_vtk_image is not None):
1291
        return (np.asarray(thickness_data, dtype=np.float),
1292
                np.asarray(t2_data_probe.mean_data, dtype=np.float),
1293
                np.asarray(seg_data_probe.most_common_data, dtype=np.int)
1294
                )
1295
    elif t2_vtk_image is not None:
1296
        return (np.asarray(thickness_data, dtype=np.float),
1297
                np.asarray(t2_data_probe.mean_data, dtype=np.float)
1298
                )
1299
    elif seg_vtk_image is not None:
1300
        return (np.asarray(thickness_data, dtype=np.float),
1301
                np.asarray(seg_data_probe.most_common_data, dtype=np.int)
1302
                )</code></pre>
1303
</details>
1304
</dd>
1305
<dt id="pymskt.mesh.meshTools.get_mesh_physical_point_coords"><code class="name flex">
1306
<span>def <span class="ident">get_mesh_physical_point_coords</span></span>(<span>mesh)</span>
1307
</code></dt>
1308
<dd>
1309
<div class="desc"><p>Get a numpy array of the x/y/z location of each point (vertex) on the <code>mesh</code>.</p>
1310
<h2 id="parameters">Parameters</h2>
1311
<dl>
1312
<dt><strong><code>mesh</code></strong></dt>
1313
<dd>[description]</dd>
1314
</dl>
1315
<h2 id="returns">Returns</h2>
1316
<dl>
1317
<dt><code>numpy.ndarray</code></dt>
1318
<dd>n_points x 3 array describing the x/y/z position of each point.</dd>
1319
</dl>
1320
<h2 id="notes">Notes</h2>
1321
<p>Below is the original method used to retrieve the point coordinates. </p>
1322
<p>point_coordinates = np.zeros((mesh.GetNumberOfPoints(), 3))
1323
for pt_idx in range(mesh.GetNumberOfPoints()):
1324
point_coordinates[pt_idx, :] = mesh.GetPoint(pt_idx)</p></div>
1325
<details class="source">
1326
<summary>
1327
<span>Expand source code</span>
1328
</summary>
1329
<pre><code class="python">def get_mesh_physical_point_coords(mesh):
1330
    &#34;&#34;&#34;
1331
    Get a numpy array of the x/y/z location of each point (vertex) on the `mesh`.
1332
1333
    Parameters
1334
    ----------
1335
    mesh : 
1336
        [description]
1337
1338
    Returns
1339
    -------
1340
    numpy.ndarray
1341
        n_points x 3 array describing the x/y/z position of each point. 
1342
    
1343
    Notes
1344
    -----
1345
    Below is the original method used to retrieve the point coordinates. 
1346
    
1347
    point_coordinates = np.zeros((mesh.GetNumberOfPoints(), 3))
1348
    for pt_idx in range(mesh.GetNumberOfPoints()):
1349
        point_coordinates[pt_idx, :] = mesh.GetPoint(pt_idx)
1350
    &#34;&#34;&#34;    
1351
    
1352
    point_coordinates = vtk_to_numpy(mesh.GetPoints().GetData())
1353
    return point_coordinates</code></pre>
1354
</details>
1355
</dd>
1356
<dt id="pymskt.mesh.meshTools.get_smoothed_scalars"><code class="name flex">
1357
<span>def <span class="ident">get_smoothed_scalars</span></span>(<span>mesh, max_dist=2.0, order=2, gaussian=False)</span>
1358
</code></dt>
1359
<dd>
1360
<div class="desc"><p>perform smoothing of scalars on the nodes of a surface mesh.
1361
return the smoothed values of the nodes so they can be used as necessary.
1362
(e.g. to replace originals or something else)
1363
Smoothing is done for all data within <code>max_dist</code> and uses a simple weighted average based on
1364
the distance to the power of <code>order</code>. Default is squared distance (<code>order=2</code>)</p>
1365
<h2 id="parameters">Parameters</h2>
1366
<dl>
1367
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1368
<dd>Surface mesh that we want to smooth scalars of.</dd>
1369
<dt><strong><code>max_dist</code></strong> :&ensp;<code>float</code>, optional</dt>
1370
<dd>Maximum distance of nodes that we want to smooth (mm), by default 2.0</dd>
1371
<dt><strong><code>order</code></strong> :&ensp;<code>int</code>, optional</dt>
1372
<dd>Order of the polynomial used for weighting other nodes within <code>max_dist</code>, by default 2</dd>
1373
<dt><strong><code>gaussian</code></strong> :&ensp;<code>bool</code>, optional</dt>
1374
<dd>Should this use a gaussian smoothing, or weighted average, by default False</dd>
1375
</dl>
1376
<h2 id="returns">Returns</h2>
1377
<dl>
1378
<dt><code>numpy.ndarray</code></dt>
1379
<dd>An array of the scalar values for each node on the <code>mesh</code> after they have been smoothed.</dd>
1380
</dl></div>
1381
<details class="source">
1382
<summary>
1383
<span>Expand source code</span>
1384
</summary>
1385
<pre><code class="python">def get_smoothed_scalars(mesh, max_dist=2.0, order=2, gaussian=False):
1386
    &#34;&#34;&#34;
1387
    perform smoothing of scalars on the nodes of a surface mesh. 
1388
    return the smoothed values of the nodes so they can be used as necessary. 
1389
    (e.g. to replace originals or something else)
1390
    Smoothing is done for all data within `max_dist` and uses a simple weighted average based on
1391
    the distance to the power of `order`. Default is squared distance (`order=2`)
1392
1393
    Parameters
1394
    ----------
1395
    mesh : vtk.vtkPolyData
1396
        Surface mesh that we want to smooth scalars of. 
1397
    max_dist : float, optional
1398
        Maximum distance of nodes that we want to smooth (mm), by default 2.0
1399
    order : int, optional
1400
        Order of the polynomial used for weighting other nodes within `max_dist`, by default 2
1401
    gaussian : bool, optional
1402
        Should this use a gaussian smoothing, or weighted average, by default False
1403
1404
    Returns
1405
    -------
1406
    numpy.ndarray
1407
        An array of the scalar values for each node on the `mesh` after they have been smoothed. 
1408
    &#34;&#34;&#34;    
1409
1410
    kDTree = vtk.vtkKdTreePointLocator()
1411
    kDTree.SetDataSet(mesh)
1412
    kDTree.BuildLocator()
1413
1414
    thickness_smoothed = np.zeros(mesh.GetNumberOfPoints())
1415
    scalars = l2n(mesh.GetPointData().GetScalars())
1416
    for idx in range(mesh.GetNumberOfPoints()):
1417
        if scalars[idx] &gt;0:  # don&#39;t smooth nodes with thickness == 0 (or negative? if that were to happen)
1418
            point = mesh.GetPoint(idx)
1419
            closest_ids = vtk.vtkIdList()
1420
            kDTree.FindPointsWithinRadius(max_dist, point, closest_ids) # This will return a value ( 0 or 1). Can use that for debudding.
1421
1422
            list_scalars = []
1423
            list_distances = []
1424
            for closest_pt_idx in range(closest_ids.GetNumberOfIds()):
1425
                pt_idx = closest_ids.GetId(closest_pt_idx)
1426
                _point = mesh.GetPoint(pt_idx)
1427
                list_scalars.append(scalars[pt_idx])
1428
                list_distances.append(np.sqrt(np.sum(np.square(np.asarray(point) - np.asarray(_point) + epsilon))))
1429
1430
            distances_weighted = (max_dist - np.asarray(list_distances))**order
1431
            scalars_weights = distances_weighted * np.asarray(list_scalars)
1432
            normalized_value = np.sum(scalars_weights) / np.sum(distances_weighted)
1433
            thickness_smoothed[idx] = normalized_value
1434
    return thickness_smoothed</code></pre>
1435
</details>
1436
</dd>
1437
<dt id="pymskt.mesh.meshTools.resample_surface"><code class="name flex">
1438
<span>def <span class="ident">resample_surface</span></span>(<span>mesh, subdivisions=2, clusters=10000)</span>
1439
</code></dt>
1440
<dd>
1441
<div class="desc"><p>Resample a surface mesh using the ACVD algorithm:
1442
Version used:
1443
- <a href="https://github.com/pyvista/pyacvd">https://github.com/pyvista/pyacvd</a>
1444
Original version w/ more references:
1445
- <a href="https://github.com/valette/ACVD">https://github.com/valette/ACVD</a></p>
1446
<h2 id="parameters">Parameters</h2>
1447
<dl>
1448
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1449
<dd>Polydata mesh to be re-sampled.</dd>
1450
<dt><strong><code>subdivisions</code></strong> :&ensp;<code>int</code>, optional</dt>
1451
<dd>Subdivide the mesh to have more points before clustering, by default 2
1452
Probably not necessary for very dense meshes.</dd>
1453
<dt><strong><code>clusters</code></strong> :&ensp;<code>int</code>, optional</dt>
1454
<dd>
1455
<p>The number of clusters (points/vertices) to create during resampling
1456
surafce, by default 10000
1457
- This is not exact, might have slight differences.</p>
1458
<h2 id="returns">Returns</h2>
1459
<p>vtk.vtkPolyData :
1460
Return the resampled mesh. This will be a pyvista version of the vtk mesh
1461
but this is usable in all vtk function so it is not an issue.</p>
1462
</dd>
1463
</dl></div>
1464
<details class="source">
1465
<summary>
1466
<span>Expand source code</span>
1467
</summary>
1468
<pre><code class="python">def resample_surface(mesh, subdivisions=2, clusters=10000):
1469
    &#34;&#34;&#34;
1470
    Resample a surface mesh using the ACVD algorithm: 
1471
    Version used: 
1472
    - https://github.com/pyvista/pyacvd
1473
    Original version w/ more references: 
1474
    - https://github.com/valette/ACVD
1475
1476
    Parameters
1477
    ----------
1478
    mesh : vtk.vtkPolyData
1479
        Polydata mesh to be re-sampled. 
1480
    subdivisions : int, optional
1481
        Subdivide the mesh to have more points before clustering, by default 2
1482
        Probably not necessary for very dense meshes.
1483
    clusters : int, optional
1484
        The number of clusters (points/vertices) to create during resampling 
1485
        surafce, by default 10000
1486
        - This is not exact, might have slight differences.
1487
    
1488
        Returns
1489
    -------
1490
    vtk.vtkPolyData :
1491
        Return the resampled mesh. This will be a pyvista version of the vtk mesh
1492
        but this is usable in all vtk function so it is not an issue. 
1493
        
1494
1495
    &#34;&#34;&#34;        
1496
    pv_smooth_mesh = pv.wrap(mesh)
1497
    clus = pyacvd.Clustering(pv_smooth_mesh)
1498
    clus.subdivide(subdivisions)
1499
    clus.cluster(clusters)
1500
    mesh = clus.create_mesh()
1501
1502
    return mesh</code></pre>
1503
</details>
1504
</dd>
1505
<dt id="pymskt.mesh.meshTools.set_mesh_physical_point_coords"><code class="name flex">
1506
<span>def <span class="ident">set_mesh_physical_point_coords</span></span>(<span>mesh, new_points)</span>
1507
</code></dt>
1508
<dd>
1509
<div class="desc"><p>Convenience function to update the x/y/z point coords of a mesh</p>
1510
<p>Nothing is returned becuase the mesh object is updated in-place. </p>
1511
<h2 id="parameters">Parameters</h2>
1512
<dl>
1513
<dt><strong><code>mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1514
<dd>Mesh object we want to update the point coordinates for</dd>
1515
<dt><strong><code>new_points</code></strong> :&ensp;<code>np.ndarray</code></dt>
1516
<dd>Numpy array shaped n_points x 3. These are the new point coordinates that
1517
we want to update the mesh to have.</dd>
1518
</dl></div>
1519
<details class="source">
1520
<summary>
1521
<span>Expand source code</span>
1522
</summary>
1523
<pre><code class="python">def set_mesh_physical_point_coords(mesh, new_points):
1524
    &#34;&#34;&#34;
1525
    Convenience function to update the x/y/z point coords of a mesh
1526
1527
    Nothing is returned becuase the mesh object is updated in-place. 
1528
1529
    Parameters
1530
    ----------
1531
    mesh : vtk.vtkPolyData
1532
        Mesh object we want to update the point coordinates for
1533
    new_points : np.ndarray
1534
        Numpy array shaped n_points x 3. These are the new point coordinates that
1535
        we want to update the mesh to have. 
1536
1537
    &#34;&#34;&#34;
1538
    orig_point_coords = get_mesh_physical_point_coords(mesh)
1539
    if new_points.shape == orig_point_coords.shape:
1540
        mesh.GetPoints().SetData(numpy_to_vtk(new_points))</code></pre>
1541
</details>
1542
</dd>
1543
<dt id="pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base"><code class="name flex">
1544
<span>def <span class="ident">smooth_scalars_from_second_mesh_onto_base</span></span>(<span>base_mesh, second_mesh, sigma=1.0, idx_coords_to_smooth_base=None, idx_coords_to_smooth_second=None, set_non_smoothed_scalars_to_zero=True)</span>
1545
</code></dt>
1546
<dd>
1547
<div class="desc"><p>Function to copy surface scalars from one mesh to another. This is done in a "smoothing" fashioon
1548
to get a weighted-average of the closest point - this is because the points on the 2 meshes won't
1549
be coincident with one another. The weighted average is done using a gaussian smoothing.</p>
1550
<h2 id="parameters">Parameters</h2>
1551
<dl>
1552
<dt><strong><code>base_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1553
<dd>The base mesh to smooth the scalars from <code>second_mesh</code> onto.</dd>
1554
<dt><strong><code>second_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1555
<dd>The mesh with the scalar values that we want to pass onto the <code>base_mesh</code>.</dd>
1556
<dt><strong><code>sigma</code></strong> :&ensp;<code>float</code>, optional</dt>
1557
<dd>Sigma (standard deviation) of gaussian filter to apply to scalars, by default 1.</dd>
1558
<dt><strong><code>idx_coords_to_smooth_base</code></strong> :&ensp;<code>list</code>, optional</dt>
1559
<dd>List of the indices of nodes that are of interest for transferring (typically cartilage),
1560
by default None</dd>
1561
<dt><strong><code>idx_coords_to_smooth_second</code></strong> :&ensp;<code>list</code>, optional</dt>
1562
<dd>List of the indices of the nodes that are of interest on the second mesh, by default None</dd>
1563
<dt><strong><code>set_non_smoothed_scalars_to_zero</code></strong> :&ensp;<code>bool</code>, optional</dt>
1564
<dd>Whether or not to set all notes that are not smoothed to zero, by default True</dd>
1565
</dl>
1566
<h2 id="returns">Returns</h2>
1567
<dl>
1568
<dt><code>numpy.ndarray</code></dt>
1569
<dd>An array of the scalar values for each node on the base mesh that includes the scalar values
1570
transfered (smoothed) from the secondary mesh.</dd>
1571
</dl></div>
1572
<details class="source">
1573
<summary>
1574
<span>Expand source code</span>
1575
</summary>
1576
<pre><code class="python">def smooth_scalars_from_second_mesh_onto_base(base_mesh,
1577
                                              second_mesh,
1578
                                              sigma=1.,
1579
                                              idx_coords_to_smooth_base=None,
1580
                                              idx_coords_to_smooth_second=None,
1581
                                              set_non_smoothed_scalars_to_zero=True
1582
                                              ):  # sigma is equal to fwhm=2 (1mm in each direction)
1583
    &#34;&#34;&#34;
1584
    Function to copy surface scalars from one mesh to another. This is done in a &#34;smoothing&#34; fashioon
1585
    to get a weighted-average of the closest point - this is because the points on the 2 meshes won&#39;t
1586
    be coincident with one another. The weighted average is done using a gaussian smoothing.
1587
1588
    Parameters
1589
    ----------
1590
    base_mesh : vtk.vtkPolyData
1591
        The base mesh to smooth the scalars from `second_mesh` onto. 
1592
    second_mesh : vtk.vtkPolyData
1593
        The mesh with the scalar values that we want to pass onto the `base_mesh`.
1594
    sigma : float, optional
1595
        Sigma (standard deviation) of gaussian filter to apply to scalars, by default 1.
1596
    idx_coords_to_smooth_base : list, optional
1597
        List of the indices of nodes that are of interest for transferring (typically cartilage), 
1598
        by default None
1599
    idx_coords_to_smooth_second : list, optional
1600
        List of the indices of the nodes that are of interest on the second mesh, by default None
1601
    set_non_smoothed_scalars_to_zero : bool, optional
1602
        Whether or not to set all notes that are not smoothed to zero, by default True
1603
1604
    Returns
1605
    -------
1606
    numpy.ndarray
1607
        An array of the scalar values for each node on the base mesh that includes the scalar values
1608
        transfered (smoothed) from the secondary mesh. 
1609
    &#34;&#34;&#34;    
1610
    base_mesh_pts = get_mesh_physical_point_coords(base_mesh)
1611
    if idx_coords_to_smooth_base is not None:
1612
        base_mesh_pts = base_mesh_pts[idx_coords_to_smooth_base, :]
1613
    second_mesh_pts = get_mesh_physical_point_coords(second_mesh)
1614
    if idx_coords_to_smooth_second is not None:
1615
        second_mesh_pts = second_mesh_pts[idx_coords_to_smooth_second, :]
1616
    gauss_kernel = gaussian_kernel(base_mesh_pts, second_mesh_pts, sigma=sigma)
1617
    second_mesh_scalars = np.copy(vtk_to_numpy(second_mesh.GetPointData().GetScalars()))
1618
    if idx_coords_to_smooth_second is not None:
1619
        # If sub-sampled second mesh - then only give the scalars from those sub-sampled points on mesh.
1620
        second_mesh_scalars = second_mesh_scalars[idx_coords_to_smooth_second]
1621
1622
    smoothed_scalars_on_base = np.sum(gauss_kernel * second_mesh_scalars, axis=1)
1623
1624
    if idx_coords_to_smooth_base is not None:
1625
        # if sub-sampled baseline mesh (only want to set cartilage to certain points/vertices), then
1626
        # set the calculated smoothed scalars to only those nodes (and leave all other nodes the same as they were
1627
        # originally.
1628
        if set_non_smoothed_scalars_to_zero is True:
1629
            base_mesh_scalars = np.zeros(base_mesh.GetNumberOfPoints())
1630
        else:
1631
            base_mesh_scalars = np.copy(vtk_to_numpy(base_mesh.GetPointData().GetScalars()))
1632
        base_mesh_scalars[idx_coords_to_smooth_base] = smoothed_scalars_on_base
1633
        return base_mesh_scalars
1634
1635
    else:
1636
        return smoothed_scalars_on_base</code></pre>
1637
</details>
1638
</dd>
1639
<dt id="pymskt.mesh.meshTools.transfer_mesh_scalars_get_weighted_average_n_closest"><code class="name flex">
1640
<span>def <span class="ident">transfer_mesh_scalars_get_weighted_average_n_closest</span></span>(<span>new_mesh, old_mesh, n=3)</span>
1641
</code></dt>
1642
<dd>
1643
<div class="desc"><p>Transfer scalars from old_mesh to new_mesh using the weighted-average of the <code>n</code> closest
1644
nodes/points/vertices. Similar but not exactly the same as <code><a title="pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base" href="#pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base">smooth_scalars_from_second_mesh_onto_base()</a></code></p>
1645
<p>This function is ideally used for things like transferring cartilage thickness values from one mesh to another
1646
after they have been registered together. This is necessary for things like performing statistical analyses or
1647
getting aggregate statistics. </p>
1648
<h2 id="parameters">Parameters</h2>
1649
<dl>
1650
<dt><strong><code>new_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1651
<dd>The new mesh that we want to transfer scalar values onto. Also <code>base_mesh</code> from
1652
<code><a title="pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base" href="#pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base">smooth_scalars_from_second_mesh_onto_base()</a></code></dd>
1653
<dt><strong><code>old_mesh</code></strong> :&ensp;<code>vtk.vtkPolyData</code></dt>
1654
<dd>The mesh that we want to transfer scalars from. Also called <code>second_mesh</code> from
1655
<code><a title="pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base" href="#pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base">smooth_scalars_from_second_mesh_onto_base()</a></code></dd>
1656
<dt><strong><code>n</code></strong> :&ensp;<code>int</code>, optional</dt>
1657
<dd>The number of closest nodes that we want to get weighed average of, by default 3</dd>
1658
</dl>
1659
<h2 id="returns">Returns</h2>
1660
<dl>
1661
<dt><code>numpy.ndarray</code></dt>
1662
<dd>An array of the scalar values for each node on the <code>new_mesh</code> that includes the scalar values
1663
transfered (smoothed) from the <code>old_mesh</code>.</dd>
1664
</dl></div>
1665
<details class="source">
1666
<summary>
1667
<span>Expand source code</span>
1668
</summary>
1669
<pre><code class="python">def transfer_mesh_scalars_get_weighted_average_n_closest(new_mesh, old_mesh, n=3):
1670
    &#34;&#34;&#34;
1671
    Transfer scalars from old_mesh to new_mesh using the weighted-average of the `n` closest
1672
    nodes/points/vertices. Similar but not exactly the same as `smooth_scalars_from_second_mesh_onto_base`
1673
    
1674
    This function is ideally used for things like transferring cartilage thickness values from one mesh to another 
1675
    after they have been registered together. This is necessary for things like performing statistical analyses or
1676
    getting aggregate statistics. 
1677
1678
    Parameters
1679
    ----------
1680
    new_mesh : vtk.vtkPolyData
1681
        The new mesh that we want to transfer scalar values onto. Also `base_mesh` from
1682
        `smooth_scalars_from_second_mesh_onto_base` 
1683
    old_mesh : vtk.vtkPolyData
1684
        The mesh that we want to transfer scalars from. Also called `second_mesh` from 
1685
        `smooth_scalars_from_second_mesh_onto_base`
1686
    n : int, optional
1687
        The number of closest nodes that we want to get weighed average of, by default 3
1688
1689
    Returns
1690
    -------
1691
    numpy.ndarray
1692
        An array of the scalar values for each node on the `new_mesh` that includes the scalar values
1693
        transfered (smoothed) from the `old_mesh`. 
1694
    &#34;&#34;&#34;    
1695
1696
    kDTree = vtk.vtkKdTreePointLocator()
1697
    kDTree.SetDataSet(old_mesh)
1698
    kDTree.BuildLocator()
1699
1700
    n_arrays = old_mesh.GetPointData().GetNumberOfArrays()
1701
    array_names = [old_mesh.GetPointData().GetArray(array_idx).GetName() for array_idx in range(n_arrays)]
1702
    new_scalars = np.zeros((new_mesh.GetNumberOfPoints(), n_arrays))
1703
    scalars_old_mesh = [np.copy(vtk_to_numpy(old_mesh.GetPointData().GetArray(array_name))) for array_name in array_names]
1704
    # print(&#39;len scalars_old_mesh&#39;, len(scalars_old_mesh))
1705
    # scalars_old_mesh = np.copy(vtk_to_numpy(old_mesh.GetPointData().GetScalars()))
1706
    for new_mesh_pt_idx in range(new_mesh.GetNumberOfPoints()):
1707
        point = new_mesh.GetPoint(new_mesh_pt_idx)
1708
        closest_ids = vtk.vtkIdList()
1709
        kDTree.FindClosestNPoints(n, point, closest_ids)
1710
1711
        list_scalars = []
1712
        distance_weighting = []
1713
        for closest_pts_idx in range(closest_ids.GetNumberOfIds()):
1714
            pt_idx = closest_ids.GetId(closest_pts_idx)
1715
            _point = old_mesh.GetPoint(pt_idx)
1716
            list_scalars.append([scalars[pt_idx] for scalars in scalars_old_mesh])
1717
            distance_weighting.append(1 / np.sqrt(np.sum(np.square(np.asarray(point) - np.asarray(_point) + epsilon))))
1718
    
1719
        total_distance = np.sum(distance_weighting)
1720
        # print(&#39;list_scalars&#39;, list_scalars)
1721
        # print(&#39;distance_weighting&#39;, distance_weighting)
1722
        # print(&#39;total_distance&#39;, total_distance)
1723
        normalized_value = np.sum(np.asarray(list_scalars) * np.expand_dims(np.asarray(distance_weighting), axis=1),
1724
                                  axis=0) / total_distance
1725
        # print(&#39;new_mesh_pt_idx&#39;, new_mesh_pt_idx)
1726
        # print(&#39;normalized_value&#39;, normalized_value)
1727
        # print(&#39;new_scalars shape&#39;, new_scalars.shape)
1728
        new_scalars[new_mesh_pt_idx, :] = normalized_value
1729
    return new_scalars</code></pre>
1730
</details>
1731
</dd>
1732
</dl>
1733
</section>
1734
<section>
1735
<h2 class="section-title" id="header-classes">Classes</h2>
1736
<dl>
1737
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine"><code class="flex name class">
1738
<span>class <span class="ident">ProbeVtkImageDataAlongLine</span></span>
1739
<span>(</span><span>line_resolution, vtk_image, save_data_in_class=True, save_mean=False, save_std=False, save_most_common=False, save_max=False, filler=0, non_zero_only=True, data_categorical=False)</span>
1740
</code></dt>
1741
<dd>
1742
<div class="desc"><p>Class to find values along a line. This is used to get things like the mean T2 value normal
1743
to a bones surface &amp; within the cartialge region. This is done by defining a line in a
1744
particualar location. </p>
1745
<h2 id="parameters">Parameters</h2>
1746
<dl>
1747
<dt><strong><code>line_resolution</code></strong> :&ensp;<code>float</code></dt>
1748
<dd>How many points to create along the line.</dd>
1749
<dt><strong><code>vtk_image</code></strong> :&ensp;<code>vtk.vtkImageData</code></dt>
1750
<dd>Image read into vtk so that we can apply the probe to it.</dd>
1751
<dt><strong><code>save_data_in_class</code></strong> :&ensp;<code>bool</code>, optional</dt>
1752
<dd>Whether or not to save data along the line(s) to the class, by default True</dd>
1753
<dt><strong><code>save_mean</code></strong> :&ensp;<code>bool</code>, optional</dt>
1754
<dd>Whether the mean value should be saved along the line, by default False</dd>
1755
<dt><strong><code>save_std</code></strong> :&ensp;<code>bool</code>, optional</dt>
1756
<dd>Whether the standard deviation of the data along the line should be
1757
saved, by default False</dd>
1758
<dt><strong><code>save_most_common</code></strong> :&ensp;<code>bool</code>, optional</dt>
1759
<dd>Whether the mode (most common) value should be saved used for identifying cartilage
1760
regions on the bone surface, by default False</dd>
1761
<dt><strong><code>filler</code></strong> :&ensp;<code>int</code>, optional</dt>
1762
<dd>What value should be placed at locations where we don't have a value
1763
(e.g., where we don't have T2 values), by default 0</dd>
1764
<dt><strong><code>non_zero_only</code></strong> :&ensp;<code>bool</code>, optional</dt>
1765
<dd>Only save non-zero values along the line, by default True
1766
This is done becuase zeros are normally regions of error (e.g.
1767
poor T2 relaxation fit) and thus would artifically reduce the outcome
1768
along the line.</dd>
1769
</dl>
1770
<h2 id="attributes">Attributes</h2>
1771
<dl>
1772
<dt><strong><code>save_mean</code></strong> :&ensp;<code>bool</code></dt>
1773
<dd>Whether the mean value should be saved along the line, by default False</dd>
1774
<dt><strong><code>save_std</code></strong> :&ensp;<code>bool</code></dt>
1775
<dd>Whether the standard deviation of the data along the line should be
1776
saved, by default False</dd>
1777
<dt><strong><code>save_most_common</code></strong> :&ensp;<code>bool </code></dt>
1778
<dd>Whether the mode (most common) value should be saved used for identifying cartilage
1779
regions on the bone surface, by default False</dd>
1780
<dt><strong><code>filler</code></strong> :&ensp;<code>float</code></dt>
1781
<dd>What value should be placed at locations where we don't have a value
1782
(e.g., where we don't have T2 values), by default 0</dd>
1783
<dt><strong><code>non_zero_only</code></strong> :&ensp;<code>bool </code></dt>
1784
<dd>Only save non-zero values along the line, by default True
1785
This is done becuase zeros are normally regions of error (e.g.
1786
poor T2 relaxation fit) and thus would artifically reduce the outcome
1787
along the line.</dd>
1788
<dt><strong><code>line</code></strong> :&ensp;<code>vtk.vtkLineSource</code></dt>
1789
<dd>Line to put into <code>probe_filter</code> and to determine mean/std/common values for.</dd>
1790
<dt><strong><code>probe_filter</code></strong> :&ensp;<code>vtk.vtkProbeFilter</code></dt>
1791
<dd>Filter to use to get the image data along the line.</dd>
1792
<dt><strong><code>_mean_data</code></strong> :&ensp;<code>list</code></dt>
1793
<dd>List of the mean values for each vertex / line projected</dd>
1794
<dt><strong><code>_std_data</code></strong> :&ensp;<code>list</code></dt>
1795
<dd>List of standard deviation of each vertex / line projected</dd>
1796
<dt><strong><code>_most_common_data</code></strong> :&ensp;<code>list</code></dt>
1797
<dd>List of most common data of each vertex / line projected</dd>
1798
</dl>
1799
<h2 id="methods">Methods</h2>
1800
<p>[summary]</p>
1801
<h2 id="parameters_1">Parameters</h2>
1802
<dl>
1803
<dt><strong><code>line_resolution</code></strong> :&ensp;<code>float</code></dt>
1804
<dd>How many points to create along the line.</dd>
1805
<dt><strong><code>vtk_image</code></strong> :&ensp;<code>vtk.vtkImageData</code></dt>
1806
<dd>Image read into vtk so that we can apply the probe to it.</dd>
1807
<dt><strong><code>save_data_in_class</code></strong> :&ensp;<code>bool</code>, optional</dt>
1808
<dd>Whether or not to save data along the line(s) to the class, by default True</dd>
1809
<dt><strong><code>save_mean</code></strong> :&ensp;<code>bool</code>, optional</dt>
1810
<dd>Whether the mean value should be saved along the line, by default False</dd>
1811
<dt><strong><code>save_std</code></strong> :&ensp;<code>bool</code>, optional</dt>
1812
<dd>Whether the standard deviation of the data along the line should be
1813
saved, by default False</dd>
1814
<dt><strong><code>save_most_common</code></strong> :&ensp;<code>bool</code>, optional</dt>
1815
<dd>Whether the mode (most common) value should be saved used for identifying cartilage
1816
regions on the bone surface, by default False</dd>
1817
<dt><strong><code>save_max</code></strong> :&ensp;<code>bool</code>, optional</dt>
1818
<dd>Whether the max value should be saved along the line, be default False</dd>
1819
<dt><strong><code>filler</code></strong> :&ensp;<code>int</code>, optional</dt>
1820
<dd>What value should be placed at locations where we don't have a value
1821
(e.g., where we don't have T2 values), by default 0</dd>
1822
<dt><strong><code>non_zero_only</code></strong> :&ensp;<code>bool</code>, optional</dt>
1823
<dd>Only save non-zero values along the line, by default True
1824
This is done becuase zeros are normally regions of error (e.g.
1825
poor T2 relaxation fit) and thus would artifically reduce the outcome
1826
along the line.</dd>
1827
<dt><strong><code>data_categorical</code></strong> :&ensp;<code>bool</code>, optional</dt>
1828
<dd>Specify whether or not the data is categorical to determine the interpolation
1829
method that should be used.</dd>
1830
</dl></div>
1831
<details class="source">
1832
<summary>
1833
<span>Expand source code</span>
1834
</summary>
1835
<pre><code class="python">class ProbeVtkImageDataAlongLine:
1836
    &#34;&#34;&#34;
1837
    Class to find values along a line. This is used to get things like the mean T2 value normal
1838
    to a bones surface &amp; within the cartialge region. This is done by defining a line in a
1839
    particualar location. 
1840
1841
    Parameters
1842
    ----------
1843
    line_resolution : float
1844
        How many points to create along the line. 
1845
    vtk_image : vtk.vtkImageData
1846
        Image read into vtk so that we can apply the probe to it. 
1847
    save_data_in_class : bool, optional
1848
        Whether or not to save data along the line(s) to the class, by default True
1849
    save_mean : bool, optional
1850
        Whether the mean value should be saved along the line, by default False
1851
    save_std : bool, optional
1852
        Whether the standard deviation of the data along the line should be
1853
        saved, by default False
1854
    save_most_common : bool, optional
1855
        Whether the mode (most common) value should be saved used for identifying cartilage
1856
        regions on the bone surface, by default False
1857
    filler : int, optional
1858
        What value should be placed at locations where we don&#39;t have a value
1859
        (e.g., where we don&#39;t have T2 values), by default 0
1860
    non_zero_only : bool, optional
1861
        Only save non-zero values along the line, by default True
1862
        This is done becuase zeros are normally regions of error (e.g.
1863
        poor T2 relaxation fit) and thus would artifically reduce the outcome
1864
        along the line. 
1865
    
1866
    
1867
    Attributes
1868
    ----------
1869
    save_mean : bool
1870
        Whether the mean value should be saved along the line, by default False
1871
    save_std : bool
1872
        Whether the standard deviation of the data along the line should be
1873
        saved, by default False
1874
    save_most_common : bool 
1875
        Whether the mode (most common) value should be saved used for identifying cartilage
1876
        regions on the bone surface, by default False
1877
    filler : float
1878
        What value should be placed at locations where we don&#39;t have a value
1879
        (e.g., where we don&#39;t have T2 values), by default 0
1880
    non_zero_only : bool 
1881
        Only save non-zero values along the line, by default True
1882
        This is done becuase zeros are normally regions of error (e.g.
1883
        poor T2 relaxation fit) and thus would artifically reduce the outcome
1884
        along the line. 
1885
    line : vtk.vtkLineSource
1886
        Line to put into `probe_filter` and to determine mean/std/common values for. 
1887
    probe_filter : vtk.vtkProbeFilter
1888
        Filter to use to get the image data along the line. 
1889
    _mean_data : list
1890
        List of the mean values for each vertex / line projected
1891
    _std_data : list
1892
        List of standard deviation of each vertex / line projected
1893
    _most_common_data : list
1894
        List of most common data of each vertex / line projected
1895
    
1896
    Methods
1897
    -------
1898
1899
1900
    &#34;&#34;&#34;    
1901
    def __init__(self,
1902
                 line_resolution,
1903
                 vtk_image,
1904
                 save_data_in_class=True,
1905
                 save_mean=False,
1906
                 save_std=False,
1907
                 save_most_common=False,
1908
                 save_max=False,
1909
                 filler=0,
1910
                 non_zero_only=True,
1911
                 data_categorical=False
1912
                 ):
1913
        &#34;&#34;&#34;[summary]
1914
1915
        Parameters
1916
        ----------
1917
        line_resolution : float
1918
            How many points to create along the line. 
1919
        vtk_image : vtk.vtkImageData
1920
            Image read into vtk so that we can apply the probe to it. 
1921
        save_data_in_class : bool, optional
1922
            Whether or not to save data along the line(s) to the class, by default True
1923
        save_mean : bool, optional
1924
            Whether the mean value should be saved along the line, by default False
1925
        save_std : bool, optional
1926
            Whether the standard deviation of the data along the line should be
1927
            saved, by default False
1928
        save_most_common : bool, optional
1929
            Whether the mode (most common) value should be saved used for identifying cartilage
1930
            regions on the bone surface, by default False
1931
        save_max : bool, optional
1932
            Whether the max value should be saved along the line, be default False
1933
        filler : int, optional
1934
            What value should be placed at locations where we don&#39;t have a value
1935
            (e.g., where we don&#39;t have T2 values), by default 0
1936
        non_zero_only : bool, optional
1937
            Only save non-zero values along the line, by default True
1938
            This is done becuase zeros are normally regions of error (e.g.
1939
            poor T2 relaxation fit) and thus would artifically reduce the outcome
1940
            along the line.
1941
        data_categorical : bool, optional
1942
            Specify whether or not the data is categorical to determine the interpolation
1943
            method that should be used. 
1944
        &#34;&#34;&#34;        
1945
        self.save_mean = save_mean
1946
        self.save_std = save_std
1947
        self.save_most_common = save_most_common
1948
        self.save_max = save_max
1949
        self.filler = filler
1950
        self.non_zero_only = non_zero_only
1951
1952
        self.line = vtk.vtkLineSource()
1953
        self.line.SetResolution(line_resolution)
1954
1955
        self.probe_filter = vtk.vtkProbeFilter()
1956
        self.probe_filter.SetSourceData(vtk_image)
1957
        if data_categorical is True:
1958
            self.probe_filter.CategoricalDataOn()
1959
1960
        if save_data_in_class is True:
1961
            if self.save_mean is True:
1962
                self._mean_data = []
1963
            if self.save_std is True:
1964
                self._std_data = []
1965
            if self.save_most_common is True:
1966
                self._most_common_data = []
1967
            if self.save_max is True:
1968
                self._max_data = []
1969
1970
    def get_data_along_line(self,
1971
                            start_pt,
1972
                            end_pt):
1973
        &#34;&#34;&#34;
1974
        Function to get scalar values along a line between `start_pt` and `end_pt`. 
1975
1976
        Parameters
1977
        ----------
1978
        start_pt : list
1979
            List of the x,y,z position of the starting point in the line. 
1980
        end_pt : list
1981
            List of the x,y,z position of the ending point in the line. 
1982
1983
        Returns
1984
        -------
1985
        numpy.ndarray
1986
            numpy array of scalar values obtained along the line.
1987
        &#34;&#34;&#34;        
1988
        self.line.SetPoint1(start_pt)
1989
        self.line.SetPoint2(end_pt)
1990
1991
        self.probe_filter.SetInputConnection(self.line.GetOutputPort())
1992
        self.probe_filter.Update()
1993
        scalars = vtk_to_numpy(self.probe_filter.GetOutput().GetPointData().GetScalars())
1994
1995
        if self.non_zero_only is True:
1996
            scalars = scalars[scalars != 0]
1997
1998
        return scalars
1999
2000
    def save_data_along_line(self,
2001
                             start_pt,
2002
                             end_pt):
2003
        &#34;&#34;&#34;
2004
        Save the appropriate outcomes to a growing list. 
2005
2006
        Parameters
2007
        ----------
2008
        start_pt : list
2009
            List of the x,y,z position of the starting point in the line. 
2010
        end_pt : list
2011
            List of the x,y,z position of the ending point in the line. 
2012
        &#34;&#34;&#34;        
2013
        scalars = self.get_data_along_line(start_pt, end_pt)
2014
        if len(scalars) &gt; 0:
2015
            if self.save_mean is True:
2016
                self._mean_data.append(np.mean(scalars))
2017
            if self.save_std is True:
2018
                self._std_data.append(np.std(scalars, ddof=1))
2019
            if self.save_most_common is True:
2020
                # most_common is for getting segmentations and trying to assign a bone region
2021
                # to be a cartilage ROI. This is becuase there might be a normal vector that
2022
                # cross &gt; 1 cartilage region (e.g., weight-bearing vs anterior fem cartilage)
2023
                self._most_common_data.append(np.bincount(scalars).argmax())
2024
            if self.save_max is True:
2025
                self._max_data.append(np.max(scalars))
2026
        else:
2027
            self.append_filler()
2028
2029
    def append_filler(self):
2030
        &#34;&#34;&#34;
2031
        Add filler value to the requisite lists (_mean_data, _std_data, etc.) as 
2032
        appropriate. 
2033
        &#34;&#34;&#34;        
2034
        if self.save_mean is True:
2035
            self._mean_data.append(self.filler)
2036
        if self.save_std is True:
2037
            self._std_data.append(self.filler)
2038
        if self.save_most_common is True:
2039
            self._most_common_data.append(self.filler)
2040
        if self.save_max is True:
2041
            self._max_data.append(self.filler)
2042
2043
    @property
2044
    def mean_data(self):
2045
        &#34;&#34;&#34;
2046
        Return the `_mean_data`
2047
2048
        Returns
2049
        -------
2050
        list
2051
            List of mean values along each line tested. 
2052
        &#34;&#34;&#34;        
2053
        if self.save_mean is True:
2054
            return self._mean_data
2055
        else:
2056
            return None
2057
2058
    @property
2059
    def std_data(self):
2060
        &#34;&#34;&#34;
2061
        Return the `_std_data`
2062
2063
        Returns
2064
        -------
2065
        list
2066
            List of the std values along each line tested. 
2067
        &#34;&#34;&#34;        
2068
        if self.save_std is True:
2069
            return self._std_data
2070
        else:
2071
            return None
2072
2073
    @property
2074
    def most_common_data(self):
2075
        &#34;&#34;&#34;
2076
        Return the `_most_common_data`
2077
2078
        Returns
2079
        -------
2080
        list
2081
            List of the most common value for each line tested. 
2082
        &#34;&#34;&#34;        
2083
        if self.save_most_common is True:
2084
            return self._most_common_data
2085
        else:
2086
            return None
2087
    
2088
    @property
2089
    def max_data(self):
2090
        &#34;&#34;&#34;
2091
        Return the `_max_data`
2092
2093
        Returns
2094
        -------
2095
        list
2096
            List of the most common value for each line tested. 
2097
        &#34;&#34;&#34;        
2098
        if self.save_max is True:
2099
            return self._max_data
2100
        else:
2101
            return None</code></pre>
2102
</details>
2103
<h3>Instance variables</h3>
2104
<dl>
2105
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.max_data"><code class="name">var <span class="ident">max_data</span></code></dt>
2106
<dd>
2107
<div class="desc"><p>Return the <code>_max_data</code></p>
2108
<h2 id="returns">Returns</h2>
2109
<dl>
2110
<dt><code>list</code></dt>
2111
<dd>List of the most common value for each line tested.</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 max_data(self):
2119
    &#34;&#34;&#34;
2120
    Return the `_max_data`
2121
2122
    Returns
2123
    -------
2124
    list
2125
        List of the most common value for each line tested. 
2126
    &#34;&#34;&#34;        
2127
    if self.save_max is True:
2128
        return self._max_data
2129
    else:
2130
        return None</code></pre>
2131
</details>
2132
</dd>
2133
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.mean_data"><code class="name">var <span class="ident">mean_data</span></code></dt>
2134
<dd>
2135
<div class="desc"><p>Return the <code>_mean_data</code></p>
2136
<h2 id="returns">Returns</h2>
2137
<dl>
2138
<dt><code>list</code></dt>
2139
<dd>List of mean values along each line tested.</dd>
2140
</dl></div>
2141
<details class="source">
2142
<summary>
2143
<span>Expand source code</span>
2144
</summary>
2145
<pre><code class="python">@property
2146
def mean_data(self):
2147
    &#34;&#34;&#34;
2148
    Return the `_mean_data`
2149
2150
    Returns
2151
    -------
2152
    list
2153
        List of mean values along each line tested. 
2154
    &#34;&#34;&#34;        
2155
    if self.save_mean is True:
2156
        return self._mean_data
2157
    else:
2158
        return None</code></pre>
2159
</details>
2160
</dd>
2161
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.most_common_data"><code class="name">var <span class="ident">most_common_data</span></code></dt>
2162
<dd>
2163
<div class="desc"><p>Return the <code>_most_common_data</code></p>
2164
<h2 id="returns">Returns</h2>
2165
<dl>
2166
<dt><code>list</code></dt>
2167
<dd>List of the most common value for each line tested.</dd>
2168
</dl></div>
2169
<details class="source">
2170
<summary>
2171
<span>Expand source code</span>
2172
</summary>
2173
<pre><code class="python">@property
2174
def most_common_data(self):
2175
    &#34;&#34;&#34;
2176
    Return the `_most_common_data`
2177
2178
    Returns
2179
    -------
2180
    list
2181
        List of the most common value for each line tested. 
2182
    &#34;&#34;&#34;        
2183
    if self.save_most_common is True:
2184
        return self._most_common_data
2185
    else:
2186
        return None</code></pre>
2187
</details>
2188
</dd>
2189
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.std_data"><code class="name">var <span class="ident">std_data</span></code></dt>
2190
<dd>
2191
<div class="desc"><p>Return the <code>_std_data</code></p>
2192
<h2 id="returns">Returns</h2>
2193
<dl>
2194
<dt><code>list</code></dt>
2195
<dd>List of the std values along each line tested.</dd>
2196
</dl></div>
2197
<details class="source">
2198
<summary>
2199
<span>Expand source code</span>
2200
</summary>
2201
<pre><code class="python">@property
2202
def std_data(self):
2203
    &#34;&#34;&#34;
2204
    Return the `_std_data`
2205
2206
    Returns
2207
    -------
2208
    list
2209
        List of the std values along each line tested. 
2210
    &#34;&#34;&#34;        
2211
    if self.save_std is True:
2212
        return self._std_data
2213
    else:
2214
        return None</code></pre>
2215
</details>
2216
</dd>
2217
</dl>
2218
<h3>Methods</h3>
2219
<dl>
2220
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.append_filler"><code class="name flex">
2221
<span>def <span class="ident">append_filler</span></span>(<span>self)</span>
2222
</code></dt>
2223
<dd>
2224
<div class="desc"><p>Add filler value to the requisite lists (_mean_data, _std_data, etc.) as
2225
appropriate.</p></div>
2226
<details class="source">
2227
<summary>
2228
<span>Expand source code</span>
2229
</summary>
2230
<pre><code class="python">def append_filler(self):
2231
    &#34;&#34;&#34;
2232
    Add filler value to the requisite lists (_mean_data, _std_data, etc.) as 
2233
    appropriate. 
2234
    &#34;&#34;&#34;        
2235
    if self.save_mean is True:
2236
        self._mean_data.append(self.filler)
2237
    if self.save_std is True:
2238
        self._std_data.append(self.filler)
2239
    if self.save_most_common is True:
2240
        self._most_common_data.append(self.filler)
2241
    if self.save_max is True:
2242
        self._max_data.append(self.filler)</code></pre>
2243
</details>
2244
</dd>
2245
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.get_data_along_line"><code class="name flex">
2246
<span>def <span class="ident">get_data_along_line</span></span>(<span>self, start_pt, end_pt)</span>
2247
</code></dt>
2248
<dd>
2249
<div class="desc"><p>Function to get scalar values along a line between <code>start_pt</code> and <code>end_pt</code>. </p>
2250
<h2 id="parameters">Parameters</h2>
2251
<dl>
2252
<dt><strong><code>start_pt</code></strong> :&ensp;<code>list</code></dt>
2253
<dd>List of the x,y,z position of the starting point in the line.</dd>
2254
<dt><strong><code>end_pt</code></strong> :&ensp;<code>list</code></dt>
2255
<dd>List of the x,y,z position of the ending point in the line.</dd>
2256
</dl>
2257
<h2 id="returns">Returns</h2>
2258
<dl>
2259
<dt><code>numpy.ndarray</code></dt>
2260
<dd>numpy array of scalar values obtained along the line.</dd>
2261
</dl></div>
2262
<details class="source">
2263
<summary>
2264
<span>Expand source code</span>
2265
</summary>
2266
<pre><code class="python">def get_data_along_line(self,
2267
                        start_pt,
2268
                        end_pt):
2269
    &#34;&#34;&#34;
2270
    Function to get scalar values along a line between `start_pt` and `end_pt`. 
2271
2272
    Parameters
2273
    ----------
2274
    start_pt : list
2275
        List of the x,y,z position of the starting point in the line. 
2276
    end_pt : list
2277
        List of the x,y,z position of the ending point in the line. 
2278
2279
    Returns
2280
    -------
2281
    numpy.ndarray
2282
        numpy array of scalar values obtained along the line.
2283
    &#34;&#34;&#34;        
2284
    self.line.SetPoint1(start_pt)
2285
    self.line.SetPoint2(end_pt)
2286
2287
    self.probe_filter.SetInputConnection(self.line.GetOutputPort())
2288
    self.probe_filter.Update()
2289
    scalars = vtk_to_numpy(self.probe_filter.GetOutput().GetPointData().GetScalars())
2290
2291
    if self.non_zero_only is True:
2292
        scalars = scalars[scalars != 0]
2293
2294
    return scalars</code></pre>
2295
</details>
2296
</dd>
2297
<dt id="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.save_data_along_line"><code class="name flex">
2298
<span>def <span class="ident">save_data_along_line</span></span>(<span>self, start_pt, end_pt)</span>
2299
</code></dt>
2300
<dd>
2301
<div class="desc"><p>Save the appropriate outcomes to a growing list. </p>
2302
<h2 id="parameters">Parameters</h2>
2303
<dl>
2304
<dt><strong><code>start_pt</code></strong> :&ensp;<code>list</code></dt>
2305
<dd>List of the x,y,z position of the starting point in the line.</dd>
2306
<dt><strong><code>end_pt</code></strong> :&ensp;<code>list</code></dt>
2307
<dd>List of the x,y,z position of the ending point in the line.</dd>
2308
</dl></div>
2309
<details class="source">
2310
<summary>
2311
<span>Expand source code</span>
2312
</summary>
2313
<pre><code class="python">def save_data_along_line(self,
2314
                         start_pt,
2315
                         end_pt):
2316
    &#34;&#34;&#34;
2317
    Save the appropriate outcomes to a growing list. 
2318
2319
    Parameters
2320
    ----------
2321
    start_pt : list
2322
        List of the x,y,z position of the starting point in the line. 
2323
    end_pt : list
2324
        List of the x,y,z position of the ending point in the line. 
2325
    &#34;&#34;&#34;        
2326
    scalars = self.get_data_along_line(start_pt, end_pt)
2327
    if len(scalars) &gt; 0:
2328
        if self.save_mean is True:
2329
            self._mean_data.append(np.mean(scalars))
2330
        if self.save_std is True:
2331
            self._std_data.append(np.std(scalars, ddof=1))
2332
        if self.save_most_common is True:
2333
            # most_common is for getting segmentations and trying to assign a bone region
2334
            # to be a cartilage ROI. This is becuase there might be a normal vector that
2335
            # cross &gt; 1 cartilage region (e.g., weight-bearing vs anterior fem cartilage)
2336
            self._most_common_data.append(np.bincount(scalars).argmax())
2337
        if self.save_max is True:
2338
            self._max_data.append(np.max(scalars))
2339
    else:
2340
        self.append_filler()</code></pre>
2341
</details>
2342
</dd>
2343
</dl>
2344
</dd>
2345
</dl>
2346
</section>
2347
</article>
2348
<nav id="sidebar">
2349
<h1>Index</h1>
2350
<div class="toc">
2351
<ul></ul>
2352
</div>
2353
<ul id="index">
2354
<li><h3>Super-module</h3>
2355
<ul>
2356
<li><code><a title="pymskt.mesh" href="index.html">pymskt.mesh</a></code></li>
2357
</ul>
2358
</li>
2359
<li><h3><a href="#header-functions">Functions</a></h3>
2360
<ul class="">
2361
<li><code><a title="pymskt.mesh.meshTools.gaussian_smooth_surface_scalars" href="#pymskt.mesh.meshTools.gaussian_smooth_surface_scalars">gaussian_smooth_surface_scalars</a></code></li>
2362
<li><code><a title="pymskt.mesh.meshTools.get_cartilage_properties_at_points" href="#pymskt.mesh.meshTools.get_cartilage_properties_at_points">get_cartilage_properties_at_points</a></code></li>
2363
<li><code><a title="pymskt.mesh.meshTools.get_mesh_physical_point_coords" href="#pymskt.mesh.meshTools.get_mesh_physical_point_coords">get_mesh_physical_point_coords</a></code></li>
2364
<li><code><a title="pymskt.mesh.meshTools.get_smoothed_scalars" href="#pymskt.mesh.meshTools.get_smoothed_scalars">get_smoothed_scalars</a></code></li>
2365
<li><code><a title="pymskt.mesh.meshTools.resample_surface" href="#pymskt.mesh.meshTools.resample_surface">resample_surface</a></code></li>
2366
<li><code><a title="pymskt.mesh.meshTools.set_mesh_physical_point_coords" href="#pymskt.mesh.meshTools.set_mesh_physical_point_coords">set_mesh_physical_point_coords</a></code></li>
2367
<li><code><a title="pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base" href="#pymskt.mesh.meshTools.smooth_scalars_from_second_mesh_onto_base">smooth_scalars_from_second_mesh_onto_base</a></code></li>
2368
<li><code><a title="pymskt.mesh.meshTools.transfer_mesh_scalars_get_weighted_average_n_closest" href="#pymskt.mesh.meshTools.transfer_mesh_scalars_get_weighted_average_n_closest">transfer_mesh_scalars_get_weighted_average_n_closest</a></code></li>
2369
</ul>
2370
</li>
2371
<li><h3><a href="#header-classes">Classes</a></h3>
2372
<ul>
2373
<li>
2374
<h4><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine">ProbeVtkImageDataAlongLine</a></code></h4>
2375
<ul class="">
2376
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.append_filler" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.append_filler">append_filler</a></code></li>
2377
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.get_data_along_line" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.get_data_along_line">get_data_along_line</a></code></li>
2378
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.max_data" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.max_data">max_data</a></code></li>
2379
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.mean_data" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.mean_data">mean_data</a></code></li>
2380
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.most_common_data" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.most_common_data">most_common_data</a></code></li>
2381
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.save_data_along_line" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.save_data_along_line">save_data_along_line</a></code></li>
2382
<li><code><a title="pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.std_data" href="#pymskt.mesh.meshTools.ProbeVtkImageDataAlongLine.std_data">std_data</a></code></li>
2383
</ul>
2384
</li>
2385
</ul>
2386
</li>
2387
</ul>
2388
</nav>
2389
</main>
2390
<footer id="footer">
2391
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
2392
</footer>
2393
</body>
2394
</html>