-
Notifications
You must be signed in to change notification settings - Fork 8
Vtk streaming encoding #35
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
base: master
Are you sure you want to change the base?
Changes from all commits
0fe089c
fee73ff
1e2ecd0
b3ffdea
e42d2ef
3723c4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import vtkmodules.vtkRenderingOpenGL2 # noqa | ||
| from trame.app import TrameApp | ||
| from trame.decorators import change | ||
| from trame.widgets import rca | ||
| from trame.widgets import vuetify3 as v3 | ||
| from trame.ui.vuetify3 import SinglePageLayout | ||
|
|
||
| from vtkmodules.vtkCommonCore import vtkLogger | ||
| from vtkmodules.vtkFiltersSources import vtkConeSource | ||
|
|
||
| from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa | ||
| from vtkmodules.vtkRenderingCore import ( | ||
| vtkActor, | ||
| vtkPolyDataMapper, | ||
| vtkRenderer, | ||
| vtkRenderWindow, | ||
| vtkRenderWindowInteractor, | ||
| ) | ||
|
|
||
| DEFAULT_RESOLUTION = 6 | ||
|
|
||
|
|
||
| class ConeApp(TrameApp): | ||
| def __init__(self, server=None): | ||
| super().__init__(server) | ||
| self.server.cli.add_argument( | ||
| "-v", | ||
| "--verbosity", | ||
| help="Set the VTK log verbosity (default: INFO). Change to TRACE to see more verbose logs", | ||
| required=False, | ||
| default="INFO", | ||
| ) | ||
| self.server.cli.add_argument( | ||
| "-f", | ||
| "--fps", | ||
| # fps can mean a number of things, so be honest to the user about what it really means in this context. | ||
| help="Target frame rate. The RcaVideoRenderScheduler encodes one frame every (1000/fps) ms." | ||
| "You would ideally want to match the client's display refresh rate.", | ||
| type=float, | ||
| required=False, | ||
| default=60, | ||
| ) | ||
| args, _ = self.server.cli.parse_known_args() | ||
|
|
||
| # Map logging options to vtkLogger | ||
| vtkLogger.SetStderrVerbosity(vtkLogger.ConvertToVerbosity(args.verbosity)) | ||
| self.render_window = self.setup_vtk() | ||
| self.target_fps = args.fps | ||
|
|
||
| self._build_ui() | ||
|
|
||
| def setup_vtk(self): | ||
| renderer = vtkRenderer() | ||
| render_window = vtkRenderWindow() | ||
|
|
||
| render_window.AddRenderer(renderer) | ||
|
|
||
| render_window_interactor = vtkRenderWindowInteractor() | ||
| render_window_interactor.SetRenderWindow(render_window) | ||
| render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() | ||
|
|
||
| self.cone_source = vtkConeSource() | ||
| mapper = vtkPolyDataMapper() | ||
| mapper.SetInputConnection(self.cone_source.GetOutputPort()) | ||
| actor = vtkActor() | ||
| actor.SetMapper(mapper) | ||
|
|
||
| renderer.AddActor(actor) | ||
| renderer.ResetCamera() | ||
| render_window.Render() | ||
|
|
||
| return render_window | ||
|
|
||
| @change("resolution") | ||
| def update_cone(self, resolution, **kwargs): | ||
| self.cone_source.SetResolution(resolution) | ||
| self.view_handler.update() | ||
|
|
||
| def update_reset_resolution(self): | ||
| self.state.resolution = DEFAULT_RESOLUTION | ||
|
|
||
| def _build_ui(self): | ||
| with SinglePageLayout(self.server, full_height=True) as layout: | ||
| layout.title.set_text("Video Encoding") | ||
| with layout.toolbar: | ||
| v3.VSpacer() | ||
| v3.VSlider( | ||
| v_model=("resolution", DEFAULT_RESOLUTION), | ||
| min=3, | ||
| max=60, | ||
| step=1, | ||
| hide_details=True, | ||
| density="compact", | ||
| style="max-width: 300px", | ||
| ) | ||
|
|
||
| v3.VBtn(icon="mdi-undo-variant", click=self.update_reset_resolution) | ||
|
|
||
| with layout.content: | ||
| view = rca.RemoteControlledArea( | ||
| display="video-decoder", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup, the JS code for VideoDecoder needs reviewed.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow up on this, I think the problem on my side is that we apply the pixel ratio twice (size sent by the browser is already the scaled up version). Commenting out the pixel ratio apply solves the problem and it was just hidden by the JS scaling for the image encoder. @jourdain do you concur or is there an OS specific thing that may be happening here? def update_size(self, origin, size):
# Resize to ten pixel min to avoid rendering problems
width = max(10, int(size.get("w", 300)))
height = max(10, int(size.get("h", 300)))
pixel_ratio = size.get("p", 1)
self._current_size = (width, height, pixel_ratio)
width = int(width * pixel_ratio * self._scale) # <-- incriminated lines
height = int(height * pixel_ratio * self._scale) |
||
| event_throttle_ms=(500 / self.target_fps,), | ||
| ) | ||
| self.view_handler = view.create_view_handler(self.render_window) | ||
| self.view_handler.target_fps = self.target_fps | ||
|
|
||
|
|
||
| # ----------------------------------------------------------------------------- | ||
| # Main | ||
| # ----------------------------------------------------------------------------- | ||
| if __name__ == "__main__": | ||
| app = ConeApp() | ||
| app.server.start() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import logging | ||
| import os | ||
| import sys | ||
|
|
||
| from .image_encoder import RcaImageEncoder | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| if sys.platform == "linux": | ||
| # Disabling vsync avoids capping the render loop at the display refresh | ||
| # rate (e.g. ~16ms/frame on a 60 Hz display). trame-rca does not override | ||
| # these itself: it is not its responsibility to mutate global OpenGL (or | ||
| # other) configuration, and trame-rca can be used without VTK. Set these in | ||
| # the environment that launches the server for optimal performance. | ||
| _vsync_vars = ("__GL_SYNC_TO_VBLANK", "vblank_mode") | ||
| if not all(var in os.environ for var in _vsync_vars): | ||
| logger.warning( | ||
| "For optimal rendering performance on Linux, disable vsync by " | ||
| "setting these environment variables before launching the server: " | ||
| "%s. Otherwise rendering may be capped at the display refresh rate.", | ||
| ", ".join(f"{var}=0" for var in _vsync_vars), | ||
| ) | ||
|
|
||
| try: | ||
| from .video_encoder import RcaVideoEncoder | ||
| except ModuleNotFoundError: | ||
| logger.warning( | ||
| "VTKStreaming Video encoding is NOT AVAILABLE (missing Python package)" | ||
| ) | ||
|
|
||
|
|
||
| __all__ = ["RcaVideoEncoder", "RcaImageEncoder"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import logging | ||
| from enum import Enum | ||
| from time import time_ns | ||
|
|
||
| from numpy.typing import NDArray | ||
| from trame_common.utils import profiler | ||
| from .pil import encode as encode_pil | ||
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
| try: | ||
| from .turbo_jpeg import encode as encode_turbo | ||
| except RuntimeError: | ||
| logger.warning("Turbo JPEG - NOT AVAILABLE (System Library)") | ||
| encode_turbo = encode_pil | ||
| except ModuleNotFoundError: | ||
| logger.warning("Turbo JPEG - NOT AVAILABLE (Python package)") | ||
| encode_turbo = encode_pil | ||
|
|
||
|
|
||
| class RcaImageEncoder(Enum): | ||
| JPEG = "jpeg" | ||
| TURBO_JPEG = "turbo-jpeg" | ||
| PNG = "png" | ||
| WEBP = "webp" | ||
| AVIF = "avif" | ||
|
|
||
| def __init__(self, value: str): | ||
| self.__value__ = value | ||
| self._timer_msg = f"rca.encode.{self.value}" | ||
|
|
||
| @property | ||
| def _impl(self): | ||
| """Return encoding method""" | ||
| if self is RcaImageEncoder.TURBO_JPEG: | ||
| return encode_turbo | ||
|
|
||
| return encode_pil | ||
|
|
||
| def encode( | ||
| self, | ||
| np_image: NDArray, | ||
| cols: int, | ||
| rows: int, | ||
| quality: int, | ||
| ) -> tuple[bytes, dict, int]: | ||
| now_ms = int(time_ns() / 1000000) | ||
| with profiler.timer(self._timer_msg): | ||
| return self._impl(np_image, self.value, cols, rows, quality, now_ms) |

Uh oh!
There was an error while loading. Please reload this page.