空間トランスクリプトーム解析(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_hires、downscaled_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")
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 は、大きな配列をチャンク(小さな塊)に分けて保存する形式です。必要な部分だけを読み出せるので、メモリに載りきらない画像を持ったまま作業できます。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 のビンが並ぶ格子は、組織画像の軸に対してわずかに回転しています。

座標を素直に画像の格子として並べ直すと、この回転のせいでモアレ縞(規則的な干渉パターン)が現れます。生物学的な模様に見えてしまうので、たちが悪い。
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(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 がそのまま使える。
関連記事
- シーケンス型 空間トランスクリプトーム 前処理の全体像
- シーケンス型 空間トランスクリプトーム:QC と組織検出
- シーケンス型 空間トランスクリプトーム:解析単位の決め方
- scRNA-seq解析の土台:AnnData のデータ構造をリレーショナルDBの発想で理解する
- scRNA-seq解析:データの読み込みと品質管理(QC)
- scRNA-seq解析:正規化・特徴選択・次元削減・クラスタリング
- JSONファイルとは何か・編集方法【Mac/Windows対応】
- ターミナルの基本操作入門【Windows版】
参考文献
- 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


コメント