Skip to content

Add standardized client preprocessing capabilities#52

Draft
bourdaisj wants to merge 6 commits intoKitware:masterfrom
bourdaisj:add-standardized-client-preprocessing-capabilities
Draft

Add standardized client preprocessing capabilities#52
bourdaisj wants to merge 6 commits intoKitware:masterfrom
bourdaisj:add-standardized-client-preprocessing-capabilities

Conversation

@bourdaisj
Copy link
Copy Markdown
Contributor

@bourdaisj bourdaisj commented Mar 19, 2026

This PR adds a widget to the client that aims to allow library consumers to register and run JS modules files with minimal boilerplate on the client side.

This PR is currently a draft.

Things that I am planning to do before marking as ready:

  • finalize examples
  • write documentatio + usage guide
  • write tests

EDIT:

  • add UMD based VTK.js example instead of wasm - STL reader setRemoveDuplicateVertices look good
  • get rid of pnpm lock file in the example
  • take a look at why the tests are failing

EDIT:

  • figure out a satisfying public API
  • figure out the .value reactive vs ref stuff

@bourdaisj bourdaisj requested a review from jourdain March 19, 2026 17:00
@bourdaisj
Copy link
Copy Markdown
Contributor Author

FYI @finetjul @finetjul @luciemac

@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from b6f44be to 5d422ba Compare March 19, 2026 17:04
Comment thread trame_client/module/__init__.py
Comment thread vue3-app/src/preprocessor/loader.js Outdated
Comment thread vue3-app/src/preprocessor/loader.js Outdated
Comment thread examples/preprocessor/01_start_here.py Outdated
@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 520fe14 to d3293d6 Compare March 24, 2026 16:25
Comment thread examples/preprocessor/04_preprocess_using_remote_es_module.py
Comment thread examples/preprocessor/03_nested_preprocessing_pipeline.py Outdated
Comment thread trame_client/widgets/trame.py Outdated
@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 169750e to 71e5673 Compare March 27, 2026 12:24
@bourdaisj bourdaisj self-assigned this Apr 2, 2026
@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch 5 times, most recently from 603ff7c to 910dd80 Compare April 2, 2026 17:20
@bourdaisj
Copy link
Copy Markdown
Contributor Author

@jourdain @psavery Can you take a look at why the CI is failing? I don't have the beginning of an idea

@jourdain
Copy link
Copy Markdown
Collaborator

jourdain commented Apr 7, 2026

I think the issue is because the vue2 client is not built.
=> At least it was my issue locally.

@jourdain
Copy link
Copy Markdown
Collaborator

jourdain commented Apr 7, 2026

.../trame_client/module/user_preprocessors_module_scripts' does not exist

You may need to pre-create the directory.

@bourdaisj
Copy link
Copy Markdown
Contributor Author

I'm confused... the folder is here, it is in the wheel, it is packaged, it has a init.py in it. so why would it missing? Thx for the tip anyway, i'll continue investigating

@bourdaisj
Copy link
Copy Markdown
Contributor Author

well turns out the init.py was gitignored, feeling dumb right now. CI is now green! I'll finalize this and mark as ready

This new PreProcessor widget aims to allow library consumers to register JS module files with minimal boilerplate, and later call
functions defined in those registered modules at runtime inside the trame client
previously, only ES module were supported. Library consumer can now pass
.umd.js files
@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 5fab55d to 9fd5195 Compare April 10, 2026 15:51
@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 9fd5195 to 31cbe9d Compare April 10, 2026 15:56
@jourdain
Copy link
Copy Markdown
Collaborator

I'll try to do a final review this weekend so we can merge it sometime next week.

@bourdaisj
Copy link
Copy Markdown
Contributor Author

I still need to iron out the public API, write some docstring for the readthedocs, and figure out how I can test this effectively, but that should not take too long. i'll tag you once done.

@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 661fac1 to 98b18db Compare April 13, 2026 09:39
@bourdaisj
Copy link
Copy Markdown
Contributor Author

@jourdain I pushed something that aim to keep the API simple and usable while decoupling from the widget module. wdyt?

Comment thread examples/preprocessor/01_start_here.py Outdated
from trame.ui.vuetify3 import SinglePageLayout

from trame.widgets import client, vuetify3, html
from trame_client.preprocessor import register_preprocessing_script
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any way for not importing things from trame_client.*? Can we just do something like

from trame.widgets import client, vuetify3, html


client.register_external_script(
   name="validate_dicom_patient_name",
   script_file_path=DICOM_UTILS_SCRIPT,
   function_names=["validateDicomFilePatientName"],
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It could be nice if we could use that same method to point to standalone JS file too that do not need method/function to be called.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did not want to pollute the widget file with too many dependencies, but yes - I think a good workaround is too keep all public API in trame.widgets.client. make sense to me

Comment thread examples/preprocessor/01_start_here.py Outdated
vuetify3.VRow(),
vuetify3.VCol(cols=3),
# 3. Instantiate the PreProcessor widget
client.PreProcessor(
Copy link
Copy Markdown
Collaborator

@jourdain jourdain Apr 17, 2026

Choose a reason for hiding this comment

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

What about ?

client.Handler(
    mode="pre-processing", # <= I don't know that we need that, but I'm thinking out loud for some genericity
    function="validate_dicom_patient_name",
    input=...,
    completed=....
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Handler might be good - yet, I'm not keen of one-word components/widgets.
what about: ScriptHandler, ScriptDispatcher.
what would be some other values of mode param?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't actually know if we have different behavior/value for that property. So if you don't think of any, no need to add it.

I still prefer Handler than ScriptHandler. The reason is that it is a client Handler, but I don't see the point of saying client.ClientHandler

Comment thread examples/preprocessor/01_start_here.py Outdated
self.preprocessing_pipeline_completed,
"[$event.type, $event.outputs]",
),
) as patient_name_preprocessor,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

... as client_handler:

# Mark your state as client_only!
self.state.client_only("dicom_file_set")

register_preprocessing_script(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.register_external_script(...

persistent_hint=True,
)

with client.PreProcessor(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.Handler(...

failure=(self.handle_nifti_convertion_error, "[$event]"),
# error event for any exception raised during logic execution
error=(self.handle_nifti_convertion_error, "[$event]"),
) as preprocessing:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

as client_handler:

# Mark your state as client_only!
self.state.client_only("dicom_file_set")

self.dicom_utils_preprocessor = register_preprocessing_script(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.register_external_script(...

vuetify3.VContainer(fluid=True),
vuetify3.VRow(),
vuetify3.VCol(cols=3),
client.PreProcessor(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.Handler(

"[$event.type, $event.outputs]",
),
),
client.PreProcessor(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.Handler

def __init__(self, server=None):
super().__init__(server)

self.read_dicom_tags_preprocessor_logic = register_preprocessing_script(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.register_external_script

self.state.decimation_factor = 5
self.state.hole_size = 1000.0

register_preprocessing_script(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.register_external_script

self.ui.title.set_text("Client Preprocessing UMD Modules")

with self.ui.toolbar:
with client.PreProcessor(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

client.Handler

@@ -0,0 +1,3 @@
*
!__init__.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do you need the init.py in that directory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is needed so that it ends up being packaged into the wheel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No, you can be explicit that the directory contains non py file. That is inside the pyproject.toml that you have to specify that.


def get_user_preprocessor_serve_url_prefix() -> Path:
return Path("__trame_client_preprocessors")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All those Path() should be const rather than methods.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I get your comment, but I don't like things that can get modified from the outside - or is there some kind of "real" const in python?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No but if you don't expose them, they are only available in that module and therefore not editable.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

__all__ = ["setup"]

Comment thread trame_client/module/__init__.py Outdated

server.enable_module(
{
"serve": {root_path.as_posix(): str(serve_path)},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Include version to prevent conflicting client version capability?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let side could be { "serve: f"/__trame_client_handlers_{__version__}" : ... }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

from trame_client import __version__

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

definitely. Looking at this, I'm also thinking the user script should be versioned/namespaced in some way so that they are cache friendly. probably a hash of the file would do the trick

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread trame_client/module/__init__.py Outdated
"get_user_preprocessor_scripts_path",
"get_user_preprocessor_es_scripts_path",
"get_user_preprocessor_umd_scripts_path",
"get_user_preprocessor_serve_url_prefix",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need those to be exposed?

I think the server needs to see setup

Comment thread trame_client/widgets/trame.py Outdated
"DeepReactive",
"LifeCycleMonitor",
"SizeObserver",
"PreProcessor",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Handler

@@ -0,0 +1,127 @@
from pathlib import Path
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

name the file external_script_handler.py ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

user_provided_script_handler? or consumer_provided_script_handler?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I technically don't care for that file name (as it is not public), so you can be as explicit as you want. I just don't like preprocessor.py

@bourdaisj bourdaisj force-pushed the add-standardized-client-preprocessing-capabilities branch from 98b18db to 0ed2f55 Compare April 17, 2026 19:14
@jourdain
Copy link
Copy Markdown
Collaborator

I like all your changes, I think we are getting really close. I guess I just want to get rid of the "preprocess*" properties and make them more generic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants