Skip to content

Commit 55f18a1

Browse files
bourdaisjjourdain
authored andcommitted
doc(Handler): add usage examples and note about docker usage
1 parent dfa0b05 commit 55f18a1

13 files changed

Lines changed: 702 additions & 0 deletions

examples/handler/01_start_here.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
Trame example that checks an input DICOM file patient tag against a patient name in the trame state.
3+
"""
4+
5+
from pathlib import Path
6+
from typing import Literal
7+
8+
from trame.app import TrameApp
9+
from trame.ui.vuetify3 import SinglePageLayout
10+
11+
from trame.widgets import client, vuetify3, html
12+
13+
# 1 - lookup the path of your JS file that contains your preprocessing logic
14+
DICOM_UTILS_SCRIPT = Path(__file__).with_name("dicom_utils.js")
15+
16+
17+
class PatientNameValidator(TrameApp):
18+
def __init__(self, server=None):
19+
super().__init__(server)
20+
21+
self.state.patient_name = "John Doe"
22+
23+
# 2 - register your JS file and entrypoint function using trame_client.Handler module API
24+
client.register_external_script(
25+
name="validate_dicom_patient_name",
26+
script_file_path=DICOM_UTILS_SCRIPT,
27+
function_names=["validateDicomFilePatientName"],
28+
)
29+
30+
self._build_ui()
31+
32+
def _build_ui(self):
33+
with SinglePageLayout(self.server) as self.ui:
34+
self.ui.title.set_text("Client Handler Preprocessing Minimal Example")
35+
36+
with (
37+
self.ui.content,
38+
vuetify3.VContainer(fluid=True),
39+
vuetify3.VRow(),
40+
vuetify3.VCol(cols=3),
41+
# 3. Instantiate the PreProcessor widget
42+
client.Handler(
43+
function="validate_dicom_patient_name",
44+
# 4. Pass additional inputs to your preprocessing logic
45+
# those must match your function signature in the JS logic file
46+
# this is a JS expression that will be passed as is to your function
47+
inputs=("{ patient_name }",),
48+
# 5. Register a callback to be run when your preprocessing is completed
49+
completed=(
50+
self.preprocessing_pipeline_completed,
51+
"[$event.type, $event.outputs]",
52+
),
53+
) as client_handler,
54+
):
55+
with html.Div(style="display: flex; flex-direction: column;"):
56+
vuetify3.VFileInput(
57+
# 6. Use the provided input_data from the widget to trigger the preprocessing on it
58+
# preprocessing.input_data is a provided slot scopped reactive value
59+
# which is observed by the PreProcessor component
60+
# it should be assigned with whatever data your want to run preprocessing on
61+
v_model=client_handler.input_data,
62+
hint="Input here a DICOM file",
63+
label="DICOM file",
64+
persistent_hint=True,
65+
)
66+
67+
vuetify3.VTextField(
68+
v_model="patient_name",
69+
hint="Input here the patient name that you expect for your DICOM file",
70+
label="Expected Patient Name",
71+
persistent_hint=True,
72+
)
73+
74+
vuetify3.VAlert(
75+
v_if="alert_title",
76+
text=("alert_text", None),
77+
title=("alert_title", None),
78+
type=("alert_type", None),
79+
)
80+
81+
def preprocessing_pipeline_completed(
82+
self, type: Literal["success", "failure"], output
83+
) -> None:
84+
print("preprocessing_pipeline_completed")
85+
print(type)
86+
print(output)
87+
88+
self.state.alert_type = type if type == "success" else "error"
89+
self.state.alert_title = self.state.alert_type
90+
91+
if type == "failure":
92+
self.state.alert_text = (
93+
f"Expected patient {output[0]['expected']}, got {output[0]['got']}"
94+
)
95+
else:
96+
self.state.alert_text = "Patient name matched! We got the file."
97+
98+
99+
def main(**kwargs):
100+
app = PatientNameValidator()
101+
app.server.start(**kwargs)
102+
103+
104+
if __name__ == "__main__":
105+
main()
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from pathlib import Path
2+
3+
from trame.app import TrameApp
4+
from trame.app.file_upload import ClientFile
5+
from trame.ui.vuetify3 import SinglePageLayout
6+
7+
from trame.widgets import client, html, vuetify3
8+
9+
DICOM_UTILS_SCRIPT = Path(__file__).with_name("dicom_utils.js")
10+
11+
12+
class DicomToNiftiConvertor(TrameApp):
13+
def __init__(self, server=None):
14+
super().__init__(server)
15+
16+
self.state.dicom_file_set = None
17+
self.state.feedback_message = ""
18+
self.state.is_error = False
19+
self.state.is_success = False
20+
21+
# Mark your state as client_only!
22+
self.state.client_only("dicom_file_set")
23+
24+
client.register_external_script(
25+
name="generate_nii_from_dcm",
26+
script_file_path=DICOM_UTILS_SCRIPT,
27+
function_names=["generateNIFTIFromDicomFileSet"],
28+
)
29+
30+
self._build_ui()
31+
32+
def _build_ui(self):
33+
with SinglePageLayout(self.server) as self.ui:
34+
self.ui.title.set_text("Explicit Pipeline Trigger")
35+
36+
with (
37+
self.ui.content,
38+
vuetify3.VContainer(fluid=True),
39+
vuetify3.VRow(),
40+
vuetify3.VCol(cols=3),
41+
):
42+
with vuetify3.VCard(flat=True):
43+
with (
44+
vuetify3.VSnackbar(
45+
model_value=("is_error || is_success",),
46+
color=("is_error ? 'error' : 'success'",),
47+
location="top left",
48+
close_on_content_click=True,
49+
timeout=6000,
50+
update_modelValue="is_error = $event; is_success = $event;",
51+
),
52+
html.Div(style="align-items: center;"),
53+
):
54+
vuetify3.VIcon("mdi-information", style="margin-right: 12px")
55+
html.Span("{{ feedback_message }}")
56+
57+
vuetify3.VFileInput(
58+
v_model="dicom_file_set",
59+
multiple=True,
60+
label="DICOM files",
61+
hint="Input here a set of DICOM files that will be converted to a single .nii file",
62+
persistent_hint=True,
63+
)
64+
65+
with client.Handler(
66+
function="generate_nii_from_dcm",
67+
inputs=("{ output_file_name: 'my_converted_file.nii' }",),
68+
# success event for the logic happy path
69+
success=(self.save_nifti_file, "[$event]"),
70+
# failure event for the logic error path (e.g. invalid data)
71+
failure=(self.handle_nifti_convertion_error, "[$event]"),
72+
# error event for any exception raised during logic execution
73+
error=(self.handle_nifti_convertion_error, "[$event]"),
74+
) as client_handler:
75+
# trigger the preprocessing on our dicom_file_set state key
76+
vuetify3.VBtn(
77+
"Convert DICOMs to NIFTI file",
78+
color="primary",
79+
click=client_handler.run("dicom_file_set"),
80+
)
81+
82+
def save_nifti_file(self, raw_nifti_file):
83+
nifti_file = ClientFile(raw_nifti_file[0])
84+
print("got a NIFTI file")
85+
86+
print(nifti_file.info)
87+
print(nifti_file.name)
88+
89+
self.state.feedback_message = "NIFTI file successfully generated"
90+
self.state.is_success = True
91+
self.state.is_error = False
92+
93+
file_path = (Path("outputs") / nifti_file.name).resolve()
94+
file_path.parent.mkdir(parents=True, exist_ok=True)
95+
96+
if file_path.exists():
97+
file_path.unlink()
98+
99+
with file_path.open(mode="xb") as file:
100+
file.write(nifti_file.content)
101+
102+
def handle_nifti_convertion_error(self, error):
103+
print(error)
104+
self.state.feedback_message = error
105+
self.state.is_error = True
106+
107+
108+
def main(**kwargs):
109+
app = DicomToNiftiConvertor()
110+
app.server.start(**kwargs)
111+
112+
113+
if __name__ == "__main__":
114+
main()
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
Trame example that checks an input DICOM file patient tag against a patient name in the trame state.
3+
"""
4+
5+
from pathlib import Path
6+
from typing import Literal
7+
8+
from trame.app import TrameApp
9+
from trame.ui.vuetify3 import SinglePageLayout
10+
11+
from trame.widgets import client, vuetify3
12+
13+
DICOM_UTILS_SCRIPT = Path(__file__).with_name("dicom_utils.js")
14+
15+
16+
class DicomProcessingPipeline(TrameApp):
17+
def __init__(self, server=None):
18+
super().__init__(server)
19+
20+
self.state.patient_name = "John Doe"
21+
22+
# Mark your state as client_only!
23+
self.state.client_only("dicom_file_set")
24+
25+
self.dicom_utils_preprocessor = client.register_external_script(
26+
DICOM_UTILS_SCRIPT,
27+
name="dicom_utils",
28+
function_names=[
29+
"validateDicomFilePatientName",
30+
"generateNIFTIFromDicomFileSet",
31+
],
32+
)
33+
34+
self._build_ui()
35+
36+
def _build_ui(self):
37+
with SinglePageLayout(self.server) as ui:
38+
ui.title.set_text("Nested Pipeline")
39+
40+
with (
41+
ui.content,
42+
vuetify3.VContainer(fluid=True),
43+
vuetify3.VRow(),
44+
vuetify3.VCol(cols=3),
45+
client.Handler(
46+
variable="validated_dicom_file",
47+
function=("dicom_utils", "generateNIFTIFromDicomFileSet"),
48+
inputs=("{ output_file_name: 'my_converted_file.nii' }",),
49+
completed=(
50+
self.preprocessing_pipeline_completed,
51+
"[$event.type, $event.outputs]",
52+
),
53+
),
54+
client.Handler(
55+
variable="input_dicom_fileset",
56+
function=self.dicom_utils_preprocessor.function(
57+
"validateDicomFilePatientName"
58+
),
59+
inputs=("{ patient_name }",),
60+
success="validated_dicom_file.value = $event",
61+
failure=(self.on_patient_name_failure, "[$event]"),
62+
),
63+
):
64+
vuetify3.VFileInput(
65+
v_model="input_dicom_fileset.value",
66+
label="DICOM files",
67+
hint="""
68+
Input here a single DICOM file that will be patient name checked
69+
and then converted to a single NIFTI file.
70+
""",
71+
persistent_hint=True,
72+
)
73+
vuetify3.VTextField(
74+
v_model="patient_name",
75+
hint="Input here the patient name that you expect for your DICOM file",
76+
label="Expected Patient Name",
77+
persistent_hint=True,
78+
)
79+
80+
vuetify3.VAlert(
81+
v_if="alert_title",
82+
text=("alert_text", None),
83+
title=("alert_title", None),
84+
type=("alert_type", None),
85+
)
86+
87+
def on_patient_name_failure(self, output):
88+
print(output)
89+
self.state.alert_type = "error"
90+
self.state.alert_title = self.state.alert_type
91+
self.state.alert_text = (
92+
f"Expected patient {output[0]['expected']}, got {output[0]['got']}"
93+
)
94+
95+
def preprocessing_pipeline_completed(
96+
self, type: Literal["success", "failure"], output
97+
) -> None:
98+
print("preprocessing_pipeline_completed")
99+
print(type)
100+
print(output)
101+
102+
self.state.alert_type = type if type == "success" else "error"
103+
self.state.alert_title = self.state.alert_type
104+
105+
if type == "failure":
106+
self.state.alert_text = "Failed to convert to NIFTI file."
107+
else:
108+
self.state.alert_text = (
109+
"Patient name matched! We got the converted NIFTI file."
110+
)
111+
112+
113+
def main(**kwargs):
114+
app = DicomProcessingPipeline()
115+
app.server.start(**kwargs)
116+
117+
118+
if __name__ == "__main__":
119+
main()

0 commit comments

Comments
 (0)