Skip to content

Commit

Permalink
Add initial visuals and chromaticity diagram inspector.
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Mansencal <thomas.mansencal@gmail.com>
  • Loading branch information
KelSolaar committed Oct 14, 2023
1 parent 573d63d commit af47589
Show file tree
Hide file tree
Showing 10 changed files with 1,006 additions and 3 deletions.
5 changes: 4 additions & 1 deletion src/apps/ocioview/ocioview/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import numpy as np
from pathlib import Path

from PySide6 import QtCore, QtGui


Expand Down Expand Up @@ -33,3 +33,6 @@
# Value edit array component label sets
RGB = ("r", "g", "b")
RGBA = tuple(list(RGB) + ["a"])

DEFAULT_FLOAT_DTYPE = np.float32
DEFAULT_INT_DTYPE = np.uint32
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
101 changes: 101 additions & 0 deletions src/apps/ocioview/ocioview/inspect/chromaticities_inspector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import numpy as np
import OpenImageIO as oiio
import PyOpenColorIO as ocio
from PySide6 import QtCore, QtGui, QtWidgets
from pygfx import OrbitController, PerspectiveCamera, Scene, WgpuRenderer
from typing import Optional
from wgpu.gui.qt import WgpuWidget

from ..message_router import MessageRouter
from ..utils import get_glyph_icon
from .visuals import VisualChromaticityDiagramCIE1931, VisualGrid


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

# Scene
self.canvas = WgpuWidget(parent=self)
self.renderer = WgpuRenderer(self.canvas)
self.scene = Scene()
self.grid = VisualGrid()
self.grid.local.position = np.array([0, 0, -1e-3])
self.chromaticity_diagram = VisualChromaticityDiagramCIE1931(
kwargs_visual_chromaticity_diagram={"opacity": 0.25}
)
self.scene.add(self.grid, self.chromaticity_diagram)
self.camera = PerspectiveCamera(45)
self.camera.show_object(
self.chromaticity_diagram, up=np.array([0, 0, 1]), scale=1.5
)
self.controller = OrbitController(register_events=self.renderer)
self.controller.add_camera(self.camera)

self.canvas.request_draw(self.draw)

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

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

def draw(self):
self.renderer.render(self.scene, self.camera)

def reset(self) -> None:
raise NotImplementedError()

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()
8 changes: 8 additions & 0 deletions src/apps/ocioview/ocioview/inspect/visuals/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .diagrams import (
VisualChromaticityDiagramCIE1931,
VisualChromaticityDiagramCIE1960UCS,
VisualChromaticityDiagramCIE1976UCS,
)
from .grid import VisualGrid
from .rgb_colorspace import VisualRGBColorspace2D, VisualRGBColorspace3D
from .rgb_scatter import VisualRGBScatter3d
127 changes: 127 additions & 0 deletions src/apps/ocioview/ocioview/inspect/visuals/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

"""
Common Utilities
================
Defines the common utilities objects that don't fall in any specific category.
"""

import numpy as np
from colour import (
Luv_to_uv,
Luv_uv_to_xy,
UCS_to_uv,
UCS_uv_to_xy,
XYZ_to_Jzazbz,
XYZ_to_Luv,
XYZ_to_OSA_UCS,
XYZ_to_UCS,
XYZ_to_xy,
convert,
xy_to_XYZ,
)
from colour.hints import ArrayLike, NDArray, NDArrayFloat, NDArrayInt, Tuple
from colour.utilities import full

from ocioview.constants import DEFAULT_FLOAT_DTYPE, DEFAULT_INT_DTYPE

__all__ = [
"METHODS_CHROMATICITY_DIAGRAM",
"XYZ_to_colourspace_model",
"as_float_array",
"as_int_array",
"as_contiguous_array",
"conform_primitive_dtype",
"append_alpha_channel",
]

METHODS_CHROMATICITY_DIAGRAM = {
"CIE 1931": {
"XYZ_to_ij": lambda a, i: XYZ_to_xy(a),
"ij_to_XYZ": lambda a, i: xy_to_XYZ(a),
},
"CIE 1960 UCS": {
"XYZ_to_ij": lambda a, i: UCS_to_uv(XYZ_to_UCS(a)),
"ij_to_XYZ": lambda a, i: xy_to_XYZ(UCS_uv_to_xy(a)),
},
"CIE 1976 UCS": {
"XYZ_to_ij": lambda a, i: Luv_to_uv(XYZ_to_Luv(a, i), i),
"ij_to_XYZ": lambda a, i: xy_to_XYZ(Luv_uv_to_xy(a)),
},
}
"""
Chromaticity diagram specific helper conversion objects.
"""


def XYZ_to_colourspace_model(
XYZ: ArrayLike, illuminant: ArrayLike, model: str, **kwargs
) -> NDArray:
"""
Converts from *CIE XYZ* tristimulus values to given colourspace model while
normalising for visual convenience some of the models.
"""

ijk = convert(
XYZ,
"CIE XYZ",
model,
illuminant=illuminant,
verbose={"mode": "Short"},
**kwargs
)

# TODO: ICtCp?
if model == "JzAzBz":
ijk /= XYZ_to_Jzazbz([1, 1, 1])[0]
elif model == "OSA UCS":
ijk /= XYZ_to_OSA_UCS([1, 1, 1])[0]

return ijk


def as_float_array(a: ArrayLike) -> NDArrayFloat:
from colour.utilities import as_float_array

return as_float_array(a, DEFAULT_FLOAT_DTYPE)


def as_int_array(a: ArrayLike) -> NDArrayInt:
from colour.utilities import as_int_array

return as_int_array(a, DEFAULT_INT_DTYPE)


def as_contiguous_array(a, dtype=DEFAULT_FLOAT_DTYPE):
return np.ascontiguousarray(a.astype(dtype))


def conform_primitive_dtype(
primitive: Tuple[NDArray, NDArray, NDArray]
) -> Tuple[NDArray, NDArray, NDArray]:
"""
Conform the given primitive to the required dtype.
"""

vertices, faces, outline = primitive

return (
vertices.astype(
[
("position", DEFAULT_FLOAT_DTYPE, (3,)),
("uv", DEFAULT_FLOAT_DTYPE, (2,)),
("normal", DEFAULT_FLOAT_DTYPE, (3,)),
("colour", DEFAULT_FLOAT_DTYPE, (4,)),
]
),
faces.astype(DEFAULT_INT_DTYPE),
outline.astype(DEFAULT_INT_DTYPE),
)


def append_alpha_channel(a: ArrayLike, alpha: float = 1) -> NDArray:
a = np.copy(a)

return np.hstack([a, full(list(a.shape[:-1]) + [1], alpha, dtype=a.dtype)])
Loading

0 comments on commit af47589

Please sign in to comment.