示例#1
0
def adjust_image(image, target_width=WORKING_WIDTH):
    """
    Adjusts given image so that it is horizontal and resizes it to given target
    width.

    Parameters
    ----------
    image : array_like
        Image to adjust.
    target_width : int, optional
        Width the image is resized to.

    Returns
    -------
    ndarray
        Resized image.
    """

    width, height = image.shape[1], image.shape[0]
    if width < height:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        height, width = width, height

    ratio = width / target_width

    if np.allclose(ratio, 1):
        return image
    else:
        return cv2.resize(image,
                          (as_int(target_width), as_int(height / ratio)),
                          interpolation=cv2.INTER_CUBIC)
示例#2
0
def adjust_image(image, target_width=WORKING_WIDTH):
    """
    Adjusts given image so that it is horizontal and resizes it to given target
    width.

    Parameters
    ----------
    image : array_like
        Image to adjust.
    target_width : int, optional
        Width the image is resized to.

    Returns
    -------
    ndarray
        Resized image.

    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [2, 4, 3])
    >>> adjust_image(image, 5)  # doctest: +ELLIPSIS
    array([[[ 0.9823518...,  0.5380895...,  1.0186476...],
            [ 0.7563578...,  0.731978 ...,  0.4231120...],
            [ 0.8726642...,  0.2936055...,  0.1687892...],
            [ 0.8540266...,  0.1457020...,  0.2416218...],
            [ 0.4018965...,  0.8263517...,  0.1943257...]],
    <BLANKLINE>
           [[ 0.8791320...,  1.0425965...,  0.1503114...],
            [ 0.7324403...,  0.1763674...,  0.3189642...],
            [ 0.2110148...,  0.4074382...,  0.3919139...],
            [ 0.2356165...,  1.0136062...,  0.5616219...],
            [ 1.0039447...,  0.7759574...,  0.8924203...]]])
    """

    width, height = image.shape[1], image.shape[0]
    if width < height:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        height, width = width, height

    ratio = width / target_width

    if np.allclose(ratio, 1):
        return image
    else:
        return cv2.resize(image,
                          (as_int(target_width), as_int(height / ratio)),
                          interpolation=cv2.INTER_CUBIC)
示例#3
0
def adjust_image(image, target_width=WORKING_WIDTH):
    """
    Adjusts given image so that it is horizontal and resizes it to given target
    width.

    Parameters
    ----------
    image : array_like
        Image to adjust.
    target_width : int, optional
        Width the image is resized to.

    Returns
    -------
    ndarray
        Resized image.

    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [2, 4, 3])
    >>> adjust_image(image, 5)  # doctest: +ELLIPSIS
    array([[[ 0.9925326...,  0.2419374..., -0.0139522...],
            [ 0.6174496...,  0.3460755...,  0.3189758...],
            [ 0.7447774...,  0.6786660...,  0.1652180...],
            [ 0.9476451...,  0.6550805...,  0.2609945...],
            [ 0.6991505...,  0.1623470...,  1.0120867...]],
    <BLANKLINE>
           [[ 0.7269885...,  0.8556784...,  0.4049920...],
            [ 0.2666564...,  1.0401633...,  0.8238320...],
            [ 0.6419699...,  0.5442698...,  0.9082210...],
            [ 0.7894426...,  0.1944301...,  0.7906868...],
            [-0.0526997...,  0.6236684...,  0.8711482...]]])
    """

    width, height = image.shape[1], image.shape[0]
    if width < height:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        height, width = width, height

    ratio = width / target_width

    if np.allclose(ratio, 1):
        return image
    else:
        return cv2.resize(image,
                          (as_int(target_width), as_int(height / ratio)),
                          interpolation=cv2.INTER_CUBIC)
示例#4
0
    def test_as_int(self):
        """Test :func:`colour.utilities.array.as_int` definition."""

        self.assertEqual(as_int(1), 1)

        self.assertEqual(as_int(np.array([1])), 1)

        np.testing.assert_almost_equal(as_int(np.array([1.0, 2.0, 3.0])),
                                       np.array([1, 2, 3]))

        self.assertEqual(
            as_int(np.array([1.0, 2.0, 3.0])).dtype, DEFAULT_INT_DTYPE)

        self.assertIsInstance(as_int(1), DEFAULT_INT_DTYPE)
示例#5
0
    def test_transfer_functions(self):
        """
        Tests transfer functions reciprocity.
        """

        ignored_transfer_functions = ('ACESproxy', 'DICOM GSDF',
                                      'Filmic Pro 6')

        decimals = {'D-Log': 1, 'F-Log': 4}

        reciprocal_mappings = [
            (LOG_ENCODINGS, LOG_DECODINGS),
            (OETFS, OETF_INVERSES),
            (EOTFS, EOTF_INVERSES),
            (CCTF_ENCODINGS, CCTF_DECODINGS),
            (OOTFS, OOTF_INVERSES),
        ]

        samples = np.hstack([
            np.linspace(0, 1, as_int(1e5)),
            np.linspace(0, 65504, 65504 * 10)
        ])

        for encoding_mapping, _decoding_mapping in reciprocal_mappings:
            for name in encoding_mapping:
                if name in ignored_transfer_functions:
                    continue

                encoded_s = CCTF_ENCODINGS[name](samples)
                decoded_s = CCTF_DECODINGS[name](encoded_s)

                np.testing.assert_almost_equal(samples,
                                               decoded_s,
                                               decimal=decimals.get(name, 7))
示例#6
0
    def test_cctf(self):
        """
        Tests colour component transfer functions from the
        :attr:`colour.models.rgb.rgb_colourspace.RGB_COLOURSPACES` attribute
        colourspace models.
        """

        ignored_colourspaces = ('ACESproxy', )

        decimals = {'DJI D-Gamut': 1, 'F-Gamut': 4}

        samples = np.hstack([
            np.linspace(0, 1, as_int(1e5)),
            np.linspace(0, 65504, 65504 * 10)
        ])

        for colourspace in RGB_COLOURSPACES.values():
            if colourspace.name in ignored_colourspaces:
                continue

            cctf_encoding_s = colourspace.cctf_encoding(samples)
            cctf_decoding_s = colourspace.cctf_decoding(cctf_encoding_s)

            np.testing.assert_almost_equal(samples,
                                           cctf_decoding_s,
                                           decimal=decimals.get(
                                               colourspace.name, 7))
示例#7
0
    def test_as_int(self):
        """
        Tests :func:`colour.utilities.array.as_int` definition.
        """

        self.assertEqual(as_int(1), 1)

        self.assertEqual(as_int(np.array([1])), 1)

        np.testing.assert_almost_equal(
            as_int(np.array([1.0, 2.0, 3.0])), np.array([1, 2, 3]))

        self.assertEqual(
            as_int(np.array([1.0, 2.0, 3.0])).dtype, DEFAULT_INT_DTYPE)

        self.assertIsInstance(as_int(1), int)
示例#8
0
def swatch_masks(
    width: Integer,
    height: Integer,
    swatches_h: Integer,
    swatches_v: Integer,
    samples: Integer,
) -> Tuple[NDArray, ...]:
    """
    Return swatch masks for given image width and height and swatches count.

    Parameters
    ----------
    width
        Image width.
    height
        Image height.
    swatches_h
        Horizontal swatches count.
    swatches_v
        Vertical swatches count.
    samples
        Samples count.

    Returns
    -------
    :class:`tuple`
        Tuple of swatch masks.

    Examples
    --------
    >>> from pprint import pprint
    >>> pprint(swatch_masks(16, 8, 4, 2, 1))  # doctest: +ELLIPSIS
    (array([2, 2, 2, 2]...),
     array([2, 2, 6, 6]...),
     array([ 2,  2, 10, 10]...),
     array([ 2,  2, 14, 14]...),
     array([6, 6, 2, 2]...),
     array([6, 6, 6, 6]...),
     array([ 6,  6, 10, 10]...),
     array([ 6,  6, 14, 14]...))
    """

    samples_half = as_int(samples / 2)

    masks = []
    offset_h = width / swatches_h / 2
    offset_v = height / swatches_v / 2
    for j in np.linspace(offset_v, height - offset_v, swatches_v):
        for i in np.linspace(offset_h, width - offset_h, swatches_h):
            masks.append(
                as_int_array([
                    j - samples_half,
                    j + samples_half,
                    i - samples_half,
                    i + samples_half,
                ]))

    return tuple(masks)
示例#9
0
def swatch_masks(width, height, swatches_h, swatches_v, samples):
    """
    Returns swatch masks for given image width and height and swatches count.

    Parameters
    ----------
    width : int
        Image width.
    height : height
        Image height.
    swatches_h : int
        Horizontal swatches count.
    swatches_v : int
        Vertical swatches count.
    samples : int
        Samples count.

    Returns
    -------
    list
        List of swatch masks.

    Examples
    --------
    >>> from pprint import pprint
    >>> pprint(swatch_masks(16, 8, 4, 2, 1))
    [array([2, 2, 2, 2]),
     array([2, 2, 6, 6]),
     array([ 2,  2, 10, 10]),
     array([ 2,  2, 14, 14]),
     array([6, 6, 2, 2]),
     array([6, 6, 6, 6]),
     array([ 6,  6, 10, 10]),
     array([ 6,  6, 14, 14])]
    """

    samples = as_int(samples / 2)

    masks = []
    offset_h = width / swatches_h / 2
    offset_v = height / swatches_v / 2
    for j in np.linspace(offset_v, height - offset_v, swatches_v):
        for i in np.linspace(offset_h, width - offset_h, swatches_h):
            masks.append(
                as_int_array(
                    [j - samples, j + samples, i - samples, i + samples]))

    return masks
示例#10
0
def load_TCS_CIE2017(shape):
    """
    Loads the *CIE 2017 Test Colour Samples* dataset appropriate for the given
    spectral shape.

    The datasets are cached and won't be loaded again on subsequent calls to
    this definition.

    Parameters
    ----------
    shape : SpectralShape
        Spectral shape of the tested illuminant.

    Returns
    -------
    MultiSpectralDistributions
        *CIE 2017 Test Colour Samples* dataset.

    Examples
    --------
    >>> sds_tcs = load_TCS_CIE2017(SpectralShape(interval=5))
    >>> len(sds_tcs.labels)
    99
    """

    global _CACHE_TCS_CIE2017

    interval = shape.interval

    assert interval in (1, 5), (
        'Spectral shape interval must be either 1nm or 5nm!')

    filename = 'tcs_cfi2017_{0}_nm.csv.gz'.format(as_int(interval))

    if filename in _CACHE_TCS_CIE2017:
        return _CACHE_TCS_CIE2017[filename]

    data = np.genfromtxt(str(
        os.path.join(RESOURCES_DIRECTORY_CIE2017, filename)),
                         delimiter=',')
    labels = ['TCS{0} (CIE 2017)'.format(i) for i in range(99)]

    return MultiSpectralDistributions(data[:, 1:], data[:, 0], labels)
    def load(self):
        """
        Syncs, parses, converts and returns the *Brendel (2020)*
        *Measured Commercial LED Spectra* dataset content.

        Returns
        -------
        OrderedDict
            *Brendel (2020)* *Measured Commercial LED Spectra* dataset content.

        Examples
        --------
        >>> from colour_datasets.utilities import suppress_stdout
        >>> dataset = DatasetLoader_Brendel2020()
        >>> with suppress_stdout():
        ...     dataset.load()
        >>> len(dataset.content.keys())
        29
        """

        super(DatasetLoader_Brendel2020, self).sync()

        self._content = OrderedDict()

        wavelengths = SpectralShape(350, 700, 2).range()

        csv_path = os.path.join(self.record.repository, 'dataset',
                                'led_spd_350_700.csv')

        for i, values in enumerate(
                np.loadtxt(csv_path, delimiter=',', skiprows=1)):
            peak = as_int(wavelengths[np.argmax(values)])
            name = '{0}nm - LED {1} - Brendel (2020)'.format(peak, i)

            self._content[name] = SpectralDistribution(
                values,
                wavelengths,
                name=name,
                interpolator=LinearInterpolator)

        return self._content
示例#12
0
def full_to_legal(
    CV: Union[FloatingOrArrayLike, IntegerOrArrayLike],
    bit_depth: Integer = 10,
    in_int: Boolean = False,
    out_int: Boolean = False,
) -> Union[FloatingOrNDArray, IntegerOrNDArray]:
    """
    Convert given code value :math:`CV` or float equivalent of a code value at
    a given bit depth from full range (full swing) to legal range
    (studio swing).

    Parameters
    ----------
    CV
        Full range code value :math:`CV` or float equivalent of a code value at
        a given bit depth.
    bit_depth
        Bit depth used for conversion.
    in_int
        Whether to treat the input value as integer code value or float
        equivalent of a code value at a given bit depth.
    out_int
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray`
        Legal range code value :math:`CV` or float equivalent of a code value
        at a given bit depth.

    Examples
    --------
    >>> full_to_legal(0.0)  # doctest: +ELLIPSIS
    0.0625610...
    >>> full_to_legal(1.0)  # doctest: +ELLIPSIS
    0.9188660...
    >>> full_to_legal(0.0, out_int=True)
    64
    >>> full_to_legal(1.0, out_int=True)
    940
    >>> full_to_legal(0, in_int=True)  # doctest: +ELLIPSIS
    0.0625610...
    >>> full_to_legal(1023, in_int=True)  # doctest: +ELLIPSIS
    0.9188660...
    >>> full_to_legal(0, in_int=True, out_int=True)
    64
    >>> full_to_legal(1023, in_int=True, out_int=True)
    940
    """

    CV = as_float_array(CV)

    MV = 2**bit_depth - 1

    CV_legal = as_int_array(np.round(CV / MV)) if in_int else CV

    B, W = CV_range(bit_depth, True, True)

    CV_legal = (W - B) * CV_legal + B

    if out_int:
        return as_int(np.round(CV_legal))
    else:
        return as_float(CV_legal / MV)
示例#13
0
def legal_to_full(
    CV: Union[FloatingOrArrayLike, IntegerOrArrayLike],
    bit_depth: Integer = 10,
    in_int: Boolean = False,
    out_int: Boolean = False,
) -> Union[FloatingOrNDArray, IntegerOrNDArray]:
    """
    Convert given code value :math:`CV` or float equivalent of a code value at
    a given bit depth from legal range (studio swing) to full range
    (full swing).

    Parameters
    ----------
    CV
        Legal range code value :math:`CV` or float equivalent of a code value
        at a given bit depth.
    bit_depth
        Bit depth used for conversion.
    in_int
        Whether to treat the input value as integer code value or float
        equivalent of a code value at a given bit depth.
    out_int
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray`
        Full range code value :math:`CV` or float equivalent of a code value
        at a given bit depth.

    Examples
    --------
    >>> legal_to_full(64 / 1023)
    0.0
    >>> legal_to_full(940 / 1023)
    1.0
    >>> legal_to_full(64 / 1023, out_int=True)
    0
    >>> legal_to_full(940 / 1023, out_int=True)
    1023
    >>> legal_to_full(64, in_int=True)
    0.0
    >>> legal_to_full(940, in_int=True)
    1.0
    >>> legal_to_full(64, in_int=True, out_int=True)
    0
    >>> legal_to_full(940, in_int=True, out_int=True)
    1023
    """

    CV = as_float_array(CV)

    MV = 2**bit_depth - 1

    CV_full = as_int_array(np.round(CV)) if in_int else CV * MV

    B, W = CV_range(bit_depth, True, True)

    CV_full = (CV_full - B) / (W - B)

    if out_int:
        return as_int(np.round(CV_full * MV))
    else:
        return as_float(CV_full)
示例#14
0
def adjust_image(
    image: ArrayLike,
    target_width: Integer,
    interpolation_method: Literal[  # type: ignore[misc]
        cv2.INTER_AREA, cv2.INTER_BITS, cv2.INTER_BITS2, cv2.INTER_CUBIC,
        cv2.INTER_LANCZOS4, cv2.INTER_LINEAR, ] = cv2.INTER_CUBIC,
) -> NDArray:
    """
    Adjust given image so that it is horizontal and resizes it to given target
    width.

    Parameters
    ----------
    image
        Image to adjust.
    target_width
        Width the image is resized to.
    interpolation_method
        Interpolation method.

    Returns
    -------
    :class:`numpy.ndarray`
        Resized image.

    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [2, 4, 3])
    >>> adjust_image(image, 5)  # doctest: +ELLIPSIS
    array([[[ 0.9925325...,  0.2419374..., -0.0139522...],
            [ 0.6174497...,  0.3460756...,  0.3189758...],
            [ 0.7447774...,  0.678666 ...,  0.1652180...],
            [ 0.9476452...,  0.6550805...,  0.2609945...],
            [ 0.6991505...,  0.1623470...,  1.0120867...]],
    <BLANKLINE>
           [[ 0.7269885...,  0.8556784...,  0.4049920...],
            [ 0.2666565...,  1.0401633...,  0.8238320...],
            [ 0.6419699...,  0.5442698...,  0.9082211...],
            [ 0.7894426...,  0.1944301...,  0.7906868...],
            [-0.0526997...,  0.6236685...,  0.8711483...]]], dtype=float32)
    """

    image = as_float_array(image, FLOAT_DTYPE_DEFAULT)[..., :3]

    width, height = image.shape[1], image.shape[0]
    if width < height:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        height, width = width, height

    ratio = width / target_width

    if np.allclose(ratio, 1):
        return cast(NDArray, image)
    else:
        return cv2.resize(
            image,
            (as_int(target_width), as_int(height / ratio)),
            interpolation=interpolation_method,
        )
示例#15
0
def eotf_inverse_DICOMGSDF(L, out_int=False):
    """
    Defines the *DICOM - Grayscale Standard Display Function* inverse
    electro-optical transfer function (EOTF / EOCF).

    Parameters
    ----------
    L : numeric or array_like
        *Luminance* :math:`L`.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    numeric or ndarray
        Just-Noticeable Difference (JND) Index, :math:`j`.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``L``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``J``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`NationalElectricalManufacturersAssociation2004b`

    Examples
    --------
    >>> eotf_inverse_DICOMGSDF(130.0662)  # doctest: +ELLIPSIS
    0.5004862...
    >>> eotf_inverse_DICOMGSDF(130.0662, out_int=True)
    512
    """

    L = to_domain_1(L)

    L_lg = np.log10(L)

    A = CONSTANTS_DICOMGSDF.A
    B = CONSTANTS_DICOMGSDF.B
    C = CONSTANTS_DICOMGSDF.C
    D = CONSTANTS_DICOMGSDF.D
    E = CONSTANTS_DICOMGSDF.E
    F = CONSTANTS_DICOMGSDF.F
    G = CONSTANTS_DICOMGSDF.G
    H = CONSTANTS_DICOMGSDF.H
    I = CONSTANTS_DICOMGSDF.I  # noqa

    J = (A + B * L_lg + C * L_lg ** 2 + D * L_lg ** 3 + E * L_lg ** 4 +
         F * L_lg ** 5 + G * L_lg ** 6 + H * L_lg ** 7 + I * L_lg ** 8)

    if out_int:
        return as_int(np.round(J))
    else:
        return as_float(from_range_1(J / 1023))
示例#16
0
def eotf_inverse_DCDM(
        XYZ: FloatingOrArrayLike,
        out_int: Boolean = False
) -> Union[FloatingOrNDArray, IntegerOrNDArray]:
    """
    Define the *DCDM* inverse electro-optical transfer function (EOTF).

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.
    out_int
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray`
        Non-linear *CIE XYZ'* tristimulus values.

    Warnings
    --------
    *DCDM* is an absolute transfer function.

    Notes
    -----
    -   *DCDM* is an absolute transfer function, thus the domain and range
        values for the *Reference* and *1* scales are only indicative that the
        data is not affected by scale transformations.

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``XYZ``        | ``UN``                | ``UN``        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``XYZ_p``      | ``UN``                | ``UN``        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`DigitalCinemaInitiatives2007b`

    Examples
    --------
    >>> eotf_inverse_DCDM(0.18)  # doctest: +ELLIPSIS
    0.1128186...
    >>> eotf_inverse_DCDM(0.18, out_int=True)
    462
    """

    XYZ = as_float_array(XYZ)

    XYZ_p = spow(XYZ / 52.37, 1 / 2.6)

    if out_int:
        return as_int(np.round(4095 * XYZ_p))
    else:
        return as_float(XYZ_p)
示例#17
0
def cctf_encoding_ROMMRGB(X, bit_depth=8, out_int=False):
    """
    Defines the *ROMM RGB* encoding colour component transfer function
    (Encoding CCTF).

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{ROMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{ROMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`ANSI2003a`, :cite:`Spaulding2000b`

    Examples
    --------
    >>> cctf_encoding_ROMMRGB(0.18)  # doctest: +ELLIPSIS
    0.3857114...
    >>> cctf_encoding_ROMMRGB(0.18, out_int=True)
    98
    """

    X = to_domain_1(X)

    I_max = 2**bit_depth - 1

    E_t = 16**(1.8 / (1 - 1.8))

    X_p = np.where(X < E_t, X * 16 * I_max, spow(X, 1 / 1.8) * I_max)

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#18
0
def oetf_ROMMRGB(X, bit_depth=8, out_int=False):
    """
    Defines the *ROMM RGB* encoding opto-electronic transfer function
    (OETF / OECF).

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{ROMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{ROMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    -   \\* This definition has an output integer switch, thus the domain-range
        scale information is only given for the floating point mode.

    References
    ----------
    :cite:`ANSI2003a`, :cite:`Spaulding2000b`

    Examples
    --------
    >>> oetf_ROMMRGB(0.18)  # doctest: +ELLIPSIS
    0.3857114...
    >>> oetf_ROMMRGB(0.18, out_int=True)
    98
    """

    X = to_domain_1(X)

    I_max = 2 ** bit_depth - 1

    E_t = 16 ** (1.8 / (1 - 1.8))

    X_p = np.where(X < E_t, X * 16 * I_max, spow(X, 1 / 1.8) * I_max)

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#19
0
def log_encoding_ACESproxy(
    lin_AP1: FloatingOrArrayLike,
    bit_depth: Literal[10, 12] = 10,
    out_int: Boolean = False,
    constants: Dict = CONSTANTS_ACES_PROXY,
) -> Union[FloatingOrNDArray, IntegerOrNDArray]:
    """
    Define the *ACESproxy* colourspace log encoding curve / opto-electronic
    transfer function.

    Parameters
    ----------
    lin_AP1
        *lin_AP1* value.
    bit_depth
        *ACESproxy* bit depth.
    out_in
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    constants
        *ACESproxy* constants.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray`
        *ACESproxy* non-linear value.

    Notes
    -----
    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``lin_AP1``    | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``ACESproxy``  | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`TheAcademyofMotionPictureArtsandSciences2014q`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014r`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014s`,
    :cite:`TheAcademyofMotionPictureArtsandSciencese`

    Examples
    --------
    >>> log_encoding_ACESproxy(0.18)  # doctest: +ELLIPSIS
    0.4164222...
    >>> log_encoding_ACESproxy(0.18, out_int=True)
    426
    """

    lin_AP1 = to_domain_1(lin_AP1)

    CV_min = constants[bit_depth].CV_min
    CV_max = constants[bit_depth].CV_max
    mid_CV_offset = constants[bit_depth].mid_CV_offset
    mid_log_offset = constants[bit_depth].mid_log_offset
    steps_per_stop = constants[bit_depth].steps_per_stop

    def float_2_cv(x: Floating) -> Floating:
        """Convert given numeric to code value."""

        return np.maximum(CV_min, np.minimum(CV_max, np.round(x)))

    ACESproxy = np.where(
        lin_AP1 > 2**-9.72,
        float_2_cv((np.log2(lin_AP1) + mid_log_offset) * steps_per_stop +
                   mid_CV_offset),
        np.resize(CV_min, lin_AP1.shape),
    )

    if out_int:
        return as_int(np.round(ACESproxy))
    else:
        return as_float(from_range_1(ACESproxy / (2**bit_depth - 1)))
示例#20
0
def colour_checkers_coordinates_segmentation(image, additional_data=False):
    """
    Detects the colour checkers coordinates in given image :math:`image` using
    segmentation.

    This is the core detection definition. The process is a follows:

    -   Input image :math:`image` is converted to a grayscale image
        :math:`image_g`.
    -   Image :math:`image_g` is denoised.
    -   Image :math:`image_g` is thresholded/segmented to image
        :math:`image_s`.
    -   Image :math:`image_s` is eroded and dilated to cleanup remaining noise.
    -   Contours are detected on image :math:`image_s`.
    -   Contours are filtered to only keep squares/swatches above and below
        defined surface area.
    -   Squares/swatches are clustered to isolate region-of-interest that are
        potentially colour checkers: Contours are scaled by a third so that
        colour checkers swatches are expected to be joined, creating a large
        rectangular cluster. Rectangles are fitted to the clusters.
    -   Clusters with an aspect ratio different to the expected one are
        rejected, a side-effect is that the complementary pane of the
        *X-Rite* *ColorChecker Passport* is omitted.
    -   Clusters with a number of swatches close to :attr:`SWATCHES` are
        kept.

    Parameters
    ----------
    image : array_like
        Image to detect the colour checkers in.
    additional_data : bool, optional
        Whether to output additional data.

    Returns
    -------
    list or ColourCheckersDetectionData
        List of colour checkers coordinates or
        :class:`ColourCheckersDetectionData` class instance with additional
        data.

    Notes
    -----
    -   Multiple colour checkers can be detected if presented in ``image``.

    Examples
    --------
    >>> import os
    >>> from colour import read_image
    >>> from colour_checker_detection import TESTS_RESOURCES_DIRECTORY
    >>> path = os.path.join(TESTS_RESOURCES_DIRECTORY,
    ...                     'colour_checker_detection', 'detection',
    ...                     'IMG_1967.png')
    >>> image = read_image(path)
    >>> colour_checkers_coordinates_segmentation(image)
    [array([[1065,  707],
           [ 369,  688],
           [ 382,  226],
           [1078,  246]])]
    """

    image = as_8_bit_BGR_image(adjust_image(image, WORKING_WIDTH))

    width, height = image.shape[1], image.shape[0]
    maximum_area = width * height / SWATCHES
    minimum_area = width * height / SWATCHES / SWATCH_MINIMUM_AREA_FACTOR

    block_size = as_int(WORKING_WIDTH * 0.015)
    block_size = block_size - block_size % 2 + 1

    # Thresholding/Segmentation.
    image_g = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    image_g = cv2.fastNlMeansDenoising(image_g, None, 10, 7, 21)
    image_s = cv2.adaptiveThreshold(image_g, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
                                    cv2.THRESH_BINARY, block_size, 3)
    # Cleanup.
    kernel = np.ones((3, 3), np.uint8)
    image_c = cv2.erode(image_s, kernel, iterations=1)
    image_c = cv2.dilate(image_c, kernel, iterations=1)

    # Detecting contours.
    _image_c, contours, _hierarchy = cv2.findContours(image_c, cv2.RETR_TREE,
                                                      cv2.CHAIN_APPROX_NONE)

    # Filtering squares/swatches contours.
    swatches = []
    for contour in contours:
        curve = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True),
                                 True)
        if minimum_area < cv2.contourArea(curve) < maximum_area and is_square(
                curve):
            swatches.append(as_int_array(cv2.boxPoints(
                cv2.minAreaRect(curve))))

    # Clustering squares/swatches.
    clusters = np.zeros(image.shape, dtype=np.uint8)
    for swatch in [
            as_int_array(scale_contour(swatch, 1 + 1 / 3))
            for swatch in swatches
    ]:
        cv2.drawContours(clusters, [swatch], -1, [255] * 3, -1)
    clusters = cv2.cvtColor(clusters, cv2.COLOR_RGB2GRAY)
    _image_c, clusters, _hierarchy = cv2.findContours(clusters,
                                                      cv2.RETR_EXTERNAL,
                                                      cv2.CHAIN_APPROX_NONE)
    clusters = [
        as_int_array(
            scale_contour(cv2.boxPoints(cv2.minAreaRect(cluster)), 0.975))
        for cluster in clusters
    ]

    # Filtering clusters using their aspect ratio.
    filtered_clusters = []
    for cluster in clusters[:]:
        rectangle = cv2.minAreaRect(cluster)
        width = max(rectangle[1][0], rectangle[1][1])
        height = min(rectangle[1][0], rectangle[1][1])
        ratio = width / height
        if ASPECT_RATIO * 0.9 < ratio < ASPECT_RATIO * 1.1:
            filtered_clusters.append(cluster)
    clusters = filtered_clusters

    # Filtering swatches within cluster.
    counts = []
    for cluster in clusters:
        count = 0
        for swatch in swatches:
            if cv2.pointPolygonTest(cluster, contour_centroid(swatch),
                                    False) == 1:
                count += 1
        counts.append(count)
    counts = np.array(counts)
    indexes = np.where(
        np.logical_and(counts >= SWATCHES * 0.75,
                       counts <= SWATCHES * 1.25))[0].tolist()

    colour_checkers = [clusters[i] for i in indexes]

    if additional_data:
        return ColourCheckersDetectionData(colour_checkers, clusters, swatches,
                                           image_c)
    else:
        return colour_checkers
示例#21
0
def polynomial_expansion_Finlayson2015(RGB,
                                       degree=1,
                                       root_polynomial_expansion=True):
    """
    Performs polynomial expansion of given *RGB* colourspace array using
    *Finlayson et al. (2015)* method.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array to expand.
    degree : int, optional
        Expanded polynomial degree.
    root_polynomial_expansion : bool
        Whether to use the root-polynomials set for the expansion.

    Returns
    -------
    ndarray
        Expanded *RGB* colourspace array.

    References
    ----------
    :cite:`Finlayson2015`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> polynomial_expansion_Finlayson2015(RGB, degree=2)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.06416938...\
,  0.0078981...,  0.0029423...,
            0.0055265...])
    """

    R, G, B = tsplit(RGB)

    # TODO: Generalise polynomial expansion.
    existing_degrees = np.array([1, 2, 3, 4])
    closest_degree = as_int(closest(existing_degrees, degree))
    if closest_degree != degree:
        raise ValueError('"Finlayson et al. (2015)" method does not define '
                         'a polynomial expansion for {0} degree, '
                         'closest polynomial expansion is {1} degree!'.format(
                             degree, closest_degree))

    if degree == 1:
        return RGB
    elif degree == 2:
        if root_polynomial_expansion:
            return tstack([
                R, G, B, (R * G) ** 1 / 2, (G * B) ** 1 / 2, (R * B) ** 1 / 2
            ])

        else:
            return tstack(
                [R, G, B, R ** 2, G ** 2, B ** 2, R * G, G * B, R * B])
    elif degree == 3:
        if root_polynomial_expansion:
            return tstack([
                R, G, B, (R * G) ** 1 / 2, (G * B) ** 1 / 2, (R * B) ** 1 / 2,
                (R * G ** 2) ** 1 / 3, (G * B ** 2) ** 1 / 3,
                (R * B ** 2) ** 1 / 3, (G * R ** 2) ** 1 / 3,
                (B * G ** 2) ** 1 / 3, (B * R ** 2) ** 1 / 3,
                (R * G * B) ** 1 / 3
            ])
        else:
            return tstack([
                R, G, B, R ** 2, G ** 2, B ** 2, R * G, G * B, R * B, R ** 3, G
                ** 3, B ** 3, R * G ** 2, G * B ** 2, R * B ** 2, G * R ** 2,
                B * G ** 2, B * R ** 2, R * G * B
            ])
    elif degree == 4:
        if root_polynomial_expansion:
            return tstack([
                R, G, B, (R * G) ** 1 / 2, (G * B) ** 1 / 2, (R * B) ** 1 / 2,
                (R * G ** 2) ** 1 / 3, (G * B ** 2) ** 1 / 3,
                (R * B ** 2) ** 1 / 3, (G * R ** 2) ** 1 / 3,
                (B * G ** 2) ** 1 / 3, (B * R ** 2) ** 1 / 3,
                (R * G * B) ** 1 / 3, (R ** 3 * G) ** 1 / 4,
                (R ** 3 * B) ** 1 / 4, (G ** 3 * R) ** 1 / 4,
                (G ** 3 * B) ** 1 / 4, (B ** 3 * R) ** 1 / 4,
                (B ** 3 * G) ** 1 / 4, (R ** 2 * G * B) ** 1 / 4,
                (G ** 2 * R * B) ** 1 / 4, (B ** 2 * R * G) ** 1 / 4
            ])
        else:
            return tstack([
                R, G, B, R ** 2, G ** 2, B ** 2, R * G, G * B, R * B, R ** 3, G
                ** 3, B ** 3, R * G ** 2, G * B ** 2, R * B ** 2, G * R ** 2,
                B * G ** 2, B * R ** 2, R * G * B, R ** 4, G ** 4, B ** 4,
                R ** 3 * G, R ** 3 * B, G ** 3 * R, G ** 3 * B, B ** 3 * R,
                B ** 3 * G, R ** 2 * G ** 2, G ** 2 * B ** 2, R ** 2 * B ** 2,
                R ** 2 * G * B, G ** 2 * R * B, B ** 2 * R * G
            ])
示例#22
0
def matrix_augmented_Cheung2004(
    RGB: ArrayLike,
    terms: Literal[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22] = 3,
) -> NDArray:
    """
    Perform polynomial expansion of given *RGB* colourspace array using
    *Cheung et al. (2004)* method.

    Parameters
    ----------
    RGB
        *RGB* colourspace array to expand.
    terms
        Number of terms of the expanded polynomial.

    Returns
    -------
    :class:`numpy.ndarray`
        Expanded *RGB* colourspace array.

    Notes
    -----
    -   This definition combines the augmented matrices given in
        :cite:`Cheung2004` and :cite:`Westland2004`.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> matrix_augmented_Cheung2004(RGB, terms=5)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.0010136...,  1...])
    """

    RGB = as_float_array(RGB)

    R, G, B = tsplit(RGB)
    tail = ones(R.shape)

    existing_terms = np.array([3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22])
    closest_terms = as_int(closest(existing_terms, terms))
    if closest_terms != terms:
        raise ValueError(
            f'"Cheung et al. (2004)" method does not define an augmented '
            f"matrix with {terms} terms, closest augmented matrix has "
            f"{closest_terms} terms!")

    if terms == 3:
        return RGB
    elif terms == 5:
        return tstack([
            R,
            G,
            B,
            R * G * B,
            tail,
        ])
    elif terms == 7:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            tail,
        ])
    elif terms == 8:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R * G * B,
            tail,
        ])
    elif terms == 10:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            tail,
        ])
    elif terms == 11:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            tail,
        ])
    elif terms == 14:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 16:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**3,
            G**3,
            B**3,
        ])
    elif terms == 17:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 19:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
        ])
    elif terms == 20:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 22:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
            R**2 * G * B,
            R * G**2 * B,
            R * G * B**2,
        ])
示例#23
0
def sd_reference_illuminant(CCT, shape):
    """
    Computes the reference illuminant for a given correlated colour temperature
    :math:`T_{cp}` for use in *CIE 2017 Colour Fidelity Index* (CFI)
    computation.

    Parameters
    ----------
    CCT : numeric
        Correlated colour temperature :math:`T_{cp}`.
    shape : SpectralShape
        Desired shape of the returned spectral distribution.

    Returns
    -------
    SpectralDistribution
        Reference illuminant for *CIE 2017 Colour Fidelity Index* (CFI)
        computation.

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     sd_reference_illuminant(  # doctest: +ELLIPSIS
    ...         4224.469705295263300, SpectralShape(380, 780, 20))
    SpectralDistribution([[ 380.        ,    0.0034089...],
                          [ 400.        ,    0.0044208...],
                          [ 420.        ,    0.0053260...],
                          [ 440.        ,    0.0062857...],
                          [ 460.        ,    0.0072767...],
                          [ 480.        ,    0.0080207...],
                          [ 500.        ,    0.0086590...],
                          [ 520.        ,    0.0092242...],
                          [ 540.        ,    0.0097686...],
                          [ 560.        ,    0.0101444...],
                          [ 580.        ,    0.0104475...],
                          [ 600.        ,    0.0107642...],
                          [ 620.        ,    0.0110439...],
                          [ 640.        ,    0.0112535...],
                          [ 660.        ,    0.0113922...],
                          [ 680.        ,    0.0115185...],
                          [ 700.        ,    0.0113155...],
                          [ 720.        ,    0.0108192...],
                          [ 740.        ,    0.0111582...],
                          [ 760.        ,    0.0101299...],
                          [ 780.        ,    0.0105638...]],
                         interpolator=SpragueInterpolator,
                         interpolator_kwargs={},
                         extrapolator=Extrapolator,
                         extrapolator_kwargs={...})
    """

    if CCT <= 5000:
        sd_planckian = sd_blackbody(CCT, shape)

    if CCT >= 4000:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_daylight = sd_CIE_illuminant_D_series(xy).align(shape)

    if CCT < 4000:
        sd_reference = sd_planckian
    elif 4000 <= CCT <= 5000:
        # Planckian and daylight illuminant must be normalised so that the
        # mixture isn't biased.
        sd_planckian /= sd_to_XYZ(sd_planckian)[1]
        sd_daylight /= sd_to_XYZ(sd_daylight)[1]

        # Mixture: 4200K should be 80% Planckian, 20% CIE Illuminant D Series.
        m = (CCT - 4000) / 1000
        values = lerp(sd_planckian.values, sd_daylight.values, m)
        name = ('{0}K Blackbody & CIE Illuminant D Series Mixture - {1:.1f}%'.
                format(as_int(CCT), 100 * m))
        sd_reference = SpectralDistribution(values, shape.range(), name=name)
    elif CCT > 5000:
        sd_reference = sd_daylight

    return sd_reference
示例#24
0
def oetf_RIMMRGB(X, bit_depth=8, out_int=False, E_clip=2.0):
    """
    Defines the *RIMM RGB* encoding opto-electronic transfer function
    (OETF / OECF).

    *RIMM RGB* encoding non-linearity is based on that specified by
    *Recommendation ITU-R BT.709-6*.

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{RIMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    E_clip : numeric, optional
        Maximum exposure level.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{RIMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    -   \\* This definition has an output integer switch, thus the domain-range
        scale information is only given for the floating point mode.

    References
    ----------
    :cite:`Spaulding2000b`

    Examples
    --------
    >>> oetf_RIMMRGB(0.18)  # doctest: +ELLIPSIS
    0.2916737...
    >>> oetf_RIMMRGB(0.18, out_int=True)
    74
    """

    X = to_domain_1(X)

    I_max = 2 ** bit_depth - 1

    V_clip = 1.099 * spow(E_clip, 0.45) - 0.099
    q = I_max / V_clip

    X_p = q * np.select([X < 0.0, X < 0.018, X >= 0.018, X > E_clip],
                        [0, 4.5 * X, 1.099 * spow(X, 0.45) - 0.099, I_max])

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#25
0
文件: tm3018.py 项目: wenh06/colour
def colour_fidelity_index_ANSIIESTM3018(sd_test, additional_data=False):
    """
    Returns the *ANSI/IES TM-30-18 Colour Fidelity Index* (CFI) :math:`R_f`
    of given spectral distribution.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.

    Returns
    -------
    numeric or ColourQuality_Specification_ANSIIESTM3018
        *ANSI/IES TM-30-18 Colour Fidelity Index* (CFI).

    References
    ----------
    :cite:`ANSI2018`

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_fidelity_index_ANSIIESTM3018(sd)  # doctest: +ELLIPSIS
    70.1208254...
    """

    if not additional_data:
        return colour_fidelity_index_CIE2017(sd_test, False)

    specification = colour_fidelity_index_CIE2017(sd_test, True)

    # Setup bins based on where the reference a'b' points are located.
    bins = [[] for _i in range(16)]
    for i, sample in enumerate(specification.colorimetry_data[1]):
        bin_index = as_int(np.floor(sample.CAM.h / 22.5))
        bins[bin_index].append(i)

    # Per-bin a'b' averages.
    averages_test = np.empty([16, 2])
    averages_reference = np.empty([16, 2])
    for i in range(16):
        apbp_s = [
            specification.colorimetry_data[0][j].Jpapbp[[1, 2]]
            for j in bins[i]
        ]
        averages_test[i, :] = np.mean(apbp_s, axis=0)
        apbp_s = [
            specification.colorimetry_data[1][j].Jpapbp[[1, 2]]
            for j in bins[i]
        ]
        averages_reference[i, :] = np.mean(apbp_s, axis=0)

    # Gamut Index.
    R_g = 100 * (averages_area(averages_test) /
                 averages_area(averages_reference))

    # Local colour fidelity indexes, i.e. 16 CFIs for each bin.
    bin_delta_E_s = [
        np.mean([specification.delta_E_s[bins[i]]]) for i in range(16)
    ]
    R_fs = delta_E_to_R_f(as_float_array(bin_delta_E_s))

    # Angles bisecting the hue bins.
    angles = (22.5 * np.arange(16) + 11.25) / 180 * np.pi
    cosines = np.cos(angles)
    sines = np.sin(angles)

    average_norms = np.linalg.norm(averages_reference, axis=1)
    a_deltas = averages_test[:, 0] - averages_reference[:, 0]
    b_deltas = averages_test[:, 1] - averages_reference[:, 1]

    # Local chromaticity shifts, multiplied by 100 to obtain percentages.
    R_cs = 100 * (a_deltas * cosines + b_deltas * sines) / average_norms

    # Local hue shifts.
    R_hs = (-a_deltas * sines + b_deltas * cosines) / average_norms

    return ColourQuality_Specification_ANSIIESTM3018(
        specification.name, sd_test, specification.sd_reference,
        specification.R_f, specification.R_s, specification.CCT,
        specification.D_uv, specification.colorimetry_data, R_g, bins,
        averages_test, averages_reference, average_norms, R_fs, R_cs, R_hs)
示例#26
0
def cctf_encoding_RIMMRGB(X, bit_depth=8, out_int=False, E_clip=2.0):
    """
    Defines the *RIMM RGB* encoding colour component transfer function
    (Encoding CCTF).

    *RIMM RGB* encoding non-linearity is based on that specified by
    *Recommendation ITU-R BT.709-6*.

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{RIMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    E_clip : numeric, optional
        Maximum exposure level.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{RIMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`Spaulding2000b`

    Examples
    --------
    >>> cctf_encoding_RIMMRGB(0.18)  # doctest: +ELLIPSIS
    0.2916737...
    >>> cctf_encoding_RIMMRGB(0.18, out_int=True)
    74
    """

    X = to_domain_1(X)

    I_max = 2**bit_depth - 1

    V_clip = 1.099 * spow(E_clip, 0.45) - 0.099
    q = I_max / V_clip

    X_p = q * np.select([X < 0.0, X < 0.018, X >= 0.018, X > E_clip],
                        [0, 4.5 * X, 1.099 * spow(X, 0.45) - 0.099, I_max])

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#27
0
def oetf_DICOMGSDF(L, out_int=False):
    """
    Defines the *DICOM - Grayscale Standard Display Function* opto-electronic
    transfer function (OETF / OECF).

    Parameters
    ----------
    L : numeric or array_like
        *Luminance* :math:`L`.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.

    Returns
    -------
    numeric or ndarray
        Just-Noticeable Difference (JND) Index, :math:`j`.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``L``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``J``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`NationalElectricalManufacturersAssociation2004b`

    Examples
    --------
    >>> oetf_DICOMGSDF(130.0662)  # doctest: +ELLIPSIS
    0.5004862...
    >>> oetf_DICOMGSDF(130.0662, out_int=True)
    512
    """

    L = to_domain_1(L)

    L_lg = np.log10(L)

    A = DICOMGSDF_CONSTANTS.A
    B = DICOMGSDF_CONSTANTS.B
    C = DICOMGSDF_CONSTANTS.C
    D = DICOMGSDF_CONSTANTS.D
    E = DICOMGSDF_CONSTANTS.E
    F = DICOMGSDF_CONSTANTS.F
    G = DICOMGSDF_CONSTANTS.G
    H = DICOMGSDF_CONSTANTS.H
    I = DICOMGSDF_CONSTANTS.I  # noqa

    J = (A + B * L_lg + C * L_lg ** 2 + D * L_lg ** 3 + E * L_lg ** 4 +
         F * L_lg ** 5 + G * L_lg ** 6 + H * L_lg ** 7 + I * L_lg ** 8)

    if out_int:
        return as_int(np.round(J))
    else:
        return as_float(from_range_1(J / 1023))
示例#28
0
def log_encoding_ERIMMRGB(X,
                          bit_depth=8,
                          out_int=False,
                          E_min=0.001,
                          E_clip=316.2):
    """
    Defines the *ERIMM RGB* log encoding curve / opto-electronic transfer
    function (OETF / OECF).

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{ERIMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    E_min : numeric, optional
        Minimum exposure limit.
    E_clip : numeric, optional
        Maximum exposure limit.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{ERIMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`Spaulding2000b`

    Examples
    --------
    >>> log_encoding_ERIMMRGB(0.18)  # doctest: +ELLIPSIS
    0.4100523...
    >>> log_encoding_ERIMMRGB(0.18, out_int=True)
    105
    """

    X = to_domain_1(X)

    I_max = 2**bit_depth - 1

    E_t = np.exp(1) * E_min

    X_p = np.select([
        X < 0.0,
        X <= E_t,
        X > E_t,
        X > E_clip,
    ], [
        0,
        I_max * ((np.log(E_t) - np.log(E_min)) /
                 (np.log(E_clip) - np.log(E_min))) * (X / E_t),
        I_max * ((np.log(X) - np.log(E_min)) /
                 (np.log(E_clip) - np.log(E_min))),
        I_max,
    ])

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#29
0
def matrix_augmented_Cheung2004(RGB, terms=3):
    """
    Performs polynomial expansion of given *RGB* colourspace array using
    *Cheung et al. (2004)* method.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array to expand.
    terms : int, optional
        Number of terms of the expanded polynomial, must be one of
        *[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22]*.

    Returns
    -------
    ndarray
        Expanded *RGB* colourspace array.

    Notes
    -----
    -   This definition combines the augmented matrices given in
        :cite:`Cheung2004` and :cite:`Westland2004`.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> matrix_augmented_Cheung2004(RGB, terms=5)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.0010136...,  1...])
    """

    R, G, B = tsplit(RGB)
    tail = ones(R.shape)

    existing_terms = np.array([3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22])
    closest_terms = as_int(closest(existing_terms, terms))
    if closest_terms != terms:
        raise ValueError('"Cheung et al. (2004)" method does not define '
                         'an augmented matrix with {0} terms, '
                         'closest augmented matrix has {1} terms!'.format(
                             terms, closest_terms))

    if terms == 3:
        return RGB
    elif terms == 5:
        return tstack([
            R,
            G,
            B,
            R * G * B,
            tail,
        ])
    elif terms == 7:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            tail,
        ])
    elif terms == 8:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R * G * B,
            tail,
        ])
    elif terms == 10:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            tail,
        ])
    elif terms == 11:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            tail,
        ])
    elif terms == 14:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 16:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**3,
            G**3,
            B**3,
        ])
    elif terms == 17:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 19:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
        ])
    elif terms == 20:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
            tail,
        ])
    elif terms == 22:
        return tstack([
            R,
            G,
            B,
            R * G,
            R * B,
            G * B,
            R**2,
            G**2,
            B**2,
            R * G * B,
            R**2 * G,
            G**2 * B,
            B**2 * R,
            R**2 * B,
            G**2 * R,
            B**2 * G,
            R**3,
            G**3,
            B**3,
            R**2 * G * B,
            R * G**2 * B,
            R * G * B**2,
        ])
示例#30
0
def log_encoding_ACESproxy(lin_AP1,
                           bit_depth=10,
                           out_int=False,
                           constants=ACES_PROXY_CONSTANTS):
    """
    Defines the *ACESproxy* colourspace log encoding curve / opto-electronic
    transfer function.

    Parameters
    ----------
    lin_AP1 : numeric or array_like
        *lin_AP1* value.
    bit_depth : int, optional
        **{10, 12}**,
        *ACESproxy* bit depth.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    constants : Structure, optional
        *ACESproxy* constants.

    Returns
    -------
    numeric or ndarray
        *ACESproxy* non-linear value.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``lin_AP1``    | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``ACESproxy``  | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an output integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`TheAcademyofMotionPictureArtsandSciences2014q`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014r`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014s`,
    :cite:`TheAcademyofMotionPictureArtsandSciencese`

    Examples
    --------
    >>> log_encoding_ACESproxy(0.18)  # doctest: +ELLIPSIS
    0.4164222...
    >>> log_encoding_ACESproxy(0.18, out_int=True)
    426
    """

    lin_AP1 = to_domain_1(lin_AP1)

    constants = constants[bit_depth]

    CV_min = np.resize(constants.CV_min, lin_AP1.shape)
    CV_max = np.resize(constants.CV_max, lin_AP1.shape)

    def float_2_cv(x):
        """
        Converts given numeric to code value.
        """

        return np.maximum(CV_min, np.minimum(CV_max, np.round(x)))

    ACESproxy = np.where(
        lin_AP1 > 2**-9.72,
        float_2_cv((np.log2(lin_AP1) + constants.mid_log_offset) *
                   constants.steps_per_stop + constants.mid_CV_offset),
        np.resize(CV_min, lin_AP1.shape),
    )

    if out_int:
        return as_int(np.round(ACESproxy))
    else:
        return as_float(from_range_1(ACESproxy / (2**bit_depth - 1)))
示例#31
0
def log_encoding_ERIMMRGB(X,
                          bit_depth=8,
                          out_int=False,
                          E_min=0.001,
                          E_clip=316.2):
    """
    Defines the *ERIMM RGB* log encoding curve / opto-electronic transfer
    function (OETF / OECF).

    Parameters
    ----------
    X : numeric or array_like
        Linear data :math:`X_{ERIMM}`.
    bit_depth : int, optional
        Bit depth used for conversion.
    out_int : bool, optional
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    E_min : numeric, optional
        Minimum exposure limit.
    E_clip : numeric, optional
        Maximum exposure limit.

    Returns
    -------
    numeric or ndarray
        Non-linear data :math:`X'_{ERIMM}`.

    Notes
    -----

    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    -   \\* This definition has an output integer switch, thus the domain-range
        scale information is only given for the floating point mode.

    References
    ----------
    :cite:`Spaulding2000b`

    Examples
    --------
    >>> log_encoding_ERIMMRGB(0.18)  # doctest: +ELLIPSIS
    0.4100523...
    >>> log_encoding_ERIMMRGB(0.18, out_int=True)
    105
    """

    X = to_domain_1(X)

    I_max = 2 ** bit_depth - 1

    E_t = np.exp(1) * E_min

    X_p = np.select([
        X < 0.0,
        X <= E_t,
        X > E_t,
        X > E_clip,
    ], [
        0,
        I_max * ((np.log(E_t) - np.log(E_min)) /
                 (np.log(E_clip) - np.log(E_min))) * (X / E_t),
        I_max * (
            (np.log(X) - np.log(E_min)) / (np.log(E_clip) - np.log(E_min))),
        I_max,
    ])

    if out_int:
        return as_int(np.round(X_p))
    else:
        return as_float(from_range_1(X_p / I_max))
示例#32
0
def polynomial_expansion_Finlayson2015(
    RGB: ArrayLike,
    degree: Literal[1, 2, 3, 4] = 1,
    root_polynomial_expansion: Boolean = True,
) -> NDArray:
    """
    Perform polynomial expansion of given *RGB* colourspace array using
    *Finlayson et al. (2015)* method.

    Parameters
    ----------
    RGB
        *RGB* colourspace array to expand.
    degree
        Expanded polynomial degree.
    root_polynomial_expansion
        Whether to use the root-polynomials set for the expansion.

    Returns
    -------
    :class:`numpy.ndarray`
        Expanded *RGB* colourspace array.

    References
    ----------
    :cite:`Finlayson2015`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> polynomial_expansion_Finlayson2015(RGB, degree=2)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.1256832...,  \
0.0767121...,
            0.1051335...])
    """

    RGB = as_float_array(RGB)

    R, G, B = tsplit(RGB)

    # TODO: Generalise polynomial expansion.
    existing_degrees = np.array([1, 2, 3, 4])
    closest_degree = as_int(closest(existing_degrees, degree))
    if closest_degree != degree:
        raise ValueError(
            f'"Finlayson et al. (2015)" method does not define a polynomial '
            f"expansion for {degree} degree, closest polynomial expansion is "
            f"{closest_degree} degree!")

    if degree == 1:
        return RGB
    elif degree == 2:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
            ])

        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
            ])
    elif degree == 3:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
                spow(R * G**2, 1 / 3),
                spow(G * B**2, 1 / 3),
                spow(R * B**2, 1 / 3),
                spow(G * R**2, 1 / 3),
                spow(B * G**2, 1 / 3),
                spow(B * R**2, 1 / 3),
                spow(R * G * B, 1 / 3),
            ])
        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
                R**3,
                G**3,
                B**3,
                R * G**2,
                G * B**2,
                R * B**2,
                G * R**2,
                B * G**2,
                B * R**2,
                R * G * B,
            ])
    elif degree == 4:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
                spow(R * G**2, 1 / 3),
                spow(G * B**2, 1 / 3),
                spow(R * B**2, 1 / 3),
                spow(G * R**2, 1 / 3),
                spow(B * G**2, 1 / 3),
                spow(B * R**2, 1 / 3),
                spow(R * G * B, 1 / 3),
                spow(R**3 * G, 1 / 4),
                spow(R**3 * B, 1 / 4),
                spow(G**3 * R, 1 / 4),
                spow(G**3 * B, 1 / 4),
                spow(B**3 * R, 1 / 4),
                spow(B**3 * G, 1 / 4),
                spow(R**2 * G * B, 1 / 4),
                spow(G**2 * R * B, 1 / 4),
                spow(B**2 * R * G, 1 / 4),
            ])
        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
                R**3,
                G**3,
                B**3,
                R * G**2,
                G * B**2,
                R * B**2,
                G * R**2,
                B * G**2,
                B * R**2,
                R * G * B,
                R**4,
                G**4,
                B**4,
                R**3 * G,
                R**3 * B,
                G**3 * R,
                G**3 * B,
                B**3 * R,
                B**3 * G,
                R**2 * G**2,
                G**2 * B**2,
                R**2 * B**2,
                R**2 * G * B,
                G**2 * R * B,
                B**2 * R * G,
            ])
示例#33
0
def eotf_inverse_DICOMGSDF(
    L: FloatingOrArrayLike,
    out_int: Boolean = False,
    constants: Structure = CONSTANTS_DICOMGSDF,
) -> Union[FloatingOrNDArray, IntegerOrNDArray]:
    """
    Define the *DICOM - Grayscale Standard Display Function* inverse
    electro-optical transfer function (EOTF).

    Parameters
    ----------
    L
        *Luminance* :math:`L`.
    out_int
        Whether to return value as integer code value or float equivalent of a
        code value at a given bit depth.
    constants
        *DICOM - Grayscale Standard Display Function* constants.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray`
        Just-Noticeable Difference (JND) Index, :math:`j`.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``L``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``J``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`NationalElectricalManufacturersAssociation2004b`

    Examples
    --------
    >>> eotf_inverse_DICOMGSDF(130.0662)  # doctest: +ELLIPSIS
    0.5004862...
    >>> eotf_inverse_DICOMGSDF(130.0662, out_int=True)
    512
    """

    L = to_domain_1(L)

    L_lg = np.log10(L)

    A = constants.A
    B = constants.B
    C = constants.C
    D = constants.D
    E = constants.E
    F = constants.F
    G = constants.G
    H = constants.H
    I = constants.I  # noqa

    J = (A + B * L_lg + C * L_lg**2 + D * L_lg**3 + E * L_lg**4 + F * L_lg**5 +
         G * L_lg**6 + H * L_lg**7 + I * L_lg**8)

    if out_int:
        return as_int(np.round(J))
    else:
        return as_float(from_range_1(J / 1023))
示例#34
0
def augmented_matrix_Cheung2004(RGB, terms=3):
    """
    Performs polynomial expansion of given *RGB* colourspace array using
    *Cheung et al. (2004)* method.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array to expand.
    terms : int, optional
        Number of terms of the expanded polynomial, must be one of
        *[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22]*.

    Returns
    -------
    ndarray
        Expanded *RGB* colourspace array.

    Notes
    -----
    -   This definition combines the augmented matrices given in
        :cite:`Cheung2004` and :cite:`Westland2004`.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> augmented_matrix_Cheung2004(RGB, terms=5)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.0010136...,  1...])
    """

    R, G, B = tsplit(RGB)
    ones = np.ones(R.shape)

    existing_terms = np.array([3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22])
    closest_terms = as_int(closest(existing_terms, terms))
    if closest_terms != terms:
        raise ValueError('"Cheung et al. (2004)" method does not define '
                         'an augmented matrix with {0} terms, '
                         'closest augmented matrix has {1} terms!'.format(
                             terms, closest_terms))

    if terms == 3:
        return RGB
    elif terms == 5:
        return tstack([R, G, B, R * G * B, ones])
    elif terms == 7:
        return tstack([R, G, B, R * G, R * B, G * B, ones])
    elif terms == 8:
        return tstack([R, G, B, R * G, R * B, G * B, R * G * B, ones])
    elif terms == 10:
        return tstack(
            [R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, ones])
    elif terms == 11:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            ones
        ])
    elif terms == 14:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B, R
            ** 3, G ** 3, B ** 3, ones
        ])
    elif terms == 16:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            R ** 2 * G, G ** 2 * B, B ** 2 * R, R ** 3, G ** 3, B ** 3
        ])
    elif terms == 17:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            R ** 2 * G, G ** 2 * B, B ** 2 * R, R ** 3, G ** 3, B ** 3, ones
        ])
    elif terms == 19:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            R ** 2 * G, G ** 2 * B, B ** 2 * R, R ** 2 * B, G ** 2 * R,
            B ** 2 * G, R ** 3, G ** 3, B ** 3
        ])
    elif terms == 20:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            R ** 2 * G, G ** 2 * B, B ** 2 * R, R ** 2 * B, G ** 2 * R,
            B ** 2 * G, R ** 3, G ** 3, B ** 3, ones
        ])
    elif terms == 22:
        return tstack([
            R, G, B, R * G, R * B, G * B, R ** 2, G ** 2, B ** 2, R * G * B,
            R ** 2 * G, G ** 2 * B, B ** 2 * R, R ** 2 * B, G ** 2 * R,
            B ** 2 * G, R ** 3, G ** 3, B ** 3, R ** 2 * G * B, R * G ** 2 * B,
            R * G * B ** 2
        ])
示例#35
0
def polynomial_expansion_Finlayson2015(RGB,
                                       degree=1,
                                       root_polynomial_expansion=True):
    """
    Performs polynomial expansion of given *RGB* colourspace array using
    *Finlayson et al. (2015)* method.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array to expand.
    degree : int, optional
        Expanded polynomial degree.
    root_polynomial_expansion : bool
        Whether to use the root-polynomials set for the expansion.

    Returns
    -------
    ndarray
        Expanded *RGB* colourspace array.

    References
    ----------
    :cite:`Finlayson2015`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> polynomial_expansion_Finlayson2015(RGB, degree=2)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.1256832...,  \
0.0767121...,
            0.1051335...])
    """

    R, G, B = tsplit(RGB)

    # TODO: Generalise polynomial expansion.
    existing_degrees = np.array([1, 2, 3, 4])
    closest_degree = as_int(closest(existing_degrees, degree))
    if closest_degree != degree:
        raise ValueError('"Finlayson et al. (2015)" method does not define '
                         'a polynomial expansion for {0} degree, '
                         'closest polynomial expansion is {1} degree!'.format(
                             degree, closest_degree))

    if degree == 1:
        return RGB
    elif degree == 2:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
            ])

        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
            ])
    elif degree == 3:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
                spow(R * G**2, 1 / 3),
                spow(G * B**2, 1 / 3),
                spow(R * B**2, 1 / 3),
                spow(G * R**2, 1 / 3),
                spow(B * G**2, 1 / 3),
                spow(B * R**2, 1 / 3),
                spow(R * G * B, 1 / 3),
            ])
        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
                R**3,
                G**3,
                B**3,
                R * G**2,
                G * B**2,
                R * B**2,
                G * R**2,
                B * G**2,
                B * R**2,
                R * G * B,
            ])
    elif degree == 4:
        if root_polynomial_expansion:
            return tstack([
                R,
                G,
                B,
                spow(R * G, 1 / 2),
                spow(G * B, 1 / 2),
                spow(R * B, 1 / 2),
                spow(R * G**2, 1 / 3),
                spow(G * B**2, 1 / 3),
                spow(R * B**2, 1 / 3),
                spow(G * R**2, 1 / 3),
                spow(B * G**2, 1 / 3),
                spow(B * R**2, 1 / 3),
                spow(R * G * B, 1 / 3),
                spow(R**3 * G, 1 / 4),
                spow(R**3 * B, 1 / 4),
                spow(G**3 * R, 1 / 4),
                spow(G**3 * B, 1 / 4),
                spow(B**3 * R, 1 / 4),
                spow(B**3 * G, 1 / 4),
                spow(R**2 * G * B, 1 / 4),
                spow(G**2 * R * B, 1 / 4),
                spow(B**2 * R * G, 1 / 4),
            ])
        else:
            return tstack([
                R,
                G,
                B,
                R**2,
                G**2,
                B**2,
                R * G,
                G * B,
                R * B,
                R**3,
                G**3,
                B**3,
                R * G**2,
                G * B**2,
                R * B**2,
                G * R**2,
                B * G**2,
                B * R**2,
                R * G * B,
                R**4,
                G**4,
                B**4,
                R**3 * G,
                R**3 * B,
                G**3 * R,
                G**3 * B,
                B**3 * R,
                B**3 * G,
                R**2 * G**2,
                G**2 * B**2,
                R**2 * B**2,
                R**2 * G * B,
                G**2 * R * B,
                B**2 * R * G,
            ])