3D顕微鏡画像における異方性の推定#

このチュートリアルでは、3D画像の構造テンソルを計算します。3D画像処理の一般的な紹介については、3D画像(細胞)の探索を参照してください。ここで使用するデータは、共焦点蛍光顕微鏡によって得られた腎臓組織の画像からサンプリングされたものです(詳細については[1]kidney-tissue-fluorescence.tifを参照)。

import matplotlib.pyplot as plt
import numpy as np
import plotly.express as px
import plotly.io

from skimage import data, feature

画像の読み込み#

この生物医学画像は、scikit-imageのデータレジストリから入手できます。

3Dマルチチャネル画像の形状とサイズは正確にはどのくらいですか?

print(f'number of dimensions: {data.ndim}')
print(f'shape: {data.shape}')
print(f'dtype: {data.dtype}')
number of dimensions: 4
shape: (16, 512, 512, 3)
dtype: uint16

このチュートリアルの目的のために、2番目のカラーチャネルのみを考慮します。これにより、3Dシングルチャネル画像になります。値の範囲は?

n_plane, n_row, n_col, n_chan = data.shape
v_min, v_max = data[:, :, :, 1].min(), data[:, :, :, 1].max()
print(f'range: ({v_min}, {v_max})')
range: (68, 4095)

3D画像の中央スライスを可視化してみましょう。

fig1 = px.imshow(
    data[n_plane // 2, :, :, 1],
    zmin=v_min,
    zmax=v_max,
    labels={'x': 'col', 'y': 'row', 'color': 'intensity'},
)

plotly.io.show(fig1)

相対的なX-Y等方性を示す特定の領域を選択してみましょう。対照的に、勾配はZ方向にかなり異なり(そして、弱いです)。

sample = data[5:13, 380:410, 370:400, 1]
step = 3
cols = sample.shape[0] // step + 1
_, axes = plt.subplots(nrows=1, ncols=cols, figsize=(16, 8))

for it, (ax, image) in enumerate(zip(axes.flatten(), sample[::step])):
    ax.imshow(image, cmap='gray', vmin=v_min, vmax=v_max)
    ax.set_title(f'Plane = {5 + it * step}')
    ax.set_xticks([])
    ax.set_yticks([])
Plane = 5, Plane = 8, Plane = 11

3Dでサンプルデータを表示するには、次のコードを実行します。

import plotly.graph_objects as go

(n_Z, n_Y, n_X) = sample.shape
Z, Y, X = np.mgrid[:n_Z, :n_Y, :n_X]

fig = go.Figure(
    data=go.Volume(
        x=X.flatten(),
        y=Y.flatten(),
        z=Z.flatten(),
        value=sample.flatten(),
        opacity=0.5,
        slices_z=dict(show=True, locations=[4])
    )
)
fig.show()

構造テンソルの計算#

サンプルデータの下部スライスを可視化し、大きな変化の典型的なサイズを決定しましょう。このサイズをウィンドウ関数の「幅」として使用します。

fig2 = px.imshow(
    sample[0, :, :],
    zmin=v_min,
    zmax=v_max,
    labels={'x': 'col', 'y': 'row', 'color': 'intensity'},
    title='Interactive view of bottom slice of sample data.',
)

plotly.io.show(fig2)

最も明るい領域(つまり、行〜22、列〜17)について、列(それぞれ行)全体で2〜3(それぞれ1〜2)ピクセルにわたる変化(したがって、強い勾配)が見られます。したがって、ウィンドウ関数に対してsigma = 1.5を選択できます。あるいは、軸ごとにsigmaを渡すこともできます(例:sigma = (1, 2, 3))。サイズは8(13 - 5)なので、最初の(Z、平面)軸に沿ってサイズ1は妥当です。X-Z平面またはY-Z平面のスライスを見ると、妥当であることがわかります。

次に、構造テンソルの固有値を計算できます。

(3, 8, 30, 30)

最大の固有値はどこにありますか?

coords = np.unravel_index(eigen.argmax(), eigen.shape)
assert coords[0] == 0  # by definition
coords
(np.int64(0), np.int64(1), np.int64(22), np.int64(16))

注記

読者は、この結果(座標(plane, row, column) = coords[1:])がsigmaの変更に対してどの程度堅牢であるかを確認できます。

最大の固有値が見つかったX-Y平面(つまり、Z = coords[1])における固有値の空間分布を見てみましょう。

fig3 = px.imshow(
    eigen[:, coords[1], :, :],
    facet_col=0,
    labels={'x': 'col', 'y': 'row', 'facet_col': 'rank'},
    title=f'Eigenvalues for plane Z = {coords[1]}.',
)

plotly.io.show(fig3)

局所的な特性を見ています。上記のX-Y平面で最大の固有値の周りの小さな近傍を考えてみましょう。

eigen[0, coords[1], coords[2] - 2 : coords[2] + 1, coords[3] - 2 : coords[3] + 1]
array([[0.05530323, 0.05929082, 0.06043806],
       [0.05922725, 0.06268274, 0.06354238],
       [0.06190861, 0.06685075, 0.06840962]])

この近傍で2番目に大きい固有値を調べると、それらは最大の固有値と同じ程度の大きさであることがわかります。

eigen[1, coords[1], coords[2] - 2 : coords[2] + 1, coords[3] - 2 : coords[3] + 1]
array([[0.03577746, 0.03577334, 0.03447714],
       [0.03819524, 0.04172036, 0.04323701],
       [0.03139592, 0.03587025, 0.03913327]])

対照的に、3番目に大きい固有値は1桁小さくなります。

eigen[2, coords[1], coords[2] - 2 : coords[2] + 1, coords[3] - 2 : coords[3] + 1]
array([[0.00337661, 0.00306529, 0.00276288],
       [0.0041869 , 0.00397519, 0.00375595],
       [0.00479742, 0.00462116, 0.00442455]])

最大の固有値が見つかったX-Y平面のサンプルデータのスライスを可視化してみましょう。

fig4 = px.imshow(
    sample[coords[1], :, :],
    zmin=v_min,
    zmax=v_max,
    labels={'x': 'col', 'y': 'row', 'color': 'intensity'},
    title=f'Interactive view of plane Z = {coords[1]}.',
)

plotly.io.show(fig4)

最大の固有値が見つかったX-Z(左)とY-Z(右)平面のサンプルデータのスライスを可視化してみましょう。Z軸は下図のサブプロットで垂直軸です。特にY-Z平面(longitudinal=1)では、Z軸に沿った予想される相対的な不変性(腎臓組織の長軸構造に対応)が見られます。

subplots = np.dstack((sample[:, coords[2], :], sample[:, :, coords[3]]))
fig5 = px.imshow(
    subplots, zmin=v_min, zmax=v_max, facet_col=2, labels={'facet_col': 'longitudinal'}
)

plotly.io.show(fig5)

結論として、ボクセル(plane, row, column) = coords[1:]周辺領域は3次元的に異方性を持っています。最大固有値と第2固有値と比較して、第3固有値は桁違いに小さい値となっています。これは図Eigenvalues for plane Z = 1を一目見ただけでも明らかです。

問題の近傍領域は、平面内(ここではXY平面に近い)では「ある程度等方性」を持っています。最大固有値と第2固有値の比は2未満です。この記述は画像で確認できる状況、つまりある方向(ここでは行軸に近い方向)に強い勾配があり、それに垂直な方向には弱い勾配があるという状況と整合性があります。

3次元構造テンソルを楕円体で表現すると[2]、パンケーキのような形状になります。

スクリプトの実行時間合計:(0分5.435秒)

Sphinx-Galleryによって生成されたギャラリー