Claude Media
Claude Codeで色覚に配慮したカラーマップを選ぶ — jetからviridisへ置換して検証

Claude Codeで色覚に配慮したカラーマップを選ぶ — jetからviridisへ置換して検証

matplotlibの図でjetをviridisなどに置き換え、グレースケール印刷と色覚シミュレーションで判別できるかをClaude Codeに測らせる手順です。

論文やダッシュボードの図は、カラーマップを jet から viridis のような明度が単調に変わるものへ替え、グレースケールと色覚シミュレーションで読めるかを測れば、色覚多様性への対応として筋が通ります。Claude Codeには、既存スクリプトの cmap 指定を洗い出させる作業と、明度を数値で測る検証スクリプトの実行を任せられます。

見た目の良し悪しをClaudeに判断させると、同じ図でも答えがぶれます。そこで、判断は数値に寄せます。この記事では、matplotlibの公式解説が示す基準を検証コードに落とし、置換から確認までを一続きの手順にします。

何を直すのか — jetが読みにくい理由を数値で見る

matplotlibの公式解説は、多くの用途で知覚的に均一なカラーマップが最適だと説明しています。人の脳はデータの変化を、色相よりも明度の変化として受け取りやすいため、明度が単調に増えていくカラーマップのほうが正しく読まれるからです。

この基準は、手元のカラーマップで数値にできます。公式解説がグレースケール変換の節で使っているのと同じ、CAM02-UCS色空間の明度L*を計算します。以下は、Claude Codeに書かせて実行した確認用スクリプトです。

python -m venv .venv && . .venv/bin/activate
pip install matplotlib colorspacious
# cmap_check.py — カラーマップの明度(L*)を調べる
# 使い方: python cmap_check.py sequential viridis cividis
#         python cmap_check.py diverging coolwarm
import sys
import numpy as np
import matplotlib as mpl
from colorspacious import cspace_convert
 
kind, names = sys.argv[1], sys.argv[2:]
x = np.linspace(0, 1, 256)
for name in names:
    rgb = mpl.colormaps[name](x)[:, :3]
    L = cspace_convert(rgb, "sRGB1", "CAM02-UCS")[:, 0]
    if kind == "sequential":
        ok = bool((np.diff(L) >= -0.5).all())  # 明度が一方向に増える
    else:
        # 両端の明度が近く、最大が中央にある
        ok = bool(abs(L[0] - L[-1]) < 5 and 0.4 < x[L.argmax()] < 0.6)
    print(f"{name:9s} L* {L[0]:5.1f} -> {L[-1]:5.1f}  "
          f"min {L.min():5.1f} max {L.max():5.1f}  {kind}_ok={ok}")

連続型(Sequential)と発散型(Diverging)では、合格の条件が違います。連続型は明度が一方向に増えること、発散型は両端の明度がほぼ等しく、中央で最大になることです。matplotlib 3.11.2で python cmap_check.py sequential jet turbo viridis cividis gray と python cmap_check.py diverging coolwarm を実行した出力は次のとおりです。

jet       L*  14.3 ->  29.8  min  14.3 max  96.1  sequential_ok=False
turbo     L*  15.6 ->  28.8  min  15.6 max  90.8  sequential_ok=False
viridis   L*  18.6 ->  92.4  min  18.6 max  92.4  sequential_ok=True
cividis   L*  16.0 ->  92.7  min  16.0 max  92.7  sequential_ok=True
gray      L*   0.0 -> 100.0  min   0.0 max 100.0  sequential_ok=True
coolwarm  L*  38.7 ->  43.1  min  38.7 max  89.6  diverging_ok=True

jet と turbo は、始点と終点がどちらも暗い色で、途中で明るくなってまた暗くなります。連続量に使うと、値の大小がそのまま明るさの大小にならないので、白黒で刷ると高い値と低い値が同じ灰色に化けます。viridis と cividis はL*が90前後まで一方向に伸びます。

数値で見る置換の根拠

  • jet と turbo は明度が途中で折り返し、連続型としては sequential_ok=False になる
  • viridis と cividis は明度が単調に増え、グレースケールでも順序が残る
  • coolwarm は両端の明度がほぼ同じ(38.7と43.1)で、灰色にすると青側と赤側の区別がつかない。中央だけが明るくなる

coolwarm のような発散型は、中央を基準に両側へ振れるデータのために作られています。中央で明るく両端で暗くなるので、連続型の判定では必ず単調でなくなります。発散型で見るのは、両端の明度が近いことと、最大が中央にあることです。coolwarm は中央で89.6まで上がりますが、両端は38.7と43.1で、明度の振れ幅が小さめです。公式解説も、これを印刷すると詳細が失われると述べています。発散型を使うなら、等高線にラベルを重ねて、色以外の手がかりを足します。

データの性質でカラーマップの種類を決める

viridis に統一すれば済むわけではありません。公式解説は、カラーマップを役割で4つに分けています。

選び方

データの性質とカラーマップの種類

  • 順序のある量

    温度や密度のように大小がある値には、明度が段階的に変わる連続型(Sequential)を使います。viridis はここに入ります。

  • 中央に意味のある量

    地形の標高やゼロ付近で振れる差分には、2色が中央の無彩色で交わる発散型(Diverging)を使います。

  • 端がつながる量

    位相角、風向、時刻のように終点が始点に戻る値には、循環型(Cyclic)の twilight が候補です。hsv は明度の変動が大きく、知覚的な用途には向かないと公式解説は述べています。

  • 順序のないカテゴリ

    質的型(Qualitative)は知覚的な連続性を目的にしておらず、Accent のように明度が行き来します。連続量の表現には使えません。

洗い出しの表に「この図のデータはどの種類か」の列を足して、Claudeに埋めさせてから置換させると、風向の図に viridis を当てて端の不連続を作るような取り違えを防げます。

Claude Codeに洗い出させる — cmapの指定箇所を一覧にする

置換の前に、リポジトリのどこで色が決まっているかを把握します。cmap= の直接指定だけでなく、rcParams["image.cmap"] や、plt.cm.jet のような属性参照、スタイルファイルの設定も対象です。

次のように依頼します。

リポジトリ内で matplotlib のカラーマップを指定している箇所を全部洗い出して。
cmap=、plt.cm.*、image.cmap、スタイルファイルを対象にして、
ファイルパス・行番号・カラーマップ名・何のデータに使っているかを表にして。
まだ編集はしないで。

表ができたら、各行を用途で3つに分けます。

用途例置換の方針
連続量(温度・密度・強度)例ヒートマップ、等高線図置換の方針明度が単調な viridis などへ
中央を基準に両側へ振れる量例差分、残差置換の方針発散型を使い、ラベルや等高線を併用
離散カテゴリ例系列ごとの折れ線、棒の色分け置換の方針色だけに頼らず、線種やマーカーを併用

カテゴリの色分けは、連続カラーマップへ替えても解決しません。色相で系列を見分けさせる図では、線種やマーカー、直接ラベルのように色以外の区別も足したほうが確実です。この3分類を先に決めておくと、Claudeが一律に viridis へ置換して離散データの図を壊す事態を避けられます。

規約をCLAUDE.mdに書いて検証コマンドを固定する

置換のたびに基準を言い直さずに済むよう、プロジェクトの CLAUDE.md に規約を置きます。Claudeは CLAUDE.md を強制される設定ではなくコンテキストとして読むため、検証できる書き方にしておくほうが守られやすくなります。

## 図のカラーマップ規約
- 連続量は viridis / cividis など明度が単調に増えるものを使う。jet / turbo / hsv / rainbow は使わない
- 赤と緑を同じ図で対比させない(赤緑の色覚特性で区別できなくなる)
- 図を変更したら `python tools/cmap_check.py sequential <連続型のcmap名>` を実行し、
  sequential_ok=True を確認してから完了とする
- 発散型は `python tools/cmap_check.py diverging <cmap名>` で、
  diverging_ok=True(両端の明度が近く、中央が最大)を確認する
- 発散型(coolwarm 等)を使う図は、等高線ラベルか数値注記を必ず付ける

赤緑を避ける規約は、公式解説の記述に沿っています。色覚特異の最も一般的な型は赤と緑の区別に関わるため、赤と緑の両方を含むカラーマップを避けると、多くの問題を防げます。

置換を実行させる — jetからviridisへ

洗い出しと規約がそろったら、置換を依頼します。

連続量の図だけ、jet を viridis に置換して。
離散カテゴリの図と発散型の図は触らない。
置換したら cmap_check.py を sequential で実行して、結果を貼って。

差分は小さくなります。

-im = ax.imshow(field, cmap="jet")
+im = ax.imshow(field, cmap="viridis")
 fig.colorbar(im, ax=ax, label="temperature [K]")

カラーバーのラベルは変えません。置換後に図の見え方が変わるので、論文なら本文中の「赤い領域」「青い領域」といった色で指す表現も読み直す必要があります。この確認はClaudeに別途頼みます。「本文と図のキャプションで色名を使っている箇所を挙げて」と依頼すれば、書き直す候補を拾えます。

グレースケールで判別できるかを測る

公式解説は、カラー図が白黒プリンターで刷られる場合があるため、グレースケール変換に注意が必要だと述べています。変換の方法は複数あり、知覚に合わせた重みづけをした線形結合や、L*を使う非線形の方法が挙げられています。

目視ではなく、実際にグレースケール画像として書き出して確かめます。

# gray_preview.py — 図をグレースケールにして保存する
import sys
from PIL import Image
 
src, dst = sys.argv[1], sys.argv[2]
Image.open(src).convert("L").save(dst)
python gray_preview.py fig_before.png fig_before_gray.png
python gray_preview.py fig_after.png fig_after_gray.png

Claudeは画像ファイルを読めるので、「2枚のグレースケール画像を見比べて、値の高い領域と低い領域が区別できるか答えて」と依頼できます。ただし、これは補助の確認です。判別できるかの根拠は、前節のL*のほうに置いてください。画像を読んだ感想は、同じ図でも答えが変わりえます。

公式解説は、autumn、spring、summer、winter はグレースケールでの変化が小さいと述べています。名前から暖色や寒色の連続的なカラーマップに見えても、明度の幅は別の話です。

色覚シミュレーションで確認する

グレースケールは明度だけを見るので、色相の混同は別に確かめます。colorspaciousライブラリには、色覚特異を模擬する変換が用意されています。deuteranomaly(最も一般的な赤緑型で、一部の男性に見られる)、protanomaly、tritanomalyを指定でき、severityは0から100で、100が完全な二色型色覚に当たります。

# cvd_preview.py — 色覚シミュレーション後の画像を保存する
import sys
import numpy as np
from PIL import Image
from colorspacious import cspace_convert
 
src, dst, kind = sys.argv[1], sys.argv[2], sys.argv[3]
rgb = np.asarray(Image.open(src).convert("RGB")) / 255.0
space = {"name": "sRGB1+CVD", "cvd_type": kind, "severity": 100}
out = np.clip(cspace_convert(rgb, space, "sRGB1"), 0, 1)
Image.fromarray((out * 255).astype(np.uint8)).save(dst)
for k in deuteranomaly protanomaly tritanomaly; do
  python cvd_preview.py fig_after.png "fig_after_$k.png" "$k"
done

変換は「CVD空間から標準sRGB空間へ」という向きで書きます。ある色覚で見たときに同じに見える色を、通常の色覚でも同じに見えるRGBへ写す、という考え方です。

シミュレーション画像は、カラーバーと本体を並べてClaudeに読ませ、「カラーバーの両端と中間の色が互いに区別できるか」を答えさせます。この判断も参考値です。確証が要る図は、色覚特異のある同僚や共著者に実際に見てもらってください。

検証をスキルにまとめて使い回す

図を作るたびに3つのスクリプトを呼ぶ手間は、スキルにまとめると減ります。Claude Codeのスキルは、.claude/skills/<スキル名>/SKILL.md に置くと、そのリポジトリのセッションで使えます。個人用なら ~/.claude/skills/<スキル名>/SKILL.md で、全プロジェクトから使えます。frontmatterの description は、Claudeがスキルを自動で読み込むかの判断材料になります。

---
description: matplotlib の図のカラーマップを検証する。図を作成・変更したとき、またはカラーマップの色覚対応を聞かれたときに使う。
---
 
## 手順
 
1. 変更した図で使っているカラーマップ名を列挙する
2. 連続型は `python tools/cmap_check.py sequential <名前...>`、発散型は `diverging` で実行し、`_ok=False` のものを報告する(循環型・質的型は対象外)
3. 図のPNGに対して gray_preview.py と cvd_preview.py を実行する
4. 判別できない箇所があれば、cmap の変更案を示す(編集は確認を取ってから)

スキル機構そのものはClaude Codeのdatavizスキルの解説で扱っています。そちらは配色の検証スクリプトを含むバンドルスキルの話で、この記事のスキルは、matplotlibに絞った自作の検証手順です。

検証でつまずきやすい点

  • カラーバーの端が切れている: vmin と vmax を狭く指定すると、カラーマップの片端しか使われません。L*を調べても、実際に使う範囲で足りているかは別問題です。
  • 離散データに連続カラーマップを当てる: 系列の数だけ等間隔に色を取り出す書き方でも、隣り合う系列の差は小さくなります。線種やマーカーも併用します。
  • 画像の埋め込み時の色変換: 投稿先によっては、PDF化の段階でカラープロファイルが変わります。投稿規定に合わせた出力設定は論文図のdpiとフォントを揃える手順に任せ、色の検証は、最終形式の画像でもう一度行います。
  • シミュレーションを過信する: シミュレーションは典型的な型の近似です。個人差があるので、通過したからといって誰にでも読めるとは言えません。

Claude Code自体の端末表示の配色は、themeコマンドの色覚多様性対応テーマで別に設定します。図の色とは設定の場所が違います。

まとめ

色覚への対応は、カラーマップの好みの問題として扱うと決着がつきません。L*の単調性、グレースケール変換、色覚シミュレーションの3つを、数値と画像の出力にして残すと、置換が妥当だったことを後から示せます。

Claude Codeに頼むのは、指定箇所の洗い出し、用途別の置換、そして検証スクリプトの実行と結果の報告です。どのカラーマップを選ぶかの前提(連続量か、中央を基準にするか、離散か)は、図の意味を知っている人が先に決めておくと、置換の手戻りが減ります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. matplotlib.org/stable/users/explain/colors/colormaps.html
  2. code.claude.com/docs/en/skills
  3. colorspacious.readthedocs.io/en/latest/tutorial.html
この記事を共有:XはてブLinkedIn