
VTK形式の三次元データには、多くの情報が含まれています。これらを視覚的に理解するためには、断面を抽出し、そのスカラー値をカラーマップとして表示することが有効です。
通常、このような処理はParaViewなどのGUIツールで行われますが、Pythonを用いたスクリプトを使えば、VTK形式のファイルの読み込みから断面データの画像化までを自動化できます。これは、複数の断面を見たい場合や何度も結果を見直さなければならない場合に便利です。
本記事では、VTK、numpy、matplotlibの組み合わせにより、3次元データから断面を切り出してカラーマップで画像化する手順を解説します。
目次
可視化の流れ
可視化するための処理の流れを以下に示します。
ステップ | 内容 | 使用するライブラリやクラス |
---|---|---|
データの読み込み | VTKファイル(.vtk)を読み込み、3Dデータとして取得 | vtk.vtkDataSetReader |
スライス平面の設定 | 任意の位置・方向の平面を定義 | vtk.vtkPlane |
断面の抽出 | 平面で3Dデータをカットし、スライス面の情報を取得 | vtk.vtkCutter |
スカラー値の取得 | スライス面上の点やセルのスカラー値を取得 | vtk_to_numpy , GetPointData() または GetCellData() |
補間処理 | 点データを構造格子状の2Dデータになるように補間 | scipy.interpolate.griddata |
可視化と保存 | matplotlibでカラーマップ画像として描画・保存 | matplotlib.pyplot.imshow() |
必要なパッケージのインストール
本記事で使用するパッケージは以下になります。pip
コマンドにより、これらのパッケージをインストールします。
pip install vtk
pip install numpy
pip install scipy
pip install matplotlib
具体的な処理
ファイルの読み込み
まず、VTK形式のファイル(.vtk)をPythonで読み込んで、3Dデータとして使えるようにします。
# === 読み込み部分 ===
reader = vtk.vtkDataSetReader() # インスタンスの作成
reader.SetFileName(filename) # 読み込むファイルのPATHを指定
reader.Update() # 実際にファイルを読み込む
dataset = reader.GetOutput() # 読み込んだ3Dデータ(点、セル、スカラー値など)を含むオブジェクトを取り出す
vtkDataSetReader
はVTK形式のファイル(.vtk)の中でもvtkDataSet
を継承している型のデータを読み込むための専用クラスです。例えば、vtkPolyData
(ポリゴンデータ)やvtkStructuredGrid
(構造格子データ)、vtkUnstructuredGrid
(非構造格子データ)などがあります。
データを読み込んだ後、GetOutput
でオブジェクトを取り出しています。このオブジェクトはvtkDataSet
を継承しているクラスのオブジェクトになります。
スライス平面の定義とスライス処理
3Dデータから断面を抽出するために平面を定義してスライス処理を行います。平面の定義とスライス処理にはvtkPlane
とvtkCutter
を使用します。vtkCutter
はvtkDataSet
クラスを継承したクラスに使用することができます。
以下のコードは、(0, 0, 25)
を通るZ軸に垂直な平面を定義しています。そして、3Dデータdataset
に対してスライス処理を行った結果をcut_output
(vtkPolyData
型になることが多い)に格納しています。
# === スライス平面を定義 ===
plane = vtk.vtkPlane() # インスタンスの作成
plane.SetOrigin(0, 0, 25.0) # 平面が通る点(X,Y,Z)
plane.SetNormal(0, 0, 1) # 平面の法線ベクトル(Nx, Ny, Nz)
# === スライスを作成 ===
cutter = vtk.vtkCutter() # インスタンスの作成
cutter.SetCutFunction(plane) # スライスに使う平面を指定
cutter.SetInputData(dataset) # スライスされるデータを指定
cutter.Update() # スライス処理を実行
cut_output = cutter.GetOutput() # スライスした結果をオブジェクトとして取り出す
スライス処理のより詳しい説明はこちらの記事をご参照ください。
座標とスカラー値の取得
スライスにより得られた断面のデータから、座標とスカラー値を取得します。節点(points)とセル(cell)のどちらの上でスカラー値が定義されているかで取得方法が異なります。
節点上にデータが定義されている場合
節点上にデータが定義されている場合には、GetPoints
メソッドで座標配列にアクセスし、GetPointData
メソッドによりフィールド(スカラーやベクトル)にアクセスします。
points = cut_output.GetPoints().GetData()
scalars = cut_output.GetPointData().GetArray(field_name)
GetPoints
はvtkDataSet
を継承したクラスで使用でき、ポリゴンデータの点群(vtkPoints
オブジェクト)を返します。このvtkPoints
オブジェクトのGetData
メソッドを使用することにより、点群の座標をもつ配列データ(vtkFloatArray
)を取得できます。
また、GetPointData
メソッドは、点に付随するフィールドを取り出すためのインターフェイスで、GetArray(field_name)
により指定された名前のフィールドの配列データ(vtkDataArray
)を取得します。
セル上にデータが定義されている場合
セル上にデータが定義されている場合には、GetPoints
で取得できるのはあくまで「点(ノード)」の情報なので、セルデータを使う場合にはそのセルの中心座標を取得する必要があります。セル中心座標(重心)は、vtkCellcenters
クラスを使用して計算します。
cell_centers = vtk.vtkCellCenters() # インスタンスを作成
cell_centers.SetInputData(cut_output) # セル中心を計算したいデータを指定
cell_centers.Update() # セルの重心を計算
重心を計算した後は、GetPoints
メソッドで座標配列にアクセスし、GeCelllData
メソッドによりフィールド値にアクセスします。
points = cell_centers.GetOutput().GetPoints().GetData()
scalars = cut_output.GetCellData().GetArray(field_name)
GetCellData
メソッドは、セルに付随するフィールドを取り出すためのインターフェイスで、GetArray(field_name)
により指定された名前のフィールドの配列データ(vtkDataArray
)を取得できます。
vtkの配列をnumpyの配列に変換する
scipy
やmatplotlib
などの他のライブラリを使いやすくするために、データをnumpy
配列に変換します。VTKの配列をnumpyの配列に変換する方法として、vtk.util.numpy_support.vtk_to_numpy
があります。この関数はvtkDataArray
の派生クラスに使用できます。
coords = numpy_support.vtk_to_numpy(points)
values = numpy_support.vtk_to_numpy(scalars)
返り値はnumpy
のndarray
になります。引数に与えたものがスカラーであれば、shape=(点数)
の配列を返し、N次元のベクトルであればshape=(点数、N)
の配列を返します。
データの補間
VTKから得られる点データは一般的にはバラバラな位置にあるので、それを整った格子(グリッド)に変換するために補間を使用します。具体的には次の理由から補間を行います。
理由 | 説明 |
---|---|
点の位置が不規則だから | VTKのスライス結果は非構造データ(ばらばらな点の集まり)になることが多いが、matplotlibのimshow など、いくつかの関数では整った格子状のデータ(構造格子)が必要になる。 |
カラーマップ表示に必要だから | 色でスカラー値を表示するには、「各ピクセルが1つの値」をもつ必要がある。そのため、等間隔な2Dデータに変換する。 |
見た目をなめらかにするため | 点のままだとぽつぽつした表示になるが、補完すると、連続的な分布として視覚的にきれいになる。 |
scipyのscipy.interpolate.griddata
を使用すると、不規則な2次元の点データを構造格子状の2次元データになるように補間できます。
from scipy.interpolate import griddata
grid_z = griddata((x, y), values, (grid_x, grid_y), method="linear")
ここで、griddata
の各引数は次の意味があります。
引数 | 説明 |
---|---|
(x, y) | 不規則な点群の座標(スライス上の点) |
values | それぞれの点上のスカラー値(x, y 上の値) |
(grid_x, grid_y) | 整ったグリッドの座標 |
method | 補間方法の選択(linear 、nearest 、cubic など) |
返り値として、(gird_x, grid_y)
上の補間値がgrid_z
に格納されます。
ここでは、次のように線形補間を行います。
# スライス面の座標を取得
x, y, z = coords[:, 0], coords[:, 1], coords[:, 2]
# グリッドの解像度を指定
grid_x, grid_y = np.mgrid[min(x) : max(x) : 200j, min(y) : max(y) : 200j]
grid_z = griddata((x, y), values, (grid_x, grid_y), method="linear")
numpyのmgrid
メソッドにより、x軸とy軸のそれぞれについて最大座標と最小座標の間を200分割して、その各点の座標を(grid_x, grid_y)
に格納した後、griddata
により線形補間を行っています。
カラーマップの作成と保存
numpyで補間した断面のスカラー値を、matplotlibを使ってカラーマップ画像として保存します。カラーマップを作成するために、2次元配列を画像として表示するimshow
を使用します。
import matplotlib.pyplot as plt
plt.figure(figsize=(6, 5)) # 描画ウィンドウを作成。サイズを幅6インチ、高さ5インチに設定
plt.imshow(
grid_z.T, # カラーマップの値。imshowは行=Y、列=Xとして扱うので転置
extent=(min(x), max(x), min(y), max(y)), # 座標のスケーリングを元の空間のx-yに合わせる
origin="lower", # 座標の原点を左下にする(デフォルトは左上)
cmap="viridis", # 色のグラデーションを指定
aspect="equal", # XとYを同じ単位にする(アスペクト比=1)
)
plt.colorbar(label="Scalar Value") # カラーバーのラベルの設定
plt.title("Interpolated Slice as Color Map") # 図のタイトル
plt.xlabel("X") # 横軸のラベル
plt.ylabel("Y") # 縦軸のラベル
plt.tight_layout() # ラベルやタイトルがはみ出ないようにレイアウトを自動調整する
plt.savefig("interpolated_slice.png", dpi=300) # 解像度を300dpiにしてpng画像として保存する
plt.show() # 画面に画像を表示する
これでカラーマップを表示できます。
サンプルプログラム
テスト用のVTKファイルの作成
テスト用のVTKを作成するためのスクリプトを示します。実行するとsample.vtk
ファイルが作成されます。
field_position
を変えることで、スカラー値の定義位置を変更できます。point
とした場合には点(ノード)上に定義され、cell
とした場合にはセル上に定義されます。
import vtk
import numpy as np
# データ定義位置(point or cell)
field_position = "point"
# field_position = "cell"
# 構造格子のサイズ
nx, ny, nz = 50, 50, 50
# スカラー値(例:中心からの距離の2乗)
scalars = np.zeros((nx, ny, nz))
for k in range(nz):
for j in range(ny):
for i in range(nx):
x = i - nx / 2
y = j - ny / 2
z = k - nz / 2
scalars[i, j, k] = x**2 + y**2 + z**2
# vtkImageDataに変換
image_data = vtk.vtkImageData()
if field_position == "point":
image_data.SetDimensions(nx, ny, nz)
else:
image_data.SetDimensions(nx + 1, ny + 1, nz + 1) # セル数より1点多くなることに注意
image_data.SetSpacing(1.0, 1.0, 1.0)
# VTK用スカラー配列に変換
flat_scalars = scalars.ravel(order="F") # VTKはFortran順
vtk_array = vtk.vtkFloatArray()
vtk_array.SetName("Distance2")
vtk_array.SetNumberOfValues(len(flat_scalars))
for i, val in enumerate(flat_scalars):
vtk_array.SetValue(i, val)
# スカラーをセット
if field_position == "point":
image_data.GetPointData().SetScalars(vtk_array)
else:
image_data.GetCellData().SetScalars(vtk_array)
# ファイルに保存
writer = vtk.vtkStructuredPointsWriter()
writer.SetFileName("sample.vtk")
writer.SetInputData(image_data)
writer.Write()
断面のカラーマップ画像の表示
カラーマップを作成するスクリプトを示します。field_position
にはVTKファイルを作成したときと同じ値を設定します。
import vtk
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
from vtk.util import numpy_support
# VTKファイルのパスを指定
filename = "sample.vtk"
# フィールド名を指定(可視化するデータの名前)
field_name = "Distance2"
# フィールドの定義点を指定(各点にデータがあるのでpointを指定)
field_position = "point"
# field_position = "cell"
# === 読み込み部分 ===
reader = vtk.vtkDataSetReader() # インスタンスの作成
reader.SetFileName(filename) # 読み込むファイルのPATHを指定
reader.Update() # 実際にファイルを読み込む
dataset = (
reader.GetOutput()
) # 読み込んだ3Dデータ(点、セル、スカラー値など)を含むオブジェクトを取り出す
# === スライス平面を定義 ===
plane = vtk.vtkPlane() # インスタンスの作成
plane.SetOrigin(0, 0, 25.0) # 平面が通る点(X,Y,Z)
plane.SetNormal(0, 0, 1) # 平面の法線ベクトル(Nx, Ny, Nz)
# === スライスを作成 ===
cutter = vtk.vtkCutter() # インスタンスの作成
cutter.SetCutFunction(plane) # スライスに使う平面を指定
cutter.SetInputData(dataset) # スライスされるデータを指定
cutter.Update() # スライス処理を実行
cut_output = cutter.GetOutput() # スライスした結果をオブジェクトとして取り出す
# === スライス結果から座標とスカラー値を取得 ===
if field_position == "cell":
cell_centers = vtk.vtkCellCenters() # インスタンスを作成
cell_centers.SetInputData(cut_output) # セル中心を計算したいデータを指定
cell_centers.Update() # セルの重心を計算
points = cell_centers.GetOutput().GetPoints().GetData()
scalars = cut_output.GetCellData().GetArray(field_name)
elif field_position == "point":
points = cut_output.GetPoints().GetData()
scalars = cut_output.GetPointData().GetArray(field_name)
else:
assert 0
if points is None or scalars is None:
raise ValueError("スライス結果が空です。平面の位置を確認してください。")
# NumPy に変換
coords = numpy_support.vtk_to_numpy(points)
values = numpy_support.vtk_to_numpy(scalars)
# 座標を取得
x, y, z = coords[:, 0], coords[:, 1], coords[:, 2]
# グリッドの解像度を指定して線形補間、構造格子状のデータに整形
grid_x, grid_y = np.mgrid[min(x) : max(x) : 200j, min(y) : max(y) : 200j]
grid_z = griddata((x, y), values, (grid_x, grid_y), method="linear")
# === カラーマップとして描画 ===
plt.figure(figsize=(6, 5)) # 描画ウィンドウを作成。サイズを幅6インチ、高さ5インチに設定
plt.imshow(
grid_z.T, # カラーマップの値。imshowは行=Y、列=Xとして扱うので転置
extent=(
min(x),
max(x),
min(y),
max(y),
), # 座標のスケーリングを元の空間のx-yに合わせる
origin="lower", # 座標の原点を左下にする(デフォルトは左上)
cmap="viridis", # 色のグラデーションを指定
aspect="equal", # XとYを同じ単位にする(アスペクト比=1)
)
plt.colorbar(label="Scalar Value") # カラーバーのラベルの設定
plt.title("Interpolated Slice as Color Map") # 図のタイトル
plt.xlabel("X") # 横軸のラベル
plt.ylabel("Y") # 縦軸のラベル
plt.tight_layout() # ラベルやタイトルがはみ出ないようにレイアウトを自動調整する
plt.savefig("interpolated_slice.png", dpi=300) # png画像として保存
実行すると、次のような断面のスカラー値のカラーマップがPNG形式で作成されます。
まとめ
本記事では、VTK形式の3次元データから任意の断面を抽出し、スカラー値をカラーマップとして可視化する手順をPythonで実装しました。
特に以下のポイントを重点的に扱いました:
vtkDataSetReader
によるVTKファイルの読み込みvtkPlane
とvtkCutter
を用いた任意平面でのスライス処理GetPointData()
/GetCellData()
によるスカラー値の取得方法- NumPy・SciPyを用いたデータの補間と、matplotlibによる画像化
点データとセルデータで処理が異なる点や、補間の必要性など、実務でも役立つ知識が得られたかと思います。今後は、複数断面の自動生成や、時間発展データへの応用もぜひ試してみてください。