Skip to content

Commit 94f2fbc

Browse files
jcfraronhelser
andcommitted
ENH: Add trame example application for cleaving mesh
This commit adds a trame based application allowing the user to select input files (.nrrd, .vti) and cleave the meshes. Co-authored-by: Aron Helser <aron.helser@kitware.com>
1 parent 462f17c commit 94f2fbc

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed

Examples/trame/app.py

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import os
2+
import tempfile
3+
4+
from trame import state
5+
from trame.layouts import SinglePageWithDrawer
6+
from trame.html import vtk, vuetify
7+
8+
from vtkmodules.vtkFiltersSources import vtkConeSource
9+
from vtkmodules.vtkRenderingCore import (
10+
vtkActor,
11+
vtkPolyDataMapper,
12+
vtkRenderer,
13+
vtkRenderWindow,
14+
vtkRenderWindowInteractor,
15+
)
16+
17+
# Required for interactor initialization
18+
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa
19+
20+
# Required for rendering initialization, not necessary for
21+
# local rendering, but doesn't hurt to include it
22+
import vtkmodules.vtkRenderingOpenGL2 # noqa
23+
24+
# vtk
25+
import vtk as vtkpackage
26+
27+
# vtkCleaver
28+
try:
29+
from vtkCleaver import vtkCleaverImageToUnstructuredGridFilter
30+
except ImportError:
31+
from vtk import vtkCleaverImageToUnstructuredGridFilter
32+
33+
34+
# -----------------------------------------------------------------------------
35+
# Constants
36+
# -----------------------------------------------------------------------------
37+
38+
DEFAULT_SAMPLING_RATE = 1.0
39+
DEFAULT_FEATURE_SCALING = 1.0
40+
DEFAULT_RATE_OF_CHANGE = 0.2
41+
42+
43+
# -----------------------------------------------------------------------------
44+
# VTK pipeline
45+
# -----------------------------------------------------------------------------
46+
47+
renderer = vtkRenderer()
48+
render_window = vtkRenderWindow()
49+
render_window.AddRenderer(renderer)
50+
51+
render_window_interactor = vtkRenderWindowInteractor()
52+
render_window_interactor.SetRenderWindow(render_window)
53+
render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
54+
55+
cleaver_mesher = vtkCleaverImageToUnstructuredGridFilter()
56+
57+
# remove the background mesh with label 0
58+
threshold = vtkpackage.vtkThreshold()
59+
threshold.SetInputConnection(cleaver_mesher.GetOutputPort())
60+
threshold.SetThresholdFunction(threshold.THRESHOLD_UPPER)
61+
threshold.SetUpperThreshold(0.99)
62+
63+
sF = vtkpackage.vtkDataSetSurfaceFilter()
64+
sF.SetInputConnection(threshold.GetOutputPort())
65+
66+
mapper = vtkpackage.vtkPolyDataMapper()
67+
mapper.SetInputConnection(sF.GetOutputPort())
68+
mapper.ScalarVisibilityOn()
69+
mapper.SetScalarModeToUseCellData()
70+
mapper.SetColorModeToMapScalars()
71+
72+
actor = vtkActor()
73+
actor.GetProperty().SetEdgeVisibility(True)
74+
actor.SetMapper(mapper)
75+
76+
xmlReader = vtkpackage.vtkXMLImageDataReader()
77+
nrrdReader = vtkpackage.vtkNrrdReader()
78+
79+
input_images = {}
80+
81+
state.cleaver_running = False
82+
state.input_available = False
83+
state.input_file_names = []
84+
85+
# -----------------------------------------------------------------------------
86+
# Functions
87+
# -----------------------------------------------------------------------------
88+
89+
@state.change("files")
90+
def load_client_files(files, **kwargs):
91+
92+
input_images.clear()
93+
94+
if files and len(files):
95+
if not files[0].get("content"):
96+
return
97+
for file in files:
98+
fileName = file.get("name")
99+
print(f"Load {fileName}")
100+
ext = os.path.splitext(fileName)[1]
101+
reader = xmlReader
102+
if ext == ".nrrd":
103+
reader = nrrdReader
104+
105+
with tempfile.NamedTemporaryFile(suffix=ext) as fp:
106+
fp.write(file.get("content"))
107+
fp.seek(0)
108+
109+
reader.SetFileName(fp.name)
110+
reader.Update()
111+
112+
img = vtkpackage.vtkImageData()
113+
img.ShallowCopy(reader.GetOutput())
114+
115+
input_images[fileName] = img
116+
117+
state.input_file_names = list(input_images.keys())
118+
119+
120+
def cleave_inputs():
121+
state.cleaver_running = True
122+
123+
cleaver_mesher.RemoveAllInputs()
124+
renderer.RemoveActor(actor)
125+
126+
for fileName in state.input_file_names:
127+
cleaver_mesher.AddInputData(0, input_images[fileName])
128+
129+
print(state.rate_of_change)
130+
131+
cleaver_mesher.SetSamplingRate(state.sampling_rate)
132+
cleaver_mesher.SetFeatureScaling(state.feature_scaling)
133+
cleaver_mesher.SetRateOfChange(state.rate_of_change)
134+
cleaver_mesher.Update()
135+
136+
ugrid = cleaver_mesher.GetOutput()
137+
mapper.SetScalarRange(ugrid.GetCellData().GetScalars().GetRange())
138+
139+
renderer.AddActor(actor)
140+
renderer.ResetCamera()
141+
142+
state.cleaver_running = False
143+
144+
html_view.update()
145+
146+
147+
@state.change("input_file_names")
148+
def update_input_available(input_file_names, **kwargs):
149+
state.input_available = len(input_file_names) > 0
150+
print(f"update_input_available: {state.input_available}")
151+
152+
153+
@state.change("sampling_rate")
154+
def update_sampling_rate(sampling_rate, **kwargs):
155+
print(f"sampling_rate: {sampling_rate}")
156+
157+
158+
def reset_sampling_rate():
159+
state.sampling_rate = DEFAULT_SAMPLING_RATE
160+
161+
162+
@state.change("feature_scaling")
163+
def update_feature_scaling(feature_scaling, **kwargs):
164+
print(f"feature_scaling: {feature_scaling}")
165+
166+
167+
def reset_feature_scaling():
168+
state.feature_scaling = DEFAULT_FEATURE_SCALING
169+
170+
171+
@state.change("rate_of_change")
172+
def update_rate_of_change(rate_of_change, **kwargs):
173+
print(f"rate_of_change: {rate_of_change}")
174+
175+
176+
def reset_rate_of_change():
177+
state.rate_of_change = DEFAULT_RATE_OF_CHANGE
178+
179+
180+
# -----------------------------------------------------------------------------
181+
# GUI Cards
182+
# -----------------------------------------------------------------------------
183+
184+
compact_style = {
185+
"hide_details": True,
186+
"dense": True,
187+
}
188+
189+
190+
def ui_card(title, ui_name):
191+
card_style = {}
192+
title_style = {
193+
"classes": "grey lighten-1 py-1 grey--text text--darken-3",
194+
"style": "user-select: none; cursor: pointer",
195+
**compact_style,
196+
}
197+
content_style = {"classes": "py-2"}
198+
with vuetify.VCard(**card_style):
199+
vuetify.VCardTitle(title, **title_style)
200+
content = vuetify.VCardText(**content_style)
201+
202+
return content
203+
204+
def input_card():
205+
with ui_card("Input", "input"):
206+
vuetify.VFileInput(
207+
multiple=True,
208+
show_size=True,
209+
small_chips=True,
210+
truncate_length=25,
211+
v_model=("files", None),
212+
dense=True,
213+
hide_details=True,
214+
style="max-width: 300px;",
215+
accept=".nrrd,.vti",
216+
__properties=["accept"],
217+
)
218+
219+
# TODO Add a component allowing to re-order "state.input_file_names"
220+
# Possible approaches:
221+
# - v-simple-table + SortableJS
222+
# See https://codepen.io/mykysyk/pen/qBdBRMB
223+
# - v-chip + draggable
224+
# See https://github.com/vuetifyjs/vuetify/issues/11614 and https://jsfiddle.net/cjrqzkf0/
225+
226+
227+
def sizing_field_row(**kwargs):
228+
with vuetify.VRow():
229+
vuetify.VSlider(
230+
**kwargs,
231+
thumb_label=True,
232+
classes="my-1",
233+
**compact_style,
234+
)
235+
with vuetify.VBtn(icon=True, click=reset_sampling_rate, **compact_style):
236+
vuetify.VIcon("mdi-restore")
237+
238+
239+
def sizing_field_card():
240+
with ui_card("Sizing Field", "sizing-field"):
241+
sizing_field_row(
242+
v_model=("sampling_rate", DEFAULT_SAMPLING_RATE),
243+
min=0.01,
244+
max=99.9,
245+
step=1.0,
246+
label="Sampling Rate"
247+
)
248+
sizing_field_row(
249+
v_model=("feature_scaling", DEFAULT_FEATURE_SCALING),
250+
min=0.0001,
251+
max=9999.0,
252+
step=0.0001,
253+
label="Feature Scaling"
254+
)
255+
sizing_field_row(
256+
v_model=("rate_of_change", DEFAULT_RATE_OF_CHANGE),
257+
min=0.0001,
258+
max=10.0,
259+
step=0.0001,
260+
label="Rate of Change (Lipschitz)"
261+
)
262+
263+
264+
def cleaving_tool_card():
265+
with ui_card("Cleaving Tool", "cleaving-tool"):
266+
with vuetify.VRow(justify="center", **compact_style):
267+
vuetify.VBtn(
268+
"Cleave Mesh",
269+
disabled=("!input_available || cleaver_running",),
270+
loading=("cleaver_running", True),
271+
click=cleave_inputs,
272+
**compact_style,
273+
)
274+
275+
# -----------------------------------------------------------------------------
276+
# GUI
277+
# -----------------------------------------------------------------------------
278+
279+
html_view = vtk.VtkLocalView(render_window)
280+
281+
layout = SinglePageWithDrawer("Cleaver", on_ready=html_view.update)
282+
layout.title.set_text("Cleaver")
283+
284+
with layout.drawer as drawer:
285+
drawer.width = 325
286+
vuetify.VDivider()
287+
input_card()
288+
vuetify.VDivider()
289+
sizing_field_card()
290+
vuetify.VDivider()
291+
cleaving_tool_card()
292+
293+
with layout.content:
294+
vuetify.VContainer(
295+
fluid=True,
296+
classes="pa-0 fill-height",
297+
children=[html_view],
298+
)
299+
300+
# -----------------------------------------------------------------------------
301+
# Main
302+
# -----------------------------------------------------------------------------
303+
304+
if __name__ == "__main__":
305+
layout.start()

0 commit comments

Comments
 (0)