├── apps ├── __init__.py └── common.py ├── tests ├── __init__.py ├── resources │ ├── synthetic_001.zip │ ├── synthetic_002.zip │ ├── synthetic_003.zip │ ├── synthetic_004.zip │ ├── synthetic_001 │ │ ├── data │ │ │ ├── grey_card │ │ │ │ ├── grey_card_0001.tif │ │ │ │ ├── grey_card_0002.tif │ │ │ │ └── grey_card_0003.tif │ │ │ └── colour_checker │ │ │ │ ├── 0 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ ├── 1 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ ├── 2 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ ├── 3 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ ├── -1 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ ├── -2 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ │ │ └── -3 │ │ │ │ ├── colour_checker_0001.tif │ │ │ │ ├── colour_checker_0002.tif │ │ │ │ └── colour_checker_0003.tif │ │ ├── synthetic_001.json │ │ └── test_project.json │ ├── synthetic_003 │ │ ├── data │ │ │ ├── grey_card │ │ │ │ └── grey_card_0001.tif │ │ │ └── colour_checker │ │ │ │ ├── 0 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── 1 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── 2 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── 3 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── -1 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── -2 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ └── -3 │ │ │ │ └── colour_checker_0001.tif │ │ └── synthetic_003.json │ ├── synthetic_004 │ │ └── data │ │ │ ├── grey_card │ │ │ └── grey_card_0001.tif │ │ │ └── colour_checker │ │ │ ├── 0 │ │ │ └── colour_checker_0001.tif │ │ │ ├── 1 │ │ │ └── colour_checker_0001.tif │ │ │ ├── 2 │ │ │ └── colour_checker_0001.tif │ │ │ ├── 3 │ │ │ └── colour_checker_0001.tif │ │ │ ├── -1 │ │ │ └── colour_checker_0001.tif │ │ │ ├── -2 │ │ │ └── colour_checker_0001.tif │ │ │ └── -3 │ │ │ └── colour_checker_0001.tif │ ├── synthetic_002 │ │ ├── data │ │ │ └── colour_checker │ │ │ │ ├── 1.75 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── 2.25 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── 3.4 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── -0.75 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── -2.125 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ ├── -3.25 │ │ │ │ └── colour_checker_0001.tif │ │ │ │ └── 0.333 │ │ │ │ └── colour_checker_0001.tif │ │ └── synthetic_002.json │ ├── project_settings.json │ ├── download_manifest.json │ ├── samples_camera.json │ ├── samples_reference.json │ ├── example_from_folder.json │ └── samples_decoded_clipped.json ├── test_constants.py ├── test_project_settings.py ├── test_utils.py └── test_common.py ├── .flake8 ├── aces ├── __init__.py └── idt │ ├── framework │ └── __init__.py │ ├── generators │ ├── __init__.py │ ├── prelinearized_idt.py │ └── tonemapped_idt.py │ ├── core │ ├── __init__.py │ ├── transform_id.py │ ├── structures.py │ └── constants.py │ ├── __init__.py │ └── application.py ├── assets ├── aces-logo.png └── custom.css ├── SUPPORT.md ├── docs └── _static │ ├── idt_calculator_p_2013_001.png │ ├── idt_archive_explicit_json_file.png │ ├── idt_archive_explicit_structure.png │ ├── idt_archive_implicit_structure.png │ ├── idt_calculator_prosumer_camera.png │ ├── idt_archive_implicit_structure_fractional_ev.png │ └── idt_archive_explicit_json_file_floating_point_ev.png ├── docker-compose.yml ├── .gitignore ├── Dockerfile ├── app.py ├── .pre-commit-config.yaml ├── LICENSE.md ├── CONTRIBUTING.md ├── requirements.txt ├── index.py ├── tasks.py ├── pyproject.toml └── README.md /apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203 4 | -------------------------------------------------------------------------------- /aces/__init__.py: -------------------------------------------------------------------------------- 1 | import matplotlib as mpl 2 | 3 | mpl.use("Agg") 4 | -------------------------------------------------------------------------------- /assets/aces-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/assets/aces-logo.png -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | For support, please visit [ACESCentral.com](https://acescentral.com) 4 | -------------------------------------------------------------------------------- /tests/resources/synthetic_001.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001.zip -------------------------------------------------------------------------------- /tests/resources/synthetic_002.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002.zip -------------------------------------------------------------------------------- /tests/resources/synthetic_003.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003.zip -------------------------------------------------------------------------------- /tests/resources/synthetic_004.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004.zip -------------------------------------------------------------------------------- /aces/idt/framework/__init__.py: -------------------------------------------------------------------------------- 1 | from .project_settings import IDTProjectSettings 2 | 3 | __all__ = ["IDTProjectSettings"] 4 | -------------------------------------------------------------------------------- /docs/_static/idt_calculator_p_2013_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_calculator_p_2013_001.png -------------------------------------------------------------------------------- /docs/_static/idt_archive_explicit_json_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_archive_explicit_json_file.png -------------------------------------------------------------------------------- /docs/_static/idt_archive_explicit_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_archive_explicit_structure.png -------------------------------------------------------------------------------- /docs/_static/idt_archive_implicit_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_archive_implicit_structure.png -------------------------------------------------------------------------------- /docs/_static/idt_calculator_prosumer_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_calculator_prosumer_camera.png -------------------------------------------------------------------------------- /docs/_static/idt_archive_implicit_structure_fractional_ev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_archive_implicit_structure_fractional_ev.png -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/grey_card/grey_card_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/grey_card/grey_card_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/grey_card/grey_card_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/grey_card/grey_card_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/grey_card/grey_card_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/grey_card/grey_card_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/grey_card/grey_card_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/grey_card/grey_card_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/grey_card/grey_card_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/grey_card/grey_card_0001.tif -------------------------------------------------------------------------------- /docs/_static/idt_archive_explicit_json_file_floating_point_ev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/docs/_static/idt_archive_explicit_json_file_floating_point_ev.png -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-1/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-2/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/-3/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/0/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/1/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/2/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0002.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0002.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0003.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_001/data/colour_checker/3/colour_checker_0003.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/-1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/-1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/-2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/-2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/-3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/-3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/0/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/0/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_003/data/colour_checker/3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_003/data/colour_checker/3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/-1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/-1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/-2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/-2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/-3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/-3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/0/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/0/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/1/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/1/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/2/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/2/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_004/data/colour_checker/3/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_004/data/colour_checker/3/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/1.75/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/1.75/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/2.25/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/2.25/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/3.4/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/3.4/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/-0.75/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/-0.75/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/-2.125/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/-2.125/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/-3.25/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/-3.25/colour_checker_0001.tif -------------------------------------------------------------------------------- /tests/resources/synthetic_002/data/colour_checker/0.333/colour_checker_0001.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ampas/idt-calculator/HEAD/tests/resources/synthetic_002/data/colour_checker/0.333/colour_checker_0001.tif -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | web: 4 | build: . 5 | image: acescentral/idt-calculator:latest 6 | ports: 7 | - "8000:8000" 8 | environment: 9 | - NAME=idt-calculator 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Common Files 2 | *.egg-info 3 | *.pyc 4 | *.pyo 5 | .DS_Store 6 | .coverage* 7 | uv.lock 8 | 9 | # Common Directories 10 | .fleet/ 11 | .idea/ 12 | .ipynb_checkpoints/ 13 | .python-version 14 | .vs/ 15 | .vscode/ 16 | .sandbox/ 17 | build/ 18 | dist/ 19 | docs/_build/ 20 | docs/generated/ 21 | node_modules/ 22 | references/ 23 | 24 | __pycache__ 25 | 26 | # Project Directories 27 | tests/output/ 28 | /tests/resources/FULL_STOPS_EXR.zip 29 | /tests/resources/PTZ_160.zip 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | RUN apt-get update \ 4 | && apt-get -y install ffmpeg libsm6 libxext6 5 | WORKDIR /tmp 6 | COPY ./requirements.txt /tmp 7 | RUN pip install -r requirements.txt \ 8 | && rm /tmp/requirements.txt 9 | 10 | ARG CACHE_DATE 11 | 12 | RUN mkdir -p /home/ampas/idt-calculator 13 | WORKDIR /home/ampas/idt-calculator 14 | COPY . /home/ampas/idt-calculator 15 | 16 | CMD sh -c 'if [ -z "${SSL_CERTIFICATE}" ]; then \ 17 | gunicorn --timeout 1200 --log-level debug -b 0.0.0.0:8000 index:SERVER; else \ 18 | gunicorn --timeout 1200 --certfile "${SSL_CERTIFICATE}" --keyfile "${SSL_KEY}" --log-level debug -b 0.0.0.0:8000 index:SERVER; fi' 19 | -------------------------------------------------------------------------------- /tests/resources/project_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "aces_transform_id": "", 3 | "aces_username": "", 4 | "additional_camera_settings": "", 5 | "camera_make": "", 6 | "camera_model": "", 7 | "cat": "CAT02", 8 | "debayering_platform": "", 9 | "debayering_settings": "", 10 | "decoding_method": "Median", 11 | "encoding_colourspace": "", 12 | "ev_range": [-1.0, 0.0, 1.0], 13 | "grey_card_reference": [0.18, 0.18, 0.18], 14 | "illuminant_interpolator": "Linear", 15 | "iso": 800, 16 | "lighting_setup_description": "", 17 | "lut_size": 1024, 18 | "lut_smoothing": 16, 19 | "optimisation_space": "Oklab", 20 | "rgb_display_colourspace": "sRGB", 21 | "temperature": 5600 22 | } 23 | -------------------------------------------------------------------------------- /aces/idt/generators/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_generator import IDTBaseGenerator 2 | from .log_camera import IDTGeneratorLogCamera 3 | from .prelinearized_idt import IDTGeneratorPreLinearizedCamera 4 | from .tonemapped_idt import IDTGeneratorToneMappedCamera 5 | 6 | GENERATORS = { 7 | IDTGeneratorLogCamera.GENERATOR_NAME: IDTGeneratorLogCamera, 8 | IDTGeneratorPreLinearizedCamera.GENERATOR_NAME: IDTGeneratorPreLinearizedCamera, 9 | IDTGeneratorToneMappedCamera.GENERATOR_NAME: IDTGeneratorToneMappedCamera, 10 | } 11 | 12 | __all__ = ["IDTBaseGenerator"] 13 | __all__ += ["IDTGeneratorLogCamera"] 14 | __all__ += ["IDTGeneratorPreLinearizedCamera"] 15 | __all__ += ["IDTGeneratorToneMappedCamera"] 16 | __all__ += ["GENERATORS"] 17 | -------------------------------------------------------------------------------- /tests/resources/download_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "FULL_STOPS_EXR.zip": { 3 | "URL": "https://www.dropbox.com/scl/fi/srna8mw98wxn47np0adoj/FULL_STOPS_EXR.zip?rlkey=e2n0t59g0olu40gxk31mm01sh&dl=0", 4 | "SHA256": "7f4bb72394e502815e85ef4a45c4a1c027d111e6a54b058b9d4d75e5b76e1127" 5 | }, 6 | "FULL_STOPS.zip": { 7 | "URL": "", 8 | "SHA256": "9a8f53d0ee191b040e1270d5e170bab6ad0b6002ee5d42fc7d71b97e81ff5710" 9 | }, 10 | "PTZ_160.zip": { 11 | "URL": "https://www.dropbox.com/scl/fi/9ni9vcpwahrx8s3l1zqkf/PTZ_160.zip?rlkey=m3ddjysrhmkbfxx7ktgpmgfft&dl=0", 12 | "SHA256": "4c3c85a293166ef4896713fc99f28870b90821c1f1d0118c6ceae3b40520d4c2" 13 | }, 14 | "DPX_10_log.zip": { 15 | "URL": "", 16 | "SHA256": "7eef2787b5e4571810b508bc14d5673637e0507f638df8242d49830714dab9fa" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/test_constants.py: -------------------------------------------------------------------------------- 1 | """Define the unit tests for the :mod:`aces.idt.core.constants` module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from aces.idt.core.constants import CAT 6 | from tests.test_utils import TestIDTBase 7 | 8 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 9 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 10 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 11 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 12 | __email__ = "acessupport@oscars.org" 13 | __status__ = "Production" 14 | 15 | __all__ = [ 16 | "TestCAT", 17 | ] 18 | 19 | 20 | class TestCAT(TestIDTBase): 21 | """ 22 | Define the unit tests for the :class:`aces.idt.CAT` class. 23 | """ 24 | 25 | def test_ALL(self) -> None: 26 | """ 27 | Test the :class:`aces.idt.CAT.ALL` property. 28 | 29 | A previous implementation of :class:`aces.idt.CAT.ALL` property was 30 | combining classmethod and property decorators which was deprecated 31 | in Python 3.11. 32 | """ 33 | 34 | self.assertEqual(len(CAT.ALL), 13) 35 | -------------------------------------------------------------------------------- /assets/custom.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(252, 252, 251) !important; 3 | } 4 | 5 | a { 6 | color: rgb(193, 161, 56) !important; 7 | } 8 | 9 | #apps { 10 | padding: 0 !important; 11 | } 12 | 13 | #aces-logo { 14 | display: inline-block; 15 | height: 75px; 16 | padding-bottom: 0.5em; 17 | padding-top: 0.5em; 18 | } 19 | 20 | #app-title { 21 | color: rgb(193, 161, 56) !important; 22 | padding-top: 0.5em; 23 | } 24 | 25 | .navbar { 26 | background-color: #323232 !important; 27 | } 28 | 29 | .nav-item { 30 | font-size: x-large; 31 | } 32 | 33 | /* Dangerous as it destroys this particular style... */ 34 | .ms-auto { 35 | margin-left: 0 !important; 36 | } 37 | 38 | .tab { 39 | border-top-left-radius: 5px; 40 | border-top-right-radius: 5px; 41 | } 42 | 43 | .tab--selected { 44 | border-top: 2px solid rgb(193, 161, 56) !important; 45 | } 46 | 47 | .tab-content { 48 | padding-top: 1em !important; 49 | } 50 | 51 | .dash-uploader-default, 52 | .dash-uploader-complete { 53 | background-color: rgba(33, 37, 41, 0.03) !important; 54 | } 55 | 56 | .btn-primary { 57 | background-color: rgb(193, 161, 56) !important; 58 | border-color: rgb(193, 161, 56) !important; 59 | } 60 | 61 | .nav-link.active { 62 | } 63 | 64 | .progress-value { 65 | display: none !important; 66 | } 67 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Application 3 | =========== 4 | """ 5 | 6 | import os 7 | 8 | import dash 9 | import dash_bootstrap_components 10 | from flask import Flask 11 | 12 | __author__ = "Alex Forsythe, Gayle McAdams, Thomas Mansencal, Nick Shaw" 13 | __copyright__ = "Copyright 2020 Academy of Motion Picture Arts and Sciences" 14 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 15 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 16 | __email__ = "acessupport@oscars.org" 17 | __status__ = "Production" 18 | 19 | __application_name__ = "AMPAS - Apps" 20 | 21 | __major_version__ = "0" 22 | __minor_version__ = "1" 23 | __change_version__ = "0" 24 | __version__ = f"{__major_version__}.{__minor_version__}.{__change_version__}" 25 | 26 | __all__ = ["SERVER", "SERVER_URL", "APP"] 27 | 28 | SERVER = Flask(__name__) 29 | """ 30 | *Flask* server hosting the *Dash* app. 31 | 32 | SERVER : Flask 33 | """ 34 | 35 | SERVER_URL = os.environ.get("AMPAS_APPS_SERVER") 36 | """ 37 | Server url used to construct permanent links for the individual apps. 38 | 39 | SERVER_URL : str 40 | """ 41 | 42 | APP = dash.Dash( 43 | __application_name__, 44 | external_scripts=os.environ.get("AMPAS_APPS_JS", "").split(","), 45 | external_stylesheets=[ 46 | dash_bootstrap_components.themes.BOOTSTRAP, 47 | *os.environ.get("AMPAS_APPS_CSS", "").split(","), 48 | ], 49 | server=SERVER, 50 | ) 51 | """ 52 | *Dash* app. 53 | 54 | APP : Dash 55 | """ 56 | 57 | APP.config["suppress_callback_exceptions"] = True 58 | -------------------------------------------------------------------------------- /tests/resources/synthetic_002/synthetic_002.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "schema_version": "0.1.0", 4 | "aces_transform_id": "URN:CSC.Synthetic.ColourScience_002_207202.a2.v1", 5 | "aces_user_name": "Synthetic colour-science Camera", 6 | "camera_make": "Synthetic", 7 | "camera_model": "colour-science", 8 | "iso": 100, 9 | "temperature": 6500, 10 | "additional_camera_settings": "", 11 | "lighting_setup_description": "D65", 12 | "debayering_platform": "", 13 | "debayering_settings": "", 14 | "encoding_colourspace": "synthetic_colourspace", 15 | "encoding_transfer_function": "synthetic_tf", 16 | "rgb_display_colourspace": "sRGB", 17 | "cat": "CAT02", 18 | "optimisation_space": "CIE Lab", 19 | "illuminant_interpolator": "Linear", 20 | "decoding_method": "Median", 21 | "ev_range": [-1.0, 0.0, 1.0], 22 | "grey_card_reference": [0.18, 0.18, 0.18], 23 | "lut_size": 1024, 24 | "lut_smoothing": 16 25 | }, 26 | "data": { 27 | "colour_checker": { 28 | "-3.25": ["data/colour_checker/-3.25/colour_checker_0001.tif"], 29 | "-2.125": ["data/colour_checker/-2.125/colour_checker_0001.tif"], 30 | "-0.75": ["data/colour_checker/-0.75/colour_checker_0001.tif"], 31 | "0.333": ["data/colour_checker/0.333/colour_checker_0001.tif"], 32 | "+1.75": ["data/colour_checker/1.75/colour_checker_0001.tif"], 33 | "+2.25": ["data/colour_checker/2.25/colour_checker_0001.tif"], 34 | "+3.4": ["data/colour_checker/3.4/colour_checker_0001.tif"] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/resources/synthetic_003/synthetic_003.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "schema_version": "0.1.0", 4 | "aces_transform_id": "URN:CSC.Synthetic.ColourScience_003_307202.a2.v1", 5 | "aces_user_name": "Synthetic colour-science Camera", 6 | "camera_make": "Synthetic", 7 | "camera_model": "colour-science", 8 | "iso": 100, 9 | "temperature": 6500, 10 | "additional_camera_settings": "", 11 | "lighting_setup_description": "D65", 12 | "debayering_platform": "", 13 | "debayering_settings": "", 14 | "encoding_colourspace": "synthetic_colourspace", 15 | "encoding_transfer_function": "synthetic_tf", 16 | "rgb_display_colourspace": "sRGB", 17 | "cat": "CAT02", 18 | "optimisation_space": "CIE Lab", 19 | "illuminant_interpolator": "Linear", 20 | "decoding_method": "Median", 21 | "ev_range": [-1.0, 0.0, 1.0], 22 | "grey_card_reference": [0.18, 0.18, 0.18], 23 | "lut_size": 1024, 24 | "lut_smoothing": 16 25 | }, 26 | "data": { 27 | "colour_checker": { 28 | "-3": ["data/colour_checker/-3/colour_checker_0001.tif"], 29 | "-2": ["data/colour_checker/-2/colour_checker_0001.tif"], 30 | "-1": ["data/colour_checker/-1/colour_checker_0001.tif"], 31 | "0": ["data/colour_checker/0/colour_checker_0001.tif"], 32 | "+1": ["data/colour_checker/1/colour_checker_0001.tif"], 33 | "+2": ["data/colour_checker/2/colour_checker_0001.tif"], 34 | "+3": ["data/colour_checker/3/colour_checker_0001.tif"] 35 | }, 36 | "grey_card": ["data/grey_card/grey_card_0001.tif"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: "v5.0.0" 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-case-conflict 7 | - id: check-merge-conflict 8 | - id: check-symlinks 9 | - id: check-yaml 10 | exclude: config-aces-reference.ocio.yaml 11 | - id: debug-statements 12 | - id: end-of-file-fixer 13 | - id: mixed-line-ending 14 | - id: name-tests-test 15 | args: ["--pytest-test-first"] 16 | - id: requirements-txt-fixer 17 | - id: trailing-whitespace 18 | - repo: https://github.com/codespell-project/codespell 19 | rev: v2.3.0 20 | hooks: 21 | - id: codespell 22 | args: ["--ignore-words-list=rIn"] 23 | - repo: https://github.com/PyCQA/isort 24 | rev: "5.13.2" 25 | hooks: 26 | - id: isort 27 | - repo: https://github.com/astral-sh/ruff-pre-commit 28 | rev: "v0.8.2" 29 | hooks: 30 | - id: ruff-format 31 | - id: ruff 32 | args: [--fix] 33 | - repo: https://github.com/adamchainz/blacken-docs 34 | rev: 1.19.1 35 | hooks: 36 | - id: blacken-docs 37 | language_version: python3.10 38 | - repo: https://github.com/pre-commit/mirrors-prettier 39 | rev: "v4.0.0-alpha.8" 40 | hooks: 41 | - id: prettier 42 | exclude: config-aces-reference.ocio.yaml 43 | - repo: https://github.com/pre-commit/pygrep-hooks 44 | rev: "v1.10.0" 45 | hooks: 46 | - id: rst-backticks 47 | - id: rst-directive-colons 48 | - id: rst-inline-touching-normal 49 | -------------------------------------------------------------------------------- /aces/idt/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import ( 2 | OPTIMISATION_FACTORIES, 3 | RGB_COLORCHECKER_CLASSIC_ACES, 4 | SAMPLES_COUNT_DEFAULT, 5 | SD_ILLUMINANT_ACES, 6 | SDS_COLORCHECKER_CLASSIC, 7 | SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC, 8 | clf_processing_elements, 9 | error_delta_E, 10 | extract_archive, 11 | find_clipped_exposures, 12 | find_similar_rows, 13 | format_exposure_key, 14 | generate_reference_colour_checker, 15 | get_sds_colour_checker, 16 | get_sds_illuminant, 17 | hash_file, 18 | list_sub_directories, 19 | mask_outliers, 20 | optimisation_factory_IPT, 21 | optimisation_factory_Oklab, 22 | png_compare_colour_checkers, 23 | slugify, 24 | sort_exposure_keys, 25 | working_directory, 26 | ) 27 | from .constants import ( 28 | CAT, 29 | EXPOSURE_CLIPPING_THRESHOLD, 30 | DecodingMethods, 31 | DirectoryStructure, 32 | Interpolators, 33 | LUTSize, 34 | OptimizationSpace, 35 | ProjectSettingsMetadataConstants, 36 | RGBDisplayColourspace, 37 | UICategories, 38 | UITypes, 39 | ) 40 | from .structures import ( 41 | Metadata, 42 | MetadataProperty, 43 | MixinSerializableProperties, 44 | PathEncoder, 45 | SerializableConstants, 46 | metadata_property, 47 | ) 48 | 49 | __all__ = [ 50 | "OPTIMISATION_FACTORIES", 51 | "RGB_COLORCHECKER_CLASSIC_ACES", 52 | "SAMPLES_COUNT_DEFAULT", 53 | "SD_ILLUMINANT_ACES", 54 | "SDS_COLORCHECKER_CLASSIC", 55 | "SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC", 56 | "clf_processing_elements", 57 | "error_delta_E", 58 | "extract_archive", 59 | "format_exposure_key", 60 | "generate_reference_colour_checker", 61 | "get_sds_colour_checker", 62 | "get_sds_illuminant", 63 | "hash_file", 64 | "list_sub_directories", 65 | "mask_outliers", 66 | "optimisation_factory_IPT", 67 | "optimisation_factory_Oklab", 68 | "png_compare_colour_checkers", 69 | "slugify", 70 | "sort_exposure_keys", 71 | "working_directory", 72 | "find_similar_rows", 73 | "find_clipped_exposures", 74 | ] 75 | 76 | __all__ += [ 77 | "EXPOSURE_CLIPPING_THRESHOLD", 78 | "CAT", 79 | "DirectoryStructure", 80 | "DecodingMethods", 81 | "Interpolators", 82 | "LUTSize", 83 | "OptimizationSpace", 84 | "ProjectSettingsMetadataConstants", 85 | "RGBDisplayColourspace", 86 | "UICategories", 87 | "UITypes", 88 | ] 89 | 90 | __all__ += [ 91 | "Metadata", 92 | "MetadataProperty", 93 | "MixinSerializableProperties", 94 | "PathEncoder", 95 | "SerializableConstants", 96 | "metadata_property", 97 | ] 98 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License Terms for Academy Color Encoding System Components 2 | 3 | Academy Color Encoding System (ACES) software and tools are provided by the Academy under the following terms and conditions: A worldwide, royalty-free, non-exclusive right to copy, modify, create derivatives, and use, in source and binary forms, is hereby granted, subject to acceptance of this license. 4 | 5 | Copyright 2019 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.). Portions contributed by others as indicated. All rights reserved. 6 | 7 | Performance of any of the aforementioned acts indicates acceptance to be bound by the following terms and conditions: 8 | 9 | > Copies of source code, in whole or in part, must retain the above copyright notice, this list of conditions and the Disclaimer of Warranty. 10 | > 11 | > Use in binary form must retain the above copyright notice, this list of conditions and the Disclaimer of Warranty in the documentation and/or other materials provided with the distribution. 12 | > 13 | > Nothing in this license shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of A.M.P.A.S. or any contributors, except as expressly stated herein. 14 | > 15 | > Neither the name "A.M.P.A.S." nor the name of any other contributors to this software may be used to endorse or promote products derivative of or based on this software without express prior written permission of A.M.P.A.S. or the contributors, as appropriate. 16 | 17 | This license shall be construed pursuant to the laws of the State of California, and any disputes related thereto shall be subject to the jurisdiction of the courts therein. 18 | 19 | Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | 21 | WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR UNDISCLOSED. 22 | -------------------------------------------------------------------------------- /tests/resources/samples_camera.json: -------------------------------------------------------------------------------- 1 | [ 2 | [0.11395355314016342, 0.11399932950735092, 0.11435028165578842], 3 | [0.13511893153190613, 0.13519522547721863, 0.13589711487293243], 4 | [0.15139985084533691, 0.15228484570980072, 0.15271209180355072], 5 | [0.1743488907814026, 0.1744709610939026, 0.1755695790052414], 6 | [0.19754229485988617, 0.19870196282863617, 0.19926652312278748], 7 | [0.2040577530860901, 0.2042103409767151, 0.2039509415626526], 8 | [0.22654908895492554, 0.22670167684555054, 0.22805969417095184], 9 | [0.2544451951980591, 0.25578805804252625, 0.25645944476127625], 10 | [0.25748175382614136, 0.25856509804725647, 0.25832095742225647], 11 | [0.2620593011379242, 0.2622424066066742, 0.2619524896144867], 12 | [0.28784626722335815, 0.28802937269210815, 0.28955525159835815], 13 | [0.30214357376098633, 0.30269289016723633, 0.30197572708129883], 14 | [0.31888240575790405, 0.32036247849464417, 0.32109490036964417], 15 | [0.32222405076026917, 0.3233989477157593, 0.3231395483016968], 16 | [0.3272288739681244, 0.3274119794368744, 0.3271068036556244], 17 | [0.34091585874557495, 0.34119051694869995, 0.33760473132133484], 18 | [0.3550606369972229, 0.3552437424659729, 0.356876403093338], 19 | [0.3702734708786011, 0.3708533048629761, 0.3701056241989136], 20 | [0.3879435658454895, 0.3894999325275421, 0.3902781307697296], 21 | [0.3914530277252197, 0.3926889896392822, 0.3924143314361572], 22 | [0.39668676257133484, 0.39688512682914734, 0.39656469225883484], 23 | [0.4109841585159302, 0.4112740755081177, 0.4075356721878052], 24 | [0.42567822337150574, 0.42587658762931824, 0.42755505442619324], 25 | [0.44139474630355835, 0.44200509786605835, 0.44122692942619324], 26 | [0.4595836102962494, 0.4611705541610718, 0.4619640111923218], 27 | [0.4631688892841339, 0.46443599462509155, 0.46416133642196655], 28 | [0.4685399830341339, 0.4687541723251343, 0.4684034585952759], 29 | [0.48315778374671936, 0.48344892263412476, 0.47964832186698914], 30 | [0.5141263604164124, 0.5147498846054077, 0.5139564275741577], 31 | [0.5325718522071838, 0.5341892838478088, 0.5350005626678467], 32 | [0.5362212657928467, 0.5375030040740967, 0.5372104048728943], 33 | [0.5416533946990967, 0.5418670177459717, 0.5415313243865967], 34 | [0.5564362406730652, 0.5567413568496704, 0.5528839230537415], 35 | [0.5876858234405518, 0.5883151292800903, 0.5875216722488403], 36 | [0.6099328398704529, 0.6112339496612549, 0.6109399199485779], 37 | [0.6154148578643799, 0.6156089901924133, 0.6152732968330383], 38 | [0.6302772760391235, 0.6305824518203735, 0.626706600189209], 39 | [0.6616800427436829, 0.6622903943061829, 0.6614969372749329], 40 | [0.6839984059333801, 0.6853007674217224, 0.6850054860115051], 41 | [0.704404890537262, 0.704710066318512, 0.7008135318756104], 42 | [0.7358686327934265, 0.7364881634712219, 0.7356947064399719], 43 | [0.7786641716957092, 0.7789693474769592, 0.7750848531723022] 44 | ] 45 | -------------------------------------------------------------------------------- /tests/resources/samples_reference.json: -------------------------------------------------------------------------------- 1 | [ 2 | [0.004681354292352056, 0.004707711883419395, 0.004869287638457769], 3 | [0.009362708584704112, 0.00941542376683879, 0.009738575276915538], 4 | [0.011834001784287838, 0.011900426739566225, 0.012045619808943012], 5 | [0.018725417169408225, 0.01883084753367758, 0.019477150553831077], 6 | [0.023668003568575676, 0.02380085347913245, 0.024091239617886024], 7 | [0.025315286747004714, 0.025303940415945837, 0.025357141282908682], 8 | [0.03745083433881645, 0.03766169506735516, 0.038954301107662154], 9 | [0.04733600713715135, 0.0476017069582649, 0.04818247923577205], 10 | [0.044181965243637135, 0.04417079370539635, 0.0442352625071515], 11 | [0.05063057349400943, 0.050607880831891675, 0.050714282565817365], 12 | [0.0749016686776329, 0.07532339013471032, 0.07790860221532431], 13 | [0.0716937369523808, 0.07156985933022324, 0.07145486681949595], 14 | [0.0946720142743027, 0.0952034139165298, 0.0963649584715441], 15 | [0.08836393048727427, 0.0883415874107927, 0.088470525014303], 16 | [0.10126114698801886, 0.10121576166378335, 0.10142856513163473], 17 | [0.10831335388455121, 0.10848966830035922, 0.107261735165307], 18 | [0.1498033373552658, 0.15064678026942063, 0.15581720443064861], 19 | [0.1433874739047616, 0.14313971866044647, 0.1429097336389919], 20 | [0.1893440285486054, 0.1904068278330596, 0.1927299169430882], 21 | [0.17672786097454854, 0.1766831748215854, 0.176941050028606], 22 | [0.20252229397603771, 0.2024315233275667, 0.20285713026326946], 23 | [0.21662670776910242, 0.21697933660071844, 0.214523470330614], 24 | [0.2996066747105316, 0.30129356053884127, 0.31163440886129723], 25 | [0.2867749478095232, 0.28627943732089295, 0.2858194672779838], 26 | [0.3786880570972108, 0.3808136556661192, 0.3854598338861764], 27 | [0.3534557219490971, 0.3533663496431708, 0.353882100057212], 28 | [0.40504458795207543, 0.4048630466551334, 0.4057142605265389], 29 | [0.43325341553820484, 0.4339586732014369, 0.429046940661228], 30 | [0.5735498956190463, 0.5725588746417859, 0.5716389345559676], 31 | [0.7573761141944216, 0.7616273113322384, 0.7709196677723528], 32 | [0.7069114438981942, 0.7067326992863416, 0.707764200114424], 33 | [0.8100891759041509, 0.8097260933102668, 0.8114285210530778], 34 | [0.8665068310764097, 0.8679173464028738, 0.858093881322456], 35 | [1.1470997912380927, 1.1451177492835718, 1.1432778691119352], 36 | [1.4138228877963883, 1.4134653985726833, 1.415528400228848], 37 | [1.6201783518083017, 1.6194521866205336, 1.6228570421061557], 38 | [1.7330136621528194, 1.7358346928057475, 1.716187762644912], 39 | [2.2941995824761854, 2.2902354985671436, 2.2865557382238704], 40 | [2.8276457755927766, 2.8269307971453665, 2.831056800457696], 41 | [3.4660273243056388, 3.471669385611495, 3.432375525289824], 42 | [4.588399164952371, 4.580470997134287, 4.573111476447741], 43 | [6.9320546486112775, 6.94333877122299, 6.864751050579648] 44 | ] 45 | -------------------------------------------------------------------------------- /tests/resources/synthetic_001/synthetic_001.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "schema_version": "0.1.0", 4 | "aces_transform_id": "URN:CSC.Synthetic.ColourScience_001_207204.a2.v1", 5 | "aces_user_name": "Synthetic", 6 | "camera_make": "Synthetic", 7 | "camera_model": "colour-science", 8 | "iso": 100, 9 | "temperature": 6500, 10 | "additional_camera_settings": "", 11 | "lighting_setup_description": "D65", 12 | "debayering_platform": "", 13 | "debayering_settings": "", 14 | "encoding_colourspace": "synthetic_colourspace", 15 | "encoding_transfer_function": "synthetic_tf", 16 | "rgb_display_colourspace": "sRGB", 17 | "cat": "CAT02", 18 | "optimisation_space": "CIE Lab", 19 | "illuminant_interpolator": "Linear", 20 | "decoding_method": "Median", 21 | "ev_range": [-1.0, 0.0, 1.0], 22 | "grey_card_reference": [0.18, 0.18, 0.18], 23 | "lut_size": 1024, 24 | "lut_smoothing": 16, 25 | "working_directory": "", 26 | "cleanup": false, 27 | "reference_colour_checker": "ISO 17321-1", 28 | "illuminant": "D60", 29 | "file_type": "", 30 | "ev_weights": [], 31 | "optimization_kwargs": {} 32 | }, 33 | "data": { 34 | "colour_checker": { 35 | "-3": [ 36 | "data/colour_checker/-3/colour_checker_0001.tif", 37 | "data/colour_checker/-3/colour_checker_0002.tif", 38 | "data/colour_checker/-3/colour_checker_0003.tif" 39 | ], 40 | "-2": [ 41 | "data/colour_checker/-2/colour_checker_0001.tif", 42 | "data/colour_checker/-2/colour_checker_0002.tif", 43 | "data/colour_checker/-2/colour_checker_0003.tif" 44 | ], 45 | "-1": [ 46 | "data/colour_checker/-1/colour_checker_0001.tif", 47 | "data/colour_checker/-1/colour_checker_0002.tif", 48 | "data/colour_checker/-1/colour_checker_0003.tif" 49 | ], 50 | "0": [ 51 | "data/colour_checker/0/colour_checker_0001.tif", 52 | "data/colour_checker/0/colour_checker_0002.tif", 53 | "data/colour_checker/0/colour_checker_0003.tif" 54 | ], 55 | "+1": [ 56 | "data/colour_checker/1/colour_checker_0001.tif", 57 | "data/colour_checker/1/colour_checker_0002.tif", 58 | "data/colour_checker/1/colour_checker_0003.tif" 59 | ], 60 | "+2": [ 61 | "data/colour_checker/2/colour_checker_0001.tif", 62 | "data/colour_checker/2/colour_checker_0002.tif", 63 | "data/colour_checker/2/colour_checker_0003.tif" 64 | ], 65 | "+3": [ 66 | "data/colour_checker/3/colour_checker_0001.tif", 67 | "data/colour_checker/3/colour_checker_0002.tif", 68 | "data/colour_checker/3/colour_checker_0003.tif" 69 | ] 70 | }, 71 | "grey_card": [ 72 | "data/grey_card/grey_card_0001.tif", 73 | "data/grey_card/grey_card_0002.tif", 74 | "data/grey_card/grey_card_0003.tif" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/resources/example_from_folder.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "schema_version": "0.1.0", 4 | "aces_transform_id": "", 5 | "aces_user_name": "", 6 | "camera_make": "", 7 | "camera_model": "", 8 | "include_white_balance_in_clf": false, 9 | "include_exposure_factor_in_clf": false, 10 | "flatten_clf": false, 11 | "iso": 800, 12 | "temperature": 6000, 13 | "additional_camera_settings": "", 14 | "lighting_setup_description": "", 15 | "debayering_platform": "", 16 | "debayering_settings": "", 17 | "encoding_colourspace": "", 18 | "encoding_transfer_function": "", 19 | "rgb_display_colourspace": "sRGB", 20 | "cat": "CAT02", 21 | "optimisation_space": "Oklab", 22 | "illuminant_interpolator": "Linear", 23 | "decoding_method": "Median", 24 | "ev_range": [-1.0, 0.0, 1.0], 25 | "grey_card_reference": [0.18, 0.18, 0.18], 26 | "lut_size": 1024, 27 | "lut_smoothing": 16, 28 | "working_directory": "", 29 | "cleanup": true, 30 | "reference_colour_checker": "ISO 17321-1", 31 | "illuminant": "D60", 32 | "file_type": "", 33 | "ev_weights": [], 34 | "optimization_kwargs": {} 35 | }, 36 | "data": { 37 | "colour_checker": { 38 | "-3": [ 39 | "data/colour_checker/-3/colour_checker_0001.tif", 40 | "data/colour_checker/-3/colour_checker_0002.tif", 41 | "data/colour_checker/-3/colour_checker_0003.tif" 42 | ], 43 | "-2": [ 44 | "data/colour_checker/-2/colour_checker_0001.tif", 45 | "data/colour_checker/-2/colour_checker_0002.tif", 46 | "data/colour_checker/-2/colour_checker_0003.tif" 47 | ], 48 | "-1": [ 49 | "data/colour_checker/-1/colour_checker_0001.tif", 50 | "data/colour_checker/-1/colour_checker_0002.tif", 51 | "data/colour_checker/-1/colour_checker_0003.tif" 52 | ], 53 | "0": [ 54 | "data/colour_checker/0/colour_checker_0001.tif", 55 | "data/colour_checker/0/colour_checker_0002.tif", 56 | "data/colour_checker/0/colour_checker_0003.tif" 57 | ], 58 | "+1": [ 59 | "data/colour_checker/1/colour_checker_0001.tif", 60 | "data/colour_checker/1/colour_checker_0002.tif", 61 | "data/colour_checker/1/colour_checker_0003.tif" 62 | ], 63 | "+2": [ 64 | "data/colour_checker/2/colour_checker_0001.tif", 65 | "data/colour_checker/2/colour_checker_0002.tif", 66 | "data/colour_checker/2/colour_checker_0003.tif" 67 | ], 68 | "+3": [ 69 | "data/colour_checker/3/colour_checker_0001.tif", 70 | "data/colour_checker/3/colour_checker_0002.tif", 71 | "data/colour_checker/3/colour_checker_0003.tif" 72 | ] 73 | }, 74 | "grey_card": [ 75 | "data/grey_card/grey_card_0001.tif", 76 | "data/grey_card/grey_card_0002.tif", 77 | "data/grey_card/grey_card_0003.tif" 78 | ], 79 | "white": [], 80 | "black": [] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/resources/synthetic_001/test_project.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "schema_version": "0.1.0", 4 | "aces_transform_id": "", 5 | "aces_user_name": "", 6 | "camera_make": "", 7 | "camera_model": "", 8 | "iso": 800, 9 | "temperature": 6000, 10 | "additional_camera_settings": "", 11 | "lighting_setup_description": "", 12 | "debayering_platform": "", 13 | "debayering_settings": "", 14 | "encoding_colourspace": "", 15 | "encoding_transfer_function": "", 16 | "rgb_display_colourspace": "sRGB", 17 | "cat": "CAT02", 18 | "optimisation_space": "Oklab", 19 | "illuminant_interpolator": "Linear", 20 | "decoding_method": "Median", 21 | "ev_range": [-1.0, 0.0, 1.0], 22 | "grey_card_reference": [0.18, 0.18, 0.18], 23 | "lut_size": 1024, 24 | "lut_smoothing": 16, 25 | "working_directory": "", 26 | "cleanup": true, 27 | "reference_colour_checker": "ISO 17321-1", 28 | "illuminant": "D60", 29 | "file_type": "", 30 | "ev_weights": [], 31 | "optimization_kwargs": {}, 32 | "include_white_balance_in_clf": false, 33 | "flatten_clf": false, 34 | "include_exposure_factor_in_clf": false 35 | }, 36 | "data": { 37 | "colour_checker": { 38 | "-3": [ 39 | "data/colour_checker/-3/colour_checker_0001.tif", 40 | "data/colour_checker/-3/colour_checker_0002.tif", 41 | "data/colour_checker/-3/colour_checker_0003.tif" 42 | ], 43 | "-2": [ 44 | "data/colour_checker/-2/colour_checker_0001.tif", 45 | "data/colour_checker/-2/colour_checker_0002.tif", 46 | "data/colour_checker/-2/colour_checker_0003.tif" 47 | ], 48 | "-1": [ 49 | "data/colour_checker/-1/colour_checker_0001.tif", 50 | "data/colour_checker/-1/colour_checker_0002.tif", 51 | "data/colour_checker/-1/colour_checker_0003.tif" 52 | ], 53 | "0": [ 54 | "data/colour_checker/0/colour_checker_0001.tif", 55 | "data/colour_checker/0/colour_checker_0002.tif", 56 | "data/colour_checker/0/colour_checker_0003.tif" 57 | ], 58 | "+1": [ 59 | "data/colour_checker/1/colour_checker_0001.tif", 60 | "data/colour_checker/1/colour_checker_0002.tif", 61 | "data/colour_checker/1/colour_checker_0003.tif" 62 | ], 63 | "+2": [ 64 | "data/colour_checker/2/colour_checker_0001.tif", 65 | "data/colour_checker/2/colour_checker_0002.tif", 66 | "data/colour_checker/2/colour_checker_0003.tif" 67 | ], 68 | "+3": [ 69 | "data/colour_checker/3/colour_checker_0001.tif", 70 | "data/colour_checker/3/colour_checker_0002.tif", 71 | "data/colour_checker/3/colour_checker_0003.tif" 72 | ] 73 | }, 74 | "grey_card": [ 75 | "data/grey_card/grey_card_0001.tif", 76 | "data/grey_card/grey_card_0002.tif", 77 | "data/grey_card/grey_card_0003.tif" 78 | ], 79 | "white": [], 80 | "black": [] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /aces/idt/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import ( 2 | CAT, 3 | OPTIMISATION_FACTORIES, 4 | RGB_COLORCHECKER_CLASSIC_ACES, 5 | SAMPLES_COUNT_DEFAULT, 6 | SD_ILLUMINANT_ACES, 7 | SDS_COLORCHECKER_CLASSIC, 8 | SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC, 9 | DecodingMethods, 10 | DirectoryStructure, 11 | Interpolators, 12 | LUTSize, 13 | Metadata, 14 | MetadataProperty, 15 | MixinSerializableProperties, 16 | OptimizationSpace, 17 | PathEncoder, 18 | ProjectSettingsMetadataConstants, 19 | RGBDisplayColourspace, 20 | SerializableConstants, 21 | UICategories, 22 | UITypes, 23 | clf_processing_elements, 24 | error_delta_E, 25 | extract_archive, 26 | format_exposure_key, 27 | generate_reference_colour_checker, 28 | get_sds_colour_checker, 29 | get_sds_illuminant, 30 | hash_file, 31 | list_sub_directories, 32 | mask_outliers, 33 | metadata_property, 34 | optimisation_factory_IPT, 35 | optimisation_factory_Oklab, 36 | png_compare_colour_checkers, 37 | slugify, 38 | sort_exposure_keys, 39 | working_directory, 40 | ) 41 | from .framework import IDTProjectSettings 42 | from .generators import ( 43 | GENERATORS, 44 | IDTBaseGenerator, 45 | IDTGeneratorLogCamera, 46 | IDTGeneratorPreLinearizedCamera, 47 | IDTGeneratorToneMappedCamera, 48 | ) 49 | 50 | from .application import IDTGeneratorApplication # isort: skip 51 | 52 | __all__ = [ 53 | "CAT", 54 | "OPTIMISATION_FACTORIES", 55 | "RGB_COLORCHECKER_CLASSIC_ACES", 56 | "SAMPLES_COUNT_DEFAULT", 57 | "SD_ILLUMINANT_ACES", 58 | "SDS_COLORCHECKER_CLASSIC", 59 | "SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC", 60 | "DecodingMethods", 61 | "DirectoryStructure", 62 | "Interpolators", 63 | "LUTSize", 64 | "Metadata", 65 | "MetadataProperty", 66 | "MixinSerializableProperties", 67 | "OptimizationSpace", 68 | "PathEncoder", 69 | "ProjectSettingsMetadataConstants", 70 | "RGBDisplayColourspace", 71 | "SerializableConstants", 72 | "UICategories", 73 | "UITypes", 74 | "clf_processing_elements", 75 | "error_delta_E", 76 | "extract_archive", 77 | "format_exposure_key", 78 | "generate_reference_colour_checker", 79 | "get_sds_colour_checker", 80 | "get_sds_illuminant", 81 | "hash_file", 82 | "list_sub_directories", 83 | "mask_outliers", 84 | "metadata_property", 85 | "optimisation_factory_IPT", 86 | "optimisation_factory_Oklab", 87 | "png_compare_colour_checkers", 88 | "slugify", 89 | "sort_exposure_keys", 90 | "working_directory", 91 | ] 92 | __all__ += ["IDTProjectSettings"] 93 | __all__ += [ 94 | "GENERATORS", 95 | "IDTBaseGenerator", 96 | "IDTGeneratorLogCamera", 97 | "IDTGeneratorPreLinearizedCamera", 98 | "IDTGeneratorToneMappedCamera", 99 | ] 100 | __all__ += ["IDTGeneratorApplication"] 101 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ACES 2 | 3 | We're thrilled that you're interested in contributing to ACES. To maintain the legal integrity of the project's codebase, we require all contributors to sign a Contributor License Agreement (CLA). 4 | 5 | ## Signing the CLA 6 | 7 | - Before we can merge any of your contributions, you must sign our CLA. 8 | - The process is simple. When you submit a pull request for the first time, you will be prompted to sign the CLA online. 9 | - If you are contributing on behalf of your employer or if your contributions are owned by someone other than yourself (e.g., your employer), please make sure you have the right to submit the contributions under our project's CLA. 10 | 11 | By signing the CLA, you assure the project and its users that your contributions do not infringe upon the rights of any third parties and that the project can use your contributions without legal repercussions. 12 | 13 | If you have any questions about the CLA process, please feel free to contact a member of the ACES Team via ACESCentral.com. 14 | 15 | ## Requirement for Signed Commits 16 | 17 | As part of our commitment to security and the integrity of our codebase, we require all commits to be signed. This helps us ensure that contributions are actually made by the account they come from and not altered by a third party. 18 | 19 | ### Why Signed Commits? 20 | 21 | Signed commits provide an additional layer of security by guaranteeing that the commits are from a verified source. This is crucial for maintaining the trustworthiness of our codebase. 22 | 23 | ### How to Sign Commits 24 | 25 | To sign commits, you'll need to use a GPG (GNU Privacy Guard) or S/MIME (Secure/Multipurpose Internet Mail Extensions) key. If you haven't already set up a GPG key, you can follow [GitHub's guide on generating a new GPG key](https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key). 26 | 27 | Once you have a GPG key, you can add it to your GitHub account. For instructions on how to do this, see [GitHub's documentation on adding a new GPG key to your account](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account). 28 | 29 | When you have your GPG key added to your GitHub account, you can start signing your commits. If you're using the command line, you can sign commits with `git commit -S -m "Your commit message"`. 30 | 31 | ### Verifying a Signed Commit 32 | 33 | You can verify that your commits are signed by looking for the "Verified" label on GitHub's commit interface. 34 | 35 | ### What if You Cannot Sign Your Commits? 36 | 37 | We understand that in some scenarios, you might not be able to sign commits. If you find yourself in this situation, please reach out to the project maintainers for assistance. 38 | 39 | For more detailed instructions on signing commits, you can refer to [GitHub's documentation on signing commits](https://docs.github.com/en/github/authenticating-to-github/signing-commits). 40 | 41 | --- 42 | 43 | We appreciate your contributions to ACES, and we thank you for adhering to our signed commits policy. This policy helps us maintain the security and integrity of our codebase. 44 | 45 | If you have any questions about this process, please feel free to contact the project maintainers. 46 | -------------------------------------------------------------------------------- /aces/idt/generators/prelinearized_idt.py: -------------------------------------------------------------------------------- 1 | """ 2 | IDT Generator for Pre-Linearized Camera 3 | ======================================= 4 | 5 | Define the *IDT* generator class for a *Pre-Linearized Camera*. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | import logging 11 | import typing 12 | 13 | from colour import LUT3x1D 14 | 15 | if typing.TYPE_CHECKING: 16 | from aces.idt.framework import IDTProjectSettings 17 | 18 | from aces.idt.generators.log_camera import IDTGeneratorLogCamera 19 | 20 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 21 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 22 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 23 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 24 | __email__ = "acessupport@oscars.org" 25 | __status__ = "Production" 26 | 27 | __all__ = [ 28 | "IDTGeneratorPreLinearizedCamera", 29 | ] 30 | 31 | LOGGER = logging.getLogger(__name__) 32 | 33 | 34 | class IDTGeneratorPreLinearizedCamera(IDTGeneratorLogCamera): 35 | """ 36 | Define an *IDT* generator for a *Pre-Linearized Camera*. 37 | 38 | Parameters 39 | ---------- 40 | project_settings 41 | *IDT* generator settings. 42 | 43 | Attributes 44 | ---------- 45 | - :attr:`~aces.idt.IDTGeneratorPreLinearizedCamera.GENERATOR_NAME` 46 | 47 | Methods 48 | ------- 49 | - :meth:`~aces.idt.IDTGeneratorPreLinearizedCamera.generate_LUT` 50 | - :meth:`~aces.idt.IDTGeneratorPreLinearizedCamera.filter_LUT` 51 | """ 52 | 53 | GENERATOR_NAME = "IDTGeneratorPreLinearizedCamera" 54 | """*IDT* generator name.""" 55 | 56 | def __init__(self, project_settings: IDTProjectSettings) -> None: 57 | super().__init__(project_settings) 58 | 59 | def generate_LUT(self) -> LUT3x1D: 60 | """Do not generate a LUT; The pre linearized camera generator does not 61 | generate a LUT, it assumes pre linearized exr files are provided so we only 62 | want a linear gain to be applied during the decode phase 63 | 64 | We provide an identity LUT to ensure the function fulfills its requirements 65 | without affecting any calculations 66 | 67 | Returns 68 | ------- 69 | :class:`LUT3x1D` 70 | Unfiltered linearisation *LUT* for the camera samples. 71 | """ 72 | size = self.project_settings.lut_size 73 | LOGGER.info('Generating unfiltered "LUT3x1D" with "%s" size...', size) 74 | self._LUT_unfiltered = LUT3x1D(size=size, name="LUT - Unfiltered") 75 | return self._LUT_unfiltered 76 | 77 | def filter_LUT(self) -> LUT3x1D: 78 | """Do not filter a LUT; The pre linearized camera generator does not 79 | generate a LUT, it assumes pre linearized exr files are provided so we only 80 | want a linear gain to be applied during the decode phase 81 | 82 | As the unfiltered LUT is an identity LUT we can simply copy it 83 | 84 | Returns 85 | ------- 86 | :class:`LUT3x1D` 87 | Filtered linearisation *LUT* for the camera samples. 88 | """ 89 | LOGGER.info('No Filtering Simply Copying "LUT3x1D"') 90 | 91 | self._LUT_filtered = self._LUT_unfiltered.copy() 92 | self._LUT_filtered.name = "LUT - Filtered" 93 | return self._LUT_filtered 94 | -------------------------------------------------------------------------------- /tests/test_project_settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define the unit tests for the :class:`aces.idt.IDTProjectSettings`class. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import json 8 | import os 9 | 10 | from aces.idt.core import constants 11 | from aces.idt.framework.project_settings import IDTProjectSettings 12 | from tests.test_utils import TestIDTBase 13 | 14 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 15 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 16 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 17 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 18 | __email__ = "acessupport@oscars.org" 19 | __status__ = "Production" 20 | 21 | __all__ = [ 22 | "TestIDTProjectSettings", 23 | ] 24 | 25 | 26 | class TestIDTProjectSettings(TestIDTBase): 27 | """ 28 | Define the unit tests for the :class:`aces.idt.IDTProjectSettings` 29 | class. 30 | """ 31 | 32 | def setUp(self) -> None: 33 | """Initialise the common tests attributes.""" 34 | 35 | self.project_settings = IDTProjectSettings() 36 | 37 | def test_properties(self) -> None: 38 | """Test :class:`aces.idt.IDTProjectSettings` class properties.""" 39 | 40 | class_props = list(self.project_settings.properties) 41 | self.assertEqual( 42 | len(class_props), len(constants.ProjectSettingsMetadataConstants.ALL) 43 | ) 44 | 45 | def test_to_file(self) -> None: 46 | """Test :class:`aces.idt.IDTProjectSettings.to_file` method.""" 47 | 48 | actual_file = os.path.join( 49 | self.get_test_output_folder(), "project_settings.json" 50 | ) 51 | expected_file = os.path.join( 52 | self.get_test_resources_folder(), "project_settings.json" 53 | ) 54 | 55 | self.project_settings.to_file(actual_file) 56 | self.assertEqual(os.path.exists(expected_file), True) 57 | 58 | def test_from_json(self) -> None: 59 | """Test :class:`aces.idt.IDTProjectSettings.from_json` method.""" 60 | 61 | self.project_settings.camera_make = "Cannon" 62 | json_string = self.project_settings.to_json() 63 | new_settings = IDTProjectSettings.from_json(json_string) 64 | json_string_loaded = new_settings.to_json() 65 | self.assertEqual(json_string, json_string_loaded) 66 | 67 | def test_from_directory(self) -> None: 68 | """Test :class:`aces.idt.IDTProjectSettings.from_directory` method.""" 69 | 70 | expected_file = os.path.join( 71 | self.get_test_resources_folder(), "example_from_folder.json" 72 | ) 73 | 74 | actual_file = os.path.join( 75 | self.get_test_resources_folder(), "synthetic_001", "test_project.json" 76 | ) 77 | 78 | folder_path = os.path.join(self.get_test_resources_folder(), "synthetic_001") 79 | new_settings = IDTProjectSettings.from_directory(folder_path) 80 | new_settings.to_file(actual_file) 81 | 82 | with open(actual_file) as actual_handle: 83 | actual = json.load(actual_handle) 84 | 85 | with open(expected_file) as expected_handle: 86 | expected = json.load(expected_handle) 87 | 88 | self.maxDiff = None 89 | 90 | self.assertEqual(actual, expected) 91 | 92 | def test_property_names_and_metadata_names_equality(self) -> None: 93 | """Test that the property names are equal to the metadata names.""" 94 | 95 | for name, prop in self.project_settings.properties: 96 | self.assertEqual(name, prop.metadata.name) 97 | -------------------------------------------------------------------------------- /aces/idt/core/transform_id.py: -------------------------------------------------------------------------------- 1 | """ 2 | Transform ID 3 | ============ 4 | 5 | Defines functions to generate and validate URNs for ACES transforms. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | import hashlib 11 | import re 12 | import uuid 13 | from enum import Enum 14 | 15 | ACES_URN_PREFIX: str = "URN:{aces_transform_type}" 16 | 17 | ACES_MAJOR_VERSION: int = 2 18 | 19 | 20 | def generate_truncated_hash(length: int = 6) -> str: 21 | """ 22 | Generate a truncated SHA-256 hash from a UUID. 23 | 24 | Parameters 25 | ---------- 26 | length 27 | The length of the truncated hash, default to 6. 28 | 29 | Returns 30 | ------- 31 | :class:`str` 32 | Truncated hexadecimal hash. 33 | """ 34 | 35 | unique_id = uuid.uuid4() 36 | 37 | hash_object = hashlib.sha256(str(unique_id).encode()) 38 | 39 | hash_hex = hash_object.hexdigest() 40 | 41 | return hash_hex[:length] 42 | 43 | 44 | class AcesTransformType(Enum): 45 | """ 46 | Enum for the type of ACES transform. 47 | 48 | Attributes 49 | ---------- 50 | CSC 51 | Color space conversion. 52 | OUTPUT 53 | Output transform. 54 | INV_OUTPUT 55 | Inverse output transform. 56 | LOOK 57 | Look transform. 58 | INV_LOOK 59 | Inverse look transform. 60 | LIB 61 | Library transform. 62 | UTIL 63 | Utility transform. 64 | """ 65 | 66 | CSC: str = "CSC" 67 | OUTPUT: str = "Output" 68 | INV_OUTPUT: str = "InvOutput" 69 | LOOK: str = "Look" 70 | INV_LOOK: str = "InvLook" 71 | LIB: str = "Lib" 72 | UTIL: str = "Util" 73 | 74 | 75 | def generate_idt_urn( 76 | colourspace_vendor: str, 77 | encoding_colourspace: str, 78 | encoding_transfer_function: str, 79 | version_number: int, 80 | ) -> str: 81 | """ 82 | Generate a URN for an ACES IDT (Input Device Transform) using the provided 83 | parameters. 84 | 85 | The URN is formatted as: 86 | `URN:.._ 87 | _.a.v` 88 | 89 | Parameters 90 | ---------- 91 | colourspace_vendor 92 | The vendor for the colorspace, e.g., the device or software provider. 93 | encoding_colourspace 94 | The name of the encoding color space. 95 | encoding_transfer_function 96 | The transfer function used to encode the colorspace. 97 | version_number 98 | The version number of the transform. 99 | 100 | Returns 101 | ------- 102 | :class:`str` 103 | The generated URN in the format described. 104 | 105 | Notes 106 | ----- 107 | - Dots in the colorspace_vendor, encoding_colourspace, and 108 | encoding_transfer_function strings are replaced with underscores to 109 | ensure valid URN format, as dots are used as delimiters in the URN. 110 | """ 111 | 112 | # Sanitize input strings by replacing dots with underscores 113 | colorspace_vendor_clean = colourspace_vendor.replace(".", "_") 114 | encoding_colourspace_clean = encoding_colourspace.replace(".", "_") 115 | encoding_transfer_function_clean = encoding_transfer_function.replace(".", "_") 116 | 117 | urn_prefix = ACES_URN_PREFIX.format(aces_transform_type=AcesTransformType.CSC.value) 118 | hash_id = generate_truncated_hash() 119 | colorspace_name = ( 120 | f"{encoding_colourspace_clean}_{encoding_transfer_function_clean}_{hash_id}" 121 | ) 122 | return ( 123 | f"{urn_prefix}.{colorspace_vendor_clean}.{colorspace_name}.a{ACES_MAJOR_VERSION}" 124 | f".v{version_number}" 125 | ) 126 | 127 | 128 | def is_valid_csc_urn(urn: str) -> bool: 129 | """ 130 | Check if the given URN string is valid according to the expected format, 131 | specifically for the CSC (Color Space Conversion) transform type. 132 | 133 | Parameters 134 | ---------- 135 | urn 136 | The URN string to validate. 137 | 138 | Returns 139 | ------- 140 | :class:`bool` 141 | Whether the URN is valid and its transform type is CSC. 142 | """ 143 | 144 | urn_regex = ( 145 | rf"^URN:{AcesTransformType.CSC.value}\.[a-zA-Z0-9_-]+" 146 | rf"\.[a-zA-Z0-9_]+_[a-zA-Z0-9_]+_[a-f0-9]{{6}}\.a{ACES_MAJOR_VERSION}\.v\d+$" 147 | ) 148 | 149 | return bool(re.match(urn_regex, urn)) 150 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv export --no-hashes --all-extras 3 | anyio==4.9.0 4 | appnope==0.1.4 ; sys_platform == 'darwin' 5 | argon2-cffi==23.1.0 6 | argon2-cffi-bindings==21.2.0 7 | arrow==1.3.0 8 | asttokens==3.0.0 9 | async-lru==2.0.5 10 | attrs==25.3.0 11 | babel==2.17.0 12 | backports-tarfile==1.2.0 ; python_full_version < '3.12' 13 | beautifulsoup4==4.13.3 14 | bleach==6.2.0 15 | blinker==1.9.0 16 | cachetools==5.5.2 17 | certifi==2025.1.31 18 | cffi==1.17.1 19 | cfgv==3.4.0 20 | charset-normalizer==3.4.1 21 | click==8.1.8 22 | colorama==0.4.6 ; sys_platform == 'win32' 23 | colour-checker-detection==0.2.1 24 | colour-datasets==0.2.6 25 | colour-science==0.4.6 26 | comm==0.2.2 27 | contourpy==1.3.1 28 | coverage==7.7.1 29 | coveralls==4.0.1 30 | cryptography==44.0.2 ; sys_platform == 'linux' 31 | cycler==0.12.1 32 | dash==3.0.1 33 | dash-bootstrap-components==2.0.0 34 | dash-renderer==1.9.1 35 | dash-uploader==0.6.1 36 | debugpy==1.8.13 37 | decorator==5.2.1 38 | defusedxml==0.7.1 39 | distlib==0.3.9 40 | docopt==0.6.2 41 | docutils==0.21.2 42 | editables==0.5 43 | exceptiongroup==1.2.2 ; python_full_version < '3.11' 44 | execnet==2.1.1 45 | executing==2.2.0 46 | fastjsonschema==2.21.1 47 | filelock==3.18.0 48 | flask==3.0.3 49 | fonttools==4.56.0 50 | fqdn==1.5.1 51 | gunicorn==23.0.0 52 | h11==0.14.0 53 | hatch==1.9.7 54 | hatchling==1.21.1 55 | httpcore==1.0.7 56 | httpx==0.28.1 57 | hyperlink==21.0.0 58 | identify==2.6.9 59 | idna==3.10 60 | imageio==2.37.0 61 | importlib-metadata==8.6.1 62 | iniconfig==2.1.0 63 | invoke==2.2.0 64 | ipykernel==6.29.5 65 | ipython==8.34.0 ; python_full_version < '3.11' 66 | ipython==9.0.2 ; python_full_version >= '3.11' 67 | ipython-pygments-lexers==1.1.1 ; python_full_version >= '3.11' 68 | ipywidgets==8.1.5 69 | isoduration==20.11.0 70 | itsdangerous==2.2.0 71 | jaraco-classes==3.4.0 72 | jaraco-context==6.0.1 73 | jaraco-functools==4.1.0 74 | jedi==0.19.2 75 | jeepney==0.9.0 ; sys_platform == 'linux' 76 | jinja2==3.1.6 77 | json5==0.10.0 78 | jsonpickle==2.2.0 79 | jsonpointer==3.0.0 80 | jsonschema==4.23.0 81 | jsonschema-specifications==2024.10.1 82 | jupyter==1.1.1 83 | jupyter-client==8.6.3 84 | jupyter-console==6.6.3 85 | jupyter-core==5.7.2 86 | jupyter-events==0.12.0 87 | jupyter-lsp==2.2.5 88 | jupyter-server==2.13.0 89 | jupyter-server-terminals==0.5.3 90 | jupyterlab==4.3.6 91 | jupyterlab-pygments==0.3.0 92 | jupyterlab-server==2.27.3 93 | jupyterlab-widgets==3.0.13 94 | keyring==25.6.0 95 | kiwisolver==1.4.8 96 | markdown-it-py==3.0.0 97 | markupsafe==3.0.2 98 | matplotlib==3.10.1 99 | matplotlib-inline==0.1.7 100 | mdurl==0.1.2 101 | mistune==3.1.3 102 | more-itertools==10.6.0 103 | narwhals==1.32.0 104 | nbclient==0.10.2 105 | nbconvert==7.16.6 106 | nbformat==5.10.4 107 | nest-asyncio==1.6.0 108 | networkx==3.4.2 109 | nh3==0.2.21 110 | nodeenv==1.9.1 111 | notebook==7.3.3 112 | notebook-shim==0.2.4 113 | numpy==2.2.4 114 | opencv-python==4.11.0.86 115 | openimageio==3.0.4.0 116 | overrides==7.7.0 117 | packaging==21.3 118 | pandas==2.2.3 119 | pandocfilters==1.5.1 120 | parso==0.8.4 121 | pathspec==0.12.1 122 | pexpect==4.9.0 123 | pillow==11.1.0 124 | pkginfo==1.12.1.2 125 | platformdirs==4.3.7 126 | plotly==6.0.1 127 | pluggy==1.5.0 128 | pre-commit==4.2.0 129 | prometheus-client==0.21.1 130 | prompt-toolkit==3.0.50 131 | psutil==7.0.0 132 | ptyprocess==0.7.0 133 | pure-eval==0.2.3 134 | pycparser==2.22 135 | pygments==2.19.1 136 | pyparsing==3.2.3 137 | pyright==1.1.398 138 | pytest==8.3.5 139 | pytest-cov==6.0.0 140 | pytest-xdist==3.6.1 141 | python-dateutil==2.9.0.post0 142 | python-json-logger==3.3.0 143 | pytz==2025.2 144 | pywin32==310 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' 145 | pywin32-ctypes==0.2.3 ; sys_platform == 'win32' 146 | pywinpty==2.0.15 ; os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux' 147 | pyyaml==6.0.2 148 | pyzmq==26.3.0 149 | readme-renderer==44.0 150 | referencing==0.36.2 151 | requests==2.32.3 152 | requests-toolbelt==1.0.0 153 | retrying==1.3.4 154 | rfc3339-validator==0.1.4 155 | rfc3986==2.0.0 156 | rfc3986-validator==0.1.1 157 | rich==13.9.4 158 | rpds-py==0.24.0 159 | scipy==1.15.2 160 | secretstorage==3.3.3 ; sys_platform == 'linux' 161 | send2trash==1.8.3 162 | setuptools==78.1.0 163 | shellingham==1.5.4 164 | six==1.17.0 165 | sniffio==1.3.1 166 | soupsieve==2.6 167 | stack-data==0.6.3 168 | terminado==0.18.1 169 | tinycss2==1.4.0 170 | toml==0.10.2 171 | tomli==2.2.1 ; python_full_version <= '3.11' 172 | tomli-w==1.2.0 173 | tomlkit==0.13.2 174 | tornado==6.4.2 175 | tqdm==4.67.1 176 | traitlets==5.14.3 177 | trove-classifiers==2025.3.19.19 178 | twine==6.0.1 179 | types-python-dateutil==2.9.0.20241206 180 | typing-extensions==4.13.0 181 | tzdata==2025.2 182 | uri-template==1.3.0 183 | urllib3==2.3.0 184 | userpath==1.9.2 185 | virtualenv==20.25.3 186 | wcwidth==0.2.13 187 | webcolors==24.11.1 188 | webencodings==0.5.1 189 | websocket-client==1.8.0 190 | werkzeug==3.0.6 191 | widgetsnbextension==4.0.13 192 | xlrd==1.2.0 193 | xxhash==3.5.0 194 | zipp==3.21.0 195 | zstandard==0.23.0 196 | -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | """ 2 | Index 3 | ===== 4 | """ 5 | 6 | import logging 7 | 8 | from dash.dcc import Link, Location, Markdown 9 | from dash.dependencies import Input, Output 10 | from dash.html import H3, A, Div, Img, Main, P 11 | from dash_bootstrap_components import ( 12 | Col, 13 | Container, 14 | NavbarSimple, 15 | NavItem, 16 | NavLink, 17 | Row, 18 | ) 19 | 20 | import apps.idt_calculator_camera as app_2 21 | import apps.idt_calculator_p_2013_001 as app_1 22 | from app import APP, SERVER # noqa: F401 23 | 24 | __author__ = "Alex Forsythe, Gayle McAdams, Thomas Mansencal, Nick Shaw" 25 | __copyright__ = "Copyright 2020 Academy of Motion Picture Arts and Sciences" 26 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 27 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 28 | __email__ = "acessupport@oscars.org" 29 | __status__ = "Production" 30 | 31 | __all__ = ["load_app"] 32 | 33 | 34 | APP.layout = Container( 35 | [ 36 | Location(id="url", refresh=False), 37 | NavbarSimple( 38 | children=[ 39 | NavItem( 40 | NavLink( 41 | app_1.APP_NAME_SHORT, 42 | href=app_1.APP_PATH, 43 | ) 44 | ), 45 | NavItem( 46 | NavLink( 47 | app_2.APP_NAME_SHORT, 48 | href=app_2.APP_PATH, 49 | ) 50 | ), 51 | ], 52 | brand=Img( 53 | id="aces-logo", 54 | src="/assets/aces-logo.png", 55 | ), 56 | brand_href="https://acescentral.com", 57 | color="primary", 58 | dark=True, 59 | ), 60 | Div(id="toc"), 61 | ], 62 | id="apps", 63 | fluid=True, 64 | ) 65 | 66 | 67 | @APP.callback(Output("toc", "children"), [Input("url", "pathname")]) 68 | def load_app(app: str) -> Container: 69 | """ 70 | Load given app into the appropriate :class:`Div` class instance. 71 | 72 | Parameters 73 | ---------- 74 | app 75 | App path. 76 | 77 | Returns 78 | ------- 79 | :class:`Container` 80 | :class:`Container` class instance of the app layout. 81 | """ 82 | 83 | if app == app_1.APP_PATH: 84 | return app_1.LAYOUT 85 | 86 | if app == app_2.APP_PATH: 87 | return app_2.LAYOUT 88 | 89 | return Container( 90 | [ 91 | Main( 92 | [ 93 | Row( 94 | [ 95 | Col( 96 | [ 97 | P( 98 | [ 99 | "Various A.M.P.A.S. colour science ", 100 | A( 101 | "Dash", 102 | href="https://dash.plot.ly/", 103 | target="_blank", 104 | ), 105 | " apps.", 106 | ] 107 | ), 108 | P( 109 | [ 110 | H3( 111 | [ 112 | Link( 113 | app_1.APP_NAME_LONG, 114 | href=app_1.APP_PATH, 115 | ), 116 | ] 117 | ), 118 | Markdown( 119 | app_1.APP_DESCRIPTION.replace( 120 | "This app c", "C" 121 | ) 122 | ), 123 | ] 124 | ), 125 | P( 126 | [ 127 | H3( 128 | [ 129 | Link( 130 | app_2.APP_NAME_LONG, 131 | href=app_2.APP_PATH, 132 | ), 133 | ] 134 | ), 135 | Markdown( 136 | app_2.APP_DESCRIPTION.replace( 137 | "This app c", "C" 138 | ) 139 | ), 140 | ] 141 | ), 142 | ] 143 | ) 144 | ] 145 | ) 146 | ] 147 | ) 148 | ] 149 | ) 150 | 151 | 152 | if __name__ == "__main__": 153 | logging.basicConfig() 154 | logging.getLogger().setLevel(logging.INFO) 155 | 156 | APP.run(debug=True) 157 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """Module for generic unit testing helpers""" 2 | 3 | from __future__ import annotations 4 | 5 | import hashlib 6 | import json 7 | import os 8 | import unittest 9 | from typing import TypeVar 10 | 11 | import requests 12 | 13 | T = TypeVar("T", bound="TestIDTBase") 14 | 15 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 16 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 17 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 18 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 19 | __email__ = "acessupport@oscars.org" 20 | __status__ = "Production" 21 | 22 | __all__ = [ 23 | "TestIDTBase", 24 | ] 25 | 26 | 27 | class TestIDTBase(unittest.TestCase): 28 | """Define a base class for other unit testing classes.""" 29 | 30 | @classmethod 31 | def setUpClass(cls) -> None: 32 | """Download any missing test resources from the manifest.""" 33 | file_path = cls.get_download_manifest() 34 | with open(file_path) as f: 35 | manifest = json.load(f) 36 | 37 | for file_path, data in manifest.items(): 38 | url = data.get("URL") 39 | expected_sha256 = data.get("SHA256") 40 | filename = os.path.basename(file_path) 41 | local_folder = cls.get_test_resources_folder() 42 | expected_file = os.path.join(local_folder, filename) 43 | download = False 44 | if not os.path.exists(expected_file): 45 | download = True 46 | else: 47 | sha256 = cls.sha256_checksum(expected_file) 48 | if sha256 != expected_sha256: 49 | download = True 50 | if download: 51 | cls.download_file_from_dropbox(url, local_folder, file_path) 52 | 53 | @classmethod 54 | def get_unit_test_folder(cls: type[T]) -> T: 55 | """ 56 | Get the folder the unit test file is stored within. 57 | 58 | Returns 59 | ------- 60 | :class:`str` 61 | The folder path holding the file which is running. 62 | """ 63 | 64 | script_file_path = os.path.abspath(__file__) 65 | 66 | return os.path.dirname(script_file_path) 67 | 68 | @classmethod 69 | def get_test_output_folder(cls: type[T]) -> T: 70 | """ 71 | Get the unit test output folder. 72 | 73 | Returns 74 | ------- 75 | :class:`str` 76 | The unit test output folder. 77 | """ 78 | 79 | return os.path.join(cls.get_unit_test_folder(), "output") 80 | 81 | @classmethod 82 | def get_test_resources_folder(cls: type[T]) -> T: 83 | """Get the unit test resource folder. 84 | 85 | Returns 86 | ------- 87 | :class:`str` 88 | The unit test resource folder 89 | """ 90 | 91 | return os.path.join(cls.get_unit_test_folder(), "resources") 92 | 93 | @classmethod 94 | def get_download_manifest(cls) -> str: 95 | """Get the download manifest for this test.""" 96 | return os.path.join(cls.get_test_resources_folder(), "download_manifest.json") 97 | 98 | @classmethod 99 | def download_file_from_dropbox( 100 | cls, sharing_link: str, local_folder: str, local_filename: str | None = None 101 | ) -> None: 102 | """ 103 | Download a file from Dropbox using the sharing link. 104 | 105 | Parameters 106 | ---------- 107 | sharing_link 108 | the Dropbox sharing link to the file 109 | local_folder 110 | the local folder to save the file to 111 | local_filename 112 | the local filename to save the file as (optional) 113 | """ 114 | # Ensure the local folder exists 115 | os.makedirs(local_folder, exist_ok=True) 116 | 117 | # Convert the sharing link to a direct download link 118 | direct_link = sharing_link.replace("?dl=0", "?dl=1") 119 | 120 | # Ensure the agent is a linux wget to ensure we get the file not the html page 121 | headers = {"user-agent": "Wget/1.16 (linux-gnu)"} 122 | response = requests.get( 123 | direct_link, stream=True, headers=headers, timeout=(5, 15) 124 | ) 125 | if response.status_code == 200: 126 | # If no local filename is provided, extract one from the URL 127 | if local_filename is None: 128 | local_filename = os.path.basename(sharing_link.split("?")[0]) 129 | 130 | # Write the content to a file 131 | file_path = os.path.join(local_folder, local_filename) 132 | with open(file_path, "wb") as f: 133 | for chunk in response.iter_content(chunk_size=65536): 134 | if chunk: 135 | f.write(chunk) 136 | else: 137 | msg = f"Failed to download file. HTTP status code: {response.status_code}" 138 | raise OSError(msg) 139 | 140 | @classmethod 141 | def sha256_checksum(cls, file_path: str, chunk_size: int = 8192) -> str: 142 | """ 143 | Calculate the SHA256 checksum of a file. 144 | 145 | Parameters 146 | ---------- 147 | file_path 148 | the file path 149 | chunk_size 150 | the chunk size to read the file in bytes 151 | 152 | Returns 153 | ------- 154 | a sha256 checksum of the file 155 | 156 | """ 157 | sha256 = hashlib.sha256() 158 | with open(file_path, "rb") as f: 159 | for chunk in iter(lambda: f.read(chunk_size), b""): 160 | sha256.update(chunk) 161 | return sha256.hexdigest() 162 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Invoke - Tasks 3 | ============== 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | import contextlib 9 | import inspect 10 | import platform 11 | 12 | from colour.utilities import message_box 13 | 14 | import app 15 | 16 | if not hasattr(inspect, "getargspec"): 17 | inspect.getargspec = inspect.getfullargspec 18 | 19 | from invoke import Context, task 20 | from invoke.exceptions import Failure 21 | 22 | __author__ = "Alex Forsythe, Gayle McAdams, Thomas Mansencal, Nick Shaw" 23 | __copyright__ = "Copyright 2020 Academy of Motion Picture Arts and Sciences" 24 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 25 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 26 | __email__ = "acessupport@oscars.org" 27 | __status__ = "Production" 28 | 29 | __all__ = [ 30 | "APPLICATION_NAME", 31 | "ORG", 32 | "CONTAINER", 33 | "clean", 34 | "precommit", 35 | "tests", 36 | "requirements", 37 | "build", 38 | "docker_build", 39 | "docker_remove", 40 | "docker_run", 41 | "docker_push", 42 | ] 43 | 44 | APPLICATION_NAME = app.__application_name__ 45 | 46 | ORG = "ampas" 47 | 48 | CONTAINER = APPLICATION_NAME.replace(" ", "").lower() 49 | 50 | 51 | @task 52 | def clean( 53 | ctx: Context, 54 | docs: bool = True, 55 | bytecode: bool = False, 56 | mypy: bool = True, 57 | pytest: bool = True, 58 | ) -> None: 59 | """ 60 | Clean the project. 61 | 62 | Parameters 63 | ---------- 64 | ctx 65 | Context. 66 | docs 67 | Whether to clean the *docs* directory. 68 | bytecode 69 | Whether to clean the bytecode files, e.g. *.pyc* files. 70 | mypy 71 | Whether to clean the *Mypy* cache directory. 72 | pytest 73 | Whether to clean the *Pytest* cache directory. 74 | """ 75 | 76 | message_box("Cleaning project...") 77 | 78 | patterns = ["build", "*.egg-info", "dist"] 79 | 80 | if docs: 81 | patterns.append("docs/_build") 82 | patterns.append("docs/generated") 83 | 84 | if bytecode: 85 | patterns.append("**/__pycache__") 86 | patterns.append("**/*.pyc") 87 | 88 | if mypy: 89 | patterns.append(".mypy_cache") 90 | 91 | if pytest: 92 | patterns.append(".pytest_cache") 93 | 94 | for pattern in patterns: 95 | ctx.run(f"rm -rf {pattern}") 96 | 97 | 98 | @task 99 | def precommit(ctx: Context) -> None: 100 | """ 101 | Run the "pre-commit" hooks on the codebase. 102 | 103 | Parameters 104 | ---------- 105 | ctx 106 | Context. 107 | """ 108 | 109 | message_box('Running "pre-commit" hooks on the codebase...') 110 | ctx.run("pre-commit run --all-files") 111 | 112 | 113 | @task 114 | def tests(ctx: Context) -> None: 115 | """ 116 | Run the unit tests with *Pytest*. 117 | 118 | Parameters 119 | ---------- 120 | ctx 121 | Context. 122 | """ 123 | 124 | message_box('Running "Pytest"...') 125 | ctx.run("pytest --doctest-modules tests") 126 | 127 | 128 | @task 129 | def requirements(ctx: Context) -> None: 130 | """ 131 | Export the *requirements.txt* file. 132 | 133 | Parameters 134 | ---------- 135 | ctx 136 | Context. 137 | """ 138 | 139 | message_box('Exporting "requirements.txt" file...') 140 | ctx.run('uv export --no-hashes --all-extras | grep -v "-e \\." > requirements.txt') 141 | 142 | 143 | @task(clean, precommit, tests, requirements) 144 | def build(ctx: Context) -> None: 145 | """ 146 | Build the project and runs dependency tasks, i.e., *docs*, *todo*, and 147 | *preflight*. 148 | 149 | Parameters 150 | ---------- 151 | ctx 152 | Context. 153 | """ 154 | 155 | message_box("Building...") 156 | ctx.run("uv build") 157 | ctx.run("twine check dist/*") 158 | 159 | 160 | @task(precommit, tests, requirements) 161 | def docker_build(ctx: Context) -> None: 162 | """ 163 | Build the *docker* image. 164 | 165 | Parameters 166 | ---------- 167 | ctx 168 | Context. 169 | """ 170 | 171 | message_box('Building "docker" image...') 172 | 173 | for architecture in ("arm64", "amd64"): 174 | ctx.run( 175 | f"docker build --platform=linux/{architecture} " 176 | f"-t {ORG}/{CONTAINER}:latest " 177 | f"-t {ORG}/{CONTAINER}:latest-{architecture} " 178 | f"-t {ORG}/{CONTAINER}:v{app.__version__}-{architecture} ." 179 | ) 180 | 181 | 182 | @task 183 | def docker_remove(ctx: Context) -> None: 184 | """ 185 | Stop and remove the *docker* container. 186 | 187 | Parameters 188 | ---------- 189 | ctx 190 | Context. 191 | """ 192 | 193 | message_box('Stopping "docker" container...') 194 | with contextlib.suppress(Failure): 195 | ctx.run(f"docker stop {CONTAINER}") 196 | 197 | message_box('Removing "docker" container...') 198 | with contextlib.suppress(Failure): 199 | ctx.run(f"docker rm {CONTAINER}") 200 | 201 | 202 | @task(docker_remove, docker_build) 203 | def docker_run(ctx: Context) -> None: 204 | """ 205 | Run the *docker* container. 206 | 207 | Parameters 208 | ---------- 209 | ctx 210 | Context. 211 | """ 212 | 213 | message_box('Running "docker" container...') 214 | ctx.run( 215 | "docker run -d " 216 | f"--name={CONTAINER} " 217 | f"-p 8010:8000 {ORG}/{CONTAINER}:latest-{platform.uname()[4].lower()}" 218 | ) 219 | 220 | 221 | @task(clean, precommit, docker_run) 222 | def docker_push(ctx: Context) -> None: 223 | """ 224 | Push the *docker* container. 225 | 226 | Parameters 227 | ---------- 228 | ctx 229 | Context. 230 | """ 231 | 232 | message_box('Pushing "docker" container...') 233 | ctx.run(f"docker push --all-tags {ORG}/{CONTAINER}") 234 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "idt-calculator" 3 | version = "0.1.0" 4 | description = "An ACES Input Device Transform (IDT) calculator" 5 | readme = "README.md" 6 | requires-python = ">=3.10,< 3.14" 7 | authors = [ 8 | { name = "Alex Forsythe", email = "aforsythe@oscars.org" }, 9 | { name = "Thomas Mansencal", email = "thomas.mansencal@gmail.com" }, 10 | { name = "Adam Davis", email = "adamdavis@netflix.com" }, 11 | ] 12 | maintainers = [ 13 | { name = "Alex Forsythe", email = "aforsythe@oscars.org" }, 14 | { name = "Thomas Mansencal", email = "thomas.mansencal@gmail.com" }, 15 | { name = "Adam Davis", email = "adamdavis@netflix.com" }, 16 | ] 17 | license = { text = "License Terms for Academy Color Encoding System Components" } 18 | keywords = [ 19 | "aces", 20 | "ampas", 21 | "color", 22 | "color-science", 23 | "color-space", 24 | "color-spaces", 25 | "colorspace", 26 | "colorspaces", 27 | "colour", 28 | "colour-science", 29 | "colour-space", 30 | "colour-spaces", 31 | "colourspace", 32 | "colourspaces", 33 | "dash", 34 | "docker", 35 | "python" 36 | ] 37 | classifiers = [ 38 | "Development Status :: 3 - Alpha", 39 | "Environment :: Console", 40 | "Intended Audience :: Developers", 41 | "Intended Audience :: Science/Research", 42 | "License :: OSI Approved", 43 | "Natural Language :: English", 44 | "Operating System :: OS Independent", 45 | "Programming Language :: Python :: 3", 46 | "Topic :: Scientific/Engineering", 47 | "Topic :: Software Development" 48 | ] 49 | 50 | dependencies = [ 51 | "colour-datasets>=0.2.5", 52 | "colour-checker-detection>=0.2.1", 53 | "colour_science>=0.4.5", 54 | "dash", 55 | "dash-bootstrap-components", 56 | "dash-renderer", 57 | "dash-uploader", 58 | "gunicorn", 59 | "imageio>=2,<3", 60 | "pandas>=2,<3", 61 | "jsonpickle>=2,<3", 62 | "matplotlib>=3.7", 63 | "networkx>=3,<4", 64 | "numpy>=1.24,<3", 65 | "OpenImageIO==3.0.4.0", 66 | "packaging<= 21.3", # Later versions currently break with Dash Uploader. 67 | "scipy>=1.10,<2", 68 | "plotly", 69 | "typing-extensions>=4,<5", 70 | "xxhash>=3,<4", 71 | ] 72 | 73 | [project.urls] 74 | Homepage = "https://www.oscars.org/science-technology/sci-tech-projects/aces" 75 | Repository = "https://github.com/ampas/idt-calculator" 76 | Issues = "https://github.com/ampas/idt-calculator/issues" 77 | Changelog = "https://github.com/ampas/idt-calculator/releases" 78 | 79 | [tool.uv] 80 | package = true 81 | dev-dependencies = [ 82 | "coverage", 83 | "coveralls", 84 | "hatch", 85 | "invoke", 86 | "jupyter", 87 | "pre-commit", 88 | "pyright", 89 | "pytest", 90 | "pytest-cov", 91 | "pytest-xdist", 92 | "requests", 93 | "toml", 94 | "twine", 95 | ] 96 | 97 | [build-system] 98 | requires = ["hatchling"] 99 | build-backend = "hatchling.build" 100 | 101 | [tool.hatch.build.targets.wheel] 102 | packages = [ "aces" ] 103 | 104 | [tool.codespell] 105 | ignore-words-list = "rIn" 106 | 107 | [tool.isort] 108 | ensure_newline_before_comments = true 109 | force_grid_wrap = 0 110 | include_trailing_comma = true 111 | line_length = 88 112 | multi_line_output = 3 113 | split_on_trailing_comma = true 114 | use_parentheses = true 115 | 116 | [tool.pyright] 117 | reportMissingImports = false 118 | reportMissingModuleSource = false 119 | reportUnboundVariable = false 120 | reportUnnecessaryCast = true 121 | reportUnnecessaryTypeIgnorComment = true 122 | reportUnsupportedDunderAll = false 123 | reportUnusedExpression = false 124 | 125 | [tool.pytest.ini_options] 126 | addopts = "-n auto --dist=loadscope --durations=5" 127 | filterwarnings = [ 128 | "ignore::RuntimeWarning", 129 | "ignore::pytest.PytestCollectionWarning", 130 | ] 131 | 132 | [tool.ruff] 133 | target-version = "py310" 134 | line-length = 88 135 | select = ["ALL"] 136 | ignore = [ 137 | "C", # Pylint - Convention 138 | "C90", # mccabe 139 | "COM", # flake8-commas 140 | "ERA", # eradicate 141 | "FBT", # flake8-boolean-trap 142 | "FIX", # flake8-fixme 143 | "PT", # flake8-pytest-style 144 | "PTH", # flake8-use-pathlib [Enable] 145 | "TD", # flake8-todos 146 | "ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs` 147 | "D200", # One-line docstring should fit on one line 148 | "D202", # No blank lines allowed after function docstring 149 | "D205", # 1 blank line required between summary line and description 150 | "D301", # Use `r"""` if any backslashes in a docstring 151 | "D400", # First line should end with a period 152 | "I001", # Import block is un-sorted or un-formatted 153 | "N801", # Class name `.*` should use CapWords convention 154 | "N802", # Function name `.*` should be lowercase 155 | "N803", # Argument name `.*` should be lowercase 156 | "N806", # Variable `.*` in function should be lowercase 157 | "N813", # Camelcase `.*` imported as lowercase `.*` 158 | "N815", # Variable `.*` in class scope should not be mixedCase 159 | "N816", # Variable `.*` in global scope should not be mixedCase 160 | "NPY002", # Replace legacy `np.random.random` call with `np.random.Generator` 161 | "PGH003", # Use specific rule codes when ignoring type issues 162 | "PLR0912", # Too many branches 163 | "PLR0913", # Too many arguments in function definition 164 | "PLR0915", # Too many statements 165 | "PLR2004", # Magic value used in comparison, consider replacing `.*` with a constant variable 166 | "PYI036", # Star-args in `.*` should be annotated with `object` 167 | "PYI051", # `Literal[".*"]` is redundant in a union with `str` 168 | "PYI056", # Calling `.append()` on `__all__` may not be supported by all type checkers (use `+=` instead) 169 | "RUF022", # [*] `__all__` is not sorted 170 | "TRY003", # Avoid specifying long messages outside the exception class 171 | "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` 172 | ] 173 | typing-modules = ["colour.hints"] 174 | 175 | [tool.ruff.pydocstyle] 176 | convention = "numpy" 177 | 178 | [tool.ruff.per-file-ignores] 179 | "__init__.py" = ["D104"] 180 | "apps/*" = ["ANN"] 181 | "docs/*" = ["INP"] 182 | "tasks.py" = ["INP"] 183 | "test_*" = ["S101"] 184 | 185 | [tool.ruff.format] 186 | docstring-code-format = true 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACES Input Device Transform Calculator Apps 2 | 3 | [![CLA assistant](https://cla-assistant.io/readme/badge/ampas/idt-calculator)](https://cla-assistant.io/ampas/idt-calculator) 4 | 5 | ## Table of Contents 6 | 7 | 1. [Introduction](#introduction) 8 | 2. [Package Contents](#package-contents) 9 | 3. [Prerequisites](#prerequisites) 10 | 4. [Installation](#installation) 11 | 5. [Usage](#usage) 12 | 6. [License](#license) 13 | 14 | ## Introduction 15 | 16 | This repository contains the source for the Academy Input Device Transform (IDT) 17 | online Apps: 18 | 19 | [Academy Input Device Transform (IDT) Calculator - P-2013-001](https://beta.devtools.acescentral.com/apps/idt_calculator_p_2013_001) 20 | ![Academy Input Device Transform (IDT) Calculator - P-2013-001](docs/_static/idt_calculator_p_2013_001.png) 21 | 22 | [Academy Input Device Transform (IDT) Calculator - Prosumer Camera](https://beta.devtools.acescentral.com/apps/idt_calculator_prosumer_camera) 23 | ![Academy Input Device Transform (IDT) Calculator - Prosumer Camera](docs/_static/idt_calculator_prosumer_camera.png) 24 | 25 | ## Package Contents 26 | 27 | - [`aces/`](./aces) - _Python_ package containing the API code. 28 | - [`apps/`](./apps) - _Python_ package of the online Apps. 29 | - [`assets/`](./assets) - Assets, e.g. CSS files for the online Apps. 30 | - [`docs/`](./docs) - Documentation for the API code. 31 | 32 | ## Prerequisites 33 | 34 | ### Docker (Users) 35 | 36 | [Docker](https://www.docker.com) is the only requirement to run the Apps locally. 37 | 38 | #### Python & Poetry (Developers) 39 | 40 | [Poetry](https://python-poetry.org) is recommended for developers willing to contribute to the project. 41 | The [`pyproject.toml`](./pyproject.toml) file defines the various packages required. 42 | It is also possible to use the [`requirements.txt`](./requirements.txt) file to generate a virtual environment with all the dependencies. 43 | 44 | ## Installation 45 | 46 | ### Docker (Users) 47 | 48 | ```bash 49 | $ docker build --platform=linux/amd64 \ 50 | -t ampas/ampas-apps:latest \ 51 | -t ampas/ampas-apps:latest-amd64 . 52 | ``` 53 | 54 | The apps can then be launched locally as follows: 55 | 56 | ```bash 57 | $ docker run -d \ 58 | --name=ampas-apps \ 59 | -p 8010:8000 ampas/ampas-apps:latest-amd64 60 | ``` 61 | 62 | #### Python & Poetry (Developers) 63 | 64 | ```bash 65 | $ poetry install 66 | ``` 67 | 68 | The Apps can then be launched locally as follows: 69 | 70 | ```bash 71 | $ poetry run python index.py 72 | ``` 73 | 74 | or 75 | 76 | ```bash 77 | $ poetry run invoke docker-run 78 | ``` 79 | 80 | ## Usage 81 | 82 | Each App has an `About` tab describing the given App and its usage. 83 | 84 | ### Prosumer Camera IDT Archive 85 | 86 | The IDT App for Prosumer Cameras requires a Zip archive file, i.e. IDT archive, with a specific structure and content. 87 | 88 | ### Explicit Specification 89 | 90 | The explicit specification of the IDT archive requires a root JSON file describing the paths to the various image sequences. 91 | 92 | ![IDT Archive Explicit Structure](docs/_static/idt_archive_explicit_structure.png) 93 | 94 | The root JSON file describes which image sequences correspond to which exposure value, flatfield and grey card. 95 | 96 | ![IDT Archive Explicit JSON File](docs/_static/idt_archive_explicit_json_file.png) 97 | 98 | The JSON schema for the IDT archive can be used to validate a new user file using a [validator](http://www.jsonschemavalidator.net/) and is defined as follows: 99 | 100 | ```json 101 | { 102 | "$schema": "http://json-schema.org/draft-04/schema#", 103 | "type": "object", 104 | "properties": { 105 | "header": { 106 | "type": "object", 107 | "properties": { 108 | "schema_version": { 109 | "type": "string" 110 | }, 111 | "aces_transform_id": { 112 | "type": "string" 113 | }, 114 | "aces_user_name": { 115 | "type": "string" 116 | }, 117 | "camera_make": { 118 | "type": "string" 119 | }, 120 | "camera_model": { 121 | "type": "string" 122 | }, 123 | "iso": { 124 | "type": "number" 125 | }, 126 | "temperature": { 127 | "type": "number" 128 | }, 129 | "additional_camera_settings": { 130 | "anyOf": [{ "type": "string" }, { "type": "null" }] 131 | }, 132 | "lighting_setup_description": { 133 | "anyOf": [{ "type": "string" }, { "type": "null" }] 134 | }, 135 | "debayering_platform": { 136 | "anyOf": [{ "type": "string" }, { "type": "null" }] 137 | }, 138 | "debayering_settings": { 139 | "anyOf": [{ "type": "string" }, { "type": "null" }] 140 | }, 141 | "encoding_colourspace": { 142 | "anyOf": [{ "type": "string" }, { "type": "null" }] 143 | } 144 | }, 145 | "required": ["schema_version", "camera_make", "camera_model"] 146 | }, 147 | "data": { 148 | "type": "object", 149 | "properties": { 150 | "colour_checker": { 151 | "type": "object", 152 | "patternProperties": { 153 | "[+-]?[0-9]+[.]?[0-9]*([e][+-]?[0-9]+)?": { 154 | "type": "array", 155 | "items": [ 156 | { 157 | "type": "string" 158 | } 159 | ] 160 | } 161 | }, 162 | "additionalProperties": false 163 | }, 164 | "flatfield": { 165 | "type": "array", 166 | "items": [ 167 | { 168 | "type": "string" 169 | } 170 | ] 171 | }, 172 | "grey_card": { 173 | "type": "array", 174 | "items": [ 175 | { 176 | "type": "string" 177 | } 178 | ] 179 | } 180 | }, 181 | "required": ["colour_checker"] 182 | } 183 | }, 184 | "required": ["header", "data"] 185 | } 186 | ``` 187 | 188 | #### Floating Point Exposure Values 189 | 190 | Floating point exposure values are also supported as keys in the JSON file: 191 | 192 | ![IDT Archive Explicit Structure - Floating Point Values](docs/_static/idt_archive_explicit_json_file_floating_point_ev.png) 193 | 194 | ### Implicit Specification 195 | 196 | The implicit specification of the IDT archive requires that the image sequences are stored in specific directories that match the JSON schema: 197 | 198 | ![IDT Archive Implicit Structure](docs/_static/idt_archive_implicit_structure.png) 199 | 200 | The implicit specification cannot represent some of the metadata that the explicit specification supports, e.g. `manufacture` or `exposure_settings`. 201 | 202 | #### Floating Point Exposure Values 203 | 204 | It is also possible to use floating point exposure values by naming the directories accordingly: 205 | 206 | ![IDT Archive Implicit Structure - Floating Point Values](docs/_static/idt_archive_implicit_structure_fractional_ev.png) 207 | 208 | ## License 209 | 210 | This project is licensed under the terms of the [LICENSE](./LICENSE.md) agreement. 211 | 212 | ## Contributing 213 | 214 | Thank you for your interest in contributing to our project. Before any contributions can be accepted, we require contributors to sign a Contributor License Agreement (CLA) to ensure that the project can freely use your contributions. You can find more details and instructions on how to sign the CLA in the [CONTRIBUTING.md](./CONTRIBUTING.md) file. 215 | 216 | ## Support 217 | 218 | For support, please visit [ACESCentral.com](https://acescentral.com) 219 | -------------------------------------------------------------------------------- /aces/idt/core/structures.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structures 3 | ========== 4 | 5 | Define various helper classes, especially to serialize properties to and from 6 | *JSON*. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | import json 12 | from dataclasses import dataclass, field 13 | from pathlib import Path 14 | from typing import TYPE_CHECKING 15 | 16 | import numpy as np 17 | 18 | if TYPE_CHECKING: 19 | from colour.hints import Any, Callable, Tuple 20 | 21 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 22 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 23 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 24 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 25 | __email__ = "acessupport@oscars.org" 26 | __status__ = "Production" 27 | 28 | __all__ = [ 29 | "PathEncoder", 30 | "SerializableConstants", 31 | "Metadata", 32 | "MetadataProperty", 33 | "metadata_property", 34 | "MixinSerializableProperties", 35 | ] 36 | 37 | 38 | class PathEncoder(json.JSONEncoder): 39 | """ 40 | Define a custom :class:`json.JSONEncoder` subclass that can encode 41 | :class:`Path` class instance objects. 42 | """ 43 | 44 | def default(self, obj: Any) -> Any: 45 | """ 46 | Convert the object to a *JSON* serializable object. 47 | 48 | Parameters 49 | ---------- 50 | obj 51 | Object to serialize. 52 | 53 | Returns 54 | ------- 55 | :class:`object` 56 | *JSON* serializable object. 57 | """ 58 | 59 | if isinstance(obj, Path): 60 | # Convert the PosixPath to a string 61 | return str(obj) 62 | 63 | if isinstance(obj, np.ndarray): 64 | return list(obj) 65 | 66 | return super().default(obj) 67 | 68 | 69 | class SerializableConstants: 70 | """Constants for the serializable.""" 71 | 72 | HEADER = "header" 73 | DATA = "data" 74 | 75 | 76 | @dataclass 77 | class Metadata: 78 | """ 79 | Store the metadata information for a serializable property. 80 | 81 | This metadata is primarily designed to store data associated with how the 82 | property might be displayed in a UI. 83 | """ 84 | 85 | default_value: Any = field(default=None) 86 | description: str = field(default="") 87 | display_name: str = field(default="") 88 | name: str = field(default="") 89 | serialize_group: str = field(default=SerializableConstants.HEADER) 90 | ui_category: str = field(default="") 91 | ui_type: str = field(default="") 92 | options: Any = field(default=None) 93 | 94 | 95 | class MetadataProperty: 96 | """ 97 | Define a property storing a :class:`Metadata` class instance with support 98 | for getter and setter functionality. 99 | """ 100 | 101 | def __init__( 102 | self, 103 | getter: Callable, 104 | setter: Callable | None = None, 105 | metadata: Metadata | None = None, 106 | ) -> None: 107 | self.getter = getter 108 | self.setter = setter 109 | self.metadata = metadata 110 | 111 | def __get__(self, instance: Any, owner: Any) -> Any: 112 | """Get the value of the property.""" 113 | 114 | if instance is None: 115 | # Return the object itself when accessed from the class. 116 | return self 117 | 118 | return self.getter(instance) 119 | 120 | def __set__(self, instance: Any, value: Any) -> Any: 121 | """Set the value of the property.""" 122 | 123 | if self.setter: 124 | self.setter(instance, value) 125 | else: 126 | msg = "No setter defined for this property" 127 | raise AttributeError(msg) 128 | 129 | 130 | def metadata_property( 131 | metadata: Metadata | None = None, validation: Callable | None = None 132 | ) -> Callable: 133 | """ 134 | Decorate a class attribute to create a property using given :class`Metadata` 135 | class instance, supporting both getter and setter functionality. 136 | """ 137 | 138 | def wrapper(getter: Callable) -> Callable: 139 | def setter(instance: MixinSerializableProperties, value: str) -> Callable: 140 | if validation and value and not validation(value): 141 | msg = f"Value {value} is not valid for this property" 142 | raise ValueError(msg) 143 | setattr(instance, f"_{getter.__name__}", value) 144 | 145 | return MetadataProperty(getter, setter, metadata) 146 | 147 | return wrapper 148 | 149 | 150 | class MixinSerializableProperties: 151 | """ 152 | Define a mixin class for serializable objects containing 153 | :class:`IDTMetadataProperty` class instances that can be converted to and 154 | from *JSON*. 155 | """ 156 | 157 | @property 158 | def properties(self) -> Tuple[str, MetadataProperty]: 159 | """ 160 | Generator for the properties of the object getting the name and 161 | property as a tuple. 162 | 163 | Yields 164 | ------ 165 | :class:`tuple` 166 | """ 167 | 168 | for name, object_ in self.__class__.__dict__.items(): 169 | if isinstance(object_, MetadataProperty): 170 | yield name, object_ 171 | 172 | def to_json(self) -> str: 173 | """ 174 | Convert the object to a *JSON* string representation. 175 | 176 | Returns 177 | ------- 178 | :class:`str`: 179 | *JSON* string representation. 180 | """ 181 | 182 | output = {SerializableConstants.HEADER: {}, SerializableConstants.DATA: {}} 183 | for name, prop in self.properties: 184 | if prop.metadata.serialize_group: 185 | output[prop.metadata.serialize_group][name] = prop.getter(self) 186 | else: 187 | output[SerializableConstants.DATA] = prop.getter(self) 188 | return json.dumps(output, indent=4, cls=PathEncoder) 189 | 190 | @classmethod 191 | def from_json(cls, data: Any) -> MixinSerializableProperties: 192 | """ 193 | Create a new instance of the class from the given object or *JSON* 194 | string. 195 | 196 | Parameters 197 | ---------- 198 | data: 199 | Object or string we want to load the *JSON* from. 200 | 201 | Returns 202 | ------- 203 | :class:`MixinSerializableProperties` 204 | Loaded object. 205 | """ 206 | 207 | item = cls() 208 | if isinstance(data, str): 209 | data = json.loads(data) 210 | 211 | for name, prop in item.properties: 212 | if prop.metadata.serialize_group == SerializableConstants.HEADER: 213 | if name in data[SerializableConstants.HEADER]: 214 | prop.setter(item, data[SerializableConstants.HEADER][name]) 215 | else: 216 | prop.setter(item, data[SerializableConstants.DATA]) 217 | 218 | return item 219 | 220 | def to_file(self, filepath: str) -> None: 221 | """ 222 | Serialize the object to a *JSON* file. 223 | 224 | Parameters 225 | ---------- 226 | filepath 227 | Path to the *JSON* file. 228 | """ 229 | 230 | with open(filepath, "w") as f: 231 | f.write(self.to_json()) 232 | 233 | @classmethod 234 | def from_file(cls, filepath: str) -> MixinSerializableProperties: 235 | """ 236 | Load the object from a *JSON* file. 237 | 238 | Parameters 239 | ---------- 240 | filepath 241 | Path to the *JSON* file. 242 | 243 | Returns 244 | ------- 245 | :class:`MixinSerializableProperties` 246 | Loaded object. 247 | """ 248 | 249 | with open(filepath) as f: 250 | data = json.load(f) 251 | 252 | return cls.from_json(data) 253 | -------------------------------------------------------------------------------- /aces/idt/generators/tonemapped_idt.py: -------------------------------------------------------------------------------- 1 | """ 2 | IDT Generator for a ToneMapped Camera 3 | ===================================== 4 | 5 | Define the *IDT* generator class for a *ToneMapped Camera*. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | import logging 11 | import typing 12 | 13 | import cv2 14 | import numpy as np 15 | from colour import LUT3x1D 16 | 17 | if typing.TYPE_CHECKING: 18 | from colour.hints import List, NDArrayFloat, NDArrayInt 19 | 20 | from colour.utilities import as_float_array, as_int_array, optional 21 | from scipy.interpolate import CubicHermiteSpline 22 | 23 | from aces.idt import DirectoryStructure 24 | from aces.idt.core.common import create_colour_checker_image, interpolate_nan_values 25 | from aces.idt.core.constants import EXPOSURE_CLIPPING_THRESHOLD 26 | 27 | if typing.TYPE_CHECKING: 28 | from aces.idt.framework import IDTProjectSettings 29 | 30 | from aces.idt.generators.log_camera import IDTGeneratorLogCamera 31 | 32 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 33 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 34 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 35 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 36 | __email__ = "acessupport@oscars.org" 37 | __status__ = "Production" 38 | 39 | __all__ = [ 40 | "IDTGeneratorToneMappedCamera", 41 | ] 42 | 43 | LOGGER = logging.getLogger(__name__) 44 | 45 | 46 | class IDTGeneratorToneMappedCamera(IDTGeneratorLogCamera): 47 | """ 48 | Define an *IDT* generator for a *ToneMapped Camera*. 49 | 50 | Parameters 51 | ---------- 52 | project_settings 53 | *IDT* generator settings. 54 | 55 | Attributes 56 | ---------- 57 | - :attr:`~aces.idt.IDTGeneratorToneMappedCamera.GENERATOR_NAME` 58 | - :attr:`~aces.idt.IDTGeneratorToneMappedCamera.exposure_times` 59 | 60 | Methods 61 | ------- 62 | - :meth:`~aces.idt.IDTGeneratorToneMappedCamera.sort` 63 | - :meth:`~aces.idt.IDTGeneratorToneMappedCamera.remove_clipped_samples` 64 | - :meth:`~aces.idt.IDTGeneratorToneMappedCamera.generate_LUT` 65 | - :meth:`~aces.idt.IDTGeneratorToneMappedCamera.filter_LUT` 66 | """ 67 | 68 | GENERATOR_NAME = "IDTGeneratorToneMappedCamera" 69 | """*IDT* generator name.""" 70 | 71 | def __init__(self, project_settings: IDTProjectSettings) -> None: 72 | super().__init__(project_settings) 73 | self._exposure_times = [] 74 | 75 | @property 76 | def exposure_times(self) -> List[float]: 77 | """Return the exposure times for the samples.""" 78 | 79 | return self._exposure_times 80 | 81 | def sort(self, start_index: int | None = None) -> NDArrayFloat: 82 | """ 83 | Sort the samples produced by the image sampling process. 84 | 85 | This override not only sorts, collects and stacks the samples, but also 86 | computes the exposure times for each sample. All the swatches from 87 | the colour checker are used for sorting not just the last 6. 88 | 89 | Parameters 90 | ---------- 91 | start_index 92 | The index to start sorting from, default to 0, so that all the 93 | samples from the colour checker are used. 94 | 95 | Returns 96 | ------- 97 | :class:`np.ndarray` 98 | Sorting indices. 99 | """ 100 | 101 | start_index = optional(start_index, 0) 102 | 103 | LOGGER.info("Sorting camera and reference samples...") 104 | 105 | reference_colour_checker_samples = ( 106 | self.project_settings.get_reference_colour_checker_samples() 107 | ) 108 | 109 | samples_camera = [] 110 | samples_reference = [] 111 | exposure_times = [] 112 | for EV, images in self._samples_analysis[ 113 | DirectoryStructure.COLOUR_CHECKER 114 | ].items(): 115 | samples_reference.append( 116 | reference_colour_checker_samples[start_index:, ...] * pow(2, EV) 117 | ) 118 | samples_EV = as_float_array(images["samples_median"])[start_index:, ...] 119 | samples_camera.append(samples_EV) 120 | 121 | num_samples = len(images["samples_median"][start_index:]) 122 | shutter = 100 / (2**EV) if EV >= 0 else 100 * (2 ** abs(EV)) 123 | emulated_shutter_time = np.full((num_samples, 3), shutter) 124 | exposure_times.append(emulated_shutter_time) 125 | 126 | self._samples_camera = np.vstack(samples_camera) 127 | self._samples_reference = np.vstack(samples_reference) 128 | self._exposure_times = np.vstack(exposure_times) 129 | 130 | return np.array([]) 131 | 132 | def remove_clipped_samples( 133 | self, 134 | threshold: float = EXPOSURE_CLIPPING_THRESHOLD, # noqa: ARG002 135 | ) -> NDArrayInt: 136 | """ 137 | Remove clipped camera samples and their corresponding reference samples. 138 | 139 | This override by pass the process entirely as this is handled during 140 | subsequent processing stages. 141 | 142 | Returns 143 | ------- 144 | :class:`np.ndarray` 145 | Clipped samples indices. 146 | """ 147 | 148 | return as_int_array([]) 149 | 150 | def generate_LUT(self) -> LUT3x1D: 151 | """ 152 | Generate an unfiltered linearisation *LUT* for the camera samples. 153 | 154 | The *LUT* generation process is as follows: The camera samples are 155 | unlikely to cover the [0, 1] domain and thus need to be extrapolated. 156 | The camera samples are then grouped by exposure time and a faux colour 157 | checker image is created, with a subtle blur. Debevec (1997) algorithm 158 | is finally then used to generate the camera response curves. Any NaNs 159 | are removed via linear interpolation. 160 | 161 | Returns 162 | ------- 163 | :class:`LUT3x1D` 164 | Unfiltered linearisation *LUT* for the camera samples. 165 | """ 166 | 167 | samples_per_exposure = {} 168 | for idx, sample in enumerate(self.samples_camera): 169 | exposure_time = self._exposure_times[idx][0] 170 | pixel = sample.tolist() 171 | samples_per_exposure.setdefault(exposure_time, []).append(pixel) 172 | 173 | image_stack = [] 174 | exposures = [] 175 | keys = sorted(samples_per_exposure.keys()) 176 | for exposure_time in keys: 177 | pixels = samples_per_exposure[exposure_time] 178 | image = create_colour_checker_image(pixels) 179 | kernel_size = (121, 121) 180 | 181 | # Apply Gaussian blur to the image 182 | blurred_image = cv2.GaussianBlur(image, kernel_size, 0) 183 | normalized_array = np.clip(blurred_image * 255, 0, 255) 184 | img = normalized_array.astype(np.uint8) 185 | image_stack.append(img) 186 | exposures.append(1.0 / exposure_time) 187 | 188 | calibrator = cv2.createCalibrateDebevec() 189 | response = calibrator.process( 190 | image_stack, np.array(exposures, dtype=np.float32) 191 | ) 192 | response = response.reshape(256, 3) 193 | response = interpolate_nan_values(response) 194 | 195 | size = self.project_settings.lut_size 196 | 197 | self._LUT_unfiltered = LUT3x1D(size=size, name="LUT - Unfiltered") 198 | self._LUT_unfiltered.table = response 199 | 200 | return self._LUT_unfiltered 201 | 202 | def filter_LUT(self) -> LUT3x1D: 203 | """ 204 | Filter the unfiltered linearisation *LUT* for the camera samples. 205 | 206 | A weighted average is used to bias the green channel increasingly, 207 | before a hermite spline is applied to interpolate the values rather 208 | than filter them. 209 | 210 | Returns 211 | ------- 212 | :class:`LUT3x1D` 213 | Filtered linearisation *LUT* for the camera samples. 214 | """ 215 | 216 | self._LUT_filtered = self._LUT_unfiltered.copy() 217 | self._LUT_filtered.name = "LUT - Filtered" 218 | 219 | size = self.project_settings.lut_size 220 | 221 | weights = np.array([1, 2, 1]) # More weight to Green 222 | weighted_average = np.average(self._LUT_filtered.table, axis=1, weights=weights) 223 | 224 | weighted_average = np.power(weighted_average, 1 / 2.0) 225 | 226 | x_values = np.arange(len(weighted_average)) 227 | derivatives = np.gradient(weighted_average, x_values) 228 | hermite_spline = CubicHermiteSpline(x_values, weighted_average, derivatives) 229 | 230 | # Generate new x values for a smoother curve 231 | x_new = np.linspace(0, len(weighted_average) - 1, size) 232 | 233 | # More points for smooth curve 234 | y_new = hermite_spline(x_new) 235 | y_new = np.power(y_new, 2.0) 236 | 237 | lut_rgb = np.tile(y_new[:, np.newaxis], (1, 3)) 238 | self._LUT_filtered.table = lut_rgb 239 | 240 | return self._LUT_filtered 241 | -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | """Define the unit tests for the :mod:`aces.idt.core.common` module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import json 6 | import os.path 7 | 8 | import numpy as np 9 | 10 | from aces.idt.core import EXPOSURE_CLIPPING_THRESHOLD 11 | from aces.idt.core.common import ( 12 | calculate_camera_npm_and_primaries_wp, 13 | find_clipped_exposures, 14 | find_similar_rows, 15 | generate_reference_colour_checker, 16 | ) 17 | from tests.test_utils import TestIDTBase 18 | 19 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 20 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 21 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 22 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 23 | __email__ = "acessupport@oscars.org" 24 | __status__ = "Production" 25 | 26 | __all__ = [ 27 | "TestGenerateReferenceColourChecker", 28 | "TestCalculateCameraNpmAndPrimariesWp", 29 | "TestFindSimilarRows", 30 | "TestFindClippedExposures", 31 | ] 32 | 33 | 34 | class TestGenerateReferenceColourChecker: 35 | """ 36 | Define :func:`aces.idt.core.common.generate_reference_colour_checker` 37 | definition unit tests methods. 38 | """ 39 | 40 | def test_generate_reference_colour_checker(self) -> None: 41 | """ 42 | Test :func:`aces.idt.core.common.generate_reference_colour_checker` 43 | definition. 44 | """ 45 | 46 | np.testing.assert_allclose( 47 | generate_reference_colour_checker(), 48 | [ 49 | [0.11876842177626151, 0.08709058563389999, 0.058951218439182544], 50 | [0.40000355538786564, 0.31916517742981565, 0.23733683167215208], 51 | [0.18476317529262326, 0.20397604937400404, 0.3130841414707486], 52 | [0.10900767177832925, 0.13511034887945872, 0.06492296526026398], 53 | [0.2668325426084729, 0.2460383081243736, 0.40929373504647487], 54 | [0.3228275347098078, 0.46209038705471595, 0.4059997268840585], 55 | [0.3860420994813448, 0.22743356338910722, 0.05776644858328035], 56 | [0.1382168532394208, 0.1303655057792208, 0.3369849589807394], 57 | [0.30200029194805983, 0.13751934107949534, 0.12757993019390476], 58 | [0.09309755810948224, 0.06346469265430443, 0.13526731719711416], 59 | [0.34876526154496384, 0.43654731605432173, 0.10611841964202463], 60 | [0.4865437930159921, 0.3668514610866347, 0.08060352133648069], 61 | [0.08730558671548493, 0.07442476376021454, 0.2726781684209821], 62 | [0.15366178743784017, 0.25691653578416973, 0.09070349092374685], 63 | [0.21739211063561842, 0.07069630521117498, 0.05129818513944815], 64 | [0.5891828043214191, 0.5394372192137751, 0.09155549948424543], 65 | [0.3090146231057637, 0.14817408797862763, 0.2742627591798639], 66 | [0.14899905555111928, 0.23378396576462934, 0.3593217013366608], 67 | [0.8665068310764097, 0.8679173464028738, 0.858093881322456], 68 | [0.5735498956190463, 0.5725588746417859, 0.5716389345559676], 69 | [0.3534557219490971, 0.3533663496431708, 0.353882100057212], 70 | [0.20252229397603771, 0.2024315233275667, 0.20285713026326946], 71 | [0.0946720142743027, 0.0952034139165298, 0.0963649584715441], 72 | [0.03745083433881645, 0.03766169506735516, 0.038954301107662154], 73 | ], 74 | atol=1e-15, 75 | ) 76 | 77 | 78 | class TestCalculateCameraNpmAndPrimariesWp: 79 | """ 80 | Define :func:`aces.idt.core.common.calculate_camera_npm_and_primaries_wp` 81 | definition unit tests methods. 82 | """ 83 | 84 | def test_calculate_camera_npm_and_primaries_wp(self) -> None: 85 | """ 86 | Test :func:`aces.idt.core.common.calculate_camera_npm_and_primaries_wp` 87 | definition. 88 | """ 89 | 90 | input_matrix = [ 91 | [0.785043, 0.083844, 0.131118], 92 | [0.023172, 1.087892, -0.111055], 93 | [-0.073769, -0.314639, 1.388537], 94 | ] 95 | 96 | npm, primaries, wp = calculate_camera_npm_and_primaries_wp(input_matrix) 97 | 98 | expected_npm = [ 99 | [0.7353579, 0.06867992, 0.14646275], 100 | [0.28673187, 0.84296573, -0.1297009], 101 | [-0.07965591, -0.34720223, 1.5155319], 102 | ] 103 | 104 | expected_primaries = [ 105 | [0.7802753326723714, 0.3042461485259778], 106 | [0.1216772523298739, 1.4934459215643787], 107 | [0.09558399073520502, -0.08464493023427101], 108 | ] 109 | 110 | expected_wp = [0.31274994, 0.32903601] 111 | 112 | np.testing.assert_allclose(expected_npm, npm, atol=1e-6) 113 | np.testing.assert_allclose(expected_primaries, primaries, atol=1e-6) 114 | np.testing.assert_allclose(expected_wp, wp, atol=1e-6) 115 | 116 | 117 | class TestFindSimilarRows(TestIDTBase): 118 | """ 119 | Define :func:`aces.idt.core.common.find_similar_rows` definition unit tests 120 | methods. 121 | """ 122 | 123 | def setUp(self) -> None: 124 | """Initialise the common tests attributes.""" 125 | 126 | self.tolerance = 0.005 127 | 128 | def test_scenario_1(self) -> None: 129 | """ 130 | Test case where none of the values are lower than the threshold and 131 | ensure that no values are masked. 132 | """ 133 | 134 | colour_checker_scenario_1 = np.array( 135 | [ 136 | [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]], 137 | [[0.15, 0.25, 0.35], [0.45, 0.55, 0.65], [0.75, 0.85, 0.95]], 138 | [[0.2, 0.3, 0.4], [0.5, 0.6, 0.7], [0.8, 0.9, 1.0]], 139 | [[0.25, 0.35, 0.45], [0.55, 0.65, 0.75], [0.85, 0.95, 1.05]], 140 | [[0.3, 0.4, 0.5], [0.6, 0.7, 0.8], [0.9, 1.0, 1.1]], 141 | ] 142 | ) 143 | clipped_indices = find_similar_rows( 144 | colour_checker_scenario_1, threshold=self.tolerance 145 | ) 146 | expected_data = [] 147 | np.testing.assert_array_equal(clipped_indices, expected_data) 148 | 149 | def test_scenario_2(self) -> None: 150 | """ 151 | Test case where the first two values are clipped and ensure that the 152 | first row is masked. 153 | """ 154 | 155 | colour_checker_scenario_2 = np.array( 156 | [ 157 | [0.1, 0.1, 0.1], 158 | [0.1, 0.1, 0.1], 159 | [0.5, 0.5, 0.5], 160 | [0.6, 0.6, 0.6], 161 | [0.7, 0.7, 0.7], 162 | ] 163 | ) 164 | clipped_indices = find_similar_rows( 165 | colour_checker_scenario_2, threshold=self.tolerance 166 | ) 167 | expected_data = [0] 168 | self.assertEqual(clipped_indices, expected_data) 169 | 170 | def test_scenario_3(self) -> None: 171 | """ 172 | Test case where the last two values are clipped and ensure that the 173 | last row is masked. 174 | """ 175 | 176 | colour_checker_scenario_3 = np.array( 177 | [ 178 | [0.1, 0.2, 0.3], 179 | [0.15, 0.25, 0.35], 180 | [0.2, 0.3, 0.4], 181 | [0.1, 0.1, 0.1], 182 | [0.1, 0.1, 0.1], 183 | ] 184 | ) 185 | clipped_indices = find_similar_rows( 186 | colour_checker_scenario_3, threshold=self.tolerance 187 | ) 188 | expected_data = [4] 189 | self.assertEqual(clipped_indices, expected_data) 190 | 191 | def test_scenario_4(self) -> None: 192 | """ 193 | Test case where both the top and bottom values are clipped and ensure 194 | that both the first and last rows are masked. 195 | """ 196 | 197 | colour_checker_scenario_4 = np.array( 198 | [ 199 | [0.1, 0.1, 0.1], 200 | [0.1, 0.1, 0.1], 201 | [0.2, 0.3, 0.4], 202 | [1.0, 1.0, 1.0], 203 | [1.0, 1.0, 1.0], 204 | ] 205 | ) 206 | clipped_indices = find_similar_rows( 207 | colour_checker_scenario_4, threshold=self.tolerance 208 | ) 209 | expected_data = [0, 4] 210 | np.testing.assert_array_equal(clipped_indices, expected_data) 211 | 212 | def test_scenario_5(self) -> None: 213 | """ 214 | Test case where both the top and bottom values are clipped with multiple 215 | instances and ensure that the first two and last rows are masked. 216 | """ 217 | 218 | colour_checker_scenario_5 = np.array( 219 | [ 220 | [0.1, 0.1, 0.1], 221 | [0.1, 0.1, 0.1], 222 | [0.1, 0.1, 0.1], 223 | [0.15, 0.15, 0.15], 224 | [0.2, 0.3, 0.4], 225 | [0.9, 0.9, 0.9], 226 | [1.0, 1.0, 1.0], 227 | [1.0, 1.0, 1.0], 228 | ] 229 | ) 230 | clipped_indices = find_similar_rows( 231 | colour_checker_scenario_5, threshold=self.tolerance 232 | ) 233 | expected_data = [0, 1, 7] 234 | np.testing.assert_array_equal(clipped_indices, expected_data) 235 | 236 | 237 | class TestFindClippedExposures(TestIDTBase): 238 | """ 239 | Define :func:`aces.idt.core.common.find_clipped_exposures` definition unit 240 | tests methods. 241 | """ 242 | 243 | def setUp(self) -> None: 244 | """Initialise the common tests attributes.""" 245 | 246 | self.tolerance = 0.0499 247 | 248 | def test_scenario_1(self) -> None: 249 | """ 250 | Test the scenario where none of the exposures haves values below the 251 | threshold and, thus, ensure that no exposures values were removed. 252 | """ 253 | 254 | colour_checker_scenario_1 = { 255 | -2.0: np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]]), 256 | -1.0: np.array( 257 | [[0.15, 0.25, 0.35], [0.45, 0.55, 0.65], [0.75, 0.85, 0.95]] 258 | ), 259 | 0.0: np.array([[0.2, 0.3, 0.4], [0.5, 0.6, 0.7], [0.8, 0.9, 1.0]]), 260 | 1.0: np.array([[0.25, 0.35, 0.45], [0.55, 0.65, 0.75], [0.85, 0.95, 1.05]]), 261 | 2.0: np.array([[0.3, 0.4, 0.5], [0.6, 0.7, 0.8], [0.9, 1.0, 1.1]]), 262 | } 263 | 264 | removed_evs = find_clipped_exposures(colour_checker_scenario_1, self.tolerance) 265 | expected_ev_keys = [] 266 | self.assertEqual(removed_evs, expected_ev_keys) 267 | 268 | def test_scenario_2(self) -> None: 269 | """ 270 | Test the scenario where exposures -2.0 and -1.0 have values below the 271 | threshold and, thus, exposure -2 should be removed. 272 | """ 273 | 274 | colour_checker_scenario_2 = { 275 | -2.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 276 | -1.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 277 | 0.0: np.array([[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]]), 278 | 1.0: np.array([[0.6, 0.6, 0.6], [0.6, 0.6, 0.6], [0.6, 0.6, 0.6]]), 279 | 2.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 280 | } 281 | 282 | removed_evs = find_clipped_exposures(colour_checker_scenario_2, self.tolerance) 283 | expected_ev_keys = [-2.0] 284 | self.assertEqual(sorted(removed_evs), expected_ev_keys) 285 | 286 | def test_scenario_3(self) -> None: 287 | """ 288 | Test the scenario where exposures 1.0 and 2.0 have values below the 289 | threshold and, thus, exposure 2 should be removed. 290 | """ 291 | 292 | colour_checker_scenario_3 = { 293 | -2.0: np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]]), 294 | -1.0: np.array( 295 | [[0.15, 0.25, 0.35], [0.45, 0.55, 0.65], [0.75, 0.85, 0.95]] 296 | ), 297 | 0.0: np.array([[0.2, 0.3, 0.4], [0.5, 0.6, 0.7], [0.8, 0.9, 1.0]]), 298 | 1.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 299 | 2.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 300 | } 301 | 302 | removed_evs = find_clipped_exposures(colour_checker_scenario_3, self.tolerance) 303 | expected_ev_keys = [2.0] 304 | self.assertEqual(removed_evs, expected_ev_keys) 305 | 306 | def test_scenario_4(self) -> None: 307 | """ 308 | Test the scenario where both the bottom and top exposures have values 309 | below the threshold and, thus, exposures -2 and 2 should be removed. 310 | """ 311 | 312 | colour_checker_scenario_4 = { 313 | -2.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 314 | -1.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 315 | 0.0: np.array([[0.2, 0.2, 0.2], [0.2, 0.2, 0.2], [0.2, 0.2, 0.2]]), 316 | 1.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 317 | 2.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 318 | } 319 | 320 | removed_evs = find_clipped_exposures(colour_checker_scenario_4, self.tolerance) 321 | expected_ev_keys = [-2.0, 2.0] 322 | self.assertEqual(removed_evs, expected_ev_keys) 323 | 324 | def test_scenario_5(self) -> None: 325 | """ 326 | Test the scenario where exposures -3, -2, 2.0, and, 3 should be removed. 327 | """ 328 | 329 | colour_checker_scenario_5 = { 330 | -3.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 331 | -2.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 332 | -1.0: np.array([[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]), 333 | 0.0: np.array([[0.2, 0.2, 0.2], [0.2, 0.2, 0.2], [0.2, 0.2, 2.0]]), 334 | 1.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 335 | 2.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 336 | 3.0: np.array([[0.7, 0.7, 0.7], [0.7, 0.7, 0.7], [0.7, 0.7, 0.7]]), 337 | } 338 | 339 | removed_evs = find_clipped_exposures(colour_checker_scenario_5, self.tolerance) 340 | expected_ev_keys = [-3.0, -2.0, 2.0, 3.0] 341 | self.assertEqual(removed_evs, expected_ev_keys) 342 | 343 | def test_scenario_6(self) -> None: 344 | """ 345 | Test the scenario where exposures -6, -5, -4, 4, 5, and, 6 should be 346 | removed. 347 | """ 348 | 349 | file_path = os.path.join( 350 | self.get_test_resources_folder(), "samples_decoded_clipped.json" 351 | ) 352 | 353 | with open(file_path) as file: 354 | temp_dict = json.load(file) 355 | colour_checker_scenario_6 = {} 356 | for key, value in temp_dict.items(): 357 | colour_checker_scenario_6[float(key)] = np.array(value) 358 | removed_evs = find_clipped_exposures( 359 | colour_checker_scenario_6, EXPOSURE_CLIPPING_THRESHOLD 360 | ) 361 | expected_ev_keys = [-6.0, -5.0, -4.0, 4.0, 5.0, 6.0] 362 | self.assertEqual(removed_evs, expected_ev_keys) 363 | -------------------------------------------------------------------------------- /aces/idt/application.py: -------------------------------------------------------------------------------- 1 | """ 2 | IDT Generator Application 3 | ========================= 4 | 5 | Define the *IDT* generator application class. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | import logging 11 | import re 12 | import typing 13 | from pathlib import Path 14 | 15 | if typing.TYPE_CHECKING: 16 | from colour.hints import List 17 | 18 | from colour.utilities import attest, optional 19 | 20 | import aces.idt.core.common 21 | from aces.idt.core.constants import DirectoryStructure 22 | from aces.idt.core.transform_id import generate_idt_urn, is_valid_csc_urn 23 | from aces.idt.framework.project_settings import IDTProjectSettings 24 | from aces.idt.generators import GENERATORS 25 | 26 | if typing.TYPE_CHECKING: 27 | from aces.idt.generators.base_generator import IDTBaseGenerator 28 | 29 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 30 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 31 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 32 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 33 | __email__ = "acessupport@oscars.org" 34 | __status__ = "Production" 35 | 36 | __all__ = [ 37 | "IDTGeneratorApplication", 38 | ] 39 | 40 | LOGGER = logging.getLogger(__name__) 41 | 42 | 43 | class IDTGeneratorApplication: 44 | """ 45 | Define the *IDT* generator application that handles project loading and 46 | saving, generator selection and execution, as well as any other analytical 47 | computations not related to the generators, such as calculating the data 48 | required for display. 49 | 50 | Parameters 51 | ---------- 52 | generator 53 | Name of the *IDT* generator to use. 54 | project_settings 55 | *IDT* project settings. 56 | """ 57 | 58 | def __init__( 59 | self, 60 | generator: str = "IDTGeneratorLogCamera", 61 | project_settings: IDTProjectSettings | None = None, 62 | ) -> None: 63 | self._project_settings = optional(project_settings, IDTProjectSettings()) 64 | self._generator = None 65 | self.generator = generator 66 | 67 | @property 68 | def generator_names(self) -> List: 69 | """ 70 | Getter property for the available *IDT* generator names. 71 | 72 | Returns 73 | ------- 74 | :class:`list` 75 | Available *IDT* generator names. 76 | """ 77 | 78 | return list(GENERATORS) 79 | 80 | @property 81 | def generator(self) -> IDTBaseGenerator: 82 | """ 83 | Getter and setter property for the selected *IDT* generator type. 84 | 85 | Returns 86 | ------- 87 | :class`IDTBaseGenerator`: 88 | Selected *IDT* generator type. 89 | """ 90 | 91 | return self._generator 92 | 93 | @generator.setter 94 | def generator(self, value: str) -> None: 95 | """Setter for the **self.generator** property.""" 96 | 97 | if value not in self.generator_names: 98 | exception = ( 99 | f'"{value}" generator is invalid, must be one of ' 100 | f'"{self.generator_names}"!' 101 | ) 102 | 103 | raise ValueError(exception) 104 | 105 | self._generator = GENERATORS[value](self.project_settings) 106 | 107 | @property 108 | def project_settings(self) -> IDTProjectSettings: 109 | """ 110 | Getter and setter property for the *IDT* project settings. 111 | 112 | A single instance of the *IDT* project settings exists within the class 113 | and its values are updated rather than replaced with the new passed 114 | instance. 115 | 116 | Returns 117 | ------- 118 | :class:`IDTProjectSettings` 119 | *IDT* project settings. 120 | """ 121 | 122 | return self._project_settings 123 | 124 | @project_settings.setter 125 | def project_settings(self, value: IDTProjectSettings) -> None: 126 | """Setter for the **self.project_settings** property.""" 127 | 128 | self._project_settings.update(value) 129 | 130 | def _update_project_settings_from_implicit_directory_structure( 131 | self, root_directory: Path 132 | ) -> None: 133 | """ 134 | Update the *IDT* project settings using the sub-directory structure under 135 | given root directory. The sub-directory structure should be defined as 136 | follows:: 137 | 138 | data 139 | colour_checker 140 | EV 141 | image1 142 | image2 143 | grey_card 144 | image1 145 | image2 146 | 147 | Parameters 148 | ---------- 149 | root_directory: 150 | Root directory holding the sub-directory structure. 151 | """ 152 | 153 | colour_checker_directory = ( 154 | root_directory / DirectoryStructure.DATA / DirectoryStructure.COLOUR_CHECKER 155 | ) 156 | attest(colour_checker_directory.exists()) 157 | for exposure_directory in colour_checker_directory.iterdir(): 158 | if re.match(r"-?\d", exposure_directory.name): 159 | EV = exposure_directory.name 160 | self.project_settings.data[DirectoryStructure.COLOUR_CHECKER][EV] = [ 161 | file 162 | for file in (colour_checker_directory / exposure_directory).glob( 163 | "*.*" 164 | ) 165 | if not file.name.startswith(".") 166 | ] 167 | 168 | flatfield_directory = ( 169 | root_directory / DirectoryStructure.DATA / DirectoryStructure.FLATFIELD 170 | ) 171 | if flatfield_directory.exists(): 172 | self.project_settings.data[DirectoryStructure.FLATFIELD] = [ 173 | file 174 | for file in flatfield_directory.glob("*.*") 175 | if not file.name.startswith(".") 176 | ] 177 | 178 | grey_card_directory = ( 179 | root_directory / DirectoryStructure.DATA / DirectoryStructure.GREY_CARD 180 | ) 181 | if grey_card_directory.exists(): 182 | self.project_settings.data[DirectoryStructure.GREY_CARD] = [ 183 | file 184 | for file in grey_card_directory.glob("*.*") 185 | if not file.name.startswith(".") 186 | ] 187 | 188 | def _verify_directory(self, root_directory: Path | str) -> None: 189 | """ 190 | Verify the *IDT* archive at given root directory. 191 | 192 | Parameters 193 | ---------- 194 | root_directory 195 | Root directory holding the *IDT* archive and that needs to be 196 | verified. 197 | """ 198 | 199 | for exposure in list( 200 | self.project_settings.data[DirectoryStructure.COLOUR_CHECKER].keys() 201 | ): 202 | images = [ 203 | Path(root_directory) / image 204 | for image in self.project_settings.data[ 205 | DirectoryStructure.COLOUR_CHECKER 206 | ].pop(exposure) 207 | ] 208 | 209 | for image in images: 210 | attest(image.exists()) 211 | 212 | self.project_settings.data[DirectoryStructure.COLOUR_CHECKER][ 213 | float(exposure) 214 | ] = images 215 | 216 | if self.project_settings.data.get(DirectoryStructure.FLATFIELD, []): 217 | images = [ 218 | Path(root_directory) / image 219 | for image in self.project_settings.data.get( 220 | DirectoryStructure.FLATFIELD, [] 221 | ) 222 | ] 223 | for image in images: 224 | attest(image.exists()) 225 | 226 | self.project_settings.data[DirectoryStructure.FLATFIELD] = images 227 | else: 228 | self.project_settings.data[DirectoryStructure.FLATFIELD] = [] 229 | 230 | if self.project_settings.data.get(DirectoryStructure.GREY_CARD, []): 231 | images = [ 232 | Path(root_directory) / image 233 | for image in self.project_settings.data.get( 234 | DirectoryStructure.GREY_CARD, [] 235 | ) 236 | ] 237 | for image in images: 238 | attest(image.exists()) 239 | 240 | self.project_settings.data[DirectoryStructure.GREY_CARD] = images 241 | else: 242 | self.project_settings.data[DirectoryStructure.GREY_CARD] = [] 243 | 244 | if self.project_settings.data.get(DirectoryStructure.BLACK, []): 245 | images = [ 246 | Path(root_directory) / image 247 | for image in self.project_settings.data.get( 248 | DirectoryStructure.BLACK, [] 249 | ) 250 | ] 251 | for image in images: 252 | attest(image.exists()) 253 | 254 | self.project_settings.data[DirectoryStructure.BLACK] = images 255 | else: 256 | self.project_settings.data[DirectoryStructure.BLACK] = [] 257 | 258 | if self.project_settings.data.get(DirectoryStructure.WHITE, []): 259 | images = [ 260 | Path(root_directory) / image 261 | for image in self.project_settings.data.get( 262 | DirectoryStructure.WHITE, [] 263 | ) 264 | ] 265 | for image in images: 266 | attest(image.exists()) 267 | 268 | self.project_settings.data[DirectoryStructure.WHITE] = images 269 | else: 270 | self.project_settings.data[DirectoryStructure.WHITE] = [] 271 | 272 | def _verify_file_type(self) -> None: 273 | """ 274 | Verify that the *IDT* archive contains a unique file type and set the 275 | used file type accordingly. 276 | """ 277 | 278 | file_types = set() 279 | for value in self.project_settings.data[ 280 | DirectoryStructure.COLOUR_CHECKER 281 | ].values(): 282 | for item in value: 283 | file_types.add(item.suffix) 284 | 285 | for item in self.project_settings.data[DirectoryStructure.GREY_CARD]: 286 | file_types.add(item.suffix) 287 | 288 | if len(file_types) > 1: 289 | msg = f'Multiple file types found in the project settings: "{file_types}"' 290 | raise ValueError(msg) 291 | 292 | if file_types: 293 | self.project_settings.file_type = next(iter(file_types)) 294 | 295 | def extract(self, archive: str, directory: str | None = None) -> str: 296 | """ 297 | Extract the *IDT* archive. 298 | 299 | Parameters 300 | ---------- 301 | archive 302 | Archive to extract. 303 | directory 304 | Directory to extract the archive to. 305 | 306 | Returns 307 | ------- 308 | :class:`str` 309 | Extracted directory. 310 | """ 311 | 312 | directory = aces.idt.core.common.extract_archive(archive, directory) 313 | extracted_directories = aces.idt.core.common.list_sub_directories(directory) 314 | root_directory = next(iter(extracted_directories)) 315 | 316 | json_files = list(root_directory.glob("*.json")) 317 | 318 | if len(json_files) > 1: 319 | msg = 'Multiple "JSON" files found in the root directory!' 320 | raise ValueError(msg) 321 | if len(json_files) == 1: 322 | json_file = next(iter(json_files)) 323 | LOGGER.info('Found explicit "%s" "IDT" project settings file.', json_file) 324 | self.project_settings = IDTProjectSettings.from_file(json_file) 325 | else: 326 | LOGGER.info('Assuming implicit "IDT" specification...') 327 | self.project_settings.camera_model = Path(archive).stem 328 | self._update_project_settings_from_implicit_directory_structure( 329 | root_directory 330 | ) 331 | 332 | self.project_settings.working_directory = root_directory 333 | 334 | return root_directory 335 | 336 | def process_archive(self, archive: str | None) -> IDTBaseGenerator: 337 | """ 338 | Compute the *IDT* either using given archive *zip* file path or the 339 | current *IDT* project settings if not given. 340 | 341 | Parameters 342 | ---------- 343 | archive 344 | Archive *zip* file path. 345 | 346 | Returns 347 | ------- 348 | :class:`IDTBaseGenerator` 349 | Instantiated *IDT* generator. 350 | """ 351 | 352 | if self.generator is None: 353 | exception = 'No "IDT" generator was set!' 354 | 355 | raise ValueError(exception) 356 | 357 | if archive is not None: 358 | self.project_settings.working_directory = self.extract(archive) 359 | 360 | # Enforcing exposure values as floating point numbers. 361 | for exposure in list( 362 | self.project_settings.data[DirectoryStructure.COLOUR_CHECKER].keys() 363 | ): 364 | images = [ 365 | image 366 | for image in self.project_settings.data[ 367 | DirectoryStructure.COLOUR_CHECKER 368 | ].pop(exposure) 369 | ] 370 | 371 | self.project_settings.data[DirectoryStructure.COLOUR_CHECKER][ 372 | float(exposure) 373 | ] = images 374 | 375 | return self.process() 376 | 377 | def process(self) -> IDTBaseGenerator: 378 | """ 379 | Run the *IDT* generator application process maintaining the execution steps. 380 | 381 | Returns 382 | ------- 383 | :class:`IDTBaseGenerator` 384 | Instantiated *IDT* generator. after the process has been run 385 | """ 386 | 387 | self.validate_project_settings() 388 | self.generator.sample() 389 | self.generator.sort() 390 | self.generator.remove_clipped_samples() 391 | self.generator.generate_LUT() 392 | self.generator.filter_LUT() 393 | self.generator.decode() 394 | self.generator.optimise() 395 | 396 | return self.generator 397 | 398 | def zip( 399 | self, output_directory: Path | str, archive_serialised_generator: bool = False 400 | ) -> Path: 401 | """ 402 | Create a *zip* file with the output of the *IDT* application process. 403 | 404 | Parameters 405 | ---------- 406 | output_directory : str 407 | Output directory for the *zip* file. 408 | archive_serialised_generator : bool 409 | Whether to serialise and archive the *IDT* generator. 410 | 411 | Returns 412 | ------- 413 | :class:`pathlib.Path` 414 | *Zip* file path. 415 | """ 416 | 417 | if not self.generator: 418 | exception = 'No "IDT" generator was set!' 419 | 420 | raise ValueError(exception) 421 | 422 | return self.generator.zip( 423 | output_directory, archive_serialised_generator=archive_serialised_generator 424 | ) 425 | 426 | def validate_project_settings(self) -> None: 427 | """Run validation checks on the project settings. 428 | 429 | Raises 430 | ------ 431 | ValueError 432 | If any of the validations fail 433 | """ 434 | 435 | # Check the aces_transform_id is a valid idt_urn and try and auto populate it 436 | if not is_valid_csc_urn(self.project_settings.aces_transform_id): 437 | # If the aces_transform_id is not valid, generate a new one 438 | new_name = generate_idt_urn( 439 | self.project_settings.aces_user_name, 440 | self.project_settings.encoding_colourspace, 441 | self.project_settings.encoding_transfer_function, 442 | 1, 443 | ) 444 | # Update the project settings with the new name, if this is still invalid 445 | # it will raise an error from the setter 446 | self.project_settings.aces_transform_id = new_name 447 | 448 | valid, errors = self.project_settings.validate() 449 | if not valid: 450 | error_string = "\n".join(errors) 451 | msg = f"Invalid project settings\n: {error_string}" 452 | raise ValueError(msg) 453 | 454 | # Verify the directory structure and file types 455 | self._verify_directory(self.project_settings.working_directory) 456 | self._verify_file_type() 457 | -------------------------------------------------------------------------------- /aces/idt/core/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | IDT Constants 3 | ============= 4 | 5 | Define the constants for the package. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | from typing import TYPE_CHECKING, ClassVar 11 | 12 | import colour 13 | 14 | from aces.idt.core.structures import Metadata 15 | 16 | if TYPE_CHECKING: 17 | from colour.hints import Tuple 18 | 19 | __author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" 20 | __copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" 21 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 22 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 23 | __email__ = "acessupport@oscars.org" 24 | __status__ = "Production" 25 | 26 | __all__ = [ 27 | "EXPOSURE_CLIPPING_THRESHOLD", 28 | "DirectoryStructure", 29 | "UITypes", 30 | "UICategories", 31 | "LUTSize", 32 | "RGBDisplayColourspace", 33 | "CAT", 34 | "OptimizationSpace", 35 | "Interpolators", 36 | "DecodingMethods", 37 | "ProjectSettingsMetadataConstants", 38 | ] 39 | 40 | EXPOSURE_CLIPPING_THRESHOLD: int = 2 / (2**10 - 1) 41 | """ 42 | The threshold for determining if RGB values are clipped. The threshold is 43 | 2 code values in a 10 bit system 44 | """ 45 | 46 | 47 | class DirectoryStructure: 48 | """Constants for the directory names which compose the data structure.""" 49 | 50 | DATA: ClassVar[str] = "data" 51 | COLOUR_CHECKER: ClassVar[str] = "colour_checker" 52 | GREY_CARD: ClassVar[str] = "grey_card" 53 | FLATFIELD: ClassVar[str] = "flatfield" 54 | WHITE: ClassVar[str] = "white" 55 | BLACK: ClassVar[str] = "black" 56 | 57 | 58 | class UITypes: 59 | """Constants for the UI categories.""" 60 | 61 | INT_FIELD: ClassVar[str] = "IntField" 62 | STRING_FIELD: ClassVar[str] = "StringField" 63 | OPTIONS_FIELD: ClassVar[str] = "OptionsField" 64 | VECTOR3_FIELD: ClassVar[str] = "Vector3Field" 65 | FOLDER_STRUCTURE: ClassVar[str] = "FOLDER_STRUCTURE" 66 | BOOLEAN_FIELD: ClassVar[str] = "BooleanField" 67 | ARRAY_FIELD: ClassVar[str] = "ArrayField" 68 | MAP_FIELD: ClassVar[str] = "MapField" 69 | 70 | 71 | class UICategories: 72 | """Constants for the UI categories.""" 73 | 74 | ADVANCED: ClassVar[str] = "Advanced" 75 | STANDARD: ClassVar[str] = "Standard" 76 | HIDDEN: ClassVar[str] = "HIDDEN" 77 | 78 | 79 | class LUTSize: 80 | """Constants for the LUT sizes.""" 81 | 82 | LUT_1024: ClassVar[int] = 1024 83 | LUT_2048: ClassVar[int] = 2048 84 | LUT_4096: ClassVar[int] = 4096 85 | LUT_8192: ClassVar[int] = 8192 86 | LUT_16384: ClassVar[int] = 16384 87 | LUT_32768: ClassVar[int] = 32768 88 | LUT_65536: ClassVar[int] = 65536 89 | DEFAULT: ClassVar[int] = LUT_1024 90 | ALL: ClassVar[tuple[int, ...]] = ( 91 | LUT_1024, 92 | LUT_2048, 93 | LUT_4096, 94 | LUT_8192, 95 | LUT_16384, 96 | LUT_32768, 97 | LUT_65536, 98 | ) 99 | 100 | 101 | class RGBDisplayColourspace: 102 | """Constants for the RGB display colourspaces.""" 103 | 104 | SRGB: ClassVar[str] = "sRGB" 105 | DCI_P3: ClassVar[str] = "Display P3" 106 | DEFAULT: ClassVar[str] = SRGB 107 | ALL: ClassVar[tuple[str, ...]] = (SRGB, DCI_P3) 108 | 109 | 110 | class CATMeta(type): 111 | @property 112 | def ALL(cls) -> Tuple[str, ...]: 113 | """ 114 | Return all the available chromatic adaptation transforms. 115 | 116 | Returns 117 | ------- 118 | :class:`tuple` 119 | Available chromatic adaptation transforms. 120 | """ 121 | 122 | return *sorted(colour.CHROMATIC_ADAPTATION_TRANSFORMS), "None" 123 | 124 | 125 | class CAT(metaclass=CATMeta): 126 | """Constants for the chromatic adaptation transforms.""" 127 | 128 | DEFAULT: ClassVar[str] = "CAT02" 129 | 130 | 131 | class OptimizationSpace: 132 | """Constants for the optimization spaces.""" 133 | 134 | OKLAB: ClassVar[str] = "Oklab" 135 | JZAZBZ: ClassVar[str] = "JzAzBz" 136 | IPT: ClassVar[str] = "IPT" 137 | CIE_LAB: ClassVar[str] = "CIE Lab" 138 | DEFAULT: ClassVar[str] = OKLAB 139 | ALL: ClassVar[tuple[str, ...]] = (OKLAB, JZAZBZ, IPT, CIE_LAB) 140 | 141 | 142 | class Interpolators: 143 | """Constants for the interpolators.""" 144 | 145 | CUBIC_SPLINE: ClassVar[str] = "Cubic Spline" 146 | LINEAR: ClassVar[str] = "Linear" 147 | PCHIP: ClassVar[str] = "PCHIP" 148 | SPRAGUE_1880: ClassVar[str] = "Sprague (1880)" 149 | DEFAULT: ClassVar[str] = LINEAR 150 | ALL: ClassVar[tuple[str, ...]] = (CUBIC_SPLINE, LINEAR, PCHIP, SPRAGUE_1880) 151 | 152 | 153 | class DecodingMethods: 154 | """Decoding methods.""" 155 | 156 | MEDIAN: ClassVar[str] = "Median" 157 | AVERAGE: ClassVar[str] = "Average" 158 | PER_CHANNEL: ClassVar[str] = "Per Channel" 159 | ACES: ClassVar[str] = "ACES" 160 | DEFAULT: ClassVar[str] = MEDIAN 161 | ALL: ClassVar[tuple[str, ...]] = (MEDIAN, AVERAGE, PER_CHANNEL, ACES) 162 | 163 | 164 | class ProjectSettingsMetadataConstants: 165 | """Constants for the project settings.""" 166 | 167 | SCHEMA_VERSION = Metadata( 168 | name="schema_version", 169 | default_value="0.1.0", 170 | description="The project settings schema version", 171 | display_name="Schema Version", 172 | ui_type=UITypes.STRING_FIELD, 173 | ui_category=UICategories.HIDDEN, 174 | ) 175 | 176 | CAMERA_MAKE = Metadata( 177 | name="camera_make", 178 | default_value="", 179 | description="The make of the camera used to capture the images", 180 | display_name="Camera Make", 181 | ui_type=UITypes.STRING_FIELD, 182 | ui_category=UICategories.STANDARD, 183 | ) 184 | 185 | CAMERA_MODEL = Metadata( 186 | name="camera_model", 187 | default_value="", 188 | description="The model of the camera used to capture the images", 189 | display_name="Camera Model", 190 | ui_type=UITypes.STRING_FIELD, 191 | ui_category=UICategories.STANDARD, 192 | ) 193 | 194 | RGB_DISPLAY_COLOURSPACE = Metadata( 195 | name="rgb_display_colourspace", 196 | default_value=RGBDisplayColourspace.DEFAULT, 197 | description="The RGB display colourspace", 198 | display_name="RGB Display Colourspace", 199 | ui_type=UITypes.OPTIONS_FIELD, 200 | options=RGBDisplayColourspace.ALL, 201 | ui_category=UICategories.ADVANCED, 202 | ) 203 | 204 | CAT = Metadata( 205 | name="cat", 206 | default_value=CAT.DEFAULT, 207 | description="The CAT", 208 | display_name="CAT", 209 | ui_type=UITypes.OPTIONS_FIELD, 210 | options=CAT.ALL, 211 | ui_category=UICategories.ADVANCED, 212 | ) 213 | 214 | OPTIMISATION_SPACE = Metadata( 215 | name="optimisation_space", 216 | default_value=OptimizationSpace.DEFAULT, 217 | description="The optimisation space", 218 | display_name="Optimisation Space", 219 | ui_type=UITypes.OPTIONS_FIELD, 220 | options=OptimizationSpace.ALL, 221 | ui_category=UICategories.ADVANCED, 222 | ) 223 | 224 | ILLUMINANT_INTERPOLATOR = Metadata( 225 | name="illuminant_interpolator", 226 | default_value=Interpolators.DEFAULT, 227 | description="The illuminant interpolator", 228 | display_name="Illuminant Interpolator", 229 | ui_type=UITypes.STRING_FIELD, 230 | options=Interpolators.ALL, 231 | ui_category=UICategories.ADVANCED, 232 | ) 233 | 234 | DECODING_METHOD = Metadata( 235 | name="decoding_method", 236 | default_value=DecodingMethods.DEFAULT, 237 | description="The decoding method", 238 | display_name="Decoding Method", 239 | ui_type=UITypes.STRING_FIELD, 240 | options=DecodingMethods.ALL, 241 | ui_category=UICategories.ADVANCED, 242 | ) 243 | 244 | EV_RANGE = Metadata( 245 | name="ev_range", 246 | default_value=[-1.0, 0.0, 1.0], 247 | description="The EV range", 248 | display_name="EV Range", 249 | ui_type=UITypes.ARRAY_FIELD, 250 | ui_category=UICategories.ADVANCED, 251 | ) 252 | 253 | GREY_CARD_REFERENCE = Metadata( 254 | name="grey_card_reference", 255 | default_value=[0.18, 0.18, 0.18], 256 | description="The grey card reference", 257 | display_name="Grey Card Reference", 258 | ui_type=UITypes.VECTOR3_FIELD, 259 | ui_category=UICategories.ADVANCED, 260 | ) 261 | 262 | LUT_SIZE = Metadata( 263 | name="lut_size", 264 | default_value=LUTSize.DEFAULT, 265 | description="The LUT size", 266 | display_name="LUT Size", 267 | ui_type=UITypes.OPTIONS_FIELD, 268 | options=LUTSize.ALL, 269 | ui_category=UICategories.STANDARD, 270 | ) 271 | 272 | LUT_SMOOTHING = Metadata( 273 | name="lut_smoothing", 274 | default_value=16, 275 | description="The LUT smoothing", 276 | display_name="LUT Smoothing", 277 | ui_type=UITypes.INT_FIELD, 278 | ui_category=UICategories.STANDARD, 279 | ) 280 | 281 | ACES_TRANSFORM_ID = Metadata( 282 | name="aces_transform_id", 283 | default_value="", 284 | description="The ACES transform ID", 285 | display_name="ACES Transform ID", 286 | ui_type=UITypes.STRING_FIELD, 287 | ui_category=UICategories.STANDARD, 288 | ) 289 | 290 | ACES_USER_NAME = Metadata( 291 | name="aces_user_name", 292 | default_value="", 293 | description="The ACES username", 294 | display_name="ACES Username", 295 | ui_type=UITypes.STRING_FIELD, 296 | ui_category=UICategories.STANDARD, 297 | ) 298 | 299 | ISO = Metadata( 300 | name="iso", 301 | default_value=800, 302 | description="The ISO", 303 | display_name="ISO", 304 | ui_type=UITypes.STRING_FIELD, 305 | ui_category=UICategories.STANDARD, 306 | ) 307 | 308 | TEMPERATURE = Metadata( 309 | name="temperature", 310 | default_value=6000, 311 | description="The temperature", 312 | display_name="Temperature", 313 | ui_type=UITypes.INT_FIELD, 314 | ui_category=UICategories.STANDARD, 315 | ) 316 | 317 | ADDITIONAL_CAMERA_SETTINGS = Metadata( 318 | name="additional_camera_settings", 319 | default_value="", 320 | description="The additional camera settings", 321 | display_name="Additional Camera Settings", 322 | ui_type=UITypes.STRING_FIELD, 323 | ui_category=UICategories.STANDARD, 324 | ) 325 | 326 | LIGHTING_SETUP_DESCRIPTION = Metadata( 327 | name="lighting_setup_description", 328 | default_value="", 329 | description="The lighting setup description", 330 | display_name="Lighting Setup Description", 331 | ui_type=UITypes.STRING_FIELD, 332 | ui_category=UICategories.STANDARD, 333 | ) 334 | 335 | DEBAYERING_PLATFORM = Metadata( 336 | name="debayering_platform", 337 | default_value="", 338 | description="The debayering platform", 339 | display_name="Debayering Platform", 340 | ui_type=UITypes.STRING_FIELD, 341 | ui_category=UICategories.STANDARD, 342 | ) 343 | 344 | DEBAYERING_SETTINGS = Metadata( 345 | name="debayering_settings", 346 | default_value="", 347 | description="The debayering settings", 348 | display_name="Debayering Settings", 349 | ui_type=UITypes.STRING_FIELD, 350 | ui_category=UICategories.STANDARD, 351 | ) 352 | 353 | ENCODING_COLOUR_SPACE = Metadata( 354 | name="encoding_colourspace", 355 | default_value="", 356 | description="The encoding colourspace", 357 | display_name="Encoding Colourspace", 358 | ui_type=UITypes.STRING_FIELD, 359 | ui_category=UICategories.STANDARD, 360 | ) 361 | 362 | ENCODING_TRANSFER_FUNCTION = Metadata( 363 | name="encoding_transfer_function", 364 | default_value="", 365 | description="The encoding transfer function", 366 | display_name="Encoding Transfer Function", 367 | ui_type=UITypes.STRING_FIELD, 368 | ui_category=UICategories.STANDARD, 369 | ) 370 | 371 | DATA = Metadata( 372 | name="data", 373 | default_value={ 374 | DirectoryStructure.COLOUR_CHECKER: {}, 375 | DirectoryStructure.GREY_CARD: {}, 376 | }, 377 | description="The folder structure for the ", 378 | display_name="Folder Structure", 379 | ui_type=UITypes.FOLDER_STRUCTURE, 380 | serialize_group="", 381 | ui_category=UICategories.HIDDEN, 382 | ) 383 | 384 | WORKING_DIR = Metadata( 385 | name="working_directory", 386 | default_value="", 387 | description="The file path to the working directory", 388 | display_name="Working Directory", 389 | ui_type=UITypes.STRING_FIELD, 390 | ui_category=UICategories.HIDDEN, 391 | ) 392 | 393 | CLEAN_UP = Metadata( 394 | name="cleanup", 395 | default_value=True, 396 | description="Do we want to cleanup the working directory after we finish?", 397 | display_name="Cleanup", 398 | ui_type=UITypes.BOOLEAN_FIELD, 399 | ui_category=UICategories.HIDDEN, 400 | ) 401 | 402 | REFERENCE_COLOUR_CHECKER = Metadata( 403 | name="reference_colour_checker", 404 | default_value="ISO 17321-1", 405 | description="The reference colour checker we want to use", 406 | ui_type=UITypes.OPTIONS_FIELD, 407 | options=sorted(colour.SDS_COLOURCHECKERS), 408 | ui_category=UICategories.ADVANCED, 409 | ) 410 | 411 | ILLUMINANT = Metadata( 412 | name="illuminant", 413 | default_value="D60", 414 | description="The illuminant we want to use for the reference colour checker", 415 | ui_type=UITypes.OPTIONS_FIELD, 416 | options=["Custom", "Daylight", "Blackbody", *sorted(colour.SDS_ILLUMINANTS)], 417 | ui_category=UICategories.ADVANCED, 418 | ) 419 | 420 | FILE_TYPE = Metadata( 421 | name="file_type", 422 | default_value="", 423 | description="The file type of the recorded footage is detected from " 424 | "the archive", 425 | display_name="File Type", 426 | ui_type=UITypes.STRING_FIELD, 427 | ui_category=UICategories.STANDARD, 428 | ) 429 | 430 | EV_WEIGHTS = Metadata( 431 | name="ev_weights", 432 | default_value=[], 433 | description="Normalised weights used to sum the exposure values. If not given," 434 | "the median of the exposure values is used.", 435 | display_name="EV Weights", 436 | ui_type=UITypes.ARRAY_FIELD, 437 | ui_category=UICategories.HIDDEN, 438 | ) 439 | 440 | OPTIMIZATION_KWARGS = Metadata( 441 | name="optimization_kwargs", 442 | default_value={}, 443 | description="Parameters for the optimization function scipy.optimize.minimize", 444 | display_name="Optimization Kwargs", 445 | ui_type=UITypes.MAP_FIELD, 446 | ui_category=UICategories.HIDDEN, 447 | ) 448 | 449 | INCLUDE_WHITE_BALANCE_IN_CLF = Metadata( 450 | name="include_white_balance_in_clf", 451 | default_value=False, 452 | description="Whether to include the White Balance Matrix in the CLF", 453 | display_name="Include White Balance in CLF", 454 | ui_type=UITypes.BOOLEAN_FIELD, 455 | ui_category=UICategories.STANDARD, 456 | ) 457 | 458 | FLATTEN_CLF = Metadata( 459 | name="flatten_clf", 460 | default_value=False, 461 | description="Whether to flatten the CLF to a 1D Lut and a single 3x3 Matrix ", 462 | display_name="Flatten CLF", 463 | ui_type=UITypes.BOOLEAN_FIELD, 464 | ui_category=UICategories.STANDARD, 465 | ) 466 | 467 | INCLUDE_EXPOSURE_FACTOR_IN_CLF = Metadata( 468 | name="include_exposure_factor_in_clf", 469 | default_value=False, 470 | description="Whether to include the exposure factor (K) in the CLF", 471 | display_name="Include Exposure Factor in CLF", 472 | ui_type=UITypes.BOOLEAN_FIELD, 473 | ui_category=UICategories.STANDARD, 474 | ) 475 | 476 | ALL: ClassVar[tuple[Metadata, ...]] = ( 477 | SCHEMA_VERSION, 478 | CAMERA_MAKE, 479 | CAMERA_MODEL, 480 | RGB_DISPLAY_COLOURSPACE, 481 | CAT, 482 | OPTIMISATION_SPACE, 483 | ILLUMINANT_INTERPOLATOR, 484 | DECODING_METHOD, 485 | EV_RANGE, 486 | GREY_CARD_REFERENCE, 487 | LUT_SIZE, 488 | LUT_SMOOTHING, 489 | ACES_TRANSFORM_ID, 490 | ACES_USER_NAME, 491 | ISO, 492 | TEMPERATURE, 493 | ADDITIONAL_CAMERA_SETTINGS, 494 | LIGHTING_SETUP_DESCRIPTION, 495 | DEBAYERING_PLATFORM, 496 | DEBAYERING_SETTINGS, 497 | ENCODING_COLOUR_SPACE, 498 | ENCODING_TRANSFER_FUNCTION, 499 | DATA, 500 | WORKING_DIR, 501 | CLEAN_UP, 502 | REFERENCE_COLOUR_CHECKER, 503 | ILLUMINANT, 504 | FILE_TYPE, 505 | EV_WEIGHTS, 506 | OPTIMIZATION_KWARGS, 507 | INCLUDE_WHITE_BALANCE_IN_CLF, 508 | FLATTEN_CLF, 509 | INCLUDE_EXPOSURE_FACTOR_IN_CLF, 510 | ) 511 | -------------------------------------------------------------------------------- /tests/resources/samples_decoded_clipped.json: -------------------------------------------------------------------------------- 1 | { 2 | "-6.0": [ 3 | [0.002498546404212147, 0.0016477920147365854, 0.0010044248865331543], 4 | [0.008911570383922743, 0.006169510408853207, 0.0045314422047628205], 5 | [0.0036516351594782755, 0.0041776667990037234, 0.006643214319053231], 6 | [0.0020572131054848236, 0.002562298217975996, 0.001045830741232389], 7 | [0.004735072268886539, 0.0044998240354448115, 0.007672268768205673], 8 | [0.0060890792358761665, 0.008861616625472928, 0.008076015519727076], 9 | [0.009037072850171095, 0.0039367128539136145, 0.00042007778310786266], 10 | [0.002031692299873817, 0.0023072340103321605, 0.007009460532160188], 11 | [0.006904333866917616, 0.002285523578919, 0.002093086061640068], 12 | [0.0017620150545975036, 0.001154080122701255, 0.002798621111393629], 13 | [0.007409502296852151, 0.008607967471024174, 0.0017640564498647602], 14 | [0.010567416353790476, 0.00654184386307819, 0.0008149025600424415], 15 | [0.0010063768777266047, 0.0011697586070470866, 0.005715036414566028], 16 | [0.002525155829847688, 0.00503340523425445, 0.0014478854238843578], 17 | [0.004912170116787667, 0.0012188503859212117, 0.0006391091693449246], 18 | [0.012328533467396608, 0.00947365899614331, 0.0004290960043672201], 19 | [0.0064100482718736276, 0.002499540353982446, 0.004939764556245714], 20 | [0.0026638192756318108, 0.004770674256921357, 0.007548334982427654], 21 | [0.01690625779100993, 0.015468789398729614, 0.015335924099799184], 22 | [0.011256011036397067, 0.010512107711069163, 0.010693637858258836], 23 | [0.007038137325359838, 0.006746155891644374, 0.006780920536403074], 24 | [0.0034846415614320668, 0.0035704912966385703, 0.0035349151544656434], 25 | [0.0016944339134635996, 0.001696060333251578, 0.0016413565957254007], 26 | [0.0007282604576323647, 0.0007194575488649345, 0.0006784631755433492] 27 | ], 28 | "-5.0": [ 29 | [0.004834720739781258, 0.0032176173705157052, 0.001975662442912159], 30 | [0.016794806097274247, 0.011647454898095723, 0.008599656475555151], 31 | [0.006978870882256193, 0.007951411201435027, 0.012560456068104743], 32 | [0.0040316646985643365, 0.004977203875510408, 0.0020450629450372237], 33 | [0.008992833372054384, 0.008551613665778689, 0.014476000135479615], 34 | [0.011496162900878001, 0.016647908909643867, 0.015205085387521373], 35 | [0.016989398309668155, 0.007504532983525391, 0.0008178675058722512], 36 | [0.00399847974235013, 0.004503928764894301, 0.013241846780081315], 37 | [0.013074231751257357, 0.004452339372664309, 0.004088176946781641], 38 | [0.00343742590178105, 0.0022673702762047174, 0.005421348651941208], 39 | [0.013976308315944451, 0.016190244547870335, 0.0034536832820105698], 40 | [0.020052683323870363, 0.012339384959364838, 0.0015831562876793073], 41 | [0.001979717948029381, 0.002286338711335086, 0.010776469082077878], 42 | [0.004931090881416064, 0.00950796030408887, 0.0028424536894021196], 43 | [0.009299954351047243, 0.002387283111046817, 0.001253581889327268], 44 | [0.02378971081072381, 0.017825650581198425, 0.0008167902438709712], 45 | [0.012080586917113667, 0.004846007702437635, 0.009337025475118972], 46 | [0.0051791687046983306, 0.009045787154499713, 0.014230305794358411], 47 | [0.03358107930282197, 0.030709639129483, 0.030484186510590913], 48 | [0.021533584480035863, 0.01989264589229983, 0.020260411885175703], 49 | [0.013292172963037412, 0.01273666039089069, 0.012813766559206045], 50 | [0.006708657997736823, 0.006839348764104179, 0.006781917544167526], 51 | [0.0033274233335913246, 0.0033130444495437384, 0.003217312206230095], 52 | [0.0014232428654160484, 0.0014124611795037335, 0.001323698797822704] 53 | ], 54 | "-4.0": [ 55 | [0.009187230996063505, 0.00621468304299186, 0.0038539120193917596], 56 | [0.03331627501114935, 0.022306011031950963, 0.01618319683320598], 57 | [0.013193436108258069, 0.01500249360963156, 0.024332155410204944], 58 | [0.007686875966319132, 0.00942862656535331, 0.003992317797566476], 59 | [0.016898172834521438, 0.016035596554821244, 0.028686814349823942], 60 | [0.021952483934238407, 0.03311269405562953, 0.030214366252327603], 61 | [0.03380882038132193, 0.014141307521440402, 0.0015971194081775776], 62 | [0.007592208432766464, 0.008533224825719195, 0.025869980716662997], 63 | [0.025486329131547664, 0.008485471950393144, 0.007795220122575236], 64 | [0.006624413337107606, 0.004421764171195264, 0.01024934735847277], 65 | [0.027555195073928597, 0.03220168385951899, 0.006638453928245434], 66 | [0.039235328391793625, 0.023811356654889166, 0.0031099757349442453], 67 | [0.003831508058369508, 0.004444884893690464, 0.020449813552438936], 68 | [0.009287933009188424, 0.017915598633752357, 0.005499758352103195], 69 | [0.017523677987134806, 0.004634315000839206, 0.002458228340603342], 70 | [0.04661998289484394, 0.03529766968045813, 0.001602857302979256], 71 | [0.023294756814370418, 0.009198165167503394, 0.017565827671268353], 72 | [0.009766480684606883, 0.017030848080612074, 0.028154419442815392], 73 | [0.0670516283415754, 0.06104247539317357, 0.060555351480649944], 74 | [0.041988142964021, 0.039058671636289094, 0.03971533165573615], 75 | [0.025982027726452885, 0.02474762688176996, 0.024901881770491727], 76 | [0.012672005504613467, 0.012907075129842837, 0.01279922789936977], 77 | [0.006390496961612266, 0.006385273075106081, 0.006195593769279981], 78 | [0.002793415525731211, 0.002752942056237252, 0.002613364203095335] 79 | ], 80 | "-3.0": [ 81 | [0.017224401951673324, 0.011721631875514133, 0.007370676896301668], 82 | [0.0666791529939935, 0.04358378003622004, 0.03216471267682369], 83 | [0.025775664664637456, 0.02978100014183188, 0.04773257431536362], 84 | [0.014512397184218255, 0.017766179853000177, 0.007620681263155147], 85 | [0.033583805487882125, 0.03202703074973756, 0.05684217391318212], 86 | [0.04294597415858847, 0.06611171241106845, 0.059991165778253676], 87 | [0.06744546772981592, 0.027991527396099943, 0.0031190266082389644], 88 | [0.01439142072825915, 0.016081667884994908, 0.05110036079171981], 89 | [0.05028172257179416, 0.015924929636840994, 0.014719359786112435], 90 | [0.012473000339090608, 0.008408601771871433, 0.019380747448084875], 91 | [0.05451347872036305, 0.06419257651413664, 0.012518974468859818], 92 | [0.07772468126002788, 0.04665030683414729, 0.005983471484634952], 93 | [0.007375640807379034, 0.008445174840659354, 0.04009658585534782], 94 | [0.01749835930881373, 0.03550451628334628, 0.010416801556610497], 95 | [0.03473661452757798, 0.008788569802752423, 0.004772445272795733], 96 | [0.09028751148645944, 0.07057783583647614, 0.0031292229818628853], 97 | [0.04554207547417231, 0.01729963644679842, 0.03487453132071194], 98 | [0.018483283336812237, 0.03381059385538263, 0.055721569100518474], 99 | [0.13236697354639956, 0.12036864779690723, 0.11926788543698219], 100 | [0.0823158637195761, 0.0775796602627996, 0.07859182580795326], 101 | [0.05129625353484129, 0.048588229674393475, 0.04895643294279501], 102 | [0.024613006542910126, 0.02518334210373417, 0.024940253814247314], 103 | [0.012098226505628197, 0.012086335144463015, 0.011734287448897896], 104 | [0.005388933313834155, 0.005354132222623242, 0.005064500726027709] 105 | ], 106 | "-2.0": [ 107 | [0.03739326883769043, 0.02401051833828731, 0.01548824912299018], 108 | [0.13714315773818742, 0.08701275490669602, 0.0681650825752154], 109 | [0.0553634808104459, 0.06118981125754064, 0.09588760418064977], 110 | [0.03183222033593438, 0.03704483967284179, 0.015803883611526386], 111 | [0.07134460232939689, 0.06514165253915376, 0.11415337697076497], 112 | [0.08780638573290275, 0.13376789512703233, 0.12080941622225345], 113 | [0.1399351439975967, 0.05742814744176034, 0.008374001032302151], 114 | [0.031273160253392766, 0.03381027294102879, 0.1030321906509465], 115 | [0.1046439865766833, 0.03368777655412127, 0.0311141962758709], 116 | [0.027189877750127704, 0.017032363616943224, 0.04043920417874574], 117 | [0.11257117090726032, 0.13009656618753127, 0.027104528208105133], 118 | [0.15897843913912554, 0.09227483296990241, 0.013169266514433927], 119 | [0.01484667842657892, 0.01715227690149539, 0.08227384478886927], 120 | [0.03843370994359156, 0.0730596570045888, 0.02199971557239273], 121 | [0.07328510448853104, 0.017956502423351433, 0.010398451791145747], 122 | [0.18116518276360205, 0.1422319015025458, 0.008437120085430573], 123 | [0.09492243953155109, 0.036363034444671934, 0.07280195170570504], 124 | [0.03919015735342632, 0.06909444460761217, 0.11184657744913838], 125 | [0.2629652789976229, 0.24050507552913047, 0.238071029306617], 126 | [0.16694693601144148, 0.1560951730572941, 0.1574497075745654], 127 | [0.10641253026703659, 0.09622991680518983, 0.0990941905879202], 128 | [0.05400541894371781, 0.05175838658586673, 0.05211532254790512], 129 | [0.02581922316208317, 0.025123477033540297, 0.024898189116875507], 130 | [0.010833722600037806, 0.011224857776451328, 0.01082193432780685] 131 | ], 132 | "-1.0": [ 133 | [0.07813146381710186, 0.0496938888772263, 0.03358617407150692], 134 | [0.27202296395729986, 0.1741636738995086, 0.13537704730186279], 135 | [0.11442468941097071, 0.12429478999091753, 0.19099612766688248], 136 | [0.06682238954802304, 0.07493621412556557, 0.03351891975920499], 137 | [0.14601045144037983, 0.13214787683355475, 0.2285597507936125], 138 | [0.1775082139393421, 0.26514176187849126, 0.23885636353267195], 139 | [0.27636838885196363, 0.11556569251510175, 0.018593821354599784], 140 | [0.06544133320629598, 0.06875160928111783, 0.20456498993443512], 141 | [0.20826030088691258, 0.06889711503743638, 0.06474668838101169], 142 | [0.0578024407076234, 0.03534592854303722, 0.0824091263736806], 143 | [0.2257062364722763, 0.25725765761359537, 0.05527939606680326], 144 | [0.3153863276754989, 0.18367894880168534, 0.028849007444762167], 145 | [0.03230573231397679, 0.036023985841193415, 0.1644853445744477], 146 | [0.08104880290990142, 0.1470730589537389, 0.046399823565373505], 147 | [0.1499152030315855, 0.037225682411089416, 0.021634139548093137], 148 | [0.36148893491059675, 0.28137145293310983, 0.01815305280624757], 149 | [0.1922045053192553, 0.07400335914503876, 0.14607154492301597], 150 | [0.08094895389309349, 0.14026253989999532, 0.22372965909180145], 151 | [0.5237526889526803, 0.47749190047833884, 0.4708858217388992], 152 | [0.3328436928909384, 0.31008695041258083, 0.31396548253005835], 153 | [0.2154736962183704, 0.19310951999072187, 0.19798075897360626], 154 | [0.11391512413949637, 0.10320110751812535, 0.10547602287809275], 155 | [0.05460149934798439, 0.05129572694914356, 0.052345266303375784], 156 | [0.023460869608149134, 0.022980534618648702, 0.022949152237538767] 157 | ], 158 | "0.0": [ 159 | [0.1585382565823435, 0.098786614015026, 0.06967821929087936], 160 | [0.5426758529762187, 0.3483976628132238, 0.27014591128056437], 161 | [0.22924692944983416, 0.24688810234159603, 0.3826505314053963], 162 | [0.13812175762726683, 0.15035796662923295, 0.06923282441523824], 163 | [0.2888369649005149, 0.2606041490525217, 0.4531080067421316], 164 | [0.35317266303089545, 0.5291718830489277, 0.47332291337493615], 165 | [0.5531953813303669, 0.23066183012839808, 0.039272565816879025], 166 | [0.13528256410535505, 0.13899681310657716, 0.4101693370618388], 167 | [0.41740161457768, 0.13917094363854593, 0.1304579062017038], 168 | [0.12006180791580616, 0.07232937003521242, 0.16427539885570216], 169 | [0.44689515830689003, 0.5124819021826073, 0.11103139884382521], 170 | [0.6370530378497814, 0.3675277071468262, 0.05795889587129264], 171 | [0.0690741157633648, 0.07374030441498722, 0.33047063351868333], 172 | [0.1646917395835552, 0.2913358241879858, 0.0914361871256672], 173 | [0.29676346285032623, 0.07579213200664685, 0.04549495143252176], 174 | [0.7329199048375514, 0.5631555956040117, 0.03655940974292516], 175 | [0.38425627776981164, 0.14917064661406962, 0.28945583514200723], 176 | [0.1643046054447545, 0.2778818282494649, 0.44246723548075306], 177 | [1.0515836457164347, 0.9509588267354303, 0.9503199105526913], 178 | [0.6746164036406402, 0.6247851774334429, 0.6315022777935951], 179 | [0.42764682077022026, 0.3860433742853428, 0.39515596157110766], 180 | [0.22939191029766523, 0.2060109658401446, 0.21150472478193771], 181 | [0.11279936478966596, 0.1019931554248042, 0.1061479950844306], 182 | [0.04970656041076801, 0.0470092917769172, 0.04740232175157989] 183 | ], 184 | "1.0": [ 185 | [0.3151111198121781, 0.19662095267691201, 0.1403895203801642], 186 | [1.0938582807540225, 0.7011368134588793, 0.535671658274985], 187 | [0.45396422007660125, 0.48888649010054586, 0.7720902987590008], 188 | [0.27240190344825843, 0.2974364839188309, 0.13799573072668583], 189 | [0.5756313696413023, 0.5184672510257264, 0.9083282982426044], 190 | [0.7134217129516863, 1.0547833863088611, 0.95088728236065], 191 | [1.110218304755822, 0.45811074194313345, 0.07865486742553197], 192 | [0.267028887347881, 0.27380073639798785, 0.8277315004694497], 193 | [0.8363091598522271, 0.2747103781025603, 0.25775812528298175], 194 | [0.23746689044490885, 0.1448672822969057, 0.3271289521562944], 195 | [0.8964843636952773, 1.021949067345687, 0.2255756754217012], 196 | [1.2990191832997335, 0.7379588825500262, 0.11840495500472334], 197 | [0.14207803471443714, 0.1478922081112807, 0.6662810601570659], 198 | [0.3290813985145632, 0.5831058708500956, 0.1812137013248979], 199 | [0.594418974786406, 0.15229157630360812, 0.09052916233047972], 200 | [1.5126334271560935, 1.1288619198890126, 0.07722119906344319], 201 | [0.7774998695581566, 0.29509437088265267, 0.5769058535225909], 202 | [0.32769431070142035, 0.5534575185628235, 0.8903147074236535], 203 | [2.1556056322410915, 1.941965162913597, 1.9296171593410893], 204 | [1.3810026425908088, 1.2624489272048944, 1.2801895978674078], 205 | [0.8589960894812009, 0.7735790162707133, 0.7977146953452732], 206 | [0.45397467709353095, 0.4096603291442576, 0.4196984003805231], 207 | [0.22480054352649795, 0.20315106892890827, 0.21082456390952997], 208 | [0.10326831711256268, 0.0930390904322239, 0.09470299450688684] 209 | ], 210 | "2.0": [ 211 | [0.632569295667089, 0.39094734872583303, 0.2778513347517395], 212 | [2.2470341900502357, 1.431008432801875, 1.0771080935149637], 213 | [0.9038503211112461, 0.9709307017662568, 1.5684219816825768], 214 | [0.5412450909182756, 0.5945212039238169, 0.2719142277352166], 215 | [1.160344029936481, 1.0335669394722964, 1.838344570091172], 216 | [1.459937932239992, 2.154302574863248, 1.9257119236810336], 217 | [2.2815032443772134, 0.9070318205790063, 0.15986081721526033], 218 | [0.5307502884191863, 0.5456919766916907, 1.6742148275743696], 219 | [1.713056455803151, 0.5467899255970256, 0.5088759557541876], 220 | [0.473100586572993, 0.2868849754052012, 0.6587493918691989], 221 | [1.8326139060744944, 2.08344395539501, 0.446671844924055], 222 | [2.676860384074591, 1.5110621594664688, 0.23351310317758917], 223 | [0.28055036858759047, 0.29215756506537877, 1.351562252997876], 224 | [0.664018551225907, 1.1689360706241174, 0.36233617526187306], 225 | [1.2050451471416066, 0.3009949905296958, 0.1801707332179312], 226 | [3.1005566620816674, 2.303398012111675, 0.1567327302172538], 227 | [1.5939722023479501, 0.5896982548932175, 1.1658638919745377], 228 | [0.6597747084956697, 1.1086536553527653, 1.7964367471864038], 229 | [4.44816196989775, 4.015514865473449, 4.073275312259032], 230 | [2.8427797574381986, 2.5849353932363783, 2.6459018626115434], 231 | [1.7554774330995984, 1.584722811992709, 1.6262739737556133], 232 | [0.9076245661054637, 0.8132250342952392, 0.8440512071737258], 233 | [0.4453627167877832, 0.403546809137972, 0.4167992953512169], 234 | [0.20659052321802246, 0.18498803601543334, 0.18762716437410581] 235 | ], 236 | "3.0": [ 237 | [1.284019732359144, 0.7795943441492648, 0.5532558894083602], 238 | [4.665373458669481, 3.3057455552632113, 2.4580700646905678], 239 | [1.8482397386277374, 1.9793067640595836, 3.2528839140201713], 240 | [1.083348537253376, 1.1959606388207635, 0.5413476006549806], 241 | [2.393676658428199, 2.1070850315774448, 3.7924982647386507], 242 | [3.0234447285229584, 4.329375182764362, 3.9885521791788903], 243 | [4.7185787456288475, 2.225488846413893, 0.38109508206582404], 244 | [1.0649457007035825, 1.088448213323765, 3.4632966469753206], 245 | [3.512941670099139, 1.0914130078834616, 1.024525360912197], 246 | [0.9454034601596497, 0.5713572299697485, 1.3353908342712366], 247 | [3.741746931202276, 4.20023485968049, 0.9052028172534226], 248 | [4.796628930203719, 4.317691912516759, 0.6385957769557268], 249 | [0.5574770586944289, 0.5842003529818265, 2.7898373463997146], 250 | [1.3618369190357515, 2.397618877394807, 0.7383686862838997], 251 | [2.471668610666213, 0.6019651648982096, 0.35665158641315886], 252 | [4.796628930203719, 4.796628930203719, 0.5877450527362562], 253 | [3.2704459507362973, 1.1805551440986684, 2.370612860946834], 254 | [1.3483752391539114, 2.2687212780211508, 3.7132654928947053], 255 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 256 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 257 | [3.5963149433224877, 3.2285175039403766, 3.368341626082841], 258 | [1.8567825932803401, 1.6695220891568103, 1.7105772525588374], 259 | [0.887455731528, 0.8013620190693296, 0.838749286455441], 260 | [0.40649460039992724, 0.3671565136925113, 0.3734574055109697] 261 | ], 262 | "4.0": [ 263 | [2.6139844504146317, 1.5756695627276602, 1.0994713697030658], 264 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 265 | [3.7614995999367036, 4.030473862888493, 4.796628930203719], 266 | [2.1876419000047234, 2.423614731747236, 1.0733955285786847], 267 | [4.796628930203719, 4.296246129992809, 4.796628930203719], 268 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 269 | [4.796628930203719, 4.54498458517612, 0.7570248206848192], 270 | [2.1483957605276487, 2.199961687075433, 4.796628930203719], 271 | [4.796628930203719, 2.2048742099833767, 2.0679263584845033], 272 | [1.9013661773903816, 1.1366284577638486, 2.7207780658562037], 273 | [4.796628930203719, 4.796628930203719, 1.8222143313015975], 274 | [4.796628930203719, 4.796628930203719, 1.2854116877047985], 275 | [1.1077847226808932, 1.1661572950865537, 4.796628930203719], 276 | [2.7755691240144333, 4.796628930203719, 1.4950375311096233], 277 | [4.796628930203719, 1.203943159123332, 0.7104173275753946], 278 | [4.796628930203719, 4.796628930203719, 1.178089969581148], 279 | [4.796628930203719, 2.3920011022152967, 4.793124757101484], 280 | [2.7474071826065023, 4.629592565720676, 4.796628930203719], 281 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 282 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 283 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 284 | [3.780798347681886, 3.3974102337729994, 3.480853906942855], 285 | [1.784021434422313, 1.6163828602444077, 1.687119943103371], 286 | [0.8020197851660065, 0.7289862184416773, 0.7411388440139616] 287 | ], 288 | "5.0": [ 289 | [4.796628930203719, 3.20230220432653, 2.2144544521856138], 290 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 291 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 292 | [4.450798075433819, 4.796628930203719, 2.1613794860829842], 293 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 294 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 295 | [4.796628930203719, 4.796628930203719, 1.5269013578233233], 296 | [4.365449440832366, 4.471505706092728, 4.796628930203719], 297 | [4.796628930203719, 4.477274051374675, 4.19482936023124], 298 | [3.8590502813255467, 2.29390097088404, 4.796628930203719], 299 | [4.796628930203719, 4.796628930203719, 3.701443414773767], 300 | [4.796628930203719, 4.796628930203719, 2.607764220898714], 301 | [2.2323583071427944, 2.352237704882396, 4.796628930203719], 302 | [4.796628930203719, 4.796628930203719, 3.0443121008753296], 303 | [4.796628930203719, 2.434639181471476, 1.4330715416966446], 304 | [4.796628930203719, 4.796628930203719, 2.3802758042113], 305 | [4.796628930203719, 4.7941937384793984, 4.796628930203719], 306 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 307 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 308 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 309 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 310 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 311 | [3.6198078826430518, 3.2816643896592415, 3.4260286865012857], 312 | [1.6145118366637616, 1.47070776107374, 1.4947046998275688] 313 | ], 314 | "6.0": [ 315 | [4.796628930203719, 4.796628930203719, 4.50424493947026], 316 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 317 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 318 | [4.796628930203719, 4.796628930203719, 4.399115039687762], 319 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 320 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 321 | [4.796628930203719, 4.796628930203719, 3.1052613480493627], 322 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 323 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 324 | [4.796628930203719, 4.661800433241723, 4.796628930203719], 325 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 326 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 327 | [4.5375097855456445, 4.763367450586476, 4.796628930203719], 328 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 329 | [4.796628930203719, 4.796628930203719, 2.916432592499058], 330 | [4.796628930203719, 4.796628930203719, 4.79119363789683], 331 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 332 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 333 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 334 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 335 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 336 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 337 | [4.796628930203719, 4.796628930203719, 4.796628930203719], 338 | [3.281091518259575, 2.997191094781939, 3.0452067509849576] 339 | ] 340 | } 341 | -------------------------------------------------------------------------------- /apps/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common Apps Utilities 3 | ===================== 4 | """ 5 | 6 | import xml.etree.ElementTree as ET 7 | 8 | import colour 9 | import colour_checker_detection # noqa: F401 10 | import colour_datasets 11 | import numpy as np 12 | from colour import ( 13 | CubicSplineInterpolator, 14 | LinearInterpolator, 15 | PchipInterpolator, 16 | SpragueInterpolator, 17 | ) 18 | from dash_bootstrap_components import ( 19 | Card, 20 | CardBody, 21 | CardHeader, 22 | ) 23 | from dash_bootstrap_components import Input as Field 24 | from dash_bootstrap_components import ( 25 | InputGroup, 26 | InputGroupText, 27 | Tooltip, 28 | ) 29 | 30 | from aces.idt import IDTProjectSettings, clf_processing_elements 31 | 32 | __author__ = "Alex Forsythe, Gayle McAdams, Thomas Mansencal, Nick Shaw" 33 | __copyright__ = "Copyright 2020 Academy of Motion Picture Arts and Sciences" 34 | __license__ = "Academy of Motion Picture Arts and Sciences License Terms" 35 | __maintainer__ = "Academy of Motion Picture Arts and Sciences" 36 | __email__ = "acessupport@oscars.org" 37 | __status__ = "Production" 38 | 39 | __all__ = [ 40 | "COLOUR_ENVIRONMENT", 41 | "metadata_card_default", 42 | "DATATABLE_DECIMALS", 43 | "CUSTOM_WAVELENGTHS", 44 | "DATASET_RAW_TO_ACES", 45 | "TRAINING_DATA_KODAK190PATCHES", 46 | "MSDS_CAMERA_SENSITIVITIES", 47 | "OPTIONS_CAMERA_SENSITIVITIES", 48 | "OPTIONS_CAT", 49 | "OPTIONS_ILLUMINANT", 50 | "OPTIONS_INTERPOLATION", 51 | "INTERPOLATORS", 52 | "OPTIONS_OPTIMISATION_SPACES", 53 | "OPTIONS_DISPLAY_COLOURSPACES", 54 | "DELAY_TOOLTIP_DEFAULT", 55 | "TEMPLATE_DEFAULT_OUTPUT", 56 | "TEMPLATE_NUKE_GROUP", 57 | "TEMPLATE_CTL_MODULE", 58 | "TEMPLATE_DCTL_MODULE", 59 | "format_float", 60 | "format_matrix_nuke", 61 | "format_vector_nuke", 62 | "format_matrix_ctl", 63 | "format_vector_ctl", 64 | "format_float_dctl", 65 | "format_matrix_dctl", 66 | "format_vector_dctl", 67 | "format_idt_clf", 68 | ] 69 | 70 | COLOUR_ENVIRONMENT = None 71 | """ 72 | *Colour* environment formatted as a string. 73 | 74 | COLOUR_ENVIRONMENT : str 75 | """ 76 | 77 | 78 | def _print_colour_environment(describe) -> None: 79 | """ 80 | Intercept colour environment description output to save it as a formatted 81 | strings. 82 | """ 83 | 84 | global COLOUR_ENVIRONMENT # noqa: PLW0603 85 | 86 | if COLOUR_ENVIRONMENT is None: 87 | COLOUR_ENVIRONMENT = "" 88 | 89 | print(describe) # noqa: T201 90 | 91 | COLOUR_ENVIRONMENT += describe 92 | COLOUR_ENVIRONMENT += "\n" 93 | 94 | 95 | colour.utilities.describe_environment(print_callable=_print_colour_environment) 96 | 97 | 98 | def metadata_card_default(_uid, *args): 99 | """ 100 | Return the default metadata card for an application. 101 | 102 | Parameters 103 | ---------- 104 | _uid : Callable 105 | Callable to generate a unique id for given id by appending the 106 | application *UID*. 107 | 108 | Other Parameters 109 | ---------------- 110 | \\*args 111 | Optional children. 112 | 113 | Returns 114 | ------- 115 | :class:`dash_bootstrap_components.Card` 116 | Metadata card. 117 | """ 118 | 119 | return Card( 120 | [ 121 | CardHeader("Metadata"), 122 | CardBody( 123 | [ 124 | InputGroup( 125 | [ 126 | InputGroupText("ACEStransformID"), 127 | Field( 128 | id=_uid("acestransformid-field"), 129 | type="text", 130 | readonly=True, 131 | placeholder="Auto-generated", 132 | debounce=True, 133 | ), 134 | ], 135 | className="mb-1", 136 | ), 137 | Tooltip( 138 | '"ACEStransformID" of the IDT, e.g. ' 139 | '"urn:ampas:aces:transformId:v1.5:IDT.ARRI.ARRI-LogC4.a1.v1"', 140 | delay=DELAY_TOOLTIP_DEFAULT, 141 | target=_uid("acestransformid-field"), 142 | ), 143 | InputGroup( 144 | [ 145 | InputGroupText("ACESuserName"), 146 | Field( 147 | id=_uid("acesusername-field"), 148 | type="text", 149 | placeholder="...", 150 | debounce=True, 151 | ), 152 | ], 153 | className="mb-1", 154 | ), 155 | Tooltip( 156 | '"ACESuserName" of the IDT, e.g. ' 157 | '"ACES 1.0 Input - ARRI LogC4"', 158 | delay=DELAY_TOOLTIP_DEFAULT, 159 | target=_uid("acesusername-field"), 160 | ), 161 | InputGroup( 162 | [ 163 | InputGroupText("Camera Make"), 164 | Field( 165 | id=_uid("camera-make-field"), 166 | type="text", 167 | placeholder="...", 168 | debounce=True, 169 | ), 170 | ], 171 | className="mb-1", 172 | ), 173 | Tooltip( 174 | 'Manufacturer of the camera, e.g. "ARRI" or "RED"', 175 | delay=DELAY_TOOLTIP_DEFAULT, 176 | target=_uid("camera-make-field"), 177 | ), 178 | InputGroup( 179 | [ 180 | InputGroupText("Camera Model"), 181 | Field( 182 | id=_uid("camera-model-field"), 183 | type="text", 184 | placeholder="...", 185 | debounce=True, 186 | ), 187 | ], 188 | className="mb-1", 189 | ), 190 | Tooltip( 191 | ( 192 | 'Model of the camera, e.g. "ALEXA 35" or ' 193 | '"V-RAPTOR XL 8K VV"' 194 | ), 195 | delay=DELAY_TOOLTIP_DEFAULT, 196 | target=_uid("camera-model-field"), 197 | ), 198 | *list(args), 199 | ] 200 | ), 201 | ], 202 | className="mb-2", 203 | ) 204 | 205 | 206 | DATATABLE_DECIMALS = 7 207 | """ 208 | Datatable decimals. 209 | 210 | DATATABLE_DECIMALS : int 211 | """ 212 | 213 | CUSTOM_WAVELENGTHS = [*list(range(380, 395, 5)), "..."] 214 | """ 215 | Custom wavelengths list. 216 | 217 | CUSTOM_WAVELENGTHS : list 218 | """ 219 | 220 | DATASET_RAW_TO_ACES = colour_datasets.load( 221 | "RAW to ACES Utility Data - Dyer et al. (2017)" 222 | ) 223 | """ 224 | *RAW to ACES* dataset. 225 | 226 | DATASET_RAW_TO_ACES : dict 227 | """ 228 | 229 | TRAINING_DATA_KODAK190PATCHES = DATASET_RAW_TO_ACES["training"]["190-patch"] 230 | """ 231 | *Kodak* 190 patches training data. 232 | 233 | TRAINING_DATA_KODAK190PATCHES : MultiSpectralDistributions 234 | """ 235 | 236 | MSDS_CAMERA_SENSITIVITIES = DATASET_RAW_TO_ACES["camera"] 237 | """ 238 | Camera sensitivities multi-spectral distributions. 239 | 240 | MSDS_CAMERA_SENSITIVITIES : dict 241 | """ 242 | 243 | OPTIONS_CAMERA_SENSITIVITIES = [ 244 | {"label": key, "value": key} 245 | for key in ["Custom", *sorted(MSDS_CAMERA_SENSITIVITIES)] 246 | ] 247 | """ 248 | Camera sensitivities options for a :class:`Dropdown` class instance. 249 | 250 | OPTIONS_CAMERA_SENSITIVITIES : list 251 | """ 252 | 253 | OPTIONS_CAT = [ 254 | {"label": key, "value": key} for key in IDTProjectSettings.cat.metadata.options 255 | ] 256 | """ 257 | *Chromatic adaptation transform* options for a :class:`Dropdown` class 258 | instance. 259 | 260 | OPTIONS_CAT : list 261 | """ 262 | 263 | OPTIONS_ILLUMINANT = [ 264 | {"label": key, "value": key} 265 | for key in IDTProjectSettings.illuminant.metadata.options 266 | ] 267 | """ 268 | Illuminant options for a :class:`Dropdown`class instance. 269 | 270 | ILLUMINANTS_OPTIONS : list 271 | """ 272 | 273 | OPTIONS_INTERPOLATION = [ 274 | {"label": key, "value": key} 275 | for key in IDTProjectSettings.illuminant_interpolator.metadata.options 276 | ] 277 | 278 | INTERPOLATORS = { 279 | "Cubic Spline": CubicSplineInterpolator, 280 | "Linear": LinearInterpolator, 281 | "PCHIP": PchipInterpolator, 282 | "Sprague (1880)": SpragueInterpolator, 283 | } 284 | """ 285 | Spectral distribution interpolators. 286 | 287 | INTERPOLATORS : dict 288 | """ 289 | 290 | OPTIONS_OPTIMISATION_SPACES = [ 291 | {"label": key, "value": key} 292 | for key in IDTProjectSettings.optimisation_space.metadata.options 293 | ] 294 | """ 295 | Optimisation colourspaces. 296 | 297 | OPTIONS_OPTIMISATION_SPACES : list 298 | """ 299 | 300 | OPTIONS_DISPLAY_COLOURSPACES = [ 301 | {"label": key, "value": key} 302 | for key in IDTProjectSettings.rgb_display_colourspace.metadata.options 303 | ] 304 | """ 305 | Display colourspaces. 306 | 307 | OPTIONS_DISPLAY_COLOURSPACES : list 308 | """ 309 | 310 | DELAY_TOOLTIP_DEFAULT = {"show": 500, "hide": 125} 311 | """ 312 | Default tooltip delay settings. 313 | 314 | DELAY_TOOLTIP_DEFAULT : list 315 | """ 316 | 317 | TEMPLATE_DEFAULT_OUTPUT = """ 318 | IDT Matrix 319 | ---------- 320 | 321 | {0} 322 | 323 | White Balance Multipliers 324 | ------------------------- 325 | 326 | {1}"""[1:] 327 | """ 328 | Default formatting template. 329 | 330 | TEMPLATE_DEFAULT_OUTPUT : str 331 | """ 332 | 333 | TEMPLATE_NUKE_GROUP = """ 334 | Group {{ 335 | name {group}_Input_Device_Transform 336 | tile_color 0xffbf00ff 337 | xpos 0 338 | ypos 0 339 | addUserKnob {{20 idt_Tab l "Input Device Transform"}} 340 | addUserKnob {{7 k_Floating_Point_Slider l "Exposure Factor"}} 341 | k_Floating_Point_Slider {k_factor} 342 | addUserKnob {{26 ""}} 343 | addUserKnob {{41 idt_matrix l "IDT Matrix" T B_ColorMatrix.matrix}} 344 | addUserKnob {{41 b_RGB_Color_Knob l "White Balance Multipliers" \ 345 | T Exposure_White_Balance_Expression.b_RGB_Color_Knob}} 346 | addUserKnob {{20 about_Tab l About}} 347 | addUserKnob {{26 description_Text l "" +STARTLINE T "\ 348 | Input Device Transform (IDT)\ 349 | \n\nComputed with {application}\ 350 | \nUrl : {url}\ 351 | \nCamera Make : {camera_make}\ 352 | \nCamera Model : {camera_model}\ 353 | \nACEStransformID : {aces_transform_id}\ 354 | \nACESuserName : {aces_user_name}\ 355 | \nScene adopted white : {illuminant}\ 356 | \nInput : Linear Camera RGB\ 357 | \nOutput : ACES 2065-1\ 358 | \nGenerated on : {date}"}} 359 | }} 360 | Input {{ 361 | inputs 0 362 | name Input 363 | xpos 0 364 | }} 365 | Expression {{ 366 | temp_name0 k 367 | temp_expr0 parent.k_Floating_Point_Slider 368 | temp_name1 min_b 369 | temp_expr1 "min(b_RGB_Color_Knob.r, b_RGB_Color_Knob.g, b_RGB_Color_Knob.b)" 370 | expr0 "clamp((b_RGB_Color_Knob.r * r * k) / min_b)" 371 | expr1 "clamp((b_RGB_Color_Knob.g * g * k) / min_b)" 372 | expr2 "clamp((b_RGB_Color_Knob.b * b * k) / min_b)" 373 | name Exposure_White_Balance_Expression 374 | xpos 0 375 | ypos 25 376 | addUserKnob {{20 white_balance_Tab l "White Balance"}} 377 | addUserKnob {{18 b_RGB_Color_Knob l b}} 378 | b_RGB_Color_Knob {{ {multipliers} }} 379 | addUserKnob {{6 b_RGB_Color_Knob_panelDropped l "panel dropped state" -STARTLINE +HIDDEN}} 380 | }} 381 | ColorMatrix {{ 382 | matrix {{ 383 | {matrix} 384 | }} 385 | name B_ColorMatrix 386 | xpos 0 387 | ypos 50 388 | }} 389 | Output {{ 390 | name Output 391 | xpos 0 392 | ypos 75 393 | }} 394 | end_group 395 | """.strip() # noqa: E501 396 | """ 397 | *The Foundry Nuke* *Input Device Transform* group template. 398 | 399 | TEMPLATE_NUKE_GROUP : str 400 | """ 401 | 402 | TEMPLATE_CTL_MODULE = """ 403 | // {aces_transform_id} 404 | // {aces_user_name} 405 | // Computed with {application} 406 | // Url : {url} 407 | // Camera Make: {camera_make} 408 | // Camera Model: {camera_model} 409 | // Scene adopted white : {illuminant} 410 | // Input : Linear Camera RGB 411 | // Output : ACES 2065-1 412 | // Generated on : {date} 413 | 414 | import "ACESlib.Utilities"; 415 | 416 | const float B[3][3] = {{ 417 | {matrix} 418 | }}; 419 | 420 | const float b[3] = {{ {multipliers} }}; 421 | const float min_b = min(b[0], min(b[1], b[2])); 422 | const float k = {k_factor}; 423 | 424 | void main ( 425 | output varying float rOut, 426 | output varying float gOut, 427 | output varying float bOut, 428 | output varying float aOut, 429 | input varying float rIn, 430 | input varying float gIn, 431 | input varying float bIn, 432 | input varying float aIn = 1.0) 433 | {{ 434 | // Apply exposure and white balance factors 435 | float Rraw = clip((b[0] * rIn * k) / min_b); 436 | float Graw = clip((b[1] * gIn * k) / min_b); 437 | float Braw = clip((b[2] * bIn * k) / min_b); 438 | 439 | // Apply IDT matrix 440 | rOut = B[0][0] * Rraw + B[0][1] * Graw + B[0][2] * Braw; 441 | gOut = B[1][0] * Rraw + B[1][1] * Graw + B[1][2] * Braw; 442 | bOut = B[2][0] * Rraw + B[2][1] * Graw + B[2][2] * Braw; 443 | aOut = aIn; 444 | }}"""[1:] 445 | """ 446 | Color Transform Language (CTL) Module template. 447 | 448 | TEMPLATE_CTL_MODULE : str 449 | """ 450 | 451 | TEMPLATE_DCTL_MODULE = """ 452 | DEFINE_ACES_PARAM(IS_PARAMETRIC_ACES_TRANSFORM: 0) 453 | 454 | // Computed with {application} 455 | // Url : {url} 456 | // Camera Make: {camera_make} 457 | // Camera Model: {camera_model} 458 | // ACEStransformID: {aces_transform_id} 459 | // ACESuserName: {aces_user_name} 460 | // Scene adopted white : {illuminant} 461 | // Input : Linear Camera RGB 462 | // Output : ACES 2065-1 463 | // Generated on : {date} 464 | 465 | __CONSTANT__ float B[3][3] = {{ 466 | {matrix} 467 | }}; 468 | 469 | __CONSTANT__ float b[3] = {{ {multipliers} }}; 470 | __CONSTANT__ float min_b = {b_min}f; 471 | __CONSTANT__ float k = {k_factor}f; 472 | 473 | __DEVICE__ inline float _clipf( float v) {{ 474 | return _fminf(v, 1.0f); 475 | }} 476 | 477 | __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B) 478 | {{ 479 | // Apply exposure and white balance factors 480 | const float Rraw = _clipf((b[0] * p_R * k) / min_b); 481 | const float Graw = _clipf((b[1] * p_G * k) / min_b); 482 | const float Braw = _clipf((b[2] * p_B * k) / min_b); 483 | 484 | // Apply IDT matrix 485 | const float rOut = B[0][0] * Rraw + B[0][1] * Graw + B[0][2] * Braw; 486 | const float gOut = B[1][0] * Rraw + B[1][1] * Graw + B[1][2] * Braw; 487 | const float bOut = B[2][0] * Rraw + B[2][1] * Graw + B[2][2] * Braw; 488 | 489 | return make_float3(rOut , gOut, bOut); 490 | }}""".strip() # noqa: E501 491 | """ 492 | DaVinci Color Transform Language (DCTL) Module template. 493 | 494 | TEMPLATE_DCTL_MODULE : str 495 | """ 496 | 497 | 498 | def format_float(a, decimals=10): 499 | """ 500 | Format given float number at given decimal places. 501 | 502 | Parameters 503 | ---------- 504 | a : numeric 505 | Float number to format. 506 | decimals : int, optional 507 | Decimal places. 508 | 509 | Returns 510 | ------- 511 | str 512 | Formatted float number 513 | """ 514 | 515 | return f"{{: 0.{decimals}f}}".format(a) 516 | 517 | 518 | def format_matrix_nuke(M, decimals=10, padding=6): 519 | """ 520 | Format given matrix for usage in *The Foundry Nuke*, i.e. *TCL* code for 521 | a *ColorMatrix* node. 522 | 523 | Parameters 524 | ---------- 525 | M : array_like 526 | Matrix to format. 527 | decimals : int, optional 528 | Decimals to use when formatting the matrix. 529 | padding : int, optional 530 | Padding to use when formatting the matrix. 531 | 532 | Returns 533 | ------- 534 | str 535 | *The Foundry Nuke* formatted matrix. 536 | """ 537 | 538 | def pretty(V): 539 | """ 540 | Prettify given vector. 541 | """ 542 | 543 | return " ".join(format_float(x, decimals) for x in V) 544 | 545 | pad = " " * padding 546 | 547 | tcl = f"{{{pretty(M[0])}}}\n" 548 | tcl += f"{pad}{{{pretty(M[1])}}}\n" 549 | tcl += f"{pad}{{{pretty(M[2])}}}" 550 | 551 | return tcl 552 | 553 | 554 | def format_vector_nuke(V, decimals=10): 555 | """ 556 | Format given vector for usage in *The Foundry Nuke*, e.g. *TCL* code for 557 | a *Multiply* node. 558 | 559 | Parameters 560 | ---------- 561 | V : array_like 562 | Vector to format. 563 | decimals : int, optional 564 | Decimals to use when formatting the vector. 565 | 566 | Returns 567 | ------- 568 | str 569 | *The Foundry Nuke* formatted vector. 570 | """ 571 | 572 | return " ".join(format_float(x, decimals) for x in V) 573 | 574 | 575 | def format_matrix_ctl(M, decimals=10, padding=4): 576 | """ 577 | Format given matrix for as *CTL* module. 578 | 579 | Parameters 580 | ---------- 581 | M : array_like 582 | Matrix to format. 583 | decimals : int, optional 584 | Decimals to use when formatting the matrix. 585 | padding : int, optional 586 | Padding to use when formatting the matrix. 587 | 588 | Returns 589 | ------- 590 | str 591 | *CTL* formatted matrix. 592 | """ 593 | 594 | def pretty(V): 595 | """ 596 | Prettify given number. 597 | """ 598 | 599 | return ", ".join(format_float(x, decimals) for x in V) 600 | 601 | pad = " " * padding 602 | 603 | ctl = f"{{{pretty(M[0])} }},\n" 604 | ctl += f"{pad}{{{pretty(M[1])} }},\n" 605 | ctl += f"{pad}{{{pretty(M[2])} }}" 606 | 607 | return ctl 608 | 609 | 610 | def format_vector_ctl(V, decimals=10): 611 | """ 612 | Format given vector for as *CTL* module. 613 | 614 | Parameters 615 | ---------- 616 | V : array_like 617 | Vector to format. 618 | decimals : int, optional 619 | Decimals to use when formatting the vector. 620 | 621 | Returns 622 | ------- 623 | str 624 | *CTL* formatted vector. 625 | """ 626 | 627 | return ", ".join(format_float(x, decimals) for x in V) 628 | 629 | 630 | def format_float_dctl(a, decimals=10): 631 | """ 632 | Format given float number for *DCTL* at given decimal places. 633 | 634 | Parameters 635 | ---------- 636 | a : numeric 637 | Float number to format. 638 | decimals : int, optional 639 | Decimal places. 640 | 641 | Returns 642 | ------- 643 | str 644 | Formatted float number 645 | """ 646 | 647 | return f"{{: 0.{decimals}f}}".format(a) 648 | 649 | 650 | def format_vector_dctl(V, decimals=10): 651 | """ 652 | Format given vector as a *DCTL* module. 653 | 654 | Parameters 655 | ---------- 656 | V : array_like 657 | Vector to format. 658 | decimals : int, optional 659 | Decimals to use when formatting the vector. 660 | 661 | Returns 662 | ------- 663 | str 664 | *DCTL* formatted vector. 665 | """ 666 | 667 | return ", ".join(format_float_dctl(x, decimals) for x in V) 668 | 669 | 670 | def format_matrix_dctl(M, decimals=10, padding=4): 671 | """ 672 | Format given matrix as a *DCTL* module. 673 | 674 | Parameters 675 | ---------- 676 | M : array_like 677 | Matrix to format. 678 | decimals : int, optional 679 | Decimals to use when formatting the matrix. 680 | padding : int, optional 681 | Padding to use when formatting the matrix. 682 | 683 | Returns 684 | ------- 685 | str 686 | *DCTL* formatted matrix. 687 | """ 688 | 689 | def pretty(V): 690 | """ 691 | Prettify given vector. 692 | """ 693 | 694 | return ", ".join(format_float_dctl(x, decimals) for x in V) 695 | 696 | pad = " " * padding 697 | 698 | dctl = f"{{{pretty(M[0])} }},\n" 699 | dctl += f"{pad}{{{pretty(M[1])} }},\n" 700 | dctl += f"{pad}{{{pretty(M[2])} }}" 701 | 702 | return dctl 703 | 704 | 705 | def format_idt_clf( 706 | aces_transform_id, 707 | aces_user_name, # noqa: ARG001 708 | camera_make, # noqa: ARG001 709 | camera_model, 710 | matrix, 711 | multipliers, 712 | k_factor, 713 | information, 714 | ): 715 | """ 716 | Format the *IDT* matrix, multipliers and exposure factor :math:`k` as a 717 | *Common LUT Format* (CLF). 718 | 719 | Parameters 720 | ---------- 721 | aces_transform_id : str 722 | *ACEStransformID* of the IDT, e.g. 723 | *urn:ampas:aces:transformId:v1.5:IDT.ARRI.ARRI-LogC4.a1.v1*. 724 | aces_user_name : str 725 | *ACESuserName* of the IDT, e.g. *ACES 1.0 Input - ARRI LogC4*. 726 | camera_make : str 727 | Manufacturer of the camera, e.g. *ARRI* or *RED*. 728 | camera_model : str 729 | Model of the camera, e.g. *ALEXA 35* or *V-RAPTOR XL 8K VV*. 730 | matrix : ArrayLike 731 | *IDT* matrix. 732 | multipliers : ArrayLike 733 | *IDT* multipliers. 734 | k_factor : float 735 | Exposure factor :math:`k` that results in a nominally "18% gray" object 736 | in the scene producing ACES values [0.18, 0.18, 0.18]. 737 | information : dict 738 | Information pertaining to the *IDT* and the computation parameters. 739 | 740 | Returns 741 | ------- 742 | str 743 | *CLF* file path. 744 | """ 745 | 746 | root = ET.Element( 747 | "ProcessList", 748 | compCLFversion="3", 749 | id=aces_transform_id, 750 | name=f"{camera_model} to ACES2065-1", 751 | ) 752 | 753 | def format_array(a): 754 | """Format given array :math:`a`.""" 755 | 756 | return "\n".join(map(str, np.ravel(a).tolist())) 757 | 758 | et_input_descriptor = ET.SubElement(root, "InputDescriptor") 759 | et_input_descriptor.text = camera_model 760 | 761 | et_output_descriptor = ET.SubElement(root, "OutputDescriptor") 762 | et_output_descriptor.text = "ACES2065-1" 763 | 764 | et_info = ET.SubElement(root, "Info") 765 | et_academy_idt_calculator = ET.SubElement(et_info, "AcademyIDTCalculator") 766 | for key, value in information.items(): 767 | sub_element = ET.SubElement(et_academy_idt_calculator, key) 768 | sub_element.text = str(value) 769 | 770 | root = clf_processing_elements(root, matrix, multipliers, k_factor) 771 | 772 | ET.indent(root) 773 | 774 | clf_content = '\n' 775 | clf_content += ET.tostring(root, encoding="UTF-8").decode("utf8") 776 | 777 | return clf_content 778 | --------------------------------------------------------------------------------