TECHNOLOGY

VTKの3次元データを断面抽出してmatplotlibで画像化する手順【Python、スカラー値】

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データから断面を抽出するために平面を定義してスライス処理を行います。平面の定義とスライス処理にはvtkPlanevtkCutterを使用します。vtkCuttervtkDataSetクラスを継承したクラスに使用することができます。

以下のコードは、(0, 0, 25)を通るZ軸に垂直な平面を定義しています。そして、3Dデータdatasetに対してスライス処理を行った結果をcut_outputvtkPolyData型になることが多い)に格納しています。

# === スライス平面を定義 ===
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)

GetPointsvtkDataSetを継承したクラスで使用でき、ポリゴンデータの点群(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の配列に変換する

scipymatplotlibなどの他のライブラリを使いやすくするために、データを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)

返り値はnumpyndarrayになります。引数に与えたものがスカラーであれば、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補間方法の選択(linearnearestcubicなど)

返り値として、(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ファイルの読み込み
  • vtkPlanevtkCutter を用いた任意平面でのスライス処理
  • GetPointData() / GetCellData() によるスカラー値の取得方法
  • NumPy・SciPyを用いたデータの補間と、matplotlibによる画像化

点データとセルデータで処理が異なる点や、補間の必要性など、実務でも役立つ知識が得られたかと思います。今後は、複数断面の自動生成や、時間発展データへの応用もぜひ試してみてください。

-TECHNOLOGY
-,