20180409

matplotlibの、cmapを、徐々に変化させる方法。

matplotlibのcmapを変化させる。

下記gif画像のように、自然に、徐々に、cmapを別のcmapへ移行させたい。

04.gif

何のためにやるのかと問われたら、

もちろん、アニメーションのエフェクトが目的。

フーリエの肖像画(著作権に関しては、スミソニアンライブラリーを参照。)のcmapを、gray(この肖像画の方は)→jet(熱伝導の研究をされていた方で、)→seismic(からの〜。)→bwr(フランス人)に徐々に変化させるという、アートなエフェクトがしたい。(このエフェクトは、「飽きさせない工夫」程度のもの。)

普通に使ってたら、こんな需要はないだろう。検索しても、出てこなかった。かなり苦労した。
cmapが変化したら、変だもんな。ここは本来なら、変えちゃだめだよね。

普通、cmapの指定は

文字列で指定する。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img = mpimg.imread("hogehoge.png")
plt.imshow(img, cmap="jet")

plt.show()

cmapを作るのは、以前やった。
ここでは、cmapを指定するために、cdictという所をいじった。

cmapは、文字列で指定するやり方以外に、変数っぽく指定するやり方がある。(ほかの(既存の)cmapは文字列で指定する。)
こんな風に使う。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img = mpimg.imread("hogehoge.png")
c_gray = plt.get_cmap("gray")
plt.imshow(img, cmap=c_gray)

plt.show()

…みたいな。

cmapの自作には、下の関数を使う。詳しくは、最後のコードを参照してください。

matplotlib.colors.LinearSegmentedColormap('cmap%s'%i, cdict, 256)

自作時に、色の指定は、cdictって所で指定する。

ざっくり仕組み。

cdictは、R,G,B別の、(値(x), 色の初期値(y0), 色の終わりの値(y1))のタプルで構成される。
(x, y0, y1)の数は、最低二つ。

import matplotlib.pyplot as plt

c_gray_r = plt.get_cmap("gray_r")
c_jet = plt.get_cmap("jet")

print c_gray_r.__dict__['_segmentdata']
print c_jet.__dict__['_segmentdata']

>>> import matplotlib.pyplot as plt
>>>
>>> cgrayr = plt.getcmap(“grayr”)
>>> c_jet = plt.get_cmap(“jet”)
>>>
>>> print c_gray_r.__dict
[‘_segmentdata’]
{u’blue’: [(0.0, 1, 1), (1.0, 0, 0)], u’green’: [(0.0, 1, 1), (1.0, 0, 0)], u’red’: [(0.0, 1, 1), (1.0, 0, 0)]}
>>> print c_jet.__dict
[‘_segmentdata’]
{u’blue’: ((0.0, 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65, 0, 0), (1, 0, 0)), u’green’: ((0.0, 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1), (0.91, 0, 0), (1, 0, 0)), u’red’: ((0.0, 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1), (1, 0.5, 0.5))}

cmapが、gray_rの場合は、すべての色のタプルが、二つしかない。
しかし、jetの場合は、青が5、緑が6、赤が5種類もある。
cmapによって、また、色によって、タプルの個数が異なっていた。

jetのようにカラフルなcmapだと、タプルの個数が多くなっている
これらを、どこだかで、線形補完して、徐々にcmapが変化しているアニメを作る。

大まかに書くと、
二つのcmapのタプルの数をそろえる。
→両者の各色の値を、線形補完した値で、タプルを作りなおす。

もう少し細かく書くと、

  1. タプルから、xだけ取り出したリストを作って配列化。
  2. その配列をソートして
  3. ダブっているのを排除。
  4. このxのリストに従って、線形補完。まずは、各cmapの中だけで、$(x_0 , y0_0 , y1_0 )\to (x_1 , y0_1 , y1_1 )$ を線形補完。そのxが無かったら、そのxより大きいxを持つ一つ上のタプルから拝借して、dx分を加味して線形補完。(ここら辺がごちゃごちゃして、読みにくくなってしまった。)
  5. 各色のcdictのタプルの長さが等しくなった二つのcmapを、分割したいフレーム数で、線形補完。

4-5の辺は、ガチャガチャやったらなんかうまくいったという感じになっている(下記、汚コードを参照)。
「ここが、こうなってー」とかの説明を求められたら、答えに窮する。
アニメのエフェクトに関して言えば、「こんなの動けばいいんだ」といいたい。

使い方

環境(バージョン)
  • python 2.7
  • numpy-1.13.1
  • matplotlib-1.5.3
  • mpl_toolkits

  • tkFileDialog

ちょっと位ちがっても、動く気がする。

usage

徐々に変化するcmap(の変数が収まったdict変数)を作る、関数にした。

引数
  • cmap1 matplotlib.colors.LinearSegmentedColormap object
  • cmap2 matplotlib.colors.LinearSegmentedColormap object
  • frames 初期値30。30個のcmapに分割する。0-29(30個)で指定する。こんな感じ。cmap=cmaps1[“%s”%i]
返り値

framesと同じ数のcmap。
dictの中に、cmap変数を入れているので、0〜(frames-1)(引数frames個)で指定する。

for i in range(fps):
c = cmap_shift(c0=plt.get_cmap("gray"), c1=plt.get_cmap("jet"))
ax1.imshow(img, cmap=cmaps1["%s"%i])
fig.savefig("%s.jpg"%i)
plt.close()

こうして作ったcmapを、また、ガチャガチャ組み合わせて、使うのであった。

とりあえず、僕の作った汚コードを載せる。
テキスト帳で「hogehoge.py」と保存して、
cmdで、「python hogehoge.py」とか打って、使ってほしい。
pythonインタープリタで動くかどうかは、試してないのでわからない。

#!/usr/bin/env python
# -*- coding: cp932 -*-

import os
import time
from datetime import datetime as dt
import numpy# as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

from matplotlib import ticker
from mpl_toolkits.axes_grid1 import make_axes_locatable

##################################
#ダイアログで、画像ファイルを開く#
##################################
import tkFileDialog as tk
name = tk.askopenfilename(filetypes=[("image",("*.png","*.bmp","*.jpg")),("all","*.*")])

img = mpimg.imread(name)
print(type(img[0][0]))

if isinstance(img[0][0],numpy.ndarray):#カラーなら平均してグレー化。グレー(白黒)ならそのまま。
img = numpy.mean(img,-1)#20180409 a.m.1:59となりのベランダから、ライターを付けるような音がした。ボヤとかは、勘弁してくれよ。たまに、雄たけび、叫び声、笑い声(「イーヒッヒ」)とかが、(それも深夜に)聞こえるが、犯罪は本当に勘弁してくれ。なんだか、ガス代を滞納してるのに、厚かましく自分で開いて使ってるみたいだし。どうしようもない奴なのは確定している。「会って話すと意外と"いい奴"かも」とか言う奴がいるが、おれの定義する"いい奴"は、ガス代を滞納したりしない。早々に引っ越したいが、いろいろあって安いので、この部屋に甘んじている。身の危険を感じたら、出ていきたい(本当は、身の危険を感じる前に出たいが)。今作っている大作を、なんとしてでも完成させたい。完成まで、多分あと一年。それまで、絶対生きねばならん。そのあとだって、アイディアだけは、たんまりあるんだ。

def remove_dup(listarg):
x=[]
for i in listarg:
if i not in x:
x = x+[i]
return x

##########################################################
#徐々に変化するcmap群を、返す関数cmap_shift関数を自作する#
##########################################################
def cmap_shift(c0, c1, frames=30):
red = {}
green = {}
blue = {}
colrow={}
dxy={}
for j in ["red","green","blue"]:
x = [c0.__dict__['_segmentdata']["%s"%j][j2][0] for j2 in range(len(c0.__dict__['_segmentdata']["%s"%j]))] + [c1.__dict__['_segmentdata']["%s"%j][i1][0] for i1 in range(len(c1.__dict__['_segmentdata']["%s"%j]))]
x = sorted(x)
x = remove_dup(x)
xy1 = []
xy2 = []
ic0=0
ic1=0

for i02 in x:
if i02 >= c0.__dict__['_segmentdata']["%s"%j][ic0+1][0]:
ic0+=1
if i02 == c0.__dict__['_segmentdata']["%s"%j][ic0][0]:
y0 = c0.__dict__['_segmentdata']["%s"%j][ic0][1]
else:
try:
y0 = c0.__dict__['_segmentdata']["%s"%j][ic0][1] + ((c0.__dict__['_segmentdata']["%s"%j][ic0+1][1] - c0.__dict__['_segmentdata']["%s"%j][ic0][1]) / (c0.__dict__['_segmentdata']["%s"%j][ic0+1][0] - c0.__dict__['_segmentdata']["%s"%j][ic0][0]))*((i02-c0.__dict__['_segmentdata']["%s"%j][ic0][0]))
except IndexError:
y0 = c0.__dict__['_segmentdata']["%s"%j][-1][1]
if i02 == c0.__dict__['_segmentdata']["%s"%j][ic0][0]:
y1 = c0.__dict__['_segmentdata']["%s"%j][ic0][2]
else:
try:
y1 = c0.__dict__['_segmentdata']["%s"%j][ic0][2] + ((c0.__dict__['_segmentdata']["%s"%j][ic0+1][2] - c0.__dict__['_segmentdata']["%s"%j][ic0][2]) / (c0.__dict__['_segmentdata']["%s"%j][ic0+1][0] - c0.__dict__['_segmentdata']["%s"%j][ic0][0]))*((i02-c0.__dict__['_segmentdata']["%s"%j][ic0][0]))
except IndexError:
y1 = c0.__dict__['_segmentdata']["%s"%j][-1][2]
xy1.append((i02, y0, y1))
if i02 >= c1.__dict__['_segmentdata']["%s"%j][ic1+1][0]:
ic1+=1
if i02 == c1.__dict__['_segmentdata']["%s"%j][ic1][0]:
y0 = c1.__dict__['_segmentdata']["%s"%j][ic1][1]
else:
try:
y0 = c1.__dict__['_segmentdata']["%s"%j][ic1][1] + ((c1.__dict__['_segmentdata']["%s"%j][ic1+1][1] - c1.__dict__['_segmentdata']["%s"%j][ic1][1]) / (c1.__dict__['_segmentdata']["%s"%j][ic1+1][0] - c1.__dict__['_segmentdata']["%s"%j][ic1][0]))*((i02-c1.__dict__['_segmentdata']["%s"%j][ic1][0]))
except IndexError:
y0 = c1.__dict__['_segmentdata']["%s"%j][-1][1]
if i02 == c1.__dict__['_segmentdata']["%s"%j][ic1][0]:
y1 = c1.__dict__['_segmentdata']["%s"%j][ic1][2]
else:
try:
y1 = c1.__dict__['_segmentdata']["%s"%j][ic1][2] + ((c1.__dict__['_segmentdata']["%s"%j][ic1+1][2] - c1.__dict__['_segmentdata']["%s"%j][ic1][2]) / (c1.__dict__['_segmentdata']["%s"%j][ic1+1][0] - c1.__dict__['_segmentdata']["%s"%j][ic1][0]))*((i02-c1.__dict__['_segmentdata']["%s"%j][ic1][0]))
except IndexError:
y1 = c1.__dict__['_segmentdata']["%s"%j][-1][2]
xy2.append((i02, y0, y1))
dxy["%s"%j]=[[(xy2[i][ii]-xy1[i][ii])/float(frames) for ii in range(3)] for i in range(len(xy1))]
colrow["%s"%j]={}
for i03 in range(frames):
colrow["%s"%j]["%s"%i03] = tuple([tuple([xy1[i02][i04] + dxy["%s"%j][i02][i04]*i03 for i04 in range(3)]) for i02 in range(len(x))])

cmaps={}
for i in range(frames):
cdicts_tmp={}
cdicts_tmp["red"] = colrow["red"]["%s"%i]
cdicts_tmp["green"] = colrow["green"]["%s"%i]
cdicts_tmp["blue"] = colrow["blue"]["%s"%i]
cmaps["%s"%i] = matplotlib.colors.LinearSegmentedColormap('cmap%s'%i, cdicts_tmp, 256)
return cmaps

########################################
#自作したcmap_shift関数で、cmaps1を作る#
#c0に"gray"、c1に"jet"を指定する。 #
#コマ数は、5 #
#cmaps1は、dict型で、返ってくる #
########################################
fms=5
cmaps1 = cmap_shift(c0=plt.get_cmap("gray"), c1=plt.get_cmap("jet"),frames=fms)

for i in range(fms):
fig,(ax)=plt.subplots(1)
##############################
#cmaps1は、ここで、使っている#
##############################
imgcol = ax.imshow(img, cmap=cmaps1["%s"%i], interpolation="none", clip_on=False)
divider = make_axes_locatable(ax)
ax_cb = divider.new_horizontal(size="10%", pad=0.05)#
fig.add_axes(ax_cb)
cb = plt.colorbar(imgcol, cax=ax_cb)
tick_locator = ticker.MaxNLocator(nbins=5)
cb.locator = tick_locator
cb.ax.yaxis.set_ticklabels([-1.0,-0.5,0,0.5,1.0])
cb.update_ticks()
ax.locator_params(axis='both', nbins=4)
cb.set_label("\"gray\" to \"jet\"")
tdatetime = dt.now()
itext = tdatetime.strftime('%Y%m%d'+ time.ctime()[11:13] + time.ctime()[14:16] + time.ctime()[17:19])
#fig.set_size_inches(19.2,10.8)
fig.savefig("%s%s.png"%(itext,i))
plt.close()

print("Files are saved on %s"%os.getcwd())

自分の書いたコードでは、gray→jetで固定してあるが、

l130-l140位にある、

cmaps1 = cmap_shift(c0=plt.get_cmap("gray"), c1=plt.get_cmap("jet"),frames=fms)

の所の”gray”とか、”jet”とかを、手動で指定すれば色を色々変えることができる。

cmapとして指定できる、色の一覧は、こことかを参考にしたり、いろいろやって自作したりしてほしい。

posted by yuchan at 07:00 | Comment(0) | python
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: