Ejemplo n.º 1
0
    def setUp(self):
        """Initialise the common tests attributes."""

        self._LUT_1 = LUT1D(LUT1D.linear_table(16) + 0.125, "Nemo 1D")
        self._LUT_2 = LUT3D(LUT3D.linear_table(16)**(1 / 2.2), "Nemo 3D")
        self._LUT_3 = LUT3x1D(LUT3x1D.linear_table(16) * 0.750, "Nemo 3x1D")
        self._LUT_sequence = LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3)

        samples = np.linspace(0, 1, 5)

        self._RGB = tstack([samples, samples, samples])
Ejemplo n.º 2
0
def read_LUT_IridasCube(path):
    """
    Reads given *Iridas* *.cube* *LUT* file.

    Parameters
    ----------
    path : unicode
        *LUT* path.

    Returns
    -------
    LUT3x1D or LUT3d
        :class:`LUT3x1D` or :class:`LUT3D` class instance.

    References
    ----------
    :cite:`AdobeSystems2013b`

    Examples
    --------
    Reading a 3x1D *Iridas* *.cube* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'iridas_cube',
    ...     'ACES_Proxy_10_to_ACES.cube')
    >>> print(read_LUT_IridasCube(path))
    LUT3x1D - ACES Proxy 10 to ACES
    -------------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (32, 3)

    Reading a 3D *Iridas* *.cube* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'iridas_cube',
    ...     'ColourCorrect.cube')
    >>> print(read_LUT_IridasCube(path))
    LUT3D - Generated by Foundry::LUT
    ---------------------------------
    <BLANKLINE>
    Dimensions : 3
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (4, 4, 4, 3)

    Reading a 3D *Iridas* *.cube* *LUT* with comments:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'iridas_cube',
    ...     'Demo.cube')
    >>> print(read_LUT_IridasCube(path))
    LUT3x1D - Demo
    --------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 1.  2.  3.]]
    Size       : (3, 3)
    Comment 01 : Comments can go anywhere
    """

    title = path_to_title(path)
    domain_min, domain_max = np.array([0, 0, 0]), np.array([1, 1, 1])
    dimensions = 3
    size = 2
    table = []
    comments = []

    with open(path) as cube_file:
        lines = cube_file.readlines()
        for line in lines:
            line = line.strip()

            if len(line) == 0:
                continue

            if line.startswith('#'):
                comments.append(line[1:].strip())
                continue

            tokens = line.split()
            if tokens[0] == 'TITLE':
                title = ' '.join(tokens[1:])[1:-1]
            elif tokens[0] == 'DOMAIN_MIN':
                domain_min = parse_array(tokens[1:])
            elif tokens[0] == 'DOMAIN_MAX':
                domain_max = parse_array(tokens[1:])
            elif tokens[0] == 'LUT_1D_SIZE':
                dimensions = 2
                size = DEFAULT_INT_DTYPE(tokens[1])
            elif tokens[0] == 'LUT_3D_SIZE':
                dimensions = 3
                size = DEFAULT_INT_DTYPE(tokens[1])
            else:
                table.append(parse_array(tokens))

    table = as_float_array(table)
    if dimensions == 2:
        return LUT3x1D(table,
                       title,
                       np.vstack([domain_min, domain_max]),
                       comments=comments)
    elif dimensions == 3:
        # The lines of table data shall be in ascending index order,
        # with the first component index (Red) changing most rapidly,
        # and the last component index (Blue) changing least rapidly.
        table = table.reshape([size, size, size, 3], order='F')

        return LUT3D(table,
                     title,
                     np.vstack([domain_min, domain_max]),
                     comments=comments)
Ejemplo n.º 3
0
def read_LUT_ResolveCube(path):
    """
    Reads given *Resolve* *.cube* *LUT* file.

    Parameters
    ----------
    path : unicode
        *LUT* path.

    Returns
    -------
    LUT3x1D or LUT3D or LUTSequence
        :class:`LUT3x1D` or :class:`LUT3D` or :class:`LUTSequence` class
        instance.

    References
    ----------
    :cite:`Chamberlain2015`

    Examples
    --------
    Reading a 3x1D *Resolve* *.cube* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'resolve_cube',
    ...     'ACES_Proxy_10_to_ACES.cube')
    >>> print(read_LUT_ResolveCube(path))
    LUT3x1D - ACES Proxy 10 to ACES
    -------------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (32, 3)

    Reading a 3D *Resolve* *.cube* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'resolve_cube',
    ...     'Colour_Correct.cube')
    >>> print(read_LUT_ResolveCube(path))
    LUT3D - Generated by Foundry::LUT
    ---------------------------------
    <BLANKLINE>
    Dimensions : 3
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (4, 4, 4, 3)

    Reading a 3D *Resolve* *.cube* *LUT* with comments:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'resolve_cube',
    ...     'Demo.cube')
    >>> print(read_LUT_ResolveCube(path))
    LUT3x1D - Demo
    --------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 3.  3.  3.]]
    Size       : (3, 3)
    Comment 01 : Comments can't go anywhere

    Reading a 3x1D + 3D *Resolve* *.cube* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'resolve_cube',
    ...     'Three_Dimensional_Table_With_Shaper.cube')
    >>> print(read_LUT_ResolveCube(path))
    LUT Sequence
    ------------
    <BLANKLINE>
    Overview
    <BLANKLINE>
        LUT3x1D ---> LUT3D
    <BLANKLINE>
    Operations
    <BLANKLINE>
        LUT3x1D - LUT3D with My Shaper - Shaper
        ---------------------------------------
    <BLANKLINE>
        Dimensions : 2
        Domain     : [[-0.1 -0.1 -0.1]
                      [ 3.   3.   3. ]]
        Size       : (10, 3)
    <BLANKLINE>
        LUT3D - LUT3D with My Shaper - Cube
        -----------------------------------
    <BLANKLINE>
        Dimensions : 3
        Domain     : [[-0.1 -0.1 -0.1]
                      [ 3.   3.   3. ]]
        Size       : (3, 3, 3, 3)
        Comment 01 : A first "Shaper" comment.
        Comment 02 : A second "Shaper" comment.
        Comment 03 : A first "LUT3D" comment.
        Comment 04 : A second "LUT3D" comment.
    """

    title = path_to_title(path)
    size_3x1D = size_3D = 2
    table = []
    comments = []
    has_3x1D, has_3D = False, False

    with open(path) as cube_file:
        lines = cube_file.readlines()
        LUT = LUTSequence(LUT3x1D(), LUT3D())
        for line in lines:
            line = line.strip()

            if len(line) == 0:
                continue

            if line.startswith('#'):
                comments.append(line[1:].strip())
                continue

            tokens = line.split()
            if tokens[0] == 'TITLE':
                title = ' '.join(tokens[1:])[1:-1]
            elif tokens[0] == 'LUT_1D_INPUT_RANGE':
                domain = parse_array(tokens[1:])
                LUT[0].domain = tstack([domain, domain, domain])
            elif tokens[0] == 'LUT_3D_INPUT_RANGE':
                domain = parse_array(tokens[1:])
                LUT[1].domain = tstack([domain, domain, domain])
            elif tokens[0] == 'LUT_1D_SIZE':
                has_3x1D = True
                size_3x1D = np.int_(tokens[1])
            elif tokens[0] == 'LUT_3D_SIZE':
                has_3D = True
                size_3D = np.int_(tokens[1])
            else:
                table.append(parse_array(tokens))

    table = as_float_array(table)
    if has_3x1D and has_3D:
        LUT[0].name = '{0} - Shaper'.format(title)
        LUT[1].name = '{0} - Cube'.format(title)
        LUT[1].comments = comments
        LUT[0].table = table[:size_3x1D]
        # The lines of table data shall be in ascending index order,
        # with the first component index (Red) changing most rapidly,
        # and the last component index (Blue) changing least rapidly.
        LUT[1].table = table[size_3x1D:].reshape(
            (size_3D, size_3D, size_3D, 3), order='F')
        return LUT
    elif has_3x1D:
        LUT[0].name = title
        LUT[0].comments = comments
        LUT[0].table = table
        return LUT[0]
    elif has_3D:
        LUT[1].name = title
        LUT[1].comments = comments
        # The lines of table data shall be in ascending index order,
        # with the first component index (Red) changing most rapidly,
        # and the last component index (Blue) changing least rapidly.
        table = table.reshape([size_3D, size_3D, size_3D, 3], order='F')
        LUT[1].table = table
        return LUT[1]
Ejemplo n.º 4
0
def write_LUT_ResolveCube(LUT, path, decimals=7):
    """
    Writes given *LUT* to given  *Resolve* *.cube* *LUT* file.

    Parameters
    ----------
    LUT : LUT1D or LUT3x1D or LUT3D or LUTSequence
        :class:`LUT1D`, :class:`LUT3x1D` or :class:`LUT3D` or
        :class:`LUTSequence` class instance to write at given path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    References
    ----------
    :cite:`Chamberlain2015`

    Examples
    --------
    Writing a 3x1D *Resolve* *.cube* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([[-0.1, -0.1, -0.1], [3.0, 3.0, 3.0]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_ResolveCube(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3D *Resolve* *.cube* *LUT*:

    >>> domain = np.array([[-0.1, -0.1, -0.1], [3.0, 3.0, 3.0]])
    >>> LUT = LUT3D(
    ...     spow(LUT3D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_ResolveCube(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3x1D + 3D *Resolve* *.cube* *LUT*:

    >>> from colour.models import RGB_to_HSV, HSV_to_RGB
    >>> from colour.utilities import tstack
    >>> def rotate_hue(a, angle):
    ...    H, S, V = RGB_to_HSV(a)
    ...    H += angle / 360
    ...    H[H > 1] -= 1
    ...    H[H < 0] += 1
    ...    return HSV_to_RGB([H, S, V])
    >>> domain = np.array([[-0.1, -0.1, -0.1], [3.0, 3.0, 3.0]])
    >>> shaper = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(10, domain), 1 / 2.2),
    ...     'My Shaper',
    ...     domain,
    ...     comments=[
    ...         'A first "Shaper" comment.', 'A second "Shaper" comment.'])
    >>> LUT = LUT3D(
    ...     rotate_hue(LUT3D.linear_table(3, domain), 10),
    ...     'LUT3D with My Shaper',
    ...     domain,
    ...     comments=['A first "LUT3D" comment.', 'A second "LUT3D" comment.'])
    >>> LUT_sequence = LUTSequence(shaper, LUT)
    >>> write_LUT_ResolveCube(LUT_sequence, 'My_LUT.cube')  # doctest: +SKIP
    """

    has_3D, has_3x1D = False, False

    if isinstance(LUT, LUTSequence):
        assert (len(LUT) == 2 and isinstance(LUT[0], (LUT1D, LUT3x1D))
                and isinstance(LUT[1], LUT3D)), (
                    'LUTSequence must be 1D + 3D or 3x1D + 3D!')

        if isinstance(LUT[0], LUT1D):
            LUT[0] = LUT[0].as_LUT(LUT3x1D)

        has_3x1D = True
        has_3D = True
        name = LUT[1].name
    elif isinstance(LUT, LUT1D):
        name = LUT.name
        LUT = LUTSequence(LUT.as_LUT(LUT3x1D), LUT3D())
        has_3x1D = True
    elif isinstance(LUT, LUT3x1D):
        name = LUT.name
        LUT = LUTSequence(LUT, LUT3D())
        has_3x1D = True
    elif isinstance(LUT, LUT3D):
        name = LUT.name
        LUT = LUTSequence(LUT3x1D(), LUT)
        has_3D = True
    else:
        raise ValueError('LUT must be 1D, 3x1D, 3D, 1D + 3D or 3x1D + 3D!')

    for i in range(2):
        assert not LUT[i].is_domain_explicit(), (
            '"LUT" domain must be implicit!')

    assert (len(np.unique(LUT[0].domain)) == 2
            and len(np.unique(LUT[1].domain)) == 2), 'LUT domain must be 1D!'

    if has_3x1D:
        assert 2 <= LUT[0].size <= 65536, (
            'Shaper size must be in domain [2, 65536]!')
    if has_3D:
        assert 2 <= LUT[1].size <= 256, 'Cube size must be in domain [2, 256]!'

    def _format_array(array):
        """
        Formats given array as a *Resolve* *.cube* data row.
        """

        return '{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array)

    def _format_tuple(array):
        """
        Formats given array as 2 space separated values to *decimals*
        precision.
        """

        return '{1:0.{0}f} {2:0.{0}f}'.format(decimals, *array)

    with open(path, 'w') as cube_file:
        cube_file.write('TITLE "{0}"\n'.format(name))

        if LUT[0].comments:
            for comment in LUT[0].comments:
                cube_file.write('# {0}\n'.format(comment))

        if LUT[1].comments:
            for comment in LUT[1].comments:
                cube_file.write('# {0}\n'.format(comment))

        default_domain = np.array([[0, 0, 0], [1, 1, 1]])

        if has_3x1D:
            cube_file.write('{0} {1}\n'.format('LUT_1D_SIZE',
                                               LUT[0].table.shape[0]))
            if not np.array_equal(LUT[0].domain, default_domain):
                cube_file.write('LUT_1D_INPUT_RANGE {0}\n'.format(
                    _format_tuple([LUT[0].domain[0][0], LUT[0].domain[1][0]])))

        if has_3D:
            cube_file.write('{0} {1}\n'.format('LUT_3D_SIZE',
                                               LUT[1].table.shape[0]))
            if not np.array_equal(LUT[1].domain, default_domain):
                cube_file.write('LUT_3D_INPUT_RANGE {0}\n'.format(
                    _format_tuple([LUT[1].domain[0][0], LUT[1].domain[1][0]])))

        if has_3x1D:
            table = LUT[0].table
            for row in table:
                cube_file.write('{0}\n'.format(_format_array(row)))
            cube_file.write('\n')

        if has_3D:
            table = LUT[1].table.reshape([-1, 3], order='F')
            for row in table:
                cube_file.write('{0}\n'.format(_format_array(row)))

    return True
Ejemplo n.º 5
0
def read_LUT_Cinespace(path):
    """
    Reads given *Cinespace* *.csp* *LUT* file.

    Parameters
    ----------
    path : unicode
        *LUT* path.

    Returns
    -------
    LUT3x1D or LUT3D or LUTSequence
        :class:`LUT3x1D` or :class:`LUT3D` or :class:`LUTSequence` class
        instance.

    References
    ----------
    :cite:`RisingSunResearch`

    Examples
    --------
    Reading a 3x1D *Cinespace* *.csp* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'cinespace',
    ...     'ACES_Proxy_10_to_ACES.csp')
    >>> print(read_LUT_Cinespace(path))
    LUT3x1D - ACES Proxy 10 to ACES
    -------------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (32, 3)

    Reading a 3D *Cinespace* *.csp* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'cinespace',
    ...     'ColourCorrect.csp')
    >>> print(read_LUT_Cinespace(path))
    LUT3D - Generated by Foundry::LUT
    ---------------------------------
    <BLANKLINE>
    Dimensions : 3
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (4, 4, 4, 3)
    """

    unity_range = np.array([[0., 0., 0.], [1., 1., 1.]])

    def _parse_metadata_section(lines):
        """
        Parses the metadata at given lines.
        """

        if len(metadata) > 0:
            title = metadata[0]
            comments = metadata[1:]
        else:
            title = ''
            comments = []

        return title, comments

    def _parse_domain_section(lines):
        """
        Parses the domain at given lines.
        """

        pre_LUT_size = max([int(lines[i]) for i in [0, 3, 6]])
        pre_LUT = [parse_array(lines[i]) for i in [1, 2, 4, 5, 7, 8]]
        pre_LUT_padded = []

        for row in pre_LUT:
            if len(row) != pre_LUT_size:
                pre_LUT_padded.append(
                    np.pad(
                        row, (0, pre_LUT_size - row.shape[0]),
                        mode='constant',
                        constant_values=np.nan))
            else:
                pre_LUT_padded.append(row)
        pre_LUT = np.asarray(pre_LUT_padded)

        return pre_LUT

    def _parse_table_section(lines):
        """
        Parses the table at given lines.
        """

        size = parse_array(lines[0]).astype(int)
        table = np.array([parse_array(line) for line in lines[1:]])

        return size, table

    with open(path) as csp_file:
        lines = csp_file.readlines()
        assert len(lines) > 0, 'LUT file empty!'
        lines = [line.strip() for line in lines if line.strip()]

        header = lines[0]
        assert header == 'CSPLUTV100', 'Invalid header!'

        kind = lines[1]
        assert kind in ('1D', '3D'), 'Invalid kind!'

        is_3D = kind == '3D'

        seek = 2
        metadata = []
        is_metadata = False
        for i, line in enumerate(lines[2:]):
            line = line.strip()
            if line == 'BEGIN METADATA':
                is_metadata = True
                continue
            elif line == 'END METADATA':
                seek += i
                break

            if is_metadata:
                metadata.append(line)

        title, comments = _parse_metadata_section(metadata)

        seek += 1
        pre_LUT = _parse_domain_section(lines[seek:seek + 9])

        seek += 9
        size, table = _parse_table_section(lines[seek:])

        assert np.product(size) == len(table), 'Invalid table size!'

        if (is_3D and pre_LUT.shape == (6, 2) and np.array_equal(
                pre_LUT.reshape(3, 4).transpose()[2:4], unity_range)):
            table = table.reshape((size[0], size[1], size[2], 3), order='F')
            LUT = LUT3D(
                domain=pre_LUT.reshape(3, 4).transpose()[0:2],
                name=title,
                comments=comments,
                table=table)
            return LUT

        if (not is_3D and pre_LUT.shape == (6, 2) and np.array_equal(
                pre_LUT.reshape(3, 4).transpose()[2:4], unity_range)):
            LUT = LUT3x1D(
                domain=pre_LUT.reshape(3, 4).transpose()[0:2],
                name=title,
                comments=comments,
                table=table)

            return LUT

        if is_3D:
            pre_domain = tstack((pre_LUT[0], pre_LUT[2], pre_LUT[4]))
            pre_table = tstack((pre_LUT[1], pre_LUT[3], pre_LUT[5]))
            shaper_name = '{0} - Shaper'.format(title)
            cube_name = '{0} - Cube'.format(title)
            table = table.reshape((size[0], size[1], size[2], 3), order='F')
            LUT_A = LUT3x1D(pre_table, shaper_name, pre_domain)
            LUT_B = LUT3D(table, cube_name, comments=comments)

            return LUTSequence(LUT_A, LUT_B)

        if not is_3D:
            pre_domain = tstack((pre_LUT[0], pre_LUT[2], pre_LUT[4]))
            pre_table = tstack((pre_LUT[1], pre_LUT[3], pre_LUT[5]))

            if np.array_equal(table, unity_range):
                return LUT3x1D(pre_table, title, pre_domain, comments=comments)
            elif table.shape == (2, 3):
                table_max = table[1]
                table_min = table[0]
                pre_table *= (table_max - table_min)
                pre_table += table_min

                return LUT3x1D(pre_table, title, pre_domain, comments=comments)
            else:
                pre_name = '{0} - preLUT'.format(title)
                table_name = '{0} - table'.format(title)
                LUT_A = LUT3x1D(pre_table, pre_name, pre_domain)
                LUT_B = LUT3x1D(table, table_name, comments=comments)

                return LUTSequence(LUT_A, LUT_B)
Ejemplo n.º 6
0
def write_LUT_Cinespace(LUT, path, decimals=7):
    """
    Writes given *LUT* to given  *Cinespace* *.csp* *LUT* file.

    Parameters
    ----------
    LUT : LUT1D or LUT3x1D or LUT3D or LUTSequence
        :class:`LUT1D`, :class:`LUT3x1D` or :class:`LUT3D` or
        :class:`LUTSequence` class instance to write at given path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    References
    ----------
    :cite:`RisingSunResearch`

    Examples
    --------
    Writing a 3x1D *Cinespace* *.csp* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_Cinespace(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3D *Cinespace* *.csp* *LUT*:

    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3D(
    ...     spow(LUT3D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_Cinespace(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    has_3D, has_3x1D, non_uniform = False, False, False

    if isinstance(LUT, LUTSequence):
        assert (len(LUT) == 2 and
                (isinstance(LUT[0], LUT1D) or isinstance(LUT[0], LUT3x1D)) and
                isinstance(LUT[1],
                           LUT3D)), 'LUTSequence must be 1D+3D or 3x1D+3D!'
        has_3x1D = True
        has_3D = True
        name = LUT[1].name
        if isinstance(LUT[0], LUT1D):
            LUT[0] = LUT[0].as_LUT(LUT3x1D)

    elif isinstance(LUT, LUT1D):
        if LUT.is_domain_explicit():
            non_uniform = True
        name = LUT.name
        LUT = LUTSequence(LUT.as_LUT(LUT3x1D), LUT3D())
        has_3x1D = True

    elif isinstance(LUT, LUT3x1D):
        if LUT.is_domain_explicit():
            non_uniform = True
        name = LUT.name
        LUT = LUTSequence(LUT, LUT3D())
        has_3x1D = True

    elif isinstance(LUT, LUT3D):
        name = LUT.name
        LUT = LUTSequence(LUT3x1D(), LUT)
        has_3D = True

    else:
        assert False, 'LUT must be 1D, 3x1D, 3D, 1D+3D or 3x1D+3D!'

    if has_3x1D:
        assert 2 <= LUT[0].size <= 65536, (
            'Shaper size must be in domain [2, 65536]!')
    if has_3D:
        assert 2 <= LUT[1].size <= 256, 'Cube size must be in domain [2, 256]!'

    def _ragged_size(table):
        """
        Return the ragged size of given table.
        """

        r, g, b = tsplit(table)
        r_len = r.shape[-1] - np.sum(np.isnan(r))
        g_len = g.shape[-1] - np.sum(np.isnan(g))
        b_len = b.shape[-1] - np.sum(np.isnan(b))

        return [r_len, g_len, b_len]

    def _format_array(array):
        """
        Formats given array as a *Cinespace* *.cube* data row.
        """

        return '{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array)

    def _format_tuple(array):
        """
        Formats given array as 2 space separated values to *decimals*
        precision.
        """

        return '{1:0.{0}f} {2:0.{0}f}'.format(decimals, *array)

    with open(path, 'w') as csp_file:
        csp_file.write('CSPLUTV100\n')

        if has_3D:
            csp_file.write('3D\n\n')
        else:
            csp_file.write('1D\n\n')

        csp_file.write('BEGIN METADATA\n')
        csp_file.write('{0}\n'.format(name))

        if LUT[0].comments:
            for comment in LUT[0].comments:
                csp_file.write('{0}\n'.format(comment))

        if LUT[1].comments:
            for comment in LUT[1].comments:
                csp_file.write('{0}\n'.format(comment))

        csp_file.write('END METADATA\n\n')

        if has_3D or non_uniform:
            if has_3x1D:
                for i in range(3):
                    if LUT[0].is_domain_explicit():
                        size = _ragged_size(LUT[0].domain)[i]
                        table_min = np.nanmin(LUT[0].table)
                        table_max = np.nanmax(LUT[0].table)
                    else:
                        size = LUT[0].size

                    csp_file.write('{0}\n'.format(size))

                    for j in range(size):
                        if LUT[0].is_domain_explicit():
                            entry = LUT[0].domain[j][i]
                        else:
                            entry = (
                                LUT[0].domain[0][i] + j *
                                (LUT[0].domain[1][i] - LUT[0].domain[0][i]) /
                                (LUT[0].size - 1))
                        csp_file.write('{0:.{1}f} '.format(entry, decimals))

                    csp_file.write('\n')

                    for j in range(size):
                        entry = LUT[0].table[j][i]
                        if non_uniform:
                            entry -= table_min
                            entry /= (table_max - table_min)
                        csp_file.write('{0:.{1}f} '.format(entry, decimals))

                    csp_file.write('\n')
            else:
                for i in range(3):
                    csp_file.write('2\n')
                    csp_file.write('{0}\n'.format(
                        _format_tuple(
                            [LUT[1].domain[0][i], LUT[1].domain[1][i]])))
                    csp_file.write('{0:.{2}f} {1:.{2}f}\n'.format(
                        0, 1, decimals))
            if non_uniform:
                csp_file.write('\n{0}\n'.format(2))
                row = [table_min, table_min, table_min]
                csp_file.write('{0}\n'.format(_format_array(row)))
                row = [table_max, table_max, table_max]
                csp_file.write('{0}\n'.format(_format_array(row)))
            else:
                csp_file.write('\n{0} {1} {2}\n'.format(
                    LUT[1].table.shape[0], LUT[1].table.shape[1],
                    LUT[1].table.shape[2]))
                table = LUT[1].table.reshape((-1, 3), order='F')

                for row in table:
                    csp_file.write('{0}\n'.format(_format_array(row)))

        else:
            for i in range(3):
                csp_file.write('2\n')
                csp_file.write('{0}\n'.format(
                    _format_tuple([LUT[0].domain[0][i], LUT[0].domain[1][i]])))
                csp_file.write('0.0 1.0\n')
            csp_file.write('\n{0}\n'.format(LUT[0].size))
            table = LUT[0].table

            for row in table:
                csp_file.write('{0}\n'.format(_format_array(row)))

    return True
Ejemplo n.º 7
0
def read_LUT_SonySPI1D(path):
    """
    Reads given *Sony* *.spi1d* *LUT* file.

    Parameters
    ----------
    path : unicode
        *LUT* path.

    Returns
    -------
    LUT1D or LUT3x1D
        :class:`LUT1D` or :class:`LUT3x1D` class instance.

    Examples
    --------
    Reading a 1D *Sony* *.spi1d* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d',
    ...     'oetf_reverse_sRGB_1D.spi1d')
    >>> print(read_LUT_SonySPI1D(path))
    LUT1D - oetf reverse sRGB 1D
    ----------------------------
    <BLANKLINE>
    Dimensions : 1
    Domain     : [-0.1  1.5]
    Size       : (16,)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.oetf_reverse_sRGB".

    Reading a 3x1D *Sony* *.spi1d* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d',
    ...     'oetf_reverse_sRGB_3x1D.spi1d')
    >>> print(read_LUT_SonySPI1D(path))
    LUT3x1D - oetf reverse sRGB 3x1D
    --------------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[-0.1 -0.1 -0.1]
                  [ 1.5  1.5  1.5]]
    Size       : (16, 3)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.oetf_reverse_sRGB".
    """

    title = path_to_title(path)
    domain_min, domain_max = np.array([0, 1])
    dimensions = 1
    table = []

    comments = []

    with open(path) as spi1d_file:
        lines = spi1d_file.readlines()
        for line in lines:
            line = line.strip()

            if len(line) == 0:
                continue

            if line.startswith('#'):
                comments.append(line[1:].strip())
                continue

            tokens = line.split()
            if tokens[0] == 'Version':
                continue
            if tokens[0] == 'From':
                domain_min, domain_max = parse_array(tokens[1:])
            elif tokens[0] == 'Length':
                continue
            elif tokens[0] == 'Components':
                component = DEFAULT_INT_DTYPE(tokens[1])
                assert component in (1, 3), (
                    'Only 1 or 3 components are supported!')

                dimensions = 1 if component == 1 else 2
            elif tokens[0] in ('{', '}'):
                continue
            else:
                table.append(parse_array(tokens))

    table = as_float_array(table)
    if dimensions == 1:
        return LUT1D(np.squeeze(table),
                     title,
                     np.array([domain_min, domain_max]),
                     comments=comments)
    elif dimensions == 2:
        return LUT3x1D(table,
                       title,
                       np.array([[domain_min, domain_min, domain_min],
                                 [domain_max, domain_max, domain_max]]),
                       comments=comments)
Ejemplo n.º 8
0
def read_LUT_Cinespace(path: str) -> Union[LUT3x1D, LUT3D, LUTSequence]:
    """
    Read given *Cinespace* *.csp* *LUT* file.

    Parameters
    ----------
    path
        *LUT* path.

    Returns
    -------
    :class:`colour.LUT3x1D` or :class:`colour.LUT3D` or \
:class:`colour.LUTSequence`
        :class:`LUT3x1D` or :class:`LUT3D` or :class:`LUTSequence` class
        instance.

    References
    ----------
    :cite:`RisingSunResearch`

    Examples
    --------
    Reading a 3x1D *Cinespace* *.csp* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'cinespace',
    ...     'ACES_Proxy_10_to_ACES.csp')
    >>> print(read_LUT_Cinespace(path))
    LUT3x1D - ACES Proxy 10 to ACES
    -------------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (32, 3)

    Reading a 3D *Cinespace* *.csp* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'cinespace',
    ...     'Colour_Correct.csp')
    >>> print(read_LUT_Cinespace(path))
    LUT3D - Generated by Foundry::LUT
    ---------------------------------
    <BLANKLINE>
    Dimensions : 3
    Domain     : [[ 0.  0.  0.]
                  [ 1.  1.  1.]]
    Size       : (4, 4, 4, 3)
    """

    unity_range = np.array([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]])

    def _parse_metadata_section(metadata: List) -> Tuple:
        """Parse the metadata at given lines."""

        return (metadata[0], metadata[1:]) if len(metadata) > 0 else ("", [])

    def _parse_domain_section(lines: List[str]) -> NDArray:
        """Parse the domain at given lines."""

        pre_LUT_size = max(int(lines[i]) for i in [0, 3, 6])
        pre_LUT = [
            as_float_array(lines[i].split()) for i in [1, 2, 4, 5, 7, 8]
        ]

        pre_LUT_padded = []
        for row in pre_LUT:
            if len(row) != pre_LUT_size:
                pre_LUT_padded.append(
                    np.pad(
                        row,
                        (0, pre_LUT_size - row.shape[0]),
                        mode="constant",
                        constant_values=np.nan,
                    ))
            else:
                pre_LUT_padded.append(row)

        return np.asarray(pre_LUT_padded)

    def _parse_table_section(lines):
        """Parse the table at given lines."""

        size = as_int_array(lines[0].split())
        table = as_float_array([line.split() for line in lines[1:]])

        return size, table

    with open(path) as csp_file:
        lines = csp_file.readlines()
        attest(len(lines) > 0, '"LUT" is empty!')
        lines = [line.strip() for line in lines if line.strip()]

        header = lines[0]
        attest(header == "CSPLUTV100", '"LUT" header is invalid!')

        kind = lines[1]
        attest(kind in ("1D", "3D"), '"LUT" type must be "1D" or "3D"!')

        is_3D = kind == "3D"

        seek = 2
        metadata = []
        is_metadata = False
        for i, line in enumerate(lines[2:]):
            line = line.strip()
            if line == "BEGIN METADATA":
                is_metadata = True
                continue
            elif line == "END METADATA":
                seek += i
                break

            if is_metadata:
                metadata.append(line)

        title, comments = _parse_metadata_section(metadata)

        seek += 1
        pre_LUT = _parse_domain_section(lines[seek:seek + 9])

        seek += 9
        size, table = _parse_table_section(lines[seek:])

        attest(np.product(size) == len(table), '"LUT" table size is invalid!')

    LUT: Union[LUT3x1D, LUT3D, LUTSequence]
    if (is_3D and pre_LUT.shape == (6, 2) and np.array_equal(
            np.transpose(np.reshape(pre_LUT, (3, 4)))[2:4], unity_range)):
        table = table.reshape([size[0], size[1], size[2], 3], order="F")
        LUT = LUT3D(
            domain=np.transpose(np.reshape(pre_LUT, (3, 4)))[0:2],
            name=title,
            comments=comments,
            table=table,
        )

    elif (not is_3D and pre_LUT.shape == (6, 2) and np.array_equal(
            np.transpose(np.reshape(pre_LUT, (3, 4)))[2:4], unity_range)):
        LUT = LUT3x1D(
            domain=pre_LUT.reshape(3, 4).transpose()[0:2],
            name=title,
            comments=comments,
            table=table,
        )

    elif is_3D:
        pre_domain = tstack((pre_LUT[0], pre_LUT[2], pre_LUT[4]))
        pre_table = tstack((pre_LUT[1], pre_LUT[3], pre_LUT[5]))
        shaper_name = f"{title} - Shaper"
        cube_name = f"{title} - Cube"
        table = table.reshape([size[0], size[1], size[2], 3], order="F")

        LUT = LUTSequence(
            LUT3x1D(pre_table, shaper_name, pre_domain),
            LUT3D(table, cube_name, comments=comments),
        )

    elif not is_3D:
        pre_domain = tstack((pre_LUT[0], pre_LUT[2], pre_LUT[4]))
        pre_table = tstack((pre_LUT[1], pre_LUT[3], pre_LUT[5]))

        if table.shape == (2, 3):
            table_max = table[1]
            table_min = table[0]
            pre_table *= table_max - table_min
            pre_table += table_min

            LUT = LUT3x1D(pre_table, title, pre_domain, comments=comments)
        else:
            pre_name = f"{title} - PreLUT"
            table_name = f"{title} - Table"

            LUT = LUTSequence(
                LUT3x1D(pre_table, pre_name, pre_domain),
                LUT3x1D(table, table_name, comments=comments),
            )

    return LUT
Ejemplo n.º 9
0
def write_LUT_Cinespace(LUT: Union[LUT3x1D, LUT3D, LUTSequence],
                        path: str,
                        decimals: Integer = 7) -> Boolean:
    """
    Write given *LUT* to given  *Cinespace* *.csp* *LUT* file.

    Parameters
    ----------
    LUT
        :class:`LUT1D`, :class:`LUT3x1D` or :class:`LUT3D` or
        :class:`LUTSequence` class instance to write at given path.
    path
        *LUT* path.
    decimals
        Formatting decimals.

    Returns
    -------
    :class:`bool`
        Definition success.

    References
    ----------
    :cite:`RisingSunResearch`

    Examples
    --------
    Writing a 3x1D *Cinespace* *.csp* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_Cinespace(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3D *Cinespace* *.csp* *LUT*:

    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3D(
    ...     spow(LUT3D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_Cinespace(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    has_3D, has_3x1D = False, False

    if isinstance(LUT, LUTSequence):
        attest(
            len(LUT) == 2 and isinstance(LUT[0], (LUT1D, LUT3x1D))
            and isinstance(LUT[1], LUT3D),
            '"LUTSequence" must be "1D + 3D" or "3x1D + 3D"!',
        )
        LUT[0] = (LUT[0].as_LUT(LUT3x1D)
                  if isinstance(LUT[0], LUT1D) else LUT[0])
        name = f"{LUT[0].name} - {LUT[1].name}"
        has_3x1D = True
        has_3D = True

    elif isinstance(LUT, LUT1D):
        name = LUT.name
        has_3x1D = True
        LUT = LUTSequence(LUT.as_LUT(LUT3x1D), LUT3D())

    elif isinstance(LUT, LUT3x1D):
        name = LUT.name
        has_3x1D = True
        LUT = LUTSequence(LUT, LUT3D())

    elif isinstance(LUT, LUT3D):
        name = LUT.name
        has_3D = True
        LUT = LUTSequence(LUT3x1D(), LUT)

    else:
        raise ValueError("LUT must be 1D, 3x1D, 3D, 1D + 3D or 3x1D + 3D!")

    if has_3x1D:
        attest(
            2 <= LUT[0].size <= 65536,
            "Shaper size must be in domain [2, 65536]!",
        )
    if has_3D:
        attest(2 <= LUT[1].size <= 256,
               "Cube size must be in domain [2, 256]!")

    def _ragged_size(table: ArrayLike) -> List:
        """Return the ragged size of given table."""

        R, G, B = tsplit(table)

        R_len = R.shape[-1] - np.sum(np.isnan(R))
        G_len = G.shape[-1] - np.sum(np.isnan(G))
        B_len = B.shape[-1] - np.sum(np.isnan(B))

        return [R_len, G_len, B_len]

    def _format_array(array: Union[List, Tuple]) -> str:
        """Format given array as a *Cinespace* *.cube* data row."""

        return "{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}".format(decimals, *array)

    def _format_tuple(array: Union[List, Tuple]) -> str:
        """
        Format given array as 2 space separated values to *decimals*
        precision.
        """

        return "{1:0.{0}f} {2:0.{0}f}".format(decimals, *array)

    with open(path, "w") as csp_file:
        csp_file.write("CSPLUTV100\n")

        if has_3D:
            csp_file.write("3D\n\n")
        else:
            csp_file.write("1D\n\n")

        csp_file.write("BEGIN METADATA\n")
        csp_file.write(f"{name}\n")

        if LUT[0].comments:
            for comment in LUT[0].comments:
                csp_file.write(f"{comment}\n")

        if LUT[1].comments:
            for comment in LUT[1].comments:
                csp_file.write(f"{comment}\n")

        csp_file.write("END METADATA\n\n")

        if has_3D:
            if has_3x1D:
                for i in range(3):
                    size = (_ragged_size(LUT[0].domain)[i]
                            if LUT[0].is_domain_explicit() else LUT[0].size)

                    csp_file.write(f"{size}\n")

                    for j in range(size):
                        entry = (LUT[0].domain[j][i]
                                 if LUT[0].is_domain_explicit() else
                                 (LUT[0].domain[0][i] + j *
                                  (LUT[0].domain[1][i] - LUT[0].domain[0][i]) /
                                  (LUT[0].size - 1)))

                        csp_file.write("{0:.{1}f} ".format(entry, decimals))

                    csp_file.write("\n")

                    for j in range(size):
                        entry = LUT[0].table[j][i]
                        csp_file.write("{0:.{1}f} ".format(entry, decimals))

                    csp_file.write("\n")
            else:
                for i in range(3):
                    csp_file.write("2\n")
                    domain = _format_tuple(
                        [LUT[1].domain[0][i], LUT[1].domain[1][i]])
                    csp_file.write(f"{domain}\n")
                    csp_file.write("{0:.{2}f} {1:.{2}f}\n".format(
                        0, 1, decimals))

            csp_file.write(f"\n{LUT[1].table.shape[0]} "
                           f"{LUT[1].table.shape[1]} "
                           f"{LUT[1].table.shape[2]}\n")
            table = LUT[1].table.reshape([-1, 3], order="F")

            for row in table:
                csp_file.write(f"{_format_array(row)}\n")
        else:
            for i in range(3):
                csp_file.write("2\n")
                domain = _format_tuple(
                    [LUT[0].domain[0][i], LUT[0].domain[1][i]])
                csp_file.write(f"{domain}\n")
                csp_file.write("0.0 1.0\n")
            csp_file.write(f"\n{LUT[0].size}\n")
            table = LUT[0].table

            for row in table:
                csp_file.write(f"{_format_array(row)}\n")

    return True
Ejemplo n.º 10
0
def read_LUT_SonySPI1D(path: str) -> Union[LUT1D, LUT3x1D]:
    """
    Read given *Sony* *.spi1d* *LUT* file.

    Parameters
    ----------
    path
        *LUT* path.

    Returns
    -------
    :class:`colour.LUT1D` or :class:`colour.LUT3x1D`
        :class:`LUT1D` or :class:`LUT3x1D` class instance.

    Examples
    --------
    Reading a 1D *Sony* *.spi1d* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d',
    ...     'eotf_sRGB_1D.spi1d')
    >>> print(read_LUT_SonySPI1D(path))
    LUT1D - eotf sRGB 1D
    --------------------
    <BLANKLINE>
    Dimensions : 1
    Domain     : [-0.1  1.5]
    Size       : (16,)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.eotf_sRGB".

    Reading a 3x1D *Sony* *.spi1d* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d',
    ...     'eotf_sRGB_3x1D.spi1d')
    >>> print(read_LUT_SonySPI1D(path))
    LUT3x1D - eotf sRGB 3x1D
    ------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[-0.1 -0.1 -0.1]
                  [ 1.5  1.5  1.5]]
    Size       : (16, 3)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.eotf_sRGB".
    """

    title = path_to_title(path)
    domain_min, domain_max = np.array([0, 1])
    dimensions = 1
    data = []

    comments = []

    with open(path) as spi1d_file:
        lines = filter(None, (line.strip() for line in spi1d_file.readlines()))
        for line in lines:
            if line.startswith("#"):
                comments.append(line[1:].strip())
                continue

            tokens = line.split()
            if tokens[0] == "Version":
                continue
            if tokens[0] == "From":
                domain_min, domain_max = as_float_array(tokens[1:])
            elif tokens[0] == "Length":
                continue
            elif tokens[0] == "Components":
                component = as_int_scalar(tokens[1])
                attest(
                    component in (1, 3),
                    "Only 1 or 3 components are supported!",
                )

                dimensions = 1 if component == 1 else 2
            elif tokens[0] in ("{", "}"):
                continue
            else:
                data.append(tokens)

    table = as_float_array(data)

    LUT: Union[LUT1D, LUT3x1D]
    if dimensions == 1:
        LUT = LUT1D(
            np.squeeze(table),
            title,
            np.array([domain_min, domain_max]),
            comments=comments,
        )
    elif dimensions == 2:
        LUT = LUT3x1D(
            table,
            title,
            np.array([
                [domain_min, domain_min, domain_min],
                [domain_max, domain_max, domain_max],
            ]),
            comments=comments,
        )

    return LUT