Skip to content

Commit 1f1c6bd

Browse files
garthvhclaude
andauthored
feat(display): add a Meshtastic SNR color scale (red/orange/yellow/green) (#75)
* feat(display): add a Meshtastic SNR color scale (red/orange/yellow/green) Coverage color scales were all general-purpose matplotlib palettes (plasma, viridis, etc.), with nothing matching the signal-quality colors Meshtastic app users already recognize. Add a "mesh" colormap — a red -> orange -> yellow -> green gradient using the same stops and order as the Meshtastic Apple app's SNR gauge (LoRaSignalStrength.swift's `Gradient(colors: [.red, .orange, .yellow, .green])`) — as a custom matplotlib colormap registered in both utils/generate_colorbars.py (the preview PNG generator) and scripts/gen_colormap_luts.py (the LUT generator), so a coverage map's color now reads the same way the app's own signal indicators do. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(display): rename SNR color scale option to "Mesh" Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 5a9d2b7 commit 1f1c6bd

5 files changed

Lines changed: 34 additions & 1 deletion

File tree

public/colormaps/mesh.png

365 Bytes
Loading

scripts/gen_colormap_luts.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,25 @@
1212

1313
matplotlib.use("Agg")
1414
import matplotlib.pyplot as plt
15+
from matplotlib.colors import LinearSegmentedColormap
1516
import numpy as np
1617

18+
# Custom colormaps not built into matplotlib, registered by name so plt.get_cmap
19+
# below picks them up like any built-in. Keep in sync with utils/generate_colorbars.py,
20+
# which registers the same colormaps for the Display panel's preview PNGs.
21+
#
22+
# "mesh": red -> orange -> yellow -> green, the same stops and order as the
23+
# Meshtastic Apple app's SNR gauge (LoRaSignalStrength.swift's
24+
# `Gradient(colors: [.red, .orange, .yellow, .green])`), so low signal reads
25+
# red and strong signal reads green, consistent with that app's UI.
26+
_CUSTOM_COLORMAPS = {
27+
"mesh": ["#FF3B30", "#FF9500", "#FFCC00", "#34C759"],
28+
}
29+
for _name, _colors in _CUSTOM_COLORMAPS.items():
30+
matplotlib.colormaps.register(LinearSegmentedColormap.from_list(_name, _colors, N=256), name=_name)
31+
1732
# Keep in sync with the options in src/components/Display.vue.
18-
COLORMAPS = ["plasma", "CMRmap", "cool", "viridis", "turbo", "jet"]
33+
COLORMAPS = ["plasma", "CMRmap", "cool", "viridis", "turbo", "jet", "mesh"]
1934

2035
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
2136
OUT = os.path.join(REPO, "src", "render", "colormaps.ts")

src/components/Display.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<option value="cool">Cool</option>
3939
<option value="turbo">Turbo</option>
4040
<option value="jet">Jet</option>
41+
<option value="mesh">Mesh</option>
4142
</select>
4243
</div>
4344
<div>

src/render/colormaps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const HEX_LUTS: Record<string, string> = {
1515
'30123b31154232184a341b51351e5836215f37236538266c3929723a2c793b2f7f3c32853c358b3d37913e3a963f3d9c4040a14043a64145ab4148b0424bb5434eba4350be4353c24456c74458cb455bce455ed24560d64563d94666dd4668e0466be3466de64670e84673eb4675ed4678f0467af2467df4467ff64682f84584f94587fb4589fc448cfd438efd4291fe4193fe4096fe3f98fe3e9bfe3c9dfd3ba0fc39a2fc38a5fb36a8f934aaf833acf631aff52fb1f32db4f12bb6ef2ab9ed28bbeb26bde925c0e623c2e421c4e120c6df1ec9dc1dcbda1ccdd71bcfd41ad1d219d3cf18d5cc18d7ca17d9c717dac417dcc217debf18e0bd18e1ba19e3b81ae4b61be5b41de7b11ee8af20e9ac22eba924eca627eda329eea02cef9d2ff09a32f19735f39438f4913bf48d3ff58a42f68746f7834af8804df97c51f97955fa7659fb725dfb6f61fc6c65fc6869fd656dfd6271fd5f74fe5c78fe597cfe5680fe5384fe5087fe4d8bfe4b8efe4892fe4695fe4498fe429bfd409efd3ea1fc3da4fc3ba6fb3aa9fb39acfa37aef937b1f836b3f835b6f735b9f534bbf434bef334c0f233c3f133c5ef33c8ee33caed33cdeb34cfea34d1e834d4e735d6e535d8e335dae236dde036dfde36e1dc37e3da37e5d838e7d738e8d538ead339ecd139edcf39efcd39f0cb3af2c83af3c63af4c43af6c23af7c039f8be39f9bc39f9ba38fab737fbb537fbb336fcb035fcae34fdab33fda932fda631fda330fea12ffe9e2efe9b2dfe982cfd952bfd9229fd8f28fd8c27fc8926fc8624fb8323fb8022fa7d20fa7a1ff9771ef8741cf7711bf76e1af66b18f56817f46516f36315f26014f15d13ef5a11ee5810ed550fec520eea500de94d0de84b0ce6490be5460ae3440ae24209e04008de3e08dd3c07db3a07d93806d73606d63405d43205d23005d02f04ce2d04cb2b03c92903c72803c52602c32402c02302be2102bb1f01b91e01b61c01b41b01b11901ae1801ac1601a91501a61401a31201a011019d10019a0e01970d01940c01910b018e0a018b09018708018407018106027d05027a0402',
1616
jet:
1717
'00007f00008400008800008d00009100009600009a00009f0000a30000a80000ac0000b10000b60000ba0000bf0000c30000c80000cc0000d10000d50000da0000de0000e30000e80000ec0000f10000f50000fa0000fe0000ff0000ff0000ff0000ff0004ff0008ff000cff0010ff0014ff0018ff001cff0020ff0024ff0028ff002cff0030ff0034ff0038ff003cff0040ff0044ff0048ff004cff0050ff0054ff0058ff005cff0060ff0064ff0068ff006cff0070ff0074ff0078ff007cff0080ff0084ff0088ff008cff0090ff0094ff0098ff009cff00a0ff00a4ff00a8ff00acff00b0ff00b4ff00b8ff00bcff00c0ff00c4ff00c8ff00ccff00d0ff00d4ff00d8ff00dcfe00e0fa00e4f702e8f405ecf108f0ed0cf4ea0ff8e712fce415ffe118ffdd1cffda1fffd722ffd425ffd029ffcd2cffca2fffc732ffc336ffc039ffbd3cffba3fffb742ffb346ffb049ffad4cffaa4fffa653ffa356ffa059ff9d5cff9a5fff9663ff9366ff9069ff8d6cff8970ff8673ff8376ff8079ff7d7cff7980ff7683ff7386ff7089ff6c8dff6990ff6693ff6396ff5f9aff5c9dff59a0ff56a3ff53a6ff4faaff4cadff49b0ff46b3ff42b7ff3fbaff3cbdff39c0ff36c3ff32c7ff2fcaff2ccdff29d0ff25d4ff22d7ff1fdaff1cddff18e0ff15e4ff12e7ff0feaff0cedff08f1fc05f4f802f7f400faf000feed00ffe900ffe500ffe200ffde00ffda00ffd700ffd300ffcf00ffcb00ffc800ffc400ffc000ffbd00ffb900ffb500ffb100ffae00ffaa00ffa600ffa300ff9f00ff9b00ff9800ff9400ff9000ff8c00ff8900ff8500ff8100ff7e00ff7a00ff7600ff7300ff6f00ff6b00ff6700ff6400ff6000ff5c00ff5900ff5500ff5100ff4d00ff4a00ff4600ff4200ff3f00ff3b00ff3700ff3400ff3000ff2c00ff2800ff2500ff2100ff1d00ff1a00ff1600fe1200fa0f00f50b00f10700ec0300e80000e30000de0000da0000d50000d10000cc0000c80000c30000bf0000ba0000b60000b10000ac0000a80000a300009f00009a00009600009100008d00008800008400007f0000',
18+
mesh:
19+
'ff3b30ff3c2fff3d2eff3e2eff3f2dff402dff412cff422cff432bff442aff452aff4629ff4729ff4828ff4928ff4a27ff4b26ff4d26ff4e25ff4f25ff5024ff5124ff5223ff5323ff5422ff5521ff5621ff5720ff5820ff591fff5a1fff5b1eff5c1dff5d1dff5f1cff601cff611bff621bff631aff6419ff6519ff6618ff6718ff6817ff6917ff6a16ff6b16ff6c15ff6d14ff6e14ff6f13ff7113ff7212ff7312ff7411ff7510ff7610ff770fff780fff790eff7a0eff7b0dff7c0cff7d0cff7e0bff7f0bff800aff810aff8309ff8409ff8508ff8607ff8707ff8806ff8906ff8a05ff8b05ff8c04ff8d03ff8e03ff8f02ff9002ff9101ff9201ff9300ff9500ff9500ff9600ff9600ff9700ff9800ff9800ff9900ff9a00ff9a00ff9b00ff9c00ff9c00ff9d00ff9e00ff9e00ff9f00ffa000ffa000ffa100ffa100ffa200ffa300ffa300ffa400ffa500ffa500ffa600ffa700ffa700ffa800ffa900ffa900ffaa00ffab00ffab00ffac00ffac00ffad00ffae00ffae00ffaf00ffb000ffb000ffb100ffb200ffb200ffb300ffb400ffb400ffb500ffb600ffb600ffb700ffb700ffb800ffb900ffb900ffba00ffbb00ffbb00ffbc00ffbd00ffbd00ffbe00ffbf00ffbf00ffc000ffc100ffc100ffc200ffc200ffc300ffc400ffc400ffc500ffc600ffc600ffc700ffc800ffc800ffc900ffca00ffca00ffcb00ffcc00fccb01facb02f7cb03f5cb04f3cb05f0cb06eecb07ebcb08e9cb09e7cb0ae4cb0be2cb0cdfcb0dddcb0edbcb0fd8cb10d6cb11d4ca12d1ca13cfca14ccca15caca17c8ca18c5ca19c3ca1ac0ca1bbeca1cbcca1db9ca1eb7ca1fb4ca20b2ca21b0ca22adca23abc924a9c925a6c926a4c927a1c9289fc9299dc92a9ac92b98c92d95c92e93c92f91c9308ec9318cc93289c93387c93485c93582c83680c8377ec8387bc83979c83a76c83b74c83c72c83d6fc83e6dc83f6ac84068c84166c84363c84461c8455ec8465cc8475ac74857c74955c74a53c74b50c74c4ec74d4bc74e49c74f47c75044c75142c7523fc7533dc7543bc75538c75636c75734c759',
1820
};
1921

2022
const decoded = new Map<string, Uint8Array>();

utils/generate_colorbars.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,25 @@
1010
filename (str): Name of the output file (e.g., "colorbar.png").
1111
"""
1212

13+
import matplotlib
1314
import matplotlib.pyplot as plt
15+
from matplotlib.colors import LinearSegmentedColormap
1416
import numpy as np
1517
import argparse
1618

19+
# Custom colormaps not built into matplotlib, registered by name so they can be
20+
# passed like any other `colormap` argument below.
21+
#
22+
# "mesh": red -> orange -> yellow -> green, the same stops and order as the
23+
# Meshtastic Apple app's SNR gauge (LoRaSignalStrength.swift's
24+
# `Gradient(colors: [.red, .orange, .yellow, .green])`), so low signal reads
25+
# red and strong signal reads green, consistent with that app's UI.
26+
_CUSTOM_COLORMAPS = {
27+
"mesh": ["#FF3B30", "#FF9500", "#FFCC00", "#34C759"],
28+
}
29+
for _name, _colors in _CUSTOM_COLORMAPS.items():
30+
matplotlib.colormaps.register(LinearSegmentedColormap.from_list(_name, _colors, N=256), name=_name)
31+
1732
def export_colormap(colormap, dimensions, filename):
1833
try:
1934
# Create a figure and axis

0 commit comments

Comments
 (0)