Skip to content

Commit 22191ec

Browse files
authored
Merge pull request #510 from PyAutoLabs/feature/cmap-magma-default
feat: make the colormap config lever loud and documented (#509)
2 parents 50cc3b2 + c8fe47f commit 22191ec

3 files changed

Lines changed: 372 additions & 8 deletions

File tree

autoarray/config/visualize/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,52 @@ The `config` folder contains configuration files which customize default **PyAut
88
- `mat_wrap.yaml`: Specify the default matplotlib settings when figures and subplots are plotted.
99
- `mat_wrap_1d.yaml`: Specify the default matplotlib settings when 1D figures and subplots are plotted.
1010
- `mat_wrap_2d.yaml`: Specify the default matplotlib settings when 2D figures and subplots are plotted.
11+
12+
# Changing the colormap
13+
14+
Every 2D figure — imaging data, fits, residual maps, inversion reconstructions —
15+
draws with the colormap named by the `colormap` key of `general.yaml`:
16+
17+
```yaml
18+
colormap: autoarray # any matplotlib colormap name, e.g. magma, viridis, inferno
19+
```
20+
21+
`autoarray` is the colormap bundled with **PyAutoArray**; any other value is
22+
looked up in matplotlib, so `magma`, `viridis`, `inferno`, `plasma`, `jet` and
23+
the rest of `list(matplotlib.colormaps)` all work. Editing this one key is
24+
enough — no plotting code needs changing.
25+
26+
A name matplotlib does not recognise (a typo, say) raises a `ValueError` naming
27+
the key and the offending value. It is **not** silently swapped back for the
28+
default, so a colormap setting never goes quietly ignored.
29+
30+
## One figure at a time
31+
32+
To override the colormap for a single figure without touching config, pass
33+
`colormap=` to any plot function:
34+
35+
```python
36+
import autoarray.plot as aplt
37+
38+
aplt.plot_array(array=image, colormap="magma")
39+
aplt.plot_inversion_reconstruction(pixel_values=values, mapper=mapper, colormap="viridis")
40+
```
41+
42+
The same argument exists on the **PyAutoGalaxy** and **PyAutoLens** plot
43+
functions (`subplot_fit`, `plot_tracer`, `subplot_sensitivity`, …), which pass
44+
it straight through to **PyAutoArray**. Its "use the config value" default is
45+
spelled `None` in **PyAutoArray** and **PyAutoLens**, and `"default"` in
46+
**PyAutoGalaxy**; both mean the same thing.
47+
48+
## Figures that deliberately ignore the setting
49+
50+
A few figures fix their colormap because the colormap carries meaning that a
51+
user preference should not override:
52+
53+
- The `array_overlay` of `plot_array` uses `Greys`, so the overlaid array stays
54+
legible on top of whatever colormap the main array is drawn in.
55+
- The weak-lensing figures in **PyAutoLens** use `twilight` for position angles
56+
(cyclic data needs a cyclic colormap) and `RdBu_r` for residuals (diverging
57+
data needs a diverging colormap centred on zero).
58+
- The cluster figures in **PyAutoLens** use `gnuplot2`, and the interactive GUI
59+
tools use `jet`, to keep faint features visible while masks are drawn by hand.

autoarray/plot/utils.py

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -901,34 +901,133 @@ def hide_unused_axes(axes) -> None:
901901
ax.axis("off")
902902

903903

904+
#: Config key holding the default colormap, quoted in error messages.
905+
_COLORMAP_CONF_KEY = "visualize/general.yaml -> colormap"
906+
907+
904908
def _default_colormap() -> str:
905-
"""Return the colormap name from config, registering the custom one if needed."""
909+
"""Return the default colormap name for 2D figures.
910+
911+
The name is read from the ``colormap`` key of ``visualize/general.yaml``.
912+
The two failure modes are deliberately kept apart:
913+
914+
- **No config at all** (``autonerves`` not installed, or no ``colormap``
915+
key on the config path — e.g. a bare install with no workspace) falls
916+
back quietly to the bundled ``"autoarray"`` colormap. Nothing is
917+
misconfigured, so nothing is said.
918+
- **A config value that matplotlib does not recognise** raises
919+
``ValueError``. A typo'd colormap name used to revert silently to
920+
``"autoarray"``, so the user never learned their setting was ignored.
921+
922+
Returns
923+
-------
924+
str
925+
A colormap name matplotlib can resolve — either ``"autoarray"`` (the
926+
bundled colormap, registered here on first use) or a matplotlib name.
927+
928+
Raises
929+
------
930+
ValueError
931+
If the ``colormap`` config key is set to something that is not a
932+
registered matplotlib colormap.
933+
"""
906934
try:
907935
from autonerves import conf
908-
name = conf.instance["visualize"]["general"]["colormap"]
909-
except Exception:
936+
from autonerves.exc import ConfigException
937+
except ImportError:
910938
name = "autoarray"
939+
else:
940+
try:
941+
name = conf.instance["visualize"]["general"]["colormap"]
942+
except (KeyError, ConfigException):
943+
name = "autoarray"
944+
911945
if name == "autoarray":
912946
from autoarray.plot.segmentdata import register
947+
913948
register()
949+
return name
950+
951+
_validate_colormap(name)
952+
914953
return name
915954

916955

956+
def _validate_colormap(name) -> None:
957+
"""Raise ``ValueError`` unless *name* is a colormap matplotlib knows about.
958+
959+
Parameters
960+
----------
961+
name
962+
The colormap name read from config (or passed by the user).
963+
964+
Raises
965+
------
966+
ValueError
967+
If *name* is not a string, or is not a registered matplotlib colormap.
968+
"""
969+
import matplotlib
970+
971+
if isinstance(name, str) and name in matplotlib.colormaps:
972+
return
973+
974+
raise ValueError(
975+
f"Unknown colormap {name!r}.\n\n"
976+
f"The config key `{_COLORMAP_CONF_KEY}` is set to {name!r}, which is "
977+
f"not a colormap matplotlib recognises, so no figure can be drawn "
978+
f"with it.\n\n"
979+
f"Use either `autoarray` (the colormap bundled with PyAutoArray) or "
980+
f"any matplotlib colormap name, for example `magma`, `viridis`, "
981+
f"`inferno`, `plasma` or `jet`.\n"
982+
f"The full list is `list(matplotlib.colormaps)`."
983+
)
984+
985+
917986
def _conf_imshow_origin() -> str:
918-
"""Return the imshow origin from config (``"upper"`` or ``"lower"``)."""
987+
"""Return the imshow origin from config (``"upper"`` or ``"lower"``).
988+
989+
An absent config falls back quietly to ``"upper"``; a value matplotlib's
990+
``imshow`` would reject raises ``ValueError`` rather than being silently
991+
swapped for the default (same contract as :func:`_default_colormap`).
992+
"""
919993
try:
920994
from autonerves import conf
921-
return conf.instance["visualize"]["general"]["general"]["imshow_origin"]
922-
except Exception:
995+
from autonerves.exc import ConfigException
996+
except ImportError:
997+
return "upper"
998+
999+
try:
1000+
origin = conf.instance["visualize"]["general"]["general"]["imshow_origin"]
1001+
except (KeyError, ConfigException):
9231002
return "upper"
9241003

1004+
if origin not in ("upper", "lower"):
1005+
raise ValueError(
1006+
f"Invalid imshow origin {origin!r}.\n\n"
1007+
f"The config key `visualize/general.yaml -> general -> "
1008+
f"imshow_origin` must be either `upper` or `lower`."
1009+
)
1010+
1011+
return origin
1012+
9251013

9261014
def _conf_output_format() -> str:
927-
"""Return the default output_format from config (``"show"``, ``"png"``, etc.)."""
1015+
"""Return the default output_format from config (``"show"``, ``"png"``, etc.).
1016+
1017+
An absent config falls back quietly to ``"show"``. The value itself is not
1018+
validated here — an unsupported format surfaces as matplotlib's own
1019+
``savefig`` error, which already names the offending format and lists the
1020+
supported ones.
1021+
"""
9281022
try:
9291023
from autonerves import conf
1024+
from autonerves.exc import ConfigException
1025+
except ImportError:
1026+
return "show"
1027+
1028+
try:
9301029
return conf.instance["visualize"]["general"]["general"]["output_format"]
931-
except Exception:
1030+
except (KeyError, ConfigException):
9321031
return "show"
9331032

9341033

0 commit comments

Comments
 (0)