From ea16ec9527df75c0c53984cfa7be1ed5727c5cae Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Tue, 20 Aug 2019 21:01:35 +1200 Subject: [PATCH] Implement support for "Dyer et al. (2017)" dataset. --- BIBLIOGRAPHY.bib | 28 +- README.rst | 3 +- colour_datasets/loaders/__init__.py | 3 + colour_datasets/loaders/dyer2017.py | 996 ++++++++++++++++++ .../loaders/tests/test_dyer2017.py | 167 +++ docs/colour_datasets.loaders.rst | 9 + ...datasets.loaders.Dyer2017DatasetLoader.rst | 33 + ...colour_datasets.loaders.build_Dyer2017.rst | 6 + docs/index.rst | 3 +- 9 files changed, 1235 insertions(+), 13 deletions(-) create mode 100644 colour_datasets/loaders/dyer2017.py create mode 100644 colour_datasets/loaders/tests/test_dyer2017.py create mode 100644 docs/generated/colour_datasets.loaders.Dyer2017DatasetLoader.rst create mode 100644 docs/generated/colour_datasets.loaders.build_Dyer2017.rst diff --git a/BIBLIOGRAPHY.bib b/BIBLIOGRAPHY.bib index f66777a..4168c74 100644 --- a/BIBLIOGRAPHY.bib +++ b/BIBLIOGRAPHY.bib @@ -55,6 +55,12 @@ @article{Breneman1987b volume = 4, year = 1987, } +@misc{Dyer2017, + author = {Dyer, Scott and Forsythe, Alexander and Irons, + Jonathon and Mansencal, Thomas and Zhu, Miaoqi}, + title = {{RAW to ACES Utility Data}}, + year = 2017, +} @inproceedings{Ebner1998, author = {Ebner, Fritz and Fairchild, Mark D.}, booktitle = {Proc. SPIE 3300, Color Imaging: Device-Independent @@ -67,23 +73,17 @@ @inproceedings{Ebner1998 url = {http://proceedings.spiedigitallibrary.org/proceeding.aspx?articleid=936964}, year = 1998, } -@misc{Haanpalo, - author = {Haanpalo, Jouni and {University of Kuopio}}, - doi = {10.5281/zenodo.3269916}, - title = {{Munsell Colors Glossy (Spectrofotometer Measured)}}, - url = {http://www.uef.fi/web/spectral/munsell-colors-glossy-spectrofotometer-measured}, -} @misc{Haanpaloa, author = {Haanpalo, Jouni and {University of Kuopio}}, doi = {10.5281/zenodo.3269922}, title = {{Paper Spectra}}, url = {http://www.uef.fi/web/spectral/paper-spectra}, } -@misc{Hauta-Kasari, - author = {Hauta-Kasari, Markku and {University of Kuopio}}, - doi = {10.5281/zenodo.3269912}, - title = {{Munsell Colors Matt (Spectrofotometer Measured)}}, - url = {http://www.uef.fi/web/spectral/munsell-colors-matt-spectrofotometer-measured}, +@misc{Haanpalo, + author = {Haanpalo, Jouni and {University of Kuopio}}, + doi = {10.5281/zenodo.3269916}, + title = {{Munsell Colors Glossy (Spectrofotometer Measured)}}, + url = {http://www.uef.fi/web/spectral/munsell-colors-glossy-spectrofotometer-measured}, } @misc{Hauta-Kasaria, author = {Hauta-Kasari, Markku and {University of Kuopio}}, @@ -91,6 +91,12 @@ @misc{Hauta-Kasaria title = {{Munsell Colors Matt (AOTF Measured)}}, url = {http://www.uef.fi/web/spectral/munsell-colors-matt-aotf-measured-}, } +@misc{Hauta-Kasari, + author = {Hauta-Kasari, Markku and {University of Kuopio}}, + doi = {10.5281/zenodo.3269912}, + title = {{Munsell Colors Matt (Spectrofotometer Measured)}}, + url = {http://www.uef.fi/web/spectral/munsell-colors-matt-spectrofotometer-measured}, +} @misc{Hiltunen, author = {Hiltunen, Jouni and {University of Kuopio}}, doi = {10.5281/zenodo.3269924}, diff --git a/README.rst b/README.rst index 3a18c35..8531e12 100644 --- a/README.rst +++ b/README.rst @@ -133,7 +133,7 @@ definition: colour-science-datasets ======================= - Datasets : 15 + Datasets : 16 Synced : 1 URL : https://zenodo.org/communities/colour-science-datasets/ @@ -155,6 +155,7 @@ definition: [ ] 3245895 : New Color Specifications for ColorChecker SG and Classic Charts [ ] 3252742 : Observer Function Database [ ] 3269922 : Paper Spectra + [ ] 3372171 : RAW to ACES Utility Data A ticked checkbox means that the particular dataset has been synced locally. A dataset is loaded by using its unique number: *3245895*: diff --git a/colour_datasets/loaders/__init__.py b/colour_datasets/loaders/__init__.py index 22bf73a..dc7e17c 100644 --- a/colour_datasets/loaders/__init__.py +++ b/colour_datasets/loaders/__init__.py @@ -11,6 +11,7 @@ from .abstract import AbstractDatasetLoader from .asano2015 import Asano2015DatasetLoader, build_Asano2015 +from .dyer2017 import Dyer2017DatasetLoader, build_Dyer2017 from .ebner1998 import Ebner1998DatasetLoader, build_Ebner1998 from .hung1995 import Hung1995DatasetLoader, build_Hung1995 from .jiang2013 import Jiang2013DatasetLoader, build_Jiang2013 @@ -20,6 +21,7 @@ __all__ = ['AbstractDatasetLoader'] __all__ += ['Asano2015DatasetLoader', 'build_Asano2015'] +__all__ += ['Dyer2017DatasetLoader', 'build_Dyer2017'] __all__ += ['Ebner1998DatasetLoader', 'build_Ebner1998'] __all__ += ['Hung1995DatasetLoader', 'build_Hung1995'] __all__ += ['Jiang2013DatasetLoader', 'build_Jiang2013'] @@ -30,6 +32,7 @@ DATASET_LOADERS = CaseInsensitiveMapping({ Asano2015DatasetLoader.ID: build_Asano2015, + Dyer2017DatasetLoader.ID: build_Dyer2017, Ebner1998DatasetLoader.ID: build_Ebner1998, Hung1995DatasetLoader.ID: build_Hung1995, Jiang2013DatasetLoader.ID: build_Jiang2013, diff --git a/colour_datasets/loaders/dyer2017.py b/colour_datasets/loaders/dyer2017.py new file mode 100644 index 0000000..efa6263 --- /dev/null +++ b/colour_datasets/loaders/dyer2017.py @@ -0,0 +1,996 @@ +# -*- coding: utf-8 -*- +""" +RAW to ACES Utility Data - Dyer et al. (2017) +============================================= + +Defines the objects implementing support for +*Dyer, Forsythe, Irons, Mansencal and Zhu (2017)* +*RAW to ACES Utility Data* dataset loading: + +- :class:`colour_datasets.loaders.Dyer2017DatasetLoader` +- :func:`colour_datasets.loaders.build_Dyer2017` + +References +---------- +- :cite:`Dyer2017` : Dyer, S., Forsythe, A., Irons, J., Mansencal, T., & Zhu, + M. (2017). RAW to ACES Utility Data. +""" + +from __future__ import division, unicode_literals + +import json +import glob +import os +from collections import OrderedDict + +from colour import SpectralDistribution +# TODO: Update accordingly whenever "Colour 0.3.14" is released. +try: + from colour import MultiSpectralDistributions +except ImportError: + from colour import MultiSpectralDistribution as MultiSpectralDistributions +from colour.continuous import Signal, MultiSignal +from colour.utilities import is_numeric, is_string + +from colour_datasets.records import datasets +from colour_datasets.loaders import AbstractDatasetLoader + +__author__ = 'Colour Developers' +__copyright__ = 'Copyright (C) 2019 - Colour Developers' +__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause' +__maintainer__ = 'Colour Developers' +__email__ = 'colour-science@googlegroups.com' +__status__ = 'Production' + +__all__ = [ + 'AMPAS_SpectralDataHeader', 'AMPAS_SpectralDataMixin', + 'SpectralDistribution_AMPAS', 'Dyer2017DatasetLoader', 'build_Dyer2017' +] + + +class AMPAS_SpectralDataHeader(object): + """ + Defines the header object for an *A.M.P.A.S* spectral data. + + Parameters + ---------- + schema_version : unicode, optional + Version of the *A.M.P.A.S* spectral data schema. + catalog_number : unicode, optional + Manufacturer's product catalog number. + description : unicode, optional + Description of the spectral data in the spectral data JSON file. + document_creator : unicode, optional + Creator of the spectral data JSON file, which may be a + test lab, a research group, a standard body, a company or an + individual. + unique_identifier : unicode, optional + Description of the equipment used to measure the spectral data. + measurement_equipment : unicode, optional + Description of the equipment used to measure the spectral data. + laboratory : unicode, optional + Testing laboratory name that performed the spectral data measurements. + document_creation_date : unicode, optional + Spectral data JSON file creation date using the + *JSON DateTime Data Type*, *YYYY-MM-DDThh:mm:ss*. + comments : unicode, optional + Additional information relating to the tested and reported data. + license : unicode, optional + License under which the data is distributed. + + Attributes + ---------- + schema_version + catalog_number + description + document_creator + unique_identifier + measurement_equipment + laboratory + document_creation_date + comments + license + """ + + def __init__(self, + schema_version=None, + catalog_number=None, + description=None, + document_creator=None, + unique_identifier=None, + measurement_equipment=None, + laboratory=None, + document_creation_date=None, + comments=None, + license=None, + **kwargs): + + self._schema_version = None + self.schema_version = schema_version + self._catalog_number = None + self.catalog_number = catalog_number + self._description = None + self.description = description + self._document_creator = None + self.document_creator = document_creator + self._unique_identifier = None + self.unique_identifier = unique_identifier + self._measurement_equipment = None + self.measurement_equipment = measurement_equipment + self._laboratory = None + self.laboratory = laboratory + self._document_creation_date = None + self.document_creation_date = document_creation_date + self._comments = None + self.comments = comments + self._license = None + self.license = license + + # TODO: Re-instate "manufacturer", "model", "illuminant" and "type" + # attributes according to outcome of + # https://github.com/ampas/rawtoaces/issues/114. Those attributes are + # currently stored in "self._kwargs". + self._kwargs = kwargs + + @property + def schema_version(self): + """ + Getter and setter property for the schema version. + + Parameters + ---------- + value : unicode + Value to set the schema version with. + + Returns + ------- + unicode + Schema version. + """ + + return self._schema_version + + @schema_version.setter + def schema_version(self, value): + """ + Setter for the **self.schema_version** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'schema_version', value)) + self._schema_version = value + + @property + def catalog_number(self): + """ + Getter and setter property for the catalog number. + + Parameters + ---------- + value : unicode + Value to set the catalog number with. + + Returns + ------- + unicode + Catalog number. + """ + + return self._catalog_number + + @catalog_number.setter + def catalog_number(self, value): + """ + Setter for the **self.catalog_number** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'catalog_number', value)) + self._catalog_number = value + + @property + def description(self): + """ + Getter and setter property for the description. + + Parameters + ---------- + value : unicode + Value to set the description with. + + Returns + ------- + unicode + Description. + """ + + return self._description + + @description.setter + def description(self, value): + """ + Setter for the **self.description** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'description', value)) + self._description = value + + @property + def document_creator(self): + """ + Getter and setter property for the document creator. + + Parameters + ---------- + value : unicode + Value to set the document creator with. + + Returns + ------- + unicode + Document creator. + """ + + return self._document_creator + + @document_creator.setter + def document_creator(self, value): + """ + Setter for the **self.document_creator** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'document_creator', value)) + self._document_creator = value + + @property + def unique_identifier(self): + """ + Getter and setter property for the unique identifier. + + Parameters + ---------- + value : unicode + Value to set the unique identifier with. + + Returns + ------- + unicode + Unique identifier. + """ + + return self._unique_identifier + + @unique_identifier.setter + def unique_identifier(self, value): + """ + Setter for the **self.unique_identifier** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'unique_identifier', value)) + self._unique_identifier = value + + @property + def measurement_equipment(self): + """ + Getter and setter property for the measurement equipment. + + Parameters + ---------- + value : unicode + Value to set the measurement equipment with. + + Returns + ------- + unicode + Measurement equipment. + """ + + return self._measurement_equipment + + @measurement_equipment.setter + def measurement_equipment(self, value): + """ + Setter for the **self.measurement_equipment** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'measurement_equipment', value)) + self._measurement_equipment = value + + @property + def laboratory(self): + """ + Getter and setter property for the laboratory. + + Parameters + ---------- + value : unicode + Value to set the laboratory with. + + Returns + ------- + unicode + Laboratory. + """ + + return self._laboratory + + @laboratory.setter + def laboratory(self, value): + """ + Setter for the **self.measurement_equipment** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'laboratory', value)) + self._laboratory = value + + @property + def document_creation_date(self): + """ + Getter and setter property for the document creation date. + + Parameters + ---------- + value : unicode + Value to set the document creation date with. + + Returns + ------- + unicode + Document creation date. + """ + + return self._document_creation_date + + @document_creation_date.setter + def document_creation_date(self, value): + """ + Setter for the **self.document_creation_date** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'document_creation_date', value)) + self._document_creation_date = value + + @property + def comments(self): + """ + Getter and setter property for the comments. + + Parameters + ---------- + value : unicode + Value to set the comments with. + + Returns + ------- + unicode + Comments. + """ + + return self._comments + + @comments.setter + def comments(self, value): + """ + Setter for the **self.comments** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'comments', value)) + self._comments = value + + @property + def license(self): + """ + Getter and setter property for the license. + + Parameters + ---------- + value : unicode + Value to set the license with. + + Returns + ------- + unicode + Comments. + """ + + return self._license + + @license.setter + def license(self, value): + """ + Setter for the **self.license** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'license', value)) + self._license = value + + +class AMPAS_SpectralDataMixin(object): + """ + Defines a mixin for *A.M.P.A.S* spectral data. + + Parameters + ---------- + path : unicode, optional + Spectral data JSON file path. + header : AMPAS_SpectralDataHeader, optional + *A.M.P.A.S.* spectral distribution header. + units : unicode, optional + **{'flux', 'absorptance', 'transmittance', 'reflectance', 'intensity', + 'irradiance', 'radiance', 'exitance', 'R-Factor', 'T-Factor', + 'relative', 'other'}**, + Quantity of measurement for each element of the spectral data. + reflection_geometry : unicode, optional + **{'di:8', 'de:8', '8:di', '8:de', 'd:d', 'd:0', '45a:0', '45c:0', + '0:45a', '45x:0', '0:45x', 'other'}**, + Spectral reflectance factors geometric conditions. + transmission_geometry : unicode, optional + **{'0:0', 'di:0', 'de:0', '0:di', '0:de', 'd:d', 'other'}**, + Spectral transmittance factors geometric conditions. + bandwidth_FWHM : numeric, optional + Spectroradiometer full-width half-maximum bandwidth in nanometers. + bandwidth_corrected : bool, optional + Specifies if bandwidth correction has been applied to the measured + data. + + Notes + ----- + *Reflection Geometry* + + - di:8: Diffuse / eight-degree, specular component included. + - de:8: Diffuse / eight-degree, specular component excluded. + - 8:di: Eight-degree / diffuse, specular component included. + - 8:de: Eight-degree / diffuse, specular component excluded. + - d:d: Diffuse / diffuse. + - d:0: Alternative diffuse. + - 45a:0: Forty-five degree annular / normal. + - 45c:0: Forty-five degree circumferential / normal. + - 0:45a: Normal / forty-five degree annular. + - 45x:0: Forty-five degree directional / normal. + - 0:45x: Normal / forty-five degree directional. + - other: User-specified in comments. + + *Transmission Geometry* + + - 0:0: Normal / normal. + - di:0: Diffuse / normal, regular component included. + - de:0: Diffuse / normal, regular component excluded. + - 0:di: Normal / diffuse, regular component included. + - 0:de: Normal / diffuse, regular component excluded. + - d:d: Diffuse / diffuse. + - other: User-specified in comments. + + Attributes + ---------- + path + header + units + reflection_geometry + transmission_geometry + bandwidth_FWHM + bandwidth_corrected + + Methods + ------- + read + + References + ---------- + :cite:`IESComputerCommittee2014a` + """ + + def __init__(self, + path=None, + header=None, + units=None, + reflection_geometry=None, + transmission_geometry=None, + bandwidth_FWHM=None, + bandwidth_corrected=None): + super(AMPAS_SpectralDataMixin, self).__init__() + + self._path = None + self.path = path + self._header = None + self.header = (header + if header is not None else AMPAS_SpectralDataHeader()) + self._units = None + self.units = units + self._reflection_geometry = None + self.reflection_geometry = reflection_geometry + self._transmission_geometry = None + self.transmission_geometry = transmission_geometry + self._bandwidth_FWHM = None + self.bandwidth_FWHM = bandwidth_FWHM + self._bandwidth_corrected = None + self.bandwidth_corrected = bandwidth_corrected + + @property + def path(self): + """ + Getter and setter property for the path. + + Parameters + ---------- + value : unicode + Value to set the path with. + + Returns + ------- + unicode + Path. + """ + + return self._path + + @path.setter + def path(self, value): + """ + Setter for the **self.path** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'path', value)) + self._path = value + + @property + def header(self): + """ + Getter and setter property for the header. + + Parameters + ---------- + value : AMPAS_SpectralDataHeader + Value to set the header with. + + Returns + ------- + AMPAS_SpectralDataHeader + Header. + """ + + return self._header + + @header.setter + def header(self, value): + """ + Setter for the **self.header** property. + """ + + if value is not None: + assert isinstance(value, AMPAS_SpectralDataHeader), ( + '"{0}" attribute: "{1}" is not a "AMPAS_SpectralDataHeader" ' + 'instance!'.format('header', value)) + self._header = value + + @property + def units(self): + """ + Getter and setter property for the units. + + Parameters + ---------- + value : unicode + Value to set the units with. + + Returns + ------- + unicode + Spectral quantity. + """ + + return self._units + + @units.setter + def units(self, value): + """ + Setter for the **self.units** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'units', value)) + self._units = value + + @property + def reflection_geometry(self): + """ + Getter and setter property for the reflection geometry. + + Parameters + ---------- + value : unicode + Value to set the reflection geometry with. + + Returns + ------- + unicode + Reflection geometry. + """ + + return self._reflection_geometry + + @reflection_geometry.setter + def reflection_geometry(self, value): + """ + Setter for the **self.reflection_geometry** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'reflection_geometry', value)) + self._reflection_geometry = value + + @property + def transmission_geometry(self): + """ + Getter and setter property for the transmission geometry. + + Parameters + ---------- + value : unicode + Value to set the transmission geometry with. + + Returns + ------- + unicode + Transmission geometry. + """ + + return self._transmission_geometry + + @transmission_geometry.setter + def transmission_geometry(self, value): + """ + Setter for the **self.transmission_geometry** property. + """ + + if value is not None: + assert is_string(value), ( + '"{0}" attribute: "{1}" is not a "string" like object!'.format( + 'transmission_geometry', value)) + self._transmission_geometry = value + + @property + def bandwidth_FWHM(self): + """ + Getter and setter property for the full-width half-maximum bandwidth. + + Parameters + ---------- + value : numeric + Value to set the full-width half-maximum bandwidth with. + + Returns + ------- + numeric + Full-width half-maximum bandwidth. + """ + + return self._bandwidth_FWHM + + @bandwidth_FWHM.setter + def bandwidth_FWHM(self, value): + """ + Setter for the **self.bandwidth_FWHM** property. + """ + + if value is not None: + assert is_numeric(value), ( + '"{0}" attribute: "{1}" is not a "numeric"!'.format( + 'bandwidth_FWHM', value)) + + self._bandwidth_FWHM = value + + @property + def bandwidth_corrected(self): + """ + Getter and setter property for whether bandwidth correction has been + applied to the measured data. + + Parameters + ---------- + value : bool + Whether bandwidth correction has been applied to the measured data. + + Returns + ------- + bool + Whether bandwidth correction has been applied to the measured data. + """ + + return self._bandwidth_corrected + + @bandwidth_corrected.setter + def bandwidth_corrected(self, value): + """ + Setter for the **self.bandwidth_corrected** property. + """ + + if value is not None: + assert isinstance(value, bool), ( + '"{0}" attribute: "{1}" is not a "bool" instance!'.format( + 'bandwidth_corrected', value)) + + self._bandwidth_corrected = value + + def read(self): + """ + Reads and parses the spectral data JSON file path. + + Returns + ------- + bool + Definition success. + """ + + with open(self._path, 'r') as json_file: + content = json.load(json_file) + + self._header = AMPAS_SpectralDataHeader(**content['header']) + for attribute in ('units', 'reflection_geometry', + 'transmission_geometry', 'bandwidth_FWHM', + 'bandwidth_corrected'): + setattr(self, '_{0}'.format(attribute), + content['spectral_data'][attribute]) + + index = content['spectral_data']['index']['main'] + data = content['spectral_data']['data']['main'] + + if len(index) == 1: + self.domain, self.range = Signal.signal_unpack_data( + {k: v[0] + for k, v in data.items()}) + else: + self.signals = MultiSignal.multi_signal_unpack_data( + data, labels=index) + + # TODO: Re-instate "manufacturer", "model", "illuminant" and "type" + # attributes according to outcome of + # https://github.com/ampas/rawtoaces/issues/114. + if ('manufacturer' in self._header._kwargs and + 'model' in self._header._kwargs): + self.name = '{0} {1}'.format(self._header._kwargs['manufacturer'], + self._header._kwargs['model']) + elif 'illuminant' in self._header._kwargs: + self.name = self._header._kwargs['illuminant'] + elif 'type' in self._header._kwargs: + self.name = self._header._kwargs['type'] + + return self + + +class SpectralDistribution_AMPAS(AMPAS_SpectralDataMixin, + SpectralDistribution): + """ + Defines an *A.M.P.A.S* spectral distribution. + + This class can read *A.M.P.A.S* spectral data JSON files. + + Parameters + ---------- + path : unicode, optional + Spectral data JSON file path. + header : AMPAS_SpectralDataHeader, optional + *A.M.P.A.S.* spectral distribution header. + units : unicode, optional + **{'flux', 'absorptance', 'transmittance', 'reflectance', 'intensity', + 'irradiance', 'radiance', 'exitance', 'R-Factor', 'T-Factor', + 'relative', 'other'}**, + Quantity of measurement for each element of the spectral data. + reflection_geometry : unicode, optional + **{'di:8', 'de:8', '8:di', '8:de', 'd:d', 'd:0', '45a:0', '45c:0', + '0:45a', '45x:0', '0:45x', 'other'}**, + Spectral reflectance factors geometric conditions. + transmission_geometry : unicode, optional + **{'0:0', 'di:0', 'de:0', '0:di', '0:de', 'd:d', 'other'}**, + Spectral transmittance factors geometric conditions. + bandwidth_FWHM : numeric, optional + Spectroradiometer full-width half-maximum bandwidth in nanometers. + bandwidth_corrected : bool, optional + Specifies if bandwidth correction has been applied to the measured + data. + + References + ---------- + :cite:`IESComputerCommittee2014a` + """ + + def __init__(self, + path=None, + header=None, + units=None, + reflection_geometry=None, + transmission_geometry=None, + bandwidth_FWHM=None, + bandwidth_corrected=None): + super(SpectralDistribution_AMPAS, self).__init__( + path, header, units, reflection_geometry, transmission_geometry, + bandwidth_FWHM, bandwidth_corrected) + + +class MultiSpectralDistributions_AMPAS(AMPAS_SpectralDataMixin, + MultiSpectralDistributions): + """ + Defines the *A.M.P.A.S* multi-spectral distributions. + + This class can read *A.M.P.A.S* spectral data JSON files. + + Parameters + ---------- + path : unicode, optional + Spectral data JSON file path. + header : AMPAS_SpectralDataHeader, optional + *A.M.P.A.S.* spectral distribution header. + units : unicode, optional + **{'flux', 'absorptance', 'transmittance', 'reflectance', 'intensity', + 'irradiance', 'radiance', 'exitance', 'R-Factor', 'T-Factor', + 'relative', 'other'}**, + Quantity of measurement for each element of the spectral data. + reflection_geometry : unicode, optional + **{'di:8', 'de:8', '8:di', '8:de', 'd:d', 'd:0', '45a:0', '45c:0', + '0:45a', '45x:0', '0:45x', 'other'}**, + Spectral reflectance factors geometric conditions. + transmission_geometry : unicode, optional + **{'0:0', 'di:0', 'de:0', '0:di', '0:de', 'd:d', 'other'}**, + Spectral transmittance factors geometric conditions. + bandwidth_FWHM : numeric, optional + Spectroradiometer full-width half-maximum bandwidth in nanometers. + bandwidth_corrected : bool, optional + Specifies if bandwidth correction has been applied to the measured + data. + + References + ---------- + :cite:`IESComputerCommittee2014a` + """ + + def __init__(self, + path=None, + header=None, + units=None, + reflection_geometry=None, + transmission_geometry=None, + bandwidth_FWHM=None, + bandwidth_corrected=None): + super(MultiSpectralDistributions_AMPAS, self).__init__( + path, header, units, reflection_geometry, transmission_geometry, + bandwidth_FWHM, bandwidth_corrected) + + +class Dyer2017DatasetLoader(AbstractDatasetLoader): + """ + Defines the *Dyer et al. (2017)* *RAW to ACES Utility Data* dataset + loader. + + Attributes + ---------- + ID + + Methods + ------- + load + + References + ---------- + :cite:`Dyer2017` + """ + + ID = '3372171' + """ + Dataset record id, i.e. the *Zenodo* record number. + + ID : unicode + """ + + def __init__(self): + super(Dyer2017DatasetLoader, + self).__init__(datasets()[Dyer2017DatasetLoader.ID]) + + def load(self): + """ + Syncs, parses, converts and returns the *Dyer et al. (2017)* + *RAW to ACES Utility Data* dataset content. + + Returns + ------- + OrderedDict + *Dyer et al. (2017)* *RAW to ACES Utility Data* dataset content. + + Examples + -------- + >>> from colour_datasets.utilities import suppress_stdout + >>> dataset = Dyer2017DatasetLoader() + >>> with suppress_stdout(): + ... dataset.load() + >>> len(dataset.content.keys()) + 4 + """ + + super(Dyer2017DatasetLoader, self).sync() + + self._content = OrderedDict() + + for directory in ('camera', 'cmf', 'illuminant', 'training'): + self._content[directory] = OrderedDict() + factory = (SpectralDistribution_AMPAS if directory == 'illuminant' + else MultiSpectralDistributions_AMPAS) + glob_pattern = os.path.join(self.record.repository, 'dataset', + 'data', directory, '*.json') + for path in glob.glob(glob_pattern): + msds = factory(path).read() + self._content[directory][msds.name] = msds + + return self._content + + +_DYER2017_DATASET_LOADER = None +""" +Singleton instance of the *Dyer et al. (2017)* *RAW to ACES Utility Data* +dataset loader. + +_DYER2017_DATASET_LOADER : Dyer2017DatasetLoader +""" + + +def build_Dyer2017(load=True): + """ + Singleton factory that builds the *Dyer et al. (2017)* + *RAW to ACES Utility Data* dataset loader. + + Parameters + ---------- + load : bool, optional + Whether to load the dataset upon instantiation. + + Returns + ------- + Dyer2017DatasetLoader + Singleton instance of the *Dyer et al. (2017)* + *RAW to ACES Utility Data* dataset loader. + + References + ---------- + :cite:`Dyer2017` + """ + + global _DYER2017_DATASET_LOADER + + if _DYER2017_DATASET_LOADER is None: + _DYER2017_DATASET_LOADER = Dyer2017DatasetLoader() + if load: + _DYER2017_DATASET_LOADER.load() + + return _DYER2017_DATASET_LOADER diff --git a/colour_datasets/loaders/tests/test_dyer2017.py b/colour_datasets/loaders/tests/test_dyer2017.py new file mode 100644 index 0000000..eaaab8d --- /dev/null +++ b/colour_datasets/loaders/tests/test_dyer2017.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +""" +Defines unit tests for :mod:`colour_datasets.loaders.dyer2017` module. +""" + +from __future__ import division, unicode_literals + +import numpy as np +import unittest + +from colour_datasets.loaders import Dyer2017DatasetLoader, build_Dyer2017 + +__author__ = 'Colour Developers' +__copyright__ = 'Copyright (C) 2019 - Colour Developers' +__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause' +__maintainer__ = 'Colour Developers' +__email__ = 'colour-science@googlegroups.com' +__status__ = 'Production' + +__all__ = ['TestDyer2017DatasetLoader', 'TestBuildDyer2017'] + + +class TestDyer2017DatasetLoader(unittest.TestCase): + """ + Defines :class:`colour_datasets.loaders.dyer2017.Dyer2017DatasetLoader` + class unit tests methods. + """ + + def test_required_attributes(self): + """ + Tests presence of required attributes. + """ + + required_attributes = ('ID', ) + + for attribute in required_attributes: + self.assertIn(attribute, dir(Dyer2017DatasetLoader)) + + def test_required_methods(self): + """ + Tests presence of required methods. + """ + + required_methods = ('load', ) + + for method in required_methods: + self.assertIn(method, dir(Dyer2017DatasetLoader)) + + def test_load(self): + """ + Tests :func:`colour_datasets.loaders.dyer2017.Dyer2017DatasetLoader.\ +load` method. + """ + + dataset = Dyer2017DatasetLoader() + self.assertListEqual( + sorted(dataset.load().keys()), + ['camera', 'cmf', 'illuminant', 'training']) + + np.testing.assert_almost_equal( + dataset.load()['camera']['canon eos 5d mark ii'][555], + np.array([ + 0.165200000000000, + 0.802800000000000, + 0.028300000000000, + ]), + decimal=7) + np.testing.assert_almost_equal( + dataset.load()['cmf']['cie-1931'][555], + np.array([ + 0.512050100000000, + 1.000000000000000, + 0.005749999000000, + ]), + decimal=7) + np.testing.assert_almost_equal( + dataset.load()['illuminant']['iso7589'][555], + np.array([0.485000000000000]), + decimal=7) + np.testing.assert_almost_equal( + dataset.load()['training']['190-patch'][555], + np.array([ + 0.016543747000000, 0.089454049000000, 0.775860114000000, + 0.199500000000000, 0.589294177000000, 0.426983879000000, + 0.299315241000000, 0.195307174000000, 0.113005514000000, + 0.065695622000000, 0.030550537000000, 0.185923210000000, + 0.138998782000000, 0.253323493000000, 0.116890395000000, + 0.059878320000000, 0.386424591000000, 0.242522104000000, + 0.042793898000000, 0.039108407000000, 0.340616303000000, + 0.109391839000000, 0.024575114000000, 0.013437553000000, + 0.165550372000000, 0.044162979000000, 0.038362653000000, + 0.050943800000000, 0.060706606000000, 0.017150009000000, + 0.030958883000000, 0.294163695000000, 0.094815764000000, + 0.013631268000000, 0.011556292000000, 0.102712966000000, + 0.014063110000000, 0.088584881000000, 0.019506551000000, + 0.049543471000000, 0.216543615000000, 0.148685793000000, + 0.426425448000000, 0.066590491000000, 0.185951857000000, + 0.161431933000000, 0.046959872000000, 0.337386898000000, + 0.044950244000000, 0.186142255000000, 0.217803413000000, + 0.176242473000000, 0.180234723000000, 0.573066803000000, + 0.396281106000000, 0.130612404000000, 0.489232284000000, + 0.086611731000000, 0.482820917000000, 0.285489705000000, + 0.390752390000000, 0.553103082000000, 0.761045838000000, + 0.448310405000000, 0.751459057000000, 0.296973364000000, + 0.845515046000000, 0.600851468000000, 0.790979892000000, + 0.116890676000000, 0.471334928000000, 0.796627165000000, + 0.318975867000000, 0.365398300000000, 0.663541772000000, + 0.243604910000000, 0.817055901000000, 0.746637464000000, + 0.142703616000000, 0.060728679000000, 0.244645070000000, + 0.525056690000000, 0.125884506000000, 0.159583709000000, + 0.333025306000000, 0.099145922000000, 0.115960832000000, + 0.142817663000000, 0.105357260000000, 0.154603755000000, + 0.136542750000000, 0.235944300000000, 0.322853029000000, + 0.636786365000000, 0.478067566000000, 0.357385246000000, + 0.233766382000000, 0.313229098000000, 0.470989753000000, + 0.219620176000000, 0.087619811000000, 0.181083141000000, + 0.237307524000000, 0.134183724000000, 0.052929690000000, + 0.335421880000000, 0.355101839000000, 0.051487691000000, + 0.225285679000000, 0.208450311000000, 0.137336941000000, + 0.069794973000000, 0.311496347000000, 0.655141187000000, + 0.092340917000000, 0.446097178000000, 0.595113151000000, + 0.051742762000000, 0.308310085000000, 0.218221361000000, + 0.459776672000000, 0.483055996000000, 0.209489271000000, + 0.270752508000000, 0.581475704000000, 0.150634167000000, + 0.162358582000000, 0.576733107000000, 0.327650514000000, + 0.341401404000000, 0.153771821000000, 0.402136399000000, + 0.079694635000000, 0.068407983000000, 0.534616880000000, + 0.183116936000000, 0.171525933000000, 0.037855717000000, + 0.168182056000000, 0.559997393000000, 0.144518923000000, + 0.108677750000000, 0.075848465000000, 0.106230967000000, + 0.271748990000000, 0.108267178000000, 0.363043033000000, + 0.041006456000000, 0.031950058000000, 0.173380906000000, + 0.359966187000000, 0.044712750000000, 0.100602091000000, + 0.175245406000000, 0.061063126000000, 0.258613296000000, + 0.026866789000000, 0.197704679000000, 0.543435154000000, + 0.113192419000000, 0.267300817000000, 0.135820481000000, + 0.154000795000000, 0.045469997000000, 0.408044588000000, + 0.011999794000000, 0.047949059000000, 0.052502489000000, + 0.065332167000000, 0.151156617000000, 0.132535937000000, + 0.037475628000000, 0.138033009000000, 0.210685187000000, + 0.265259355000000, 0.523381186000000, 0.105874515000000, + 0.164640208000000, 0.109354860000000, 0.437779019000000, + 0.024237616000000, 0.144939306000000, 0.297763330000000, + 0.178469229000000, 0.312304014000000, 0.327352013000000, + 0.026469427000000, 0.431901773000000, 0.015418874000000, + 0.158126080000000 + ]), + decimal=7) + + +class TestBuildDyer2017(unittest.TestCase): + """ + Defines :func:`colour_datasets.loaders.dyer2017.build_Dyer2017` + definition unit tests methods. + """ + + def test_build_Dyer2017(self): + """ + Tests :func:`colour_datasets.loaders.dyer2017.build_Dyer2017` + definition. + """ + + self.assertIs(build_Dyer2017(), build_Dyer2017()) + + +if __name__ == '__main__': + unittest.main() diff --git a/docs/colour_datasets.loaders.rst b/docs/colour_datasets.loaders.rst index b3ed901..8065fc3 100644 --- a/docs/colour_datasets.loaders.rst +++ b/docs/colour_datasets.loaders.rst @@ -145,6 +145,15 @@ Paper Spectra PaperSpectraDatasetLoader build_PaperSpectra +RAW to ACES Utility Data - Dyer et al. (2017) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + Dyer2017DatasetLoader + build_Dyer2017 + Loading the Datasets -------------------- diff --git a/docs/generated/colour_datasets.loaders.Dyer2017DatasetLoader.rst b/docs/generated/colour_datasets.loaders.Dyer2017DatasetLoader.rst new file mode 100644 index 0000000..31d58f6 --- /dev/null +++ b/docs/generated/colour_datasets.loaders.Dyer2017DatasetLoader.rst @@ -0,0 +1,33 @@ +colour\_datasets.loaders.Dyer2017DatasetLoader +============================================== + +.. currentmodule:: colour_datasets.loaders + +.. autoclass:: Dyer2017DatasetLoader + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Dyer2017DatasetLoader.__init__ + ~Dyer2017DatasetLoader.load + ~Dyer2017DatasetLoader.sync + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~Dyer2017DatasetLoader.ID + ~Dyer2017DatasetLoader.content + ~Dyer2017DatasetLoader.id + ~Dyer2017DatasetLoader.record + + \ No newline at end of file diff --git a/docs/generated/colour_datasets.loaders.build_Dyer2017.rst b/docs/generated/colour_datasets.loaders.build_Dyer2017.rst new file mode 100644 index 0000000..1be9d53 --- /dev/null +++ b/docs/generated/colour_datasets.loaders.build_Dyer2017.rst @@ -0,0 +1,6 @@ +colour\_datasets.loaders.build\_Dyer2017 +======================================== + +.. currentmodule:: colour_datasets.loaders + +.. autofunction:: build_Dyer2017 \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 123077e..728d692 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -120,7 +120,7 @@ definition: colour-science-datasets ======================= - Datasets : 15 + Datasets : 16 Synced : 1 URL : https://zenodo.org/communities/colour-science-datasets/ @@ -142,6 +142,7 @@ definition: [ ] 3245895 : New Color Specifications for ColorChecker SG and Classic Charts [ ] 3252742 : Observer Function Database [ ] 3269922 : Paper Spectra + [ ] 3372171 : RAW to ACES Utility Data A ticked checkbox means that the particular dataset has been synced locally. A dataset is loaded by using its unique number: *3245895*: