fullseye

物理PDE・人工生命・トモグラフィ・3D — 使い方ガイド

この族は何をする道具箱か

この族は、画像を「絵」ではなく 力学系・物理場・投影データ として扱う op を集めた道具箱です。入力の 2-D 画像を PDE(偏微分方程式)の初期条件とみなして数ステップ積分する物理フロー(ph_*: 熱拡散・Perona-Malik・平均曲率流・TV 流)、パターン形成モデルの初期状態とみなす生成 op(alife_*: 反応拡散・セルオートマトン・励起媒質・Lenia・砂山)、そして CT のサイノグラム(生投影) とみなして再構成する断層撮影 op(tm_*: Radon 前方投影 / フィルタ補正逆投影 / 代数再構成)が中心です。加えて、scipy.ndimage が N 次元であることを活かした 3-D ボリューム op(vol_*: CT/MRI/深度スタックの平滑・モルフォロジ・しきい値・投影)、進化探索が発見したチャンピオン pipeline を 1 op に凍結した マクロ op(macro_*)、中央走査線の暗バーを数える バーコード(decode_barcode)が入っています。

用途をひとことで言うと「ノイズ除去・エッジ保存平滑(physics/macro)」「テクスチャ・模様の生成(alife)」「疎ビュー/限定角 CT の再構成(tomography)」「3-D スタックの前処理と投影(3d)」です。入出力の型は op ごとに異なり、image→image が大半ですが、断層の前方投影は image(スライス)→image(サイノグラム)、ボリューム投影は volume→image、macro_edge は image→region、decode_barcode は image→feature(スカラ)になります。挙動はすべて決定的(同一入力→ビット一致)で、examples/gallery2d_physics_alife_3d.py が全 op を叩いて契約(有限・型・[0,1]・決定性)と既知挙動 GT を検証済みです。

代表的なパイプライン(op の繋がり)

断層撮影は「スライス→サイノグラム→スライス」のデータ変換が明確な連鎖です。

flowchart LR
  slice["スライス画像<br/>(image)"] -->|tm_radon_forward| sino["サイノグラム<br/>(image: 行=角度, 列=検出器)"]
  sino -->|tm_sinogram_denoise| sino2["平滑サイノグラム<br/>(image)"]
  sino2 -->|tm_fbp_reconstruct| rec["再構成スライス<br/>(image)"]
  sino2 -->|tm_backproject_unfiltered| blur["ぼけた逆投影<br/>(image, 対照)"]
  sino2 -->|tm_sart_reconstruct| rec2["代数再構成<br/>(image)"]

PDE 平滑と 3-D 前処理は「場を整えてから測る/投影する」連鎖になります。

flowchart LR
  noisy["ノイズ画像<br/>(image)"] -->|ph_perona_malik / ph_total_variation_flow| clean["エッジ保存平滑<br/>(image)"]
  clean -->|macro_edge| reg["エッジ領域<br/>(region 0/1)"]
  vol["ボリューム<br/>(volume)"] -->|vol_gaussian| vols["平滑ボリューム<br/>(volume)"]
  vols -->|vol_threshold| volb["二値ボリューム<br/>(volume)"]
  volb -->|vol_mip| proj["最大値投影<br/>(image)"]

使い方(op グループ別)

2-D image op は「1 画像 + 2 つのつまみ a,b∈[0,1]」で呼びます: fullseye.apply(img, "name", a, b)。volume op は 3-D 配列を同じ形で渡します: fullseye.apply(vol, "name", a, b)

物理 PDE フロー(ph_*, image→image)

人工生命 / 生成場(alife_*, image→image)

正直な限界: alife_* は「フィルタ」ではなく 生成 op です。入力を初期条件としてのみ消費し、その後は自前の力学を固定ステップ回します。平坦/一様な入力では対称性が破れず、いくつかの op は平坦な出力を返します(モデルの正しい挙動であって失敗ではありません)。

断層撮影(tm_*, image↔サイノグラム)

3-D ボリューム(vol_*, volume→volume|image)

マクロ(進化チャンピオンの凍結 op)

進化探索が発見した pipeline を名前固定ステージ列として凍結した「DNA op」。a,b は凍結済み(不使用)で、ステージを逐次実行するのとビット一致します。

バーコード

動く最小例(検証済み gallery2d_physics_alife_3d から)

repo 直下で py -3.11 <file>.py として実行できます(examples/gallery2d_physics_alife_3d.py の検証済みコードを土台に短縮)。

# -*- coding: utf-8 -*-
"""物理PDE・トモグラフィ・3D・alife・バーコード族の最小 GT 例(repo 直下で実行)。"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[0]))  # repo root

import numpy as np
import fullseye
import ops

# --- 構造のある 2-D 画像とボリュームを合成(examples と同じ作り) --------------- #
n = 48
yy, xx = np.mgrid[0:n, 0:n].astype(np.float64)
grad = xx / (n - 1)
disk = ((yy - n * 0.35) ** 2 + (xx - n * 0.4) ** 2) < (n * 0.18) ** 2
noise = 0.03 * np.random.default_rng(20260812).standard_normal((n, n))
img = np.clip(0.35 * grad + 0.45 * disk + noise, 0.0, 1.0)

zz, vy, vx = np.mgrid[0:8, 0:24, 0:24]
vol = np.clip(0.5 + 0.3 * np.sin(vx / 3.0) * np.cos(vy / 4.0) * (zz / 8.0), 0.0, 1.0)

# --- GT1: 熱拡散は分散を下げる(ぼかし)。null=何もしない は分散を保つ ---------- #
heat = fullseye.apply(img, "ph_heat_flow", 1.0, 0.0)
assert heat.shape == img.shape and np.isfinite(heat).all()
assert heat.var() < img.var() * 0.9, (heat.var(), img.var())

# --- GT2: サイノグラム角度方向平滑は隣接行差を下げる ---------------------------- #
sino = fullseye.apply(img, "tm_radon_forward", 0.7, 1.0)      # image -> サイノグラム
den = fullseye.apply(sino, "tm_sinogram_denoise", 1.0, 0.0)   # 角度軸(行)平滑
d_before = float(np.abs(np.diff(sino, axis=0)).mean())
d_after = float(np.abs(np.diff(den, axis=0)).mean())
assert d_after < d_before, (d_after, d_before)
rec = fullseye.apply(sino, "tm_fbp_reconstruct", 0.0, 0.3)    # FBP 再構成
assert rec.shape == img.shape and np.isfinite(rec).all()

# --- GT3: vol_threshold は厳密二値・両クラスを含む / vol_mip は 2-D 画像 ------- #
vt = fullseye.apply(vol, "vol_threshold", 0.5, 0.5)
assert set(np.unique(vt).tolist()) <= {0.0, 1.0}
assert vt.min() == 0.0 and vt.max() == 1.0
mip = fullseye.apply(vol, "vol_mip", 0.0, 0.0)
assert mip.ndim == 2 and 0.0 - 1e-9 <= mip.min() and mip.max() <= 1.0 + 1e-9

# --- GT4: decode_barcode は仕込んだ 5 本を正確に数える(a=0.5 -> 暗閾値 0.5) --- #
bar = np.ones((10, 40), np.float64)
for k in range(5):
    s = 3 + k * 7
    bar[5, s:s + 3] = 0.0
cnt = float(fullseye.apply(bar, "decode_barcode", 0.5, 0.0))
assert cnt == 5.0, cnt

# --- GT5: macro_edge は構造画像を 2 クラス、平坦画像を 1 クラスに(エッジのみ分割) - #
me = fullseye.apply(img, "macro_edge", 0.5, 0.5)
assert set(np.unique(me).tolist()) <= {0.0, 1.0} and np.unique(me).size == 2
flat = fullseye.apply(np.full((n, n), 0.42), "macro_edge", 0.5, 0.5)
assert np.unique(flat).size == 1

# --- 決定性: 同一入力 -> ビット一致(alife も含め全 op が満たす契約) ---------- #
assert np.array_equal(fullseye.apply(img, "alife_life_step", 0.0, 0.5),
                      fullseye.apply(img, "alife_life_step", 0.0, 0.5))

print("PASS")

数式(必要な op のみ)

熱方程式 / 等方拡散(ph_heat_flow)は 5 点ラプラシアンの陽解法(FTCS):

\[I_t = \nabla^2 I,\qquad I^{k+1} = I^k + \Delta t\,\big(I_{i-1,j}+I_{i+1,j}+I_{i,j-1}+I_{i,j+1}-4I_{i,j}\big).\]

Perona-Malik 異方性拡散(ph_perona_malik)は伝導度 $g$ でエッジを保つ:

\[I_t = \operatorname{div}\!\big(g(|\nabla I|)\,\nabla I\big),\qquad g(s) = \frac{1}{1+(s/k)^2}.\]

平均曲率流(ph_mean_curvature_motion / alife_curvature_flow)は数値安定な代数形で:

\[I_t = |\nabla I|\,\operatorname{div}\!\frac{\nabla I}{|\nabla I|} = \frac{I_{xx}I_y^2 - 2I_xI_yI_{xy} + I_{yy}I_x^2}{I_x^2 + I_y^2 + \varepsilon}.\]

Total Variation(ROF)流(ph_total_variation_flow)はエネルギー $\mathrm{TV}(I) + \tfrac{\lambda}{2}\lVert I-I_0\rVert^2$ の勾配降下:

\[I_t = \operatorname{div}\!\frac{\nabla I}{|\nabla I|} - \lambda\,(I - I_0).\]

Gray-Scott 反応拡散(ph_reaction_diffusion / alife_gray_scott)は 2 種 $u,v$ の連立:

\[u_t = D_u\nabla^2 u - uv^2 + F(1-u),\qquad v_t = D_v\nabla^2 v + uv^2 - (F+\kappa)v.\]

FBP(tm_fbp_reconstruct)は各角度 $\theta$ の投影 $p_\theta$ をランプフィルタ後に逆投影:

\[f(x,y) = \int_0^\pi \big(p_\theta * h\big)\big(x\cos\theta + y\sin\theta\big)\,d\theta,\qquad \hat h(\omega) = |\omega|.\]

サンプルデータ

../../SAMPLES.md を参照。この族のデバッグには 2-D の合成画像 checker_noisy / blobs(import sample_images; sample_images.load("blobs"))や skimage.datacoins / cell(PDE 平滑・断層の入力スライスに好適)が使えます。3-D vol_* の検証には open-scivis(open-scivis-datasets、SAMPLES.md 参照)のボリュームや、上例のような合成ボリュームを使います。

参考文献(正典)

台帳 ../../../REFERENCES.md。この族のアルゴリズムの古典:

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.