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 May 9, 2024
1 parent 5e9182c commit e6cde44
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/apps/ocioview/ocioview/inspect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +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
119 changes: 119 additions & 0 deletions src/apps/ocioview/ocioview/inspect/chromaticities_inspector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import numpy as np
import pygfx as gfx
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 ..processor_context import ProcessorContext
from ..utils import get_glyph_icon, subsampling_factor


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_array = None

self._wgpu_viewer = WgpuCanvasOffScreenViewer()
self._root = None
self._visuals = {}
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]))
)
)
self._visuals = {
"grid": VisualGrid(size=2),
"chromaticity_diagram": VisualChromaticityDiagramCIE1931(
kwargs_visual_chromaticity_diagram={"opacity": 0.25}
),
"rgb_scatter_3d": VisualRGBScatter3D(np.zeros(3), "ACES2065-1",
size=4),
}

self._root = gfx.Group()
for visual in self._visuals.values():
self._root.add(visual)
self._wgpu_viewer.wgpu_scene.add(self._root)

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.processor_updates_allowed = True
msg_router.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.processor_updates_allowed = False
msg_router.image_updates_allowed = False

@QtCore.Slot(ocio.CPUProcessor)
def _on_processor_ready(self, proc_context: ProcessorContext,
cpu_proc: ocio.CPUProcessor) -> None:
print("ChromaticitiesInspector._on_processor_ready")

self._cpu_proc = cpu_proc

@QtCore.Slot(np.ndarray)
def _on_image_ready(self, image_array: np.ndarray) -> None:
print("ChromaticitiesInspector._on_image_ready")

if self._cpu_proc is not None:
sub_sampling_factor = int(
np.sqrt(subsampling_factor(image_array, 1e6)))
self._image_array = image_array[
::sub_sampling_factor, ::sub_sampling_factor,
...
]
self._cpu_proc.applyRGB(image_array)
self._visuals["rgb_scatter_3d"].RGB = image_array

self._wgpu_viewer.render()


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

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

from PySide6 import QtCore, QtWidgets

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

Expand All @@ -25,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 @@ -48,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()
39 changes: 35 additions & 4 deletions src/apps/ocioview/ocioview/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Optional, Union

import PyOpenColorIO as ocio
import numpy as np
import qtawesome
from pygments import highlight
from pygments.lexers import GLShaderLexer, HLSLShaderLexer, XmlLexer, YamlLexer
Expand Down Expand Up @@ -135,7 +136,9 @@ def item_type_label(item_type: type) -> str:
:param item_type: Config item type
:return: Friendly type name
"""
return " ".join(filter(None, re.split(r"([A-Z]+[a-z]+)", item_type.__name__)))
return " ".join(
filter(None, re.split(r"([A-Z]+[a-z]+)", item_type.__name__))
)


def m44_to_m33(m44: list) -> list:
Expand Down Expand Up @@ -178,7 +181,9 @@ def config_to_html(config: ocio.Config) -> str:
)


def processor_to_ctf_html(processor: ocio.Processor) -> tuple[str, ocio.GroupTransform]:
def processor_to_ctf_html(
processor: ocio.Processor,
) -> tuple[str, ocio.GroupTransform]:
"""Return processor CTF formatted as HTML."""
config = ocio.GetCurrentConfig()
group_tf = processor.createGroupTransform()
Expand Down Expand Up @@ -220,14 +225,20 @@ def processor_to_shader_html(
Return processor shader in the requested language, formatted as
HTML.
"""
gpu_shader_desc = ocio.GpuShaderDesc.CreateShaderDesc(language=gpu_language)
gpu_shader_desc = ocio.GpuShaderDesc.CreateShaderDesc(
language=gpu_language
)
gpu_proc.extractGpuShaderInfo(gpu_shader_desc)
shader_data = gpu_shader_desc.getShaderText()

return increase_html_lineno_padding(
highlight(
shader_data,
(GLShaderLexer if "GLSL" in gpu_language.name else HLSLShaderLexer)(),
(
GLShaderLexer
if "GLSL" in gpu_language.name
else HLSLShaderLexer
)(),
HtmlFormatter(linenos="inline"),
)
)
Expand All @@ -254,3 +265,23 @@ def float_to_uint8(value: float) -> int:
:return: Integer value
"""
return max(0, min(255, int(value * 255)))


def subsampling_factor(a: np.ndarray, maximum_size: float) -> int:
"""
Return the best factor to sub-sample given :math:`a` array and have its
size less or equal to given maximum size.
:param a: Array :math:`a` to find the best sub-sample factor.
:param maximum_size: Maximum size of the sub-sampled array :math:`a`.
:return: Sub-sampling factor.
"""

size = a.size

sub_sampling_factor = 1
while True:
if size / sub_sampling_factor <= maximum_size:
return sub_sampling_factor

sub_sampling_factor += 1
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,4 +2,5 @@
# Copyright Contributors to the OpenColorIO Project.

from .image_viewer import ViewerChannels, ImageViewer
from .offscreen_viewer import WgpuCanvasOffScreenViewer
from .utils import load_image
2 changes: 1 addition & 1 deletion src/apps/ocioview/ocioview/viewer/image_plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def load_image(self, image_path: Path) -> None:
color_space_name,
self._ocio_proc_context.transform_item_type,
self._ocio_proc_context.transform_item_name,
self._ocio_proc_context.direction,
self._ocio_proc_context.transform_direction,
)
else:
proc_context = ProcessorContext(color_space_name, None, None)
Expand Down
Loading

0 comments on commit e6cde44

Please sign in to comment.