Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 4 additions & 55 deletions src/apps/ocioview/main.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,17 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import logging
import os
import sys
from pathlib import Path

import PyOpenColorIO as ocio
from PySide6 import QtCore, QtGui, QtWidgets

import ocioview.log_handlers # Import to initialize logging
from ocioview.main_window import OCIOView
from ocioview.style import QSS, DarkPalette


ROOT_DIR = Path(__file__).resolve().parent.parent
FONTS_DIR = ROOT_DIR / "fonts"


def excepthook(exc_type, exc_value, exc_tb):
"""Log uncaught errors"""
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_tb)
return
logging.error(f"{exc_value}", exc_info=exc_value)
from ocioview.setup import setup_app


if __name__ == "__main__":
sys.excepthook = excepthook

# OpenGL core profile needed on macOS to access programmatic pipeline
gl_format = QtGui.QSurfaceFormat()
gl_format.setProfile(QtGui.QSurfaceFormat.CoreProfile)
gl_format.setSwapInterval(1)
gl_format.setVersion(4, 0)
QtGui.QSurfaceFormat.setDefaultFormat(gl_format)

# Create app
app = QtWidgets.QApplication(sys.argv)

# Initialize style
app.setStyle("fusion")
app.setPalette(DarkPalette())
app.setStyleSheet(QSS)
app.setEffectEnabled(QtCore.Qt.UI_AnimateCombo, False)

font = app.font()
font.setPointSize(8)
app.setFont(font)

# Clean OCIO environment to isolate working config
for env_var in (
ocio.OCIO_CONFIG_ENVVAR,
ocio.OCIO_ACTIVE_VIEWS_ENVVAR,
ocio.OCIO_ACTIVE_DISPLAYS_ENVVAR,
ocio.OCIO_INACTIVE_COLORSPACES_ENVVAR,
ocio.OCIO_OPTIMIZATION_FLAGS_ENVVAR,
ocio.OCIO_USER_CATEGORIES_ENVVAR,
):
if env_var in os.environ:
del os.environ[env_var]
app = setup_app()

# Start ocioview
ocioview = OCIOView()
ocioview.show()
ocio_view = OCIOView()
ocio_view.show()

sys.exit(app.exec_())
49 changes: 39 additions & 10 deletions src/apps/ocioview/ocioview/config_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class ConfigCache:
_active_views: Optional[list[str]] = None
_all_names: Optional[list[str]] = None
_categories: Optional[list[str]] = None
_color_spaces: dict[bool, list[ocio.ColorSpace]] = {}
_color_spaces: dict[
tuple[bool, ocio.SearchReferenceSpaceType, ocio.ColorSpaceVisibility],
Union[list[ocio.ColorSpace], ocio.ColorSpaceSet],
] = {}
_color_space_names: dict[ocio.SearchReferenceSpaceType, list[str]] = {}
_default_color_space_name: Optional[str] = None
_default_view_transform_name: Optional[str] = None
Expand Down Expand Up @@ -117,7 +120,10 @@ def get_active_displays(cls) -> list[str]:
cls._active_displays = list(
filter(
None,
re.split(r" *[,:] *", ocio.GetCurrentConfig().getActiveDisplays()),
re.split(
r" *[,:] *",
ocio.GetCurrentConfig().getActiveDisplays(),
),
)
)

Expand All @@ -132,7 +138,9 @@ def get_active_views(cls) -> list[str]:
cls._active_views = list(
filter(
None,
re.split(r" *[,:] *", ocio.GetCurrentConfig().getActiveViews()),
re.split(
r" *[,:] *", ocio.GetCurrentConfig().getActiveViews()
),
)
)

Expand Down Expand Up @@ -213,23 +221,34 @@ def get_categories(cls) -> list[str]:

@classmethod
def get_color_spaces(
cls, as_set: bool = False
cls,
reference_space_type: Optional[ocio.SearchReferenceSpaceType] = None,
visibility: Optional[ocio.ColorSpaceVisibility] = None,
as_set: bool = False,
) -> Union[list[ocio.ColorSpace], ocio.ColorSpaceSet]:
"""
Get all (all reference space types and visibility states) color
spaces from the current config.

:param reference_space_type: Optionally filter by reference
space type.
:param visibility: Optional filter by visibility
:param as_set: If True, put returned color spaces into a
ColorSpaceSet, which copies the spaces to insulate from config
changes.
:return: list or color space set of color spaces
"""
cache_key = as_set
if reference_space_type is None:
reference_space_type = ocio.SEARCH_REFERENCE_SPACE_ALL
if visibility is None:
visibility = ocio.COLORSPACE_ALL

cache_key = (as_set, reference_space_type, visibility)

if not cls.validate() or cache_key not in cls._color_spaces:
config = ocio.GetCurrentConfig()
color_spaces = config.getColorSpaces(
ocio.SEARCH_REFERENCE_SPACE_ALL, ocio.COLORSPACE_ALL
reference_space_type, visibility
)
if as_set:
color_space_set = ocio.ColorSpaceSet()
Expand All @@ -253,7 +272,10 @@ def get_color_space_names(
"""
cache_key = reference_space_type

if not cls.validate() or reference_space_type not in cls._color_space_names:
if (
not cls.validate()
or reference_space_type not in cls._color_space_names
):
cls._color_space_names[cache_key] = list(
ocio.GetCurrentConfig().getColorSpaceNames(
reference_space_type, ocio.COLORSPACE_ALL
Expand Down Expand Up @@ -402,7 +424,9 @@ def get_named_transforms(cls) -> list[ocio.NamedTransform]:
"""
if not cls.validate() or cls._named_transforms is None:
cls._named_transforms = list(
ocio.GetCurrentConfig().getNamedTransforms(ocio.NAMEDTRANSFORM_ALL)
ocio.GetCurrentConfig().getNamedTransforms(
ocio.NAMEDTRANSFORM_ALL
)
)

return cls._named_transforms
Expand Down Expand Up @@ -471,7 +495,9 @@ def get_view_transforms(cls) -> list[ocio.ViewTransform]:
:return: List of view transforms from the current config
"""
if not cls.validate() or cls._view_transforms is None:
cls._view_transforms = list(ocio.GetCurrentConfig().getViewTransforms())
cls._view_transforms = list(
ocio.GetCurrentConfig().getViewTransforms()
)

return cls._view_transforms

Expand All @@ -496,7 +522,10 @@ def get_viewing_rule_names(cls) -> list[str]:
if not cls.validate() or cls._viewing_rule_names is None:
viewing_rules = ocio.GetCurrentConfig().getViewingRules()
cls._viewing_rule_names = sorted(
[viewing_rules.getName(i) for i in range(viewing_rules.getNumEntries())]
[
viewing_rules.getName(i)
for i in range(viewing_rules.getNumEntries())
]
)

return cls._viewing_rule_names
34 changes: 26 additions & 8 deletions src/apps/ocioview/ocioview/config_dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import PyOpenColorIO as ocio
from PySide6 import QtCore, QtWidgets

from .signal_router import SignalRouter
from .items import (
ColorSpaceEdit,
ConfigPropertiesEdit,
Expand All @@ -26,10 +27,21 @@ class ConfigDock(TabbedDockWidget):
Dockable widget for editing the current config.
"""

config_changed = QtCore.Signal()

def __init__(self, parent: Optional[QtCore.QObject] = None):
super().__init__("Config", get_glyph_icon("ph.file-text"), parent=parent)
def __init__(
self,
corner_widget: Optional[QtWidgets.QWidget] = None,
parent: Optional[QtCore.QObject] = None,
):
"""
:param corner_widget: Optional widget to place on the right
side of the dock title bar.
"""
super().__init__(
"Config",
get_glyph_icon("ph.file-text"),
corner_widget=corner_widget,
parent=parent,
)

self._models = []

Expand All @@ -50,9 +62,13 @@ def __init__(self, parent: Optional[QtCore.QObject] = None):
self._connect_config_item_model(self.rule_edit.viewing_rule_edit.model)

self.display_view_edit = DisplayViewEdit()
self._connect_config_item_model(self.display_view_edit.view_edit.display_model)
self._connect_config_item_model(
self.display_view_edit.view_edit.display_model
)
self._connect_config_item_model(self.display_view_edit.view_edit.model)
self._connect_config_item_model(self.display_view_edit.shared_view_edit.model)
self._connect_config_item_model(
self.display_view_edit.shared_view_edit.model
)
self._connect_config_item_model(
self.display_view_edit.active_display_view_edit.active_display_edit.model
)
Expand Down Expand Up @@ -137,7 +153,9 @@ def update_config_views(self) -> None:
"""
message_queue.put_nowait(ocio.GetCurrentConfig())

def _connect_config_item_model(self, model: QtCore.QAbstractItemModel) -> None:
def _connect_config_item_model(
self, model: QtCore.QAbstractItemModel
) -> None:
"""
Collect model and route all config changes to the
'config_changed' signal.
Expand All @@ -154,7 +172,7 @@ def _on_config_changed(self, *args, **kwargs) -> None:
"""
Broadcast to the wider application that the config has changed.
"""
self.config_changed.emit()
SignalRouter.get_instance().emit_config_changed()
self.update_config_views()

def _on_warning_raised(self, message: str) -> None:
Expand Down
33 changes: 18 additions & 15 deletions src/apps/ocioview/ocioview/inspect/chromaticities_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,9 @@ def _setup_visuals(self) -> None:
)
self._visuals["rgb_color_space_input_3d"].visible = False
self._visuals["rgb_color_space_chromaticities_2d"].visible = False
self._visuals["rgb_color_space_chromaticities_2d"].local.position = (
np.array([0, 0, 0.00005])
)
self._visuals[
"rgb_color_space_chromaticities_2d"
].local.position = np.array([0, 0, 0.00005])
self._visuals["rgb_color_space_chromaticities_3d"].visible = False
self._visuals["rgb_scatter_3d"].visible = False

Expand Down Expand Up @@ -494,9 +494,11 @@ def _update_visuals(self, *args):
conversion_chain = []

image_array = np.copy(self._image_array)
# Don't try to process single or zero pixel images
image_empty = image_array.size <= 3

# 1. Apply current active processor
if self._processor is not None:
if not image_empty and self._processor is not None:
if self._context.transform_item_name is not None:
conversion_chain += [
self._context.input_color_space,
Expand All @@ -508,12 +510,12 @@ def _update_visuals(self, *args):
)

if rgb_colourspace is not None:
self._visuals["rgb_color_space_input_2d"].colourspace = (
rgb_colourspace
)
self._visuals["rgb_color_space_input_3d"].colourspace = (
rgb_colourspace
)
self._visuals[
"rgb_color_space_input_2d"
].colourspace = rgb_colourspace
self._visuals[
"rgb_color_space_input_3d"
].colourspace = rgb_colourspace
self._processor.applyRGB(image_array)

# 2. Convert from chromaticities input space to "CIE-XYZ-D65" interchange
Expand Down Expand Up @@ -559,11 +561,12 @@ def _update_visuals(self, *args):
# 3. Convert from "CIE-XYZ-D65" to "VisualRGBScatter3D" working space
conversion_chain += ["CIE-XYZ-D65", self._working_space]

image_array = XYZ_to_RGB(
image_array,
self._working_space,
illuminant=self._working_whitepoint,
)
if not image_empty:
image_array = XYZ_to_RGB(
image_array,
self._working_space,
illuminant=self._working_whitepoint,
)

conversion_chain = [
color_space for color_space, _group in groupby(conversion_chain)
Expand Down
28 changes: 21 additions & 7 deletions src/apps/ocioview/ocioview/inspect/code_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,22 @@ def __init__(self, parent: Optional[QtCore.QObject] = None):
self.export_button = QtWidgets.QToolButton()
self.export_button.setIcon(get_glyph_icon("mdi6.file-export-outline"))
self.export_button.setText("Export CTF")
self.export_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.export_button.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon
)
self.export_button.released.connect(self._on_export_button_released)

self.ctf_view = LogView()
self.ctf_view.document().setDefaultStyleSheet(html_css)
self.ctf_view.append_tool_bar_widget(self.export_button)

self.gpu_language_box = EnumComboBox(ocio.GpuLanguage)
self.gpu_language_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.gpu_language_box.set_member(MessageRouter.get_instance().gpu_language)
self.gpu_language_box.setSizeAdjustPolicy(
QtWidgets.QComboBox.AdjustToContents
)
self.gpu_language_box.set_member(
MessageRouter.get_instance().gpu_language
)
self.gpu_language_box.currentIndexChanged[int].connect(
self._on_gpu_language_changed
)
Expand Down Expand Up @@ -136,7 +142,9 @@ def _scroll_preserved(self, log_view: LogView) -> None:
h_scroll_bar = log_view.horizontalScrollBar()

# Get line number from bottom of view
prev_cursor = log_view.cursorForPosition(log_view.html_view.rect().bottomLeft())
prev_cursor = log_view.cursorForPosition(
log_view.html_view.rect().bottomLeft()
)
prev_line_num = prev_cursor.blockNumber()

# Get scroll bar positions
Expand All @@ -149,7 +157,9 @@ def _scroll_preserved(self, log_view: LogView) -> None:
# Restore current line number
cursor = QtGui.QTextCursor(log_view.document())
cursor.movePosition(
QtGui.QTextCursor.Down, QtGui.QTextCursor.MoveAnchor, prev_line_num - 1
QtGui.QTextCursor.Down,
QtGui.QTextCursor.MoveAnchor,
prev_line_num - 1,
)
log_view.setTextCursor(cursor)

Expand All @@ -167,7 +177,9 @@ def _on_config_html_ready(self, record: str) -> None:
self.config_view.setHtml(record)

@QtCore.Slot(str, ocio.GroupTransform)
def _on_ctf_html_ready(self, record: str, group_tf: ocio.GroupTransform) -> None:
def _on_ctf_html_ready(
self, record: str, group_tf: ocio.GroupTransform
) -> None:
"""
Update CTF view with a lossless XML representation of an
OCIO processor.
Expand All @@ -178,7 +190,9 @@ def _on_ctf_html_ready(self, record: str, group_tf: ocio.GroupTransform) -> None
self.ctf_view.setHtml(record)

@QtCore.Slot(str, ocio.GPUProcessor)
def _on_shader_html_ready(self, record: str, gpu_proc: ocio.GPUProcessor) -> None:
def _on_shader_html_ready(
self, record: str, gpu_proc: ocio.GPUProcessor
) -> None:
"""
Update shader view with fragment shader source created
from an OCIO GPU processor.
Expand Down
Loading