Skip to content

[python][bug] GradingPrimary.contrast odd behavior. #1643

@MrLixm

Description

@MrLixm

Hello, yes me again, I'm sorry but seems this poor GradingPrimary class didn't get as much love as it should have.

This time I'm simply modifying the contrast attribute but I can't manage to get a result I can reproduce in Nuke, nor understand the logic behind GradingRGBM.

As mentioned in issue #1640 , some attribute require a GradingRGBM instance and other a float. But I don't understand why do we
need to specify a red/green/blue value when the output result observed doesn't change the overall "hue" at all but just act as a regular contrast on the 3 channels.

Even when modifying the master and attribute and leaving the r/g/b attributes at 1.0 (test_contrast_master), the result is still different than expected.

As always here are the following test suite I ran, expected values are computed from Nuke using the same 0.18 pivot.
The 2 last tests are with exposure to try to get the logic, but once again r/g/b give unexpected result while this time the master value
works as expected.

unittest.py (code not relevant anymore, check next comment)
# python>3
import unittest
from pathlib import Path
from typing import Tuple

import PyOpenColorIO as ocio
import numpy
import numpy.testing


def make_img(color: Tuple[float, float, float]):
    """Create a 64x64 RGB image with the given color."""
    return numpy.full((64, 64, 3), color, dtype=numpy.float32)


class TestGradingPrimaryTransform(unittest.TestCase):

    config_path = Path(
        r"YOURCONFIG\config.ocio"
    )

    def setUp(self) -> None:
        self.config: ocio.Config = ocio.Config().CreateFromFile(str(self.config_path))
        self.img1 = make_img((0.5, 0.1, 0.1))
        self.gp: ocio.GradingPrimary = ocio.GradingPrimary(ocio.GRADING_LIN)
        return

    def tearDown(self) -> None:
        self.config = None
        self.img1 = None
        self.gp = None
        return

    def _apply_gp_on_img(self):

        tsfm_gp = ocio.GradingPrimaryTransform(
            self.gp,
            ocio.GRADING_LIN,
            False,
        )

        proc: ocio.Processor = self.config.getProcessor(tsfm_gp)
        proc: ocio.CPUProcessor = proc.getDefaultCPUProcessor()

        proc.applyRGB(self.img1)

        return

    def test_contrast_r(self):

        self.gp.contrast = ocio.GradingRGBM(0.1, 1.0, 1.0, 1.0)

        self._apply_gp_on_img()

        expected = make_img((0.22305, 0.1, 0.1))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_contrast_r_triggered(self):
        """
        Trying to see if contrast need to be manually triggered by another
        parameter change.
        """

        self.gp.contrast = ocio.GradingRGBM(0.1, 1.0, 1.0, 1.0)
        self.gp.clampBlack = 0.15

        self._apply_gp_on_img()

        expected = make_img((0.22305, 0.15, 0.15))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_contrast_rg(self):

        self.gp.contrast = ocio.GradingRGBM(0.1, 0.2, 1.0, 1.0)

        self._apply_gp_on_img()

        expected = make_img((0.22305, 0.04880, 0.1))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_contrast_rgb(self):

        self.gp.contrast = ocio.GradingRGBM(0.1, 0.2, 1.2, 1.0)

        self._apply_gp_on_img()

        expected = make_img((0.22305, 0.04880, 0.11965))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_contrast_master(self):

        self.gp.contrast = ocio.GradingRGBM(1.0, 1.0, 1.0, 0.66)

        self._apply_gp_on_img()

        expected = make_img((0.36858, 0.07372, 0.07372))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_exposure_rgb(self):

        self.gp.exposure = ocio.GradingRGBM(0.0, 0.2, 0.6, 1.0)

        self._apply_gp_on_img()

        expected = make_img((0.5, 0.11487, 0.15157))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )

    def test_exposure_master(self):

        self.gp.exposure = ocio.GradingRGBM(0.0, 0.0, 0.0, 0.5)

        self._apply_gp_on_img()

        expected = make_img((0.70711, 0.14142, 0.14142))
        numpy.testing.assert_almost_equal(
            self.img1,
            expected,
            4,
            f"img1 is actually {self.img1[1][1]} while expected is {expected[1][1]}",
        )


if __name__ == "__main__":
    unittest.main()

(Here only the last test test_exposure_master pass)

I'm looking forward to seeing if this is a user error on this one or some bug.

CONTEXT: Windows10, OCIO 2.1.0

Cheers.
Liam.

Edit

Check last comment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions