1 枚の画像から、明るい塊を点(keypoints)にし、高さを載せて 3-D の点群にし、点ごとのガウシアンにして体積(voxel)にし、等値面のメッシュや近傍のグラフへ渡します。逆向きも揃っていて、点群 → keypoints → 計数画像、体積の相関 → 位置 → 画像と戻れます。どの変換も軸の約束を op 名に書いてあり(keypoints_uv_to_points は (u, v) → (z, y, x))、往復できるものは bit 一致、できないものは何が落ちるかを opsreprconv.roundtrips() が宣言しています。
Turns the bright blobs of one image into keypoints, lifts them to a 3-D point cloud with a height, spreads each point into a Gaussian and samples a voxel volume, and hands that to an iso-surface mesh or a nearest-neighbour graph. The reverse direction exists as well: point cloud → keypoints → count image, and volume correlation → position → image. Every conversion spells its axis convention in its name (keypoints_uv_to_points is (u, v) → (z, y, x)); the reversible ones round-trip bit for bit, and opsreprconv.roundtrips() declares what the others lose.
向く: 「数える・位置を出す・つながりを見る・ずれを測る」を、それぞれ向いた型でやりたいとき(poc_bga_ball_type_roundtrip: 欠け 1 個の BGA 71 球を keypoints・メッシュのオイラー標数・グラフの 3 つで数えて全部 71、既知のずれは体積の相関のピークに厳密に立つ)。変換を挟んでも保たれるべき量(個数・質量・位相)で鎖の正しさを確かめたいとき。
向かない: ★くっついた塊を分けること —— 8 近傍の連結成分は融合した 2 点を 1 点にする(分けるなら先に watershed など)。★点群の列の流儀が分からないもの —— pointmap_to_points は (x, y, z)、points_zyx_to_keypoints_uv は (z, y, x) を期待し、取り違えても例外は出ない(poc_bga_ball_type_roundtrip で 15360 画素が左端の 4 列に積もった)。
keypoints_from_image2d → keypoints_uv_to_points → knn_graph → knn_graph_to_conn_graph → graph_components
画像の塊を点にし、z を載せて点群にし、k 近傍のグラフを隣接行列にして連結成分を数える。成分の数はラプラシアンの 0 固有値の数(graph_laplacian_spectrum)と独立に一致するはずで、それを門にする。体積へ渡すなら points_to_gaussians → gaussians_to_voxel(opsreprconv 版、opsreprconv.call で呼ぶ)→ voxel_to_mesh。
(row, col) の点は keypoints_rowcol_to_uv で (u, v) にしてから渡す。高さ画像を全画素の点群にするなら depth_to_organized_points → pointmap_to_points(列を (z, y, x) に反転してから)。体積の形は voxel_to_mesh → mesh_volume、つながりの別解は graph_laplacian_spectrum、点群の代表 1 点は points_to_position。
gaussians_to_voxel は ops3d(match3d、点群を受ける)と opsreprconv(gaussians を受ける)で同名の別物。fs.ledger は ops3d 版を返す。op は格子の添字で計算するので、画素・voxel の実寸は変換の外で持つ。gaussians_to_voxel には origin と spacing を必ず渡す(既定の刻み 1 は世界座標をそのまま添字にする)。高さ画像の正射の pointmap は x, y が画素、z が高さの単位なので、単位の混在に注意。
import numpy as np
import fullseye as fs
import opsreprconv
R = opsreprconv.call
img = np.zeros((40, 60))
truth = [(10, 10), (10, 20), (10, 30), (30, 40), (30, 50)] # (row, col)、2 つの群
for r, c in truth:
img[r, c] = 1.0
kp = R("keypoints_from_image2d", img) # (u, v)
pts = R("keypoints_uv_to_points", kp, z=0.0) # (z, y, x)
back = R("keypoints_to_image2d", R("points_zyx_to_keypoints_uv", pts), shape=img.shape)
idx, dist = fs.ledger.knn_graph(pts, k=1)
comp = fs.ledger.graph_components(R("knn_graph_to_conn_graph", (idx, dist), weight="binary"))
print(len(kp), "points; image round trip:", bool(np.array_equal(back, img)),
"; components:", int(comp.max()) + 1)