From 33ff94b154a08bb61cee0d65dd89d531a9381f8c Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Fri, 3 Feb 2023 21:55:36 +1300 Subject: [PATCH 1/5] Update "tasks.py" file. --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 1fe432a..cafde64 100644 --- a/tasks.py +++ b/tasks.py @@ -85,7 +85,7 @@ def quality( if pyright: message_box('Checking codebase with "Pyright"...') - ctx.run("pyright --skipunannotated") + ctx.run("pyright --skipunannotated --level warning") @task From 42f203876fb9587352c3508a07cb3c31ada5c3bd Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Fri, 3 Feb 2023 22:12:59 +1300 Subject: [PATCH 2/5] Implement support for "Ruff". --- .pre-commit-config.yaml | 20 ++------ index.py | 2 +- pyproject.toml | 104 +++++++++++++++++++++++++++++++++++----- requirements.txt | 29 +++++------ tasks.py | 9 ++-- 5 files changed, 110 insertions(+), 54 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f93c2c9..3345920 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,12 @@ repos: -- repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 - hooks: - - id: pyupgrade - args: [--py39-plus] - repo: https://github.com/ikamensh/flynt/ rev: '0.77' hooks: - id: flynt +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.239' + hooks: + - id: ruff - repo: https://github.com/psf/black rev: 22.10.0 hooks: @@ -18,14 +17,3 @@ repos: hooks: - id: blackdoc language_version: python3.9 -- repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 -- repo: https://github.com/pycqa/pydocstyle - rev: 6.1.1 - hooks: - - id: pydocstyle - args: - - --convention=numpy - - --add-ignore=D104,D200,D202,D205,D301,D400 \ No newline at end of file diff --git a/index.py b/index.py index 2355b70..f8cf745 100644 --- a/index.py +++ b/index.py @@ -9,7 +9,7 @@ import apps.rgb_colourspace_transformation_matrix as app_1 import apps.rgb_colourspace_chromatically_adapted_primaries as app_2 -from app import APP, SERVER # noqa +from app import APP __author__ = "Colour Developers" __copyright__ = "Copyright 2018 Colour Developers" diff --git a/pyproject.toml b/pyproject.toml index b47af98..df67948 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,32 +53,28 @@ black = { version = "*", optional = true } # Development dependency. blackdoc = { version = "*", optional = true } # Development dependency. coverage = { version = "!= 6.3", optional = true } # Development dependency. coveralls = { version = "*", optional = true } # Development dependency. -flake8 = { version = "*", optional = true } # Development dependency. flynt = { version = "*", optional = true } # Development dependency. invoke = { version = "*", optional = true } # Development dependency. pre-commit = { version = "*", optional = true } # Development dependency. -pydocstyle = { version = "*", optional = true } # Development dependency. pyright = { version = "*", optional = true } # Development dependency. pytest = { version = "*", optional = true } # Development dependency. pytest-cov = { version = "*", optional = true } # Development dependency. pytest-xdist = { version = "*", optional = true } # Development dependency. -pyupgrade = { version = "*", optional = true } # Development dependency. +ruff = { version = "*", optional = true } # Development dependency. [tool.poetry.dev-dependencies] black = "*" blackdoc = "*" coverage = "*" coveralls = "*" -flake8 = "*" flynt = "*" invoke = "*" pre-commit = "*" -pydocstyle = "*" pyright = "*" pytest = "*" pytest-cov = "*" pytest-xdist = "*" -pyupgrade = "*" +ruff = "*" [tool.poetry.extras] development = [ @@ -86,17 +82,15 @@ development = [ "blackdoc", "coverage", "coveralls", - "flake8", "flynt", "invoke", "mypy", "pre-commit", - "pydocstyle", "pyright", "pytest", "pytest-cov", "pytest-xdist", - "pyupgrade", + "ruff", ] [tool.black] @@ -112,10 +106,6 @@ exclude = ''' [tool.flynt] line_length=999 -[tool.pydocstyle] -convention = "numpy" -add-ignore = "D104,D200,D202,D205,D301,D400" - [tool.pyright] reportMissingImports = false reportMissingModuleSource = false @@ -125,6 +115,94 @@ reportUnnecessaryTypeIgnoreComment = true reportUnsupportedDunderAll = false reportUnusedExpression = false +[tool.ruff] +target-version = "py39" +line-length = 88 +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + # "ANN", # flake8-annotations + "B", # flake8-bugbear + # "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + # "C90", # mccabe + # "COM", # flake8-commas + "DTZ", # flake8-datetimez + "D", # pydocstyle + "E", # pydocstyle + # "ERA", # eradicate + # "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # flake8 + # "FBT", # flake8-boolean-trap + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + # "PD", # pandas-vet + "PIE", # flake8-pie + "PGH", # pygrep-hooks + "PL", # pylint + # "PT", # flake8-pytest-style + # "PTH", # flake8-use-pathlib [Enable] + "Q", # flake8-quotes + "RET", # flake8-return + "RUF", # Ruff + "S", # flake8-bandit + "SIM", # flake8-simplify + "T10", # flake8-debugger + "T20", # flake8-print + # "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pydocstyle + "YTT" # flake8-2020 +] +ignore = [ + "B008", + "B905", + "D104", + "D200", + "D202", + "D205", + "D301", + "D400", + "I001", + "N801", + "N802", + "N803", + "N806", + "N813", + "N815", + "N816", + "PIE804", + "PLE0605", + "PLR0913", + "PLR2004", + "RET504", + "RET505", + "RET506", + "RET507", + "RET508", + "TRY003", + "TRY300", +] +typing-modules = ["colour.hints"] +fixable = ["B", "C", "E", "F", "PIE", "RUF", "SIM", "UP", "W"] + +[tool.ruff.pydocstyle] +convention = "numpy" + +[tool.ruff.per-file-ignores] +"docs/*" = ["INP"] +"app.py" = ["INP"] +"index.py" = ["INP"] +"setup.py" = ["INP"] +"tasks.py" = ["INP"] + [build-system] requires = [ "poetry_core>=1.0.0" ] build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 79c6db2..8dfa86b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ astor==0.8.1 attrs==22.2.0 -black==22.12.0 +black==23.1.0 blackdoc==0.3.8 certifi==2022.12.7 cfgv==3.3.1 @@ -9,7 +9,7 @@ click==8.1.3 colour-science==0.4.2 coverage==6.5.0 coveralls==3.3.1 -dash==2.7.1 +dash==2.8.1 dash-core-components==2.0.0 dash-html-components==2.0.0 dash-renderer==1.9.1 @@ -18,50 +18,43 @@ distlib==0.3.6 docopt==0.6.2 execnet==1.9.0 filelock==3.9.0 -flake8==6.0.0 Flask==2.2.2 flynt==0.77 gunicorn==20.1.0 -identify==2.5.13 +identify==2.5.17 idna==3.4 -imageio==2.24.0 +imageio==2.25.0 iniconfig==2.0.0 invoke==2.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 markdown-it-py==2.1.0 MarkupSafe==2.1.2 -mccabe==0.7.0 mdurl==0.1.2 more-itertools==9.0.0 mypy-extensions==0.4.3 nodeenv==1.7.0 numpy==1.24.1 packaging==23.0 -pathspec==0.10.3 +pathspec==0.11.0 Pillow==9.4.0 pip==22.3.1 platformdirs==2.6.2 -plotly==5.12.0 +plotly==5.13.0 pluggy==1.0.0 -pre-commit==2.21.0 -pycodestyle==2.10.0 -pydocstyle==6.3.0 -pyflakes==3.0.1 +pre-commit==3.0.3 Pygments==2.14.0 -pyright==1.1.290 +pyright==1.1.292 pytest==7.2.1 pytest-cov==4.0.0 pytest-xdist==3.1.0 -pyupgrade==3.3.1 PyYAML==6.0 requests==2.28.2 -rich==13.2.0 +rich==13.3.1 +ruff==0.0.240 scipy==1.10.0 -setuptools==66.1.1 -snowballstemmer==2.2.0 +setuptools==67.1.0 tenacity==8.1.0 -tokenize-rt==5.0.0 tomli==2.0.1 typing_extensions==4.4.0 urllib3==1.26.14 diff --git a/tasks.py b/tasks.py index cafde64..ad4204d 100644 --- a/tasks.py +++ b/tasks.py @@ -3,6 +3,7 @@ ============== """ +import contextlib import platform from invoke.exceptions import Failure @@ -156,16 +157,12 @@ def docker_remove(ctx: Context): """ message_box('Stopping "docker" container...') - try: + with contextlib.suppress(Failure): ctx.run(f"docker stop {CONTAINER}") - except Failure: - pass message_box('Removing "docker" container...') - try: + with contextlib.suppress(Failure): ctx.run(f"docker rm {CONTAINER}") - except Failure: - pass @task(docker_remove, docker_build) From 2947a7543da4088b4d5180531801bc7eb65f8ef8 Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Fri, 3 Feb 2023 23:01:45 +1300 Subject: [PATCH 3/5] Implement support for "spimtx" formatter. --- apps/common.py | 27 +++++++++++++++++++ apps/rgb_colourspace_transformation_matrix.py | 6 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/common.py b/apps/common.py index 9ef2dfd..c3197ad 100644 --- a/apps/common.py +++ b/apps/common.py @@ -3,8 +3,10 @@ ====== """ +from io import StringIO from colour.adaptation import CHROMATIC_ADAPTATION_TRANSFORMS from colour.colorimetry import CCS_ILLUMINANTS +from colour.io import LUTOperatorMatrix, write_LUT_SonySPImtx from colour.models import RGB_COLOURSPACES from colour.utilities import as_float_array @@ -23,6 +25,7 @@ "ILLUMINANTS_OPTIONS", "NUKE_COLORMATRIX_NODE_TEMPLATE", "nuke_format_matrix", + "spimtx_format_matrix", ] RGB_COLOURSPACE_OPTIONS: List[Dict] = [ @@ -102,3 +105,27 @@ def pretty(x: Iterable) -> str: tcl += f" {{{pretty(M[2])}}}" return tcl + + +def spimtx_format_matrix(M: ArrayLike, decimals: int = 10) -> str: + """ + Format given matrix as a *Sony* *.spimtx* *LUT* formatted matrix. + + Parameters + ---------- + M + Matrix to format. + decimals + Decimals to use when formatting the matrix. + + Returns + ------- + :class:`str` + *Sony* *.spimtx* *LUT* formatted matrix. + """ + + string = StringIO() + + write_LUT_SonySPImtx(LUTOperatorMatrix(M), string, decimals) + + return string.getvalue() diff --git a/apps/rgb_colourspace_transformation_matrix.py b/apps/rgb_colourspace_transformation_matrix.py index 4954fb9..3f3236f 100644 --- a/apps/rgb_colourspace_transformation_matrix.py +++ b/apps/rgb_colourspace_transformation_matrix.py @@ -21,6 +21,7 @@ NUKE_COLORMATRIX_NODE_TEMPLATE, RGB_COLOURSPACE_OPTIONS, nuke_format_matrix, + spimtx_format_matrix, ) __author__ = "Colour Developers" @@ -118,6 +119,7 @@ {"label": "str", "value": "str"}, {"label": "repr", "value": "repr"}, {"label": "Nuke", "value": "Nuke"}, + {"label": "Spimtx", "value": "Spimtx"}, ], value=DEFAULT_STATE["formatter"], clearable=False, @@ -250,7 +252,7 @@ def set_RGB_to_RGB_matrix_output( M_f = str(M) elif formatter == "repr": M_f = repr(M) - else: + elif formatter == "Nuke": def slugify(string: str) -> str: """Slugify given string for *Nuke*.""" @@ -270,6 +272,8 @@ def slugify(string: str) -> str: f"{slugify(output_colourspace)}" ), ) + elif formatter == "Spimtx": + M_f = spimtx_format_matrix(M, decimals) return M_f From e988210fe80bd4f3bb8d9d8712e9d0d62013a224 Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Sat, 4 Feb 2023 10:42:05 +1300 Subject: [PATCH 4/5] Implement support for "OCIO" formatter and copy to clipboard button. --- apps/common.py | 69 ++++++-- ...urspace_chromatically_adapted_primaries.py | 125 +++++++++----- apps/rgb_colourspace_transformation_matrix.py | 154 ++++++++++++------ 3 files changed, 245 insertions(+), 103 deletions(-) diff --git a/apps/common.py b/apps/common.py index c3197ad..2b04826 100644 --- a/apps/common.py +++ b/apps/common.py @@ -20,15 +20,17 @@ __status__ = "Production" __all__ = [ - "RGB_COLOURSPACE_OPTIONS", - "CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS", - "ILLUMINANTS_OPTIONS", - "NUKE_COLORMATRIX_NODE_TEMPLATE", + "OPTIONS_RGB_COLOURSPACE", + "OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM", + "OPTIONS_ILLUMINANTS", + "TEMPLATE_NUKE_NODE_COLORMATRIX", "nuke_format_matrix", "spimtx_format_matrix", + "TEMPLATE_OCIO_COLORSPACE", + "matrix_3x3_to_4x4", ] -RGB_COLOURSPACE_OPTIONS: List[Dict] = [ +OPTIONS_RGB_COLOURSPACE: List[Dict] = [ {"label": key, "value": key} for key in sorted(RGB_COLOURSPACES.keys()) if key not in ("aces", "adobe1998", "prophoto") @@ -37,7 +39,7 @@ *RGB* colourspace options for a :class:`Dropdown` class instance. """ -CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS: List[Dict] = [ +OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM: List[Dict] = [ {"label": key, "value": key} for key in sorted(CHROMATIC_ADAPTATION_TRANSFORMS.keys()) ] @@ -46,7 +48,7 @@ instance. """ -ILLUMINANTS_OPTIONS: List[Dict] = [ +OPTIONS_ILLUMINANTS: List[Dict] = [ {"label": key, "value": key} for key in sorted( CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"].keys() @@ -57,13 +59,13 @@ :class:`Dropdown`class instance. """ -NUKE_COLORMATRIX_NODE_TEMPLATE: str = """ +TEMPLATE_NUKE_NODE_COLORMATRIX: str = """ ColorMatrix {{ inputs 0 matrix {{ - {0} + {matrix} }} - name "{1}" + name "{name}" selected true xpos 0 ypos 0 @@ -129,3 +131,50 @@ def spimtx_format_matrix(M: ArrayLike, decimals: int = 10) -> str: write_LUT_SonySPImtx(LUTOperatorMatrix(M), string, decimals) return string.getvalue() + + +TEMPLATE_OCIO_COLORSPACE = """ + - ! + name: Linear {name} + aliases: [] + family: Utility + equalitygroup: "" + bitdepth: 32f + description: | + Convert from {input_colourspace} to Linear {output_colourspace} + isdata: false + encoding: scene-linear + allocation: uniform + from_scene_reference: ! + name: {input_colourspace} to Linear {output_colourspace} + children: + - ! {{matrix: {matrix}}} +"""[ + 1: +] +""" +*OpenColorIO* *ColorSpace* template. +""" + + +def matrix_3x3_to_4x4(M): + """ + Convert given 3x3 matrix :math:`M` to a raveled 4x4 matrix. + + Parameters + ---------- + M : array_like + 3x3 matrix :math:`M` to convert. + + Returns + ------- + list + Raveled 4x4 matrix. + """ + + import numpy as np + + M_I = np.identity(4) + M_I[:3, :3] = M + + return np.ravel(M_I) diff --git a/apps/rgb_colourspace_chromatically_adapted_primaries.py b/apps/rgb_colourspace_chromatically_adapted_primaries.py index 097756b..efe9488 100644 --- a/apps/rgb_colourspace_chromatically_adapted_primaries.py +++ b/apps/rgb_colourspace_chromatically_adapted_primaries.py @@ -8,7 +8,7 @@ from contextlib import suppress from dash.dcc import Dropdown, Link, Location, Markdown, Slider from dash.dependencies import Input, Output -from dash.html import A, Code, Div, H3, H5, Li, Pre, Ul +from dash.html import A, Button, Code, Div, H3, H5, Li, Pre, Ul from urllib.parse import parse_qs, urlencode, urlparse from colour.colorimetry import CCS_ILLUMINANTS @@ -17,9 +17,9 @@ from app import APP, SERVER_URL from apps.common import ( - CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS, - ILLUMINANTS_OPTIONS, - RGB_COLOURSPACE_OPTIONS, + OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM, + OPTIONS_ILLUMINANTS, + OPTIONS_RGB_COLOURSPACE, ) __author__ = "Colour Developers" @@ -34,7 +34,7 @@ "APP_PATH", "APP_DESCRIPTION", "APP_UID", - "DEFAULT_STATE", + "STATE_DEFAULT", "LAYOUT", "set_primaries_output", "update_state_on_url_query_change", @@ -66,10 +66,19 @@ App unique id. """ -DEFAULT_STATE = { - "colourspace": RGB_COLOURSPACE_OPTIONS[0]["value"], - "illuminant": ILLUMINANTS_OPTIONS[0]["value"], - "chromatic_adaptation_transform": CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS[ + +def _uid(id_): + """ + Generate a unique id for given id by appending the application *UID*. + """ + + return f"{id_}-{APP_UID}" + + +STATE_DEFAULT = { + "colourspace": OPTIONS_RGB_COLOURSPACE[0]["value"], + "illuminant": OPTIONS_ILLUMINANTS[0]["value"], + "chromatic_adaptation_transform": OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM[ 0 ]["value"], "formatter": "str", @@ -81,58 +90,68 @@ LAYOUT: Div = Div( [ - Location(id=f"url-{APP_UID}", refresh=False), + Location(id=_uid("url"), refresh=False), H3([Link(APP_NAME, href=APP_PATH)], className="text-center"), Div( [ Markdown(APP_DESCRIPTION), H5(children="Colourspace"), Dropdown( - id=f"colourspace-{APP_UID}", - options=RGB_COLOURSPACE_OPTIONS, - value=DEFAULT_STATE["colourspace"], + id=_uid("colourspace"), + options=OPTIONS_RGB_COLOURSPACE, + value=STATE_DEFAULT["colourspace"], clearable=False, className="app-widget", ), H5(children="Illuminant"), Dropdown( - id=f"illuminant-{APP_UID}", - options=ILLUMINANTS_OPTIONS, - value=DEFAULT_STATE["illuminant"], + id=_uid("illuminant"), + options=OPTIONS_ILLUMINANTS, + value=STATE_DEFAULT["illuminant"], clearable=False, className="app-widget", ), H5(children="Chromatic Adaptation Transform"), Dropdown( - id=f"chromatic-adaptation-transform-{APP_UID}", - options=CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS, - value=DEFAULT_STATE["chromatic_adaptation_transform"], + id=_uid("chromatic-adaptation-transform"), + options=OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM, + value=STATE_DEFAULT["chromatic_adaptation_transform"], clearable=False, className="app-widget", ), H5(children="Formatter"), Dropdown( - id=f"formatter-{APP_UID}", + id=_uid("formatter"), options=[ {"label": "str", "value": "str"}, {"label": "repr", "value": "repr"}, ], - value=DEFAULT_STATE["formatter"], + value=STATE_DEFAULT["formatter"], clearable=False, className="app-widget", ), H5(children="Decimals"), Slider( - id=f"decimals-{APP_UID}", + id=_uid("decimals"), min=1, max=15, step=1, - value=DEFAULT_STATE["decimals"], + value=STATE_DEFAULT["decimals"], marks={i + 1: str(i + 1) for i in range(15)}, className="app-widget", ), + Button( + "Copy to Clipboard", + id=_uid("copy-to-clipboard-button"), + n_clicks=0, + style={"width": "100%"}, + ), Pre( - [Code(id=f"primaries-{APP_UID}", className="code shell")], + [ + Code( + id=_uid("primaries-output"), className="code shell" + ) + ], className="app-widget app-output", ), Ul( @@ -172,6 +191,7 @@ ], className="list-inline text-center", ), + Div(id=_uid("dev-null"), style={"display": "none"}), ], className="col-6 mx-auto", ), @@ -185,13 +205,15 @@ @APP.callback( - Output(component_id=f"primaries-{APP_UID}", component_property="children"), + Output( + component_id=_uid("primaries-output"), component_property="children" + ), [ - Input(f"colourspace-{APP_UID}", "value"), - Input(f"illuminant-{APP_UID}", "value"), - Input(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Input(f"formatter-{APP_UID}", "value"), - Input(f"decimals-{APP_UID}", "value"), + Input(_uid("colourspace"), "value"), + Input(_uid("illuminant"), "value"), + Input(_uid("chromatic-adaptation-transform"), "value"), + Input(_uid("formatter"), "value"), + Input(_uid("decimals"), "value"), ], ) def set_primaries_output( @@ -248,14 +270,14 @@ def set_primaries_output( @APP.callback( [ - Output(f"colourspace-{APP_UID}", "value"), - Output(f"illuminant-{APP_UID}", "value"), - Output(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Output(f"formatter-{APP_UID}", "value"), - Output(f"decimals-{APP_UID}", "value"), + Output(_uid("colourspace"), "value"), + Output(_uid("illuminant"), "value"), + Output(_uid("chromatic-adaptation-transform"), "value"), + Output(_uid("formatter"), "value"), + Output(_uid("decimals"), "value"), ], [ - Input("url", "href"), + Input(_uid("url"), "href"), ], ) def update_state_on_url_query_change(href: str) -> tuple: @@ -283,7 +305,7 @@ def value_from_query(value: str) -> str: with suppress(KeyError): return query[value][0] - return DEFAULT_STATE[value.replace("-", "_")] + return STATE_DEFAULT[value.replace("-", "_")] state = ( value_from_query("colourspace"), @@ -297,13 +319,13 @@ def value_from_query(value: str) -> str: @APP.callback( - Output(f"url-{APP_UID}", "search"), + Output(_uid("url"), "search"), [ - Input(f"colourspace-{APP_UID}", "value"), - Input(f"illuminant-{APP_UID}", "value"), - Input(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Input(f"formatter-{APP_UID}", "value"), - Input(f"decimals-{APP_UID}", "value"), + Input(_uid("colourspace"), "value"), + Input(_uid("illuminant"), "value"), + Input(_uid("chromatic-adaptation-transform"), "value"), + Input(_uid("formatter"), "value"), + Input(_uid("decimals"), "value"), ], ) def update_url_query_on_state_change( @@ -347,3 +369,20 @@ def update_url_query_on_state_change( ) return f"?{query}" + + +APP.clientside_callback( + f""" + function(n_clicks) {{ + var primariesOutput = document.getElementById(\ +"{_uid('primaries-output')}"); + var content = primariesOutput.textContent; + navigator.clipboard.writeText(content).then(function() {{ + }}, function() {{ + }}); + return content; + }} + """, + [Output(component_id=_uid("dev-null"), component_property="children")], + [Input(_uid("copy-to-clipboard-button"), "n_clicks")], +) diff --git a/apps/rgb_colourspace_transformation_matrix.py b/apps/rgb_colourspace_transformation_matrix.py index 3f3236f..794d131 100644 --- a/apps/rgb_colourspace_transformation_matrix.py +++ b/apps/rgb_colourspace_transformation_matrix.py @@ -9,7 +9,7 @@ from contextlib import suppress from dash.dcc import Dropdown, Location, Link, Markdown, Slider from dash.dependencies import Input, Output -from dash.html import A, Code, Div, H3, H5, Li, Pre, Ul +from dash.html import A, Button, Code, Div, H3, H5, Li, Pre, Ul from urllib.parse import parse_qs, urlencode, urlparse from colour.models import RGB_COLOURSPACES, matrix_RGB_to_RGB @@ -17,9 +17,11 @@ from app import APP, SERVER_URL from apps.common import ( - CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS, - NUKE_COLORMATRIX_NODE_TEMPLATE, - RGB_COLOURSPACE_OPTIONS, + OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM, + OPTIONS_RGB_COLOURSPACE, + TEMPLATE_NUKE_NODE_COLORMATRIX, + TEMPLATE_OCIO_COLORSPACE, + matrix_3x3_to_4x4, nuke_format_matrix, spimtx_format_matrix, ) @@ -36,7 +38,7 @@ "APP_NAME", "APP_DESCRIPTION", "APP_UID", - "DEFAULT_STATE", + "STATE_DEFAULT", "LAYOUT", "set_RGB_to_RGB_matrix_output", "update_state_on_url_query_change", @@ -68,10 +70,19 @@ App unique id. """ -DEFAULT_STATE = { - "input_colourspace": RGB_COLOURSPACE_OPTIONS[0]["value"], - "output_colourspace": RGB_COLOURSPACE_OPTIONS[0]["value"], - "chromatic_adaptation_transform": CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS[ + +def _uid(id_): + """ + Generate a unique id for given id by appending the application *UID*. + """ + + return f"{id_}-{APP_UID}" + + +STATE_DEFAULT = { + "input_colourspace": OPTIONS_RGB_COLOURSPACE[0]["value"], + "output_colourspace": OPTIONS_RGB_COLOURSPACE[0]["value"], + "chromatic_adaptation_transform": OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM[ 0 ]["value"], "formatter": "str", @@ -83,62 +94,71 @@ LAYOUT: Div = Div( [ - Location(id=f"url-{APP_UID}", refresh=False), + Location(id=_uid("url"), refresh=False), H3([Link(APP_NAME, href=APP_PATH)], className="text-center"), Div( [ Markdown(APP_DESCRIPTION), H5(children="Input Colourspace"), Dropdown( - id=f"input-colourspace-{APP_UID}", - options=RGB_COLOURSPACE_OPTIONS, - value=DEFAULT_STATE["input_colourspace"], + id=_uid("input-colourspace"), + options=OPTIONS_RGB_COLOURSPACE, + value=STATE_DEFAULT["input_colourspace"], clearable=False, className="app-widget", ), H5(children="Output Colourspace"), Dropdown( - id=f"output-colourspace-{APP_UID}", - options=RGB_COLOURSPACE_OPTIONS, - value=DEFAULT_STATE["output_colourspace"], + id=_uid("output-colourspace"), + options=OPTIONS_RGB_COLOURSPACE, + value=STATE_DEFAULT["output_colourspace"], clearable=False, className="app-widget", ), H5(children="Chromatic Adaptation Transform"), Dropdown( - id=f"chromatic-adaptation-transform-{APP_UID}", - options=CHROMATIC_ADAPTATION_TRANSFORM_OPTIONS, - value=DEFAULT_STATE["chromatic_adaptation_transform"], + id=_uid("chromatic-adaptation-transform"), + options=OPTIONS_CHROMATIC_ADAPTATION_TRANSFORM, + value=STATE_DEFAULT["chromatic_adaptation_transform"], clearable=False, className="app-widget", ), H5(children="Formatter"), Dropdown( - id=f"formatter-{APP_UID}", + id=_uid("formatter"), options=[ {"label": "str", "value": "str"}, {"label": "repr", "value": "repr"}, - {"label": "Nuke", "value": "Nuke"}, - {"label": "Spimtx", "value": "Spimtx"}, + {"label": "Nuke", "value": "nuke"}, + {"label": "OpenColorIO", "value": "opencolorio"}, + {"label": "Spimtx", "value": "spimtx"}, ], - value=DEFAULT_STATE["formatter"], + value=STATE_DEFAULT["formatter"], clearable=False, className="app-widget", ), H5(children="Decimals"), Slider( - id=f"decimals-{APP_UID}", + id=_uid("decimals"), min=1, max=15, step=1, - value=DEFAULT_STATE["decimals"], + value=STATE_DEFAULT["decimals"], marks={i + 1: str(i + 1) for i in range(15)}, className="app-widget", ), + Button( + "Copy to Clipboard", + id=_uid("copy-to-clipboard-button"), + n_clicks=0, + style={"width": "100%"}, + ), Pre( [ Code( - id=f"RGB-transformation-matrix-{APP_UID}", + id=_uid( + "rgb-colourspace-transformation-matrix-output" + ), className="code shell", ) ], @@ -181,6 +201,7 @@ ], className="list-inline text-center", ), + Div(id=_uid("dev-null"), style={"display": "none"}), ], className="col-6 mx-auto", ), @@ -195,15 +216,15 @@ @APP.callback( Output( - component_id=f"RGB-transformation-matrix-{APP_UID}", + component_id=_uid("rgb-colourspace-transformation-matrix-output"), component_property="children", ), [ - Input(f"input-colourspace-{APP_UID}", "value"), - Input(f"output-colourspace-{APP_UID}", "value"), - Input(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Input(f"formatter-{APP_UID}", "value"), - Input(f"decimals-{APP_UID}", "value"), + Input(_uid("input-colourspace"), "value"), + Input(_uid("output-colourspace"), "value"), + Input(_uid("chromatic-adaptation-transform"), "value"), + Input(_uid("formatter"), "value"), + Input(_uid("decimals"), "value"), ], ) def set_RGB_to_RGB_matrix_output( @@ -252,7 +273,7 @@ def set_RGB_to_RGB_matrix_output( M_f = str(M) elif formatter == "repr": M_f = repr(M) - elif formatter == "Nuke": + elif formatter == "nuke": def slugify(string: str) -> str: """Slugify given string for *Nuke*.""" @@ -264,15 +285,31 @@ def slugify(string: str) -> str: string = re.sub(pattern, "_", string) return string - M_f = NUKE_COLORMATRIX_NODE_TEMPLATE.format( - nuke_format_matrix(M, decimals), - ( + M_f = TEMPLATE_NUKE_NODE_COLORMATRIX.format( + name=( f"{slugify(input_colourspace)}" f"__to__" f"{slugify(output_colourspace)}" ), + matrix=nuke_format_matrix(M, decimals), ) - elif formatter == "Spimtx": + elif formatter == "opencolorio": + M_f = TEMPLATE_OCIO_COLORSPACE.format( + name=output_colourspace, + input_colourspace=input_colourspace, + output_colourspace=output_colourspace, + matrix=re.sub( + r"\s+", + " ", + repr(matrix_3x3_to_4x4(M)) + .replace("array(", "") + .replace("[ ", "[") + .replace(")", "") + .replace("\n", ""), + ), + ) + + elif formatter == "spimtx": M_f = spimtx_format_matrix(M, decimals) return M_f @@ -280,14 +317,14 @@ def slugify(string: str) -> str: @APP.callback( [ - Output(f"input-colourspace-{APP_UID}", "value"), - Output(f"output-colourspace-{APP_UID}", "value"), - Output(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Output(f"formatter-{APP_UID}", "value"), - Output(f"decimals-{APP_UID}", "value"), + Output(_uid("input-colourspace"), "value"), + Output(_uid("output-colourspace"), "value"), + Output(_uid("chromatic-adaptation-transform"), "value"), + Output(_uid("formatter"), "value"), + Output(_uid("decimals"), "value"), ], [ - Input("url", "href"), + Input(_uid("url"), "href"), ], ) def update_state_on_url_query_change(href: str) -> tuple: @@ -315,7 +352,7 @@ def value_from_query(value: str) -> str: with suppress(KeyError): return query[value][0] - return DEFAULT_STATE[value.replace("-", "_")] + return STATE_DEFAULT[value.replace("-", "_")] state = ( value_from_query("input-colourspace"), @@ -329,13 +366,13 @@ def value_from_query(value: str) -> str: @APP.callback( - Output(f"url-{APP_UID}", "search"), + Output(_uid("url"), "search"), [ - Input(f"input-colourspace-{APP_UID}", "value"), - Input(f"output-colourspace-{APP_UID}", "value"), - Input(f"chromatic-adaptation-transform-{APP_UID}", "value"), - Input(f"formatter-{APP_UID}", "value"), - Input(f"decimals-{APP_UID}", "value"), + Input(_uid("input-colourspace"), "value"), + Input(_uid("output-colourspace"), "value"), + Input(_uid("chromatic-adaptation-transform"), "value"), + Input(_uid("formatter"), "value"), + Input(_uid("decimals"), "value"), ], ) def update_url_query_on_state_change( @@ -378,3 +415,20 @@ def update_url_query_on_state_change( ) return f"?{query}" + + +APP.clientside_callback( + f""" + function(n_clicks) {{ + var rgbColourspaceTransformationMatrixOutput = document.getElementById(\ +"{_uid('rgb-colourspace-transformation-matrix-output')}"); + var content = rgbColourspaceTransformationMatrixOutput.textContent; + navigator.clipboard.writeText(content).then(function() {{ + }}, function() {{ + }}); + return content; + }} + """, + [Output(component_id=_uid("dev-null"), component_property="children")], + [Input(_uid("copy-to-clipboard-button"), "n_clicks")], +) From 716f2a66d2cfb3db947e6b4eb8853e94285f7677 Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Sat, 4 Feb 2023 10:43:12 +1300 Subject: [PATCH 5/5] Raise app version to 0.2.1. --- app.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 4ca2803..2a8bee1 100644 --- a/app.py +++ b/app.py @@ -20,7 +20,7 @@ __major_version__ = "0" __minor_version__ = "2" -__change_version__ = "0" +__change_version__ = "1" __version__ = ".".join( (__major_version__, __minor_version__, __change_version__) ) diff --git a/pyproject.toml b/pyproject.toml index df67948..0222173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "colour-dash" -version = "0.2.0" +version = "0.2.1" description = "Various colour science Dash apps built on top of Colour" license = "BSD-3-Clause" authors = [ "Colour Developers " ]