Source code for colour_datasets.loaders.luo1997
"""
LUTCHI Colour Appearance Data - Luo and Rhodes (1997)
=====================================================
Defines the objects implementing support for *Luo and Rhodes (1997)*
*LUTCHI Colour Appearance Data* dataset loading:
- :class:`colour_datasets.loaders.DatasetLoader_Luo1997`
- :func:`colour_datasets.loaders.build_Luo1997`
References
----------
- :cite:`Luo1991` : Luo, M. R., Clarke, A. A., Rhodes, P. A., Schappo, A.,
Scrivener, S. A. R., & Tait, C. J. (1991b). Quantifying colour appearance.
Part I. Lutchi colour appearance data. Color Research & Application, 16(3),
166-180. doi:10.1002/col.5080160307
- :cite:`Luo1991a` : Luo, M. R., Clarke, A. A., Rhodes, P. A., Schappo, A.,
Scrivener, S. A. R., & Tait, C. J. (1991a). Quantifying colour appearance.
Part II. Testing colour models performance using lutchi colour appearance
data. Color Research & Application, 16(3), 181-197.
doi:10.1002/col.5080160308
- :cite:`Luo1993` : Luo, M. R., Gao, X. W., Rhodes, P. A., Xin, H. J.,
Clarke, A. A., & Scrivener, S. A. R. (1993). Quantifying colour appearance.
part III. Supplementary LUTCHI colour appearance data. Color Research &
Application, 18(2), 98-113. doi:10.1002/col.5080180207
- :cite:`Luo1997` : Luo, M. R., & Rhodes, P. A. (1997). Using the LUTCHI
Colour Appearance Data. Retrieved September 10, 2019, from
https://web.archive.org/web/20040212195937/\
http://colour.derby.ac.uk:80/colour/info/lutchi/
"""
from __future__ import annotations
import os
from collections import namedtuple
import numpy as np
from colour.hints import Dict, Tuple
from colour.utilities import as_float_array, usage_warning
from colour_datasets.loaders import AbstractDatasetLoader
from colour_datasets.records import datasets
__author__ = "Colour Developers"
__copyright__ = "Copyright (C) 2019 - Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-science@googlegroups.com"
__status__ = "Production"
__all__ = [
"ExperimentalGroupLuo1997",
"ExperimentalPhaseLuo1997",
"DatasetLoader_Luo1997",
"build_Luo1997",
]
class ExperimentalGroupLuo1997(
namedtuple("ExperimentalGroupLuo1997", ("name", "phases", "metadata"))
):
"""
Define a *Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data*
experimental group, i.e. a group of experimental phases.
Parameters
----------
name
*Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data* experimental
group name.
phases
Experimental phases.
metadata
Experimental group metadata.
"""
class ExperimentalPhaseLuo1997(
namedtuple(
"ExperimentalPhaseLuo1997",
(
"name",
"JQCH_v",
"xyY_c",
"S_Y_c",
"Y_b",
"Y_r",
"XYZ_o",
"metadata",
),
)
):
"""
Define a *Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data*
experimental phase.
Parameters
----------
name
*Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data* experimental
phase name.
JQCH_v
:math:`JQCH_v` array from the visual file where :math:`JQ`, :math:`C`,
and :math:`H` are the mean visual results of the lightness or
brightness, colourfulness and hue. The arithmetic mean was used for
calculating lightness (0 for black, 100 for white) and hue (0 for red,
100 for yellow, 200 for green, 300 for blue and 400 for red) results,
the geometric mean for brightness (0 for black) and colourfulness (0
for neutral colours) results in open ended scales.
xyY_c
*CIE xyY* colourspace array :math:`xyY_c` from the colorimetric file
and measured using a telespectroradiometer (TSR).
S_Y_c
Scaling factor :math:`S_Y` of the Y values used for adjusting those in
the colorimetric file, i.e. *CIE xyY* colourspace array :math:`xyY_c`.
Y_b
Relative luminance of background :math:`Y_b` in :math:`cd/m^2`.
Y_r
Reference white sample luminance :math:`Y_r` in :math:`cd/m^2`.
XYZ_o
*CIE XYZ* tristimulus values of the illuminant.
metadata
Experimental phase metadata.
"""
[docs]
class DatasetLoader_Luo1997(AbstractDatasetLoader):
"""
Define the *Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data* dataset
loader.
Attributes
----------
- :attr:`colour_datasets.loaders.DatasetLoader_Luo1997.ID`
Methods
-------
- :meth:`colour_datasets.loaders.DatasetLoader_Luo1997.__init__`
- :meth:`colour_datasets.loaders.DatasetLoader_Luo1997.load`
References
----------
:cite:`Luo1991`, :cite:`Luo1991a`, :cite:`Luo1993`, :cite:`Luo1997`
"""
ID: str = "4394536"
"""Dataset record id, i.e. the *Zenodo* record number."""
def __init__(self) -> None:
super().__init__(datasets()[DatasetLoader_Luo1997.ID])
[docs]
def load(self) -> Dict[str, ExperimentalGroupLuo1997]:
"""
Sync, parse, convert and return the *Luo and Rhodes (1997)*
*LUTCHI Colour Appearance Data* dataset content.
Returns
-------
:class:`dict`
*Luo and Rhodes (1997)* *LUTCHI Colour Appearance Data* dataset
content.
Notes
-----
- The *cold65wnl* file located at the following url:
https://web.archive.org/web/20031230164218/\
http://colour.derby.ac.uk/colour/info/lutchi/data/cold65wnl is empty. Mark
Fairchild's archive located at the following url:
http://www.rit-mcsl.org/fairchild/files/LUTCHI_Data.sit also
contains an empty *cold65wnl* file. A single line break has been
added to the original file so that it can be uploaded to *Zenodo*.
- The *BIT.p\\*.\\** files are effectively named *bit_p\\*.\\**.
- The *cola.l* file does not exist and is assumed to be named
*colal.l*.
- The *Self-luminous* entry for
*Table I: Summary of the experimental groups* is named *CRT* in
the sub-sequent tables.
- The *mean4.p\\** and *col.rf.p\\** files should all have 40
samples, unexpectedly all the *col.rf.p\\** files have 41 samples.
The first data rows are used as they are better correlated between
the two datasets. The last row could be the experimental
whitepoint.
- The *mean4.p7*, *mean4.p8*, *mean4.p9*, *mean4.p10*, *mean4.p11*,
and *mean4.p12* files represent brightness experimental results.
- The *bit_p3.vis* file has 5 columns instead of 4 only the last 3
are accounted for.
Examples
--------
>>> from colour_datasets.utilities import suppress_stdout
>>> dataset = DatasetLoader_Luo1997()
>>> with suppress_stdout():
... dataset.load()
...
>>> len(dataset.content.keys())
8
"""
super().sync()
group_metadata_headers = (
"No. of phases",
"Description of each data group",
"Reference no.",
)
phase_metadata_headers = (
"No. of Samples",
"Neutrals",
)
experimental_groups_summary: Dict[str, Tuple] = {
"R-HL": (
6,
"Reflective media with luminances ranging 364-232 cd/m2",
1,
),
"R-LL": (
6,
"Reflective media with luminances ranging 44-41 cd/m2",
1,
),
"R-VL": (
12,
"Reflective media with luminances ranging 843-0.4 cd/m2",
2,
),
"R-textile": (
3,
"Large textile samples with luminances ranging 730-340 cd/m2",
4,
),
"CRT": (
11,
"Monitor colours with luminances ranging 45-20 cd/m2",
1,
),
"35mm": (
6,
"35mm transparency with luminances ranging 113-45 cd/m2",
3,
),
"LT": (
10,
(
"Cut-sheet transparency with luminances ranging "
"2100-320 cd/m2"
),
3,
),
"BIT": (
5,
"Isolated viewing field with luminances ranging 90-3.6 cd/m2",
5,
),
}
experimental_groups: Dict[str, Tuple] = {
"R-HL": (
(
1,
"nlmean.wh",
"cold50wnl",
105,
41,
46,
0.88,
100,
264,
np.array([97.13, 100, 76.62]),
),
(
2,
"nlmean.bh",
"cold50gb",
105,
41,
46,
0.84,
6.2,
252,
np.array([97.09, 100, 83.1]),
),
(
3,
"nlmean.gh",
"cold50gb",
105,
41,
46,
0.84,
21.5,
252,
np.array([97.09, 100, 83.1]),
),
(
4,
"nld65.gh",
"cold65",
105,
41,
46,
0.81,
21.5,
243,
np.array([94.52, 100, 114.98]),
),
(
5,
"nlwf.gh",
"colwf",
105,
41,
46,
0.84,
21.5,
252,
np.array([102.5, 100, 47.93]),
),
(
6,
"nla.gh",
"colah",
105,
41,
46,
0.84,
21.5,
232,
np.array([112.92, 100, 28.62]),
),
),
"R-LL": (
(
1,
"nlmean.wl",
"cold50wnl",
105,
41,
46,
0.88,
100,
44,
np.array([97.13, 100, 76.62]),
),
(
2,
"nlmean.bl",
"cold50gb",
105,
41,
46,
0.84,
6.2,
42,
np.array([97.09, 100, 83.1]),
),
(
3,
"nlmean.gl",
"cold50gb",
105,
41,
46,
0.84,
21.5,
42,
np.array([97.09, 100, 83.1]),
),
(
4,
"nld65.gl",
"cold65",
105,
41,
46,
0.81,
21.5,
40.5,
np.array([94.52, 100, 114.98]),
),
(
5,
"nlwf.gl",
"colwf",
105,
41,
46,
0.84,
21.5,
42,
np.array([102.5, 100, 47.93]),
),
(
6,
"nla.gl",
"colal",
105,
41,
46,
0.84,
21.4,
42,
np.array([117.26, 100, 22.44]),
),
),
"R-VL": (
(
1,
"mean4.p1",
"col.rf.p1",
40,
37,
40,
1,
21.5,
843,
np.array([94.04, 100, 76.29]),
),
(
2,
"mean4.p2",
"col.rf.p2",
40,
37,
40,
1,
21.5,
200,
np.array([93.04, 100, 72.24]),
),
(
3,
"mean4.p3",
"col.rf.p3",
40,
37,
40,
1,
21.5,
62,
np.array([93.94, 100, 73.51]),
),
(
4,
"mean4.p4",
"col.rf.p4",
40,
37,
40,
1,
21.5,
17,
np.array([93.44, 100, 72.49]),
),
(
5,
"mean4.p5",
"col.rf.p5",
40,
37,
40,
1,
21.5,
6,
np.array([92.22, 100, 70.12]),
),
(
6,
"mean4.p6",
"col.rf.p6",
40,
37,
40,
1,
21.5,
0.4,
np.array([90.56, 100, 58.59]),
),
(
7,
"mean4.p7",
"col.rf.p1",
40,
37,
40,
1,
21.5,
843,
np.array([94.04, 100, 76.29]),
),
(
8,
"mean4.p8",
"col.rf.p2",
40,
37,
40,
1,
21.5,
200,
np.array([93.04, 100, 72.24]),
),
(
9,
"mean4.p9",
"col.rf.p3",
40,
37,
40,
1,
21.5,
62,
np.array([93.94, 100, 73.51]),
),
(
10,
"mean4.p10",
"col.rf.p4",
40,
37,
40,
1,
21.5,
17,
np.array([93.44, 100, 72.49]),
),
(
11,
"mean4.p11",
"col.rf.p5",
40,
37,
40,
1,
21.5,
6,
np.array([92.22, 100, 70.12]),
),
(
12,
"mean4.p12",
"col.rf.p6",
40,
37,
40,
1,
21.5,
0.4,
np.array([90.56, 100, 58.59]),
),
),
"R-textile": (
(
1,
"kuo.d65.vis",
"kuo.d65.col",
240,
1,
12,
0.74,
16,
250,
np.array([96.46, 100, 108.62]),
),
(
2,
"kuo.tl84.vis",
"kuo.tl84.col",
239,
1,
10,
0.74,
16,
540,
np.array([103.07, 100, 64.29]),
),
(
3,
"kuo.a.vis",
"kuo.a.col",
239,
1,
11,
0.74,
16,
250,
np.array([115.19, 100, 23.75]),
),
),
"CRT": (
(
1,
"lmean.ww",
"cold50wl",
94,
39,
44,
0.89,
100,
40,
np.array([97.13, 100, 76.62]),
),
(
2,
"lmean.bb",
"cold50gbl",
100,
39,
44,
0.89,
5,
44.5,
np.array([97.13, 100, 76.62]),
),
(
3,
"lmean.gg",
"cold50gbl",
100,
39,
44,
0.89,
20,
44.5,
np.array([97.13, 100, 76.62]),
),
(
4,
"lmean.gw",
"cold50gbl",
100,
39,
44,
0.89,
20,
44.5,
np.array([97.13, 100, 76.62]),
),
(
5,
"lmean.gb",
"cold50gbl",
100,
39,
44,
0.89,
20,
44.5,
np.array([97.13, 100, 76.62]),
),
(
6,
"ld65.gg",
"cold65.l",
103,
39,
44,
0.81,
21.5,
40.5,
np.array([94.52, 100, 114.98]),
),
(
7,
"ld65.gw",
"cold65.l",
103,
39,
44,
0.81,
21.5,
40.5,
np.array([94.52, 100, 114.98]),
),
(
8,
"lwf.gg",
"colwf.l",
103,
39,
44,
0.84,
21.5,
28.4,
np.array([102.5, 100, 47.93]),
),
(
9,
"lwf.gw",
"colwf.l",
103,
39,
44,
0.84,
21.5,
28.4,
np.array([102.5, 100, 47.93]),
),
(
10,
"la.gg",
"colal.l",
61,
29,
34,
0.84,
21.5,
20.3,
np.array([117.26, 100, 22.44]),
),
(
11,
"la.gw",
"colal.l",
61,
29,
34,
0.84,
21.5,
20.3,
np.array([117.26, 100, 22.44]),
),
),
"35mm": (
(
1,
"mean.35.p1",
"col.35.p1",
99,
93,
99,
1,
15.6,
75,
np.array([92.9, 100, 46.1]),
),
(
2,
"mean.35.p2",
"col.35.p2",
99,
93,
98,
1,
14.7,
75,
np.array([86.71, 100, 75.49]),
),
(
3,
"mean.35.p3",
"col.35.p3",
99,
93,
99,
1,
18.9,
113,
np.array([92.57, 100, 45.47]),
),
(
4,
"mean.35.p4",
"col.35.p1",
99,
93,
99,
1,
18.9,
45,
np.array([92.9, 100, 46.1]),
),
(
5,
"mean.35.p5",
"col.35.p5",
95,
89,
95,
1,
19.2,
47,
np.array([93.8, 100, 52.39]),
),
(
6,
"mean.35.p6",
"col.35.p6",
36,
26,
30,
1,
18.9,
113,
np.array([95.32, 100, 53.37]),
),
),
"LT": (
(
1,
"mean.p1",
"col.p1",
98,
94,
98,
1,
15.9,
2259,
np.array([93.09, 100, 62.02]),
),
(
2,
"mean.p2",
"col.p2",
98,
94,
98,
1,
17.1,
689,
np.array([93.23, 100, 61.15]),
),
(
3,
"mean.p3",
"col.p3",
98,
94,
98,
1,
16.9,
325,
np.array([92.36, 100, 59.91]),
),
(
4,
"mean.p4",
"col.p4",
98,
94,
98,
1,
17.4,
670,
np.array([93.05, 100, 58.58]),
),
(
5,
"mean.p5t",
"col.p5t",
97,
93,
97,
1,
9.6,
1954,
np.array([93.3, 100, 59.54]),
),
(
6,
"mean.p6t",
"col.p6t",
94,
90,
94,
1,
9.5,
619,
np.array([93.2, 100, 60.48]),
),
(
7,
"mean.p7t",
"col.p7t",
93,
89,
93,
1,
9.8,
319,
np.array([92.43, 100, 59.21]),
),
(
8,
"mean.p8",
"col.p8",
98,
94,
98,
1,
9.4,
642,
np.array([93.34, 100, 57.98]),
),
(
9,
"mean.p10",
"col.p10",
98,
94,
98,
1,
9.6,
658,
np.array([93.34, 100, 57.98]),
),
(
10,
"mean.p1t",
"col.p1t",
94,
90,
94,
1,
17.5,
680,
np.array([93.34, 100, 57.98]),
),
),
"BIT": (
(
1,
"bit_p1.vis",
"bit_p1.col",
120,
1,
16,
1,
0.6,
90,
np.array([100.6, 100, 113.2]),
),
(
2,
"bit_p2.vis",
"bit_p2.col",
120,
1,
18,
1,
0.6,
3.6,
np.array([100.6, 100, 113.2]),
),
(
3,
"bit_p3.vis",
"bit_p3.col",
120,
1,
15,
1,
0.6,
90,
np.array([100.6, 100, 113.2]),
),
(
4,
"bit_p4.vis",
"bit_p4.col",
90,
1,
18,
1,
0.6,
90,
np.array([100.6, 100, 113.2]),
),
(
5,
"bit_p5.vis",
"bit_p5.col",
90,
1,
12,
1,
0.6,
3.6,
np.array([100.6, 100, 113.2]),
),
),
}
filenames = set()
self._content = {}
for group, phases in experimental_groups.items():
experimental_phases = {}
for (
phase,
visual_filename,
colorimetric_filename,
samples_count,
neutrals_start,
neutrals_end,
S_Y_c,
Y_b,
Y_r,
XYZ_o,
) in phases:
filenames.add(visual_filename)
filenames.add(colorimetric_filename)
phase = str(phase) # noqa: PLW2901
visual_path = os.path.join(
self.record.repository, "dataset", visual_filename
)
visual_data = np.loadtxt(visual_path)
if visual_data.shape[1] >= 4:
visual_data = visual_data[..., -3:]
colorimetric_path = os.path.join(
self.record.repository, "dataset", colorimetric_filename
)
colorimetric_data = np.loadtxt(colorimetric_path)
if colorimetric_data.shape[1] >= 4:
colorimetric_data = colorimetric_data[..., -3:]
if visual_data.shape != colorimetric_data.shape:
usage_warning(
f'"{visual_filename}" visual and '
f'"{colorimetric_filename}" colorimetric file have '
f"different shape, using first data rows!"
)
colorimetric_data = colorimetric_data[:-1, ...]
experimental_phases[phase] = ExperimentalPhaseLuo1997(
phase,
as_float_array(visual_data),
as_float_array(colorimetric_data),
S_Y_c,
Y_b,
Y_r,
XYZ_o,
dict(
zip(
phase_metadata_headers,
[samples_count, (neutrals_start, neutrals_end)],
)
),
)
self._content[group] = ExperimentalGroupLuo1997(
group,
experimental_phases,
dict(
zip(
group_metadata_headers,
experimental_groups_summary[group],
)
),
)
return self._content
_DATASET_LOADER_LUO1997: DatasetLoader_Luo1997 | None = None
"""
Singleton instance of the *Luo and Rhodes (1997)*
*LUTCHI Colour Appearance Data* dataset loader.
"""
[docs]
def build_Luo1997(load: bool = True) -> DatasetLoader_Luo1997:
"""
Singleton factory that the builds *Luo and Rhodes (1997)*
*LUTCHI Colour Appearance Data* dataset loader.
Parameters
----------
load
Whether to load the dataset upon instantiation.
Returns
-------
:class:`colour_datasets.loaders.DatasetLoader_Luo1997`
Singleton instance of the *Luo and Rhodes (1997)*
*LUTCHI Colour Appearance Data* dataset loader.
References
----------
:cite:`Luo1991`, :cite:`Luo1991a`, :cite:`Luo1993`, :cite:`Luo1997`
"""
global _DATASET_LOADER_LUO1997 # noqa: PLW0603
if _DATASET_LOADER_LUO1997 is None:
_DATASET_LOADER_LUO1997 = DatasetLoader_Luo1997()
if load:
_DATASET_LOADER_LUO1997.load()
return _DATASET_LOADER_LUO1997