Skip to content

Commit 541cc84

Browse files
Merge pull request #29 from Kitware/fix_unwanted_vtk_import
fix(utils): do not import vtk unnecessarily
2 parents 77eb625 + dba8237 commit 541cc84

5 files changed

Lines changed: 112 additions & 93 deletions

File tree

examples/00_vanilla/vanilla_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from trame.app.testing import enable_testing
55
from trame.decorators import TrameApp, change
66
from trame_rca.widgets import rca
7-
from trame_rca.utils import AbstractWindow
87
from trame.app import get_server
98
from trame.ui.vuetify3 import SinglePageLayout
109
from trame.widgets import vuetify3 as v3
@@ -14,7 +13,7 @@
1413
DEFAULT_ROTATION_STEP = 45
1514

1615

17-
class RotatableImageWindow(AbstractWindow):
16+
class RotatableImageWindow:
1817
def __init__(self, path):
1918
self._image = Image.open(path).convert("RGB")
2019
self._image_angle = 0

examples/02_doom/doom.wad

12.4 MB
Binary file not shown.

trame_rca/protocol.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,49 @@
1+
from typing import Protocol, runtime_checkable
2+
from numpy.typing import NDArray
13
from wslink import register as exportRpc
24
from wslink.websocket import LinkProtocol
35

46

7+
@runtime_checkable
8+
class AbstractWindow(Protocol):
9+
"""
10+
Protocol defining the interface for interacting with a remote window through RCA.
11+
12+
Any class matching this interface can be used as a remote window, regardless of inheritance.
13+
Implementing classes must define the required methods and properties to enable window interaction.
14+
"""
15+
16+
@property
17+
def img_cols_rows(self) -> tuple[NDArray, int, int]:
18+
"""
19+
Returns a tuple containing:
20+
- the window content as a NumPy array,
21+
- the number of columns,
22+
- and the number of rows.
23+
24+
Called by the scheduler to render the current window view.
25+
"""
26+
pass
27+
28+
def process_resize_event(self, width: int, height: int) -> None:
29+
"""
30+
Handle a resize event for the RCA (RenderWindowInteractor).
31+
32+
This method is triggered by the adapter whenever the window is resized.
33+
"""
34+
pass
35+
36+
def process_interaction_event(self, event: dict) -> None:
37+
"""
38+
Handle an interaction event from the RCA (RenderWindowInteractor).
39+
40+
This method is invoked by the adapter whenever an interaction event occurs.
41+
Refer to the event types defined in:
42+
https://github.com/Kitware/vtk-js/blob/master/Sources/Rendering/Core/RenderWindowInteractor/index.js
43+
"""
44+
pass
45+
46+
547
class AreaAdapter:
648
def __init__(self, name):
749
self.area_name = name

trame_rca/utils.py

Lines changed: 22 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
import os
44
import asyncio
55
import time
6-
from abc import ABC, abstractmethod
76
from asyncio import Queue
87
from concurrent.futures.thread import ThreadPoolExecutor
98
from concurrent.futures import Executor
109
from enum import Enum
11-
from packaging.version import Version
12-
from typing import Callable, Optional
10+
from typing import Callable, Optional, TYPE_CHECKING
1311

1412
from numpy.typing import NDArray
1513
from trame.app import asynchronous
16-
from vtkmodules.util.numpy_support import vtk_to_numpy
17-
from vtkmodules.vtkRenderingCore import vtkRenderWindow, vtkWindowToImageFilter
18-
import json
1914
from trame_rca.encoders.pil import encode as encode_pil
2015

21-
from vtkmodules.vtkCommonCore import vtkCommand, vtkVersion
22-
from vtkmodules.vtkWebCore import vtkRemoteInteractionAdapter
16+
if TYPE_CHECKING:
17+
from vtkmodules.vtkRenderingCore import vtkRenderWindow
18+
19+
from trame_rca.protocol import AbstractWindow
20+
21+
try:
22+
from trame_rca.vtk_utils import VtkWindow
23+
except ModuleNotFoundError as e:
24+
print(e.msg)
2325

2426
try:
2527
from trame_rca.encoders.turbo_jpeg import encode as encode_turbo
@@ -32,6 +34,14 @@
3234

3335
ENCODING_POOL = ThreadPoolExecutor(max(4, os.cpu_count()))
3436

37+
__all__ = [
38+
"AbstractWindow",
39+
"RcaEncoder",
40+
"RcaViewAdapter",
41+
"RcaRenderScheduler",
42+
"VtkWindow",
43+
]
44+
3545

3646
def time_now_ms() -> int:
3747
return int(time.time_ns() / 1000000)
@@ -40,8 +50,12 @@ def time_now_ms() -> int:
4050
def window_wrapper(window: AbstractWindow | vtkRenderWindow) -> AbstractWindow:
4151
if isinstance(window, AbstractWindow):
4252
return window
53+
54+
from vtkmodules.vtkRenderingCore import vtkRenderWindow
55+
4356
if isinstance(window, vtkRenderWindow):
4457
return VtkWindow(window)
58+
4559
raise RuntimeError(
4660
"Invalid window object provided: expected an instance of AbstractWindow"
4761
)
@@ -73,89 +87,6 @@ def encode(
7387
return self._impl(np_image, self.value, cols, rows, quality, now_ms)
7488

7589

76-
class AbstractWindow(ABC):
77-
"""
78-
Abstract base class for interacting with a remote window through the RCA.
79-
80-
Subclasses must implement the abstract methods defined in this class
81-
to enable interaction with the window.
82-
"""
83-
84-
@property
85-
@abstractmethod
86-
def img_cols_rows(self) -> tuple[NDArray, int, int]:
87-
"""
88-
Returns a tuple containing:
89-
- the window content as a NumPy array,
90-
- the number of columns,
91-
- and the number of rows.
92-
93-
Called by the scheduler to render the current window view.
94-
"""
95-
pass
96-
97-
@abstractmethod
98-
def process_resize_event(self, width: int, height: int) -> None:
99-
"""
100-
Handle a resize event for the RCA (RenderWindowInteractor).
101-
102-
This method is triggered by the adapter whenever the window is resized.
103-
"""
104-
pass
105-
106-
@abstractmethod
107-
def process_interaction_event(self, event: dict) -> None:
108-
"""
109-
Handle an interaction event from the RCA (RenderWindowInteractor).
110-
111-
This method is invoked by the adapter whenever an interaction event occurs.
112-
Refer to the event types defined in:
113-
https://github.com/Kitware/vtk-js/blob/master/Sources/Rendering/Core/RenderWindowInteractor/index.js
114-
"""
115-
pass
116-
117-
118-
class VtkWindow(AbstractWindow):
119-
def __init__(self, vtk_render_window: vtkRenderWindow):
120-
self._vtk_render_window = vtk_render_window
121-
self._window_to_image = vtkWindowToImageFilter()
122-
self._window_to_image.SetInput(vtk_render_window)
123-
self._window_to_image.SetScale(1)
124-
self._window_to_image.ReadFrontBufferOff()
125-
self._window_to_image.ShouldRerenderOff()
126-
self._window_to_image.FixBoundaryOn()
127-
self._iren = self._vtk_render_window.GetInteractor()
128-
self._iren.EnableRenderOff()
129-
self._vtk_render_window.ShowWindowOff()
130-
131-
@property
132-
def img_cols_rows(self):
133-
self._vtk_render_window.Render()
134-
self._window_to_image.Modified()
135-
self._window_to_image.Update()
136-
137-
image_data = self._window_to_image.GetOutput()
138-
rows, cols, _ = image_data.GetDimensions()
139-
scalars = image_data.GetPointData().GetScalars()
140-
np_image = vtk_to_numpy(scalars)
141-
np_image = np_image.reshape((cols, rows, -1))
142-
np_image[:] = np_image[::-1, :, :]
143-
return np_image, cols, rows
144-
145-
def process_resize_event(self, width, height):
146-
self._iren.UpdateSize(width, height)
147-
148-
if Version(vtkVersion().vtk_version) < Version("9.5"):
149-
self._iren.InvokeEvent(vtkCommand.WindowResizeEvent)
150-
151-
def process_interaction_event(self, event):
152-
event_type = event["type"]
153-
if event_type in ["StartInteractionEvent", "EndInteractionEvent"]:
154-
return
155-
156-
vtkRemoteInteractionAdapter.ProcessEvent(self._iren, json.dumps(event))
157-
158-
15990
class RcaRenderScheduler:
16091
"""
16192
Render scheduler which renders images and pushing the encoded output to a given callback function.

trame_rca/vtk_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import json
2+
from packaging.version import Version
3+
import vtkmodules.vtkRenderingOpenGL2 # noqa
4+
from vtkmodules.util.numpy_support import vtk_to_numpy
5+
from vtkmodules.vtkCommonCore import vtkCommand, vtkVersion
6+
from vtkmodules.vtkRenderingCore import vtkRenderWindow, vtkWindowToImageFilter
7+
from vtkmodules.vtkWebCore import vtkRemoteInteractionAdapter
8+
9+
10+
class VtkWindow:
11+
def __init__(self, vtk_render_window: vtkRenderWindow):
12+
self._vtk_render_window = vtk_render_window
13+
self._window_to_image = vtkWindowToImageFilter()
14+
self._window_to_image.SetInput(vtk_render_window)
15+
self._window_to_image.SetScale(1)
16+
self._window_to_image.ReadFrontBufferOff()
17+
self._window_to_image.ShouldRerenderOff()
18+
self._window_to_image.FixBoundaryOn()
19+
self._iren = self._vtk_render_window.GetInteractor()
20+
self._iren.EnableRenderOff()
21+
self._vtk_render_window.ShowWindowOff()
22+
23+
@property
24+
def img_cols_rows(self):
25+
self._vtk_render_window.Render()
26+
self._window_to_image.Modified()
27+
self._window_to_image.Update()
28+
29+
image_data = self._window_to_image.GetOutput()
30+
rows, cols, _ = image_data.GetDimensions()
31+
scalars = image_data.GetPointData().GetScalars()
32+
np_image = vtk_to_numpy(scalars)
33+
np_image = np_image.reshape((cols, rows, -1))
34+
np_image[:] = np_image[::-1, :, :]
35+
return np_image, cols, rows
36+
37+
def process_resize_event(self, width, height):
38+
self._iren.UpdateSize(width, height)
39+
if Version(vtkVersion().vtk_version) < Version("9.5"):
40+
self._iren.InvokeEvent(vtkCommand.WindowResizeEvent)
41+
42+
def process_interaction_event(self, event):
43+
event_type = event["type"]
44+
if event_type in ["StartInteractionEvent", "EndInteractionEvent"]:
45+
return
46+
47+
vtkRemoteInteractionAdapter.ProcessEvent(self._iren, json.dumps(event))

0 commit comments

Comments
 (0)