シーケンス型 空間トランスクリプトーム:データ読み込みとデータ構造 ─ Space Ranger 出力から SpatialData まで

Spatial transcriptome
📚 この記事について
シーケンス型のデータを読み込み、中身を理解するところまでを扱います。座標と画像がなぜズレるのか、SpatialData がそれをどう解決しているのかを、実際のコードで確認します。
🔙 前の記事シーケンス型 空間トランスクリプトーム 前処理の全体像
🔜 次の記事:シーケンス型 空間トランスクリプトーム:QC と組織検出
📌 前提AnnData のデータ構造JSONファイルとは何か

空間トランスクリプトーム解析(spatial transcriptomics)のうちシーケンス型(Visium・Stereo-seq など NGS で読み出す方式)は、発現行列だけでなく、組織の画像と、各フィーチャの座標がセットで出力されます。この3つを正しく重ね合わせるところが、最初の作業になります。そして、ここでのズレはエラーを出さずに静かに起きます。座標が少しずれたまま解析を進めても、コードは最後まで動いてしまう。この記事では、なぜズレるのか、SpatialData がそれをどう解決しているのかを見ていきます。

1. 出力に必ず入っている3つのもの


プラットフォームが違っても、シーケンス型の出力には必ず次の3つが入っています。

中身 Visium / Visium HD Stereo-seq Curio Seeker
発現行列 filtered_feature_bc_matrix.h5 .gef ファイル カウント行列
フィーチャの座標 spatial/tissue_positions.csv .gef に同梱 バーコード座標ファイル
組織の画像 spatial/tissue_hires_image.png ほか ssDNA / DAPI / H&E 画像 (任意)
座標と画像を結ぶ情報 spatial/scalefactors_json.json レジストレーション情報

Visium HD の場合、これらが binned_outputs/ の下にビンの大きさごと(square_002um / square_008um / square_016um)に入ります。つまり1回の実験から、解像度の違う3つのデータセットが出てきます。

💡 ファイル名が変わったことがある
Space Ranger v2.0 から、スポットの座標ファイルは tissue_positions_list.csv から tissue_positions.csv に名前が変わり、ヘッダー行が付きました。古いチュートリアルのコードをそのまま動かすと、ファイルが見つからないか、1行目のデータがヘッダーとして食われます。

2. なぜ座標と画像がズレるのか


ここが、この記事でいちばん大事なところです。

tissue_positions.csv に書かれている座標は、フル解像度の顕微鏡画像のピクセルです。ところが、Space Ranger が出力する組織画像(tissue_hires_image.png、tissue_lowres_image.png)は、扱いやすいように縮小されたものです。座標はフル解像度のまま、画像は縮小版。この2つをそのまま重ねれば、当然ズレます。

図:座標はフル解像度、画像は縮小版。だから係数が要る
図:座標はフル解像度、画像は縮小版。だから係数が要る

この差を埋めるのが scalefactors_json.json です。中身は次のような値です。

キー 意味
tissue_hires_scalef フル解像度のピクセル座標を、hires 画像の座標に変換する倍率
tissue_lowres_scalef 同じく lowres 画像に変換する倍率
spot_diameter_fullres フル解像度画像上での、1スポットの直径(ピクセル)
fiducial_diameter_fullres 同じく、位置合わせ用マーカーの直径

Visium HD では、spot_diameter_fullres は円の直径ではなく正方形ビンの一辺(2 µm ぶんのピクセル数)を指します。

⚠️ この症状を見たら、係数を疑う
係数を掛け忘れると、スポットが画像の左上の隅に小さく固まって表示されます。逆に、hires 画像に lowres の係数を掛けると、スポットが画像からはみ出します。エラーは出ません。図を描いて初めて気づきます。さらに、tissue_positions.csv の pxl_col_in_fullres が x、pxl_row_in_fullres が y です。row と col を x と y に素直に対応させると、図が転置されます。

3. SpatialData はこれをどう解決するか


SpatialData(Marconato et al., Nature Methods, 2025)の考え方は単純です。座標を書き換えるのではなく、座標系を複数用意して、要素ごとに変換を紐づける。

図:同じ要素が、複数の座標系に別々の変換で登録される
図:同じ要素が、複数の座標系に別々の変換で登録される

Visium HD を読み込むと、座標系が3つできます。global(フル解像度)、downscaled_hiresdownscaled_lowres。ビンの正方形(square_008um)は、この3つすべてに登録されます。ただし、それぞれに違う変換が紐づいています。global では変換なし(Identity)、downscaled_hires では 0.2 倍の Scale、というふうに。

だから、どの画像に重ねて描いても、スケールファクターを自分で掛ける必要がありません。「どの座標系で描くか」を指定するだけです。

💡 変換はメタデータであって、データは動かない
set_transformation で変換を足しても、画像の中身も座標の値も書き換わりません。軽いメタデータが1つ増えるだけです。実際に座標を計算し直したいときにだけ、明示的に transform() を呼びます。この設計のおかげで、巨大な画像を持ったまま何度でも座標系を足し引きできます。

4. 読み込む


spatialdata-io が、各社の出力ディレクトリをそのまま受け取ります。

python
import spatialdata_io as sdio

# Visium(v1 / v2・CytAssist)
sdata = sdio.visium("data/visium_run/outs")

# Visium HD(ビンごとにテーブルが分かれる)
sdata = sdio.visium_hd("data/visium_hd_run/outs")

# Stereo-seq(SAW の出力ディレクトリ)
sdata = sdio.stereoseq("data/stereoseq_run")

# Curio Seeker
sdata = sdio.curio("data/curio_run")
⚠️ Stereo-seq のバージョンに注意
spatialdata-io の Stereo-seq リーダーは、SAW 7.x の出力に対応していますが、8.x にはまだ対応していません。8.x を使っている場合は、STOmics 公式の Stereopy で読み込み、AnnData に書き出してから合流させるのが現実的です。この種の対応状況は変わるので、使う前にリポジトリの README を確認してください。

5. 中身を確認する


読み込んだら、まず repr を見ます。何がどの座標系に入っているかが、そのまま印字されます。

格納される場所 中身
Images 解像度ごとの組織画像 ‘hires_image’ / ‘lowres_image’
Shapes フィーチャの輪郭(円・正方形) ‘square_008um’
テーブル AnnData そのもの ‘square_008um’ (9311 × 19059)
座標系 変換の紐づけ先 ‘global’ / ‘downscaled_hires’ / ‘downscaled_lowres’
python
# repr を表示すると、上の内容がそのまま印字される
print(sdata)
print(sdata.coordinate_systems)

# 要素は名前で直接取り出せる
adata = sdata["square_008um"]
print(adata.shape)

テーブルに入っているのは AnnData そのものです。AnnData のデータ構造を知っていれば、そこから先は同じです。Shapes はフィーチャの輪郭(Visium なら円、Visium HD なら正方形)、Images は解像度ごとの組織画像です。

座標系ごとの変換は、こう確認します。

python
from spatialdata.transformations import get_transformation

# 同じ要素が、座標系ごとに違う変換を持っている
elem = sdata.shapes["square_008um"]
print(get_transformation(elem, to_coordinate_system="global"))
#   Identity
print(get_transformation(elem, to_coordinate_system="downscaled_hires"))
#   Scale (x, y) [0.2 0.2]

# 変換はメタデータ。座標そのものを書き換えたいときだけ transform() を呼ぶ
from spatialdata import transform
moved = transform(elem, to_coordinate_system="downscaled_hires")

6. Zarr に保存する ─ 読み込みは一度だけ


Space Ranger や SAW の出力をパースするのは、それなりに時間がかかります。一度 Zarr 形式で書き出しておけば、次からはそこを読むだけで済みます。

python
# 一度 Zarr に書いておく。次からはパースし直さずに済む
sdata.write("data/sample01.zarr")

import spatialdata as sd
sdata = sd.read_zarr("data/sample01.zarr")
# 大きな画像は遅延読み込みされる。メモリに全部載せない
💡 なぜ Zarr なのか
Zarr は、大きな配列をチャンク(小さな塊)に分けて保存する形式です。必要な部分だけを読み出せるので、メモリに載りきらない画像を持ったまま作業できます。Visium HD のフル解像度画像は数万ピクセル四方になることがあり、これを毎回まるごとメモリに読むのは現実的ではありません。

7. 画像とビンを重ねて描く


画像とフィーチャを重ねて描くとき、スケールファクターを自分で掛ける必要はありません。どの座標系で描くかを指定するだけです。

python
import spatialdata_plot  # import するだけで sdata.pl が使えるようになる

# 画像とビンを重ねて描く。変換は内部で処理される
(
    sdata.pl.render_images("hires_image")
    .pl.render_shapes("square_008um", color="EPCAM")
    .pl.show(coordinate_systems="downscaled_hires")
)
💡 大きなデータは、範囲を切り出してから描く
フル解像度のまま全体を描くと、時間もメモリも溶けます。SpatialData には矩形範囲で切り出す機能があり、どの座標系での範囲なのかを必ず指定します。「7000〜10000」がフル解像度なのか縮小版なのかが決まらないと、切り出す場所が定まらないからです。まず一部で確認してから全体に進むのが定石です。

8. Visium HD の格子は、画像の軸に平行ではない


Visium HD 特有の注意点です。2 µm のビンが並ぶ格子は、組織画像の軸に対してわずかに回転しています

図:Visium HD の格子は、画像の軸に平行ではない
図:Visium HD の格子は、画像の軸に平行ではない

座標を素直に画像の格子として並べ直すと、この回転のせいでモアレ縞(規則的な干渉パターン)が現れます。生物学的な模様に見えてしまうので、たちが悪い。

💡 rasterize_bins() が回転を吸収する
spatialdata の rasterize_bins() は、ビンを画像としてラスタ化する関数です。回転とスケールを含む変換を保持したまま描くので、モアレが出ません。呼び出しには、どのビン要素を使うか、対応する AnnData はどれか、そして格子の列・行を示すキー(array_col / array_row)を渡します。引数名は版によって変わることがあるので、使う前に公式ドキュメントで確認してください。ビンの大きさを変えても、高解像度画像と正しく重なります。

9. AnnData に戻して、scanpy と squidpy に渡す


SpatialData を使っていても、日々の解析は AnnData の上で行います。テーブルを取り出せば、scanpy の関数がそのまま使えます。

python
import scanpy as sc
import squidpy as sq

# テーブルは AnnData そのもの。scanpy の関数がそのまま使える
adata = sdata["square_008um"].copy()
print(adata.obsm["spatial"].shape)   # (フィーチャ数, 2)

# 空間近傍グラフを作れば、squidpy の空間統計に進める
sq.gr.spatial_neighbors(adata, coord_type="grid", n_neighs=6)

# 解析が終わったら、結果を SpatialData 側へ戻す
sdata["square_008um"] = adata
💡 squidpy の空間近傍グラフ
Squidpy(Palla et al., Nature Methods, 2022)の spatial_neighbors は、フィーチャどうしの「隣り合っている関係」をグラフとして作ります。Visium HD のような格子状の配置なら coord_type=”grid”、Curio Seeker のようにビーズがランダムに並ぶ配置なら coord_type=”generic” を使います。このグラフが、空間ドメイン同定や近傍解析の土台になります。

10. よくあるつまずき


症状 原因 対処
スポットが画像の左上に固まる スケールファクターの掛け忘れ SpatialData の座標系を使う。自分で掛けない
スポットが画像からはみ出す hires 画像に lowres の係数を掛けた 座標系の名前と画像の名前を揃える
図が転置されている row と col を x と y に素直に対応させた col が x、row が y
規則的な縞模様が出る Visium HD の格子の回転 rasterize_bins() を使う
ファイルが見つからない Space Ranger v2.0 でのファイル名変更 tissue_positions.csv(旧 tissue_positions_list.csv)
読み込みが毎回遅い 毎回パースし直している 一度 Zarr に書き出す
Stereo-seq が読めない spatialdata-io が SAW 8.x 未対応 Stereopy で読み、AnnData 経由で合流

まとめ


  • 出力には必ず発現行列・フィーチャの座標・組織画像の3つが入っている。この3つを重ね合わせるのが最初の作業。
  • 座標はフル解像度のピクセル、画像は縮小版。だからスケールファクターが要る。掛け忘れてもエラーは出ない。図を描いて初めて気づく。
  • SpatialData は、座標を書き換えずに座標系を複数用意して変換を紐づける。だからスケールファクターを自分で掛ける必要がない。
  • 変換はメタデータ。データそのものは動かない。
  • 一度 Zarr に書き出す。次からは遅延読み込みで、メモリに載りきらない画像も扱える。
  • Visium HD の格子はわずかに回転している。素直に並べるとモアレが出る。rasterize_bins() を使う。
  • テーブルは AnnData そのもの。取り出せば scanpy と squidpy がそのまま使える。

関連記事


参考文献


  • 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
  • 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
  • Ståhl, P. L., Salmén, F., Vickovic, S., et al. (2016). Visualization and analysis of gene expression in tissue sections by spatial transcriptomics. Science, 353(6294), 78–82. doi:10.1126/science.aaf2403
  • Chen, A., Liao, S., Cheng, M., et al. (2022). Spatiotemporal transcriptomic atlas of mouse organogenesis using DNA nanoball-patterned arrays. Cell, 185(10), 1777–1792. doi:10.1016/j.cell.2022.04.003
  • Stickels, R. R., Murray, E., Kumar, P., et al. (2021). Highly sensitive spatial transcriptomics at near-cellular resolution with Slide-seqV2. Nature Biotechnology, 39(3), 313–319. doi:10.1038/s41587-020-0739-1
  • Polański, K., Bartolomé-Casado, R., Sarropoulos, I., et al. (2024). Bin2cell reconstructs cells from high resolution Visium HD data. Bioinformatics, 40, btae546. doi:10.1093/bioinformatics/btae546

コメント

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