<div align="center">
<img src="https://raw.githubusercontent.com/leonard-seydoux/pycpt-city/main/logo/logo.gif" alt="pycpt logo" width="100"/><p style="line-height: 1.1;"><br><strong>PyCPT</strong><br>Color Palette Tables from <a href="http://seaviewsensing.com/pub/cpt-city/" target="_blank">cpt-city</a><br>in your Python environment.<br>Made in 2025 by Léonard Seydoux</p><br clear="all"/>
</div>
## What is cpt-city?
CPT is short for Color Palette Table, a file format popularized by the [Generic Mapping Tools](https://www.generic-mapping-tools.org/) (GMT) for defining colormaps as piecewise-constant color bands between numeric boundaries.
The [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website maintained by J. J. Green is a community-curated archive of color palettes collected from many projects (e.g., GMT, cmocean, Matplotlib, and more). Palettes are organized in family folders and typically include metadata files like `DESC.xml` and `COPYING.xml` that describe provenance and licensing.
## License and Usage
This package is shipped with a `cpt-city/` directory that contains the entire archive obtained from the website. The archive can be rebuilt with the `pycpt.update_bundle()` method. Be mindful that individual palettes may carry different licenses-refer to the accompanying `COPYING.xml` files. This archive only contains palettes for which redistribution is permitted. Learn more at on the [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website.
## Installation
The simplest way to install PyCPT is via pip:
pip install pycpt
## Quickstart
This package parses common CPT formats (including GMT-style lines) and exposes a simple `Palette` API that you can read from a CPT file with the name of a palette from the bundled `cpt-city/` folder, or a path to any CPT file. The reader supports flexible path resolution: you can pass either an absolute/relative file path, or a short name underneath a bundled `cpt-city/` data folder (extensionless is fine, `.cpt` is added automatically).
Once loaded, the `Palette` object provides several useful attributes and methods, such as:
- `palette.cmap` to be used with Matplotlib plotting functions
- `palette.norm` to preserve original CPT boundaries
And many helpers to inspect, scale and interpolate the palette, or plot colorbars and previews. The following sections illustrate some of these features.
```python
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
import numpy as np
import pycpt
```
### Reading a CPT file
The method `pycpt.read` accepts either a short name relative to the bundled cpt-city archive (e.g., "cmocean/algae", "cl/fs2010", or simply "algae" when unique), or a file path on disk. The file extension (.cpt) is optional and added automatically.
You can also set the logical palette type with `kind` ("sequential" or "diverging"). If divering is selected, the later scaling is centered around the diverging point (default 0).
```python
palette = pycpt.read("wiki-2.0", kind="diverging", diverging_point=0)
palette.plot()
```

You can load palettes from many families (e.g., `cmocean`, `xkcd`, `gmt`, `wkp`, …). Later in this notebook, we’ll list an entire family with `pycpt.files.get_family(...)` and preview each palette quickly.
Below we switch to another palette and preview its bands.
```python
palette = pycpt.read("cmocean/algae") # also work with "algae"
palette.plot()
```

## Using the colormap in Matplotlib
There are two common ways to apply a palette:
- Using only `cmap` lets Matplotlib rescale colors to your data range (smooth but may shift intended boundaries).
- Using `cmap` together with `palette.norm` preserves the original CPT boundaries (discrete bands at the authored values).
In the next code cell, the three panels show:
1) Left: `cmap` only (colors are rescaled to the data range).
2) Middle: `cmap + norm` (colors follow original boundaries).
3) Right: Same palette after `palette.scale(vmin, vmax)` and `palette.interpolate(n=...)`, then used with `cmap + norm` and a matching colorbar via `palette.colorbar(...)`.
Tip: For diverging data centered at a value, read with `kind="diverging"` and pass the center to `palette.scale(vmin, vmax, at=center)` so left/right segments preserve their balance.
```python
# Create data
x = y = np.linspace(-1, 1, 200)
x, y = np.meshgrid(x, y)
z = 4000 * (x + np.sin(y) + 0.5) + 1000
sea_level = 0
# Get colormap and norm from palette
cpt = pycpt.read("colombia", kind="diverging")
# Create figure
fig, ax = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True)
ax = ax.flatten()
# Without norm
vals = ax[0].pcolormesh(x, y, z, cmap=cpt.cmap, rasterized=True)
fig.colorbar(vals, ax=ax[0], label="$z$ values", pad=0.1)
ax[0].clabel(ax[0].contour(x, y, z, levels=[sea_level]), [sea_level])
# With norm
vals = ax[1].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)
fig.colorbar(vals, ax=ax[1], label="$z$ values", pad=0.1, norm=cpt.norm)
ax[1].clabel(ax[1].contour(x, y, z, levels=[sea_level]), [sea_level])
# With norm
cpt.scale(-5000, 10000)
ax[2].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)
cpt.colorbar(ax=ax[2], label="z values", pad=0.1)
ax[2].clabel(ax[2].contour(x, y, z, levels=[sea_level]), [sea_level])
# Interpolated norm
cpt.interpolate(257)
ax[3].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)
cpt.colorbar(ax=ax[3], label="z values", pad=0.1)
ax[3].clabel(ax[3].contour(x, y, z, levels=[sea_level]), [sea_level])
# Labels
ax[0].set(title="Basic", ylabel="$y$ axis")
ax[1].set(title="Original norm")
ax[2].set(title="Rescaled norm", xlabel="$x$ axis", ylabel="$y$ axis")
ax[3].set(title="Interpolated norm", xlabel="$x$ axis")
fig.tight_layout()
plt.show()
```

## Example with an actual digital elevation model
```python
import cartopy.crs as ccrs
from matplotlib.colors import LightSource
from pygmrt.tiles import download_tiles
from scipy.ndimage import gaussian_filter
# La Réunion Island topography and bathymetry
tiles = download_tiles(bbox=[55.05, -21.5, 55.95, -20.7], resolution="low")
topo = tiles.read(1)
bbox = tiles.bounds
extent = (bbox.left, bbox.right, bbox.bottom, bbox.top)
# Palette
palette = pycpt.read("wiki-france", kind="diverging")
palette.scale(-4000, 3000)
palette.interpolate(257)
# Create figure
fig = plt.figure(figsize=(7, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
# Hillshade
sun = LightSource(azdeg=45, altdeg=50)
shaded = sun.shade(
topo,
cmap=palette.cmap,
norm=palette.norm,
vert_exag=0.02,
blend_mode="soft",
)
# Show
ax.imshow(shaded, extent=extent, transform=ccrs.PlateCarree())
# Extra map features
gridlines = ax.gridlines(draw_labels=True, color="white", alpha=0.3)
gridlines.top_labels = False
gridlines.right_labels = False
palette.colorbar(ax=ax, label="Elevation (m)", pad=0.1, shrink=0.5)
ax.set_title("La Réunion Island with illumination")
plt.show()
```
Text(0.5, 1.0, 'La Réunion Island with illumination')

## Listing and previewing a palette family
`pycpt.files.get_family(name)` returns all CPT files under a given family. This is handy to browse a collection and quickly preview each palette’s discrete bands.
Below, we grid a few palettes from the `wkp` family and call `palette.plot` on each. Unused axes are hidden for clarity.
```python
# List all GMT palettes
files = pycpt.get_family("gmt")
# Plot all palettes in a grid
n_cols = 2
n_rows = int(np.ceil(len(files) / n_cols))
fig, axes = plt.subplots(
figsize=(7, n_rows / 1.1),
ncols=n_cols,
nrows=n_rows,
gridspec_kw={"wspace": 0.3, "hspace": 4},
)
axes = axes.ravel()
# Plot
for ax, filepath in zip(axes, files):
pycpt.read(filepath).plot(ax=ax)
# Clear unused axes
for j in range(len(files), len(axes)):
axes[j].axis("off")
plt.show()
```

## About the archive
The files from the [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website are bundled in the `cpt-city/` folder. You can also download the latest archive from [here](http://seaviewsensing.com/pub/cpt-city/pkg/cpt-city-cpt-2.27.zip) and extract it to replace the existing folder.
Alternatively, there is a helper function `pycpt.files.update_bundle()` that downloads and extracts the latest archive automatically.
```python
pycpt.update_bundle()
```
## Contribution
Contributions are welcome! Please refer to the `CONTRIBUTING.md` file for guidelines on how to contribute to this project.
This notebook was generated with the `nbconvert` tool. To regenerate it, run:
python build_readme.py
Raw data
{
"_id": null,
"home_page": null,
"name": "pycpt-city",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "colormap, palette, cpt, cpt-city, matplotlib, visualization",
"author": null,
"author_email": "L\u00e9onard Seydoux <seydoux@ipgp.fr>",
"download_url": "https://files.pythonhosted.org/packages/b0/2d/679c88c69138bae157bdcec0570c08bba3f74159d69d26fcdc9469bfd144/pycpt_city-0.1.5.tar.gz",
"platform": null,
"description": "<div align=\"center\">\n<img src=\"https://raw.githubusercontent.com/leonard-seydoux/pycpt-city/main/logo/logo.gif\" alt=\"pycpt logo\" width=\"100\"/><p style=\"line-height: 1.1;\"><br><strong>PyCPT</strong><br>Color Palette Tables from <a href=\"http://seaviewsensing.com/pub/cpt-city/\" target=\"_blank\">cpt-city</a><br>in your Python environment.<br>Made in 2025 by L\u00e9onard Seydoux</p><br clear=\"all\"/>\n</div>\n\n## What is cpt-city?\n\nCPT is short for Color Palette Table, a file format popularized by the [Generic Mapping Tools](https://www.generic-mapping-tools.org/) (GMT) for defining colormaps as piecewise-constant color bands between numeric boundaries.\n\nThe [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website maintained by J. J. Green is a community-curated archive of color palettes collected from many projects (e.g., GMT, cmocean, Matplotlib, and more). Palettes are organized in family folders and typically include metadata files like `DESC.xml` and `COPYING.xml` that describe provenance and licensing.\n\n## License and Usage\n\nThis package is shipped with a `cpt-city/` directory that contains the entire archive obtained from the website. The archive can be rebuilt with the `pycpt.update_bundle()` method. Be mindful that individual palettes may carry different licenses-refer to the accompanying `COPYING.xml` files. This archive only contains palettes for which redistribution is permitted. Learn more at on the [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website.\n\n## Installation\n\nThe simplest way to install PyCPT is via pip:\npip install pycpt\n## Quickstart\n\nThis package parses common CPT formats (including GMT-style lines) and exposes a simple `Palette` API that you can read from a CPT file with the name of a palette from the bundled `cpt-city/` folder, or a path to any CPT file. The reader supports flexible path resolution: you can pass either an absolute/relative file path, or a short name underneath a bundled `cpt-city/` data folder (extensionless is fine, `.cpt` is added automatically).\nOnce loaded, the `Palette` object provides several useful attributes and methods, such as:\n- `palette.cmap` to be used with Matplotlib plotting functions\n- `palette.norm` to preserve original CPT boundaries\n\nAnd many helpers to inspect, scale and interpolate the palette, or plot colorbars and previews. The following sections illustrate some of these features.\n\n\n```python\n%config InlineBackend.figure_format = 'svg'\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pycpt\n```\n\n### Reading a CPT file\n\nThe method `pycpt.read` accepts either a short name relative to the bundled cpt-city archive (e.g., \"cmocean/algae\", \"cl/fs2010\", or simply \"algae\" when unique), or a file path on disk. The file extension (.cpt) is optional and added automatically. \n\nYou can also set the logical palette type with `kind` (\"sequential\" or \"diverging\"). If divering is selected, the later scaling is centered around the diverging point (default 0).\n\n\n```python\npalette = pycpt.read(\"wiki-2.0\", kind=\"diverging\", diverging_point=0)\npalette.plot()\n```\n\n\n \n\n \n\n\nYou can load palettes from many families (e.g., `cmocean`, `xkcd`, `gmt`, `wkp`, \u2026). Later in this notebook, we\u2019ll list an entire family with `pycpt.files.get_family(...)` and preview each palette quickly.\n\nBelow we switch to another palette and preview its bands.\n\n\n```python\npalette = pycpt.read(\"cmocean/algae\") # also work with \"algae\"\npalette.plot()\n```\n\n\n \n\n \n\n\n## Using the colormap in Matplotlib\n\nThere are two common ways to apply a palette:\n- Using only `cmap` lets Matplotlib rescale colors to your data range (smooth but may shift intended boundaries).\n- Using `cmap` together with `palette.norm` preserves the original CPT boundaries (discrete bands at the authored values).\n\nIn the next code cell, the three panels show:\n1) Left: `cmap` only (colors are rescaled to the data range).\n2) Middle: `cmap + norm` (colors follow original boundaries).\n3) Right: Same palette after `palette.scale(vmin, vmax)` and `palette.interpolate(n=...)`, then used with `cmap + norm` and a matching colorbar via `palette.colorbar(...)`.\n\nTip: For diverging data centered at a value, read with `kind=\"diverging\"` and pass the center to `palette.scale(vmin, vmax, at=center)` so left/right segments preserve their balance.\n\n\n```python\n# Create data\nx = y = np.linspace(-1, 1, 200)\nx, y = np.meshgrid(x, y)\nz = 4000 * (x + np.sin(y) + 0.5) + 1000\nsea_level = 0\n\n# Get colormap and norm from palette\ncpt = pycpt.read(\"colombia\", kind=\"diverging\")\n\n# Create figure\nfig, ax = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True)\nax = ax.flatten()\n\n# Without norm\nvals = ax[0].pcolormesh(x, y, z, cmap=cpt.cmap, rasterized=True)\nfig.colorbar(vals, ax=ax[0], label=\"$z$ values\", pad=0.1)\nax[0].clabel(ax[0].contour(x, y, z, levels=[sea_level]), [sea_level])\n\n# With norm\nvals = ax[1].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)\nfig.colorbar(vals, ax=ax[1], label=\"$z$ values\", pad=0.1, norm=cpt.norm)\nax[1].clabel(ax[1].contour(x, y, z, levels=[sea_level]), [sea_level])\n\n# With norm\ncpt.scale(-5000, 10000)\nax[2].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)\ncpt.colorbar(ax=ax[2], label=\"z values\", pad=0.1)\nax[2].clabel(ax[2].contour(x, y, z, levels=[sea_level]), [sea_level])\n\n# Interpolated norm\ncpt.interpolate(257)\nax[3].pcolormesh(x, y, z, norm=cpt.norm, cmap=cpt.cmap, rasterized=True)\ncpt.colorbar(ax=ax[3], label=\"z values\", pad=0.1)\nax[3].clabel(ax[3].contour(x, y, z, levels=[sea_level]), [sea_level])\n\n# Labels\nax[0].set(title=\"Basic\", ylabel=\"$y$ axis\")\nax[1].set(title=\"Original norm\")\nax[2].set(title=\"Rescaled norm\", xlabel=\"$x$ axis\", ylabel=\"$y$ axis\")\nax[3].set(title=\"Interpolated norm\", xlabel=\"$x$ axis\")\n\nfig.tight_layout()\nplt.show()\n```\n\n\n \n\n \n\n\n## Example with an actual digital elevation model\n\n\n```python\nimport cartopy.crs as ccrs\n\nfrom matplotlib.colors import LightSource\nfrom pygmrt.tiles import download_tiles\nfrom scipy.ndimage import gaussian_filter\n\n# La R\u00e9union Island topography and bathymetry\ntiles = download_tiles(bbox=[55.05, -21.5, 55.95, -20.7], resolution=\"low\")\ntopo = tiles.read(1)\nbbox = tiles.bounds\nextent = (bbox.left, bbox.right, bbox.bottom, bbox.top)\n\n# Palette\npalette = pycpt.read(\"wiki-france\", kind=\"diverging\")\npalette.scale(-4000, 3000)\npalette.interpolate(257)\n\n# Create figure\nfig = plt.figure(figsize=(7, 7))\nax = plt.axes(projection=ccrs.PlateCarree())\n\n# Hillshade\nsun = LightSource(azdeg=45, altdeg=50)\nshaded = sun.shade(\n topo,\n cmap=palette.cmap,\n norm=palette.norm,\n vert_exag=0.02,\n blend_mode=\"soft\",\n)\n\n# Show\nax.imshow(shaded, extent=extent, transform=ccrs.PlateCarree())\n\n# Extra map features\ngridlines = ax.gridlines(draw_labels=True, color=\"white\", alpha=0.3)\ngridlines.top_labels = False\ngridlines.right_labels = False\npalette.colorbar(ax=ax, label=\"Elevation (m)\", pad=0.1, shrink=0.5)\nax.set_title(\"La R\u00e9union Island with illumination\")\n\nplt.show()\n```\n\n\n\n\n Text(0.5, 1.0, 'La R\u00e9union Island with illumination')\n\n\n\n\n \n\n \n\n\n## Listing and previewing a palette family\n\n`pycpt.files.get_family(name)` returns all CPT files under a given family. This is handy to browse a collection and quickly preview each palette\u2019s discrete bands.\n\nBelow, we grid a few palettes from the `wkp` family and call `palette.plot` on each. Unused axes are hidden for clarity.\n\n\n```python\n# List all GMT palettes\nfiles = pycpt.get_family(\"gmt\")\n\n# Plot all palettes in a grid\nn_cols = 2\nn_rows = int(np.ceil(len(files) / n_cols))\nfig, axes = plt.subplots(\n figsize=(7, n_rows / 1.1),\n ncols=n_cols,\n nrows=n_rows,\n gridspec_kw={\"wspace\": 0.3, \"hspace\": 4},\n)\naxes = axes.ravel()\n\n# Plot\nfor ax, filepath in zip(axes, files):\n pycpt.read(filepath).plot(ax=ax)\n\n# Clear unused axes\nfor j in range(len(files), len(axes)):\n axes[j].axis(\"off\")\n\nplt.show()\n```\n\n\n \n\n \n\n\n## About the archive\n\nThe files from the [cpt-city](http://seaviewsensing.com/pub/cpt-city/) website are bundled in the `cpt-city/` folder. You can also download the latest archive from [here](http://seaviewsensing.com/pub/cpt-city/pkg/cpt-city-cpt-2.27.zip) and extract it to replace the existing folder.\n\nAlternatively, there is a helper function `pycpt.files.update_bundle()` that downloads and extracts the latest archive automatically.\n\n\n```python\npycpt.update_bundle()\n```\n\n## Contribution\n\nContributions are welcome! Please refer to the `CONTRIBUTING.md` file for guidelines on how to contribute to this project.\n\nThis notebook was generated with the `nbconvert` tool. To regenerate it, run:\npython build_readme.py\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Read and use CPT (Color Palette Table) colormaps from cpt-city in Matplotlib",
"version": "0.1.5",
"project_urls": {
"Homepage": "https://github.com/leonard-seydoux/pycpt-city",
"Issues": "https://github.com/leonard-seydoux/pycpt-city/issues",
"Repository": "https://github.com/leonard-seydoux/pycpt-city"
},
"split_keywords": [
"colormap",
" palette",
" cpt",
" cpt-city",
" matplotlib",
" visualization"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "bd902431d33024ff775b63bdebc0fc8d1058613870dbfb97f11d157fed10303b",
"md5": "bbe7cbc6e426ed286b18dbb5d5f9d906",
"sha256": "fb830a595bfdf9c2849734c2e37b6e2f086f3903a4c0a40a9585e89acf848de6"
},
"downloads": -1,
"filename": "pycpt_city-0.1.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bbe7cbc6e426ed286b18dbb5d5f9d906",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 2860682,
"upload_time": "2025-10-28T22:13:09",
"upload_time_iso_8601": "2025-10-28T22:13:09.381695Z",
"url": "https://files.pythonhosted.org/packages/bd/90/2431d33024ff775b63bdebc0fc8d1058613870dbfb97f11d157fed10303b/pycpt_city-0.1.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b02d679c88c69138bae157bdcec0570c08bba3f74159d69d26fcdc9469bfd144",
"md5": "c79af4fecd7375a15cc4548dbb56a196",
"sha256": "78ba6f61f6749e438a30cb7e183a45dd37e1a2baaf69dcb5138a02a688edfb26"
},
"downloads": -1,
"filename": "pycpt_city-0.1.5.tar.gz",
"has_sig": false,
"md5_digest": "c79af4fecd7375a15cc4548dbb56a196",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 1142955,
"upload_time": "2025-10-28T22:13:13",
"upload_time_iso_8601": "2025-10-28T22:13:13.213920Z",
"url": "https://files.pythonhosted.org/packages/b0/2d/679c88c69138bae157bdcec0570c08bba3f74159d69d26fcdc9469bfd144/pycpt_city-0.1.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-28 22:13:13",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "leonard-seydoux",
"github_project": "pycpt-city",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "pycpt-city"
}