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