--- a +++ b/docs/mesh/utils.html @@ -0,0 +1,1465 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> +<meta name="generator" content="pdoc 0.10.0" /> +<title>pymskt.mesh.utils API documentation</title> +<meta name="description" content="" /> +<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin> +<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin> +<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin> +<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style> +<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style> +<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style> +<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script> +<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script> +</head> +<body> +<main> +<article id="content"> +<header> +<h1 class="title">Module <code>pymskt.mesh.utils</code></h1> +</header> +<section id="section-intro"> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">import vtk +import numpy as np +from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk +from pymskt.utils import sigma2fwhm +import pyvista as pv +import pymskt + + +# Some functions were originally based on the tutorial on ray casting in python + vtk +# by Adamos Kyriakou @: +# https://pyscience.wordpress.com/2014/09/21/ray-casting-with-python-and-vtk-intersecting-linesrays-with-surface-meshes/ + + +def is_hit(obb_tree, source, target): + """ + Return True if line intersects mesh (`obb_tree`). The line starts at `source` and ends at `target`. + + Parameters + ---------- + obb_tree : vtk.vtkOBBTree + OBBTree of a surface mesh. + source : list + x/y/z position of starting point of ray (to find intersection) + target : list + x/y/z position of ending point of ray (to find intersection) + + Returns + ------- + bool + Telling if the line (source to target) intersects the obb_tree. + """ + + code = obb_tree.IntersectWithLine(source, target, None, None) + if code == 0: + return False + else: + return True + + +def get_intersect(obbTree, pSource, pTarget): + """ + Get intersecting points on the obbTree between a line from pSource to pTarget. + + Parameters + ---------- + obb_tree : vtk.vtkOBBTree + OBBTree of a surface mesh. + pSource : list + x/y/z position of starting point of ray (to find intersection) + pTarget : list + x/y/z position of ending point of ray (to find intersection) + + Returns + ------- + tuple (list1, list2) + list1 is of the intersection points + list2 is the idx of the cells that were intersected. + """ + # Create an empty 'vtkPoints' object to store the intersection point coordinates + points = vtk.vtkPoints() + # Create an empty 'vtkIdList' object to store the ids of the cells that intersect + # with the cast rays + cell_ids = vtk.vtkIdList() + + # Perform intersection + code = obbTree.IntersectWithLine(pSource, pTarget, points, cell_ids) + + # Get point-data + point_data = points.GetData() + # Get number of intersection points found + n_points = point_data.GetNumberOfTuples() + # Get number of intersected cell ids + n_Ids = cell_ids.GetNumberOfIds() + + assert (n_points == n_Ids) + + # Loop through the found points and cells and store + # them in lists + points_inter = [] + cell_ids_inter = [] + for idx in range(n_points): + points_inter.append(point_data.GetTuple3(idx)) + cell_ids_inter.append(cell_ids.GetId(idx)) + + return points_inter, cell_ids_inter + + +def get_surface_normals(surface, + point_normals_on=True, + cell_normals_on=True): + """ + Get the surface normals of a mesh (`surface` + + Parameters + ---------- + surface : vtk.vtkPolyData + surface mesh to get normals from + point_normals_on : bool, optional + Whether or not to get normals of points (vertices), by default True + cell_normals_on : bool, optional + Whether or not to get normals from cells (faces?), by default True + + Returns + ------- + vtk.vtkPolyDataNormals + Normval vectors for points/cells. + """ + + normals = vtk.vtkPolyDataNormals() + normals.SetInputData(surface) + + # Disable normal calculation at cell vertices + if point_normals_on is True: + normals.ComputePointNormalsOn() + elif point_normals_on is False: + normals.ComputePointNormalsOff() + # Enable normal calculation at cell centers + if cell_normals_on is True: + normals.ComputeCellNormalsOn() + elif cell_normals_on is False: + normals.ComputeCellNormalsOff() + # Disable splitting of sharp edges + normals.SplittingOff() + # Disable global flipping of normal orientation + normals.FlipNormalsOff() + # Enable automatic determination of correct normal orientation + normals.AutoOrientNormalsOn() + # Perform calculation + normals.Update() + + return normals + + +def get_obb_surface(surface): + """ + Get vtk.vtkOBBTree for a surface mesh + Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface. + + Parameters + ---------- + surface : vtk.vtkPolyData + The surface mesh to get an OBBTree for. + + Returns + ------- + vtk.vtkOBBTree + The OBBTree to be used to find intersections for calculating cartilage thickness etc. + """ + + obb = vtk.vtkOBBTree() + obb.SetDataSet(surface) + obb.BuildLocator() + return obb + + +def vtk_deep_copy(mesh): + """ + "Deep" copy a vtk.vtkPolyData so that they are not connected in any way. + + Parameters + ---------- + mesh : vtk.vtkPolyData + Mesh to copy. + + Returns + ------- + vtk.vtkPolyData + Copy of the input mesh. + """ + new_mesh = vtk.vtkPolyData() + new_mesh.DeepCopy(mesh) + return new_mesh + +def estimate_mesh_scalars_FWHMs(mesh, scalar_name='thickness_mm'): + """ + Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. + + Parameters + ---------- + mesh : vtk.vtkPolyData + Surface mesh to estimate FWHM of the scalars from. + scalar_name : str, optional + Name of the scalars to calcualte FWHM for, by default 'thickness_mm' + + Returns + ------- + list + List of the FWHM values. Assuming they are for X/Y/Z + """ + gradient_filter = vtk.vtkGradientFilter() + gradient_filter.SetInputData(mesh) + gradient_filter.Update() + gradient_mesh = vtk.vtkPolyData() + gradient_mesh.DeepCopy(gradient_filter.GetOutput()) + + scalars = vtk_to_numpy(mesh.GetPointData().GetScalars()) + location_non_zero = np.where(scalars != 0) + gradient_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray('Gradients')) + cartilage_gradients = gradient_scalars[location_non_zero, :][0] + + thickness_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(scalar_name)) + cartilage_thicknesses = thickness_scalars[location_non_zero] + + V0 = np.mean((cartilage_thicknesses - np.mean(cartilage_thicknesses)) ** 2) + V1 = np.mean((cartilage_gradients - np.mean(cartilage_gradients)) ** 2, axis=0) + sigma2s = -1 / (4 * np.log(1 - (V1 / (2 * V0)))) + sigmas = np.sqrt(sigma2s) + FWHMs = [sigma2fwhm(x) for x in sigmas] + + return FWHMs + +def get_surface_distance(surface_1, + surface_2, + return_RMS=True, + return_individual_distances=False): + + if (return_RMS is True) & (return_individual_distances is True): + raise Exception('Nothing to return - either return_RMS or return_individual_distances must be `True`') + + pt_locator = vtk.vtkPointLocator() + pt_locator.SetDataSet(surface_2) + pt_locator.AutomaticOn() + pt_locator.BuildLocator() + + distances = np.zeros(surface_1.GetNumberOfPoints()) + + for pt_idx in range(surface_1.GetNumberOfPoints()): + point_1 = np.asarray(surface_1.GetPoint(pt_idx)) + pt_idx_2 = pt_locator.FindClosestPoint(point_1) + point_2 = np.asarray(surface_2.GetPoint(pt_idx_2)) + distances[pt_idx] = np.sqrt(np.sum(np.square(point_2-point_1))) + + RMS = np.sqrt(np.mean(np.square(distances))) + + if return_individual_distances is True: + if return_RMS is True: + return RMS, distances + else: + return distances + else: + if return_RMS is True: + return RMS + +def get_symmetric_surface_distance(surface_1, surface_2): + surf1_to_2_distances = get_surface_distance(surface_1, surface_2, return_RMS=False, return_individual_distances=True) + surf2_to_1_distances = get_surface_distance(surface_2, surface_1, return_RMS=False, return_individual_distances=True) + + symmetric_distance = (np.sum(surf1_to_2_distances) + np.sum(surf2_to_1_distances)) / (len(surf1_to_2_distances) + len(surf2_to_1_distances)) + + return symmetric_distance + +class GIF: + """ + Class for generating GIF of surface meshes. + + Parameters + ---------- + plotter : pyvista.Plotter + Plotter to use for plotting. + color: str, optional + Color to use for object, by default 'orange' + show_edges: bool, optional + Whether to show edges on mesh, by default True + edge_color: str, optional + Color to use for edges, by default 'black' + camera_position: list or string, optional + Camera position to use, by default 'xz' + window_size: list, optional + Window size to use for GIF, by default [3000, 4000] + background_color: str, optional + Background color to use, by default 'white' + path_save: str, optional + Path to save GIF, by default '~/Downloads/ssm.gif' + + Attributes + ---------- + _plotter : pyvista.Plotter + Plotter to use for plotting. + _color : str + Color to use for object. + _show_edges : bool + Whether to show edges on mesh. + _edge_color : str + Color to use for edges. + _camera_position : list or string + Camera position to use. + _window_size : list + Window size to use for GIF. + _background_color : str + Background color to use. + _path_save : str + Path to save GIF. + + Methods + ------- + add_mesh_frame(mesh) + Add a mesh to the GIF. + update_view() + Update the view of the plotter. + done() + Close the plotter. + + + """ + def __init__( + self, + plotter=None, + color='orange', + show_edges=True, + edge_color='black', + camera_position='xz', + window_size=[3000, 4000], + background_color='white', + path_save='~/Downloads/ssm.gif' + ): + """ + Initialize the GIF class. + + Parameters + ---------- + plotter : pyvista.Plotter, optional + Plotter to use for plotting, by default None + color: str, optional + Color to use for object, by default 'orange' + show_edges: bool, optional + Whether to show edges on mesh, by default True + edge_color: str, optional + Color to use for edges, by default 'black' + camera_position: list or string, optional + Camera position to use, by default 'xz' + window_size: list, optional + Window size to use for GIF, by default [3000, 4000] + background_color: str, optional + Background color to use, by default 'white' + path_save: str, optional + Path to save GIF, by default '~/Downloads/ssm.gif' + + """ + if plotter is None: + self._plotter = pv.Plotter(notebook=False, off_screen=True) + else: + self._plotter = plotter + + if path_save[-3:] != 'gif': + raise Exception('path must be to a file ending with suffix `.gif`') + + self.counter = 0 + + self._plotter.open_gif(path_save) + + self._color = color + self._show_edges = show_edges + self._edge_color = edge_color + self._camera_position = camera_position + self._window_size = window_size + self._background_color = background_color + self._path_save = path_save + + def update_view( + self + ): + self._plotter.camera_position = self._camera_position + self._plotter.window_size = self._window_size + self._plotter.set_background(color=self._background_color) + + def add_mesh_frame(self, mesh): + if type(mesh) in (list, tuple): + actors = [] + for mesh_ in mesh: + actors.append(self._plotter.add_mesh( + mesh_, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + )) + else: + actor = self._plotter.add_mesh( + mesh, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + ) + + if self.counter == 0: + self.update_view() + self._plotter.write_frame() + + if type(mesh) in (list, tuple): + for actor in actors: + self._plotter.remove_actor(actor) + else: + self._plotter.remove_actor(actor) + self.counter += 1 + + def done(self): + self._plotter.close() + + @property + def color(self): + return self._color + + @color.setter + def color(self, color): + self._color = color + + @property + def show_edges(self): + return self._show_edges + + @show_edges.setter + def show_edges(self, show_edges): + self._show_edges = show_edges + + @property + def edge_color(self): + return self._edge_color + + @edge_color.setter + def edge_color(self, edge_color): + self._edge_color = edge_color + + @property + def camera_position(self): + return self._camera_position + + @camera_position.setter + def camera_position(self, camera_position): + self._camera_position = camera_position + + @property + def window_size(self): + return self._window_size + + @window_size.setter + def window_size(self, window_size): + self._window_size = window_size + + @property + def background_color(self): + return self._background_color + + @background_color.setter + def background_color(self, background_color): + self._background_color = background_color + + @property + def path_save(self): + return self._path_save + +def get_arrow( + direction, + origin, + scale=100, + tip_length=0.25, + tip_radius=0.1, + tip_resolution=20, + shaft_radius=0.05, + shaft_resolution=20, +): + + arrow = vtk.vtkArrowSource() + arrow.SetTipLength(tip_length) + arrow.SetTipRadius(tip_radius) + arrow.SetTipResolution(tip_resolution) + arrow.SetShaftRadius(shaft_radius) + arrow.SetShaftResolution(shaft_resolution) + arrow.Update() + + arrow = arrow.GetOutput() + points = arrow.GetPoints().GetData() + array = vtk_to_numpy(points) + array *= scale + arrow.GetPoints().SetData(numpy_to_vtk(array)) + + normx = np.array(direction) / np.linalg.norm(direction) + normz = np.cross(normx, [0, 1.0, 0.0001]) + normz /= np.linalg.norm(normz) + normy = np.cross(normz, normx) + + four_by_four = np.identity(4) + four_by_four[:3,0] = normx + four_by_four[:3,1] = normy + four_by_four[:3,2] = normz + four_by_four[:3, 3] = origin + + transform = pymskt.mesh.meshTransform.create_transform(four_by_four) + arrow = pymskt.mesh.meshTransform.apply_transform(arrow, transform) + + return arrow</code></pre> +</details> +</section> +<section> +</section> +<section> +</section> +<section> +<h2 class="section-title" id="header-functions">Functions</h2> +<dl> +<dt id="pymskt.mesh.utils.estimate_mesh_scalars_FWHMs"><code class="name flex"> +<span>def <span class="ident">estimate_mesh_scalars_FWHMs</span></span>(<span>mesh, scalar_name='thickness_mm')</span> +</code></dt> +<dd> +<div class="desc"><p>Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code></dt> +<dd>Surface mesh to estimate FWHM of the scalars from.</dd> +<dt><strong><code>scalar_name</code></strong> : <code>str</code>, optional</dt> +<dd>Name of the scalars to calcualte FWHM for, by default 'thickness_mm'</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>list</code></dt> +<dd>List of the FWHM values. Assuming they are for X/Y/Z</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def estimate_mesh_scalars_FWHMs(mesh, scalar_name='thickness_mm'): + """ + Calculate the Full Width Half Maximum (FWHM) based on surface mesh scalars. + + Parameters + ---------- + mesh : vtk.vtkPolyData + Surface mesh to estimate FWHM of the scalars from. + scalar_name : str, optional + Name of the scalars to calcualte FWHM for, by default 'thickness_mm' + + Returns + ------- + list + List of the FWHM values. Assuming they are for X/Y/Z + """ + gradient_filter = vtk.vtkGradientFilter() + gradient_filter.SetInputData(mesh) + gradient_filter.Update() + gradient_mesh = vtk.vtkPolyData() + gradient_mesh.DeepCopy(gradient_filter.GetOutput()) + + scalars = vtk_to_numpy(mesh.GetPointData().GetScalars()) + location_non_zero = np.where(scalars != 0) + gradient_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray('Gradients')) + cartilage_gradients = gradient_scalars[location_non_zero, :][0] + + thickness_scalars = vtk_to_numpy(gradient_mesh.GetPointData().GetAbstractArray(scalar_name)) + cartilage_thicknesses = thickness_scalars[location_non_zero] + + V0 = np.mean((cartilage_thicknesses - np.mean(cartilage_thicknesses)) ** 2) + V1 = np.mean((cartilage_gradients - np.mean(cartilage_gradients)) ** 2, axis=0) + sigma2s = -1 / (4 * np.log(1 - (V1 / (2 * V0)))) + sigmas = np.sqrt(sigma2s) + FWHMs = [sigma2fwhm(x) for x in sigmas] + + return FWHMs</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_arrow"><code class="name flex"> +<span>def <span class="ident">get_arrow</span></span>(<span>direction, origin, scale=100, tip_length=0.25, tip_radius=0.1, tip_resolution=20, shaft_radius=0.05, shaft_resolution=20)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_arrow( + direction, + origin, + scale=100, + tip_length=0.25, + tip_radius=0.1, + tip_resolution=20, + shaft_radius=0.05, + shaft_resolution=20, +): + + arrow = vtk.vtkArrowSource() + arrow.SetTipLength(tip_length) + arrow.SetTipRadius(tip_radius) + arrow.SetTipResolution(tip_resolution) + arrow.SetShaftRadius(shaft_radius) + arrow.SetShaftResolution(shaft_resolution) + arrow.Update() + + arrow = arrow.GetOutput() + points = arrow.GetPoints().GetData() + array = vtk_to_numpy(points) + array *= scale + arrow.GetPoints().SetData(numpy_to_vtk(array)) + + normx = np.array(direction) / np.linalg.norm(direction) + normz = np.cross(normx, [0, 1.0, 0.0001]) + normz /= np.linalg.norm(normz) + normy = np.cross(normz, normx) + + four_by_four = np.identity(4) + four_by_four[:3,0] = normx + four_by_four[:3,1] = normy + four_by_four[:3,2] = normz + four_by_four[:3, 3] = origin + + transform = pymskt.mesh.meshTransform.create_transform(four_by_four) + arrow = pymskt.mesh.meshTransform.apply_transform(arrow, transform) + + return arrow</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_intersect"><code class="name flex"> +<span>def <span class="ident">get_intersect</span></span>(<span>obbTree, pSource, pTarget)</span> +</code></dt> +<dd> +<div class="desc"><p>Get intersecting points on the obbTree between a line from pSource to pTarget. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>obb_tree</code></strong> : <code>vtk.vtkOBBTree</code></dt> +<dd>OBBTree of a surface mesh.</dd> +<dt><strong><code>pSource</code></strong> : <code>list</code></dt> +<dd>x/y/z position of starting point of ray (to find intersection)</dd> +<dt><strong><code>pTarget</code></strong> : <code>list</code></dt> +<dd>x/y/z position of ending point of ray (to find intersection)</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>tuple (list1, list2)</code></dt> +<dd>list1 is of the intersection points +list2 is the idx of the cells that were intersected.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_intersect(obbTree, pSource, pTarget): + """ + Get intersecting points on the obbTree between a line from pSource to pTarget. + + Parameters + ---------- + obb_tree : vtk.vtkOBBTree + OBBTree of a surface mesh. + pSource : list + x/y/z position of starting point of ray (to find intersection) + pTarget : list + x/y/z position of ending point of ray (to find intersection) + + Returns + ------- + tuple (list1, list2) + list1 is of the intersection points + list2 is the idx of the cells that were intersected. + """ + # Create an empty 'vtkPoints' object to store the intersection point coordinates + points = vtk.vtkPoints() + # Create an empty 'vtkIdList' object to store the ids of the cells that intersect + # with the cast rays + cell_ids = vtk.vtkIdList() + + # Perform intersection + code = obbTree.IntersectWithLine(pSource, pTarget, points, cell_ids) + + # Get point-data + point_data = points.GetData() + # Get number of intersection points found + n_points = point_data.GetNumberOfTuples() + # Get number of intersected cell ids + n_Ids = cell_ids.GetNumberOfIds() + + assert (n_points == n_Ids) + + # Loop through the found points and cells and store + # them in lists + points_inter = [] + cell_ids_inter = [] + for idx in range(n_points): + points_inter.append(point_data.GetTuple3(idx)) + cell_ids_inter.append(cell_ids.GetId(idx)) + + return points_inter, cell_ids_inter</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_obb_surface"><code class="name flex"> +<span>def <span class="ident">get_obb_surface</span></span>(<span>surface)</span> +</code></dt> +<dd> +<div class="desc"><p>Get vtk.vtkOBBTree for a surface mesh +Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface.</p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>surface</code></strong> : <code>vtk.vtkPolyData</code></dt> +<dd>The surface mesh to get an OBBTree for.</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>vtk.vtkOBBTree</code></dt> +<dd>The OBBTree to be used to find intersections for calculating cartilage thickness etc.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_obb_surface(surface): + """ + Get vtk.vtkOBBTree for a surface mesh + Get obb of a surface mesh. This can be queried to see if a line etc. intersects a surface. + + Parameters + ---------- + surface : vtk.vtkPolyData + The surface mesh to get an OBBTree for. + + Returns + ------- + vtk.vtkOBBTree + The OBBTree to be used to find intersections for calculating cartilage thickness etc. + """ + + obb = vtk.vtkOBBTree() + obb.SetDataSet(surface) + obb.BuildLocator() + return obb</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_surface_distance"><code class="name flex"> +<span>def <span class="ident">get_surface_distance</span></span>(<span>surface_1, surface_2, return_RMS=True, return_individual_distances=False)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_surface_distance(surface_1, + surface_2, + return_RMS=True, + return_individual_distances=False): + + if (return_RMS is True) & (return_individual_distances is True): + raise Exception('Nothing to return - either return_RMS or return_individual_distances must be `True`') + + pt_locator = vtk.vtkPointLocator() + pt_locator.SetDataSet(surface_2) + pt_locator.AutomaticOn() + pt_locator.BuildLocator() + + distances = np.zeros(surface_1.GetNumberOfPoints()) + + for pt_idx in range(surface_1.GetNumberOfPoints()): + point_1 = np.asarray(surface_1.GetPoint(pt_idx)) + pt_idx_2 = pt_locator.FindClosestPoint(point_1) + point_2 = np.asarray(surface_2.GetPoint(pt_idx_2)) + distances[pt_idx] = np.sqrt(np.sum(np.square(point_2-point_1))) + + RMS = np.sqrt(np.mean(np.square(distances))) + + if return_individual_distances is True: + if return_RMS is True: + return RMS, distances + else: + return distances + else: + if return_RMS is True: + return RMS</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_surface_normals"><code class="name flex"> +<span>def <span class="ident">get_surface_normals</span></span>(<span>surface, point_normals_on=True, cell_normals_on=True)</span> +</code></dt> +<dd> +<div class="desc"><p>Get the surface normals of a mesh (<code>surface</code></p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>surface</code></strong> : <code>vtk.vtkPolyData</code></dt> +<dd>surface mesh to get normals from</dd> +<dt><strong><code>point_normals_on</code></strong> : <code>bool</code>, optional</dt> +<dd>Whether or not to get normals of points (vertices), by default True</dd> +<dt><strong><code>cell_normals_on</code></strong> : <code>bool</code>, optional</dt> +<dd>Whether or not to get normals from cells (faces?), by default True</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>vtk.vtkPolyDataNormals</code></dt> +<dd>Normval vectors for points/cells.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_surface_normals(surface, + point_normals_on=True, + cell_normals_on=True): + """ + Get the surface normals of a mesh (`surface` + + Parameters + ---------- + surface : vtk.vtkPolyData + surface mesh to get normals from + point_normals_on : bool, optional + Whether or not to get normals of points (vertices), by default True + cell_normals_on : bool, optional + Whether or not to get normals from cells (faces?), by default True + + Returns + ------- + vtk.vtkPolyDataNormals + Normval vectors for points/cells. + """ + + normals = vtk.vtkPolyDataNormals() + normals.SetInputData(surface) + + # Disable normal calculation at cell vertices + if point_normals_on is True: + normals.ComputePointNormalsOn() + elif point_normals_on is False: + normals.ComputePointNormalsOff() + # Enable normal calculation at cell centers + if cell_normals_on is True: + normals.ComputeCellNormalsOn() + elif cell_normals_on is False: + normals.ComputeCellNormalsOff() + # Disable splitting of sharp edges + normals.SplittingOff() + # Disable global flipping of normal orientation + normals.FlipNormalsOff() + # Enable automatic determination of correct normal orientation + normals.AutoOrientNormalsOn() + # Perform calculation + normals.Update() + + return normals</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.get_symmetric_surface_distance"><code class="name flex"> +<span>def <span class="ident">get_symmetric_surface_distance</span></span>(<span>surface_1, surface_2)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def get_symmetric_surface_distance(surface_1, surface_2): + surf1_to_2_distances = get_surface_distance(surface_1, surface_2, return_RMS=False, return_individual_distances=True) + surf2_to_1_distances = get_surface_distance(surface_2, surface_1, return_RMS=False, return_individual_distances=True) + + symmetric_distance = (np.sum(surf1_to_2_distances) + np.sum(surf2_to_1_distances)) / (len(surf1_to_2_distances) + len(surf2_to_1_distances)) + + return symmetric_distance</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.is_hit"><code class="name flex"> +<span>def <span class="ident">is_hit</span></span>(<span>obb_tree, source, target)</span> +</code></dt> +<dd> +<div class="desc"><p>Return True if line intersects mesh (<code>obb_tree</code>). The line starts at <code>source</code> and ends at <code>target</code>.</p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>obb_tree</code></strong> : <code>vtk.vtkOBBTree</code></dt> +<dd>OBBTree of a surface mesh.</dd> +<dt><strong><code>source</code></strong> : <code>list</code></dt> +<dd>x/y/z position of starting point of ray (to find intersection)</dd> +<dt><strong><code>target</code></strong> : <code>list</code></dt> +<dd>x/y/z position of ending point of ray (to find intersection)</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>bool</code></dt> +<dd>Telling if the line (source to target) intersects the obb_tree.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def is_hit(obb_tree, source, target): + """ + Return True if line intersects mesh (`obb_tree`). The line starts at `source` and ends at `target`. + + Parameters + ---------- + obb_tree : vtk.vtkOBBTree + OBBTree of a surface mesh. + source : list + x/y/z position of starting point of ray (to find intersection) + target : list + x/y/z position of ending point of ray (to find intersection) + + Returns + ------- + bool + Telling if the line (source to target) intersects the obb_tree. + """ + + code = obb_tree.IntersectWithLine(source, target, None, None) + if code == 0: + return False + else: + return True</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.vtk_deep_copy"><code class="name flex"> +<span>def <span class="ident">vtk_deep_copy</span></span>(<span>mesh)</span> +</code></dt> +<dd> +<div class="desc"><p>"Deep" copy a vtk.vtkPolyData so that they are not connected in any way. </p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>mesh</code></strong> : <code>vtk.vtkPolyData</code></dt> +<dd>Mesh to copy.</dd> +</dl> +<h2 id="returns">Returns</h2> +<dl> +<dt><code>vtk.vtkPolyData</code></dt> +<dd>Copy of the input mesh.</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def vtk_deep_copy(mesh): + """ + "Deep" copy a vtk.vtkPolyData so that they are not connected in any way. + + Parameters + ---------- + mesh : vtk.vtkPolyData + Mesh to copy. + + Returns + ------- + vtk.vtkPolyData + Copy of the input mesh. + """ + new_mesh = vtk.vtkPolyData() + new_mesh.DeepCopy(mesh) + return new_mesh</code></pre> +</details> +</dd> +</dl> +</section> +<section> +<h2 class="section-title" id="header-classes">Classes</h2> +<dl> +<dt id="pymskt.mesh.utils.GIF"><code class="flex name class"> +<span>class <span class="ident">GIF</span></span> +<span>(</span><span>plotter=None, color='orange', show_edges=True, edge_color='black', camera_position='xz', window_size=[3000, 4000], background_color='white', path_save='~/Downloads/ssm.gif')</span> +</code></dt> +<dd> +<div class="desc"><p>Class for generating GIF of surface meshes.</p> +<h2 id="parameters">Parameters</h2> +<dl> +<dt><strong><code>plotter</code></strong> : <code>pyvista.Plotter</code></dt> +<dd>Plotter to use for plotting.</dd> +<dt><strong><code>color</code></strong> : <code>str</code>, optional</dt> +<dd>Color to use for object, by default 'orange'</dd> +<dt><strong><code>show_edges</code></strong> : <code>bool</code>, optional</dt> +<dd>Whether to show edges on mesh, by default True</dd> +<dt><strong><code>edge_color</code></strong> : <code>str</code>, optional</dt> +<dd>Color to use for edges, by default 'black'</dd> +<dt><strong><code>camera_position</code></strong> : <code>list</code> or <code>string</code>, optional</dt> +<dd>Camera position to use, by default 'xz'</dd> +<dt><strong><code>window_size</code></strong> : <code>list</code>, optional</dt> +<dd>Window size to use for GIF, by default [3000, 4000]</dd> +<dt><strong><code>background_color</code></strong> : <code>str</code>, optional</dt> +<dd>Background color to use, by default 'white'</dd> +<dt><strong><code>path_save</code></strong> : <code>str</code>, optional</dt> +<dd>Path to save GIF, by default '~/Downloads/ssm.gif'</dd> +</dl> +<h2 id="attributes">Attributes</h2> +<dl> +<dt><strong><code>_plotter</code></strong> : <code>pyvista.Plotter</code></dt> +<dd>Plotter to use for plotting.</dd> +<dt><strong><code>_color</code></strong> : <code>str</code></dt> +<dd>Color to use for object.</dd> +<dt><strong><code>_show_edges</code></strong> : <code>bool</code></dt> +<dd>Whether to show edges on mesh.</dd> +<dt><strong><code>_edge_color</code></strong> : <code>str</code></dt> +<dd>Color to use for edges.</dd> +<dt><strong><code>_camera_position</code></strong> : <code>list</code> or <code>string</code></dt> +<dd>Camera position to use.</dd> +<dt><strong><code>_window_size</code></strong> : <code>list</code></dt> +<dd>Window size to use for GIF.</dd> +<dt><strong><code>_background_color</code></strong> : <code>str</code></dt> +<dd>Background color to use.</dd> +<dt><strong><code>_path_save</code></strong> : <code>str</code></dt> +<dd>Path to save GIF.</dd> +</dl> +<h2 id="methods">Methods</h2> +<p>add_mesh_frame(mesh) +Add a mesh to the GIF. +update_view() +Update the view of the plotter. +done() +Close the plotter.</p> +<p>Initialize the GIF class.</p> +<h2 id="parameters_1">Parameters</h2> +<dl> +<dt><strong><code>plotter</code></strong> : <code>pyvista.Plotter</code>, optional</dt> +<dd>Plotter to use for plotting, by default None</dd> +<dt><strong><code>color</code></strong> : <code>str</code>, optional</dt> +<dd>Color to use for object, by default 'orange'</dd> +<dt><strong><code>show_edges</code></strong> : <code>bool</code>, optional</dt> +<dd>Whether to show edges on mesh, by default True</dd> +<dt><strong><code>edge_color</code></strong> : <code>str</code>, optional</dt> +<dd>Color to use for edges, by default 'black'</dd> +<dt><strong><code>camera_position</code></strong> : <code>list</code> or <code>string</code>, optional</dt> +<dd>Camera position to use, by default 'xz'</dd> +<dt><strong><code>window_size</code></strong> : <code>list</code>, optional</dt> +<dd>Window size to use for GIF, by default [3000, 4000]</dd> +<dt><strong><code>background_color</code></strong> : <code>str</code>, optional</dt> +<dd>Background color to use, by default 'white'</dd> +<dt><strong><code>path_save</code></strong> : <code>str</code>, optional</dt> +<dd>Path to save GIF, by default '~/Downloads/ssm.gif'</dd> +</dl></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">class GIF: + """ + Class for generating GIF of surface meshes. + + Parameters + ---------- + plotter : pyvista.Plotter + Plotter to use for plotting. + color: str, optional + Color to use for object, by default 'orange' + show_edges: bool, optional + Whether to show edges on mesh, by default True + edge_color: str, optional + Color to use for edges, by default 'black' + camera_position: list or string, optional + Camera position to use, by default 'xz' + window_size: list, optional + Window size to use for GIF, by default [3000, 4000] + background_color: str, optional + Background color to use, by default 'white' + path_save: str, optional + Path to save GIF, by default '~/Downloads/ssm.gif' + + Attributes + ---------- + _plotter : pyvista.Plotter + Plotter to use for plotting. + _color : str + Color to use for object. + _show_edges : bool + Whether to show edges on mesh. + _edge_color : str + Color to use for edges. + _camera_position : list or string + Camera position to use. + _window_size : list + Window size to use for GIF. + _background_color : str + Background color to use. + _path_save : str + Path to save GIF. + + Methods + ------- + add_mesh_frame(mesh) + Add a mesh to the GIF. + update_view() + Update the view of the plotter. + done() + Close the plotter. + + + """ + def __init__( + self, + plotter=None, + color='orange', + show_edges=True, + edge_color='black', + camera_position='xz', + window_size=[3000, 4000], + background_color='white', + path_save='~/Downloads/ssm.gif' + ): + """ + Initialize the GIF class. + + Parameters + ---------- + plotter : pyvista.Plotter, optional + Plotter to use for plotting, by default None + color: str, optional + Color to use for object, by default 'orange' + show_edges: bool, optional + Whether to show edges on mesh, by default True + edge_color: str, optional + Color to use for edges, by default 'black' + camera_position: list or string, optional + Camera position to use, by default 'xz' + window_size: list, optional + Window size to use for GIF, by default [3000, 4000] + background_color: str, optional + Background color to use, by default 'white' + path_save: str, optional + Path to save GIF, by default '~/Downloads/ssm.gif' + + """ + if plotter is None: + self._plotter = pv.Plotter(notebook=False, off_screen=True) + else: + self._plotter = plotter + + if path_save[-3:] != 'gif': + raise Exception('path must be to a file ending with suffix `.gif`') + + self.counter = 0 + + self._plotter.open_gif(path_save) + + self._color = color + self._show_edges = show_edges + self._edge_color = edge_color + self._camera_position = camera_position + self._window_size = window_size + self._background_color = background_color + self._path_save = path_save + + def update_view( + self + ): + self._plotter.camera_position = self._camera_position + self._plotter.window_size = self._window_size + self._plotter.set_background(color=self._background_color) + + def add_mesh_frame(self, mesh): + if type(mesh) in (list, tuple): + actors = [] + for mesh_ in mesh: + actors.append(self._plotter.add_mesh( + mesh_, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + )) + else: + actor = self._plotter.add_mesh( + mesh, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + ) + + if self.counter == 0: + self.update_view() + self._plotter.write_frame() + + if type(mesh) in (list, tuple): + for actor in actors: + self._plotter.remove_actor(actor) + else: + self._plotter.remove_actor(actor) + self.counter += 1 + + def done(self): + self._plotter.close() + + @property + def color(self): + return self._color + + @color.setter + def color(self, color): + self._color = color + + @property + def show_edges(self): + return self._show_edges + + @show_edges.setter + def show_edges(self, show_edges): + self._show_edges = show_edges + + @property + def edge_color(self): + return self._edge_color + + @edge_color.setter + def edge_color(self, edge_color): + self._edge_color = edge_color + + @property + def camera_position(self): + return self._camera_position + + @camera_position.setter + def camera_position(self, camera_position): + self._camera_position = camera_position + + @property + def window_size(self): + return self._window_size + + @window_size.setter + def window_size(self, window_size): + self._window_size = window_size + + @property + def background_color(self): + return self._background_color + + @background_color.setter + def background_color(self, background_color): + self._background_color = background_color + + @property + def path_save(self): + return self._path_save</code></pre> +</details> +<h3>Instance variables</h3> +<dl> +<dt id="pymskt.mesh.utils.GIF.background_color"><code class="name">var <span class="ident">background_color</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def background_color(self): + return self._background_color</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.camera_position"><code class="name">var <span class="ident">camera_position</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def camera_position(self): + return self._camera_position</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.color"><code class="name">var <span class="ident">color</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def color(self): + return self._color</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.edge_color"><code class="name">var <span class="ident">edge_color</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def edge_color(self): + return self._edge_color</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.path_save"><code class="name">var <span class="ident">path_save</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def path_save(self): + return self._path_save</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.show_edges"><code class="name">var <span class="ident">show_edges</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def show_edges(self): + return self._show_edges</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.window_size"><code class="name">var <span class="ident">window_size</span></code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">@property +def window_size(self): + return self._window_size</code></pre> +</details> +</dd> +</dl> +<h3>Methods</h3> +<dl> +<dt id="pymskt.mesh.utils.GIF.add_mesh_frame"><code class="name flex"> +<span>def <span class="ident">add_mesh_frame</span></span>(<span>self, mesh)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def add_mesh_frame(self, mesh): + if type(mesh) in (list, tuple): + actors = [] + for mesh_ in mesh: + actors.append(self._plotter.add_mesh( + mesh_, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + )) + else: + actor = self._plotter.add_mesh( + mesh, + render=False, + color=self._color, + edge_color=self._edge_color, + show_edges=self._show_edges + ) + + if self.counter == 0: + self.update_view() + self._plotter.write_frame() + + if type(mesh) in (list, tuple): + for actor in actors: + self._plotter.remove_actor(actor) + else: + self._plotter.remove_actor(actor) + self.counter += 1</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.done"><code class="name flex"> +<span>def <span class="ident">done</span></span>(<span>self)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def done(self): + self._plotter.close()</code></pre> +</details> +</dd> +<dt id="pymskt.mesh.utils.GIF.update_view"><code class="name flex"> +<span>def <span class="ident">update_view</span></span>(<span>self)</span> +</code></dt> +<dd> +<div class="desc"></div> +<details class="source"> +<summary> +<span>Expand source code</span> +</summary> +<pre><code class="python">def update_view( + self +): + self._plotter.camera_position = self._camera_position + self._plotter.window_size = self._window_size + self._plotter.set_background(color=self._background_color)</code></pre> +</details> +</dd> +</dl> +</dd> +</dl> +</section> +</article> +<nav id="sidebar"> +<h1>Index</h1> +<div class="toc"> +<ul></ul> +</div> +<ul id="index"> +<li><h3>Super-module</h3> +<ul> +<li><code><a title="pymskt.mesh" href="index.html">pymskt.mesh</a></code></li> +</ul> +</li> +<li><h3><a href="#header-functions">Functions</a></h3> +<ul class=""> +<li><code><a title="pymskt.mesh.utils.estimate_mesh_scalars_FWHMs" href="#pymskt.mesh.utils.estimate_mesh_scalars_FWHMs">estimate_mesh_scalars_FWHMs</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_arrow" href="#pymskt.mesh.utils.get_arrow">get_arrow</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_intersect" href="#pymskt.mesh.utils.get_intersect">get_intersect</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_obb_surface" href="#pymskt.mesh.utils.get_obb_surface">get_obb_surface</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_surface_distance" href="#pymskt.mesh.utils.get_surface_distance">get_surface_distance</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_surface_normals" href="#pymskt.mesh.utils.get_surface_normals">get_surface_normals</a></code></li> +<li><code><a title="pymskt.mesh.utils.get_symmetric_surface_distance" href="#pymskt.mesh.utils.get_symmetric_surface_distance">get_symmetric_surface_distance</a></code></li> +<li><code><a title="pymskt.mesh.utils.is_hit" href="#pymskt.mesh.utils.is_hit">is_hit</a></code></li> +<li><code><a title="pymskt.mesh.utils.vtk_deep_copy" href="#pymskt.mesh.utils.vtk_deep_copy">vtk_deep_copy</a></code></li> +</ul> +</li> +<li><h3><a href="#header-classes">Classes</a></h3> +<ul> +<li> +<h4><code><a title="pymskt.mesh.utils.GIF" href="#pymskt.mesh.utils.GIF">GIF</a></code></h4> +<ul class="two-column"> +<li><code><a title="pymskt.mesh.utils.GIF.add_mesh_frame" href="#pymskt.mesh.utils.GIF.add_mesh_frame">add_mesh_frame</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.background_color" href="#pymskt.mesh.utils.GIF.background_color">background_color</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.camera_position" href="#pymskt.mesh.utils.GIF.camera_position">camera_position</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.color" href="#pymskt.mesh.utils.GIF.color">color</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.done" href="#pymskt.mesh.utils.GIF.done">done</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.edge_color" href="#pymskt.mesh.utils.GIF.edge_color">edge_color</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.path_save" href="#pymskt.mesh.utils.GIF.path_save">path_save</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.show_edges" href="#pymskt.mesh.utils.GIF.show_edges">show_edges</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.update_view" href="#pymskt.mesh.utils.GIF.update_view">update_view</a></code></li> +<li><code><a title="pymskt.mesh.utils.GIF.window_size" href="#pymskt.mesh.utils.GIF.window_size">window_size</a></code></li> +</ul> +</li> +</ul> +</li> +</ul> +</nav> +</main> +<footer id="footer"> +<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p> +</footer> +</body> +</html> \ No newline at end of file