diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..a5e406f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,43 @@ +name: Bug Report +description: Report an issue or a bug. +title: "[BUG]: << Please use a comprehensive title... >>" +labels: [ Defect ] + +body: + - type: markdown + attributes: + value: > + Thank you for taking the time to file a bug report. Before continuing, please take some time to check the existing [issues](https://github.com/colour-science/colour-visuals/issues). + The issue could already be fixed in the [develop](https://github.com/colour-science/colour-visuals) branch. If you have an installation problem, the [installation guide](https://www.colour-science.org/installation-guide/) describes the recommended process. + + - type: textarea + attributes: + label: "Description" + description: > + Please describe the issue in a few short sentences. + validations: + required: true + + - type: textarea + attributes: + label: "Code for Reproduction" + description: > + If possible, please provide a minimum self-contained example reproducing the issue. + placeholder: | + << Your code here... >> + render: python + + - type: textarea + attributes: + label: "Exception Message" + description: > + If any, please paste the *full* exception message. + placeholder: | + << Full traceback starting from `Traceback (most recent call last):`... >> + render: shell + + - type: textarea + attributes: + label: "Environment Information" + description: If possible, please paste the output from `import colour; colour.utilities.describe_environment()`. + render: shell diff --git a/.github/ISSUE_TEMPLATE/documentation-improvement.yml b/.github/ISSUE_TEMPLATE/documentation-improvement.yml new file mode 100644 index 0000000..3680b71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-improvement.yml @@ -0,0 +1,40 @@ +name: Documentation Improvement +description: Report a documentation improvement. +title: "[DOCUMENTATION]: << Please use a comprehensive title... >>" +labels: [ Documentation ] + +body: + - type: markdown + attributes: + value: > + Thank you for taking the time to file a documentation improvement report. Before continuing, please take some time to check the existing [issues](https://github.com/colour-science/colour-visuals/issues). + + - type: input + attributes: + label: Documentation Link + description: > + Please link to any documentation or examples that you are referencing. Suggested improvements should be based on the [development version of the documentation](https://colour-visuals.readthedocs.io/en/develop/). + placeholder: > + << https://colour-visuals.readthedocs.io/en/develop/... >> + validations: + required: true + + - type: textarea + attributes: + label: Description + description: > + Please describe what is missing, unclear or incorrect. + validations: + required: true + + - type: textarea + attributes: + label: Suggested Improvement + description: > + Please describe how the documentation could be improved. + + - type: textarea + attributes: + label: "Environment Information" + description: If possible, please paste the output from `import colour; colour.utilities.describe_environment()`. + render: shell diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..6ead72d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,18 @@ +name: Feature Request +description: Suggest a new feature to implement. +title: "[FEATURE]: << Please use a comprehensive title... >>" +labels: [ Feature ] + +body: + - type: markdown + attributes: + value: > + Thank you for taking the time to file a feature request. Before continuing, please take some time to check the existing [issues](https://github.com/colour-science/colour-visuals/issues) and also the [draft release notes](https://gist.github.com/KelSolaar/4a6ebe9ec3d389f0934b154fec8df51d). + + - type: textarea + attributes: + label: "Description" + description: > + Please describe the new feature in a few short sentences. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..12dbeca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,17 @@ +name: Question +description: Ask a question. +title: "[DISCUSSION]: << Please use a comprehensive title... >>" +labels: [ Discussion ] + +body: + - type: markdown + attributes: + value: Thank you for taking the time to ask a question or discuss. Before continuing, we would be glad if you were to start this discussion in the dedicated [discussions](https://github.com/colour-science/colour-visuals/discussions) area. + + - type: textarea + attributes: + label: "Question" + description: > + If you are still here, please consider using the dedicated [discussions](https://github.com/colour-science/colour-visuals/discussions) area. + placeholder: > + << The discussions area is this way: https://github.com/colour-science/colour-visuals/discussions... >> diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..32e3642 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +# Summary + + + +# Preflight + + + +**Code Style and Quality** + +- [ ] Unit tests have been implemented and passed. +- [ ] Pyright static checking has been run and passed. +- [ ] Pre-commit hooks have been run and passed. + + + + +**Documentation** + +- [ ] New features are documented along with examples if relevant. +- [ ] The documentation is [Sphinx](https://www.sphinx-doc.org/en/master/) and [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant. + + diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..2614032 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: colour-science # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/continuous-integration-documentation.yml b/.github/workflows/continuous-integration-documentation.yml new file mode 100644 index 0000000..6877a5a --- /dev/null +++ b/.github/workflows/continuous-integration-documentation.yml @@ -0,0 +1,48 @@ +name: Continuous Integration - Documentation + +on: [push, pull_request] + +jobs: + continuous-integration-documentation: + name: ${{ matrix.os }} - Python ${{ matrix.python-version }} + strategy: + matrix: + os: [ubuntu-22.04] + python-version: [3.11] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + - name: Environment Variables + run: | + echo "CI_PYTHON_VERSION=${{ matrix.python-version }}" >> $GITHUB_ENV + echo "CI_PACKAGE=colour_visuals" >> $GITHUB_ENV + echo "CI_SHA=${{ github.sha }}" >> $GITHUB_ENV + echo "MPLBACKEND=AGG" >> $GITHUB_ENV + echo "COLOUR_SCIENCE__DOCUMENTATION_BUILD=True" >> $GITHUB_ENV + shell: bash + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get --yes install latexmk texlive-full + - name: Install Poetry + env: + POETRY_VERSION: 1.4.0 + run: | + curl -sSL https://install.python-poetry.org | POETRY_HOME=$HOME/.poetry python3 - + echo "$HOME/.poetry/bin" >> $GITHUB_PATH + shell: bash + - name: Install Package Dependencies + run: | + poetry run python -m pip install --upgrade pip + poetry install + poetry run python -c "import imageio;imageio.plugins.freeimage.download()" + shell: bash + - name: Build Documentation + run: | + poetry run invoke docs + shell: bash diff --git a/.github/workflows/continuous-integration-quality-unit-tests.yml b/.github/workflows/continuous-integration-quality-unit-tests.yml new file mode 100644 index 0000000..2d3ca71 --- /dev/null +++ b/.github/workflows/continuous-integration-quality-unit-tests.yml @@ -0,0 +1,62 @@ +name: Continuous Integration - Quality & Unit Tests + +on: [push, pull_request] + +jobs: + continuous-integration-quality-unit-tests: + name: ${{ matrix.os }} - Python ${{ matrix.python-version }} + strategy: + matrix: + os: [macOS-latest, ubuntu-22.04, windows-latest] + python-version: [3.9, '3.10', 3.11] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + with: + submodules: true + - name: Environment Variables + run: | + echo "CI_PYTHON_VERSION=${{ matrix.python-version }}" >> $GITHUB_ENV + echo "CI_PACKAGE=colour_visuals" >> $GITHUB_ENV + echo "CI_SHA=${{ github.sha }}" >> $GITHUB_ENV + echo "COVERALLS_REPO_TOKEN=${{ secrets.COVERALLS_REPO_TOKEN }}" >> $GITHUB_ENV + shell: bash + - name: Set up Python 3.9 for Pre-Commit + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Poetry + env: + POETRY_VERSION: 1.4.0 + run: | + curl -sSL https://install.python-poetry.org | POETRY_HOME=$HOME/.poetry python3 - + echo "$HOME/.poetry/bin" >> $GITHUB_PATH + shell: bash + - name: Install Package Dependencies + run: | + poetry run python -m pip install --upgrade pip + poetry install + poetry run python -c "import imageio;imageio.plugins.freeimage.download()" + shell: bash + - name: Pre-Commit (All Files) + run: | + poetry run pre-commit run --all-files + shell: bash + - name: Test Optimised Python Execution + run: | + poetry run python -OO -c "import $CI_PACKAGE" + shell: bash + - name: Test with Pytest + run: | + poetry run python -W ignore -m pytest --doctest-modules --ignore=$CI_PACKAGE/examples --cov=$CI_PACKAGE $CI_PACKAGE + shell: bash + - name: Upload Coverage to coveralls.io + if: matrix.os == 'macOS-latest' && matrix.python-version == '3.11' + run: | + if [ -z "$COVERALLS_REPO_TOKEN" ]; then echo \"COVERALLS_REPO_TOKEN\" secret is undefined!; else poetry run coveralls; fi + shell: bash diff --git a/.github/workflows/continuous-integration-static-type-checking.yml b/.github/workflows/continuous-integration-static-type-checking.yml new file mode 100644 index 0000000..91c5a17 --- /dev/null +++ b/.github/workflows/continuous-integration-static-type-checking.yml @@ -0,0 +1,29 @@ +name: Continuous Integration - Static Type Checking + +on: [push, pull_request] + +jobs: + continuous-integration-static-type-checking: + name: ${{ matrix.os }} - Python ${{ matrix.python-version }} + strategy: + matrix: + os: [macOS-latest] + python-version: [3.11] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + - name: Environment Variables + run: | + echo "CI_PACKAGE=colour_visuals" >> $GITHUB_ENV + shell: bash + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Package Dependencies + run: | + pip install -r requirements.txt + - name: Static Type Checking + run: | + pyright --skipunannotated diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6b3d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.egg-info +*.pyc +*.pyo +.DS_Store +.coverage* +.fleet +.idea +.ipynb_checkpoints +.sandbox +.vs +.vscode + +__pycache__ + +build +colour_visuals.egg-info +dist +docs/_build +docs/generated +poetry.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b287c09 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "colour_visuals/resources/colour-visuals-tests-datasets"] + path = colour_visuals/resources/colour-visuals-tests-datasets + url = https://github.com/colour-science/colour-visuals-tests-datasets.git +[submodule "colour_visuals/resources/colour-visuals-examples-datasets"] + path = colour_visuals/resources/colour-visuals-examples-datasets + url = https://github.com/colour-science/colour-visuals-examples-datasets.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b527df6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: +- repo: https://github.com/ikamensh/flynt/ + rev: '1.0.1' + hooks: + - id: flynt +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.285' + hooks: + - id: ruff +- repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black + language_version: python3.9 +- repo: https://github.com/keewis/blackdoc + rev: v0.3.8 + hooks: + - id: blackdoc + language_version: python3.9 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..c93eb8a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +formats: + - htmlzip + - pdf + +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..67ab8e5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,51 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting Thomas Mansencal and Michael Mauderer via email at thomas@colour-science.org and michael@colour-science.org respectively. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][homepage]. + +For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq][faq]. + + +[homepage]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +[faq]: https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst new file mode 100644 index 0000000..b5bd688 --- /dev/null +++ b/CONTRIBUTORS.rst @@ -0,0 +1,20 @@ +Contributors +============ + +Development & Technical Support +------------------------------- + +- **Thomas Mansencal**, *Technology Supervisor @ Wētā FX* + + Project coordination, overall development. + +Issues & Discussions +-------------------- + +About +----- + +| **Colour - Visuals** by Colour Developers +| Copyright 2023 Colour Developers – `colour-developers@colour-science.org `__ +| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause +| `https://github.com/colour-science/colour-visuals `__ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..83ed44a --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +Copyright 2023 Colour Developers + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 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 diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8addf40 --- /dev/null +++ b/README.rst @@ -0,0 +1,89 @@ +Colour - Visuals +================ + +.. start-badges + +|actions| |coveralls| |codacy| |version| + +.. |actions| image:: https://img.shields.io/github/actions/workflow/status/colour-science/colour-visuals/.github/workflows/continuous-integration-quality-unit-tests.yml?branch=develop&style=flat-square + :target: https://github.com/colour-science/colour-visuals/actions + :alt: Develop Build Status +.. |coveralls| image:: http://img.shields.io/coveralls/colour-science/colour-visuals/develop.svg?style=flat-square + :target: https://coveralls.io/r/colour-science/colour-visuals + :alt: Coverage Status +.. |codacy| image:: https://img.shields.io/codacy/grade/2862b4f2217742ae83c972d7e3af44d7/develop.svg?style=flat-square + :target: https://www.codacy.com/app/colour-science/colour-visuals + :alt: Code Grade +.. |version| image:: https://img.shields.io/pypi/v/colour-visuals.svg?style=flat-square + :target: https://pypi.org/project/colour-visuals + :alt: Package Version + +.. end-badges + +A `Python `__ package implementing various +`WebGPU-based `__ visuals for colour science applications. + +It is open source and freely available under the +`BSD-3-Clause `__ terms. + +.. contents:: **Table of Contents** + :backlinks: none + :depth: 2 + +.. sectnum:: + +Features +-------- + +Examples +^^^^^^^^ + +User Guide +---------- + +Installation +^^^^^^^^^^^^ + +Primary Dependencies +~~~~~~~~~~~~~~~~~~~~ + +Pypi +~~~~ + +Contributing +^^^^^^^^^^^^ + +If you would like to contribute to `Colour - Visuals `__, +please refer to the following `Contributing `__ +guide for `Colour `__. + +API Reference +------------- + +The main technical reference for `Colour - Visuals `__ +is the `API Reference `__. + +Code of Conduct +--------------- + +The *Code of Conduct*, adapted from the `Contributor Covenant 1.4 `__, +is available on the `Code of Conduct `__ page. + +Contact & Social +---------------- + +The *Colour Developers* can be reached via different means: + +- `Email `__ +- `Facebook `__ +- `Github Discussions `__ +- `Gitter `__ +- `Twitter `__ + +About +----- + +| **Colour - Visuals** by Colour Developers +| Copyright 2023 Colour Developers – `colour-developers@colour-science.org `__ +| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause +| `https://github.com/colour-science/colour-visuals `__ diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..401525d --- /dev/null +++ b/TODO.rst @@ -0,0 +1,17 @@ +Colour - Visuals - TODO +=========================== + +TODO +---- + +- colour_visuals/__init__.py + + - Line 82 : # TODO: Remove legacy printing support when deemed appropriate. + +About +----- + +| **Colour - Visuals** by Colour Developers +| Copyright 2023 Colour Developers - `colour-developers@colour-science.org `__ +| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause +| `https://github.com/colour-science/colour-visuals `__ diff --git a/colour_visuals/__init__.py b/colour_visuals/__init__.py new file mode 100644 index 0000000..2fbca54 --- /dev/null +++ b/colour_visuals/__init__.py @@ -0,0 +1,56 @@ +""" +Colour - Visuals +================ + +WebGPU-based visuals for colour science applications. +""" + +from __future__ import annotations + +import contextlib +import numpy as np +import os +import subprocess + +import colour + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [] + +__application_name__ = "Colour - Visuals" + +__major_version__ = "0" +__minor_version__ = "1" +__change_version__ = "0" +__version__ = ".".join( + (__major_version__, __minor_version__, __change_version__) +) + +try: + _version: str = ( + subprocess.check_output( + ["git", "describe"], # noqa: S603, S607 + cwd=os.path.dirname(__file__), + stderr=subprocess.STDOUT, + ) + .strip() + .decode("utf-8") + ) +except Exception: + _version: str = __version__ + +colour.utilities.ANCILLARY_COLOUR_SCIENCE_PACKAGES[ # pyright: ignore + "colour-visuals" +] = _version + +del _version + +# TODO: Remove legacy printing support when deemed appropriate. +with contextlib.suppress(TypeError): + np.set_printoptions(legacy="1.13") diff --git a/colour_visuals/common.py b/colour_visuals/common.py new file mode 100644 index 0000000..26fccc0 --- /dev/null +++ b/colour_visuals/common.py @@ -0,0 +1,96 @@ +""" +Common Utilities +================ + +Defines the common utilities objects that don't fall in any specific category. +""" + +from __future__ import annotations + +import numpy as np + +from colour.graph import convert +from colour.hints import ArrayLike, NDArray, Tuple +from colour.models import ( + XYZ_to_Jzazbz, + XYZ_to_OSA_UCS, +) +from colour.utilities import full + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [ + "DEFAULT_FLOAT_DTYPE_WGPU", + "DEFAULT_INT_DTYPE_WGPU", + "XYZ_to_colourspace_model", + "as_contiguous_array", + "conform_primitive_dtype", + "append_alpha_channel", +] + +DEFAULT_FLOAT_DTYPE_WGPU = np.float32 +DEFAULT_INT_DTYPE_WGPU = np.uint32 + + +def XYZ_to_colourspace_model( + XYZ: ArrayLike, illuminant: ArrayLike, model: str, **kwargs +) -> NDArray: + """ + Converts from *CIE XYZ* tristimulus values to given colourspace model while + normalising for visual convenience some of the models. + """ + + ijk = convert( + XYZ, + "CIE XYZ", + model, + illuminant=illuminant, + verbose={"mode": "Short"}, + **kwargs, + ) + + # TODO: ICtCp? + if model == "JzAzBz": + ijk /= XYZ_to_Jzazbz([1, 1, 1])[0] + elif model == "OSA UCS": + ijk /= XYZ_to_OSA_UCS([1, 1, 1])[0] + + return ijk + + +def as_contiguous_array(a, dtype=DEFAULT_FLOAT_DTYPE_WGPU): + return np.ascontiguousarray(a.astype(dtype)) + + +def conform_primitive_dtype( + primitive: Tuple[NDArray, NDArray, NDArray] +) -> Tuple[NDArray, NDArray, NDArray]: + """ + Conform the given primitive to the required dtype. + """ + + vertices, faces, outline = primitive + + return ( + vertices.astype( + [ + ("position", DEFAULT_FLOAT_DTYPE_WGPU, (3,)), + ("uv", DEFAULT_FLOAT_DTYPE_WGPU, (2,)), + ("normal", DEFAULT_FLOAT_DTYPE_WGPU, (3,)), + ("colour", DEFAULT_FLOAT_DTYPE_WGPU, (4,)), + ] + ), + faces.astype(DEFAULT_INT_DTYPE_WGPU), + outline.astype(DEFAULT_INT_DTYPE_WGPU), + ) + + +def append_alpha_channel(a: ArrayLike, alpha: float = 1) -> NDArray: + a = np.copy(a) + + return np.hstack([a, full(list(a.shape[:-1]) + [1], alpha, dtype=a.dtype)]) diff --git a/colour_visuals/diagrams.py b/colour_visuals/diagrams.py new file mode 100644 index 0000000..72efdf2 --- /dev/null +++ b/colour_visuals/diagrams.py @@ -0,0 +1,454 @@ +# !/usr/bin/env python +""" +Chromaticity Diagram Visuals +============================ + +Defines the *chromaticity diagram* visuals: + +- :class:`colour_visuals.VisualSpectralLocus2D` +- :class:`colour_visuals.VisualSpectralLocus3D` +- :class:`colour_visuals.VisualChromaticityDiagram` +- :class:`colour_visuals.VisualChromaticityDiagramCIE1931` +- :class:`colour_visuals.VisualChromaticityDiagramCIE1960UCS` +- :class:`colour_visuals.VisualChromaticityDiagramCIE1976UCS` +""" + +from __future__ import annotations + +import numpy as np +import pygfx as gfx +from scipy.spatial import Delaunay + +from colour.algebra import euclidean_distance, normalise_maximum +from colour.colorimetry import MultiSpectralDistributions +from colour.hints import ArrayLike, Optional, Sequence, Type, cast +from colour.models import XYZ_to_RGB +from colour.plotting import ( + CONSTANTS_COLOUR_STYLE, + LABELS_CHROMATICITY_DIAGRAM_DEFAULT, + METHODS_CHROMATICITY_DIAGRAM, + XYZ_to_plotting_colourspace, + colourspace_model_axis_reorder, + filter_cmfs, +) +from colour.plotting.diagrams import lines_spectral_locus +from colour.utilities import ( + first_item, + full, + optional, + tstack, + validate_method, +) + +from colour_visuals.common import ( + DEFAULT_FLOAT_DTYPE_WGPU, + DEFAULT_INT_DTYPE_WGPU, + append_alpha_channel, + as_contiguous_array, + XYZ_to_colourspace_model, +) + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [ + "VisualSpectralLocus2D", + "VisualChromaticityDiagram", + "VisualChromaticityDiagramCIE1931", + "VisualChromaticityDiagramCIE1960UCS", + "VisualChromaticityDiagramCIE1976UCS", +] + + +class VisualSpectralLocus2D(gfx.Group): + """ + Create a *Spectral Locus* 2D visual. + """ + + def __init__( + self, + cmfs: str = "CIE 1931 2 Degree Standard Observer", + method: str = "CIE 1931", + labels: Sequence | None = None, + colors: ArrayLike | None = None, + opacity: float = 1, + thickness: float = 1, + ): + super().__init__() + + cmfs = cast( + MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()) + ) + + method = validate_method(method, tuple(METHODS_CHROMATICITY_DIAGRAM)) + + labels = optional(labels, LABELS_CHROMATICITY_DIAGRAM_DEFAULT[method]) + lines_sl, lines_w = lines_spectral_locus(cmfs, labels, method) + + positions = np.concatenate( + [lines_sl["position"][:-1], lines_sl["position"][1:]], axis=1 + ).reshape([-1, 2]) + + positions = np.hstack( + [ + positions, + np.full((positions.shape[0], 1), 0, DEFAULT_FLOAT_DTYPE_WGPU), + ] + ) + + if colors is None: + colors_sl = np.concatenate( + [lines_sl["colour"][:-1], lines_sl["colour"][1:]], axis=1 + ).reshape([-1, 3]) + else: + colors_sl = np.tile(colors, (positions.shape[0], 1)) + + self._spectral_locus = gfx.Line( + gfx.Geometry( + positions=as_contiguous_array(positions), + colors=as_contiguous_array( + append_alpha_channel(colors_sl, opacity) + ), + ), + gfx.LineSegmentMaterial(thickness=thickness, color_mode="vertex"), + ) + self.add(self._spectral_locus) + + positions = lines_w["position"] + positions = np.hstack( + [ + positions, + np.full((positions.shape[0], 1), 0, DEFAULT_FLOAT_DTYPE_WGPU), + ] + ) + + if colors is None: + colors_w = lines_w["colour"] + else: + colors_w = np.tile(colors, (positions.shape[0], 1)) + + self._line_wavelengths = gfx.Line( + gfx.Geometry( + positions=as_contiguous_array(positions), + colors=as_contiguous_array( + append_alpha_channel(colors_w, opacity) + ), + ), + gfx.LineSegmentMaterial(thickness=thickness, color_mode="vertex"), + ) + self.add(self._line_wavelengths) + + self._labels = [] + for i, label in enumerate( + [label for label in labels if label in cmfs.wavelengths] + ): + positions = lines_w["position"][::2] + normals = lines_w["normal"][::2] + + text = gfx.Text( + gfx.TextGeometry( + str(label), + font_size=CONSTANTS_COLOUR_STYLE.font_size.medium, + screen_space=True, + anchor="Center-Left" + if lines_w["normal"][::2][i, 0] >= 0 + else "Center-Right", + ), + gfx.TextMaterial(color=CONSTANTS_COLOUR_STYLE.colour.light), + ) + text.local.position = np.array( + [ + positions[i, 0] + normals[i, 0] * 1.5, + positions[i, 1] + normals[i, 1] * 1.5, + 0, + ] + ) + self._labels.append(text) + self.add(text) + + positions = np.hstack( + [ + lines_w["position"][::2], + np.full( + (lines_w["position"][::2].shape[0], 1), + 0, + DEFAULT_FLOAT_DTYPE_WGPU, + ), + ] + ) + + if colors is None: + colors_lp = lines_w["colour"][::2] + else: + colors_lp = np.tile(colors, (positions.shape[0], 1)) + + self._label_points = gfx.Points( + gfx.Geometry( + positions=as_contiguous_array(positions), + sizes=as_contiguous_array( + full(lines_w["position"][::2].shape[0], thickness * 5) + ), + colors=as_contiguous_array( + append_alpha_channel(colors_lp, opacity) + ), + ), + gfx.PointsMaterial(color_mode="vertex", vertex_sizes=True), + ) + self.add(self._label_points) + + +class VisualSpectralLocus3D(gfx.Line): + """ + Create a *Spectral Locus* 3D visual. + """ + + def __init__( + self, + cmfs: str = "CIE 1931 2 Degree Standard Observer", + colourspace_model: str = "CIE xyY", + colors: ArrayLike | None = None, + opacity: float = 1, + thickness: float = 1, + ): + super().__init__() + + cmfs = cast( + MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()) + ) + + colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace + + positions = colourspace_model_axis_reorder( + XYZ_to_colourspace_model( + cmfs.values, colourspace.whitepoint, colourspace_model + ), + colourspace_model, + ) + positions = np.concatenate( + [positions[:-1], positions[1:]], axis=1 + ).reshape([-1, 3]) + + if colors is None: + colors = XYZ_to_RGB(cmfs.values, colourspace) + colors = np.concatenate([colors[:-1], colors[1:]], axis=1).reshape( + [-1, 3] + ) + else: + colors = np.tile(colors, (positions.shape[0], 1)) + + super().__init__( + gfx.Geometry( + positions=as_contiguous_array(positions), + colors=as_contiguous_array( + append_alpha_channel(colors, opacity) + ), + ), + gfx.LineSegmentMaterial(thickness=thickness, color_mode="vertex"), + ) + + +class VisualChromaticityDiagram(gfx.Mesh): + """ + Create a *chromaticity diagram* visual. + """ + + def __init__( + self, + samples=64, + cmfs: str = "CIE 1931 2 Degree Standard Observer", + method: str = "CIE 1931", + material: Type[gfx.MeshAbstractMaterial] = gfx.MeshBasicMaterial, + colors: Optional[ArrayLike] = None, + opacity: float = 1, + wireframe: bool = False, + ): + cmfs = first_item(filter_cmfs(cmfs).values()) + + illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint + + XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"] + ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"] + + # CMFS + ij_l = XYZ_to_ij(cmfs.values, illuminant) + + # Line of Purples + d = euclidean_distance(ij_l[0], ij_l[-1]) + ij_p = tstack( + [ + np.linspace(ij_l[0][0], ij_l[-1][0], int(d * samples)), + np.linspace(ij_l[0][1], ij_l[-1][1], int(d * samples)), + ] + ) + + # Grid + triangulation = Delaunay(ij_l, qhull_options="QJ") + samples = np.linspace(0, 1, samples) + ii_g, jj_g = np.meshgrid(samples, samples) + ij_g = tstack([ii_g, jj_g]) + ij_g = ij_g[triangulation.find_simplex(ij_g) > 0] + + ij = np.vstack([ij_l, illuminant, ij_p, ij_g]) + triangulation = Delaunay(ij, qhull_options="QJ") + positions = np.hstack( + [ij, np.full((ij.shape[0], 1), 0, DEFAULT_FLOAT_DTYPE_WGPU)] + ) + + if colors is None: + colors = normalise_maximum( + XYZ_to_plotting_colourspace( + ij_to_XYZ(positions[..., :2], illuminant), illuminant + ), + axis=-1, + ) + else: + colors = np.tile(colors, (positions.shape[0], 1)) + + geometry = gfx.Geometry( + positions=as_contiguous_array(positions), + indices=as_contiguous_array( + triangulation.simplices, DEFAULT_INT_DTYPE_WGPU + ), + colors=as_contiguous_array(append_alpha_channel(colors, opacity)), + ) + + super().__init__( + geometry, + material(color_mode="vertex", wireframe=wireframe) + if wireframe + else material(color_mode="vertex"), + ) + + +class VisualChromaticityDiagramCIE1931(gfx.Group): + """ + Create the *CIE 1931* chromaticity diagram visual. + """ + + def __init__( + self, + kwargs_visual_chromaticity_diagram: Optional[dict] = None, + kwargs_visual_spectral_locus: Optional[dict] = None, + ): + super().__init__() + + self._chromaticity_diagram = VisualChromaticityDiagram( + method="CIE 1931", + **(optional(kwargs_visual_chromaticity_diagram, {})), + ) + self.add(self._chromaticity_diagram) + + self._spectral_locus = VisualSpectralLocus2D( + method="CIE 1931", **(optional(kwargs_visual_spectral_locus, {})) + ) + self.add(self._spectral_locus) + + +class VisualChromaticityDiagramCIE1960UCS(gfx.Group): + """ + Create the *CIE 1960 UCS* chromaticity diagram visual. + """ + + def __init__( + self, + kwargs_visual_chromaticity_diagram: Optional[dict] = None, + kwargs_visual_spectral_locus: Optional[dict] = None, + ): + super().__init__() + + self._chromaticity_diagram = VisualChromaticityDiagram( + method="CIE 1960 UCS", + **(optional(kwargs_visual_chromaticity_diagram, {})), + ) + self.add(self._chromaticity_diagram) + + self._spectral_locus = VisualSpectralLocus2D( + method="CIE 1960 UCS", + **(optional(kwargs_visual_spectral_locus, {})), + ) + self.add(self._spectral_locus) + + +class VisualChromaticityDiagramCIE1976UCS(gfx.Group): + """ + Create the *CIE 1976 UCS* chromaticity diagram visual. + """ + + def __init__( + self, + kwargs_visual_chromaticity_diagram: Optional[dict] = None, + kwargs_visual_spectral_locus: Optional[dict] = None, + ): + super().__init__() + + self._chromaticity_diagram = VisualChromaticityDiagram( + method="CIE 1976 UCS", + **(optional(kwargs_visual_chromaticity_diagram, {})), + ) + self.add(self._chromaticity_diagram) + + self._spectral_locus = VisualSpectralLocus2D( + method="CIE 1976 UCS", + **(optional(kwargs_visual_spectral_locus, {})), + ) + self.add(self._spectral_locus) + + +if __name__ == "__main__": + from pygfx import ( + Background, + BackgroundMaterial, + Display, + Scene, + ) + + scene = Scene() + + scene.add( + Background(None, BackgroundMaterial(np.array([0.18, 0.18, 0.18]))) + ) + + mesh_1 = VisualChromaticityDiagramCIE1931() + scene.add(mesh_1) + + mesh_2 = VisualChromaticityDiagramCIE1931( + kwargs_visual_chromaticity_diagram={"wireframe": True, "opacity": 0.5} + ) + mesh_2.local.position = np.array([1, 0, 0]) + scene.add(mesh_2) + + mesh_3 = VisualChromaticityDiagramCIE1931( + kwargs_visual_chromaticity_diagram={"colors": [0.36, 0.36, 0.36]} + ) + mesh_3.local.position = np.array([2, 0, 0]) + scene.add(mesh_3) + + mesh_4 = VisualChromaticityDiagramCIE1960UCS() + mesh_4.local.position = np.array([3, 0, 0]) + scene.add(mesh_4) + + mesh_5 = VisualChromaticityDiagramCIE1976UCS() + mesh_5.local.position = np.array([4, 0, 0]) + scene.add(mesh_5) + + mesh_6 = VisualSpectralLocus2D(colors=[0.36, 0.36, 0.36]) + mesh_6.local.position = np.array([5, 0, 0]) + scene.add(mesh_6) + + mesh_7 = VisualSpectralLocus3D() + scene.add(mesh_7) + + mesh_8 = VisualSpectralLocus3D(colors=[0.36, 0.36, 0.36]) + mesh_8.local.position = np.array([5, 0, 0]) + scene.add(mesh_8) + + mesh_9 = VisualSpectralLocus3D(colourspace_model="CIE XYZ") + mesh_9.local.position = np.array([6, 0, 0]) + scene.add(mesh_9) + + display = Display() + display.show(scene, up=np.array([0, 0, 1])) diff --git a/colour_visuals/grid.py b/colour_visuals/grid.py new file mode 100644 index 0000000..bf0e738 --- /dev/null +++ b/colour_visuals/grid.py @@ -0,0 +1,216 @@ +# !/usr/bin/env python +""" +Grid Visuals +============ + +Defines the grid visuals: + +- :class:`colour_visuals.VisualGrid` +""" + +from __future__ import annotations + +import numpy as np +import pygfx as gfx + +from colour.hints import ArrayLike +from colour.geometry import primitive_grid +from colour.plotting import CONSTANTS_COLOUR_STYLE + +from colour_visuals.common import ( + DEFAULT_FLOAT_DTYPE_WGPU, + append_alpha_channel, + conform_primitive_dtype, + as_contiguous_array, +) + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = ["VisualGrid"] + + +class VisualGrid(gfx.Group): + """ + Create a *RGB* 3D scatter visual. + """ + + def __init__( + self, + size: int = 20, + major_grid_color: ArrayLike = np.array([0.5, 0.5, 0.5]), + minor_grid_color: ArrayLike = np.array([0.25, 0.25, 0.25]), + major_tick_labels=True, + major_tick_label_color: ArrayLike = np.array([0.75, 0.75, 0.75]), + minor_tick_labels=True, + minor_tick_label_color: ArrayLike = np.array([0.5, 0.5, 0.5]), + ): + super().__init__() + + size = int(size) + + vertices, faces, outline = conform_primitive_dtype( + primitive_grid( + width_segments=size, + height_segments=size, + ) + ) + positions = vertices["position"] + + self._grid_major = gfx.Mesh( + gfx.Geometry( + positions=as_contiguous_array(positions), + indices=outline[..., 1].reshape([-1, 4]), + colors=as_contiguous_array( + append_alpha_channel( + np.tile(major_grid_color, (positions.shape[0], 1)), 1 + ) + ), + ), + gfx.MeshBasicMaterial(color_mode="vertex", wireframe=True), + ) + self._grid_major.local.scale = np.array([size, size, 1]) + self.add(self._grid_major) + + vertices, faces, outline = conform_primitive_dtype( + primitive_grid( + width_segments=size * 10, + height_segments=size * 10, + ) + ) + positions = vertices["position"] + + self._grid_minor = gfx.Mesh( + gfx.Geometry( + positions=as_contiguous_array(positions), + indices=outline[..., 1].reshape([-1, 4]), + colors=as_contiguous_array( + append_alpha_channel( + np.tile(minor_grid_color, (positions.shape[0], 1)), 1 + ) + ), + ), + gfx.MeshBasicMaterial(color_mode="vertex", wireframe=True), + ) + self._grid_minor.local.position = np.array([0, 0, -1e-3]) + self._grid_minor.local.scale = np.array([size, size, 1]) + self.add(self._grid_minor) + + axes_positions = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 0, 0], + [0, 1, 0], + ], + dtype=DEFAULT_FLOAT_DTYPE_WGPU, + ) + axes_positions *= size / 2 + + axes_colors = np.array( + [ + [1, 0, 0, 1], + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 1, 0, 1], + ], + dtype=DEFAULT_FLOAT_DTYPE_WGPU, + ) + + self._axes_helper = gfx.Line( + gfx.Geometry(positions=axes_positions, colors=axes_colors), + gfx.LineSegmentMaterial(color_mode="vertex", thickness=2), + ) + self.add(self._axes_helper) + + if major_tick_labels: + self._ticks_major_x, self._ticks_major_y = [], [] + for i in np.arange(-size // 2, size // 2 + 1, 1): + x_text = gfx.Text( + gfx.TextGeometry( + f"{i} " if i == 0 else str(i), + font_size=CONSTANTS_COLOUR_STYLE.font_size.medium, + screen_space=True, + anchor="Top-Right" if i == 0 else "Top-Center", + ), + gfx.TextMaterial(color=major_tick_label_color), + ) + x_text.local.position = np.array([i, 0, 1e-3]) + self.add(x_text) + self._ticks_major_x.append(x_text) + + if i == 0: + continue + + y_text = gfx.Text( + gfx.TextGeometry( + f"{i} ", + font_size=CONSTANTS_COLOUR_STYLE.font_size.medium, + screen_space=True, + anchor="Center-Right", + ), + gfx.TextMaterial(color=major_tick_label_color), + ) + y_text.local.position = np.array([0, i, 1e-3]) + self.add(y_text) + self._ticks_major_y.append(y_text) + + if minor_tick_labels: + self._ticks_minor_x, self._ticks_minor_y = [], [] + for i in np.arange(-size // 2, size // 2 + 0.1, 0.1): + if np.around(i, 0) == np.around(i, 1): + continue + + i = np.around(i, 1) + + x_text = gfx.Text( + gfx.TextGeometry( + f"{i} " if i == 0 else str(i), + font_size=CONSTANTS_COLOUR_STYLE.font_size.small, + screen_space=True, + anchor="Top-Right" if i == 0 else "Top-Center", + ), + gfx.TextMaterial(color=minor_tick_label_color), + ) + x_text.local.position = np.array([i, 0, 1e-3]) + self.add(x_text) + self._ticks_minor_x.append(x_text) + + if i == 0: + continue + + y_text = gfx.Text( + gfx.TextGeometry( + f"{i} ", + font_size=CONSTANTS_COLOUR_STYLE.font_size.small, + screen_space=True, + anchor="Center-Right", + ), + gfx.TextMaterial(color=minor_tick_label_color), + ) + y_text.local.position = np.array([0, i, 1e-3]) + self.add(y_text) + self._ticks_minor_y.append(y_text) + + +if __name__ == "__main__": + from pygfx import ( + Background, + Display, + BackgroundMaterial, + Scene, + ) + + scene = Scene() + + scene.add(Background(None, BackgroundMaterial(np.array([0, 0, 0])))) + + grid_1 = VisualGrid() + scene.add(grid_1) + + display = Display() + display.show(scene, up=np.array([0, 0, 1])) diff --git a/colour_visuals/py.typed b/colour_visuals/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/colour_visuals/rgb_colourspace.py b/colour_visuals/rgb_colourspace.py new file mode 100644 index 0000000..8340f2d --- /dev/null +++ b/colour_visuals/rgb_colourspace.py @@ -0,0 +1,211 @@ +# !/usr/bin/env python +""" +RGB Colourspace Visuals +======================= + +Defines the *RGB colourspace* visuals: + +- :class:`colour_visuals.VisualRGBColourspace2D` +- :class:`colour_visuals.VisualRGBColourspace3D` +""" + +from __future__ import annotations + +import numpy as np +import pygfx as gfx +from colour.constants import EPSILON +from colour.geometry import primitive_cube +from colour.hints import ArrayLike, Optional, Type +from colour.models import RGB_to_XYZ, XYZ_to_RGB, xy_to_XYZ +from colour.plotting import ( + CONSTANTS_COLOUR_STYLE, + METHODS_CHROMATICITY_DIAGRAM, + colourspace_model_axis_reorder, + filter_RGB_colourspaces, +) +from colour.utilities import first_item + +from colour_visuals.common import ( + append_alpha_channel, + as_contiguous_array, + conform_primitive_dtype, + XYZ_to_colourspace_model, +) + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [ + "VisualRGBColourspace2D", + "VisualRGBColourspace3D", +] + + +class VisualRGBColourspace2D(gfx.Line): + """ + Create a *RGB* colourspace 2D gamut visual. + """ + + def __init__( + self, + colourspace: str = "ITU-R BT.709", + chromaticity_diagram: str = "CIE 1931", + colors: Optional[ArrayLike] = None, + opacity: float = 0.5, + thickness: float = 1, + ): + colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) + + plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace + + XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[chromaticity_diagram][ + "XYZ_to_ij" + ] + + ij = XYZ_to_ij( + xy_to_XYZ(colourspace.primaries), plotting_colourspace.whitepoint + ) + ij[np.isnan(ij)] = 0 + + positions = append_alpha_channel( + np.array([ij[0], ij[1], ij[1], ij[2], ij[2], ij[0]]), 0 + ) + + if colors is None: + RGB = XYZ_to_RGB( + xy_to_XYZ(colourspace.primaries), plotting_colourspace + ) + colors = np.array([RGB[0], RGB[1], RGB[1], RGB[2], RGB[2], RGB[0]]) + else: + colors = np.tile(colors, (positions.shape[0], 1)) + + geometry = gfx.Geometry( + positions=as_contiguous_array(positions), + colors=as_contiguous_array(append_alpha_channel(colors, opacity)), + ) + + super().__init__( + geometry, + gfx.LineSegmentMaterial(thickness=thickness, color_mode="vertex"), + ) + + +class VisualRGBColourspace3D(gfx.Mesh): + """ + Create a *RGB* colourspace 3D volume visual. + """ + + def __init__( + self, + colourspace: str = "ITU-R BT.709", + colourspace_model: str = "CIE xyY", + segments: int = 16, + material: Type[gfx.MeshAbstractMaterial] = gfx.MeshBasicMaterial, + colors: Optional[ArrayLike] = None, + opacity: float = 0.5, + wireframe: bool = False, + ): + colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) + + vertices, faces, outline = conform_primitive_dtype( + primitive_cube( + width_segments=segments, + height_segments=segments, + depth_segments=segments, + ) + ) + + positions = vertices["position"] + 0.5 + + if colors is None: + colors = positions + else: + colors = np.tile(colors, (positions.shape[0], 1)) + + positions[positions == 0] = EPSILON + XYZ = RGB_to_XYZ(positions, colourspace) + positions = colourspace_model_axis_reorder( + XYZ_to_colourspace_model( + XYZ, colourspace.whitepoint, colourspace_model + ), + colourspace_model, + ) + + geometry = gfx.Geometry( + positions=as_contiguous_array(positions), + normals=vertices["normal"], + indices=outline[..., 1].reshape([-1, 4]), + colors=as_contiguous_array(append_alpha_channel(colors, opacity)), + ) + + super().__init__( + geometry, + material(color_mode="vertex", wireframe=wireframe) + if wireframe + else material(color_mode="vertex"), + ) + + +if __name__ == "__main__": + from pygfx import ( + AmbientLight, + Background, + BackgroundMaterial, + DirectionalLight, + Display, + MeshStandardMaterial, + MeshNormalMaterial, + Scene, + ) + + scene = Scene() + + scene.add( + Background(None, BackgroundMaterial(np.array([0.18, 0.18, 0.18]))) + ) + + light_1 = AmbientLight() + scene.add(light_1) + + light_2 = DirectionalLight() + light_2.local.position = np.array([1, 1, 0]) + scene.add(light_2) + + mesh_1 = VisualRGBColourspace3D() + scene.add(mesh_1) + + mesh_2 = VisualRGBColourspace3D(wireframe=True) + mesh_2.local.position = np.array([0.5, 0, 0]) + scene.add(mesh_2) + + mesh_3 = VisualRGBColourspace3D(material=MeshNormalMaterial) + mesh_3.local.position = np.array([1, 0, 0]) + scene.add(mesh_3) + + mesh_4 = VisualRGBColourspace3D( + colourspace_model="CIE Lab", + colors=np.array([0.36, 0.36, 0.36]), + opacity=1, + material=MeshStandardMaterial, + ) + mesh_4.local.position = np.array([2.5, 0, 0]) + scene.add(mesh_4) + + mesh_5 = VisualRGBColourspace2D() + mesh_5.local.position = np.array([3.5, 0, 0]) + scene.add(mesh_5) + + mesh_6 = VisualRGBColourspace2D( + chromaticity_diagram="CIE 1976 UCS", + colors=np.array([0.36, 0.36, 0.36]), + opacity=1, + ) + mesh_6.local.position = np.array([4.5, 0, 0]) + scene.add(mesh_6) + + display = Display() + display.show(scene, up=np.array([0, 0, 1])) diff --git a/colour_visuals/rgb_scatter.py b/colour_visuals/rgb_scatter.py new file mode 100644 index 0000000..6e94cab --- /dev/null +++ b/colour_visuals/rgb_scatter.py @@ -0,0 +1,113 @@ +# !/usr/bin/env python +""" +RGB Scatter Visuals +=================== + +Defines the *RGB* scatter visuals: + +- :class:`colour_visuals.VisualRGBScatter3D` +""" + +from __future__ import annotations + +import numpy as np +import pygfx as gfx +from colour import RGB_to_XYZ +from colour.constants import EPSILON +from colour.hints import ArrayLike, Optional, Type +from colour.plotting import ( + colourspace_model_axis_reorder, + filter_RGB_colourspaces, +) +from colour.utilities import as_float_array, first_item + +from colour_visuals.common import ( + append_alpha_channel, + as_contiguous_array, + XYZ_to_colourspace_model, +) + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = ["VisualRGBScatter3D"] + + +class VisualRGBScatter3D(gfx.Points): + """ + Create a *RGB* 3D scatter visual. + """ + + def __init__( + self, + RGB: ArrayLike, + colourspace: str = "ITU-R BT.709", + colourspace_model: str = "CIE xyY", + size: float = 3.0, + material: Type[gfx.MeshAbstractMaterial] = gfx.PointsMaterial, + colors: Optional[ArrayLike] = None, + opacity: float = 0.5, + ): + colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) + + RGB = as_float_array(RGB).reshape(-1, 3) + + RGB[RGB == 0] = EPSILON + + XYZ = RGB_to_XYZ(RGB, colourspace) + + positions = colourspace_model_axis_reorder( + XYZ_to_colourspace_model( + XYZ, colourspace.whitepoint, colourspace_model + ), + colourspace_model, + ) + + if colors is None: + colors = RGB + else: + colors = np.tile(colors, (RGB.shape[0], 1)) + + geometry = gfx.Geometry( + positions=as_contiguous_array(positions), + sizes=as_contiguous_array(np.full(RGB.shape[0], size)), + colors=as_contiguous_array(append_alpha_channel(colors, opacity)), + ) + + super().__init__( + geometry, + material(color_mode="vertex", vertex_sizes=True) + if material is gfx.PointsMaterial + else material(), + ) + + +if __name__ == "__main__": + from pygfx import ( + Background, + Display, + BackgroundMaterial, + Scene, + ) + + scene = Scene() + + scene.add( + Background(None, BackgroundMaterial(np.array([0.18, 0.18, 0.18]))) + ) + + scatter_1 = VisualRGBScatter3D(np.random.random((64, 64, 3))) + scene.add(scatter_1) + + scatter_2 = VisualRGBScatter3D( + np.random.random((64, 64, 3)), colors=np.array([0.36, 0.36, 0.36]) + ) + scatter_2.local.position = np.array([0.5, 0, 0]) + scene.add(scatter_2) + + display = Display() + display.show(scene, up=np.array([0, 0, 1])) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..8ca604b --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/colour_visuals.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/colour_visuals.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/colour_visuals" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/colour_visuals" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_static/Demosaicing_001.png b/docs/_static/Demosaicing_001.png new file mode 100644 index 0000000..8c7e949 Binary files /dev/null and b/docs/_static/Demosaicing_001.png differ diff --git a/docs/_static/Logo_Dark_001.svg b/docs/_static/Logo_Dark_001.svg new file mode 100644 index 0000000..eb0ff27 --- /dev/null +++ b/docs/_static/Logo_Dark_001.svg @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/docs/_static/Logo_Light_001.svg b/docs/_static/Logo_Light_001.svg new file mode 100644 index 0000000..3d7da27 --- /dev/null +++ b/docs/_static/Logo_Light_001.svg @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/docs/_static/Logo_Medium_001.png b/docs/_static/Logo_Medium_001.png new file mode 100644 index 0000000..ca33043 Binary files /dev/null and b/docs/_static/Logo_Medium_001.png differ diff --git a/docs/_static/Logo_Small_001.png b/docs/_static/Logo_Small_001.png new file mode 100644 index 0000000..0fc3bb3 Binary files /dev/null and b/docs/_static/Logo_Small_001.png differ diff --git a/docs/colour_visuals.rst b/docs/colour_visuals.rst new file mode 100644 index 0000000..c5ffb0c --- /dev/null +++ b/docs/colour_visuals.rst @@ -0,0 +1,7 @@ +Colour - Visuals +================ + +.. toctree:: + :maxdepth: 3 + + colour_visuals diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..dad55d3 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,189 @@ +""" +Colour - Visuals - Documentation Configuration +============================================== +""" + +import re +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +import colour_visuals as package # noqa: E402 + +basename = re.sub( + "_(\\w)", lambda x: x.group(1).upper(), package.__name__.title() +) + +# -- General configuration ------------------------------------------------ +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.ifconfig", + "sphinx.ext.inheritance_diagram", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinxcontrib.bibtex", +] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3.11", None), + "matplotlib": ("https://matplotlib.org/stable", None), + "numpy": ("https://numpy.org/doc/stable", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/dev", None), + "scipy": ("https://docs.scipy.org/doc/scipy-1.8.0/", None), +} + +autodoc_member_order = "bysource" +autodoc_mock_imports = [ + "colour", + "scipy", + "scipy.ndimage.filters", +] +autodoc_typehints = "both" +autodoc_type_aliases = { + "ArrayLike": "ArrayLike", + "DType": "DType", + "DTypeBoolean": "DTypeBoolean", + "DTypeComplex": "DTypeComplex", + "DTypeFloat": "DTypeFloat", + "DTypeInt": "DTypeInt", + "DTypeReal": "DTypeReal", + "Dataclass": "Dataclass", + "NDArrayBoolean": "NDArrayBoolean", + "NDArrayComplex": "NDArrayComplex", + "NDArrayFloat": "NDArrayFloat", + "NDArrayInt": "NDArrayInt", + "NDArrayReal": "NDArrayReal", + "NDArrayStr": "NDArrayStr", + "Real": "Real", +} +autodoc_preserve_defaults = True + +autoclass_content = "both" + +autosummary_generate = True + +bibtex_bibfiles = ["bibliography.bib"] +bibtex_encoding = "utf8" + +napoleon_custom_sections = ["Attributes", "Methods"] + +templates_path = ["_templates"] +source_suffix = ".rst" +master_doc = "index" + +project = package.__application_name__ +copyright = package.__copyright__.replace("Copyright (C)", "") # noqa: A001 +version = f"{package.__major_version__}.{package.__minor_version__}" +release = package.__version__ + +exclude_patterns = ["_build"] + +pygments_style = "lovelace" + +# -- Options for HTML output ---------------------------------------------- +html_theme = "pydata_sphinx_theme" +html_theme_options = { + "show_nav_level": 2, + "icon_links": [ + { + "name": "Email", + "url": "mailto:colour-developers@colour-science.org", + "icon": "fas fa-envelope", + }, + { + "name": "GitHub", + "url": ( + f"https://github.com/colour-science/" + f"{package.__name__.replace('_', '-')}" + ), + "icon": "fab fa-github", + }, + { + "name": "Facebook", + "url": "https://www.facebook.com/python.colour.science", + "icon": "fab fa-facebook", + }, + { + "name": "Gitter", + "url": "https://gitter.im/colour-science/colour", + "icon": "fab fa-gitter", + }, + { + "name": "Twitter", + "url": "https://twitter.com/colour_science", + "icon": "fab fa-twitter", + }, + ], +} +html_logo = "_static/Logo_Light_001.svg" +html_static_path = ["_static"] +htmlhelp_basename = f"{basename}Doc" + +# -- Options for LaTeX output --------------------------------------------- +latex_elements = { + "papersize": "a4paper", + "pointsize": "10pt", + "preamble": """ +\\usepackage{charter} +\\usepackage[defaultsans]{lato} +\\usepackage{inconsolata} + +% Ignoring unicode errors. +\\makeatletter +\\def\\UTFviii@defined#1{% + \\ifx#1\\relax + ?% + \\else\\expandafter + #1% + \\fi +} +\\makeatother + """, +} +latex_documents = [ + ( + "index", + f"{basename}.tex", + f"{package.__application_name__} Documentation", + package.__author__, + "manual", + ), +] +latex_logo = "_static/Logo_Medium_001.png" + +# -- Options for manual page output --------------------------------------- +man_pages = [ + ( + "index", + basename, + f"{package.__application_name__} Documentation", + [package.__author__], + 1, + ) +] + +# -- Options for Texinfo output ------------------------------------------- +texinfo_documents = [ + ( + "index", + basename, + f"{package.__application_name__} Documentation", + package.__author__, + package.__application_name__, + basename, + "Miscellaneous", + ), +] + +# -- Options for Epub output ---------------------------------------------- +epub_title = package.__application_name__ +epub_author = package.__author__ +epub_publisher = package.__author__ +epub_copyright = package.__copyright__.replace("Copyright (C)", "") +epub_exclude_files = ["search.html"] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..5dda663 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,68 @@ +Colour - Visuals +==================== + +A `Python `__ package implementing various +CFA (Colour Filter Array) visuals algorithms and related utilities. + +It is open source and freely available under the +`BSD-3-Clause `__ terms. + +.. image:: https://raw.githubusercontent.com/colour-science/colour-visuals/master/docs/_static/Visuals_001.png + +.. sectnum:: + +Features +-------- + +The following CFA (Colour Filter Array) visuals algorithms are implemented: + +- Bilinear +- Malvar (2004) +- DDFAPD - Menon (2007) + +Examples +^^^^^^^^ + +Various usage examples are available from the +`examples directory `__. + +User Guide +---------- + +.. toctree:: + :maxdepth: 2 + + user-guide + +API Reference +------------- + +.. toctree:: + :maxdepth: 2 + + reference + +Code of Conduct +--------------- + +The *Code of Conduct*, adapted from the `Contributor Covenant 1.4 `__, +is available on the `Code of Conduct `__ page. + +Contact & Social +---------------- + +The *Colour Developers* can be reached via different means: + +- `Email `__ +- `Facebook `__ +- `Github Discussions `__ +- `Gitter `__ +- `Twitter `__ + +About +----- + +| **Colour - Visuals** by Colour Developers +| Copyright 2023 Colour Developers – `colour-developers@colour-science.org `__ +| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause +| `https://github.com/colour-science/colour-visuals `__ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..96c47c9 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,8 @@ +Installation Guide +================== + +Primary Dependencies +-------------------- + +Pypi +---- diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..fd7b014 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\colour_visuals.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\colour_visuals.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/reference.rst b/docs/reference.rst new file mode 100644 index 0000000..050b44b --- /dev/null +++ b/docs/reference.rst @@ -0,0 +1,13 @@ +API Reference +============= + +.. toctree:: + :titlesonly: + + colour_visuals + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..9cde8f7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,50 @@ +accessible-pygments==0.0.4 ; python_version >= "3.9" and python_version < "3.12" +alabaster==0.7.13 ; python_version >= "3.9" and python_version < "3.12" +babel==2.12.1 ; python_version >= "3.9" and python_version < "3.12" +beautifulsoup4==4.12.2 ; python_version >= "3.9" and python_version < "3.12" +biblib-simple==0.1.2 ; python_version >= "3.9" and python_version < "3.12" +certifi==2023.7.22 ; python_version >= "3.9" and python_version < "3.12" +charset-normalizer==3.2.0 ; python_version >= "3.9" and python_version < "3.12" +colorama==0.4.6 ; python_version >= "3.9" and python_version < "3.12" and sys_platform == "win32" +colour-science==0.4.3 ; python_version >= "3.9" and python_version < "3.12" +contourpy==1.1.0 ; python_version >= "3.9" and python_version < "3.12" +cycler==0.11.0 ; python_version >= "3.9" and python_version < "3.12" +docutils==0.17.1 ; python_version >= "3.9" and python_version < "3.12" +fonttools==4.42.1 ; python_version >= "3.9" and python_version < "3.12" +idna==3.4 ; python_version >= "3.9" and python_version < "3.12" +imageio==2.31.2 ; python_version >= "3.9" and python_version < "3.12" +imagesize==1.4.1 ; python_version >= "3.9" and python_version < "3.12" +importlib-metadata==6.8.0 ; python_version >= "3.9" and python_version < "3.10" +importlib-resources==6.0.1 ; python_version >= "3.9" and python_version < "3.10" +jinja2==3.1.2 ; python_version >= "3.9" and python_version < "3.12" +kiwisolver==1.4.5 ; python_version >= "3.9" and python_version < "3.12" +latexcodec==2.0.1 ; python_version >= "3.9" and python_version < "3.12" +markupsafe==2.1.3 ; python_version >= "3.9" and python_version < "3.12" +matplotlib==3.7.2 ; python_version >= "3.9" and python_version < "3.12" +numpy==1.25.2 ; python_version >= "3.9" and python_version < "3.12" +packaging==23.1 ; python_version >= "3.9" and python_version < "3.12" +pillow==10.0.0 ; python_version >= "3.9" and python_version < "3.12" +pybtex-docutils==1.0.3 ; python_version >= "3.9" and python_version < "3.12" +pybtex==0.24.0 ; python_version >= "3.9" and python_version < "3.12" +pydata-sphinx-theme==0.13.3 ; python_version >= "3.9" and python_version < "3.12" +pygments==2.16.1 ; python_version >= "3.9" and python_version < "3.12" +pyparsing==3.0.9 ; python_version >= "3.9" and python_version < "3.12" +python-dateutil==2.8.2 ; python_version >= "3.9" and python_version < "3.12" +pyyaml==6.0.1 ; python_version >= "3.9" and python_version < "3.12" +requests==2.31.0 ; python_version >= "3.9" and python_version < "3.12" +restructuredtext-lint==1.4.0 ; python_version >= "3.9" and python_version < "3.12" +scipy==1.11.2 ; python_version >= "3.9" and python_version < "3.12" +six==1.16.0 ; python_version >= "3.9" and python_version < "3.12" +snowballstemmer==2.2.0 ; python_version >= "3.9" and python_version < "3.12" +soupsieve==2.4.1 ; python_version >= "3.9" and python_version < "3.12" +sphinx==4.5.0 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-applehelp==1.0.4 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-bibtex==2.6.0 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-htmlhelp==2.0.1 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.9" and python_version < "3.12" +typing-extensions==4.7.1 ; python_version >= "3.9" and python_version < "3.12" +urllib3==2.0.4 ; python_version >= "3.9" and python_version < "3.12" +zipp==3.16.2 ; python_version >= "3.9" and python_version < "3.10" diff --git a/docs/user-guide.rst b/docs/user-guide.rst new file mode 100644 index 0000000..54b9e29 --- /dev/null +++ b/docs/user-guide.rst @@ -0,0 +1,13 @@ +User Guide +========== + +The user guide provides an overview of **Colour - Visuals** and +explains important concepts and features, details can be found in the +`API Reference `__. + +.. toctree:: + :maxdepth: 1 + + Installation + Contributing + Changes diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..98b0902 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,195 @@ +[tool.poetry] +name = "colour-visuals" +version = "0.1.0" +description = "WebGPU-based visuals for colour science applications" +license = "BSD-3-Clause" +authors = [ "Colour Developers " ] +maintainers = [ "Colour Developers " ] +readme = 'README.rst' +repository = "https://github.com/colour-science/colour-visuals" +homepage = "https://www.colour-science.org/" +keywords = [ + "color", + "color-science", + "color-space", + "color-spaces", + "colorspace", + "colorspaces", + "colour", + "colour-science", + "colour-space", + "colour-spaces", + "colourspace", + "colourspaces", + "visuals", + "webgpu", + "python", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering", + "Topic :: Software Development" +] + +[tool.poetry.dependencies] +python = ">= 3.9, < 3.12" +colour-science = {git = "https://github.com/colour-science/colour.git"} +imageio = ">= 2, < 3" +matplotlib = ">= 3.5, != 3.5.0, != 3.5.1" +networkx = ">= 2.7, < 3" +numpy = ">= 1.22, < 2" +pygfx = ">= 0.1, < 0.2" +pyside6 = ">= 6, < 7" +scipy = ">= 1.8, < 2" + +[tool.poetry.group.dev.dependencies] +black = "*" +blackdoc = "*" +coverage = "!= 6.3" +coveralls = "*" +flynt = "*" +invoke = "*" +jupyter = "*" +pre-commit = "*" +pyright = "*" +pytest = "*" +pytest-cov = "*" +pytest-xdist = "*" +ruff = "*" +toml = "*" +twine = "*" + +[tool.poetry.group.docs.dependencies] +biblib-simple = "*" +pydata-sphinx-theme = "*" +restructuredtext-lint = "*" +sphinx = "*" +sphinxcontrib-bibtex = "*" + +[tool.black] +line-length = 79 +exclude = ''' +/( + \.git + | build + | dist +)/ +''' + +[tool.flynt] +line_length=999 + +[tool.pyright] +reportMissingImports = false +reportMissingModuleSource = false +reportUnboundVariable = false +reportUnnecessaryCast = true +reportUnnecessaryTypeIgnoreComment = true +reportUnsupportedDunderAll = false +reportUnusedExpression = false + +[tool.pytest.ini_options] +addopts = "-n auto --dist=loadscope --durations=5" + +[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", + "PGH003", + "PIE804", + "PLE0605", + "PLR0911", + "PLR0912", + "PLR0913", + "PLR0915", + "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] +"colour_visuals/examples/*" = ["INP", "T201", "T203"] +"docs/*" = ["INP"] +"tasks.py" = ["INP"] +"utilities/*" = ["EXE001", "INP"] +"utilities/unicode_to_ascii.py" = ["RUF001"] + +[build-system] +requires = [ "poetry_core>=1.0.0" ] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..edd4afd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,180 @@ +accessible-pygments==0.0.4 ; python_version >= "3.9" and python_version < "3.12" +alabaster==0.7.13 ; python_version >= "3.9" and python_version < "3.12" +anyio==3.7.1 ; python_version >= "3.9" and python_version < "3.12" +appnope==0.1.3 ; python_version >= "3.9" and python_version < "3.12" and (platform_system == "Darwin" or sys_platform == "darwin") +argon2-cffi-bindings==21.2.0 ; python_version >= "3.9" and python_version < "3.12" +argon2-cffi==23.1.0 ; python_version >= "3.9" and python_version < "3.12" +arrow==1.2.3 ; python_version >= "3.9" and python_version < "3.12" +astor==0.8.1 ; python_version >= "3.9" and python_version < "3.12" +asttokens==2.2.1 ; python_version >= "3.9" and python_version < "3.12" +async-lru==2.0.4 ; python_version >= "3.9" and python_version < "3.12" +attrs==23.1.0 ; python_version >= "3.9" and python_version < "3.12" +babel==2.12.1 ; python_version >= "3.9" and python_version < "3.12" +backcall==0.2.0 ; python_version >= "3.9" and python_version < "3.12" +beautifulsoup4==4.12.2 ; python_version >= "3.9" and python_version < "3.12" +biblib-simple==0.1.2 ; python_version >= "3.9" and python_version < "3.12" +black==23.7.0 ; python_version >= "3.9" and python_version < "3.12" +blackdoc==0.3.8 ; python_version >= "3.9" and python_version < "3.12" +bleach==6.0.0 ; python_version >= "3.9" and python_version < "3.12" +certifi==2023.7.22 ; python_version >= "3.9" and python_version < "3.12" +cffi==1.15.1 ; python_version >= "3.9" and python_version < "3.12" +cfgv==3.4.0 ; python_version >= "3.9" and python_version < "3.12" +charset-normalizer==3.2.0 ; python_version >= "3.9" and python_version < "3.12" +click==8.1.7 ; python_version >= "3.9" and python_version < "3.12" +colorama==0.4.6 ; python_version >= "3.9" and python_version < "3.12" and (sys_platform == "win32" or platform_system == "Windows") +colour-science==0.4.3 ; python_version >= "3.9" and python_version < "3.12" +comm==0.1.4 ; python_version >= "3.9" and python_version < "3.12" +contourpy==1.1.0 ; python_version >= "3.9" and python_version < "3.12" +coverage==6.5.0 ; python_version >= "3.9" and python_version < "3.12" +coverage[toml]==6.5.0 ; python_version >= "3.9" and python_version < "3.12" +coveralls==3.3.1 ; python_version >= "3.9" and python_version < "3.12" +cryptography==41.0.3 ; python_version >= "3.9" and python_version < "3.12" and sys_platform == "linux" +cycler==0.11.0 ; python_version >= "3.9" and python_version < "3.12" +debugpy==1.6.7.post1 ; python_version >= "3.9" and python_version < "3.12" +decorator==5.1.1 ; python_version >= "3.9" and python_version < "3.12" +defusedxml==0.7.1 ; python_version >= "3.9" and python_version < "3.12" +distlib==0.3.7 ; python_version >= "3.9" and python_version < "3.12" +docopt==0.6.2 ; python_version >= "3.9" and python_version < "3.12" +docutils==0.17.1 ; python_version >= "3.9" and python_version < "3.12" +exceptiongroup==1.1.3 ; python_version >= "3.9" and python_version < "3.11" +execnet==2.0.2 ; python_version >= "3.9" and python_version < "3.12" +executing==1.2.0 ; python_version >= "3.9" and python_version < "3.12" +fastjsonschema==2.18.0 ; python_version >= "3.9" and python_version < "3.12" +filelock==3.12.2 ; python_version >= "3.9" and python_version < "3.12" +flynt==1.0.1 ; python_version >= "3.9" and python_version < "3.12" +fonttools==4.42.1 ; python_version >= "3.9" and python_version < "3.12" +fqdn==1.5.1 ; python_version >= "3.9" and python_version < "3.12" +identify==2.5.27 ; python_version >= "3.9" and python_version < "3.12" +idna==3.4 ; python_version >= "3.9" and python_version < "3.12" +imageio==2.31.2 ; python_version >= "3.9" and python_version < "3.12" +imagesize==1.4.1 ; python_version >= "3.9" and python_version < "3.12" +importlib-metadata==6.8.0 ; python_version >= "3.9" and python_version < "3.12" +importlib-resources==6.0.1 ; python_version >= "3.9" and python_version < "3.10" +iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "3.12" +invoke==2.2.0 ; python_version >= "3.9" and python_version < "3.12" +ipykernel==6.25.1 ; python_version >= "3.9" and python_version < "3.12" +ipython-genutils==0.2.0 ; python_version >= "3.9" and python_version < "3.12" +ipython==8.14.0 ; python_version >= "3.9" and python_version < "3.12" +ipywidgets==8.1.0 ; python_version >= "3.9" and python_version < "3.12" +isoduration==20.11.0 ; python_version >= "3.9" and python_version < "3.12" +jaraco-classes==3.3.0 ; python_version >= "3.9" and python_version < "3.12" +jedi==0.19.0 ; python_version >= "3.9" and python_version < "3.12" +jeepney==0.8.0 ; python_version >= "3.9" and python_version < "3.12" and sys_platform == "linux" +jinja2==3.1.2 ; python_version >= "3.9" and python_version < "3.12" +json5==0.9.14 ; python_version >= "3.9" and python_version < "3.12" +jsonpointer==2.4 ; python_version >= "3.9" and python_version < "3.12" +jsonschema-specifications==2023.7.1 ; python_version >= "3.9" and python_version < "3.12" +jsonschema==4.19.0 ; python_version >= "3.9" and python_version < "3.12" +jsonschema[format-nongpl]==4.19.0 ; python_version >= "3.9" and python_version < "3.12" +jupyter-client==8.3.0 ; python_version >= "3.9" and python_version < "3.12" +jupyter-console==6.6.3 ; python_version >= "3.9" and python_version < "3.12" +jupyter-core==5.3.1 ; python_version >= "3.9" and python_version < "3.12" +jupyter-events==0.7.0 ; python_version >= "3.9" and python_version < "3.12" +jupyter-lsp==2.2.0 ; python_version >= "3.9" and python_version < "3.12" +jupyter-server-terminals==0.4.4 ; python_version >= "3.9" and python_version < "3.12" +jupyter-server==2.7.2 ; python_version >= "3.9" and python_version < "3.12" +jupyter==1.0.0 ; python_version >= "3.9" and python_version < "3.12" +jupyterlab-pygments==0.2.2 ; python_version >= "3.9" and python_version < "3.12" +jupyterlab-server==2.24.0 ; python_version >= "3.9" and python_version < "3.12" +jupyterlab-widgets==3.0.8 ; python_version >= "3.9" and python_version < "3.12" +jupyterlab==4.0.5 ; python_version >= "3.9" and python_version < "3.12" +keyring==24.2.0 ; python_version >= "3.9" and python_version < "3.12" +kiwisolver==1.4.5 ; python_version >= "3.9" and python_version < "3.12" +latexcodec==2.0.1 ; python_version >= "3.9" and python_version < "3.12" +markdown-it-py==3.0.0 ; python_version >= "3.9" and python_version < "3.12" +markupsafe==2.1.3 ; python_version >= "3.9" and python_version < "3.12" +matplotlib-inline==0.1.6 ; python_version >= "3.9" and python_version < "3.12" +matplotlib==3.7.2 ; python_version >= "3.9" and python_version < "3.12" +mdurl==0.1.2 ; python_version >= "3.9" and python_version < "3.12" +mistune==3.0.1 ; python_version >= "3.9" and python_version < "3.12" +more-itertools==10.1.0 ; python_version >= "3.9" and python_version < "3.12" +mypy-extensions==1.0.0 ; python_version >= "3.9" and python_version < "3.12" +nbclient==0.8.0 ; python_version >= "3.9" and python_version < "3.12" +nbconvert==7.7.4 ; python_version >= "3.9" and python_version < "3.12" +nbformat==5.9.2 ; python_version >= "3.9" and python_version < "3.12" +nest-asyncio==1.5.7 ; python_version >= "3.9" and python_version < "3.12" +nodeenv==1.8.0 ; python_version >= "3.9" and python_version < "3.12" +notebook-shim==0.2.3 ; python_version >= "3.9" and python_version < "3.12" +notebook==7.0.2 ; python_version >= "3.9" and python_version < "3.12" +numpy==1.25.2 ; python_version >= "3.9" and python_version < "3.12" +overrides==7.4.0 ; python_version >= "3.9" and python_version < "3.12" +packaging==23.1 ; python_version >= "3.9" and python_version < "3.12" +pandocfilters==1.5.0 ; python_version >= "3.9" and python_version < "3.12" +parso==0.8.3 ; python_version >= "3.9" and python_version < "3.12" +pathspec==0.11.2 ; python_version >= "3.9" and python_version < "3.12" +pexpect==4.8.0 ; python_version >= "3.9" and python_version < "3.12" and sys_platform != "win32" +pickleshare==0.7.5 ; python_version >= "3.9" and python_version < "3.12" +pillow==10.0.0 ; python_version >= "3.9" and python_version < "3.12" +pkginfo==1.9.6 ; python_version >= "3.9" and python_version < "3.12" +platformdirs==3.10.0 ; python_version >= "3.9" and python_version < "3.12" +pluggy==1.3.0 ; python_version >= "3.9" and python_version < "3.12" +pre-commit==3.3.3 ; python_version >= "3.9" and python_version < "3.12" +prometheus-client==0.17.1 ; python_version >= "3.9" and python_version < "3.12" +prompt-toolkit==3.0.39 ; python_version >= "3.9" and python_version < "3.12" +psutil==5.9.5 ; python_version >= "3.9" and python_version < "3.12" +ptyprocess==0.7.0 ; python_version >= "3.9" and python_version < "3.12" and (sys_platform != "win32" or os_name != "nt") +pure-eval==0.2.2 ; python_version >= "3.9" and python_version < "3.12" +pybtex-docutils==1.0.3 ; python_version >= "3.9" and python_version < "3.12" +pybtex==0.24.0 ; python_version >= "3.9" and python_version < "3.12" +pycparser==2.21 ; python_version >= "3.9" and python_version < "3.12" +pydata-sphinx-theme==0.13.3 ; python_version >= "3.9" and python_version < "3.12" +pygments==2.16.1 ; python_version >= "3.9" and python_version < "3.12" +pyparsing==3.0.9 ; python_version >= "3.9" and python_version < "3.12" +pyright==1.1.324 ; python_version >= "3.9" and python_version < "3.12" +pytest-cov==4.1.0 ; python_version >= "3.9" and python_version < "3.12" +pytest-xdist==3.3.1 ; python_version >= "3.9" and python_version < "3.12" +pytest==7.4.0 ; python_version >= "3.9" and python_version < "3.12" +python-dateutil==2.8.2 ; python_version >= "3.9" and python_version < "3.12" +python-json-logger==2.0.7 ; python_version >= "3.9" and python_version < "3.12" +pywin32-ctypes==0.2.2 ; python_version >= "3.9" and python_version < "3.12" and sys_platform == "win32" +pywin32==306 ; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_version >= "3.9" and python_version < "3.12" +pywinpty==2.0.11 ; python_version >= "3.9" and python_version < "3.12" and os_name == "nt" +pyyaml==6.0.1 ; python_version >= "3.9" and python_version < "3.12" +pyzmq==25.1.1 ; python_version >= "3.9" and python_version < "3.12" +qtconsole==5.4.3 ; python_version >= "3.9" and python_version < "3.12" +qtpy==2.3.1 ; python_version >= "3.9" and python_version < "3.12" +readme-renderer==41.0 ; python_version >= "3.9" and python_version < "3.12" +referencing==0.30.2 ; python_version >= "3.9" and python_version < "3.12" +requests-toolbelt==1.0.0 ; python_version >= "3.9" and python_version < "3.12" +requests==2.31.0 ; python_version >= "3.9" and python_version < "3.12" +restructuredtext-lint==1.4.0 ; python_version >= "3.9" and python_version < "3.12" +rfc3339-validator==0.1.4 ; python_version >= "3.9" and python_version < "3.12" +rfc3986-validator==0.1.1 ; python_version >= "3.9" and python_version < "3.12" +rfc3986==2.0.0 ; python_version >= "3.9" and python_version < "3.12" +rich==13.5.2 ; python_version >= "3.9" and python_version < "3.12" +rpds-py==0.9.2 ; python_version >= "3.9" and python_version < "3.12" +ruff==0.0.286 ; python_version >= "3.9" and python_version < "3.12" +scipy==1.11.2 ; python_version >= "3.9" and python_version < "3.12" +secretstorage==3.3.3 ; python_version >= "3.9" and python_version < "3.12" and sys_platform == "linux" +send2trash==1.8.2 ; python_version >= "3.9" and python_version < "3.12" +setuptools==68.1.2 ; python_version >= "3.9" and python_version < "3.12" +six==1.16.0 ; python_version >= "3.9" and python_version < "3.12" +sniffio==1.3.0 ; python_version >= "3.9" and python_version < "3.12" +snowballstemmer==2.2.0 ; python_version >= "3.9" and python_version < "3.12" +soupsieve==2.4.1 ; python_version >= "3.9" and python_version < "3.12" +sphinx==4.5.0 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-applehelp==1.0.4 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-bibtex==2.6.0 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-htmlhelp==2.0.1 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.9" and python_version < "3.12" +sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.9" and python_version < "3.12" +stack-data==0.6.2 ; python_version >= "3.9" and python_version < "3.12" +terminado==0.17.1 ; python_version >= "3.9" and python_version < "3.12" +tinycss2==1.2.1 ; python_version >= "3.9" and python_version < "3.12" +toml==0.10.2 ; python_version >= "3.9" and python_version < "3.12" +tomli==2.0.1 ; python_version >= "3.9" and python_version < "3.12" +tornado==6.3.3 ; python_version >= "3.9" and python_version < "3.12" +traitlets==5.9.0 ; python_version >= "3.9" and python_version < "3.12" +twine==4.0.2 ; python_version >= "3.9" and python_version < "3.12" +typing-extensions==4.7.1 ; python_version >= "3.9" and python_version < "3.12" +uri-template==1.3.0 ; python_version >= "3.9" and python_version < "3.12" +urllib3==2.0.4 ; python_version >= "3.9" and python_version < "3.12" +virtualenv==20.24.3 ; python_version >= "3.9" and python_version < "3.12" +wcwidth==0.2.6 ; python_version >= "3.9" and python_version < "3.12" +webcolors==1.13 ; python_version >= "3.9" and python_version < "3.12" +webencodings==0.5.1 ; python_version >= "3.9" and python_version < "3.12" +websocket-client==1.6.2 ; python_version >= "3.9" and python_version < "3.12" +widgetsnbextension==4.0.8 ; python_version >= "3.9" and python_version < "3.12" +zipp==3.16.2 ; python_version >= "3.9" and python_version < "3.12" diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..7a2c5f5 --- /dev/null +++ b/tasks.py @@ -0,0 +1,469 @@ +""" +Invoke - Tasks +============== +""" + +from __future__ import annotations + +import biblib.bib +import contextlib +import fnmatch +import os +import re +import uuid + +import colour_visuals +from colour.utilities import message_box + +import inspect + +if not hasattr(inspect, "getargspec"): + inspect.getargspec = inspect.getfullargspec # pyright: ignore + +from invoke.tasks import task +from invoke.context import Context + +__author__ = "Colour Developers" +__copyright__ = "Copyright 2023 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [ + "APPLICATION_NAME", + "APPLICATION_VERSION", + "PYTHON_PACKAGE_NAME", + "PYPI_PACKAGE_NAME", + "PYPI_ARCHIVE_NAME", + "BIBLIOGRAPHY_NAME", + "clean", + "formatting", + "quality", + "precommit", + "tests", + "examples", + "preflight", + "docs", + "todo", + "requirements", + "build", + "virtualise", + "tag", + "release", + "sha256", +] + +APPLICATION_NAME: str = colour_visuals.__application_name__ + +APPLICATION_VERSION: str = colour_visuals.__version__ + +PYTHON_PACKAGE_NAME: str = colour_visuals.__name__ + +PYPI_PACKAGE_NAME: str = "colour-visuals" +PYPI_ARCHIVE_NAME: str = PYPI_PACKAGE_NAME.replace("-", "_") + +BIBLIOGRAPHY_NAME: str = "BIBLIOGRAPHY.bib" + + +@task +def clean( + ctx: Context, + docs: bool = True, + bytecode: bool = False, + pytest: bool = True, +): + """ + Clean the project. + + Parameters + ---------- + ctx + Context. + docs + Whether to clean the *docs* directory. + bytecode + Whether to clean the bytecode files, e.g. *.pyc* files. + pytest + Whether to clean the *Pytest* cache directory. + """ + + message_box("Cleaning project...") + + patterns = ["build", "*.egg-info", "dist"] + + if docs: + patterns.append("docs/_build") + patterns.append("docs/generated") + + if bytecode: + patterns.append("**/__pycache__") + patterns.append("**/*.pyc") + + if pytest: + patterns.append(".pytest_cache") + + for pattern in patterns: + ctx.run(f"rm -rf {pattern}") + + +@task +def formatting( + ctx: Context, + asciify: bool = True, + bibtex: bool = True, +): + """ + Convert unicode characters to ASCII and cleanup the *BibTeX* file. + + Parameters + ---------- + ctx + Context. + asciify + Whether to convert unicode characters to ASCII. + bibtex + Whether to cleanup the *BibTeX* file. + """ + + if asciify: + message_box("Converting unicode characters to ASCII...") + with ctx.cd("utilities"): + ctx.run("./unicode_to_ascii.py") + + if bibtex: + message_box('Cleaning up "BibTeX" file...') + bibtex_path = BIBLIOGRAPHY_NAME + with open(bibtex_path) as bibtex_file: + entries = ( + biblib.bib.Parser().parse(bibtex_file.read()).get_entries() + ) + + for entry in sorted(entries.values(), key=lambda x: x.key): + with contextlib.suppress(KeyError): + del entry["file"] + + for key, value in entry.items(): + entry[key] = re.sub("(?`__ +| This software is released under terms of BSD-3-Clause: \ +https://opensource.org/licenses/BSD-3-Clause +| `https://github.com/colour-science/colour-visuals \ +`__ +"""[ + 1: +] + + +def extract_todo_items(root_directory: str) -> dict: + """ + Extract the TODO items from given directory. + + Parameters + ---------- + root_directory + Directory to extract the TODO items from. + + Returns + ------- + :class:`dict` + TODO items. + """ + + todo_items = {} + for root, _dirnames, filenames in os.walk(root_directory): + for filename in filenames: + if not filename.endswith(".py"): + continue + + filename = os.path.join(root, filename) # noqa: PLW2901 + with codecs.open(filename, encoding="utf8") as file_handle: + content = file_handle.readlines() + + in_todo = False + line_number = 1 + todo_item = [] + for i, line in enumerate(content): + line = line.strip() # noqa: PLW2901 + if line.startswith("# TODO:"): + in_todo = True + line_number = i + 1 + todo_item.append(line) + continue + + if in_todo and line.startswith("#"): + todo_item.append(line.replace("#", "").strip()) + elif len(todo_item): + key = filename.replace("../", "") + if not todo_items.get(key): + todo_items[key] = [] + + todo_items[key].append((line_number, " ".join(todo_item))) + in_todo = False + todo_item = [] + + return todo_items + + +def export_todo_items(todo_items: dict, file_path: str): + """ + Export TODO items to given file. + + Parameters + ---------- + todo_items + TODO items. + file_path + File to write the TODO items to. + """ + + todo_rst = [] + for module, module_todo_items in todo_items.items(): + todo_rst.append(f"- {module}\n") + for line_numer, todo_item in module_todo_items: + todo_rst.append(f" - Line {line_numer} : {todo_item}") + + todo_rst.append("\n") + + with codecs.open(file_path, "w", encoding="utf8") as todo_file: + todo_file.write(TODO_FILE_TEMPLATE.format("\n".join(todo_rst[:-1]))) + + +if __name__ == "__main__": + os.chdir(os.path.dirname(__file__)) + + export_todo_items( + extract_todo_items(os.path.join("..", "colour_visuals")), + os.path.join("..", "TODO.rst"), + ) diff --git a/utilities/unicode_to_ascii.py b/utilities/unicode_to_ascii.py new file mode 100755 index 0000000..4eb3ebb --- /dev/null +++ b/utilities/unicode_to_ascii.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +""" +Unicode to ASCII Utility +======================== +""" + +from __future__ import annotations + +import codecs +import os +import unicodedata + +__copyright__ = "Copyright 2015 Colour Developers" +__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" +__maintainer__ = "Colour Developers" +__email__ = "colour-developers@colour-science.org" +__status__ = "Production" + +__all__ = [ + "SUBSTITUTIONS", + "unicode_to_ascii", +] + +SUBSTITUTIONS: dict[str, str] = { + "–": "-", + "“": '"', + "”": '"', + "‘": "'", + "’": "'", + "′": "'", +} + + +def unicode_to_ascii(root_directory: str): + """ + Recursively convert from unicode to ASCII *.py*, *.bib* and *.rst* files + in given directory. + + Parameters + ---------- + root_directory + Directory to convert the files from unicode to ASCII. + """ + + for root, _dirnames, filenames in os.walk(root_directory): + for filename in filenames: + if ( + not filename.endswith(".tex") + and not filename.endswith(".py") + and not filename.endswith(".bib") + and not filename.endswith(".rst") + ): + continue + + if filename == "unicode_to_ascii.py": + continue + + filename = os.path.join(root, filename) # noqa: PLW2901 + with codecs.open(filename, encoding="utf8") as file_handle: + content = file_handle.read() + + with codecs.open(filename, "w", encoding="utf8") as file_handle: + for key, value in SUBSTITUTIONS.items(): + content = content.replace(key, value) + + content = unicodedata.normalize("NFD", content) + + file_handle.write(content) + + +if __name__ == "__main__": + os.chdir(os.path.dirname(__file__)) + + unicode_to_ascii(os.path.join("..", "colour_visuals"))