[Git][debian-gis-team/trollimage][master] 5 commits: New upstream version 1.22.0

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Fri Nov 24 07:37:38 GMT 2023



Antonio Valentino pushed to branch master at Debian GIS Project / trollimage


Commits:
46d69694 by Antonio Valentino at 2023-11-24T07:22:57+00:00
New upstream version 1.22.0
- - - - -
2257b5fb by Antonio Valentino at 2023-11-24T07:23:04+00:00
Update upstream source from tag 'upstream/1.22.0'

Update to upstream version '1.22.0'
with Debian dir 53c7d633911f298bafa8fec5eb95ac39d135ce22
- - - - -
be1129c2 by Antonio Valentino at 2023-11-24T07:27:01+00:00
New upstream release

- - - - -
9791bdf3 by Antonio Valentino at 2023-11-24T07:28:15+00:00
Refresh all patches

- - - - -
233e21d4 by Antonio Valentino at 2023-11-24T07:36:01+00:00
Skip tests with too strict numerical accuracy

- - - - -


13 changed files:

- .github/workflows/ci.yaml
- .github/workflows/deploy.yaml
- .readthedocs.yml
- CHANGELOG.md
- debian/changelog
- debian/patches/0001-No-display.patch
- debian/rules
- + load_colorbrewer_colormaps.py
- pyproject.toml
- trollimage/colormap.py
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py


Changes:

=====================================
.github/workflows/ci.yaml
=====================================
@@ -10,7 +10,7 @@ jobs:
       fail-fast: true
       matrix:
         os: ["windows-latest", "ubuntu-latest", "macos-latest"]
-        python-version: ["3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.11", "3.12"]
         experimental: [false]
         include:
           - python-version: "3.11"


=====================================
.github/workflows/deploy.yaml
=====================================
@@ -28,19 +28,38 @@ jobs:
           path: dist/*.tar.gz
 
   build_wheels:
-    name: Build wheels on ${{ matrix.os }}
+    name: "Build wheels on ${{ matrix.os }} ${{ matrix.cibw_archs }}"
     runs-on: ${{ matrix.os }}
     strategy:
+      fail-fast: false
       matrix:
-        os: [ubuntu-20.04, windows-2019, macOS-11]
+        include:
+          - os: windows-2019
+            cibw_archs: "AMD64 ARM64"
+          - os: macos-11
+            cibw_archs: "x86_64 arm64"
+          - os: "ubuntu-20.04"
+            cibw_archs: "aarch64"
+          - os: "ubuntu-20.04"
+            cibw_archs: "x86_64"
 
     steps:
       - uses: actions/checkout at v4
+      - run: |
+          git fetch --prune --unshallow
+
+      - name: Set up QEMU
+        if: runner.os == 'Linux'
+        uses: docker/setup-qemu-action at v3
+        with:
+          platforms: all
 
       - name: Build wheels
-        uses: pypa/cibuildwheel at v2.15.0
+        uses: pypa/cibuildwheel at v2.16.2
         env:
-          CIBW_SKIP: "cp36-* cp37-* cp38-* cp312-* pp* *-i686"
+          CIBW_SKIP: "cp36-* cp37-* cp38-* pp* *-manylinux_i686 *-musllinux_i686 *-musllinux_aarch64 *-win32"
+          CIBW_ARCHS: "${{ matrix.cibw_archs }}"
+          CIBW_TEST_SKIP: "*_arm64 *_universal2:arm64"
 
       - uses: actions/upload-artifact at v3
         with:


=====================================
.readthedocs.yml
=====================================
@@ -3,7 +3,12 @@ version: 2
 build:
   os: "ubuntu-20.04"
   tools:
-    python: "3.10"
+    python: "3.11"
+  jobs:
+    post_checkout:
+      - git fetch --tags
+    pre_install:
+      - git update-index --assume-unchanged rtd_requirements.txt doc/conf.py
 sphinx:
   configuration: doc/conf.py
   fail_on_warning: true


=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,21 @@
+## Version 1.22.0 (2023/11/23)
+
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 145](https://github.com/pytroll/trollimage/pull/145) - Do not apply linear stretch to alpha band
+
+#### Features added
+
+* [PR 151](https://github.com/pytroll/trollimage/pull/151) - Preserve dtypes in XRImage "enhancements"
+* [PR 150](https://github.com/pytroll/trollimage/pull/150) - Keep the original dtype of the data when stretching
+* [PR 141](https://github.com/pytroll/trollimage/pull/141) - Update colorbrew colormaps to be more accurate
+
+In this release 4 pull requests were closed.
+
+
 ## Version 1.21.0 (2023/09/04)
 
 ### Issues Closed


=====================================
debian/changelog
=====================================
@@ -1,3 +1,14 @@
+trollimage (1.22.0-1) UNRELEASED; urgency=medium
+
+  * New upstream release.
+  * debian/patches:
+    - Refresh all patches.
+  * debian/rules:
+    - Skip testt with too strict numerical acciracy
+      (test_gamma_per_channel).
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Fri, 24 Nov 2023 07:23:16 +0000
+
 trollimage (1.21.0-1) unstable; urgency=medium
 
   [ Bas Couwenberg ]


=====================================
debian/patches/0001-No-display.patch
=====================================
@@ -8,10 +8,10 @@ Skip tests that require display.
  1 file changed, 1 insertion(+)
 
 diff --git a/trollimage/tests/test_image.py b/trollimage/tests/test_image.py
-index be83bfa..c9b8754 100644
+index 3421efe..64650c1 100644
 --- a/trollimage/tests/test_image.py
 +++ b/trollimage/tests/test_image.py
-@@ -1936,6 +1936,7 @@ class TestXRImage:
+@@ -2034,6 +2034,7 @@ class TestXRImage:
          """Test putalpha."""
          pass
  


=====================================
debian/rules
=====================================
@@ -6,7 +6,11 @@
 
 export DEB_BUILD_MAINT_OPTIONS=hardening=+all
 export PYBUILD_NAME=trollimage
-export PYBUILD_TEST_ARGS=--pyargs ${PYBUILD_NAME}
+
+# skip tests with too strict numerical accuracy
+export PYBUILD_TEST_ARGS=\
+-k "not test_gamma_per_channel" \
+--pyargs ${PYBUILD_NAME}
 
 %:
 	dh $@ --buildsystem=pybuild


=====================================
load_colorbrewer_colormaps.py
=====================================
@@ -0,0 +1,76 @@
+"""Helper script to convert colormaps from https://colorbrewer2.org into trollimage Colormap code.
+
+The text output by this script should be copied to the ``trollimage/colormap.py``.
+
+"""
+
+import json
+import sys
+import urllib.request
+
+JSON_URL = "https://raw.githubusercontent.com/axismaps/colorbrewer/master/export/colorbrewer.json"
+
+
+def main():
+    """Print python code version of trollimage Colormap objects for each colorbrewer colormap."""
+    cmap_groups = _load_colormap_info_from_colorbrewer()
+    _print_colormap_group(cmap_groups["seq"], "Sequential", "sequential_colormaps")
+    _print_colormap_group(cmap_groups["div"], "Diverging", "diverging_colormaps")
+    _print_colormap_group(cmap_groups["qual"], "Qualitative", "qualitative_colormaps",
+                          normalize_values=False)
+
+
+def _load_colormap_info_from_colorbrewer() -> dict[str, dict[str, list]]:
+    with urllib.request.urlopen(JSON_URL) as json_file:  # nosec: B310
+        colorbrewer_dict = json.load(json_file)
+        cmap_groups: dict[str, dict[str, list]] = {"div": {}, "seq": {}, "qual": {}}
+        for cmap_name, cmap_info in colorbrewer_dict.items():
+            max_colors = max((num_colors_str for num_colors_str in cmap_info.keys() if num_colors_str != "type"),
+                             key=lambda num_color_str: int(num_color_str))
+            cmap_colors = cmap_info[max_colors]
+            color_tuples = [rgb_color_str.replace("rgb(", "").replace(")", "").split(",")
+                            for rgb_color_str in cmap_colors]
+            cmap_groups[cmap_info["type"]][cmap_name.lower()] = color_tuples
+    return cmap_groups
+
+
+def _print_colormap_group(cmap_group: dict[str, list], human_group_name: str, group_var_name: str,
+                          normalize_values: bool = True) -> None:
+    print(f"# * {human_group_name} Colormaps *\n")
+    for cmap_name, cmap_colors in sorted(cmap_group.items()):
+        cmap_values, color_human_strings = _color_info_as_human_friendly_strings(cmap_colors, normalize_values)
+        _print_single_colormap(cmap_name, cmap_values, color_human_strings)
+
+    print(f"{group_var_name} = {{")
+    for cmap_name in sorted(cmap_group.keys()):
+        print(f"    \"{cmap_name}\": {cmap_name},")
+    print("}\n")
+
+
+def _color_info_as_human_friendly_strings(
+        cmap_colors: list[tuple[int, int, int]],
+        normalize_values: bool,
+) -> tuple[list[str], list[tuple[str, str, str]]]:
+    num_colors = len(cmap_colors)
+    color_human_strings = [
+        (f"{rgb_color[0]} / 255", f"{rgb_color[1]} / 255", f"{rgb_color[2]} / 255")
+        for rgb_color in cmap_colors
+    ]
+    cmap_values = [str(color_idx) for color_idx in range(num_colors)]
+    if normalize_values:
+        # 0 - 1 normalized values for non-qualitative colormaps
+        cmap_values = [f"{cval} / {num_colors - 1}" for cval in cmap_values]
+    return cmap_values, color_human_strings
+
+
+def _print_single_colormap(cmap_name: str, cmap_values: list[str], cmap_colors: list[tuple[str, str, str]]) -> None:
+    cmap_pairs = [(cval, rgb_color) for cval, rgb_color in zip(cmap_values, cmap_colors)]
+    print(f"{cmap_name} = Colormap(")
+    for cmap_value_str, cmap_color_tuple in cmap_pairs:
+        print(f"    ({cmap_value_str}, "
+              f"({cmap_color_tuple[0]}, {cmap_color_tuple[1]}, {cmap_color_tuple[2]})),")
+    print(")\n")
+
+
+if __name__ == "__main__":
+    sys.exit(main())


=====================================
pyproject.toml
=====================================
@@ -1,5 +1,12 @@
 [build-system]
-requires = ["setuptools", "wheel", "oldest-supported-numpy", "Cython>=3", "versioneer"]
+requires = [
+    "wheel",
+    'numpy; python_full_version<"3.12.0rc1"',
+    'numpy>=1.26.0rc1; python_full_version>="3.12.0rc1"',
+    "setuptools>=42",
+    "versioneer",
+    "Cython>=3.0.0"
+]
 build-backend = "setuptools.build_meta"
 
 [tool.coverage.run]


=====================================
trollimage/colormap.py
=====================================
@@ -782,297 +782,494 @@ rainbow = Colormap((0.000, (0.0, 0.0, 0.5)),
 # * Colors from www.ColorBrewer.org by Cynthia A. Brewer, Geography,
 # * Pennsylvania State University.
 
-# * Single hue *
-
-blues = Colormap((0.000, (247 / 255.0, 251 / 255.0, 1.0)),
-                 (1.000, (8 / 255.0, 48 / 255.0, 107 / 255.0)))
-
-greens = Colormap((0.000, (247 / 255.0, 252 / 255.0, 245 / 255.0)),
-                  (1.000, (0.0, 68 / 255.0, 27 / 255.0)))
-
-greys = Colormap((0.0, (1.0, 1.0, 1.0)),
-                 (1.0, (0.0, 0.0, 0.0)))
-
-oranges = Colormap((0.0, (1.0, 245 / 255.0, 235 / 255.0)),
-                   (1.0, (127 / 255.0, 39 / 255.0, 4 / 255.0)))
-
-purples = Colormap((0.0, (252 / 255.0, 251 / 255.0, 253 / 255.0)),
-                   (1.0, (63 / 255.0, 0.0, 125 / 255.0)))
-
-reds = Colormap((0.0, (1.0, 245 / 255.0, 240 / 255.0)),
-                (1.0, (103 / 255.0, 0.0, 13 / 255.0)))
-
-# * Multihue *
-
-# BuGn
-
-bugn = Colormap((0.000, (247 / 255.0, 252 / 255.0, 253 / 255.0)),
-                (1.000, (0.0, 68 / 255.0, 27 / 255.0)))
-
-# BuPu
-
-bupu = Colormap((0.000, (247 / 255.0, 252 / 255.0, 253 / 255.0)),
-                (1.000, (77 / 255.0, 0.0, 75 / 255.0)))
-
-# GnBu
-
-gnbu = Colormap((0.000, (247 / 255.0, 252 / 255.0, 240 / 255.0)),
-                (1.000, (8 / 255.0, 64 / 255.0, 129 / 255.0)))
-
-# OrRd
-
-orrd = Colormap((0.000, (255 / 255.0, 247 / 255.0, 236 / 255.0)),
-                (1.000, (127 / 255.0, 0.0, 0.0)))
-
-# PuBu
-
-pubu = Colormap((0.000, (1.0, 247 / 255.0, 251 / 255.0)),
-                (0.500, (116 / 255.0, 169 / 255.0, 207 / 255.0)),
-                (1.000, (2 / 255.0, 56 / 255.0, 88 / 255.0)))
-
-# PuBuGn
-
-pubugn = Colormap((0.000, (1.0, 247 / 255.0, 251 / 255.0)),
-                  (0.500, (103 / 255.0, 169 / 255.0, 207 / 255.0)),
-                  (1.000, (1 / 255.0, 70 / 255.0, 54 / 255.0)))
-
-# PuRd
-
-purd = Colormap((0.000, (247 / 255.0, 244 / 255.0, 249 / 255.0)),
-                (1.000, (103 / 255.0, 0.0, 31 / 255.0)))
-
-# RdPu
-
-rdpu = Colormap((0.000, (1.0, 247 / 255.0, 243 / 255.0)),
-                (1.000, (73 / 255.0, 0.0, 106 / 255.0)))
-
-# YlGn
-
-ylgn = Colormap((0.000, (1.0, 1.0, 229 / 255.0)),
-                (1.000, (0.0, 69 / 255.0, 41 / 255.0)))
-
-# YlGnBu
-
-ylgnbu = Colormap((0.000, (1.0, 1.0, 217 / 255.0)),
-                  (0.500, (65 / 255.0, 182 / 255.0, 196 / 255.0)),
-                  (1.000, (8 / 255.0, 29 / 255.0, 88 / 255.0)))
-
-# YlOrBr
-
-ylorbr = Colormap((0.000, (1.0, 1.0, 229 / 255.0)),
-                  (0.500, (254 / 255.0, 153 / 255.0, 41 / 255.0)),
-                  (1.000, (102 / 255.0, 37 / 255.0, 6 / 255.0)))
-
-# YlOrRd
-
-ylorrd = Colormap((0.000, (1.0, 1.0, 204 / 255.0)),
-                  (0.500, (254 / 255.0, 141 / 255.0, 60 / 255.0)),
-                  (1.000, (128 / 255.0, 0.0, 38 / 255.0)))
-
-sequential_colormaps = [blues, greens, greys, oranges, purples, reds,
-                        bugn, bupu, gnbu, orrd, pubu, pubugn, purd, rdpu,
-                        ylgn, ylgnbu, ylorbr, ylorrd]
-
-# * Diverging *
-
-brbg = Colormap((0.0, (84 / 255.0, 48 / 255.0, 5 / 255.0)),
-                (0.1, (140 / 255.0, 81 / 255.0, 10 / 255.0)),
-                (0.2, (191 / 255.0, 129 / 255.0, 45 / 255.0)),
-                (0.3, (223 / 255.0, 194 / 255.0, 125 / 255.0)),
-                (0.4, (246 / 255.0, 232 / 255.0, 195 / 255.0)),
-                (0.5, (245 / 255.0, 245 / 255.0, 245 / 255.0)),
-                (0.6, (199 / 255.0, 234 / 255.0, 229 / 255.0)),
-                (0.7, (128 / 255.0, 205 / 255.0, 193 / 255.0)),
-                (0.8, (53 / 255.0, 151 / 255.0, 143 / 255.0)),
-                (0.9, (1 / 255.0, 102 / 255.0, 94 / 255.0)),
-                (1.0, (0 / 255.0, 60 / 255.0, 48 / 255.0)))
-
-piyg = Colormap((0.0, (142 / 255.0, 1 / 255.0, 82 / 255.0)),
-                (0.1, (197 / 255.0, 27 / 255.0, 125 / 255.0)),
-                (0.2, (222 / 255.0, 119 / 255.0, 174 / 255.0)),
-                (0.3, (241 / 255.0, 182 / 255.0, 218 / 255.0)),
-                (0.4, (253 / 255.0, 224 / 255.0, 239 / 255.0)),
-                (0.5, (247 / 255.0, 247 / 255.0, 247 / 255.0)),
-                (0.6, (230 / 255.0, 245 / 255.0, 208 / 255.0)),
-                (0.7, (184 / 255.0, 225 / 255.0, 134 / 255.0)),
-                (0.8, (127 / 255.0, 188 / 255.0, 65 / 255.0)),
-                (0.9, (77 / 255.0, 146 / 255.0, 33 / 255.0)),
-                (1.0, (39 / 255.0, 100 / 255.0, 25 / 255.0)))
-
-prgn = Colormap((0.0, (64 / 255.0, 0 / 255.0, 75 / 255.0)),
-                (0.1, (118 / 255.0, 42 / 255.0, 131 / 255.0)),
-                (0.2, (153 / 255.0, 112 / 255.0, 171 / 255.0)),
-                (0.3, (194 / 255.0, 165 / 255.0, 207 / 255.0)),
-                (0.4, (231 / 255.0, 212 / 255.0, 232 / 255.0)),
-                (0.5, (247 / 255.0, 247 / 255.0, 247 / 255.0)),
-                (0.6, (217 / 255.0, 240 / 255.0, 211 / 255.0)),
-                (0.7, (166 / 255.0, 219 / 255.0, 160 / 255.0)),
-                (0.8, (90 / 255.0, 174 / 255.0, 97 / 255.0)),
-                (0.9, (27 / 255.0, 120 / 255.0, 55 / 255.0)),
-                (1.0, (0 / 255.0, 68 / 255.0, 27 / 255.0)))
-
-puor = Colormap((0.0, (127 / 255.0, 59 / 255.0, 8 / 255.0)),
-                (0.1, (179 / 255.0, 88 / 255.0, 6 / 255.0)),
-                (0.2, (224 / 255.0, 130 / 255.0, 20 / 255.0)),
-                (0.3, (253 / 255.0, 184 / 255.0, 99 / 255.0)),
-                (0.4, (254 / 255.0, 224 / 255.0, 182 / 255.0)),
-                (0.5, (247 / 255.0, 247 / 255.0, 247 / 255.0)),
-                (0.6, (216 / 255.0, 218 / 255.0, 235 / 255.0)),
-                (0.7, (178 / 255.0, 171 / 255.0, 210 / 255.0)),
-                (0.8, (128 / 255.0, 115 / 255.0, 172 / 255.0)),
-                (0.9, (84 / 255.0, 39 / 255.0, 136 / 255.0)),
-                (1.0, (45 / 255.0, 0 / 255.0, 75 / 255.0)))
-
-rdbu = Colormap((0.0, (103 / 255.0, 0 / 255.0, 31 / 255.0)),
-                (0.1, (178 / 255.0, 24 / 255.0, 43 / 255.0)),
-                (0.2, (214 / 255.0, 96 / 255.0, 77 / 255.0)),
-                (0.3, (244 / 255.0, 165 / 255.0, 130 / 255.0)),
-                (0.4, (253 / 255.0, 219 / 255.0, 199 / 255.0)),
-                (0.5, (247 / 255.0, 247 / 255.0, 247 / 255.0)),
-                (0.6, (209 / 255.0, 229 / 255.0, 240 / 255.0)),
-                (0.7, (146 / 255.0, 197 / 255.0, 222 / 255.0)),
-                (0.8, (67 / 255.0, 147 / 255.0, 195 / 255.0)),
-                (0.9, (33 / 255.0, 102 / 255.0, 172 / 255.0)),
-                (1.0, (5 / 255.0, 48 / 255.0, 97 / 255.0)))
-
-rdgy = Colormap((0.0, (103 / 255.0, 0 / 255.0, 31 / 255.0)),
-                (0.1, (178 / 255.0, 24 / 255.0, 43 / 255.0)),
-                (0.2, (214 / 255.0, 96 / 255.0, 77 / 255.0)),
-                (0.3, (244 / 255.0, 165 / 255.0, 130 / 255.0)),
-                (0.4, (253 / 255.0, 219 / 255.0, 199 / 255.0)),
-                (0.5, (255 / 255.0, 255 / 255.0, 255 / 255.0)),
-                (0.6, (224 / 255.0, 224 / 255.0, 224 / 255.0)),
-                (0.7, (186 / 255.0, 186 / 255.0, 186 / 255.0)),
-                (0.8, (135 / 255.0, 135 / 255.0, 135 / 255.0)),
-                (0.9, (77 / 255.0, 77 / 255.0, 77 / 255.0)),
-                (1.0, (26 / 255.0, 26 / 255.0, 26 / 255.0)))
-
-rdylbu = Colormap((0.0, (165 / 255.0, 0 / 255.0, 38 / 255.0)),
-                  (0.1, (215 / 255.0, 48 / 255.0, 39 / 255.0)),
-                  (0.2, (244 / 255.0, 109 / 255.0, 67 / 255.0)),
-                  (0.3, (253 / 255.0, 174 / 255.0, 97 / 255.0)),
-                  (0.4, (254 / 255.0, 224 / 255.0, 144 / 255.0)),
-                  (0.5, (255 / 255.0, 255 / 255.0, 191 / 255.0)),
-                  (0.6, (224 / 255.0, 243 / 255.0, 248 / 255.0)),
-                  (0.7, (171 / 255.0, 217 / 255.0, 233 / 255.0)),
-                  (0.8, (116 / 255.0, 173 / 255.0, 209 / 255.0)),
-                  (0.9, (69 / 255.0, 117 / 255.0, 180 / 255.0)),
-                  (1.0, (49 / 255.0, 54 / 255.0, 149 / 255.0)))
-
-rdylgn = Colormap((0.0, (165 / 255.0, 0 / 255.0, 38 / 255.0)),
-                  (0.1, (215 / 255.0, 48 / 255.0, 39 / 255.0)),
-                  (0.2, (244 / 255.0, 109 / 255.0, 67 / 255.0)),
-                  (0.3, (253 / 255.0, 174 / 255.0, 97 / 255.0)),
-                  (0.4, (254 / 255.0, 224 / 255.0, 139 / 255.0)),
-                  (0.5, (255 / 255.0, 255 / 255.0, 191 / 255.0)),
-                  (0.6, (217 / 255.0, 239 / 255.0, 139 / 255.0)),
-                  (0.7, (166 / 255.0, 217 / 255.0, 106 / 255.0)),
-                  (0.8, (102 / 255.0, 189 / 255.0, 99 / 255.0)),
-                  (0.9, (26 / 255.0, 152 / 255.0, 80 / 255.0)),
-                  (1.0, (0 / 255.0, 104 / 255.0, 55 / 255.0)))
-
-spectral = Colormap((0.0, (158 / 255.0, 1 / 255.0, 66 / 255.0)),
-                    (0.1, (213 / 255.0, 62 / 255.0, 79 / 255.0)),
-                    (0.2, (244 / 255.0, 109 / 255.0, 67 / 255.0)),
-                    (0.3, (253 / 255.0, 174 / 255.0, 97 / 255.0)),
-                    (0.4, (254 / 255.0, 224 / 255.0, 139 / 255.0)),
-                    (0.5, (255 / 255.0, 255 / 255.0, 191 / 255.0)),
-                    (0.6, (230 / 255.0, 245 / 255.0, 152 / 255.0)),
-                    (0.7, (171 / 255.0, 221 / 255.0, 164 / 255.0)),
-                    (0.8, (102 / 255.0, 194 / 255.0, 165 / 255.0)),
-                    (0.9, (50 / 255.0, 136 / 255.0, 189 / 255.0)),
-                    (1.0, (94 / 255.0, 79 / 255.0, 162 / 255.0)))
-
-diverging_colormaps = [brbg, piyg, prgn, puor, rdbu, rdgy, rdylbu, rdylgn,
-                       spectral]
-
-# * qualitative colormaps *
-
-set1 = Colormap((0, (228 / 255.0, 26 / 255.0, 28 / 255.0)),
-                (1, (55 / 255.0, 126 / 255.0, 184 / 255.0)),
-                (2, (77 / 255.0, 175 / 255.0, 74 / 255.0)),
-                (3, (152 / 255.0, 78 / 255.0, 163 / 255.0)),
-                (4, (255 / 255.0, 127 / 255.0, 0 / 255.0)),
-                (5, (255 / 255.0, 255 / 255.0, 51 / 255.0)),
-                (6, (166 / 255.0, 86 / 255.0, 40 / 255.0)),
-                (7, (247 / 255.0, 129 / 255.0, 191 / 255.0)),
-                (8, (153 / 255.0, 153 / 255.0, 153 / 255.0)))
-
-set2 = Colormap((0, (102 / 255.0, 194 / 255.0, 165 / 255.0)),
-                (1, (252 / 255.0, 141 / 255.0, 98 / 255.0)),
-                (2, (141 / 255.0, 160 / 255.0, 203 / 255.0)),
-                (3, (231 / 255.0, 138 / 255.0, 195 / 255.0)),
-                (4, (166 / 255.0, 216 / 255.0, 84 / 255.0)),
-                (5, (255 / 255.0, 217 / 255.0, 47 / 255.0)),
-                (6, (229 / 255.0, 196 / 255.0, 148 / 255.0)),
-                (7, (179 / 255.0, 179 / 255.0, 179 / 255.0)))
-
-set3 = Colormap((0, (141 / 255.0, 211 / 255.0, 199 / 255.0)),
-                (1, (255 / 255.0, 255 / 255.0, 179 / 255.0)),
-                (2, (190 / 255.0, 186 / 255.0, 218 / 255.0)),
-                (3, (251 / 255.0, 128 / 255.0, 114 / 255.0)),
-                (4, (128 / 255.0, 177 / 255.0, 211 / 255.0)),
-                (5, (253 / 255.0, 180 / 255.0, 98 / 255.0)),
-                (6, (179 / 255.0, 222 / 255.0, 105 / 255.0)),
-                (7, (252 / 255.0, 205 / 255.0, 229 / 255.0)),
-                (8, (217 / 255.0, 217 / 255.0, 217 / 255.0)),
-                (9, (188 / 255.0, 128 / 255.0, 189 / 255.0)),
-                (10, (204 / 255.0, 235 / 255.0, 197 / 255.0)),
-                (11, (255 / 255.0, 237 / 255.0, 111 / 255.0)))
-
-paired = Colormap((0, (166 / 255.0, 206 / 255.0, 227 / 255.0)),
-                  (1, (31 / 255.0, 120 / 255.0, 180 / 255.0)),
-                  (2, (178 / 255.0, 223 / 255.0, 138 / 255.0)),
-                  (3, (51 / 255.0, 160 / 255.0, 44 / 255.0)),
-                  (4, (251 / 255.0, 154 / 255.0, 153 / 255.0)),
-                  (5, (227 / 255.0, 26 / 255.0, 28 / 255.0)),
-                  (6, (253 / 255.0, 191 / 255.0, 111 / 255.0)),
-                  (7, (255 / 255.0, 127 / 255.0, 0 / 255.0)),
-                  (8, (202 / 255.0, 178 / 255.0, 214 / 255.0)),
-                  (9, (106 / 255.0, 61 / 255.0, 154 / 255.0)),
-                  (10, (255 / 255.0, 255 / 255.0, 153 / 255.0)),
-                  (11, (177 / 255.0, 89 / 255.0, 40 / 255.0)))
-
-accent = Colormap((0, (127 / 255.0, 201 / 255.0, 127 / 255.0)),
-                  (1, (190 / 255.0, 174 / 255.0, 212 / 255.0)),
-                  (2, (253 / 255.0, 192 / 255.0, 134 / 255.0)),
-                  (3, (255 / 255.0, 255 / 255.0, 153 / 255.0)),
-                  (4, (56 / 255.0, 108 / 255.0, 176 / 255.0)),
-                  (5, (240 / 255.0, 2 / 255.0, 127 / 255.0)),
-                  (6, (191 / 255.0, 91 / 255.0, 23 / 255.0)),
-                  (7, (102 / 255.0, 102 / 255.0, 102 / 255.0)))
-
-dark2 = Colormap((0, (27 / 255.0, 158 / 255.0, 119 / 255.0)),
-                 (1, (217 / 255.0, 95 / 255.0, 2 / 255.0)),
-                 (2, (117 / 255.0, 112 / 255.0, 179 / 255.0)),
-                 (3, (231 / 255.0, 41 / 255.0, 138 / 255.0)),
-                 (4, (102 / 255.0, 166 / 255.0, 30 / 255.0)),
-                 (5, (230 / 255.0, 171 / 255.0, 2 / 255.0)),
-                 (6, (166 / 255.0, 118 / 255.0, 29 / 255.0)),
-                 (7, (102 / 255.0, 102 / 255.0, 102 / 255.0)))
-
-pastel1 = Colormap((0, (251 / 255.0, 180 / 255.0, 174 / 255.0)),
-                   (1, (179 / 255.0, 205 / 255.0, 227 / 255.0)),
-                   (2, (204 / 255.0, 235 / 255.0, 197 / 255.0)),
-                   (3, (222 / 255.0, 203 / 255.0, 228 / 255.0)),
-                   (4, (254 / 255.0, 217 / 255.0, 166 / 255.0)),
-                   (5, (255 / 255.0, 255 / 255.0, 204 / 255.0)),
-                   (6, (229 / 255.0, 216 / 255.0, 189 / 255.0)),
-                   (7, (253 / 255.0, 218 / 255.0, 236 / 255.0)),
-                   (8, (242 / 255.0, 242 / 255.0, 242 / 255.0)))
-
-pastel2 = Colormap((0, (179 / 255.0, 226 / 255.0, 205 / 255.0)),
-                   (1, (253 / 255.0, 205 / 255.0, 172 / 255.0)),
-                   (2, (203 / 255.0, 213 / 255.0, 232 / 255.0)),
-                   (3, (244 / 255.0, 202 / 255.0, 228 / 255.0)),
-                   (4, (230 / 255.0, 245 / 255.0, 201 / 255.0)),
-                   (5, (255 / 255.0, 242 / 255.0, 174 / 255.0)),
-                   (6, (241 / 255.0, 226 / 255.0, 204 / 255.0)),
-                   (7, (204 / 255.0, 204 / 255.0, 204 / 255.0)))
-
-qualitative_colormaps = [set1, set2, set3,
-                         paired, accent, dark2,
-                         pastel1, pastel2]
+# * Sequential Colormaps *
+
+blues = Colormap(
+    (0 / 8, (247 / 255, 251 / 255, 255 / 255)),
+    (1 / 8, (222 / 255, 235 / 255, 247 / 255)),
+    (2 / 8, (198 / 255, 219 / 255, 239 / 255)),
+    (3 / 8, (158 / 255, 202 / 255, 225 / 255)),
+    (4 / 8, (107 / 255, 174 / 255, 214 / 255)),
+    (5 / 8, (66 / 255, 146 / 255, 198 / 255)),
+    (6 / 8, (33 / 255, 113 / 255, 181 / 255)),
+    (7 / 8, (8 / 255, 81 / 255, 156 / 255)),
+    (8 / 8, (8 / 255, 48 / 255, 107 / 255)),
+)
+
+bugn = Colormap(
+    (0 / 8, (247 / 255, 252 / 255, 253 / 255)),
+    (1 / 8, (229 / 255, 245 / 255, 249 / 255)),
+    (2 / 8, (204 / 255, 236 / 255, 230 / 255)),
+    (3 / 8, (153 / 255, 216 / 255, 201 / 255)),
+    (4 / 8, (102 / 255, 194 / 255, 164 / 255)),
+    (5 / 8, (65 / 255, 174 / 255, 118 / 255)),
+    (6 / 8, (35 / 255, 139 / 255, 69 / 255)),
+    (7 / 8, (0 / 255, 109 / 255, 44 / 255)),
+    (8 / 8, (0 / 255, 68 / 255, 27 / 255)),
+)
+
+bupu = Colormap(
+    (0 / 8, (247 / 255, 252 / 255, 253 / 255)),
+    (1 / 8, (224 / 255, 236 / 255, 244 / 255)),
+    (2 / 8, (191 / 255, 211 / 255, 230 / 255)),
+    (3 / 8, (158 / 255, 188 / 255, 218 / 255)),
+    (4 / 8, (140 / 255, 150 / 255, 198 / 255)),
+    (5 / 8, (140 / 255, 107 / 255, 177 / 255)),
+    (6 / 8, (136 / 255, 65 / 255, 157 / 255)),
+    (7 / 8, (129 / 255, 15 / 255, 124 / 255)),
+    (8 / 8, (77 / 255, 0 / 255, 75 / 255)),
+)
+
+gnbu = Colormap(
+    (0 / 8, (247 / 255, 252 / 255, 240 / 255)),
+    (1 / 8, (224 / 255, 243 / 255, 219 / 255)),
+    (2 / 8, (204 / 255, 235 / 255, 197 / 255)),
+    (3 / 8, (168 / 255, 221 / 255, 181 / 255)),
+    (4 / 8, (123 / 255, 204 / 255, 196 / 255)),
+    (5 / 8, (78 / 255, 179 / 255, 211 / 255)),
+    (6 / 8, (43 / 255, 140 / 255, 190 / 255)),
+    (7 / 8, (8 / 255, 104 / 255, 172 / 255)),
+    (8 / 8, (8 / 255, 64 / 255, 129 / 255)),
+)
+
+greens = Colormap(
+    (0 / 8, (247 / 255, 252 / 255, 245 / 255)),
+    (1 / 8, (229 / 255, 245 / 255, 224 / 255)),
+    (2 / 8, (199 / 255, 233 / 255, 192 / 255)),
+    (3 / 8, (161 / 255, 217 / 255, 155 / 255)),
+    (4 / 8, (116 / 255, 196 / 255, 118 / 255)),
+    (5 / 8, (65 / 255, 171 / 255, 93 / 255)),
+    (6 / 8, (35 / 255, 139 / 255, 69 / 255)),
+    (7 / 8, (0 / 255, 109 / 255, 44 / 255)),
+    (8 / 8, (0 / 255, 68 / 255, 27 / 255)),
+)
+
+greys = Colormap(
+    (0 / 8, (255 / 255, 255 / 255, 255 / 255)),
+    (1 / 8, (240 / 255, 240 / 255, 240 / 255)),
+    (2 / 8, (217 / 255, 217 / 255, 217 / 255)),
+    (3 / 8, (189 / 255, 189 / 255, 189 / 255)),
+    (4 / 8, (150 / 255, 150 / 255, 150 / 255)),
+    (5 / 8, (115 / 255, 115 / 255, 115 / 255)),
+    (6 / 8, (82 / 255, 82 / 255, 82 / 255)),
+    (7 / 8, (37 / 255, 37 / 255, 37 / 255)),
+    (8 / 8, (0 / 255, 0 / 255, 0 / 255)),
+)
+
+oranges = Colormap(
+    (0 / 8, (255 / 255, 245 / 255, 235 / 255)),
+    (1 / 8, (254 / 255, 230 / 255, 206 / 255)),
+    (2 / 8, (253 / 255, 208 / 255, 162 / 255)),
+    (3 / 8, (253 / 255, 174 / 255, 107 / 255)),
+    (4 / 8, (253 / 255, 141 / 255, 60 / 255)),
+    (5 / 8, (241 / 255, 105 / 255, 19 / 255)),
+    (6 / 8, (217 / 255, 72 / 255, 1 / 255)),
+    (7 / 8, (166 / 255, 54 / 255, 3 / 255)),
+    (8 / 8, (127 / 255, 39 / 255, 4 / 255)),
+)
+
+orrd = Colormap(
+    (0 / 8, (255 / 255, 247 / 255, 236 / 255)),
+    (1 / 8, (254 / 255, 232 / 255, 200 / 255)),
+    (2 / 8, (253 / 255, 212 / 255, 158 / 255)),
+    (3 / 8, (253 / 255, 187 / 255, 132 / 255)),
+    (4 / 8, (252 / 255, 141 / 255, 89 / 255)),
+    (5 / 8, (239 / 255, 101 / 255, 72 / 255)),
+    (6 / 8, (215 / 255, 48 / 255, 31 / 255)),
+    (7 / 8, (179 / 255, 0 / 255, 0 / 255)),
+    (8 / 8, (127 / 255, 0 / 255, 0 / 255)),
+)
+
+pubu = Colormap(
+    (0 / 8, (255 / 255, 247 / 255, 251 / 255)),
+    (1 / 8, (236 / 255, 231 / 255, 242 / 255)),
+    (2 / 8, (208 / 255, 209 / 255, 230 / 255)),
+    (3 / 8, (166 / 255, 189 / 255, 219 / 255)),
+    (4 / 8, (116 / 255, 169 / 255, 207 / 255)),
+    (5 / 8, (54 / 255, 144 / 255, 192 / 255)),
+    (6 / 8, (5 / 255, 112 / 255, 176 / 255)),
+    (7 / 8, (4 / 255, 90 / 255, 141 / 255)),
+    (8 / 8, (2 / 255, 56 / 255, 88 / 255)),
+)
+
+pubugn = Colormap(
+    (0 / 8, (255 / 255, 247 / 255, 251 / 255)),
+    (1 / 8, (236 / 255, 226 / 255, 240 / 255)),
+    (2 / 8, (208 / 255, 209 / 255, 230 / 255)),
+    (3 / 8, (166 / 255, 189 / 255, 219 / 255)),
+    (4 / 8, (103 / 255, 169 / 255, 207 / 255)),
+    (5 / 8, (54 / 255, 144 / 255, 192 / 255)),
+    (6 / 8, (2 / 255, 129 / 255, 138 / 255)),
+    (7 / 8, (1 / 255, 108 / 255, 89 / 255)),
+    (8 / 8, (1 / 255, 70 / 255, 54 / 255)),
+)
+
+purd = Colormap(
+    (0 / 8, (247 / 255, 244 / 255, 249 / 255)),
+    (1 / 8, (231 / 255, 225 / 255, 239 / 255)),
+    (2 / 8, (212 / 255, 185 / 255, 218 / 255)),
+    (3 / 8, (201 / 255, 148 / 255, 199 / 255)),
+    (4 / 8, (223 / 255, 101 / 255, 176 / 255)),
+    (5 / 8, (231 / 255, 41 / 255, 138 / 255)),
+    (6 / 8, (206 / 255, 18 / 255, 86 / 255)),
+    (7 / 8, (152 / 255, 0 / 255, 67 / 255)),
+    (8 / 8, (103 / 255, 0 / 255, 31 / 255)),
+)
+
+purples = Colormap(
+    (0 / 8, (252 / 255, 251 / 255, 253 / 255)),
+    (1 / 8, (239 / 255, 237 / 255, 245 / 255)),
+    (2 / 8, (218 / 255, 218 / 255, 235 / 255)),
+    (3 / 8, (188 / 255, 189 / 255, 220 / 255)),
+    (4 / 8, (158 / 255, 154 / 255, 200 / 255)),
+    (5 / 8, (128 / 255, 125 / 255, 186 / 255)),
+    (6 / 8, (106 / 255, 81 / 255, 163 / 255)),
+    (7 / 8, (84 / 255, 39 / 255, 143 / 255)),
+    (8 / 8, (63 / 255, 0 / 255, 125 / 255)),
+)
+
+rdpu = Colormap(
+    (0 / 8, (255 / 255, 247 / 255, 243 / 255)),
+    (1 / 8, (253 / 255, 224 / 255, 221 / 255)),
+    (2 / 8, (252 / 255, 197 / 255, 192 / 255)),
+    (3 / 8, (250 / 255, 159 / 255, 181 / 255)),
+    (4 / 8, (247 / 255, 104 / 255, 161 / 255)),
+    (5 / 8, (221 / 255, 52 / 255, 151 / 255)),
+    (6 / 8, (174 / 255, 1 / 255, 126 / 255)),
+    (7 / 8, (122 / 255, 1 / 255, 119 / 255)),
+    (8 / 8, (73 / 255, 0 / 255, 106 / 255)),
+)
+
+reds = Colormap(
+    (0 / 8, (255 / 255, 245 / 255, 240 / 255)),
+    (1 / 8, (254 / 255, 224 / 255, 210 / 255)),
+    (2 / 8, (252 / 255, 187 / 255, 161 / 255)),
+    (3 / 8, (252 / 255, 146 / 255, 114 / 255)),
+    (4 / 8, (251 / 255, 106 / 255, 74 / 255)),
+    (5 / 8, (239 / 255, 59 / 255, 44 / 255)),
+    (6 / 8, (203 / 255, 24 / 255, 29 / 255)),
+    (7 / 8, (165 / 255, 15 / 255, 21 / 255)),
+    (8 / 8, (103 / 255, 0 / 255, 13 / 255)),
+)
+
+ylgn = Colormap(
+    (0 / 8, (255 / 255, 255 / 255, 229 / 255)),
+    (1 / 8, (247 / 255, 252 / 255, 185 / 255)),
+    (2 / 8, (217 / 255, 240 / 255, 163 / 255)),
+    (3 / 8, (173 / 255, 221 / 255, 142 / 255)),
+    (4 / 8, (120 / 255, 198 / 255, 121 / 255)),
+    (5 / 8, (65 / 255, 171 / 255, 93 / 255)),
+    (6 / 8, (35 / 255, 132 / 255, 67 / 255)),
+    (7 / 8, (0 / 255, 104 / 255, 55 / 255)),
+    (8 / 8, (0 / 255, 69 / 255, 41 / 255)),
+)
+
+ylgnbu = Colormap(
+    (0 / 8, (255 / 255, 255 / 255, 217 / 255)),
+    (1 / 8, (237 / 255, 248 / 255, 177 / 255)),
+    (2 / 8, (199 / 255, 233 / 255, 180 / 255)),
+    (3 / 8, (127 / 255, 205 / 255, 187 / 255)),
+    (4 / 8, (65 / 255, 182 / 255, 196 / 255)),
+    (5 / 8, (29 / 255, 145 / 255, 192 / 255)),
+    (6 / 8, (34 / 255, 94 / 255, 168 / 255)),
+    (7 / 8, (37 / 255, 52 / 255, 148 / 255)),
+    (8 / 8, (8 / 255, 29 / 255, 88 / 255)),
+)
+
+ylorbr = Colormap(
+    (0 / 8, (255 / 255, 255 / 255, 229 / 255)),
+    (1 / 8, (255 / 255, 247 / 255, 188 / 255)),
+    (2 / 8, (254 / 255, 227 / 255, 145 / 255)),
+    (3 / 8, (254 / 255, 196 / 255, 79 / 255)),
+    (4 / 8, (254 / 255, 153 / 255, 41 / 255)),
+    (5 / 8, (236 / 255, 112 / 255, 20 / 255)),
+    (6 / 8, (204 / 255, 76 / 255, 2 / 255)),
+    (7 / 8, (153 / 255, 52 / 255, 4 / 255)),
+    (8 / 8, (102 / 255, 37 / 255, 6 / 255)),
+)
+
+ylorrd = Colormap(
+    (0 / 7, (255 / 255, 255 / 255, 204 / 255)),
+    (1 / 7, (255 / 255, 237 / 255, 160 / 255)),
+    (2 / 7, (254 / 255, 217 / 255, 118 / 255)),
+    (3 / 7, (254 / 255, 178 / 255, 76 / 255)),
+    (4 / 7, (253 / 255, 141 / 255, 60 / 255)),
+    (5 / 7, (252 / 255, 78 / 255, 42 / 255)),
+    (6 / 7, (227 / 255, 26 / 255, 28 / 255)),
+    (7 / 7, (177 / 255, 0 / 255, 38 / 255)),
+)
+
+sequential_colormaps = {
+    "blues": blues,
+    "bugn": bugn,
+    "bupu": bupu,
+    "gnbu": gnbu,
+    "greens": greens,
+    "greys": greys,
+    "oranges": oranges,
+    "orrd": orrd,
+    "pubu": pubu,
+    "pubugn": pubugn,
+    "purd": purd,
+    "purples": purples,
+    "rdpu": rdpu,
+    "reds": reds,
+    "ylgn": ylgn,
+    "ylgnbu": ylgnbu,
+    "ylorbr": ylorbr,
+    "ylorrd": ylorrd,
+}
+
+# * Diverging Colormaps *
+
+brbg = Colormap(
+    (0 / 10, (84 / 255, 48 / 255, 5 / 255)),
+    (1 / 10, (140 / 255, 81 / 255, 10 / 255)),
+    (2 / 10, (191 / 255, 129 / 255, 45 / 255)),
+    (3 / 10, (223 / 255, 194 / 255, 125 / 255)),
+    (4 / 10, (246 / 255, 232 / 255, 195 / 255)),
+    (5 / 10, (245 / 255, 245 / 255, 245 / 255)),
+    (6 / 10, (199 / 255, 234 / 255, 229 / 255)),
+    (7 / 10, (128 / 255, 205 / 255, 193 / 255)),
+    (8 / 10, (53 / 255, 151 / 255, 143 / 255)),
+    (9 / 10, (1 / 255, 102 / 255, 94 / 255)),
+    (10 / 10, (0 / 255, 60 / 255, 48 / 255)),
+)
+
+piyg = Colormap(
+    (0 / 10, (142 / 255, 1 / 255, 82 / 255)),
+    (1 / 10, (197 / 255, 27 / 255, 125 / 255)),
+    (2 / 10, (222 / 255, 119 / 255, 174 / 255)),
+    (3 / 10, (241 / 255, 182 / 255, 218 / 255)),
+    (4 / 10, (253 / 255, 224 / 255, 239 / 255)),
+    (5 / 10, (247 / 255, 247 / 255, 247 / 255)),
+    (6 / 10, (230 / 255, 245 / 255, 208 / 255)),
+    (7 / 10, (184 / 255, 225 / 255, 134 / 255)),
+    (8 / 10, (127 / 255, 188 / 255, 65 / 255)),
+    (9 / 10, (77 / 255, 146 / 255, 33 / 255)),
+    (10 / 10, (39 / 255, 100 / 255, 25 / 255)),
+)
+
+prgn = Colormap(
+    (0 / 10, (64 / 255, 0 / 255, 75 / 255)),
+    (1 / 10, (118 / 255, 42 / 255, 131 / 255)),
+    (2 / 10, (153 / 255, 112 / 255, 171 / 255)),
+    (3 / 10, (194 / 255, 165 / 255, 207 / 255)),
+    (4 / 10, (231 / 255, 212 / 255, 232 / 255)),
+    (5 / 10, (247 / 255, 247 / 255, 247 / 255)),
+    (6 / 10, (217 / 255, 240 / 255, 211 / 255)),
+    (7 / 10, (166 / 255, 219 / 255, 160 / 255)),
+    (8 / 10, (90 / 255, 174 / 255, 97 / 255)),
+    (9 / 10, (27 / 255, 120 / 255, 55 / 255)),
+    (10 / 10, (0 / 255, 68 / 255, 27 / 255)),
+)
+
+puor = Colormap(
+    (0 / 10, (127 / 255, 59 / 255, 8 / 255)),
+    (1 / 10, (179 / 255, 88 / 255, 6 / 255)),
+    (2 / 10, (224 / 255, 130 / 255, 20 / 255)),
+    (3 / 10, (253 / 255, 184 / 255, 99 / 255)),
+    (4 / 10, (254 / 255, 224 / 255, 182 / 255)),
+    (5 / 10, (247 / 255, 247 / 255, 247 / 255)),
+    (6 / 10, (216 / 255, 218 / 255, 235 / 255)),
+    (7 / 10, (178 / 255, 171 / 255, 210 / 255)),
+    (8 / 10, (128 / 255, 115 / 255, 172 / 255)),
+    (9 / 10, (84 / 255, 39 / 255, 136 / 255)),
+    (10 / 10, (45 / 255, 0 / 255, 75 / 255)),
+)
+
+rdbu = Colormap(
+    (0 / 10, (103 / 255, 0 / 255, 31 / 255)),
+    (1 / 10, (178 / 255, 24 / 255, 43 / 255)),
+    (2 / 10, (214 / 255, 96 / 255, 77 / 255)),
+    (3 / 10, (244 / 255, 165 / 255, 130 / 255)),
+    (4 / 10, (253 / 255, 219 / 255, 199 / 255)),
+    (5 / 10, (247 / 255, 247 / 255, 247 / 255)),
+    (6 / 10, (209 / 255, 229 / 255, 240 / 255)),
+    (7 / 10, (146 / 255, 197 / 255, 222 / 255)),
+    (8 / 10, (67 / 255, 147 / 255, 195 / 255)),
+    (9 / 10, (33 / 255, 102 / 255, 172 / 255)),
+    (10 / 10, (5 / 255, 48 / 255, 97 / 255)),
+)
+
+rdgy = Colormap(
+    (0 / 10, (103 / 255, 0 / 255, 31 / 255)),
+    (1 / 10, (178 / 255, 24 / 255, 43 / 255)),
+    (2 / 10, (214 / 255, 96 / 255, 77 / 255)),
+    (3 / 10, (244 / 255, 165 / 255, 130 / 255)),
+    (4 / 10, (253 / 255, 219 / 255, 199 / 255)),
+    (5 / 10, (255 / 255, 255 / 255, 255 / 255)),
+    (6 / 10, (224 / 255, 224 / 255, 224 / 255)),
+    (7 / 10, (186 / 255, 186 / 255, 186 / 255)),
+    (8 / 10, (135 / 255, 135 / 255, 135 / 255)),
+    (9 / 10, (77 / 255, 77 / 255, 77 / 255)),
+    (10 / 10, (26 / 255, 26 / 255, 26 / 255)),
+)
+
+rdylbu = Colormap(
+    (0 / 10, (165 / 255, 0 / 255, 38 / 255)),
+    (1 / 10, (215 / 255, 48 / 255, 39 / 255)),
+    (2 / 10, (244 / 255, 109 / 255, 67 / 255)),
+    (3 / 10, (253 / 255, 174 / 255, 97 / 255)),
+    (4 / 10, (254 / 255, 224 / 255, 144 / 255)),
+    (5 / 10, (255 / 255, 255 / 255, 191 / 255)),
+    (6 / 10, (224 / 255, 243 / 255, 248 / 255)),
+    (7 / 10, (171 / 255, 217 / 255, 233 / 255)),
+    (8 / 10, (116 / 255, 173 / 255, 209 / 255)),
+    (9 / 10, (69 / 255, 117 / 255, 180 / 255)),
+    (10 / 10, (49 / 255, 54 / 255, 149 / 255)),
+)
+
+rdylgn = Colormap(
+    (0 / 10, (165 / 255, 0 / 255, 38 / 255)),
+    (1 / 10, (215 / 255, 48 / 255, 39 / 255)),
+    (2 / 10, (244 / 255, 109 / 255, 67 / 255)),
+    (3 / 10, (253 / 255, 174 / 255, 97 / 255)),
+    (4 / 10, (254 / 255, 224 / 255, 139 / 255)),
+    (5 / 10, (255 / 255, 255 / 255, 191 / 255)),
+    (6 / 10, (217 / 255, 239 / 255, 139 / 255)),
+    (7 / 10, (166 / 255, 217 / 255, 106 / 255)),
+    (8 / 10, (102 / 255, 189 / 255, 99 / 255)),
+    (9 / 10, (26 / 255, 152 / 255, 80 / 255)),
+    (10 / 10, (0 / 255, 104 / 255, 55 / 255)),
+)
+
+spectral = Colormap(
+    (0 / 10, (158 / 255, 1 / 255, 66 / 255)),
+    (1 / 10, (213 / 255, 62 / 255, 79 / 255)),
+    (2 / 10, (244 / 255, 109 / 255, 67 / 255)),
+    (3 / 10, (253 / 255, 174 / 255, 97 / 255)),
+    (4 / 10, (254 / 255, 224 / 255, 139 / 255)),
+    (5 / 10, (255 / 255, 255 / 255, 191 / 255)),
+    (6 / 10, (230 / 255, 245 / 255, 152 / 255)),
+    (7 / 10, (171 / 255, 221 / 255, 164 / 255)),
+    (8 / 10, (102 / 255, 194 / 255, 165 / 255)),
+    (9 / 10, (50 / 255, 136 / 255, 189 / 255)),
+    (10 / 10, (94 / 255, 79 / 255, 162 / 255)),
+)
+
+diverging_colormaps = {
+    "brbg": brbg,
+    "piyg": piyg,
+    "prgn": prgn,
+    "puor": puor,
+    "rdbu": rdbu,
+    "rdgy": rdgy,
+    "rdylbu": rdylbu,
+    "rdylgn": rdylgn,
+    "spectral": spectral,
+}
+
+# * Qualitative Colormaps *
+
+accent = Colormap(
+    (0, (127 / 255, 201 / 255, 127 / 255)),
+    (1, (190 / 255, 174 / 255, 212 / 255)),
+    (2, (253 / 255, 192 / 255, 134 / 255)),
+    (3, (255 / 255, 255 / 255, 153 / 255)),
+    (4, (56 / 255, 108 / 255, 176 / 255)),
+    (5, (240 / 255, 2 / 255, 127 / 255)),
+    (6, (191 / 255, 91 / 255, 23 / 255)),
+    (7, (102 / 255, 102 / 255, 102 / 255)),
+)
+
+dark2 = Colormap(
+    (0, (27 / 255, 158 / 255, 119 / 255)),
+    (1, (217 / 255, 95 / 255, 2 / 255)),
+    (2, (117 / 255, 112 / 255, 179 / 255)),
+    (3, (231 / 255, 41 / 255, 138 / 255)),
+    (4, (102 / 255, 166 / 255, 30 / 255)),
+    (5, (230 / 255, 171 / 255, 2 / 255)),
+    (6, (166 / 255, 118 / 255, 29 / 255)),
+    (7, (102 / 255, 102 / 255, 102 / 255)),
+)
+
+paired = Colormap(
+    (0, (166 / 255, 206 / 255, 227 / 255)),
+    (1, (31 / 255, 120 / 255, 180 / 255)),
+    (2, (178 / 255, 223 / 255, 138 / 255)),
+    (3, (51 / 255, 160 / 255, 44 / 255)),
+    (4, (251 / 255, 154 / 255, 153 / 255)),
+    (5, (227 / 255, 26 / 255, 28 / 255)),
+    (6, (253 / 255, 191 / 255, 111 / 255)),
+    (7, (255 / 255, 127 / 255, 0 / 255)),
+    (8, (202 / 255, 178 / 255, 214 / 255)),
+    (9, (106 / 255, 61 / 255, 154 / 255)),
+    (10, (255 / 255, 255 / 255, 153 / 255)),
+    (11, (177 / 255, 89 / 255, 40 / 255)),
+)
+
+pastel1 = Colormap(
+    (0, (251 / 255, 180 / 255, 174 / 255)),
+    (1, (179 / 255, 205 / 255, 227 / 255)),
+    (2, (204 / 255, 235 / 255, 197 / 255)),
+    (3, (222 / 255, 203 / 255, 228 / 255)),
+    (4, (254 / 255, 217 / 255, 166 / 255)),
+    (5, (255 / 255, 255 / 255, 204 / 255)),
+    (6, (229 / 255, 216 / 255, 189 / 255)),
+    (7, (253 / 255, 218 / 255, 236 / 255)),
+    (8, (242 / 255, 242 / 255, 242 / 255)),
+)
+
+pastel2 = Colormap(
+    (0, (179 / 255, 226 / 255, 205 / 255)),
+    (1, (253 / 255, 205 / 255, 172 / 255)),
+    (2, (203 / 255, 213 / 255, 232 / 255)),
+    (3, (244 / 255, 202 / 255, 228 / 255)),
+    (4, (230 / 255, 245 / 255, 201 / 255)),
+    (5, (255 / 255, 242 / 255, 174 / 255)),
+    (6, (241 / 255, 226 / 255, 204 / 255)),
+    (7, (204 / 255, 204 / 255, 204 / 255)),
+)
+
+set1 = Colormap(
+    (0, (228 / 255, 26 / 255, 28 / 255)),
+    (1, (55 / 255, 126 / 255, 184 / 255)),
+    (2, (77 / 255, 175 / 255, 74 / 255)),
+    (3, (152 / 255, 78 / 255, 163 / 255)),
+    (4, (255 / 255, 127 / 255, 0 / 255)),
+    (5, (255 / 255, 255 / 255, 51 / 255)),
+    (6, (166 / 255, 86 / 255, 40 / 255)),
+    (7, (247 / 255, 129 / 255, 191 / 255)),
+    (8, (153 / 255, 153 / 255, 153 / 255)),
+)
+
+set2 = Colormap(
+    (0, (102 / 255, 194 / 255, 165 / 255)),
+    (1, (252 / 255, 141 / 255, 98 / 255)),
+    (2, (141 / 255, 160 / 255, 203 / 255)),
+    (3, (231 / 255, 138 / 255, 195 / 255)),
+    (4, (166 / 255, 216 / 255, 84 / 255)),
+    (5, (255 / 255, 217 / 255, 47 / 255)),
+    (6, (229 / 255, 196 / 255, 148 / 255)),
+    (7, (179 / 255, 179 / 255, 179 / 255)),
+)
+
+set3 = Colormap(
+    (0, (141 / 255, 211 / 255, 199 / 255)),
+    (1, (255 / 255, 255 / 255, 179 / 255)),
+    (2, (190 / 255, 186 / 255, 218 / 255)),
+    (3, (251 / 255, 128 / 255, 114 / 255)),
+    (4, (128 / 255, 177 / 255, 211 / 255)),
+    (5, (253 / 255, 180 / 255, 98 / 255)),
+    (6, (179 / 255, 222 / 255, 105 / 255)),
+    (7, (252 / 255, 205 / 255, 229 / 255)),
+    (8, (217 / 255, 217 / 255, 217 / 255)),
+    (9, (188 / 255, 128 / 255, 189 / 255)),
+    (10, (204 / 255, 235 / 255, 197 / 255)),
+    (11, (255 / 255, 237 / 255, 111 / 255)),
+)
+
+qualitative_colormaps = {
+    "accent": accent,
+    "dark2": dark2,
+    "paired": paired,
+    "pastel1": pastel1,
+    "pastel2": pastel2,
+    "set1": set1,
+    "set2": set2,
+    "set3": set3,
+}
 
 
 def colorbar(height, length, colormap, category=False):


=====================================
trollimage/tests/test_image.py
=====================================
@@ -1440,23 +1440,55 @@ class TestXRImage:
             with rio.open(tmp.name) as f:
                 assert f.tags() == tags
 
-    def test_gamma(self):
-        """Test gamma correction."""
-        arr = np.arange(75).reshape(5, 5, 3) / 75.
-        data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_gamma_single_value(self, dtype):
+        """Test gamma correction for one value for all channels."""
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 75.
+        data = xr.DataArray(arr, dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
         img.gamma(.5)
-        assert np.allclose(img.data.values, arr ** 2)
+        assert img.data.dtype == dtype
+        np.testing.assert_allclose(img.data.values, arr ** 2)
         assert img.data.attrs['enhancement_history'][0] == {'gamma': 0.5}
 
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    @pytest.mark.parametrize(
+        ("gamma_val"),
+        [
+            (None),
+            (1.0),
+            ([1.0, 1.0, 1.0]),
+            ([None, None, None]),
+        ]
+    )
+    def test_gamma_noop(self, gamma_val, dtype):
+        """Test variety of unity gamma corrections."""
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 75.
+        data = xr.DataArray(arr, dims=['y', 'x', 'bands'],
+                            coords={'bands': ['R', 'G', 'B']})
+        img = xrimage.XRImage(data)
+        img.gamma(gamma_val)
+        assert img.data.dtype == dtype
+        np.testing.assert_equal(img.data.values, arr)
+        assert 'enhancement_history' not in img.data.attrs
+
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_gamma_per_channel(self, dtype):
+        """Test gamma correction with a value for each channel."""
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 75.
+        data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
+                            coords={'bands': ['R', 'G', 'B']})
+        img = xrimage.XRImage(data)
         img.gamma([2., 2., 2.])
-        assert len(img.data.attrs['enhancement_history']) == 2
-        assert np.allclose(img.data.values, arr)
+        assert img.data.dtype == dtype
+        assert img.data.attrs['enhancement_history'][0] == {'gamma': [2.0, 2.0, 2.0]}
+        np.testing.assert_allclose(img.data.values, arr ** 0.5)
 
-    def test_crude_stretch(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_crude_stretch(self, dtype):
         """Check crude stretching."""
-        arr = np.arange(75).reshape(5, 5, 3)
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3)
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
@@ -1467,22 +1499,39 @@ class TestXRImage:
         enhs = img.data.attrs['enhancement_history'][0]
         scale_expected = np.array([0.01388889, 0.01388889, 0.01388889])
         offset_expected = np.array([0., -0.01388889, -0.02777778])
+        assert img.data.dtype == dtype
         np.testing.assert_allclose(enhs['scale'].values, scale_expected)
         np.testing.assert_allclose(enhs['offset'].values, offset_expected)
-        np.testing.assert_allclose(red, arr[:, :, 0] / 72.)
-        np.testing.assert_allclose(green, (arr[:, :, 1] - 1.) / (73. - 1.))
-        np.testing.assert_allclose(blue, (arr[:, :, 2] - 2.) / (74. - 2.))
+        expected_red = arr[:, :, 0] / 72.
+        np.testing.assert_allclose(red, expected_red.astype(dtype), rtol=1e-6)
+        expected_green = (arr[:, :, 1] - 1.) / (73. - 1.)
+        np.testing.assert_allclose(green, expected_green.astype(dtype), rtol=1e-6)
+        expected_blue = (arr[:, :, 2] - 2.) / (74. - 2.)
+        np.testing.assert_allclose(blue, expected_blue.astype(dtype), rtol=1e-6)
+
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_crude_stretch_with_limits(self, dtype):
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3)
+        data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
+                            coords={'bands': ['R', 'G', 'B']})
+        img = xrimage.XRImage(data)
+        img.crude_stretch(0, 74)
+        assert img.data.dtype == dtype
+        np.testing.assert_allclose(img.data.values, arr / 74., rtol=1e-6)
 
-        arr = np.arange(75).reshape(5, 5, 3).astype(float)
+    def test_crude_stretch_integer_data(self):
+        arr = np.arange(75, dtype=int).reshape(5, 5, 3)
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
         img.crude_stretch(0, 74)
-        np.testing.assert_allclose(img.data.values, arr / 74.)
+        assert img.data.dtype == np.float32
+        np.testing.assert_allclose(img.data.values, arr.astype(np.float32) / 74., rtol=1e-6)
 
-    def test_invert(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_invert(self, dtype):
         """Check inversion of the image."""
-        arr = np.arange(75).reshape(5, 5, 3) / 75.
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 75.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
@@ -1490,6 +1539,7 @@ class TestXRImage:
         img.invert(True)
         enhs = img.data.attrs['enhancement_history'][0]
         assert enhs == {'scale': -1, 'offset': 1}
+        assert img.data.dtype == dtype
         assert np.allclose(img.data.values, 1 - arr)
 
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
@@ -1503,13 +1553,15 @@ class TestXRImage:
                              coords={'bands': ['R', 'G', 'B']})
         np.testing.assert_allclose(img.data.values, (data * scale + offset).values)
 
-    def test_linear_stretch(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_linear_stretch(self, dtype):
         """Test linear stretching with cutoffs."""
-        arr = np.arange(75).reshape(5, 5, 3) / 74.
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
         img.stretch_linear()
+        assert img.data.dtype == dtype
         enhs = img.data.attrs['enhancement_history'][0]
         np.testing.assert_allclose(enhs['scale'].values, np.array([1.03815937, 1.03815937, 1.03815937]))
         np.testing.assert_allclose(enhs['offset'].values, np.array([-0.00505051, -0.01907969, -0.03310887]), atol=1e-8)
@@ -1537,19 +1589,59 @@ class TestXRImage:
                          [0.878788, 0.878788, 0.878788],
                          [0.920875, 0.920875, 0.920875],
                          [0.962963, 0.962963, 0.962963],
-                         [1.005051, 1.005051, 1.005051]]])
+                         [1.005051, 1.005051, 1.005051]]], dtype=dtype)
+
+        np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
+
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_linear_stretch_does_not_affect_alpha(self, dtype):
+        """Test linear stretching with cutoffs."""
+        arr = np.arange(100, dtype=dtype).reshape(5, 5, 4) / 74.
+        arr[:, :, -1] = 1  # alpha channel, fully opaque
+        data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
+                            coords={'bands': ['R', 'G', 'B', 'A']})
+        img = xrimage.XRImage(data)
+        img.stretch_linear((0.005, 0.005))
+        assert img.data.dtype == dtype
+        res = np.array([[[-0.005051, -0.005051, -0.005051, 1.],
+                         [0.037037, 0.037037, 0.037037, 1.],
+                         [0.079125, 0.079125, 0.079125, 1.],
+                         [0.121212, 0.121212, 0.121212, 1.],
+                         [0.1633, 0.1633, 0.1633, 1.]],
+                        [[0.205387, 0.205387, 0.205387, 1.],
+                         [0.247475, 0.247475, 0.247475, 1.],
+                         [0.289562, 0.289562, 0.289562, 1.],
+                         [0.33165, 0.33165, 0.33165, 1.],
+                         [0.373737, 0.373737, 0.373737, 1.]],
+                        [[0.415825, 0.415825, 0.415825, 1.],
+                         [0.457912, 0.457912, 0.457912, 1.],
+                         [0.5, 0.5, 0.5, 1.],
+                         [0.542088, 0.542088, 0.542088, 1.],
+                         [0.584175, 0.584175, 0.584175, 1.]],
+                        [[0.626263, 0.626263, 0.626263, 1.],
+                         [0.66835, 0.66835, 0.66835, 1.],
+                         [0.710438, 0.710438, 0.710438, 1.],
+                         [0.752525, 0.752525, 0.752525, 1.],
+                         [0.794613, 0.794613, 0.794613, 1.]],
+                        [[0.8367, 0.8367, 0.8367, 1.],
+                         [0.878788, 0.878788, 0.878788, 1.],
+                         [0.920875, 0.920875, 0.920875, 1.],
+                         [0.962963, 0.962963, 0.962963, 1.],
+                         [1.005051, 1.005051, 1.005051, 1.]]], dtype=dtype)
 
         np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
 
-    def test_histogram_stretch(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_histogram_stretch(self, dtype):
         """Test histogram stretching."""
-        arr = np.arange(75).reshape(5, 5, 3) / 74.
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
         img.stretch('histogram')
         enhs = img.data.attrs['enhancement_history'][0]
         assert enhs == {'hist_equalize': True}
+        assert img.data.dtype == dtype
         res = np.array([[[0., 0., 0.],
                          [0.04166667, 0.04166667, 0.04166667],
                          [0.08333333, 0.08333333, 0.08333333],
@@ -1578,10 +1670,11 @@ class TestXRImage:
                          [0.875, 0.875, 0.875],
                          [0.91666667, 0.91666667, 0.91666667],
                          [0.95833333, 0.95833333, 0.95833333],
-                         [0.99951172, 0.99951172, 0.99951172]]])
+                         [0.99951172, 0.99951172, 0.99951172]]], dtype=dtype)
 
         np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
 
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
     @pytest.mark.parametrize(
         ("min_stretch", "max_stretch"),
         [
@@ -1590,9 +1683,9 @@ class TestXRImage:
         ]
     )
     @pytest.mark.parametrize("base", ["e", "10", "2"])
-    def test_logarithmic_stretch(self, min_stretch, max_stretch, base):
+    def test_logarithmic_stretch(self, min_stretch, max_stretch, base, dtype):
         """Test logarithmic strecthing."""
-        arr = np.arange(75).reshape(5, 5, 3) / 74.
+        arr = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         with assert_maximum_dask_computes(0):
@@ -1603,6 +1696,7 @@ class TestXRImage:
                         base=base)
         enhs = img.data.attrs['enhancement_history'][0]
         assert enhs == {'log_factor': 100.0}
+        assert img.data.dtype == dtype
         res = np.array([[[0., 0., 0.],
                          [0.35484693, 0.35484693, 0.35484693],
                          [0.48307087, 0.48307087, 0.48307087],
@@ -1631,21 +1725,23 @@ class TestXRImage:
                          [0.97131402, 0.97131402, 0.97131402],
                          [0.98130304, 0.98130304, 0.98130304],
                          [0.99085269, 0.99085269, 0.99085269],
-                         [1., 1., 1.]]])
+                         [1., 1., 1.]]], dtype=dtype)
 
         np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
 
-    def test_weber_fechner_stretch(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_weber_fechner_stretch(self, dtype):
         """Test applying S=2.3klog10I+C to the data."""
         from trollimage import xrimage
 
-        arr = np.arange(75).reshape(5, 5, 3) / 74.
+        arr = np.arange(75., dtype=dtype).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
         img = xrimage.XRImage(data)
         img.stretch_weber_fechner(2.5, 0.2)
         enhs = img.data.attrs['enhancement_history'][0]
         assert enhs == {'weber_fechner': (2.5, 0.2)}
+        assert img.data.dtype == dtype
         res = np.array([[[-np.inf, -6.73656795, -5.0037],
                          [-3.99003723, -3.27083205, -2.71297317],
                          [-2.25716928, -1.87179258, -1.5379641],
@@ -1674,7 +1770,7 @@ class TestXRImage:
                          [3.62126886, 3.66063976, 3.69940022],
                          [3.7375689, 3.7751636, 3.81220131],
                          [3.84869831, 3.88467015, 3.92013174],
-                         [3.95509735, 3.98958065, 4.02359478]]])
+                         [3.95509735, 3.98958065, 4.02359478]]], dtype=dtype)
 
         np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
 
@@ -1861,19 +1957,19 @@ class TestXRImage:
         from trollimage import xrimage
 
         # background image
-        arr1 = np.zeros((2, 2))
+        arr1 = np.zeros((2, 2), dtype=np.float32)
         data1 = xr.DataArray(arr1, dims=['y', 'x'])
         bkg = xrimage.XRImage(data1)
 
         # image to be stacked
-        arr2 = np.full((2, 2), np.nan)
+        arr2 = np.full((2, 2), np.nan, dtype=np.float32)
         arr2[0] = 1
         data2 = xr.DataArray(arr2, dims=['y', 'x'])
         img = xrimage.XRImage(data2)
 
         # expected result
         arr3 = arr1.copy()
-        arr3[0] = 1
+        arr3[0] = 1.0
         data3 = xr.DataArray(arr3, dims=['y', 'x'])
         res = xrimage.XRImage(data3)
 
@@ -1887,26 +1983,27 @@ class TestXRImage:
         """Test merge."""
         pass
 
-    def test_blend(self):
+    @pytest.mark.parametrize("dtype", (np.float32, np.float64, float))
+    def test_blend(self, dtype):
         """Test blend."""
         from trollimage import xrimage
 
-        core1 = np.arange(75).reshape(5, 5, 3) / 75.0
-        alpha1 = np.linspace(0, 1, 25).reshape(5, 5, 1)
+        core1 = np.arange(75, dtype=dtype).reshape(5, 5, 3) / 75.0
+        alpha1 = np.linspace(0, 1, 25, dtype=dtype).reshape(5, 5, 1)
         arr1 = np.concatenate([core1, alpha1], 2)
         data1 = xr.DataArray(arr1, dims=['y', 'x', 'bands'],
                              coords={'bands': ['R', 'G', 'B', 'A']})
         img1 = xrimage.XRImage(data1)
 
-        core2 = np.arange(75, 0, -1).reshape(5, 5, 3) / 75.0
-        alpha2 = np.linspace(1, 0, 25).reshape(5, 5, 1)
+        core2 = np.arange(75, 0, -1, dtype=dtype).reshape(5, 5, 3) / 75.0
+        alpha2 = np.linspace(1, 0, 25, dtype=dtype).reshape(5, 5, 1)
         arr2 = np.concatenate([core2, alpha2], 2)
         data2 = xr.DataArray(arr2, dims=['y', 'x', 'bands'],
                              coords={'bands': ['R', 'G', 'B', 'A']})
         img2 = xrimage.XRImage(data2)
-
         img3 = img1.blend(img2)
 
+        assert img3.data.dtype == dtype
         np.testing.assert_allclose(
                 (alpha1 + alpha2 * (1 - alpha1)).squeeze(),
                 img3.data.sel(bands="A"))
@@ -1918,7 +2015,8 @@ class TestXRImage:
                  [0.768815,     0.72,       0.6728228,  0.62857145, 0.5885714],
                  [0.55412847,   0.5264665,  0.50666666, 0.495612,   0.49394494],
                  [0.5020408,    0.52,       0.5476586,  0.5846154,  0.63027024],
-                 [0.683871,     0.7445614,  0.81142855, 0.8835443,  0.96]]))
+                 [0.683871,     0.7445614,  0.81142855, 0.8835443,  0.96]], dtype=dtype),
+                 rtol=2e-6)
 
         with pytest.raises(TypeError):
             img1.blend("Salekhard")
@@ -2014,7 +2112,7 @@ class TestImageColorize:
         img = image.Image(channels=[arr.copy(), alpha], mode="LA")
         img.colorize(brbg)
 
-        expected = list(TestXRImageColorize._expected)
+        expected = list(TestXRImageColorize._expected[np.float64])
         np.testing.assert_allclose(img.channels[0], expected[0])
         np.testing.assert_allclose(img.channels[1], expected[1])
         np.testing.assert_allclose(img.channels[2], expected[2])
@@ -2024,67 +2122,114 @@ class TestImageColorize:
 class TestXRImageColorize:
     """Test the colorize method of the XRImage class."""
 
-    _expected = np.array([[
-        [3.29411723e-01, 3.57655082e-01, 3.86434110e-01, 4.15693606e-01,
-         4.45354600e-01, 4.75400861e-01, 5.05821366e-01, 5.36605929e-01,
-         5.65154978e-01, 5.92088497e-01, 6.19067971e-01, 6.46087246e-01,
-         6.73140324e-01, 7.00221360e-01, 7.27324645e-01],
-        [7.52329770e-01, 7.68885184e-01, 7.85480717e-01, 8.02165033e-01,
-         8.18991652e-01, 8.36019205e-01, 8.53311577e-01, 8.70937939e-01,
-         8.84215464e-01, 8.96340860e-01, 9.08470028e-01, 9.20615990e-01,
-         9.32792728e-01, 9.45015153e-01, 9.57299069e-01],
-        [9.57098327e-01, 9.40960114e-01, 9.29584947e-01, 9.23677290e-01,
-         9.23753761e-01, 9.30068208e-01, 9.42551542e-01, 9.60784273e-01,
-         9.41109213e-01, 9.19647970e-01, 8.96571901e-01, 8.72056790e-01,
-         8.46282229e-01, 8.19431622e-01, 7.91692975e-01],
-        [7.58448192e-01, 7.21741664e-01, 6.84822731e-01, 6.47626513e-01,
-         6.10070647e-01, 5.72048960e-01, 5.33421992e-01, 4.94570855e-01,
-         4.57464094e-01, 4.20002632e-01, 3.82018455e-01, 3.43266518e-01,
-         3.03372572e-01, 2.61727458e-01, 2.17242854e-01],
-        [1.89905753e-01, 1.67063022e-01, 1.43524406e-01, 1.18889110e-01,
-         9.24115112e-02, 6.24348956e-02, 2.53761173e-02, 4.08181032e-03,
-         4.27986478e-03, 4.17929690e-03, 3.78662146e-03, 3.12692318e-03,
-         2.24023940e-03, 1.17807264e-03, 3.21825881e-08]],
-       [[1.88235327e-01, 2.05148705e-01, 2.22246533e-01, 2.39526068e-01,
-         2.56989487e-01, 2.74629823e-01, 2.92439994e-01, 3.10413422e-01,
-         3.32343814e-01, 3.57065419e-01, 3.82068278e-01, 4.07348961e-01,
-         4.32903760e-01, 4.58728817e-01, 4.84820203e-01],
-        [5.12920806e-01, 5.47946930e-01, 5.82732545e-01, 6.17314757e-01,
-         6.51719365e-01, 6.85963748e-01, 7.20058900e-01, 7.54010901e-01,
-         7.76938576e-01, 7.97119666e-01, 8.17286145e-01, 8.37436050e-01,
-         8.57567245e-01, 8.77677444e-01, 8.97764221e-01],
-        [9.14974807e-01, 9.26634684e-01, 9.36490370e-01, 9.44572058e-01,
-         9.50936890e-01, 9.55671974e-01, 9.58899694e-01, 9.60784377e-01,
-         9.54533524e-01, 9.48563492e-01, 9.42789228e-01, 9.37126769e-01,
-         9.31494725e-01, 9.25815456e-01, 9.20015949e-01],
-        [9.08501736e-01, 8.93232137e-01, 8.77927046e-01, 8.62584949e-01,
-         8.47204357e-01, 8.31783811e-01, 8.16321878e-01, 7.98071154e-01,
-         7.68921238e-01, 7.39943765e-01, 7.11141597e-01, 6.82517728e-01,
-         6.54075286e-01, 6.25817541e-01, 5.97747906e-01],
-        [5.70776450e-01, 5.44247780e-01, 5.17943011e-01, 4.91867999e-01,
-         4.66028940e-01, 4.40432405e-01, 4.15085375e-01, 3.90762603e-01,
-         3.67819656e-01, 3.45100714e-01, 3.22617398e-01, 3.00381887e-01,
-         2.78406993e-01, 2.56706267e-01, 2.35294109e-01]],
-       [[1.96078164e-02, 2.42548791e-02, 2.74972980e-02, 2.96227786e-02,
-         3.17156285e-02, 3.38568546e-02, 3.60498743e-02, 3.82990372e-02,
-         5.17340107e-02, 7.13424499e-02, 9.00791380e-02, 1.08349520e-01,
-         1.26372958e-01, 1.44280386e-01, 1.62155431e-01],
-        [1.84723724e-01, 2.25766583e-01, 2.66872651e-01, 3.08395883e-01,
-         3.50522786e-01, 3.93349758e-01, 4.36919863e-01, 4.81242202e-01,
-         5.19495725e-01, 5.56210021e-01, 5.93054317e-01, 6.30051817e-01,
-         6.67218407e-01, 7.04564489e-01, 7.42096284e-01],
-        [7.75703258e-01, 8.04590533e-01, 8.34519408e-01, 8.64314044e-01,
-         8.92841230e-01, 9.19042335e-01, 9.41963593e-01, 9.60784303e-01,
-         9.45735072e-01, 9.32649658e-01, 9.21608884e-01, 9.12665692e-01,
-         9.05844317e-01, 9.01139812e-01, 8.98517976e-01],
-        [8.86846758e-01, 8.68087757e-01, 8.49200397e-01, 8.30188000e-01,
-         8.11054008e-01, 7.91801982e-01, 7.72435606e-01, 7.51368872e-01,
-         7.24059052e-01, 6.97016433e-01, 6.70243011e-01, 6.43740898e-01,
-         6.17512331e-01, 5.91559687e-01, 5.65885492e-01],
-        [5.39262087e-01, 5.12603461e-01, 4.86221750e-01, 4.60123396e-01,
-         4.34315297e-01, 4.08804859e-01, 3.83600057e-01, 3.58016749e-01,
-         3.31909003e-01, 3.06406088e-01, 2.81515756e-01, 2.57245695e-01,
-         2.33603632e-01, 2.10597439e-01, 1.88235281e-01]]])
+    _expected = {
+        np.float64: np.array([[
+            [3.29411723e-01, 3.57655082e-01, 3.86434110e-01, 4.15693606e-01,
+             4.45354600e-01, 4.75400861e-01, 5.05821366e-01, 5.36605929e-01,
+             5.65154978e-01, 5.92088497e-01, 6.19067971e-01, 6.46087246e-01,
+             6.73140324e-01, 7.00221360e-01, 7.27324645e-01],
+            [7.52329770e-01, 7.68885184e-01, 7.85480717e-01, 8.02165033e-01,
+             8.18991652e-01, 8.36019205e-01, 8.53311577e-01, 8.70937939e-01,
+             8.84215464e-01, 8.96340860e-01, 9.08470028e-01, 9.20615990e-01,
+             9.32792728e-01, 9.45015153e-01, 9.57299069e-01],
+            [9.57098327e-01, 9.40960114e-01, 9.29584947e-01, 9.23677290e-01,
+             9.23753761e-01, 9.30068208e-01, 9.42551542e-01, 9.60784273e-01,
+             9.41109213e-01, 9.19647970e-01, 8.96571901e-01, 8.72056790e-01,
+             8.46282229e-01, 8.19431622e-01, 7.91692975e-01],
+            [7.58448192e-01, 7.21741664e-01, 6.84822731e-01, 6.47626513e-01,
+             6.10070647e-01, 5.72048960e-01, 5.33421992e-01, 4.94570855e-01,
+             4.57464094e-01, 4.20002632e-01, 3.82018455e-01, 3.43266518e-01,
+             3.03372572e-01, 2.61727458e-01, 2.17242854e-01],
+            [1.89905753e-01, 1.67063022e-01, 1.43524406e-01, 1.18889110e-01,
+             9.24115112e-02, 6.24348956e-02, 2.53761173e-02, 4.08181032e-03,
+             4.27986478e-03, 4.17929690e-03, 3.78662146e-03, 3.12692318e-03,
+             2.24023940e-03, 1.17807264e-03, 3.21825881e-08]],
+           [[1.88235327e-01, 2.05148705e-01, 2.22246533e-01, 2.39526068e-01,
+             2.56989487e-01, 2.74629823e-01, 2.92439994e-01, 3.10413422e-01,
+             3.32343814e-01, 3.57065419e-01, 3.82068278e-01, 4.07348961e-01,
+             4.32903760e-01, 4.58728817e-01, 4.84820203e-01],
+            [5.12920806e-01, 5.47946930e-01, 5.82732545e-01, 6.17314757e-01,
+             6.51719365e-01, 6.85963748e-01, 7.20058900e-01, 7.54010901e-01,
+             7.76938576e-01, 7.97119666e-01, 8.17286145e-01, 8.37436050e-01,
+             8.57567245e-01, 8.77677444e-01, 8.97764221e-01],
+            [9.14974807e-01, 9.26634684e-01, 9.36490370e-01, 9.44572058e-01,
+             9.50936890e-01, 9.55671974e-01, 9.58899694e-01, 9.60784377e-01,
+             9.54533524e-01, 9.48563492e-01, 9.42789228e-01, 9.37126769e-01,
+             9.31494725e-01, 9.25815456e-01, 9.20015949e-01],
+            [9.08501736e-01, 8.93232137e-01, 8.77927046e-01, 8.62584949e-01,
+             8.47204357e-01, 8.31783811e-01, 8.16321878e-01, 7.98071154e-01,
+             7.68921238e-01, 7.39943765e-01, 7.11141597e-01, 6.82517728e-01,
+             6.54075286e-01, 6.25817541e-01, 5.97747906e-01],
+            [5.70776450e-01, 5.44247780e-01, 5.17943011e-01, 4.91867999e-01,
+             4.66028940e-01, 4.40432405e-01, 4.15085375e-01, 3.90762603e-01,
+             3.67819656e-01, 3.45100714e-01, 3.22617398e-01, 3.00381887e-01,
+             2.78406993e-01, 2.56706267e-01, 2.35294109e-01]],
+           [[1.96078164e-02, 2.42548791e-02, 2.74972980e-02, 2.96227786e-02,
+             3.17156285e-02, 3.38568546e-02, 3.60498743e-02, 3.82990372e-02,
+             5.17340107e-02, 7.13424499e-02, 9.00791380e-02, 1.08349520e-01,
+             1.26372958e-01, 1.44280386e-01, 1.62155431e-01],
+            [1.84723724e-01, 2.25766583e-01, 2.66872651e-01, 3.08395883e-01,
+             3.50522786e-01, 3.93349758e-01, 4.36919863e-01, 4.81242202e-01,
+             5.19495725e-01, 5.56210021e-01, 5.93054317e-01, 6.30051817e-01,
+             6.67218407e-01, 7.04564489e-01, 7.42096284e-01],
+            [7.75703258e-01, 8.04590533e-01, 8.34519408e-01, 8.64314044e-01,
+             8.92841230e-01, 9.19042335e-01, 9.41963593e-01, 9.60784303e-01,
+             9.45735072e-01, 9.32649658e-01, 9.21608884e-01, 9.12665692e-01,
+             9.05844317e-01, 9.01139812e-01, 8.98517976e-01],
+            [8.86846758e-01, 8.68087757e-01, 8.49200397e-01, 8.30188000e-01,
+             8.11054008e-01, 7.91801982e-01, 7.72435606e-01, 7.51368872e-01,
+             7.24059052e-01, 6.97016433e-01, 6.70243011e-01, 6.43740898e-01,
+             6.17512331e-01, 5.91559687e-01, 5.65885492e-01],
+            [5.39262087e-01, 5.12603461e-01, 4.86221750e-01, 4.60123396e-01,
+             4.34315297e-01, 4.08804859e-01, 3.83600057e-01, 3.58016749e-01,
+             3.31909003e-01, 3.06406088e-01, 2.81515756e-01, 2.57245695e-01,
+             2.33603632e-01, 2.10597439e-01, 1.88235281e-01]]], dtype=np.float64),
+        np.float32: np.array([[
+            [0.32941175, 0.35765505, 0.38643414, 0.4156936 , 0.44535455,
+             0.47540084, 0.5058213 , 0.53660583, 0.56515497, 0.5920884 ,
+             0.61906797, 0.64608735, 0.67314035, 0.7002214 , 0.72732455],
+            [0.7523298 , 0.76888514, 0.7854807 , 0.8021649 , 0.8189918 ,
+             0.8360192 , 0.8533116 , 0.8709379 , 0.88421535, 0.8963408 ,
+             0.90846986, 0.9206159 , 0.9327928 , 0.9450152 , 0.95729893],
+            [0.9561593 , 0.9379867 , 0.925193  , 0.9186452 , 0.9189398 ,
+             0.9262958 , 0.9404795 , 0.96078414, 0.9400202 , 0.9179358 ,
+             0.89463943, 0.8702369 , 0.84483355, 0.8185319 , 0.79143286],
+            [0.75844824, 0.7217417 , 0.6848227 , 0.64762676, 0.61007077,
+             0.57204896, 0.533422  , 0.49457097, 0.4574643 , 0.42000294,
+             0.38201877, 0.34326664, 0.30337277, 0.26172766, 0.21724297],
+            [0.18990554, 0.1670632 , 0.14352395, 0.11888929, 0.09241185,
+             0.06243531, 0.02537645, 0.00408208, 0.0042801 , 0.00417955,
+             0.00378686, 0.00312716, 0.00224016, 0.00117794, 0.        ]],
+           [[0.18823533, 0.20514871, 0.22224654, 0.23952611, 0.25698954,
+             0.27462983, 0.2924401 , 0.31041354, 0.33234388, 0.35706556,
+             0.38206834, 0.40734893, 0.4329038 , 0.45872888, 0.4848204 ],
+            [0.51292086, 0.54794705, 0.5827325 , 0.6173149 , 0.65171933,
+             0.6859637 , 0.720059  , 0.754011  , 0.7769386 , 0.7971198 ,
+             0.81728625, 0.8374362 , 0.8575672 , 0.87767744, 0.8977643 ],
+            [0.9152645 , 0.92748123, 0.93765223, 0.9458176 , 0.9520587 ,
+             0.95650315, 0.9593312 , 0.96078444, 0.9547297 , 0.9488435 ,
+             0.9430687 , 0.9373506 , 0.93163645, 0.9258765 , 0.92002386],
+            [0.9085018 , 0.89323217, 0.8779272 , 0.86258495, 0.84720427,
+             0.83178383, 0.81632185, 0.79807115, 0.7689212 , 0.73994386,
+             0.71114165, 0.68251765, 0.65407526, 0.62581754, 0.597748  ],
+            [0.5707766 , 0.5442478 , 0.51794314, 0.49186793, 0.46602884,
+             0.4404323 , 0.4150853 , 0.39076254, 0.3678196 , 0.34510067,
+             0.32261738, 0.3003818 , 0.27840695, 0.25670624, 0.23529409]],
+           [[0.01960781, 0.02425484, 0.02749731, 0.02962274, 0.03171561,
+             0.03385685, 0.03604981, 0.03829896, 0.05173399, 0.07134236,
+             0.0900792 , 0.10834952, 0.12637301, 0.14428039, 0.16215537],
+            [0.18472369, 0.22576652, 0.26687256, 0.30839586, 0.35052276,
+             0.39334968, 0.43691984, 0.48124215, 0.51949567, 0.55621   ,
+             0.59305423, 0.6300518 , 0.6672183 , 0.7045645 , 0.7420964 ],
+            [0.7757837 , 0.80522877, 0.83597785, 0.8665506 , 0.8955321 ,
+             0.9216227 , 0.9436848 , 0.96078426, 0.94695187, 0.93475634,
+             0.9242341 , 0.9154071 , 0.9082816 , 0.90284896, 0.8990856 ],
+            [0.8868469 , 0.8680878 , 0.8492005 , 0.83018804, 0.8110541 ,
+             0.791802  , 0.7724357 , 0.7513689 , 0.7240591 , 0.69701654,
+             0.67024314, 0.64374095, 0.6175124 , 0.59155977, 0.5658856 ],
+            [0.53926224, 0.5126035 , 0.48622182, 0.4601233 , 0.43431523,
+             0.40880483, 0.3836    , 0.35801673, 0.331909  , 0.30640608,
+             0.28151578, 0.25724563, 0.23360366, 0.21059746, 0.18823537]]], dtype=np.float32)}
 
     @pytest.mark.parametrize("colormap_tag", [None, "colormap"])
     def test_colorize_geotiff_tag(self, tmp_path, colormap_tag):
@@ -2109,35 +2254,47 @@ class TestXRImageColorize:
                 np.testing.assert_allclose(new_brbg.colors, loaded_brbg.colors)
 
     @pytest.mark.parametrize(
-        ("new_range", "input_scale", "input_offset", "expected_scale", "expected_offset"),
+        ("new_range", "input_scale", "input_offset", "expected_scale", "expected_offset", "dtype"),
         [
-            ((0.0, 1.0), 1.0, 0.0, 1.0, 0.0),
-            ((0.0, 0.5), 1.0, 0.0, 2.0, 0.0),
-            ((2.0, 4.0), 2.0, 2.0, 0.5, -1.0),
+            ((0.0, 1.0), 1.0, 0.0, 1.0, 0.0, np.float32),
+            ((0.0, 0.5), 1.0, 0.0, 2.0, 0.0, np.float32),
+            ((2.0, 4.0), 2.0, 2.0, 0.5, -1.0, np.float32),
+            ((0.0, 1.0), 1.0, 0.0, 1.0, 0.0, np.float64),
+            ((0.0, 0.5), 1.0, 0.0, 2.0, 0.0, np.float64),
+            ((2.0, 4.0), 2.0, 2.0, 0.5, -1.0, np.float64),
         ],
     )
-    def test_colorize_l_rgb(self, new_range, input_scale, input_offset, expected_scale, expected_offset):
-        """Test colorize with an RGB colormap."""
-        arr = np.arange(75).reshape(5, 15) / 74. * input_scale + input_offset
-        data = xr.DataArray(arr.copy(), dims=['y', 'x'])
+    def test_colorize_l_rgb(self, new_range, input_scale, input_offset, expected_scale, expected_offset, dtype):
+        """Test colorize with a RGB colormap."""
+        img = self._get_input_image(dtype, input_scale, input_offset)
         new_brbg = brbg.set_range(*new_range, inplace=False)
-        img = xrimage.XRImage(data)
         img.colorize(new_brbg)
         values = img.data.compute()
+        assert values.dtype == dtype
 
+        expected = self._get_expected_colorize_l_rgb(new_range, dtype)
+        np.testing.assert_allclose(values, expected, atol=1e-6)
+        assert "enhancement_history" in img.data.attrs
+        assert img.data.attrs["enhancement_history"][-1]["scale"] == expected_scale
+        assert img.data.attrs["enhancement_history"][-1]["offset"] == expected_offset
+        assert isinstance(img.data.attrs["enhancement_history"][-1]["colormap"], Colormap)
+
+    @staticmethod
+    def _get_input_image(dtype, input_scale, input_offset):
+        arr = np.arange(75, dtype=dtype).reshape(5, 15) / 74. * input_scale + input_offset
+        data = xr.DataArray(arr, dims=['y', 'x'])
+        return xrimage.XRImage(data)
+
+    def _get_expected_colorize_l_rgb(self, new_range, dtype):
         if new_range[1] == 0.5:
-            expected2 = self._expected.copy().reshape((3, 75))
-            flat_expected = self._expected.reshape((3, 75))
+            expected2 = self._expected[dtype].copy().reshape((3, 75))
+            flat_expected = self._expected[dtype].reshape((3, 75))
             expected2[:, :38] = flat_expected[:, ::2]
             expected2[:, 38:] = flat_expected[:, -1:]
             expected = expected2.reshape((3, 5, 15))
         else:
-            expected = self._expected
-        np.testing.assert_allclose(values, expected)
-        assert "enhancement_history" in img.data.attrs
-        assert img.data.attrs["enhancement_history"][-1]["scale"] == expected_scale
-        assert img.data.attrs["enhancement_history"][-1]["offset"] == expected_offset
-        assert isinstance(img.data.attrs["enhancement_history"][-1]["colormap"], Colormap)
+            expected = self._expected[dtype]
+        return expected
 
     def test_colorize_int_l_rgb_with_fills(self):
         """Test integer data with _FillValue is masked (NaN) when colorized."""
@@ -2149,6 +2306,8 @@ class TestXRImageColorize:
         img = xrimage.XRImage(data)
         img.colorize(new_brbg)
         values = img.data.compute()
+        # Integer data inherits dtype from the colormap when colorized
+        assert values.dtype == new_brbg.colors.dtype
         assert values.shape == (3,) + arr.shape  # RGB
         np.testing.assert_allclose(values[:, 1, :], np.nan)
         assert np.count_nonzero(np.isnan(values)) == arr.shape[1] * 3
@@ -2169,7 +2328,7 @@ class TestXRImageColorize:
         img.colorize(brbg)
 
         values = img.data.values
-        expected = np.concatenate((self._expected,
+        expected = np.concatenate((self._expected[np.float64],
                                    alpha.reshape((1,) + alpha.shape)))
         np.testing.assert_allclose(values, expected)
         assert "enhancement_history" in img.data.attrs
@@ -2234,6 +2393,7 @@ class TestXRImagePalettize:
             expected2[:, :38] = flat_expected[:, ::2]
             expected2[:, 38:] = flat_expected[:, -1:]
             expected = expected2.reshape((1, 5, 15))
+        assert np.issubdtype(values.dtype, np.integer)
         np.testing.assert_allclose(values, expected)
         assert "enhancement_history" in img.data.attrs
         assert img.data.attrs["enhancement_history"][-1]["scale"] == expected_scale
@@ -2289,10 +2449,10 @@ class TestXRImageSaveScaleOffset:
     def setup_method(self) -> None:
         """Set up the test case."""
         from trollimage import xrimage
-        data = xr.DataArray(np.arange(25).reshape(5, 5, 1), dims=[
+        data = xr.DataArray(np.arange(25, dtype=np.float32).reshape(5, 5, 1), dims=[
             'y', 'x', 'bands'], coords={'bands': ['L']})
         self.img = xrimage.XRImage(data)
-        rgb_data = xr.DataArray(np.arange(3 * 25).reshape(5, 5, 3), dims=[
+        rgb_data = xr.DataArray(np.arange(3 * 25, dtype=np.float32).reshape(5, 5, 3), dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
         self.rgb_img = xrimage.XRImage(rgb_data)
 


=====================================
trollimage/version.py
=====================================
@@ -26,9 +26,9 @@ def get_keywords():
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (tag: v1.21.0)"
-    git_full = "57116d66bcbffdecb4b19555ca0b858d80a222d6"
-    git_date = "2023-09-04 20:03:38 -0500"
+    git_refnames = " (HEAD -> main, tag: v1.22.0)"
+    git_full = "dd16cdfbf40d8a245454ab5675166a1e9282b20c"
+    git_date = "2023-11-23 08:59:37 +0100"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
trollimage/xrimage.py
=====================================
@@ -344,7 +344,7 @@ class XRImage:
             warnings.warn(
                 "include_scale_offset_tags is deprecated, please use "
                 "scale_offset_tags to indicate tag labels",
-                DeprecationWarning)
+                DeprecationWarning, stacklevel=2)
             scale_offset_tags = scale_offset_tags or ("scale", "offset")
 
         if tags is None:
@@ -659,7 +659,7 @@ class XRImage:
                     "Specified fill value will overlap with valid "
                     "data. To avoid this warning specify a fill_value "
                     "that is the minimum or maximum for the data type "
-                    "being saved to.")
+                    "being saved to.", stacklevel=3)
         return scale, offset
 
     def _scale_to_dtype(self, data, dtype, fill_value=None):
@@ -940,18 +940,28 @@ class XRImage:
         the normal [0,1] range of the channels.
 
         """
-        if isinstance(gamma, (list, tuple)):
-            gamma = self.xrify_tuples(gamma)
-        elif gamma is None or gamma == 1.0:
+        if _is_unity_or_none(gamma):
             return
 
+        inverse_gamma = self._get_inverse_gamma(gamma)
         logger.debug("Applying gamma %s", str(gamma))
         attrs = self.data.attrs
         self.data = self.data.clip(min=0)
-        self.data **= 1.0 / gamma
+        self.data **= inverse_gamma
         self.data.attrs = attrs
         self.data.attrs.setdefault('enhancement_history', []).append({'gamma': gamma})
 
+    def _get_inverse_gamma(self, gamma):
+        if np.issubdtype(self.data.dtype, np.floating):
+            dtype = self.data.dtype
+        else:
+            dtype = np.float32
+        if isinstance(gamma, (list, tuple)):
+            gamma = self.xrify_tuples(gamma).astype(dtype)
+        else:
+            gamma = np.array(gamma, dtype=dtype)
+        return 1.0 / gamma
+
     def stretch(self, stretch="crude", **kwargs):
         """Apply stretching to the current image.
 
@@ -1019,29 +1029,45 @@ class XRImage:
         """
         logger.debug("Perform a linear contrast stretch.")
 
+        left, right = self._get_left_and_right_quantiles_for_linear_stretch(cutoffs)
+
+        self.crude_stretch(left, right)
+
+    def _get_left_and_right_quantiles_for_linear_stretch(self, cutoffs):
         logger.debug("Calculate the histogram quantiles: ")
         logger.debug("Left and right quantiles: " +
                      str(cutoffs[0]) + " " + str(cutoffs[1]))
-
         cutoff_type = np.float64
         # numpy percentile (which quantile calls) returns 64-bit floats
         # unless the value is a higher order float
         if np.issubdtype(self.data.dtype, np.floating) and \
                 np.dtype(self.data.dtype).itemsize > 8:
             cutoff_type = self.data.dtype
-        left, right = dask.delayed(self._compute_quantile, nout=2)(self.data.data, self.data.dims, cutoffs)
-        left_data = da.from_delayed(left,
-                                    shape=(self.data.sizes['bands'],),
-                                    dtype=cutoff_type)
+
+        data = self.data
+        if 'A' in self.data.coords['bands'].values:
+            data = self.data.sel(bands=self.data.coords['bands'].values[:-1])
+
+        left_data, right_data = self._get_left_and_right_quantiles_without_alpha(data, cutoffs, cutoff_type)
+
+        if 'A' in self.data.coords['bands'].values:
+            left_data = np.hstack([left_data, np.array([0])])
+            right_data = np.hstack([right_data, np.array([1])])
         left = xr.DataArray(left_data, dims=('bands',),
                             coords={'bands': self.data['bands']})
-        right_data = da.from_delayed(right,
-                                     shape=(self.data.sizes['bands'],),
-                                     dtype=cutoff_type)
         right = xr.DataArray(right_data, dims=('bands',),
                              coords={'bands': self.data['bands']})
+        return left, right
 
-        self.crude_stretch(left, right)
+    def _get_left_and_right_quantiles_without_alpha(self, data, cutoffs, cutoff_type):
+        left, right = dask.delayed(self._compute_quantile, nout=2)(data.data, data.dims, cutoffs)
+        left_data = da.from_delayed(left,
+                                    shape=(data.sizes['bands'],),
+                                    dtype=cutoff_type)
+        right_data = da.from_delayed(right,
+                                     shape=(data.sizes['bands'],),
+                                     dtype=cutoff_type)
+        return left_data, right_data
 
     def crude_stretch(self, min_stretch=None, max_stretch=None):
         """Perform simple linear stretching.
@@ -1050,31 +1076,47 @@ class XRImage:
         normalizes to the [0,1] range.
 
         """
-        if min_stretch is None:
-            non_band_dims = tuple(x for x in self.data.dims if x != 'bands')
-            min_stretch = self.data.min(dim=non_band_dims)
-        if max_stretch is None:
+        min_stretch = self._check_stretch_value(min_stretch, kind='min')
+        max_stretch = self._check_stretch_value(max_stretch, kind='max')
+        scale_factor = self._get_scale_factor(min_stretch, max_stretch)
+
+        attrs = self.data.attrs
+        offset = -min_stretch * scale_factor
+        self.data = np.multiply(self.data, scale_factor, dtype=scale_factor.dtype) + offset
+        self.data.attrs = attrs
+        self.data.attrs.setdefault('enhancement_history', []).append({'scale': scale_factor,
+                                                                      'offset': offset})
+
+    def _check_stretch_value(self, val, kind='min'):
+        if val is None:
             non_band_dims = tuple(x for x in self.data.dims if x != 'bands')
-            max_stretch = self.data.max(dim=non_band_dims)
+            val = getattr(self.data, kind)(dim=non_band_dims)
 
-        if isinstance(min_stretch, (list, tuple)):
-            min_stretch = self.xrify_tuples(min_stretch)
-        if isinstance(max_stretch, (list, tuple)):
-            max_stretch = self.xrify_tuples(max_stretch)
+        if isinstance(val, (list, tuple)):
+            val = self.xrify_tuples(val)
+
+        try:
+            val = val.astype(self.data.dtype)
+        except AttributeError:
+            val = self.data.dtype.type(val)
 
+        return val
+
+    def _get_scale_factor(self, min_stretch, max_stretch):
         delta = (max_stretch - min_stretch)
+        dtype = self._infer_scale_factor_dtype()
         if isinstance(delta, xr.DataArray):
             # fillna if delta is NaN
-            scale_factor = (1.0 / delta).fillna(0)
+            scale_factor = (1.0 / delta).fillna(0).astype(dtype)
         else:
-            scale_factor = 1.0 / delta
-        attrs = self.data.attrs
-        offset = -min_stretch * scale_factor
-        self.data *= scale_factor
-        self.data += offset
-        self.data.attrs = attrs
-        self.data.attrs.setdefault('enhancement_history', []).append({'scale': scale_factor,
-                                                                      'offset': offset})
+            scale_factor = np.array(1.0 / delta, dtype=dtype)
+
+        return scale_factor
+
+    def _infer_scale_factor_dtype(self):
+        if np.issubdtype(self.data.dtype, np.integer):
+            return np.float32
+        return self.data.dtype
 
     def stretch_hist_equalize(self, approximate=False):
         """Stretch the current image's colors through histogram equalization.
@@ -1284,6 +1326,7 @@ class XRImage:
         if self.mode not in ("L", "LA"):
             raise ValueError("Image should be grayscale to colorize")
 
+        colormap = self._adjust_colormap_dtype(colormap)
         l_data = self._get_masked_floating_luminance_data()
         alpha = self.data.sel(bands=['A']) if self.mode == "LA" else None
         new_data = colormap.colorize(l_data.data)
@@ -1309,6 +1352,12 @@ class XRImage:
             'colormap': colormap,
         })
 
+    def _adjust_colormap_dtype(self, colormap):
+        if np.issubdtype(self.data.dtype, np.floating) and colormap.colors.dtype != self.data.dtype:
+            colormap.colors = colormap.colors.astype(self.data.dtype)
+            colormap.values = colormap.values.astype(self.data.dtype)
+        return colormap
+
     def _get_masked_floating_luminance_data(self):
         l_data = self.data.sel(bands='L')
         # mask any integer fields with _FillValue
@@ -1468,3 +1517,11 @@ class XRImage:
         b = io.BytesIO()
         self.pil_image().save(b, format='png')
         return b.getvalue()
+
+
+def _is_unity_or_none(gamma):
+    if gamma is None or gamma == 1.0:
+        return True
+    if not hasattr(gamma, "__iter__"):
+        return False
+    return all(g == 1.0 for g in gamma) or all(g is None for g in gamma)



View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/a0c2b213874783926666ec2a612b3bf512a9aa6e...233e21d4ab458a7d7e388c023f5cca28367f74b0

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/a0c2b213874783926666ec2a612b3bf512a9aa6e...233e21d4ab458a7d7e388c023f5cca28367f74b0
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/20231124/f844dfae/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list