イメージング型 空間トランスクリプトーム:データ読み込みとデータ構造

Spatial transcriptome
📚 この記事について
イメージング型のデータを読み込み、中身を理解するところまでを扱います。出力に含まれる4つの要素、行列に入っている分子と入っていない分子の違い、そして座標で気をつけることを整理します。
🔙 前の記事:イメージング型 空間トランスクリプトーム:前処理の全体像
🔜 次の記事:イメージング型 空間トランスクリプトーム:転写産物レベルの QC
📌 前提AnnData のデータ構造ターミナルの基本操作入門

空間トランスクリプトーム解析(spatial transcriptomics)のうちイメージング型(Xenium・CosMx・MERSCOPE など顕微鏡で1分子ずつ数える方式)は、出力に細胞 × 遺伝子の行列が含まれています。読み込んですぐ解析を始められそうに見えますが、その行列は装置が切った輪郭で作られたものです。自分で切り直せば、別の行列になります。この記事では、出力に何が入っているのか、そして読み込みの段階で確認すべきことを見ていきます。

1. 出力は4つの要素でできている


図:どの装置でも、出力は4つの要素でできている
図:どの装置でも、出力は4つの要素でできている
要素 中身 SpatialData での置き場所
1分子の表 x・y・z 座標、遺伝子名、品質スコア Points
画像 核染色(DAPI など)。座標の基準になる Images
輪郭 細胞と核のポリゴン。装置が切った結果 Shapes
行列 細胞 × 遺伝子。切った後にできる テーブル

SpatialData(Marconato et al., Nature Methods, 2025)は、この4つを同じ座標系の上にそろえて保持します。イメージング型では 1分子の表が数千万から数億行になるので、遅延読み込みに対応した枠組みがあることが実務上とても重要です。

python
import spatialdata_io as sdio
import spatialdata as sd

# 装置ごとのリーダーを呼ぶだけ
sdata = sdio.xenium("data/xenium_run")
# sdata = sdio.cosmx("data/cosmx_run")
# sdata = sdio.merscope("data/merscope_run")

print(sdata)
print(sdata.coordinate_systems)

# 一度 Zarr に書いておく。次からはここを読む
sdata.write("data/sample01.zarr")
sdata = sd.read_zarr("data/sample01.zarr")

2. 行列に入っている分子は、全体の一部でしかない


図:行列に入っている分子は、全体の一部でしかない
図:行列に入っている分子は、全体の一部でしかない

これが、この記事でいちばん大事な点です。細胞 × 遺伝子の行列は、次の条件を両方満たした分子だけでできています。

  • 品質スコアが閾値以上であること
  • 細胞に割り当てられたこと

たとえば Xenium の cell-feature matrix は、Q-Score が 20 以上の分子だけを含みます。一方、転写産物のファイルには閾値を通らなかった分子も残っています

⚠️ 自分で切り直すなら、フィルタも自分で当て直す
1分子の表からセグメンテーションをやり直す場合、品質スコアのフィルタも対照プローブの除去も、装置はやってくれていません。自分で当てる必要があります。10x が公開している Baysor 向けの前処理スクリプトが Q-Score 20 を既定にし、対照プローブを外す処理を含んでいるのは、この理由です。フィルタを忘れると、ノイズを含んだまま細胞を切ることになります。
python
# 1分子の表は Dask で遅延読み込みされる。全部をメモリに載せない
tx = sdata.points["transcripts"]
print(type(tx))          # dask.dataframe
print(tx.columns.tolist())
# ['x', 'y', 'z', 'feature_name', 'qv', 'cell_id', 'overlaps_nucleus', ...]

# 行数を数えるだけでも計算が走る
print("分子の総数:", len(tx))

# まず先頭だけ見て、列の中身を確かめる
print(tx.head(5))

# 座標の範囲を見れば、単位の見当がつく
print(tx[["x", "y"]].describe().compute())
# 範囲が数万なら µm、数十万なら画素の可能性が高い
💡 cell_id 列を見れば、装置の割り当てが分かる
1分子の表には cell_id という列があり、その分子がどの細胞に割り当てられたかが入っています。どの細胞にも入らなかった分子には、割り当てなしを示す値が入ります。この列を数えれば、装置のセグメンテーションが分子の何割を回収したかがその場で分かります。核と重なっているかを示す列(overlaps_nucleus)がある場合は、核内と細胞質のどちらから来た分子かも区別できます。

3. パネルの中身を確認する


フィーチャの一覧には、実際の遺伝子だけでなく対照プローブが混ざっています。何が何個あるのかを、最初に数えておきます。

python
# パネルの中身を確認する。対照プローブが混ざっている
cnt = tx["feature_name"].value_counts().compute()
names = cnt.index.astype(str)

ctrl = names.str.contains("NegControl|BLANK|Unassigned|Deprecated|Negative",
                          case=False)
print("全フィーチャ:", len(cnt))
print("うち対照:", int(ctrl.sum()))
print("実際の遺伝子:", int((~ctrl).sum()))

# 対照の割合は、外す前に必ず記録しておく
print("対照の分子の割合:", round(float(cnt[ctrl].sum() / cnt.sum()) * 100, 3), "%")
⚠️ 対照プローブを外し忘れると、解析に紛れ込む
対照プローブは、装置が品質を測るために混ぜているものです。遺伝子ではないので、クラスタリングや細胞型アノテーションに入れてはいけません。外す前に、その割合を必ず記録してください。その値がデータの背景ノイズの水準を表しています。詳しい種類と読み方は 転写産物レベルの QCで扱います。

4. 座標で気をつける3つのこと


図:座標で気をつける3つのこと
図:座標で気をつける3つのこと
確認すること なぜ どうする
単位 µm で書かれた座標と、画素で書かれた座標がある 座標の範囲を見て見当をつける。画像と重ねて確かめる
視野の連結 装置は視野ごとに撮影し、重なりを整理して連結する 継ぎ目に不自然な線や密度の段差がないかを見る
z 座標 分子は3次元で記録される 2次元に潰すかどうかを意識して決める
⚠️ 座標がずれても、エラーは出ない
分子の座標と画像が重なっていなくても、コードは最後まで動いてしまいます。図を描いて初めて気づきます。読み込んだ直後に、必ず画像・輪郭・分子を重ねて描いてください。
python
import spatialdata_plot  # import すると sdata.pl が使えるようになる

# 一部だけ切り出す。全体をいきなり描かない
# 描画は範囲を絞ってから行う(下の注記を参照)

# 画像・輪郭・分子を重ねて描く。ここで目視確認する
(
    sdata.pl.render_images("morphology_focus")
    .pl.render_shapes("cell_boundaries", fill_alpha=0, outline_alpha=1)
    .pl.render_points("transcripts", size=1)
    .pl.show()
)
# 分子が輪郭の中に収まっているか、画像とずれていないかを見る
💡 3次元であることの意味
分子には z 座標も記録されています。Xenium では、3次元のまま分子を細胞に割り当てる処理が行われます。多くの解析は2次元に潰して進めますが、切片には厚みがあり、上下に別の細胞が重なっていることがあります。潰してしまうと、その2つが同じ位置にあるように見えます。セグメンテーションの手法には3次元を扱えるものもあるので、細胞が密に重なる組織では検討する価値があります。

5. 装置の輪郭を使うか、切り直すか


読み込みの段階で、この方針を決めておくと無駄がありません。

方針 使うもの 向いている場面
装置の輪郭をそのまま使う テーブル の行列 まず全体像を見たい。標準的な組織
自分で切り直す Points の表 + Images 細胞の形が不規則。装置の結果が不自然
両方を比べる 両方 どちらが良いか判断できないとき
python
import scanpy as sc

# 装置が切った結果の行列。これは4つ目の要素であって、出発点ではない
adata = sdata["table"]
print(adata.shape)
print(adata.obs.columns.tolist())
# ['cell_id', 'transcript_counts', 'control_probe_counts',
#  'control_codeword_counts', 'cell_area', 'nucleus_area', ...]

# 行列に入った分子と、表にある分子の数を比べる
in_matrix = int(adata.X.sum())
all_tx = len(tx)
print("行列に入った割合:", round(in_matrix / all_tx, 3))
# 低ければ、多くの分子が捨てられている。切り方を疑う材料になる
💡 行列に入った分子の割合が、判断材料になる
1分子の表の総数と、行列に入った分子の総数を比べてください。この割合が低ければ、多くの分子が捨てられているということです。原因は、品質フィルタで落ちたか、どの細胞にも割り当てられなかったか、のどちらかです。後者が多いなら、輪郭が小さすぎる可能性があります。切り直しを検討する具体的な材料になります。

6. 大きなデータをどう扱うか


1分子の表は数億行になりえます。素直に読み込むとメモリが足りません。

  • 遅延読み込み:SpatialData は Points を Dask のデータフレームとして扱う。計算が必要になるまで実際には読まない。
  • Zarr に書き出す:一度書いておけば、次からは必要な部分だけを読める。パースし直す時間も省ける。
  • 範囲を切り出す:全体をいきなり描かない。まず一部を切り出して確認し、問題がなければ全体に進む。
⚠️ compute() を呼ぶ場所に気をつける
Dask のデータフレームは、compute() を呼んだ時点で実際の計算が走ります。うっかり全体に対して呼ぶと、数億行の処理が始まります。先に絞り込んでから compute() を呼ぶのが基本です。len() のような一見軽い操作でも計算が走ることがあるので、大きなデータでは注意してください。

7. よくあるつまずき


症状 原因 対処
分子と画像が重ならない 座標の単位が違う 座標の範囲を確認し、変換を確かめる
行列の細胞数が少なすぎる 装置のセグメンテーションが保守的 1分子の表から切り直す
行列に入った分子の割合が低い 輪郭が小さい、または品質フィルタが厳しい cell_id の割り当て率を確認する
対照プローブがクラスタを作る 外し忘れ 解析の前に除去する
メモリが足りない Points を全部読んでいる 遅延読み込みと範囲の切り出しを使う
継ぎ目に不自然な線が出る 視野の連結の問題 分子密度を空間プロットで確認する

まとめ


  • 出力は1分子の表・画像・輪郭・行列の4要素。SpatialData が同じ座標系にそろえて保持する。
  • 行列は4つ目の要素であって、出発点ではない。装置が切った輪郭で作られたもので、切り直せば別の行列になる。
  • 行列に入るのは品質スコアが閾値以上、かつ細胞に割り当てられた分子だけ。1分子の表には、閾値を通らなかった分子も残っている。
  • 自分で切り直すなら、フィルタも自分で当て直す
  • cell_id 列を数えれば、装置が分子の何割を回収したかがその場で分かる。
  • 座標では単位・視野の連結・z 座標の3つに気をつける。ずれてもエラーは出ないので、読み込み直後に重ねて描く。
  • 数億行の表は遅延読み込みと Zarr で扱う。compute() を呼ぶ場所に注意する。

関連記事


参考文献


  • Marconato, L., Palla, G., Yamauchi, K. A., et al. (2025). SpatialData: an open and universal data framework for spatial omics. Nature Methods, 22(1), 58–62. doi:10.1038/s41592-024-02212-x
  • Palla, G., Spitzer, H., Klein, M., et al. (2022). Squidpy: a scalable framework for spatial omics analysis. Nature Methods, 19(2), 171–178. doi:10.1038/s41592-021-01358-2
  • Janesick, A., Shelansky, R., Gottscho, A. D., et al. (2023). High resolution mapping of the tumor microenvironment using integrated single-cell, spatial and in situ analysis. Nature Communications, 14(1), 8353. doi:10.1038/s41467-023-43458-x
  • Petukhov, V., Xu, R. J., Soldatov, R. A., et al. (2022). Cell segmentation in imaging-based spatial transcriptomics. Nature Biotechnology, 40(3), 345–354. doi:10.1038/s41587-021-01044-w
  • Heidari, E., Moorman, A., Unyi, D., et al. (2025). Segger: fast and accurate cell segmentation of imaging-based spatial transcriptomics data. bioRxiv(プレプリント). doi:10.1101/2025.03.14.643160
  • Wolf, F. A., Angerer, P., & Theis, F. J. (2018). SCANPY: large-scale single-cell gene expression data analysis. Genome Biology, 19, 15. doi:10.1186/s13059-017-1382-0

コメント

タイトルとURLをコピーしました