Skip to content

ENH: Add trame example application for cleaving mesh #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 305 additions & 0 deletions Examples/trame/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
import os
import tempfile

from trame import state
from trame.layouts import SinglePageWithDrawer
from trame.html import vtk, vuetify

from vtkmodules.vtkFiltersSources import vtkConeSource
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper,
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you import vtk as vtkpackage you might want to simply use it rather than using vtkmodules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, we will revisit.

Background

Since I initially thought I add to discover the relevant modules associated with each classes, in the interest of time it was easier to import from vtkpackage, especially considering I was adapting a script written by @aronhelser.


# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2 # noqa

# vtk
import vtk as vtkpackage

# vtkCleaver
try:
from vtkCleaver import vtkCleaverImageToUnstructuredGridFilter
except ImportError:
from vtk import vtkCleaverImageToUnstructuredGridFilter


# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------

DEFAULT_SAMPLING_RATE = 1.0
DEFAULT_FEATURE_SCALING = 1.0
DEFAULT_RATE_OF_CHANGE = 0.2


# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

renderer = vtkRenderer()
render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)

render_window_interactor = vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

cleaver_mesher = vtkCleaverImageToUnstructuredGridFilter()

# remove the background mesh with label 0
threshold = vtkpackage.vtkThreshold()
threshold.SetInputConnection(cleaver_mesher.GetOutputPort())
threshold.SetThresholdFunction(threshold.THRESHOLD_UPPER)
threshold.SetUpperThreshold(0.99)

sF = vtkpackage.vtkDataSetSurfaceFilter()
sF.SetInputConnection(threshold.GetOutputPort())

mapper = vtkpackage.vtkPolyDataMapper()
mapper.SetInputConnection(sF.GetOutputPort())
mapper.ScalarVisibilityOn()
mapper.SetScalarModeToUseCellData()
mapper.SetColorModeToMapScalars()

actor = vtkActor()
actor.GetProperty().SetEdgeVisibility(True)
actor.SetMapper(mapper)

xmlReader = vtkpackage.vtkXMLImageDataReader()
nrrdReader = vtkpackage.vtkNrrdReader()

input_images = {}

state.cleaver_running = False
state.input_available = False
state.input_file_names = []

# -----------------------------------------------------------------------------
# Functions
# -----------------------------------------------------------------------------

@state.change("files")
def load_client_files(files, **kwargs):

input_images.clear()

if files and len(files):
if not files[0].get("content"):
return
for file in files:
fileName = file.get("name")
print(f"Load {fileName}")
ext = os.path.splitext(fileName)[1]
reader = xmlReader
if ext == ".nrrd":
reader = nrrdReader

with tempfile.NamedTemporaryFile(suffix=ext) as fp:
fp.write(file.get("content"))
fp.seek(0)

reader.SetFileName(fp.name)
reader.Update()

img = vtkpackage.vtkImageData()
img.ShallowCopy(reader.GetOutput())

input_images[fileName] = img

state.input_file_names = list(input_images.keys())


def cleave_inputs():
state.cleaver_running = True
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where I updated the cleaver_running state


cleaver_mesher.RemoveAllInputs()
renderer.RemoveActor(actor)

for fileName in state.input_file_names:
cleaver_mesher.AddInputData(0, input_images[fileName])

print(state.rate_of_change)

cleaver_mesher.SetSamplingRate(state.sampling_rate)
cleaver_mesher.SetFeatureScaling(state.feature_scaling)
cleaver_mesher.SetRateOfChange(state.rate_of_change)
cleaver_mesher.Update()

ugrid = cleaver_mesher.GetOutput()
mapper.SetScalarRange(ugrid.GetCellData().GetScalars().GetRange())

renderer.AddActor(actor)
renderer.ResetCamera()

state.cleaver_running = False

html_view.update()


@state.change("input_file_names")
def update_input_available(input_file_names, **kwargs):
state.input_available = len(input_file_names) > 0
print(f"update_input_available: {state.input_available}")


@state.change("sampling_rate")
def update_sampling_rate(sampling_rate, **kwargs):
print(f"sampling_rate: {sampling_rate}")


def reset_sampling_rate():
state.sampling_rate = DEFAULT_SAMPLING_RATE


@state.change("feature_scaling")
def update_feature_scaling(feature_scaling, **kwargs):
print(f"feature_scaling: {feature_scaling}")


def reset_feature_scaling():
state.feature_scaling = DEFAULT_FEATURE_SCALING


@state.change("rate_of_change")
def update_rate_of_change(rate_of_change, **kwargs):
print(f"rate_of_change: {rate_of_change}")


def reset_rate_of_change():
state.rate_of_change = DEFAULT_RATE_OF_CHANGE


# -----------------------------------------------------------------------------
# GUI Cards
# -----------------------------------------------------------------------------

compact_style = {
"hide_details": True,
"dense": True,
}


def ui_card(title, ui_name):
card_style = {}
title_style = {
"classes": "grey lighten-1 py-1 grey--text text--darken-3",
"style": "user-select: none; cursor: pointer",
**compact_style,
}
content_style = {"classes": "py-2"}
with vuetify.VCard(**card_style):
vuetify.VCardTitle(title, **title_style)
content = vuetify.VCardText(**content_style)

return content

def input_card():
with ui_card("Input", "input"):
vuetify.VFileInput(
multiple=True,
show_size=True,
small_chips=True,
truncate_length=25,
v_model=("files", None),
dense=True,
hide_details=True,
style="max-width: 300px;",
accept=".nrrd,.vti",
__properties=["accept"],
)

# TODO Add a component allowing to re-order "state.input_file_names"
# Possible approaches:
# - v-simple-table + SortableJS
# See https://codepen.io/mykysyk/pen/qBdBRMB
# - v-chip + draggable
# See https://github.com/vuetifyjs/vuetify/issues/11614 and https://jsfiddle.net/cjrqzkf0/
Comment on lines +219 to +224
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jourdain I would like the ability to re-order the input. Is there a component that would allow me to do so ?

Something like this where I could drag and drop to re-order:

image

These are two approaches:

Do you have any recommendation ? Should I directly use functionality from pywebvue to include sortable.js ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No specific recommendation and in your code base you can use enable_module to serve some JS files and enable them in vue.js. Let me know if you need help in that JS integration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use enable_module to serve some JS files and enable them in vue.js.

Do you happen to have an example handy ?

Copy link

@jourdain jourdain Apr 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



def sizing_field_row(**kwargs):
with vuetify.VRow():
vuetify.VSlider(
**kwargs,
thumb_label=True,
classes="my-1",
**compact_style,
)
with vuetify.VBtn(icon=True, click=reset_sampling_rate, **compact_style):
vuetify.VIcon("mdi-restore")


def sizing_field_card():
with ui_card("Sizing Field", "sizing-field"):
sizing_field_row(
v_model=("sampling_rate", DEFAULT_SAMPLING_RATE),
min=0.01,
max=99.9,
step=1.0,
label="Sampling Rate"
)
sizing_field_row(
v_model=("feature_scaling", DEFAULT_FEATURE_SCALING),
min=0.0001,
max=9999.0,
step=0.0001,
label="Feature Scaling"
)
sizing_field_row(
v_model=("rate_of_change", DEFAULT_RATE_OF_CHANGE),
min=0.0001,
max=10.0,
step=0.0001,
label="Rate of Change (Lipschitz)"
)


def cleaving_tool_card():
with ui_card("Cleaving Tool", "cleaving-tool"):
with vuetify.VRow(justify="center", **compact_style):
vuetify.VBtn(
"Cleave Mesh",
disabled=("!input_available || cleaver_running",),
loading=("cleaver_running", True),
Comment on lines +269 to +270
Copy link
Contributor Author

@jcfr jcfr Apr 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state of the button is not updated, I was not able to "force" the state to be updated on the client.

At the following location (See https://github.com/SCIInstitute/VTKCleaver/pull/3/files#r850536393), I tried to add the following without success ...

state.flush("cleaver_running")

@jourdain Would appreciate any suggestion.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably because your server is busy working. You need to make it async and run the C++ exec in a task so you can flush the state before your work is done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I was thinking, I will then apply the approach used in https://github.com/Kitware/trame-mnist/blob/master/trame_mnist/app/engine/main.py by using ProcessPoolExecutor ...

I naively thought updating the state object would be sufficient ...

Would it make sense to have the management of state run in a dedicated process by default?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a tricky decision, but I have been thinking of it and I'm not sure yet on how to properly do it. BTW, you do not need ProcessPoolExecutor. Just a task could be enough.

click=cleave_inputs,
**compact_style,
)

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

html_view = vtk.VtkLocalView(render_window)

layout = SinglePageWithDrawer("Cleaver", on_ready=html_view.update)
layout.title.set_text("Cleaver")

with layout.drawer as drawer:
drawer.width = 325
vuetify.VDivider()
input_card()
vuetify.VDivider()
sizing_field_card()
vuetify.VDivider()
cleaving_tool_card()

with layout.content:
vuetify.VContainer(
fluid=True,
classes="pa-0 fill-height",
children=[html_view],
)

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
layout.start()