import matplotlib.pyplot as plt
import numpy
import psiclone.visualization as vis
from psiclone.psi2tree import Psi2tree
可視化モジュール psiclone.visualization
ここでは,可視化用のモジュール psiclone.visualiztion
の関数群の使い方を見ていきます.このモジュールの主な関数は以下のとおりです:
draw_COT_labels()
…… COT ラベルのみをレーブグラフの各辺近傍に描画draw_barcode()
…… パーシステントホモロジーとレーブグラフのバーコード図を描画draw_bitmap()
……GrayscaleImage
型と整合する入力データ空間を描画draw_contour()
…… 等高線図を描画draw_geometry()
……Geometry
型の入力データ空間を描画draw_graph()
…… レーブグラフのみを描画 (NetworkX)draw_graph_markers()
…… レーブグラフのノードとエッジを入力データ空間の座標で描画draw_partition()
…… レーブグラフに対応するパーティション図を描画get_COT_constructions()
…… COT 表現の再帰的構成と対応するパーティション情報を取得get_barcode_data()
…… バーコード図の情報を取得 (draw_barcode()
に対応)get_graph_markers_data()
…… レーブグラフの座標情報を取得 (draw_graph_markers_data()
に対応)get_partition_data()
…… パーティション図の構成情報を取得 (draw_partition()
に対応)
可視化用の関数 draw_***()
の多くは,あくまで初期的実験やチュートリアル導入を簡易的に行えるように用意されたものです.高度・専門的・発展的な可視化の目的では,これらの簡易関数を用いることは非推奨です.高度な可視化を行いたい場合は,Matplotlib などのライブラリの可視化関数を直接利用することを推奨しています.例えば,visualization
モジュールは常に現在の Axes (matplotlib.pyplot.gca()
= get current axes) に対して描画を行います.特定の Axes に描画したいなどより細かなカスタマイズを加えたい場合には,ユーザーが個別に可視化コードを組む必要があります.この場合,描画データ取得用の関数 get_***()
を活用してください.
入力データの用意
まずは入力データ空間を用意します.チュートリアルと同じ方法で例を構成します.
from psiclone.util.convert import convert_array_to_geometry
= -3.0, 3.0, -2.5, 2.5
x0, x1, y0, y1 = numpy.mgrid[x0:x1:15j, y0:y1:15j]
x, y = x + y * 1j
z = numpy.abs(z) >= 1.0
flow_domain = ~flow_domain
obstacle_domain
= numpy.ma.masked_array(z, obstacle_domain)
z = z + 1.0 / z
W = numpy.imag(W)
data
= numpy.ma.masked_array([x, y, data]).reshape((3, -1)).T
table = convert_array_to_geometry(table, obstacles=[(0, 0)], int_grid=False)
geom geom
<psiclone.data.geometry.Geometry at 0x7f6d83eb6c00>
予め COT 表現も計算しておきます.
from psiclone.cot import to_str
= Psi2tree(geom, threshold=1.0e-2)
psi2tree
psi2tree.compute() psi2tree.get_tree().convert(to_str)
'a0(a2(L+,L-))'
基本的な描画関数
psiclone.visualization.draw_bitmap()
draw_bitmap()
関数は,GrayscaleImage
型と整合的な整数座標を持つ入力データ空間の高さ level
を描画します.
vis.draw_bitmap(geom)
psiclone
では,基本的に描画座標の原点を左下にとるように統一されています.これは,psiclone
が流線図を解析するためのライブラリであるからです.matplotlib.pyplot.contour
のデフォルト座標および数学的な慣習に合わせています.
psiclone.visualization.draw_geometry()
draw_geometry()
関数は,一般の Geometry
型の入力データ空間を描画します.bitmap=True
を指定するとdraw_bitmap()
を用います.bitmap=False
のときは draw_networkx()
を用います.以降の複数の描画関数でも同様の bitmap
オプションが存在しています.
=True) vis.draw_geometry(geom, bitmap
原則として,大きなデータの bitmap=False
(NetworkX) による可視化は非推奨です.今の入力データ空間例は十分粗いので,figsize
と dpi
を大きく指定することで敢えて bitmap=False
を指定してみます.
=(7, 5), dpi=150)
plt.figure(figsize=False) vis.draw_geometry(geom, bitmap
Psi2tree
は計算の際にトポロジカルな境界条件を調整するため,入力データ空間の隣接構造を直接書き換えます.このため,内側境界だけでなく外側境界の周りにも複数のエッジが張られていることが確認できます.このようなトポロジー構造を直接確認したい場合以外,bitmap=False
は指定しないでください.
psiclone.visualization.draw_contour()
draw_contour()
関数は入力データ空間の等高線図を描画します.より詳細に描画内容をカスタマイズしたい場合は,matplotlib.pyplot.contour()
などを用いてください.
=True) vis.draw_contour(psi2tree, bitmap
psiclone.visualization.draw_partition()
draw_partition()
関数は,Psi2tree
の計算結果からパーティション図を描画します.これはレーブグラフによる同値類分割に対応します.
=True) vis.draw_partition(psi2tree, bitmap
分割図では別々の同値類を識別しやすいよう色付けられています.数は少ないですがレーブグラフのノードに対応するピクセルも異なる色が割り当てられます.
psiclone.visualization.draw_graph_markers()
draw_graph_markers()
関数は入力データ空間の座標でレーブグラフを描画します.以下の例では,入力データ空間の座標に合わせるために,デフォルトの整数座標ではなく描画する座標範囲を指定していることに注意してください.
vis.draw_bitmap(
geom,# 入力データ空間の座標と整合するよう extent を指定する
=[x0, x1, y0, y1],
extent
)=3, edge_color="y-", node_color="g*") vis.draw_graph_markers(psi2tree, lw
psiclone.visualization.draw_COT_labels()
draw_COT_labels()
関数は,レーブグラフの各エッジ周辺に,対応する COT ラベルを描画します.
vis.draw_partition(
psi2tree,=True,
bitmap# 入力データ空間の座標と整合するよう extent を指定する
=[numpy.amin(x), numpy.amax(x), numpy.amin(y), numpy.amax(y)],
extent
)=1)
vis.draw_graph_markers(psi2tree, lw vis.draw_COT_labels(psi2tree)
psiclone.visualization.draw_barcode()
draw_barcode()
関数は,入力データ空間から計算されたパーシステントホモロジーおよびレーブグラフのバーコード図を描画します.
vis.draw_barcode(psi2tree)
左から順に,
- サブレベルフィルトレーションパーシステントホモロジーのバーコード図
- スーパーレベルフィルトレーションパーシステントホモロジーのバーコード図
- レーブグラフのバーコード図
になっています.バーコード図とは,高さ付きグラフの各エッジを対応する高さのバーで描画したものです.
発展的な描画の方法
より高度な可視化をするための関数の使い方を見ていきます.まずは,少し入力データを複雑なものに変更します.
= -3.0, 3.0, -2.5, 2.5
x0, x1, y0, y1 = 129j, 129j # 分割幅は実数,整数の虚数倍は分割数の指定
dx, dy = numpy.mgrid[x0:x1:dx, y0:y1:dy]
x, y = x + y * 1j
z = numpy.abs(z) >= 1.0
flow_domain = ~flow_domain
obstacle_domain
= numpy.ma.masked_array(z, obstacle_domain)
z = z + 1.0 / z
W = numpy.zeros_like(x)
perturbation += 0.2 * y
perturbation += 0.3 * numpy.ma.log(numpy.abs(z - (2.0 + 2.0j)))
perturbation -= 0.3 * numpy.ma.log(numpy.abs(z - (2.0 - 1.5j)))
perturbation = numpy.imag(W) + flow_domain * perturbation
data
= numpy.ma.masked_array([x, y, data]).reshape((3, -1)).T
table = convert_array_to_geometry(table, obstacles=[(0, 0)], int_grid=False)
geom next(node for node in geom.nodes if node.geotype == 2).level = 0.0
= Psi2tree(geom, threshold=0.1)
psi2tree
psi2tree.compute()
vis.draw_bitmap(geom)
plt.title(psi2tree.get_tree().convert(to_str)) plt.show()
psiclone.visualization.get_partition_data()
get_partition_data()
関数はレーブグラフの同値類分割の情報を取得します.これは draw_partition()
に対応したデータで,partition
, vmin
, vmax
の三つ組です.partition
は入力データ空間の高さ level
値に由来する分類値のリストになっています.
= vis.get_partition_data(psi2tree)
partition, vmin, vmax
# flow_domain を転置 (ij-indexing) して描画
="lower", cmap="gray")
plt.imshow(flow_domain.T, origin=partition, vmin=vmin, vmax=vmax) vis.draw_bitmap(geom, node_color
ここでは Psi2tree
が一様流れの計算を行ったため,入力データ空間 geom
に level
値
vmin
と vmax
は,partition
ではなく元の入力データの有界な level
値の中での最小値および最大値です.外側境界までトポロジカルに近いところに最小値・最大値があると partition
の中に vmin
, vmax
が含まれないことがあります.元のデータと整合するカラーレンジでパーティション図を描画したい場合は vmin
と vmax
を適切に利用するのが良いでしょう.
vmin, numpy.amin(data), vmax, numpy.amax(data)
(np.float64(-2.763353687123051),
np.float64(-2.7529496539518234),
np.float64(2.763353687123051),
np.float64(2.763353687123051))
また,ノイズ除去の影響でパーティションには「どこにも分類されない部分」が含まれます.これは partition
の中では None
として表現されます.
# None の個数をカウント
len([i for i, x in enumerate(partition) if x is None])
5
discrete=True
を指定すると,入力データの level
値の代わりに新たに割り振る整数値によって同値類の分割を表現します.この場合,partition
は optional int
値のリストになります.
="lower", cmap="gray")
plt.imshow(flow_domain.T, origin= vis.get_partition_data(psi2tree, discrete=True)
partition, vmin, vmax =partition, vmin=vmin, vmax=vmax) vis.draw_bitmap(geom, node_color
psiclone.visualization.get_COT_constructions()
get_COT_constructions()
関数は,COT 表現の再帰的構成と対応するレーブグラフ同値類の情報を取得します.COT ノードオブジェクトをキーとして,同値類の情報が格納された辞書式リストが得られます.
= vis.get_COT_constructions(psi2tree)
constructions
for cot_node, partition in constructions.items():
print(f"{cot_node.label()}: key={cot_node}\tvalue={numpy.array(partition)}")
a0: key=<psiclone.cot.TreeA0 object at 0x7f6d28dfaff0> value=[3 3 3 ... 4 2 0]
a+: key=<psiclone.cot.TreeA object at 0x7f6d28dfbf20> value=[1 1 1 ... None 0 None]
s+: key=<psiclone.cot.Genus object at 0x7f6d2b4a2ab0> value=[None None None ... None None None]
a2: key=<psiclone.cot.TreeA2 object at 0x7f6d290fe6c0> value=[None None None ... 0 None None]
a-: key=<psiclone.cot.TreeA object at 0x7f6d290bfd40> value=[None None None ... 0 None None]
s-: key=<psiclone.cot.Genus object at 0x7f6d28e75070> value=[None None None ... None None None]
各同値類は,入力データ空間のノード配列と対応するサイズ・並びの optional float
値のリストです.
= next(iter(constructions.keys()))
cot_node len(constructions[cot_node]) == len(geom.nodes)
True
これらのリストは,draw_bitmap
または draw_geometry
の node_color
オプションに渡すことができます.
for cot_node, partition in constructions.items():
= cot_node.label(tex=True)
tex =f"${tex}$", node_color=partition)
vis.draw_bitmap(geom, title plt.show()
psiclone.visualization.get_graph_markers_data()
get_graph_markers_data()
関数は,レーブグラフを入力データ空間の座標で描画するための情報を取得します.これは draw_graph_markers()
関数に対応したデータで,lines
, points
の二つ組です.
= vis.get_graph_markers_data(psi2tree)
lines, points
=True, extent=[x0, x1, y0, y1])
vis.draw_partition(psi2tree, bitmapfor line in lines:
0], line[1], "black", lw=1) plt.plot(line[
=True, extent=[x0, x1, y0, y1])
vis.draw_partition(psi2tree, bitmap0], points[:, 1], s=150, c="black", marker="*") plt.scatter(points[:,
psiclone.visualization.get_barcode_data()
get_barcode_data()
関数はバーコードの情報を取得します.これは draw_barcode()
に対応したデータで,文字列 ‘sub’, ‘sup’, ‘reeb’ をキーとする辞書式リストが得られます.これはそれぞれ,サブレベルパーシステントホモロジー,スーパーレベルパーシステントホモロジー,レーブグラフに対応するバーコードを表現します.各バーコードは,配列 x
, y0
, y1
の三つ組で構成されています.x
はバーの系列番号,y0
はバーの下限値,y1
はバーの上限値です.
vis.get_barcode_data(psi2tree)
{'sub': (array([0, 1]),
array([-2.75294965, 0.55322506]),
array([2.76335369, 1.08143079])),
'sup': (array([ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
array([-0.02617826, 0.00358474, 0.01381636, 0.02492754, 0.03030266,
0.03121934, 0.05834316, 0.06569003, 0.06629733, 0.07192172,
0.07369314, 0.07440814, 0.07892039, 2.76335369]),
array([-0.50588341, -2.75294965, -2.75294965, -2.75294965, -2.75294965,
-2.75294965, 0.02752536, 0.06562705, 0.02152453, 0.03687794,
0.05984956, 0.04302202, 0.05388596, -2.75294965])),
'reeb': (array([16, 17, 18, 19, 20, 21]),
array([-2.75294965, -0.50588341, -0.50588341, 0. , 0.55322506,
1.08143079]),
array([-0.50588341, -0.02617826, 0. , 1.08143079, 1.08143079,
2.76335369]))}
= vis.get_barcode_data(psi2tree)["reeb"]
x, y_bottom, y_top
- y_bottom, bottom=y_bottom) plt.bar(x, y_top