Skip to content

Commit

Permalink
Implement initial chromaticity inspector.
Browse files Browse the repository at this point in the history
  • Loading branch information
KelSolaar committed Oct 31, 2023
1 parent 7ed3b91 commit 164c7dc
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/apps/ocioview/ocioview/inspect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

from .chromaticities_inspector import ChromaticitiesInspector
from .code_inspector import CodeInspector
from .curve_inspector import CurveInspector
from .log_inspector import LogInspector
110 changes: 110 additions & 0 deletions src/apps/ocioview/ocioview/inspect/chromaticities_inspector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import numpy as np
import pygfx as gfx
import OpenImageIO as oiio
import PyOpenColorIO as ocio
from colour_visuals import *
from PySide6 import QtCore, QtGui, QtWidgets
from typing import Optional

from ..viewer import WgpuCanvasOffScreenViewer
from ..message_router import MessageRouter
from ..utils import get_glyph_icon


class ChromaticitiesInspector(QtWidgets.QWidget):
@classmethod
def label(cls) -> str:
return "Chromaticities"

@classmethod
def icon(cls) -> QtGui.QIcon:
return get_glyph_icon("mdi6.grain")

def __init__(self, parent: Optional[QtCore.QObject] = None):
super().__init__(parent=parent)

self._cpu_proc = None
self._image_buf = None

self._wgpu_viewer = WgpuCanvasOffScreenViewer()
self._setup_scene()

# Layout
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
layout.addWidget(self._wgpu_viewer)

msg_router = MessageRouter.get_instance()
# msg_router.processor_ready.connect(self._on_processor_ready)
# msg_router.image_ready.connect(self._on_image_ready)

@property
def wgpu_viewer(self):
return self._wgpu_viewer

def _setup_scene(self):
self._wgpu_viewer.wgpu_scene.add(
gfx.Background(
None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18]))
)
)
visuals = [
VisualGrid(size=2),
VisualChromaticityDiagramCIE1931(
kwargs_visual_chromaticity_diagram={"opacity": 0.25}
),
VisualRGBColourspace2D("ACEScg"),
VisualRGBColourspace2D(
"Display P3", colours=np.array([0.5, 0.5, 0.5])
),
VisualRGBColourspace3D("Display P3", opacity=0.5, wireframe=True),
VisualRGBScatter3D(np.random.random([24, 32, 3]), "ACEScg"),
]

group = gfx.Group()
for visual in visuals:
group.add(visual)
self._wgpu_viewer.wgpu_scene.add(group)

def reset(self) -> None:
pass

def showEvent(self, event: QtGui.QShowEvent) -> None:
"""Start listening for processor updates, if visible."""
super().showEvent(event)

msg_router = MessageRouter.get_instance()
# msg_router.set_processor_updates_allowed(True)
# msg_router.set_image_updates_allowed(True)

def hideEvent(self, event: QtGui.QHideEvent) -> None:
"""Stop listening for processor updates, if not visible."""
super().hideEvent(event)

msg_router = MessageRouter.get_instance()
# msg_router.set_processor_updates_allowed(False)
# msg_router.set_image_updates_allowed(False)

@QtCore.Slot(ocio.CPUProcessor)
def _on_processor_ready(self, cpu_proc: ocio.CPUProcessor) -> None:
self._cpu_proc = cpu_proc

print("_on_processor_ready")

@QtCore.Slot(np.ndarray)
def _on_image_ready(self, image_buf: oiio.ImageBuf) -> None:
self._image_buf = image_buf

print("_on_image_ready")


if __name__ == "__main__":
application = QtWidgets.QApplication([])
chromaticity_inspector = ChromaticitiesInspector()
chromaticity_inspector.resize(800, 600)
chromaticity_inspector.show()

application.exec()
10 changes: 8 additions & 2 deletions src/apps/ocioview/ocioview/inspect_dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

from PySide6 import QtCore, QtWidgets

from .inspect.curve_inspector import CurveInspector
from .inspect import LogInspector, CodeInspector
from .inspect import ChromaticitiesInspector, CodeInspector, CurveInspector, LogInspector
from .utils import get_glyph_icon
from .widgets.structure import TabbedDockWidget

Expand All @@ -26,11 +25,17 @@ def __init__(self, parent: Optional[QtCore.QObject] = None):
self.tabs.setTabPosition(QtWidgets.QTabWidget.West)

# Widgets
self.chromaticities_inspector = ChromaticitiesInspector()
self.curve_inspector = CurveInspector()
self.code_inspector = CodeInspector()
self.log_inspector = LogInspector()

# Layout
self.add_tab(
self.chromaticities_inspector,
self.chromaticities_inspector.label(),
self.chromaticities_inspector.icon(),
)
self.add_tab(
self.curve_inspector,
self.curve_inspector.label(),
Expand All @@ -49,6 +54,7 @@ def __init__(self, parent: Optional[QtCore.QObject] = None):

def reset(self) -> None:
"""Reset data for all inspectors."""
self.chromaticities_inspector.reset()
self.curve_inspector.reset()
self.code_inspector.reset()
self.log_inspector.reset()
1 change: 1 addition & 0 deletions src/apps/ocioview/ocioview/viewer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
# Copyright Contributors to the OpenColorIO Project.

from .image_viewer import ViewerChannels, ImageViewer
from .offscreen_viewer import WgpuCanvasOffScreenViewer
158 changes: 158 additions & 0 deletions src/apps/ocioview/ocioview/viewer/offscreen_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import numpy as np
import pygfx as gfx
from PySide6 import QtCore, QtGui, QtWidgets
from wgpu.gui.offscreen import WgpuCanvas
from wgpu.gui.qt import BUTTON_MAP, MODIFIERS_MAP


class WgpuCanvasOffScreenViewer(QtWidgets.QGraphicsView):
def __init__(self):
super().__init__()

# WebGPU
self._wgpu_canvas = WgpuCanvas(size=self._viewport_size)
self._wgpu_renderer = gfx.renderers.WgpuRenderer(self._wgpu_canvas)
self._wgpu_camera = gfx.PerspectiveCamera(50, 16 / 9)
self._wgpu_controller = gfx.OrbitController(self._wgpu_camera)
self._wgpu_controller.register_events(self._wgpu_renderer)

self._wgpu_scene = gfx.Scene()

self._wgpu_canvas.request_draw(
lambda: self._wgpu_renderer.render(
self._wgpu_scene, self._wgpu_camera
)
)

self._wgpu_camera.local.position = np.array([-0.25, -0.5, 2])
self._wgpu_camera.show_pos(np.array([1 / 3, 1 / 3, 0.4]))

# QGraphicsView
self.setScene(QtWidgets.QGraphicsScene(self))
self.setTransformationAnchor(self.ViewportAnchor.AnchorUnderMouse)
self.image_plane = QtWidgets.QGraphicsPixmapItem(
self._render_to_pixmap()
)
self.scene().addItem(self.image_plane)
self.scale(0.5, 0.5)

@property
def wgpu_canvas(self):
return self._wgpu_canvas

@property
def wgpu_renderer(self):
return self._wgpu_renderer

@property
def wgpu_camera(self):
return self._wgpu_camera

@property
def wgpu_controller(self):
return self._wgpu_controller

@property
def wgpu_scene(self):
return self._wgpu_scene

@property
def _viewport_size(self):
return (
self.viewport().size().width() * 2,
self.viewport().size().height() * 2,
)

def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
super().resizeEvent(event)

self._wgpu_canvas.set_logical_size(*self._viewport_size)

self.image_plane.setPixmap(self._render_to_pixmap())

def _render_to_pixmap(self):
render = np.array(self._wgpu_renderer.target.draw())[..., :3]

height, width, _channel = render.shape
return QtGui.QPixmap.fromImage(
QtGui.QImage(
np.ascontiguousarray(render),
width,
height,
3 * width,
QtGui.QImage.Format_RGB888,
)
)

def _mouse_event(self, event_type, event, touches=True):
button = BUTTON_MAP.get(event.button(), 0)
buttons = [
BUTTON_MAP[button]
for button in BUTTON_MAP.keys()
if button & event.buttons()
]

modifiers = [
MODIFIERS_MAP[mod]
for mod in MODIFIERS_MAP.keys()
if mod & event.modifiers()
]

wgpu_event = {
"event_type": event_type,
"x": event.pos().x(),
"y": event.pos().y(),
"button": button,
"buttons": buttons,
"modifiers": modifiers,
}
if touches:
wgpu_event.update(
{
"ntouches": 0,
"touches": {},
}
)

self._wgpu_canvas.handle_event(wgpu_event)

self.image_plane.setPixmap(self._render_to_pixmap())

def mousePressEvent(self, event):
self._mouse_event("pointer_down", event)

def mouseMoveEvent(self, event):
self._mouse_event("pointer_move", event)

def mouseReleaseEvent(self, event):
self._mouse_event("pointer_up", event)

def mouseDoubleClickEvent(self, event):
self._mouse_event("double_click", event, touches=False)

def wheelEvent(self, event):
modifiers = [
MODIFIERS_MAP[mod]
for mod in MODIFIERS_MAP.keys()
if mod & event.modifiers()
]

wgpu_event = {
"event_type": "wheel",
"dx": -event.angleDelta().x(),
"dy": -event.angleDelta().y(),
"x": event.position().x(),
"y": event.position().y(),
"modifiers": modifiers,
}

self._wgpu_canvas.handle_event(wgpu_event)

self.image_plane.setPixmap(self._render_to_pixmap())


def call_later(delay, callback, *args):
QtCore.QTimer.singleShot(int(delay * 1000), lambda: callback(*args))

0 comments on commit 164c7dc

Please sign in to comment.