[Git][debian-gis-team/pydecorate][upstream] New upstream version 0.3.3
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Sun Mar 6 08:21:04 GMT 2022
Antonio Valentino pushed to branch upstream at Debian GIS Project / pydecorate
Commits:
8af1fef6 by Antonio Valentino at 2022-03-06T07:14:15+00:00
New upstream version 0.3.3
- - - - -
27 changed files:
- + .bandit
- .git_archival.txt
- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- .gitignore
- + .pre-commit-config.yaml
- − .travis.yml
- CHANGELOG.md
- MANIFEST.in
- README.rst
- RELEASING.md
- − appveyor.yml
- + continuous_integration/environment.yaml
- + doc/source/_static/.gitkeep
- doc/source/conf.py
- doc/source/index.rst
- doc/source/usage.rst
- pydecorate/__init__.py
- pydecorate/decorator.py
- pydecorate/decorator_agg.py
- pydecorate/decorator_base.py
- + pydecorate/tests/__init__.py
- + pydecorate/tests/test_decorator_agg.py
- test.py → pydecorate/tests/test_legacy.py
- pyproject.toml
- + setup.cfg
- setup.py
Changes:
=====================================
.bandit
=====================================
@@ -0,0 +1,3 @@
+[bandit]
+skips: B506
+exclude: pydecorate/tests
=====================================
.git_archival.txt
=====================================
@@ -1 +1 @@
-ref-names: HEAD -> master, tag: v0.3.1
+ref-names: HEAD -> main, tag: v0.3.3
=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,131 @@
+name: CI
+# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency
+# https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types#pullrequestevent
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.type }}
+ cancel-in-progress: true
+
+on: [push, pull_request]
+
+jobs:
+ lint:
+ name: lint and style checks
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+ - name: Set up Python
+ uses: actions/setup-python at v2
+ with:
+ python-version: 3.9
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install flake8 flake8-docstrings flake8-debugger flake8-bugbear pytest
+ - name: Install Pydecorate
+ run: |
+ pip install -e .
+ - name: Run linting
+ run: |
+ flake8 pydecorate/
+
+ website:
+ name: build website
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+ with:
+ fetch-depth: 0
+
+ - name: Set up Python
+ uses: actions/setup-python at v2
+ with:
+ python-version: 3.9
+
+ - name: Install Pydecorate
+ shell: bash -l {0}
+ run: |
+ pip install -e .[docs]
+
+ - name: Run Sphinx Build
+ shell: bash -l {0}
+ run: |
+ cd doc; \
+ make html SPHINXOPTS="-W"
+
+ test:
+ runs-on: ${{ matrix.os }}
+ continue-on-error: ${{ matrix.experimental }}
+ needs: [lint]
+ strategy:
+ fail-fast: true
+ matrix:
+ os: ["windows-latest", "ubuntu-latest", "macos-latest"]
+ python-version: ["3.7", "3.10"]
+ experimental: [false]
+ include:
+ - python-version: "3.10"
+ os: "ubuntu-latest"
+ experimental: true
+
+ env:
+ PYTHON_VERSION: ${{ matrix.python-version }}
+ OS: ${{ matrix.os }}
+ UNSTABLE: ${{ matrix.experimental }}
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+# - name: Set up Python
+# uses: actions/setup-python at v2
+# with:
+# python-version: "${{ matrix.python-version }}"
+
+ - name: Setup Conda Environment
+ uses: conda-incubator/setup-miniconda at v2
+ with:
+ miniforge-variant: Mambaforge
+ miniforge-version: latest
+ use-mamba: true
+ python-version: "${{ matrix.python-version }}"
+ environment-file: continuous_integration/environment.yaml
+ activate-environment: test-environment
+
+ - name: Install unstable dependencies
+ if: matrix.experimental == true
+ shell: bash -l {0}
+ run: |
+ python -m pip install \
+ --index-url https://pypi.anaconda.org/scipy-wheels-nightly/simple/ \
+ --trusted-host pypi.anaconda.org \
+ --no-deps --pre --upgrade \
+ numpy;
+
+ - name: Install pydecorate
+ shell: bash -l {0}
+ run: |
+ python -m pip install -e . --no-deps
+
+ - name: Run unit tests
+ shell: bash -l {0}
+ run: |
+ pytest --cov=pydecorate pydecorate/tests
+
+ - name: Coveralls Parallel
+ uses: AndreMiras/coveralls-python-action at develop
+ with:
+ flag-name: run-${{ matrix.test_number }}
+ parallel: true
+ if: runner.os == 'Linux'
+
+ coveralls:
+ needs: [test]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Coveralls Finished
+ uses: AndreMiras/coveralls-python-action at develop
+ with:
+ parallel-finished: true
=====================================
.github/workflows/deploy-sdist.yaml
=====================================
@@ -0,0 +1,27 @@
+name: Deploy sdist
+
+on:
+ release:
+ types:
+ - published
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+ - name: Create sdist
+ shell: bash -l {0}
+ run: |
+ python -m pip install -U build pip
+ python -m build --sdist
+
+ - name: Publish package to PyPI
+ if: github.event.action == 'published'
+ uses: pypa/gh-action-pypi-publish at v1.4.1
+ with:
+ user: __token__
+ password: ${{ secrets.pypi_password }}
=====================================
.gitignore
=====================================
@@ -67,4 +67,3 @@ nosetests.xml
# vi / vim swp files
*.swp
-
=====================================
.pre-commit-config.yaml
=====================================
@@ -0,0 +1,47 @@
+exclude: '^$'
+fail_fast: false
+repos:
+ - repo: https://github.com/psf/black
+ rev: 22.1.0
+ hooks:
+ - id: black
+ language_version: python3
+ exclude: versioneer.py
+ args:
+ - --target-version=py38
+ - repo: https://github.com/PyCQA/flake8
+ rev: 4.0.1
+ hooks:
+ - id: flake8
+ additional_dependencies: [flake8-docstrings, flake8-debugger, flake8-bugbear, mccabe]
+ args: [--max-complexity, "10"]
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ args: [--unsafe]
+ - repo: https://github.com/PyCQA/bandit
+ rev: '1.7.2' # Update me!
+ hooks:
+ - id: bandit
+ args: [--ini, .bandit]
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: 'v0.931' # Use the sha / tag you want to point at
+ hooks:
+ - id: mypy
+ additional_dependencies:
+ - types-docutils
+ - types-pkg-resources
+ - types-PyYAML
+ - types-requests
+ - repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+ language_version: python3
+ci:
+ # To trigger manually, comment on a pull request with "pre-commit.ci autofix"
+ autofix_prs: false
+ skip: [bandit]
=====================================
.travis.yml deleted
=====================================
@@ -1,42 +0,0 @@
-language: python
-env:
- global:
- - NUMPY_VERSION=stable
- - MAIN_CMD='python setup.py'
- - CONDA_DEPENDENCIES='sphinx pillow coveralls coverage codecov trollimage aggdraw
- pytest pytest-cov'
- - PIP_DEPENDENCIES=''
- - SETUP_XVFB=False
- - EVENT_TYPE='push pull_request'
- - SETUP_CMD='test'
- - CONDA_CHANNELS='conda-forge'
- - CONDA_CHANNEL_PRIORITY='strict'
-matrix:
- include:
- - env: PYTHON_VERSION=3.8
- os: linux
- language: generic
- - env: PYTHON_VERSION=3.7
- os: linux
- language: generic
- - env: PYTHON_VERSION=3.8
- os: osx
- language: generic
-install:
-- git clone --depth 1 git://github.com/astropy/ci-helpers.git
-- source ci-helpers/travis/setup_conda.sh
-- pip install --no-deps -e .
-script:
-- pytest --cov=pydecorate test.py
-after_success:
-- if [[ $PYTHON_VERSION == 3.8 ]]; then coveralls; codecov; fi
-deploy:
- - provider: pypi
- user: dhoese
- password:
- secure: PoMrJlgTJRzDvqDRs5H/k/1nkqada+zfiUTUXZEvulebkAVCu3Sj945fNQVsgq3Ha0hlyttuNGyJbLf8KUZIX9y9OJr3TuP253hQx+Ghe6scH5SFHrSj6s4ROinc9HeZfwzGlObZoeV042aSgf77O/ocGUiA54f2ZLYa8U15w9A=
- distributions: sdist
- skip_existing: true
- on:
- tags: true
- repo: pytroll/pydecorate
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,31 @@
+## Version 0.3.3 (2022/02/17)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 19](https://github.com/pytroll/pydecorate/pull/19) - Add missing setuptools dependency
+
+In this release 1 pull request was closed.
+
+
+## Version 0.3.2 (2022/02/17)
+
+### Issues Closed
+
+* [Issue 17](https://github.com/pytroll/pydecorate/issues/17) - pydecorate.DecoratorAGG: How to put text/colorbar outside of image and control width of colorbar.
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 18](https://github.com/pytroll/pydecorate/pull/18) - Fix colorbar creation when limits are inverted
+
+In this release 1 pull request was closed.
+
+
## Version 0.3.1 (2020/04/06)
### Pull Requests Merged
=====================================
MANIFEST.in
=====================================
@@ -3,4 +3,4 @@ include docs/Makefile
recursive-include pydecorate/fonts *
include README.rst
include LICENSE.txt
-include pydecorate/version.py
\ No newline at end of file
+include pydecorate/version.py
=====================================
README.rst
=====================================
@@ -1,14 +1,11 @@
pydecorate
==========
-.. image:: https://travis-ci.org/pytroll/pydecorate.svg?branch=master
- :target: https://travis-ci.org/pytroll/pydecorate
+.. image:: https://github.com/pytroll/pydecorate/workflows/CI/badge.svg?branch=main
+ :target: https://github.com/pytroll/pydecorate/actions?query=workflow%3A%22CI%22
-.. image:: https://ci.appveyor.com/api/projects/status/61r5wqu2j2ay07ns/branch/master?svg=true
- :target: https://ci.appveyor.com/project/pytroll/pydecorate/branch/master
-
-.. image:: https://coveralls.io/repos/github/pytroll/pydecorate/badge.svg?branch=master
- :target: https://coveralls.io/github/pytroll/pydecorate?branch=master
+.. image:: https://coveralls.io/repos/github/pytroll/pydecorate/badge.svg?branch=main
+ :target: https://coveralls.io/github/pytroll/pydecorate?branch=main
Pydecorate is a package for decorating PIL images with logos, texts, and color
scales.
=====================================
RELEASING.md
=====================================
@@ -1,37 +1,45 @@
# Releasing Pydecorate
-1. checkout master
+1. checkout main branch
2. pull from repo
3. run the unittests
-4. run `loghub`. Replace <github username> and <previous version> with proper
- values. To get the previous version run `git tag` and select the most
- recent with highest version number.
+4. run `loghub` and update the `CHANGELOG.md` file:
-```
-loghub pytroll/pydecorate -u <github username> -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backwards incompatible changes"
-```
+ ```
+ loghub pytroll/pydecorate --token $LOGHUB_GITHUB_TOKEN -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backward incompatible changes" -plg refactor "Refactoring"
+ ```
-This command will create a CHANGELOG.temp file which need to be added
-to the top of the CHANGELOG.md file. The same content is also printed
-to terminal, so that can be copy-pasted, too. Remember to update also
-the version number to the same given in step 5. Don't forget to commit
-CHANGELOG.md!
+ This uses a `LOGHUB_GITHUB_TOKEN` environment variable. This must be created
+ on GitHub and it is recommended that you add it to your `.bashrc` or
+ `.bash_profile` or equivalent.
-5. Create a tag with the new version number, starting with a 'v', eg:
+ This command will create a CHANGELOG.temp file which need to be added
+ to the top of the CHANGLOG.md file. The same content is also printed
+ to terminal, so that can be copy-pasted, too. Remember to update also
+ the version number to the same given in step 5. Don't forget to commit
+ CHANGELOG.md!
-```
-git tag -a v<new version> -m "Version <new version>"
-```
+5. Create a tag with the new version number, starting with a 'v', eg:
-For example if the previous tag was `v0.9.0` and the new release is a
-patch release, do:
+ ```
+ git tag -a v<new version> -m "Version <new version>"
+ ```
-```
-git tag -a v0.9.1 -m "Version 0.9.1"
-```
+ For example if the previous tag was `v0.9.0` and the new release is a
+ patch release, do:
-See [semver.org](http://semver.org/) on how to write a version number.
+ ```
+ git tag -a v0.9.1 -m "Version 0.9.1"
+ ```
+ See [semver.org](http://semver.org/) on how to write a version number..
6. push changes to github `git push --follow-tags`
-7. Verify travis tests passed and deployed sdist and wheel to PyPI
+7. Verify github action unittests passed.
+8. Create a "Release" on GitHub by going to
+ https://github.com/pytroll/pydecorate/releases and clicking "Draft a new release".
+ On the next page enter the newly created tag in the "Tag version" field,
+ "Version X.Y.Z" in the "Release title" field, and paste the markdown from
+ the changelog (the portion under the version section header) in the
+ "Describe this release" box. Finally click "Publish release".
+9. Verify the GitHub actions for deployment succeed and the release is on PyPI.
=====================================
appveyor.yml deleted
=====================================
@@ -1,39 +0,0 @@
-environment:
- global:
- PYTHON: "C:\\conda"
- MINICONDA_VERSION: "latest"
- CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd"
- CONDA_DEPENDENCIES: "sphinx pillow coverage trollimage aggdraw pytest pytest-cov"
- PIP_DEPENDENCIES: ""
- CONDA_CHANNELS: "conda-forge"
-
- matrix:
- - PYTHON: "C:\\Python38_64"
- PYTHON_VERSION: "3.8"
- PYTHON_ARCH: "64"
- NUMPY_VERSION: "stable"
-
-install:
- - "git clone --depth 1 git://github.com/astropy/ci-helpers.git"
- - "powershell ci-helpers/appveyor/install-miniconda.ps1"
- - "conda activate test"
- - "pip install --no-deps -e ."
-
-build: false # Not a C# project, build stuff at the test step instead.
-
-test_script:
- # Build the compiled extension and run the project tests
- - "%CMD_IN_ENV% pytest --cov=pydecorate test.py"
-
-after_test:
- # If tests are successful, create a whl package for the project.
- - "%CMD_IN_ENV% python setup.py bdist_wheel bdist_wininst"
- - ps: "ls dist"
-
-artifacts:
- # Archive the generated wheel package in the ci.appveyor.com build report.
- - path: dist\*
-
-#on_success:
-# - TODO: upload the content of dist/*.whl to a public wheelhouse
-#
=====================================
continuous_integration/environment.yaml
=====================================
@@ -0,0 +1,10 @@
+name: test-environment
+channels:
+ - conda-forge
+dependencies:
+ - numpy
+ - pillow
+ - aggdraw
+ - pytest
+ - pytest-cov
+ - trollimage
=====================================
doc/source/_static/.gitkeep
=====================================
=====================================
doc/source/conf.py
=====================================
@@ -10,208 +10,214 @@
#
# All configuration values have a default; values that are commented out
# serve to show the default.
+"""Sphinx configuration file for pydecorate documentation."""
# To generate apidoc modules:
# sphinx-apidoc -f -T -o source/api ../pydecorate ../pydecorate/tests
+from __future__ import annotations
import os
import sys
+
from pkg_resources import get_distribution
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('../../'))
+sys.path.insert(0, os.path.abspath("../../"))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']
+extensions = ["sphinx.ext.autodoc", "sphinx.ext.doctest"]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'Pydecorate'
-copyright = u'2013, Hrobjartur Thorsteinsson'
+project = "Pydecorate"
+copyright = "2013, Hrobjartur Thorsteinsson"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# get version using setuptools-scm
-release = get_distribution('pydecorate').version
+release = get_distribution("pydecorate").version
# The full version, including alpha/beta/rc tags.
# for example take major/minor
-version = '.'.join(release.split('.')[:2])
+version = ".".join(release.split(".")[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
-#language = None
+# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = []
+exclude_patterns: list[str] = []
# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css"..
-html_static_path = ['_static']
+html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'Pydecoratedoc'
+htmlhelp_basename = "Pydecoratedoc"
# -- Options for LaTeX output --------------------------------------------------
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+latex_elements: dict[str, str] = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- ('index', 'Pydecorate.tex', u'Pydecorate Documentation',
- u'Hrobjartur Thorsteinsson', 'manual'),
+ (
+ "index",
+ "Pydecorate.tex",
+ "Pydecorate Documentation",
+ "Hrobjartur Thorsteinsson",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@@ -219,12 +225,11 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'pydecorate', u'Pydecorate Documentation',
- [u'Hrobjartur Thorsteinsson'], 1)
+ ("index", "pydecorate", "Pydecorate Documentation", ["Hrobjartur Thorsteinsson"], 1)
]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@@ -233,16 +238,22 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ('index', 'Pydecorate', u'Pydecorate Documentation',
- u'Hrobjartur Thorsteinsson', 'Pydecorate', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ "index",
+ "Pydecorate",
+ "Pydecorate Documentation",
+ "Hrobjartur Thorsteinsson",
+ "Pydecorate",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
=====================================
doc/source/index.rst
=====================================
@@ -33,4 +33,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
=====================================
doc/source/usage.rst
=====================================
@@ -34,7 +34,7 @@ demonstration logos:
>>> dc.add_logo("logos/pytroll_light_big.png",height=80.0)
>>> dc.add_logo("logos/NASA_Logo.gif")
- >>>
+ >>>
>>> img.show()
.. image:: images/logo_image.png
@@ -49,7 +49,7 @@ To add text, you could do:
>>> dc.add_text("MSG SEVIRI\nThermal blue marble\n1/1/1977 00:00")
>>> dc.add_logo("logos/pytroll_light_big.png")
>>> dc.add_logo("logos/NASA_Logo.gif")
- >>>
+ >>>
>>> img.show()
.. image:: images/text_and_logo_image.png
@@ -74,7 +74,7 @@ scales and legends (DRAFT)
++++++++++++++++++++++++++
PyDecorate can work with trollimage colormap objects to add colour scales.
-To add some logos along with a standard scale feature based on the 'rdbu'
+To add some logos along with a standard scale feature based on the 'rdbu'
scale from trollmap one might do as follows,
>>> from trollimage.colormap import rdbu
@@ -83,14 +83,14 @@ scale from trollmap one might do as follows,
>>> dc.add_logo("logos/pytroll_light_big.png")
>>> dc.add_logo("logos/NASA_Logo.gif")
>>> dc.add_scale(rdbu,extend=True)
-
+
.. image:: images/logo_and_scale1.png
:width: 400px
:align: center
Note that the extend=True option sets the scale feature to extend to the full
available space. Without this option the scale will inherit the previous width,
-or one might pass a specific width as argument. See more on this topic in the
+or one might pass a specific width as argument. See more on this topic in the
Styles section below.
Off course the placement of the logos and scale feature is very flexible..
@@ -137,7 +137,7 @@ can be changed.
alignment
+++++++++
-Continuing from the previous example,
+Continuing from the previous example,
we can align the cursor to the bottom-right corner, by executing
>>> dc.align_right()
@@ -242,5 +242,3 @@ to be repeated on successive calls,
.. image:: images/style_retention.png
:width: 400px
:align: center
-
-
=====================================
pydecorate/__init__.py
=====================================
@@ -1,19 +1,19 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
-#
-#Copyright (C) 2011 Hrobjartur Thorsteinsson
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
-#This program is free software: you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# Copyright (C) 2011 Hrobjartur Thorsteinsson
#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-#GNU General Public License for more details.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
#
-#You should have received a copy of the GNU General Public License
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
@@ -22,4 +22,4 @@ except ImportError:
# package is not installed
pass
-from pydecorate.decorator_agg import DecoratorAGG
+from pydecorate.decorator_agg import DecoratorAGG # noqa
=====================================
pydecorate/decorator.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Hrobjartur Thorsteinsson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,25 +15,49 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""PIL-based image decoration class."""
-try:
- from PIL import ImageDraw
-except ImportError:
- print("ImportError: Missing module: ImageDraw")
+from PIL import ImageDraw, ImageFont
from .decorator_base import DecoratorBase
class Decorator(DecoratorBase):
+ """PIL-based image decoration class."""
- def add_scale(self, color_def, font=None, size=None, fill='black',
- outline=None, outline_width=1, bg='white', extend=False, unit='', margins=None, minortick=0.0,
- nan_color=(0, 0, 0), nan_check_color=(1, 1, 1), nan_check_size=0):
- """ Todo
- """
- self._add_scale(color_def, font=font, size=size, fill=fill,
- outline=outline, outline_widht=outline_width, bg=bg, extend=extend, unit=unit, margins=margins, minortick=minortick,
- nan_color=nan_color, nan_check_color=nan_check_color, nan_check_size=nan_check_size)
+ def add_scale(
+ self,
+ color_def,
+ font=None,
+ size=None,
+ fill="black",
+ outline=None,
+ outline_width=1,
+ bg="white",
+ extend=False,
+ unit="",
+ margins=None,
+ minortick=0.0,
+ nan_color=(0, 0, 0),
+ nan_check_color=(1, 1, 1),
+ nan_check_size=0,
+ ):
+ self._add_scale(
+ color_def,
+ font=font,
+ size=size,
+ fill=fill,
+ outline=outline,
+ outline_widht=outline_width,
+ bg=bg,
+ extend=extend,
+ unit=unit,
+ margins=margins,
+ minortick=minortick,
+ nan_color=nan_color,
+ nan_check_color=nan_check_color,
+ nan_check_size=nan_check_size,
+ )
def _load_default_font(self):
return ImageFont.load_default()
@@ -45,6 +69,5 @@ class Decorator(DecoratorBase):
self._add_logo(logo_path, **kwargs)
def _get_canvas(self, image):
- """Returns PIL image object
- """
+ """Return PIL image object."""
return ImageDraw.Draw(image)
=====================================
pydecorate/decorator_agg.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
-# Copyright (C) 2011, 2016 Hrobjartur Thorsteinsson
+# Copyright (C) 2011 Hrobjartur Thorsteinsson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,23 +15,29 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Aggdraw-based image decoration class."""
+
+import aggdraw
+from pkg_resources import resource_filename
+
from pydecorate.decorator_base import DecoratorBase
+
class DecoratorAGG(DecoratorBase):
+ """Aggdraw-based image decoration class."""
def add_scale(self, colormap, **kwargs):
self._add_scale(colormap, **kwargs)
def _load_default_font(self):
- import aggdraw
- from pkg_resources import resource_filename
- font_path = resource_filename('pydecorate.fonts', 'DejaVuSerif.ttf')
- return aggdraw.Font('black', font_path, size=16)
+ font_path = resource_filename("pydecorate.fonts", "DejaVuSerif.ttf")
+ return aggdraw.Font("black", font_path, size=16)
def _load_font(self):
- import aggdraw
try:
- return aggdraw.Font(self.style['line'], self.style['font'], self.style['font_size'])
+ return aggdraw.Font(
+ self.style["line"], self.style["font"], self.style["font_size"]
+ )
except IOError:
raise
@@ -42,23 +48,28 @@ class DecoratorAGG(DecoratorBase):
self._add_logo(logo_path, **kwargs)
def _get_canvas(self, image):
- """Returns AGG image object
- """
- import aggdraw
+ """Return AGG image object."""
return aggdraw.Draw(image)
def _finalize(self, draw):
- """Flush the AGG image object
- """
+ """Flush the AGG image object."""
draw.flush()
- def _draw_text_line(self, draw, xy, text, font, fill='black'):
+ def _draw_text_line(self, draw, xy, text, font, fill="black"):
draw.text(xy, text, font)
- def _draw_rectangle(self, draw, xys, outline='white', bg='white', bg_opacity=255, outline_width=1, outline_opacity=255, **kwargs):
- import aggdraw
- pen = aggdraw.Pen(
- outline, width=outline_width, opacity=outline_opacity)
+ def _draw_rectangle(
+ self,
+ draw,
+ xys,
+ outline="white",
+ bg="white",
+ bg_opacity=255,
+ outline_width=1,
+ outline_opacity=255,
+ **kwargs,
+ ):
+ pen = aggdraw.Pen(outline, width=outline_width, opacity=outline_opacity)
brush = aggdraw.Brush(bg, opacity=bg_opacity)
# draw bg and outline
# bg unaliased (otherwise gaps between successive bgs)
@@ -77,41 +88,34 @@ class DecoratorAGG(DecoratorBase):
draw.rectangle(xys, pen, None)
def _draw_line(self, draw, xys, **kwargs):
- import aggdraw
- if kwargs['line'] is None:
+ if kwargs["line"] is None:
pen = None
else:
pen = aggdraw.Pen(
- kwargs['line'], width=kwargs['line_width'], opacity=kwargs['line_opacity'])
+ kwargs["line"],
+ width=kwargs["line_width"],
+ opacity=kwargs["line_opacity"],
+ )
xys_straight = [item for t in xys for item in t]
draw.line(xys_straight, pen)
- def _draw_polygon(self, draw, xys, outline=None, fill='white', fill_opacity=255, outline_width=1, outline_opacity=255):
- import aggdraw
+ def _draw_polygon(
+ self,
+ draw,
+ xys,
+ outline=None,
+ fill="white",
+ fill_opacity=255,
+ outline_width=1,
+ outline_opacity=255,
+ ):
if outline is None:
pen = None
else:
- pen = aggdraw.Pen(
- outline, width=outline_width, opacity=outline_opacity)
+ pen = aggdraw.Pen(outline, width=outline_width, opacity=outline_opacity)
if fill is None:
brush = None
else:
brush = aggdraw.Brush(fill, opacity=fill_opacity)
xys_straight = [item for t in xys for item in t]
draw.polygon(xys_straight, pen, brush)
-
-
-#########################################
-# float list generator
-def _frange(x, y, jump):
- while x < y:
- yield x
- x += jump
-
-
-def frange(x, y, jump):
- return [p for p in _frange(x, y, jump)]
-
-
-class ShapeFileError(Exception):
- pass
=====================================
pydecorate/decorator_base.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
-# Copyright (C) 2011, 2016 Hrobjartur Thorsteinsson
+# Copyright (C) 2011 Hrobjartur Thorsteinsson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,166 +15,157 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Base class and utilities for decorating images."""
-import numpy as np
+import copy
-try:
- from PIL import Image, ImageFont
-except ImportError:
- print("ImportError: Missing PIL image objects")
+import numpy as np
+from PIL import Image
try:
- from PIL import ImageDraw
+ from trollimage.image import Image as TImage
except ImportError:
- print("ImportError: Missing module: ImageDraw")
+ TImage = None
# style dictionary defines default options
# some only used by aggdraw version of the decorator
default_style_dict = {
- 'cursor': [0, 0],
- 'margins': [5, 5],
- 'height': 60,
- 'width': 60,
- 'propagation': [1, 0],
- 'newline_propagation': [0, 1],
- 'alignment': [0.0, 0.0],
- 'bg': 'white',
- 'bg_opacity': 127,
- 'line': "black",
- 'line_width': 1,
- 'line_opacity': 255,
- 'outline': None,
- 'outline_width': 1,
- 'outline_opacity': 255,
- 'fill': 'black',
- 'fill_opacity': 255,
- 'font': None,
- 'font_size': 16,
- 'start_border': [0, 0],
- 'extend': False,
- 'tick_marks': 1.0,
- 'minor_tick_marks': 0.5,
- 'unit': None
+ "cursor": [0, 0],
+ "margins": [5, 5],
+ "height": 60,
+ "width": 60,
+ "propagation": [1, 0],
+ "newline_propagation": [0, 1],
+ "alignment": [0.0, 0.0],
+ "bg": "white",
+ "bg_opacity": 127,
+ "line": "black",
+ "line_width": 1,
+ "line_opacity": 255,
+ "outline": None,
+ "outline_width": 1,
+ "outline_opacity": 255,
+ "fill": "black",
+ "fill_opacity": 255,
+ "font": None,
+ "font_size": 16,
+ "start_border": [0, 0],
+ "extend": False,
+ "tick_marks": 1.0,
+ "minor_tick_marks": 0.5,
+ "unit": None,
}
class DecoratorBase(object):
+ """Base class for drawing decorations on an Image."""
def __init__(self, image):
- """
- Probably users only want to instantiate DecoratorAgg or the Decorator implementations.
- DecoratorBase is a base class outlining common operations and interface for the Decorator (PIL drawing engine) and DecoratorAgg (Aggdraw drawing engine)
- """
+ """Initialize decorator and create handle to provided image object."""
self.image = image
-
- import copy
self.style = copy.deepcopy(default_style_dict)
def set_style(self, **kwargs):
self.style.update(kwargs)
- self.style['cursor'] = list(self.style['cursor'])
+ self.style["cursor"] = list(self.style["cursor"])
def _finalize(self, draw):
- """Do any need finalization of the drawing
- """
+ """Do any needed finalization of the drawing."""
pass
def write_vertically(self):
# top-right
- if self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 0.0:
- self.style['propagation'] = [0, 1]
- self.style['newline_propagation'] = [-1, 0]
+ if self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 0.0:
+ self.style["propagation"] = [0, 1]
+ self.style["newline_propagation"] = [-1, 0]
# bottom-right
- elif self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 1.0:
- self.style['propagation'] = [0, -1]
- self.style['newline_propagation'] = [-1, 0]
+ elif self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 1.0:
+ self.style["propagation"] = [0, -1]
+ self.style["newline_propagation"] = [-1, 0]
# bottom-left
- elif self.style['alignment'][0] == 0.0 and self.style['alignment'][1] == 1.0:
- self.style['propagation'] = [0, -1]
- self.style['newline_propagation'] = [1, 0]
+ elif self.style["alignment"][0] == 0.0 and self.style["alignment"][1] == 1.0:
+ self.style["propagation"] = [0, -1]
+ self.style["newline_propagation"] = [1, 0]
# top-left and other alignments
else:
- self.style['propagation'] = [0, 1]
- self.style['newline_propagation'] = [1, 0]
+ self.style["propagation"] = [0, 1]
+ self.style["newline_propagation"] = [1, 0]
def write_horizontally(self):
# top-right
- if self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 0.0:
- self.style['propagation'] = [-1, 0]
- self.style['newline_propagation'] = [0, 1]
+ if self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 0.0:
+ self.style["propagation"] = [-1, 0]
+ self.style["newline_propagation"] = [0, 1]
# bottom-right
- elif self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 1.0:
- self.style['propagation'] = [-1, 0]
- self.style['newline_propagation'] = [0, -1]
+ elif self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 1.0:
+ self.style["propagation"] = [-1, 0]
+ self.style["newline_propagation"] = [0, -1]
# bottom-left
- elif self.style['alignment'][0] == 0.0 and self.style['alignment'][1] == 1.0:
- self.style['propagation'] = [1, 0]
- self.style['newline_propagation'] = [0, -1]
+ elif self.style["alignment"][0] == 0.0 and self.style["alignment"][1] == 1.0:
+ self.style["propagation"] = [1, 0]
+ self.style["newline_propagation"] = [0, -1]
# top-left and other alignments
else:
- self.style['propagation'] = [1, 0]
- self.style['newline_propagation'] = [0, 1]
+ self.style["propagation"] = [1, 0]
+ self.style["newline_propagation"] = [0, 1]
def align_bottom(self):
- if self.style['alignment'][1] != 1.0:
- self.style['alignment'][1] = 1.0
- self.style['newline_propagation'][
- 1] = -self.style['newline_propagation'][1]
- self.style['propagation'][1] = -self.style['propagation'][1]
+ if self.style["alignment"][1] != 1.0:
+ self.style["alignment"][1] = 1.0
+ self.style["newline_propagation"][1] = -self.style["newline_propagation"][1]
+ self.style["propagation"][1] = -self.style["propagation"][1]
self.home()
def align_top(self):
- if self.style['alignment'][1] != 0.0:
- self.style['alignment'][1] = 0.0
- self.style['newline_propagation'][
- 1] = -self.style['newline_propagation'][1]
- self.style['propagation'][1] = -self.style['propagation'][1]
+ if self.style["alignment"][1] != 0.0:
+ self.style["alignment"][1] = 0.0
+ self.style["newline_propagation"][1] = -self.style["newline_propagation"][1]
+ self.style["propagation"][1] = -self.style["propagation"][1]
self.home()
def align_right(self):
- if self.style['alignment'][0] != 1.0:
- self.style['alignment'][0] = 1.0
- self.style['propagation'][0] = -self.style['propagation'][0]
- self.style['newline_propagation'][
- 0] = -self.style['newline_propagation'][0]
+ if self.style["alignment"][0] != 1.0:
+ self.style["alignment"][0] = 1.0
+ self.style["propagation"][0] = -self.style["propagation"][0]
+ self.style["newline_propagation"][0] = -self.style["newline_propagation"][0]
self.home()
def align_left(self):
- if self.style['alignment'][0] != 0.0:
- self.style['alignment'][0] = 0.0
- self.style['propagation'][0] = -self.style['propagation'][0]
- self.style['newline_propagation'][
- 0] = -self.style['newline_propagation'][0]
+ if self.style["alignment"][0] != 0.0:
+ self.style["alignment"][0] = 0.0
+ self.style["propagation"][0] = -self.style["propagation"][0]
+ self.style["newline_propagation"][0] = -self.style["newline_propagation"][0]
self.home()
def home(self):
- self.style['cursor'][0] = int(
- self.style['alignment'][0] * self.image.size[0])
- self.style['cursor'][1] = int(
- self.style['alignment'][1] * self.image.size[1])
+ self.style["cursor"][0] = int(self.style["alignment"][0] * self.image.size[0])
+ self.style["cursor"][1] = int(self.style["alignment"][1] * self.image.size[1])
def rewind(self):
- if self.style['newline_propagation'][0] == 0:
- self.style['cursor'][0] = int(
- self.style['alignment'][0] * self.image.size[0])
- if self.style['newline_propagation'][1] == 0:
- self.style['cursor'][1] = int(
- self.style['alignment'][1] * self.image.size[1])
+ if self.style["newline_propagation"][0] == 0:
+ self.style["cursor"][0] = int(
+ self.style["alignment"][0] * self.image.size[0]
+ )
+ if self.style["newline_propagation"][1] == 0:
+ self.style["cursor"][1] = int(
+ self.style["alignment"][1] * self.image.size[1]
+ )
def new_line(self):
+ """Set position of next decoration under the previously added decorations."""
# set new line
- self.style['cursor'][
- 0] += self.style['newline_propagation'][0] * self.style['width']
- self.style['cursor'][
- 1] += self.style['newline_propagation'][1] * self.style['height']
+ self.style["cursor"][0] += (
+ self.style["newline_propagation"][0] * self.style["width"]
+ )
+ self.style["cursor"][1] += (
+ self.style["newline_propagation"][1] * self.style["height"]
+ )
# rewind
self.rewind()
def _step_cursor(self):
- self.style['cursor'][
- 0] += self.style['propagation'][0] * self.style['width']
- self.style['cursor'][1] += self.style['propagation'][1] * \
- self.style['height']
+ self.style["cursor"][0] += self.style["propagation"][0] * self.style["width"]
+ self.style["cursor"][1] += self.style["propagation"][1] * self.style["height"]
# def start_border(self):
# self.style['start_border'] = list( self.style['cursor'] )
@@ -189,7 +180,7 @@ class DecoratorBase(object):
# self._finalize(draw)
def _draw_polygon(self, draw, xys, **kwargs):
- draw.polygon(xys, fill=kwargs['fill'], outline=kwargs['outline'])
+ draw.polygon(xys, fill=kwargs["fill"], outline=kwargs["outline"])
def _get_canvas(self, img):
raise NotImplementedError("Derived class implements this.")
@@ -197,11 +188,10 @@ class DecoratorBase(object):
def _load_default_font(self):
raise NotImplementedError("Derived class implements this.")
- def _draw_text(self, draw, xy, txt, font, fill='black', align='cc', dry_run=False, **kwargs):
- """
- Elementary text draw routine,
- with alignment. Returns text size.
- """
+ def _draw_text(
+ self, draw, xy, txt, font, fill="black", align="cc", dry_run=False, **kwargs
+ ):
+ """Elementary text draw routine, with alignment. Returns text size."""
# check for font object
if font is None:
font = self._load_default_font()
@@ -211,13 +201,13 @@ class DecoratorBase(object):
# align text position
x, y = xy
- if align[0] == 'c':
+ if align[0] == "c":
x -= tw / 2.0
- elif align[0] == 'r':
+ elif align[0] == "r":
x -= tw
- if align[1] == 'c':
+ if align[1] == "c":
y -= th / 2.0
- elif align[1] == 'r':
+ elif align[1] == "r":
y -= th
# draw the text
@@ -227,23 +217,23 @@ class DecoratorBase(object):
return tw, th
def _check_align(self):
- if 'align' in self.style:
- if 'top_bottom' in self.style['align']:
- if self.style['align']['top_bottom'] == 'top':
+ if "align" in self.style:
+ if "top_bottom" in self.style["align"]:
+ if self.style["align"]["top_bottom"] == "top":
self.align_top()
- elif self.style['align']['top_bottom'] == 'bottom':
+ elif self.style["align"]["top_bottom"] == "bottom":
self.align_bottom()
- if 'left_right' in self.style['align']:
- if self.style['align']['left_right'] == 'left':
+ if "left_right" in self.style["align"]:
+ if self.style["align"]["left_right"] == "left":
self.align_left()
- elif self.style['align']['left_right'] == 'right':
+ elif self.style["align"]["left_right"] == "right":
self.align_right()
def _get_current_font(self):
- if self.style['font'] is None:
- self.style['font'] = self._load_default_font()
- elif isinstance(self.style['font'], str):
- self.style['font'] = self._load_font()
+ if self.style["font"] is None:
+ self.style["font"] = self._load_default_font()
+ elif isinstance(self.style["font"], str):
+ self.style["font"] = self._load_font()
else:
pass # assume self.style['font'] has already been assigned as Font obj. FIXME
@@ -262,36 +252,34 @@ class DecoratorBase(object):
x_size, y_size = self.image.size
# split text into newlines '\n'
- txt_nl = txt.split('\n')
+ txt_nl = txt.split("\n")
# current xy and margins
- x = self.style['cursor'][0]
- y = self.style['cursor'][1]
- mx = self.style['margins'][0]
- my = self.style['margins'][1]
- prev_width = self.style['width']
- prev_height = self.style['height']
+ x = self.style["cursor"][0]
+ y = self.style["cursor"][1]
+ mx = self.style["margins"][0]
+ my = self.style["margins"][1]
+ # prev_width = self.style["width"]
+ prev_height = self.style["height"]
# calculate text space
- tw, th = draw.textsize(txt_nl[0], self.style['font'])
+ tw, th = draw.textsize(txt_nl[0], self.style["font"])
for t in txt_nl:
- w, tmp = draw.textsize(t, self.style['font'])
+ w, tmp = draw.textsize(t, self.style["font"])
if w > tw:
tw = w
hh = len(txt_nl) * th
# set height/width for subsequent draw operations
if prev_height < int(hh + 2 * my):
- self.style['height'] = int(hh + 2 * my)
- self.style['width'] = int(tw + 2 * mx)
+ self.style["height"] = int(hh + 2 * my)
+ self.style["width"] = int(tw + 2 * mx)
# draw base
- px = (self.style['propagation'][0] +
- self.style['newline_propagation'][0])
- py = (self.style['propagation'][1] +
- self.style['newline_propagation'][1])
+ px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+ py = self.style["propagation"][1] + self.style["newline_propagation"][1]
x1 = x + px * (tw + 2 * mx)
- y1 = y + py * self.style['height']
+ y1 = y + py * self.style["height"]
self._draw_rectangle(draw, [x, y, x1, y1], **self.style)
# draw
@@ -299,11 +287,16 @@ class DecoratorBase(object):
pos_x = x + mx
pos_y = y + i * th + my
if py < 0:
- pos_y += py * self.style['height']
+ pos_y += py * self.style["height"]
if px < 0:
- pos_x += px * self.style['width']
+ pos_x += px * self.style["width"]
self._draw_text_line(
- draw, (pos_x, pos_y), txt_nl[i], self.style['font'], fill=self.style['fill'])
+ draw,
+ (pos_x, pos_y),
+ txt_nl[i],
+ self.style["font"],
+ fill=self.style["fill"],
+ )
# update cursor
self._step_cursor()
@@ -311,19 +304,19 @@ class DecoratorBase(object):
# finalize
self._finalize(draw)
- def _draw_text_line(self, draw, xy, text, font, fill='black'):
+ def _draw_text_line(self, draw, xy, text, font, fill="black"):
draw.text(xy, text, font=font, fill=fill)
def _draw_line(self, draw, xys, **kwargs):
# inconvenient to use fill for a line so swapped def.
- draw.line(xys, fill=kwargs['line'])
+ draw.line(xys, fill=kwargs["line"])
def _draw_rectangle(self, draw, xys, **kwargs):
# adjust extent of rectangle to draw up to but not including xys[2/3]
xys[2] -= 1
xys[3] -= 1
- if kwargs['bg'] or kwargs['outline']:
- draw.rectangle(xys, fill=kwargs['bg'], outline=kwargs['outline'])
+ if kwargs["bg"] or kwargs["outline"]:
+ draw.rectangle(xys, fill=kwargs["bg"], outline=kwargs["outline"])
def _add_logo(self, logo_path, **kwargs):
# synchronize kwargs into style
@@ -331,17 +324,17 @@ class DecoratorBase(object):
self._check_align()
# current xy and margins
- x = self.style['cursor'][0]
- y = self.style['cursor'][1]
+ x = self.style["cursor"][0]
+ y = self.style["cursor"][1]
- mx = self.style['margins'][0]
- my = self.style['margins'][1]
+ mx = self.style["margins"][0]
+ my = self.style["margins"][1]
# draw object
draw = self._get_canvas(self.image)
# get logo image
- logo = Image.open(logo_path, "r").convert('RGBA')
+ logo = Image.open(logo_path, "r").convert("RGBA")
# default size is _line_size set by previous draw operation
# else do not resize
@@ -350,24 +343,22 @@ class DecoratorBase(object):
# default logo sizes ...
# use previously set line_size
- if self.style['propagation'][0] != 0:
- ny = self.style['height']
+ if self.style["propagation"][0] != 0:
+ ny = self.style["height"]
nyi = int(round(ny - 2 * my))
nxi = int(round(nyi / aspect))
- nx = (nxi + 2 * mx)
- elif self.style['propagation'][1] != 0:
- nx = self.style['width']
+ nx = nxi + 2 * mx
+ elif self.style["propagation"][1] != 0:
+ nx = self.style["width"]
nxi = int(round(nx - 2 * mx))
nyi = int(round(nxi * aspect))
- ny = (nyi + 2 * my)
+ ny = nyi + 2 * my
logo = logo.resize((nxi, nyi), resample=Image.ANTIALIAS)
# draw base
- px = (self.style['propagation'][0] +
- self.style['newline_propagation'][0])
- py = (self.style['propagation'][1] +
- self.style['newline_propagation'][1])
+ px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+ py = self.style["propagation"][1] + self.style["newline_propagation"][1]
box = [x, y, x + px * nx, y + py * ny]
self._draw_rectangle(draw, box, **self.style)
@@ -375,13 +366,12 @@ class DecoratorBase(object):
self._finalize(draw)
# paste logo
- box = [x + px * mx, y + py * my, x + px *
- mx + px * nxi, y + py * my + py * nyi]
+ box = [x + px * mx, y + py * my, x + px * mx + px * nxi, y + py * my + py * nyi]
self._insert_RGBA_image(logo, box)
# update cursor
- self.style['width'] = int(nx)
- self.style['height'] = int(ny)
+ self.style["width"] = int(nx)
+ self.style["height"] = int(ny)
self._step_cursor()
def _add_scale(self, colormap, title=None, **kwargs):
@@ -389,42 +379,34 @@ class DecoratorBase(object):
self.set_style(**kwargs)
# sizes, current xy and margins
- x = self.style['cursor'][0]
- y = self.style['cursor'][1]
- mx = self.style['margins'][0]
- my = self.style['margins'][1]
+ x = self.style["cursor"][0]
+ y = self.style["cursor"][1]
+ mx = self.style["margins"][0]
+ my = self.style["margins"][1]
x_size, y_size = self.image.size
# horizontal/vertical?
- is_vertical = False
- if self.style['propagation'][1] != 0:
- is_vertical = True
-
+ is_vertical = self.style["propagation"][1] != 0
# left/right?
- is_right = False
- if self.style['alignment'][0] == 1.0:
- is_right = True
-
+ is_right = self.style["alignment"][0] == 1.0
# top/bottom?
- is_bottom = False
- if self.style['alignment'][1] == 1.0:
- is_bottom = True
+ is_bottom = self.style["alignment"][1] == 1.0
# adjust new size based on extend (fill space) style,
- if self.style['extend']:
- if self.style['propagation'][0] == 1:
- self.style['width'] = (x_size - x)
- elif self.style['propagation'][0] == -1:
- self.style['width'] = x
- if self.style['propagation'][1] == 1:
- self.style['height'] = (y_size - y)
- elif self.style['propagation'][1] == -1:
- self.style['height'] = y
+ if self.style["extend"]:
+ if self.style["propagation"][0] == 1:
+ self.style["width"] = x_size - x
+ elif self.style["propagation"][0] == -1:
+ self.style["width"] = x
+ if self.style["propagation"][1] == 1:
+ self.style["height"] = y_size - y
+ elif self.style["propagation"][1] == -1:
+ self.style["height"] = y
# set scale spacer for units and other
x_spacer = 0
y_spacer = 0
- if self.style['unit']:
+ if self.style["unit"]:
if is_vertical:
y_spacer = 40
else:
@@ -437,35 +419,22 @@ class DecoratorBase(object):
self._get_current_font()
# draw base
- px = (self.style['propagation'][0] +
- self.style['newline_propagation'][0])
- py = (self.style['propagation'][1] +
- self.style['newline_propagation'][1])
- x1 = x + px * self.style['width']
- y1 = y + py * self.style['height']
+ px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+ py = self.style["propagation"][1] + self.style["newline_propagation"][1]
+ x1 = x + px * self.style["width"]
+ y1 = y + py * self.style["height"]
self._draw_rectangle(draw, [x, y, x1, y1], **self.style)
# scale dimensions
- scale_width = self.style['width'] - 2 * mx - x_spacer
- scale_height = self.style['height'] - 2 * my - y_spacer
+ scale_width = self.style["width"] - 2 * mx - x_spacer
+ scale_height = self.style["height"] - 2 * my - y_spacer
# generate color scale image obj inset by margin size mx my,
- from trollimage.image import Image as TImage
-
- #### THIS PART TO BE INGESTED INTO A COLORMAP FUNCTION ####
+ # TODO: THIS PART TO BE INGESTED INTO A COLORMAP FUNCTION
minval, maxval = colormap.values[0], colormap.values[-1]
-
- if is_vertical:
- linedata = np.ones(
- (scale_width, 1)) * np.arange(minval, maxval, float(maxval - minval) / scale_height)
- linedata = linedata.transpose()
- else:
- linedata = np.ones(
- (scale_height, 1)) * np.arange(minval, maxval, float(maxval - minval) / scale_width)
-
- timg = TImage(linedata, mode="L")
- timg.colorize(colormap)
- scale = timg.pil_image()
+ scale = _create_colorbar_image(
+ colormap, minval, maxval, scale_height, scale_width, is_vertical
+ )
###########################################################
# finalize (must be before paste)
@@ -477,100 +446,287 @@ class DecoratorBase(object):
# reload draw object
draw = self._get_canvas(self.image)
+ self._draw_colorbar_ticks(
+ draw,
+ minval,
+ maxval,
+ is_vertical,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ )
+
+ # draw unit and/or power if set
+ if self.style["unit"]:
+ self._add_colorbar_units(
+ draw,
+ self.style["unit"],
+ is_vertical,
+ is_right,
+ is_bottom,
+ x,
+ y,
+ mx,
+ my,
+ scale_width,
+ scale_height,
+ x_spacer,
+ y_spacer,
+ )
+
+ if title:
+ self._add_colorbar_title(
+ draw,
+ title,
+ is_vertical,
+ is_right,
+ is_bottom,
+ x,
+ y,
+ mx,
+ my,
+ scale_width,
+ scale_height,
+ )
- # draw tick marks
- val_steps = _round_arange2(minval, maxval, self.style['tick_marks'])
- minor_steps = _round_arange(
- minval, maxval, self.style['minor_tick_marks'])
+ # finalize
+ self._finalize(draw)
- ffra, fpow = _optimize_scale_numbers(
- minval, maxval, self.style['tick_marks'])
+ def _draw_colorbar_ticks(
+ self,
+ draw,
+ minval,
+ maxval,
+ is_vertical,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ ):
+ val_steps = _round_arange2(minval, maxval, self.style["tick_marks"])
+ minor_steps = _round_arange(minval, maxval, self.style["minor_tick_marks"])
+
+ ffra, fpow = _optimize_scale_numbers(minval, maxval, self.style["tick_marks"])
form = "%" + "." + str(ffra) + "f"
- last_x = x + px * mx
- last_y = y + py * my
ref_w, ref_h = self._draw_text(
- draw, (0, 0), form % (val_steps[0]), dry_run=True, **self.style)
+ draw, (0, 0), form % (val_steps[0]), dry_run=True, **self.style
+ )
if is_vertical:
- # major
- offset_start = val_steps[0] - minval
- offset_end = val_steps[-1] - maxval
- y_steps = py * (val_steps - minval - offset_start -
- offset_end) * scale_height / (maxval - minval) + y + py * my
- y_steps = y_steps[::-1]
- for i, ys in enumerate(y_steps):
- self._draw_line(
- draw, [(x + px * mx, ys), (x + px * (mx + scale_width / 3.0), ys)], **self.style)
- if abs(ys - last_y) > ref_h:
- self._draw_text(
- draw, (x + px * (mx + 2 * scale_width / 3.0), ys), (form % (val_steps[i])).strip(), **self.style)
- last_y = ys
- # minor
- y_steps = py * (minor_steps - minval) * \
- scale_height / (maxval - minval) + y + py * my
- y_steps = y_steps[::-1]
- for i, ys in enumerate(y_steps):
- self._draw_line(
- draw, [(x + px * mx, ys), (x + px * (mx + scale_width / 6.0), ys)], **self.style)
+ self._draw_vertical_colorbar_ticks(
+ draw,
+ minval,
+ maxval,
+ val_steps,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ minor_steps,
+ ref_h,
+ form,
+ )
else:
- # major
- x_steps = px * (val_steps - minval) * \
- scale_width / (maxval - minval) + x + px * mx
- for i, xs in enumerate(x_steps):
- self._draw_line(
- draw, [(xs, y + py * my), (xs, y + py * (my + scale_height / 3.0))], **self.style)
- if abs(xs - last_x) > ref_w:
- self._draw_text(
- draw, (xs, y + py * (my + 2 * scale_height / 3.0)), (form % (val_steps[i])).strip(), **self.style)
- last_x = xs
- # minor
- x_steps = px * (minor_steps - minval) * \
- scale_width / (maxval - minval) + x + px * mx
- for i, xs in enumerate(x_steps):
- self._draw_line(
- draw, [(xs, y + py * my), (xs, y + py * (my + scale_height / 6.0))], **self.style)
-
- # draw unit and/or power if set
- if self.style['unit']:
- # calculate position
- if is_vertical:
- if is_right:
- x_ = x - mx - scale_width / 2.0
- else:
- x_ = x + mx + scale_width / 2.0
- y_ = y + my + scale_height + y_spacer / 2.0
+ self._draw_horizontal_colorbar_ticks(
+ draw,
+ minval,
+ maxval,
+ val_steps,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ minor_steps,
+ ref_w,
+ form,
+ )
+
+ def _draw_vertical_colorbar_ticks(
+ self,
+ draw,
+ minval,
+ maxval,
+ val_steps,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ minor_steps,
+ ref_h,
+ form,
+ ):
+ # major
+ offset_start = val_steps[0] - minval
+ offset_end = val_steps[-1] - maxval
+ y_steps = (
+ py
+ * (val_steps - minval - offset_start - offset_end)
+ * scale_height
+ / (maxval - minval)
+ + y
+ + py * my
+ )
+ y_steps = y_steps[::-1]
+ last_y = y + py * my
+ for i, ys in enumerate(y_steps):
+ self._draw_line(
+ draw,
+ [(x + px * mx, ys), (x + px * (mx + scale_width / 3.0), ys)],
+ **self.style,
+ )
+ if abs(ys - last_y) > ref_h:
+ self._draw_text(
+ draw,
+ (x + px * (mx + 2 * scale_width / 3.0), ys),
+ (form % (val_steps[i])).strip(),
+ **self.style,
+ )
+ last_y = ys
+ # minor
+ y_steps = (
+ py * (minor_steps - minval) * scale_height / (maxval - minval) + y + py * my
+ )
+ y_steps = y_steps[::-1]
+ for ys in y_steps:
+ self._draw_line(
+ draw,
+ [(x + px * mx, ys), (x + px * (mx + scale_width / 6.0), ys)],
+ **self.style,
+ )
+
+ def _draw_horizontal_colorbar_ticks(
+ self,
+ draw,
+ minval,
+ maxval,
+ val_steps,
+ scale_width,
+ scale_height,
+ px,
+ py,
+ mx,
+ my,
+ x,
+ y,
+ minor_steps,
+ ref_w,
+ form,
+ ):
+ # major
+ x_steps = (
+ px * (val_steps - minval) * scale_width / (maxval - minval) + x + px * mx
+ )
+ last_x = x + px * mx
+ for i, xs in enumerate(x_steps):
+ self._draw_line(
+ draw,
+ [(xs, y + py * my), (xs, y + py * (my + scale_height / 3.0))],
+ **self.style,
+ )
+ if abs(xs - last_x) > ref_w:
+ self._draw_text(
+ draw,
+ (xs, y + py * (my + 2 * scale_height / 3.0)),
+ (form % (val_steps[i])).strip(),
+ **self.style,
+ )
+ last_x = xs
+ # minor
+ x_steps = (
+ px * (minor_steps - minval) * scale_width / (maxval - minval) + x + px * mx
+ )
+ for xs in x_steps:
+ self._draw_line(
+ draw,
+ [(xs, y + py * my), (xs, y + py * (my + scale_height / 6.0))],
+ **self.style,
+ )
+
+ def _add_colorbar_units(
+ self,
+ draw,
+ unit,
+ is_vertical,
+ is_right,
+ is_bottom,
+ x,
+ y,
+ mx,
+ my,
+ scale_width,
+ scale_height,
+ x_spacer,
+ y_spacer,
+ ):
+ if is_vertical:
+ if is_right:
+ x_ = x - mx - scale_width / 2.0
else:
- x_ = x + mx + scale_width + x_spacer / 2.0
- if is_bottom:
- y_ = y - my - scale_height / 2.0
- else:
- y_ = y + my + scale_height / 2.0
- # draw marking
- self._draw_text(draw, (x_, y_), self.style['unit'], **self.style)
-
- if title:
- # check for font object
- self._get_current_font()
-
- # calculate position
- tw, th = draw.textsize(title, self.style['font'])
- if is_vertical:
- # TODO: Rotate the text?
- if is_right:
- x = x - mx - scale_width - tw
- else:
- x = x + mx + scale_width + tw
- y = y + my + scale_height / 2.0
+ x_ = x + mx + scale_width / 2.0
+ y_ = y + my + scale_height + y_spacer / 2.0
+ else:
+ x_ = x + mx + scale_width + x_spacer / 2.0
+ if is_bottom:
+ y_ = y - my - scale_height / 2.0
else:
- x = x + mx + scale_width / 2.0
- if is_bottom:
- y = y - my - scale_height - th
- else:
- y = y + my + scale_height + th
- self._draw_text(draw, (x, y), title, **self.style)
+ y_ = y + my + scale_height / 2.0
+ # draw marking
+ self._draw_text(draw, (x_, y_), unit, **self.style)
+
+ def _add_colorbar_title(
+ self,
+ draw,
+ title,
+ is_vertical,
+ is_right,
+ is_bottom,
+ x,
+ y,
+ mx,
+ my,
+ scale_width,
+ scale_height,
+ ):
+ # check for font object
+ self._get_current_font()
- # finalize
- self._finalize(draw)
+ # calculate position
+ tw, th = draw.textsize(title, self.style["font"])
+ if is_vertical:
+ # TODO: Rotate the text?
+ if is_right:
+ x = x - mx - scale_width - tw
+ else:
+ x = x + mx + scale_width + tw
+ y = y + my + scale_height / 2.0
+ else:
+ x = x + mx + scale_width / 2.0
+ if is_bottom:
+ y = y - my - scale_height - th
+ else:
+ y = y + my + scale_height + th
+ self._draw_text(draw, (x, y), title, **self.style)
def _form_xy_box(self, box):
newbox = box + []
@@ -591,13 +747,37 @@ class DecoratorBase(object):
self.image.paste(comp, box)
+def _create_colorbar_image(
+ colormap, minval, maxval, scale_height, scale_width, is_vertical
+):
+ if TImage is None:
+ raise ImportError(
+ "Missing 'trollimage' dependency. Required colorbar creation."
+ )
+
+ if is_vertical:
+ linedata = np.ones((scale_width, 1)) * np.arange(
+ minval, maxval, float(maxval - minval) / scale_height
+ )
+ linedata = linedata.transpose()
+ else:
+ linedata = np.ones((scale_height, 1)) * np.arange(
+ minval, maxval, float(maxval - minval) / scale_width
+ )
+
+ timg = TImage(linedata, mode="L")
+ timg.colorize(colormap)
+ return timg.pil_image()
+
+
def _round_arange(val_min, val_max, dval):
- """
- Returns an array of values in the range from valmin to valmax
- but with stepping, dval. This is similar to numpy.arange except
+ """Get an array of values in the range from valmin to valmax but with stepping by dval.
+
+ This is similar to numpy.arange except
the values must be rounded to the nearest multiple of dval.
"""
- vals = np.arange(val_min, val_max, dval)
+ delta = val_max - val_min
+ vals = np.arange(val_min, val_max, np.sign(delta) * dval)
round_vals = vals - vals % dval
if round_vals[0] < val_min:
round_vals = round_vals[1:]
@@ -605,25 +785,26 @@ def _round_arange(val_min, val_max, dval):
def _round_arange2(val_min, val_max, dval):
- """
- Returns an array of values in the range from valmin to valmax
- but with stepping, dval. This is similar to numpy.linspace except
+ """Get an array of values in the range from valmin to valmax with stepping by dval.
+
+ This is similar to numpy.linspace except
the values must be rounded to the nearest multiple of dval.
The difference to _round_arange is that the return values include
- also the bounary value val_max.
+ also the boundary value val_max.
"""
val_min_round = val_min + (dval - val_min % dval) % dval
val_max_round = val_max - val_max % dval
- n_points = int((val_max_round - val_min_round) / dval) + 1
+ n_points = int(abs(val_max_round - val_min_round) / dval) + 1
vals = np.linspace(val_min_round, val_max_round, num=n_points)
return vals
def _optimize_scale_numbers(minval, maxval, dval):
- """
- find a suitable number format, A and B in "%A.Bf" and power if numbers are large
- for display of scale numbers.
+ """Find a suitable number format for display of scale numbers.
+
+ Returns:
+ A and B in "%A.Bf" and power if numbers are large.
"""
ffra = 1
# no fractions, so turn off remainder
=====================================
pydecorate/tests/__init__.py
=====================================
=====================================
pydecorate/tests/test_decorator_agg.py
=====================================
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2022 Pydecorate developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Tests for the aggdraw-based decorator."""
+
+import numpy as np
+import pytest
+from PIL import Image
+from trollimage.colormap import rdbu
+
+from pydecorate import DecoratorAGG
+
+
+ at pytest.mark.parametrize(
+ "orientation_func_name", ["write_vertically", "write_horizontally"]
+)
+ at pytest.mark.parametrize(
+ "align_func_name", ["align_top", "align_bottom", "align_left", "align_right"]
+)
+ at pytest.mark.parametrize("clims", [(-90, 10), (10, -90)])
+def test_colorbar(tmp_path, orientation_func_name, align_func_name, clims):
+ fn = tmp_path / "test_colorbar.png"
+ img = Image.fromarray(np.zeros((200, 100, 3), dtype=np.uint8))
+ dc = DecoratorAGG(img)
+ getattr(dc, align_func_name)()
+ getattr(dc, orientation_func_name)()
+ cmap = rdbu.set_range(*clims, inplace=False)
+ dc.add_scale(cmap, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
+ img.save(fn)
+
+ # check results
+ output_img = Image.open(fn)
+ arr = np.array(output_img)
+ _assert_colorbar_orientation_alignment(arr, orientation_func_name, align_func_name)
+
+
+def _assert_colorbar_orientation_alignment(
+ img_arr, orientation_func_name, align_func_name
+):
+ cbar_size = 60
+ if orientation_func_name == "write_vertically":
+ if align_func_name in ("align_left", "align_top", "align_bottom"):
+ assert np.unique(img_arr[:, :cbar_size]).size >= 100
+ np.testing.assert_allclose(img_arr[:, cbar_size:], 0)
+ else:
+ assert np.unique(img_arr[:, -cbar_size:]).size >= 100
+ np.testing.assert_allclose(img_arr[:, :-cbar_size], 0)
+ else:
+ if align_func_name in ("align_top", "align_left", "align_right"):
+ assert np.unique(img_arr[:cbar_size, :]).size >= 100
+ np.testing.assert_allclose(img_arr[-cbar_size:, :], 0)
+ else:
+ assert np.unique(img_arr[-cbar_size:, :]).size >= 100
+ np.testing.assert_allclose(img_arr[:cbar_size, :], 0)
=====================================
test.py → pydecorate/tests/test_legacy.py
=====================================
@@ -6,44 +6,50 @@ NOTE: These aren't proper unit tests
"""
+import os
+
+REPOS_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+FONTS_ROOT = os.path.join(REPOS_ROOT, "pydecorate", "fonts")
+DEJAVU_FONT = os.path.join(FONTS_ROOT, "DejaVuSerif.ttf")
+
def test_style_retention():
+ # import aggdraw
from PIL import Image
- from pydecorate import DecoratorAGG
- import aggdraw
from trollimage.colormap import rdbu
- font = aggdraw.Font("navy", "pydecorate/fonts/DejaVuSerif.ttf", size=20)
- font_scale = aggdraw.Font("black", "pydecorate/fonts/DejaVuSerif.ttf", size=12)
+ from pydecorate import DecoratorAGG
+
+ # font = aggdraw.Font("navy", DEJAVU_FONT, size=20)
+ # font_scale = aggdraw.Font("black", DEJAVU_FONT, size=12)
rdbu.colors = rdbu.colors[::-1]
rdbu.set_range(-90, 10)
- img = Image.open('BMNG_clouds_201109181715_areaT2.png')
+ img = Image.open(os.path.join(REPOS_ROOT, "BMNG_clouds_201109181715_areaT2.png"))
dc = DecoratorAGG(img)
+ # dc.write_vertically()
+ # dc.add_logo("logos/pytroll_light_big.png")
+ # dc.add_logo("logos/NASA_Logo.gif",margins=[10,10],bg='yellow')
+ # dc.add_logo("logos/pytroll_light_big.png")
+ # font = aggdraw.Font("blue", DEJAVU_FONT, size=16)
+ # dc.add_text("Some text",font=font)
- #dc.write_vertically()
- #dc.add_logo("logos/pytroll_light_big.png")
- #dc.add_logo("logos/NASA_Logo.gif",margins=[10,10],bg='yellow')
- #dc.add_logo("logos/pytroll_light_big.png")
- font = aggdraw.Font("blue", "pydecorate/fonts/DejaVuSerif.ttf", size=16)
- #dc.add_text("Some text",font=font)
-
-
- #dc.align_right()
- dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit='K')
+ # dc.align_right()
+ dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
- #dc.align_bottom()
- #dc.add_scale(rdbu, extend=True, tick_marks=2.0, line_opacity=100, width=60)
+ # dc.align_bottom()
+ # dc.add_scale(rdbu, extend=True, tick_marks=2.0, line_opacity=100, width=60)
- #dc.align_right()
- #dc.write_vertically()
+ # dc.align_right()
+ # dc.write_vertically()
dc.align_bottom()
- dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit='K')
+ dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
- #dc.align_left()
- #dc.add_scale(rdbu, extend=True, font=font_scale, tick_marks=2.0, minor_tick_marks=1.0, line_opacity=100, width=60, unit='K')
+ # dc.align_left()
+ # dc.add_scale(rdbu, extend=True, font=font_scale, tick_marks=2.0, minor_tick_marks=1.0,
+ # line_opacity=100, width=60, unit='K')
# img.show()
img.save("style_retention.png")
@@ -132,9 +138,3 @@ def test_style_retention():
#
#
# img.show()
-
-
-if __name__ == "__main__":
- import sys
- import pytest
- sys.exit(pytest.main())
=====================================
pyproject.toml
=====================================
@@ -2,4 +2,12 @@
requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4", "setuptools_scm_git_archive"]
[tool.setuptools_scm]
-write_to = "pydecorate/version.py"
\ No newline at end of file
+write_to = "pydecorate/version.py"
+
+[tool.isort]
+sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
+profile = "black"
+skip_gitignore = true
+default_section = "THIRDPARTY"
+known_first_party = "pydecorate"
+line_length = 120
=====================================
setup.cfg
=====================================
@@ -0,0 +1,10 @@
+[flake8]
+max-line-length = 120
+ignore = D102,D103,D104,D107,W503
+exclude =
+ pydecorate/version.py
+
+[coverage:run]
+relative_files = True
+omit =
+ pydecorate/version.py
=====================================
setup.py
=====================================
@@ -1,25 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
-# Copyright (c) 2013, 2016 Adam Dybbroe
-
-# Author(s):
-
-# Hrobjartur Thorsteinsson <thorsteinssonh at gmail.com>
-
+#
+# Copyright (c) 2013 Pydecorate developers
+#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
-
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-
+#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Package building definition and script."""
+import sys
from setuptools import setup
@@ -27,6 +25,7 @@ try:
# HACK: https://github.com/pypa/setuptools_scm/issues/190#issuecomment-351181286
# Stop setuptools_scm from including all repository files
import setuptools_scm.integration
+
setuptools_scm.integration.find_files = lambda _: []
except ImportError:
pass
@@ -34,32 +33,39 @@ except ImportError:
with open("./README.rst", "r") as fd:
long_description = fd.read()
+tests_require = ["pytest", "pytest-cov", "trollimage"]
+if sys.platform.startswith("win"):
+ tests_require.append("freetype-py")
-setup(name='pydecorate',
- description='Decorating PIL images: logos, texts, pallettes',
- long_description=long_description,
- author='Hrobjartur Thorsteinsson',
- author_email='thorsteinssonh at gmail.com',
- classifiers=["Development Status :: 4 - Beta",
- "Intended Audience :: Science/Research",
- "License :: OSI Approved :: GNU General Public License v3 " +
- "or later (GPLv3+)",
- "Operating System :: OS Independent",
- "Programming Language :: Python",
- "Topic :: Scientific/Engineering"],
- url="https://github.com/pytroll/pydecorate",
- license='GPLv3',
- packages=['pydecorate'],
- include_package_data=True,
- package_data={'pydecorate': ['fonts/*.ttf']},
- # Project should use reStructuredText, so ensure that the docutils get
- # installed or upgraded on the target machine
- install_requires=['pillow', 'aggdraw'],
- setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'],
- scripts=[],
- data_files=[],
- # test_suite="",
- tests_require=['pytest'],
- python_requires='>=3.6',
- use_scm_version={'write_to': 'pydecorate/version.py'},
- zip_safe=False)
+setup(
+ name="pydecorate",
+ description="Decorating PIL images: logos, texts, pallettes",
+ long_description=long_description,
+ long_description_content_type="text/x-rst",
+ author="Hrobjartur Thorsteinsson",
+ author_email="thorsteinssonh at gmail.com",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Topic :: Scientific/Engineering",
+ ],
+ url="https://github.com/pytroll/pydecorate",
+ license="GPLv3+",
+ packages=["pydecorate"],
+ include_package_data=True,
+ package_data={"pydecorate": ["fonts/*.ttf"]},
+ install_requires=["pillow", "aggdraw", "numpy", "setuptools"],
+ setup_requires=["setuptools_scm", "setuptools_scm_git_archive"],
+ scripts=[],
+ data_files=[],
+ python_requires=">=3.7",
+ extras_require={
+ "tests": tests_require,
+ "docs": ["sphinx", "sphinx_rtd_theme", "sphinxcontrib-apidoc", "trollimage"],
+ },
+ use_scm_version={"write_to": "pydecorate/version.py"},
+ zip_safe=False,
+)
View it on GitLab: https://salsa.debian.org/debian-gis-team/pydecorate/-/commit/8af1fef61c1d8426a2af0e3c4def6db66f27b571
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pydecorate/-/commit/8af1fef61c1d8426a2af0e3c4def6db66f27b571
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20220306/6801e8e1/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list