0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

XeniumとVisiumのデータ構造の違いと統合解析への指針

0
Posted at

XeniumとVisiumのデータ構造の違いと統合解析への指針

作成日: 2024年11月
データセット: 10x Genomics Cross-Platform Ovarian Cancer Dataset
著者: Spatial Data Analysis Team


目次

  1. データ構造の本質的な違い
  2. Visiumモデルへの適用戦略
  3. 実装上の主要な懸念点
  4. 実装の推奨フロー
  5. 成功の鍵(まとめ)

1. データ構造の本質的な違い

1.1 観測単位の違い(最重要)

Visium / Visium HD

  • 物理的グリッド: 固定された正方形領域(8µm × 8µm)
  • 生物学的意味: 不明確(1グリッドに0〜複数細胞)
  • データ性質: 空間的に均一だが、生物学的には混合
  • 本データセット: 445,751スポット

Xenium

  • 生物学的単位: セグメンテーションで同定された個々の細胞
  • 生物学的意味: 明確(1観測 = 1細胞)
  • データ性質: 空間的に不均一だが、生物学的に純粋
  • 本データセット: 200,900細胞

重要な帰結: Visiumは「場所」を測定、Xeniumは「細胞」を測定


1.2 遺伝子カバレッジの違い

Visium

  • 全トランスクリプトーム: 18,132遺伝子(本データセット)
  • 利点: 未知の遺伝子も検出、探索的解析に強い
  • 欠点: 低発現遺伝子の検出感度が低い(ドロップアウト)

Xenium

  • ターゲットパネル: 5,001遺伝子(本データセット)
  • 利点: パネル遺伝子は超高感度、分子数に近い定量
  • 欠点: パネル外の遺伝子は完全欠損

本データセットでの互換性:

  • 共通遺伝子: 4,831(Xeniumの96.6%)
  • Visium専用: 13,301遺伝子
  • Xenium専用: 170遺伝子

実用上の問題: Xeniumパネルに含まれる遺伝子のみで解析する必要


1.3 空間情報の表現方法

Visium

座標系: 規則的グリッド (row, col)
隣接関係: 自明(上下左右の4近傍または8近傍)
距離計算: ユークリッド距離が直感的
データ構造: numpy配列やスパース行列で効率的

Xenium

座標系: 不規則な点群 (x_centroid, y_centroid)
隣接関係: 不明確(KNNやドロネー三角形分割が必要)
距離計算: 細胞密度を考慮する必要
データ構造: 点群として扱う必要

実装への影響: Xeniumでは空間近傍グラフを明示的に構築する必要


1.4 データのスパース性

本データセットでの実測値:

  • Visium HD: 非ゼロ率 6.9%(スパース率 93.1%)
    • 561.49M 非ゼロエントリ / 8.08B 全要素
  • Xenium: 非ゼロ率 15.7%(スパース率 84.3%)
    • 158.27M 非ゼロエントリ / 1.00B 全要素

重要な観察:

  • Xeniumの方が約2.3倍高い検出率
  • これは技術的な違い(NGS vs イメージング)を反映
  • 正規化手法の選択に影響(Visium用の手法は過剰補正の可能性)

1.5 データ品質メトリクス(参考値)

メトリクス Visium HD Xenium
観測数 445,751 200,900
遺伝子数 18,132 5,001
中央値 total counts/観測 ~1,000-2,000 ~500-1,000
中央値 検出遺伝子数/観測 ~500-1,000 ~300-500
検出率 6.9% 15.7%

2. Visiumモデルへの適用戦略

2.1 前提となる典型的なVisiumモデルの想定

多くのVisium向けツール(Tangram, SpaGCN, STdeconvolve等)は以下を想定:

# 入力データ構造
adata.X                    # (n_spots, n_genes) のカウント行列
adata.obsm['spatial']      # (n_spots, 2) の座標
adata.obs                  # スポットのメタデータ
adata.var                  # 遺伝子のメタデータ

# 空間構造の想定
- グリッド状の規則的配置
- 隣接関係が自明
- スポット数: 数千数万程度
- 計算量: O(N) または O(N log N) で処理可能

2.2 戦略A: 細胞を「超高解像度スポット」として扱う

基本アイデア: Xeniumの各細胞を、極めて小さいVisiumスポットと見なす

ステップ1: データ形式の統一

# Xeniumデータの準備
import pandas as pd

# cells.parquet または cells.csv から座標を読み込み
cells_df = pd.read_csv('cells.csv')
cells_df = cells_df.set_index('cell_id')

# AnnDataに座標を追加(Visiumと同じ形式)
adata_xenium.obsm['spatial'] = cells_df.loc[
    adata_xenium.obs_names, 
    ['x_centroid', 'y_centroid']
].values

ステップ2: 空間近傍グラフの構築

import squidpy as sq

# KNN法で近傍グラフを構築(Visiumの6近傍に相当)
sq.gr.spatial_neighbors(
    adata_xenium, 
    coord_type='generic',
    n_neighs=6
)

# または距離ベース(例: 50µm以内の細胞)
sq.gr.spatial_neighbors(
    adata_xenium, 
    coord_type='generic',
    radius=50.0
)

ステップ3: 遺伝子の絞り込み

# 共通遺伝子のみに限定
common_genes = list(
    set(adata_visium.var_names) & set(adata_xenium.var_names)
)
print(f"共通遺伝子数: {len(common_genes)}")

adata_xenium_filtered = adata_xenium[:, common_genes].copy()

# さらに高品質遺伝子のみに絞る(推奨)
# 両プラットフォームで100+観測で発現している遺伝子
visium_counts = (adata_visium[:, common_genes].X > 0).sum(axis=0)
xenium_counts = (adata_xenium[:, common_genes].X > 0).sum(axis=0)

high_quality_genes = [
    gene for i, gene in enumerate(common_genes)
    if visium_counts[0, i] >= 100 and xenium_counts[0, i] >= 100
]
print(f"高品質遺伝子数: {len(high_quality_genes)}")

ステップ4: ダウンサンプリング(必須)

import numpy as np

# 空間的に均等なサンプリング
def spatial_uniform_sampling(adata, n_sample=10000, n_grid=100):
    coords = adata.obsm['spatial']
    x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
    y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
    
    # グリッド分割
    x_bins = np.linspace(x_min, x_max, n_grid + 1)
    y_bins = np.linspace(y_min, y_max, n_grid + 1)
    
    x_idx = np.digitize(coords[:, 0], x_bins) - 1
    y_idx = np.digitize(coords[:, 1], y_bins) - 1
    grid_idx = x_idx * n_grid + y_idx
    
    # 各グリッドから均等にサンプリング
    sample_idx = []
    cells_per_grid = max(1, n_sample // len(np.unique(grid_idx)))
    
    for gid in np.unique(grid_idx):
        cells_in_grid = np.where(grid_idx == gid)[0]
        n_to_sample = min(cells_per_grid, len(cells_in_grid))
        sampled = np.random.choice(cells_in_grid, n_to_sample, replace=False)
        sample_idx.extend(sampled)
    
    # 目標数に調整
    if len(sample_idx) > n_sample:
        sample_idx = np.random.choice(sample_idx, n_sample, replace=False)
    
    return adata[sample_idx].copy()

# 実行
adata_xenium_sampled = spatial_uniform_sampling(
    adata_xenium_filtered, 
    n_sample=10000
)
print(f"サンプリング後: {adata_xenium_sampled.shape}")

利点:

  • ✅ 細胞レベルの解像度を保持
  • ✅ 生物学的に意味のある単位で解析
  • ✅ セルタイプ情報が直接得られる

欠点:

  • ❌ 計算量が爆発的に増加(ダウンサンプリング必須)
  • ❌ モデルが想定していない高解像度
  • ❌ 不規則な点群への対応が必要

2.3 戦略B: グリッド化(ビニング)

基本アイデア: Xeniumデータを人工的にVisiumのグリッド構造に変換

実装方法

def bin_xenium_to_grid(adata_xenium, bin_size=55):
    """
    XeniumデータをVisium風のグリッドに変換
    
    Parameters:
    -----------
    adata_xenium : AnnData
        Xeniumデータ(spatial座標付き)
    bin_size : float
        グリッドサイズ(µm)。55はVisium標準、8はVisium HD
    
    Returns:
    --------
    adata_binned : AnnData
        グリッド化されたデータ
    """
    coords = adata_xenium.obsm['spatial']
    x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
    y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
    
    # グリッド定義
    x_bins = np.arange(x_min, x_max + bin_size, bin_size)
    y_bins = np.arange(y_min, y_max + bin_size, bin_size)
    
    # 各細胞がどのグリッドに属するか
    x_idx = np.digitize(coords[:, 0], x_bins) - 1
    y_idx = np.digitize(coords[:, 1], y_bins) - 1
    grid_idx = x_idx * len(y_bins) + y_idx
    
    # グリッドごとに発現を集計
    unique_grids = np.unique(grid_idx)
    n_grids = len(unique_grids)
    n_genes = adata_xenium.n_vars
    
    # 疎行列で効率的に集計
    from scipy.sparse import lil_matrix
    binned_X = lil_matrix((n_grids, n_genes))
    grid_coords = []
    
    for i, gid in enumerate(unique_grids):
        mask = grid_idx == gid
        # このグリッドに属する細胞の発現を合計
        binned_X[i] = adata_xenium.X[mask].sum(axis=0)
        
        # グリッドの中心座標
        grid_x = x_bins[gid // len(y_bins)] + bin_size / 2
        grid_y = y_bins[gid % len(y_bins)] + bin_size / 2
        grid_coords.append([grid_x, grid_y])
    
    # 新しいAnnDataオブジェクト作成
    import anndata as ad
    adata_binned = ad.AnnData(
        X=binned_X.tocsr(),
        var=adata_xenium.var.copy(),
        obs=pd.DataFrame(index=[f"grid_{i}" for i in range(n_grids)])
    )
    adata_binned.obsm['spatial'] = np.array(grid_coords)
    
    return adata_binned

# 実行例
adata_xenium_binned = bin_xenium_to_grid(
    adata_xenium, 
    bin_size=55  # Visium標準サイズ
)
print(f"グリッド化後: {adata_xenium_binned.shape}")

利点:

  • ✅ Visiumモデルにそのまま適用可能
  • ✅ 計算量が大幅に削減される
  • ✅ グリッド構造を前提とするCNNモデルでも使える
  • ✅ 実装が比較的シンプル

欠点:

  • ❌ Xeniumの高解像度情報が失われる
  • ❌ 細胞境界をまたぐ場合の処理が曖昧
  • ❌ 単一細胞レベルの情報が得られない

2.4 具体例: Tangramへの適用

Tangramは、scRNA-seqデータを空間データにマッピングするツールです。

標準的なTangramの使用法(Visium)

import tangram as tg

# 標準的な使用法
tg.pp_adatas(ad_sc, ad_sp, genes=None)

ad_map = tg.map_cells_to_space(
    ad_sc,              # 参照scRNA-seq
    ad_sp,              # 空間データ(Visium)
    mode='cells',
    density_prior='uniform'
)

Xeniumを使う場合の修正版

import tangram as tg

# 1. 共通遺伝子に絞る
common_genes = ad_sc.var_names.intersection(ad_xenium.var_names)
print(f"共通遺伝子数: {len(common_genes)}")

ad_sc_common = ad_sc[:, common_genes].copy()
ad_xenium_common = ad_xenium[:, common_genes].copy()

# 2. Xeniumをダウンサンプリング(必須)
ad_xenium_sampled = spatial_uniform_sampling(
    ad_xenium_common, 
    n_sample=10000
)

# 3. 前処理
tg.pp_adatas(ad_sc_common, ad_xenium_sampled, genes=None)

# 4. マッピング実行
ad_map = tg.map_cells_to_space(
    ad_sc_common,
    ad_xenium_sampled,
    mode='cells',
    density_prior='rna_count_based',  # ★ 重要: uniformではなく
    num_epochs=500,
    device='cuda:0'  # GPU推奨
)

# 5. 結果の可視化
tg.project_cell_annotations(ad_map, ad_xenium_sampled, annotation='cell_type')

重要な変更点:

  1. density_prior='rna_count_based': Xeniumは細胞密度が不均一
  2. ダウンサンプリングは必須(20万細胞は処理不可)
  3. 共通遺伝子のみを使用
  4. GPU使用を強く推奨

2.5 他のモデルへの適用例

SpaGCN(グラフベースのクラスタリング)

import SpaGCN as spg

# Xeniumの場合、空間グラフを明示的に構築
import squidpy as sq
sq.gr.spatial_neighbors(adata_xenium_sampled, n_neighs=6)

# SpaGCN実行
spg.prefilter_genes(adata_xenium_sampled, min_cells=3)
spg.prefilter_specialgenes(adata_xenium_sampled)

# 空間グラフを使用
adj = adata_xenium_sampled.obsp['spatial_connectivities']
l = spg.search_l(p=0.5, adj=adj, start=0.01, end=1000, tol=0.01)

# クラスタリング
res = spg.search_res(
    adata_xenium_sampled, 
    adj, 
    l=l, 
    target_num=10
)

STdeconvolve(セルタイプ分解)

# Xeniumは既に単一細胞レベルなので、
# STdeconvolveは不要(直接セルタイプアノテーション可能)

# ただし、グリッド化したXeniumには適用可能
# adata_xenium_binned を使用

3. 実装上の主要な懸念点

3.1 計算量の問題(最大の課題)

問題の本質

Visium想定:
  5,000スポット × 5,000遺伝子 = 25,000,000要素
  メモリ: ~200MB(スパース行列)
  計算時間: 数分〜数十分

Xenium実際:
  200,900細胞 × 5,001遺伝子 = 1,004,590,900要素
  メモリ: ~8GB(スパース行列)
  計算時間: 数時間〜不可能

比率: 約40倍のデータ量

アルゴリズムの計算量

アルゴリズム 計算量 Visium (5K) Xenium (200K) 比率
Tangram O(N×M) 25M 1B 40x
SpaGCN O(N²) 25M 40B 1,600x
近傍探索 O(N log N) 60K 2.6M 43x

結論: そのままでは計算不可能。ダウンサンプリングは必須

解決策の優先順位

1. ダウンサンプリング(必須)

# 推奨: 10,000細胞(元の5%)
# これでVisiumの2倍程度の計算量に
adata_xenium_sampled = spatial_uniform_sampling(adata_xenium, 10000)

2. タイリング(推奨)

# 空間を1mm×1mmのタイルに分割
tiles = create_spatial_tiles(adata_xenium, tile_size=1000)

results = []
for tile in tiles:
    result = process_tile(tile)  # 各タイルを独立に処理
    results.append(result)

# 結果を統合
final_result = merge_tile_results(results)

3. バッチ処理(GPU使用時)

# GPUメモリに収まる単位で処理
batch_size = 1000
for i in range(0, n_cells, batch_size):
    batch = adata_xenium[i:i+batch_size]
    process_batch(batch)

3.2 遺伝子カバレッジの制約

問題の詳細

本データセットの場合:
  Xeniumパネル: 5,001遺伝子
  共通遺伝子: 4,831(96.6%)
  → 実質的には十分

しかし:
  - 一部の重要なマーカー遺伝子が欠損する可能性
  - 細胞型によってはパネルに含まれないマーカー
  - 新規遺伝子や未知の転写産物は検出不可

対策

1. 事前確認

# 重要なマーカー遺伝子がパネルに含まれるか確認
marker_genes = ['CD3D', 'CD8A', 'CD4', 'MS4A1', 'CD14', 'FCGR3A']
missing_markers = [g for g in marker_genes if g not in adata_xenium.var_names]

if missing_markers:
    print(f"警告: 以下のマーカーがパネルに含まれません: {missing_markers}")

2. 高品質遺伝子の選定

# 両プラットフォームで十分な発現がある遺伝子のみ使用
def select_high_quality_genes(adata_v, adata_x, min_cells=100):
    common = list(set(adata_v.var_names) & set(adata_x.var_names))
    
    v_counts = (adata_v[:, common].X > 0).sum(axis=0)
    x_counts = (adata_x[:, common].X > 0).sum(axis=0)
    
    hq_genes = [
        gene for i, gene in enumerate(common)
        if v_counts[0, i] >= min_cells and x_counts[0, i] >= min_cells
    ]
    
    return hq_genes

3. パネル拡張の検討

  • カスタムXeniumパネルの設計
  • 複数のパネルを組み合わせる
  • 他の技術(MERFISH等)との併用

3.3 ノイズモデルの不一致

Visium(NGS)のノイズ特性

1. PCR増幅バイアス
   - 配列依存的な増幅効率の違い
   - GCバイアス

2. シーケンシングエラー
   - ランダムエラー(~0.1%)
   - 体系的エラー(特定の配列で高頻度)

3. ドロップアウト
   - 確率的な検出漏れ
   - 低発現遺伝子で顕著
   - ゼロインフレーション

Xenium(イメージング)のノイズ特性

1. バックグラウンド蛍光
   - 非特異的ハイブリダイゼーション
   - 自家蛍光

2. セグメンテーションエラー
   - 細胞境界の誤検出
   - 隣接細胞間のコンタミネーション

3. プローブ効率のばらつき
   - プローブ設計依存
   - 組織環境依存

正規化手法の選択

Visium用の正規化(注意が必要):

# SCTransform: NGSのドロップアウトを補正
# → Xeniumには不適切(過剰補正)

# LogNormalize: 総カウント数で正規化
# → 比較的安全だが、技術的違いは残る

Xenium推奨の正規化:

# 最小限の正規化
import scanpy as sc

# オプション1: log1p変換のみ
sc.pp.normalize_total(adata_xenium, target_sum=1e4)
sc.pp.log1p(adata_xenium)

# オプション2: raw countsを使用
# 一部のモデル(Tangram等)はraw countsを推奨

実装例

def normalize_for_integration(adata_visium, adata_xenium, method='minimal'):
    """
    統合解析のための正規化
    
    Parameters:
    -----------
    method : str
        'minimal': log1p変換のみ
        'standard': 標準的な正規化
        'raw': 正規化なし
    """
    if method == 'minimal':
        # 両方に最小限の正規化
        for adata in [adata_visium, adata_xenium]:
            sc.pp.normalize_total(adata, target_sum=1e4)
            sc.pp.log1p(adata)
    
    elif method == 'standard':
        # Visiumは標準的な正規化
        sc.pp.normalize_total(adata_visium, target_sum=1e4)
        sc.pp.log1p(adata_visium)
        sc.pp.scale(adata_visium)
        
        # Xeniumは軽めの正規化
        sc.pp.normalize_total(adata_xenium, target_sum=1e4)
        sc.pp.log1p(adata_xenium)
    
    elif method == 'raw':
        # 正規化なし
        pass
    
    return adata_visium, adata_xenium

3.4 セグメンテーション品質への依存

Xenium特有の問題

細胞セグメンテーションの品質 → 解析結果の信頼性

問題のパターン:
1. Over-segmentation: 1細胞が複数に分割
   → 発現量が分散、低カウント細胞が増加

2. Under-segmentation: 複数細胞が1つに統合
   → 異なる細胞型の発現が混合

3. 境界の誤検出: 細胞境界が不正確
   → 隣接細胞からのコンタミネーション

QCメトリクスの確認

def check_segmentation_quality(adata_xenium):
    """
    セグメンテーション品質のチェック
    """
    # 1. 細胞サイズの分布
    if 'cell_area' in adata_xenium.obs.columns:
        area = adata_xenium.obs['cell_area']
        print(f"細胞面積: median={area.median():.1f}, "
              f"range=({area.min():.1f}, {area.max():.1f})")
        
        # 異常に小さい/大きい細胞を検出
        too_small = (area < area.quantile(0.01)).sum()
        too_large = (area > area.quantile(0.99)).sum()
        print(f"異常サイズ: 小={too_small}, 大={too_large}")
    
    # 2. 総カウント数の分布
    total_counts = adata_xenium.X.sum(axis=1).A1
    print(f"総カウント: median={np.median(total_counts):.0f}")
    
    # 3. 検出遺伝子数の分布
    n_genes = (adata_xenium.X > 0).sum(axis=1).A1
    print(f"検出遺伝子数: median={np.median(n_genes):.0f}")
    
    # 4. 異常細胞の割合
    low_count = (total_counts < 10).sum()
    low_genes = (n_genes < 10).sum()
    print(f"低品質細胞: カウント<10が{low_count}個, "
          f"遺伝子<10が{low_genes}")
    
    return {
        'total_counts': total_counts,
        'n_genes': n_genes
    }

# 実行
qc_metrics = check_segmentation_quality(adata_xenium)

フィルタリング

def filter_low_quality_cells(adata_xenium, 
                             min_counts=50, 
                             min_genes=20,
                             max_counts=None):
    """
    低品質細胞の除去
    """
    # QCメトリクスの計算
    sc.pp.calculate_qc_metrics(adata_xenium, inplace=True)
    
    # フィルタリング
    sc.pp.filter_cells(adata_xenium, min_counts=min_counts)
    sc.pp.filter_cells(adata_xenium, min_genes=min_genes)
    
    if max_counts:
        adata_xenium = adata_xenium[
            adata_xenium.obs['total_counts'] < max_counts
        ]
    
    print(f"フィルタリング後: {adata_xenium.shape}")
    return adata_xenium

3.5 座標系と空間スケールの違い

問題

Visium:
  - 座標単位: ピクセルまたはµm
  - スケール: 固定グリッド(8µm, 55µm等)
  - 原点: 画像の左上または中心

Xenium:
  - 座標単位: µm(通常)
  - スケール: 細胞サイズに依存(不均一)
  - 原点: 組織の任意の点

→ 直接比較や統合が困難

対策

1. 座標の正規化

def normalize_coordinates(adata, scale='um'):
    """
    座標を正規化
    """
    coords = adata.obsm['spatial'].copy()
    
    # 原点を(0, 0)に
    coords -= coords.min(axis=0)
    
    # スケールを統一
    if scale == 'normalized':
        # [0, 1]に正規化
        coords /= coords.max(axis=0)
    
    adata.obsm['spatial'] = coords
    return adata

2. H&E画像との対応付け(Registration)

import cv2
from skimage import transform

def register_to_image(adata, he_image, transform_matrix=None):
    """
    空間座標をH&E画像に対応付け
    """
    coords = adata.obsm['spatial']
    
    if transform_matrix is None:
        # アフィン変換行列を推定
        # (ランドマークポイントが必要)
        transform_matrix = estimate_transform(coords, he_image)
    
    # 座標を変換
    coords_transformed = transform.matrix_transform(
        coords, 
        transform_matrix
    )
    
    adata.obsm['spatial_registered'] = coords_transformed
    return adata

4. 実装の推奨フロー

4.1 データ準備フェーズ

ステップ1: データ読み込みと基本QC

import scanpy as sc
import pandas as pd
import numpy as np

# 1. Visium HDの読み込み
adata_visium = sc.read_10x_h5('visium_hd.h5')
adata_visium.var_names_make_unique()

# 2. Xeniumの読み込み
adata_xenium = sc.read_10x_h5('xenium.h5')
adata_xenium.var_names_make_unique()

# 3. Xeniumの座標情報を追加
cells_df = pd.read_csv('cells.csv').set_index('cell_id')
adata_xenium.obsm['spatial'] = cells_df.loc[
    adata_xenium.obs_names, 
    ['x_centroid', 'y_centroid']
].values

# 4. 基本QC
sc.pp.calculate_qc_metrics(adata_visium, inplace=True)
sc.pp.calculate_qc_metrics(adata_xenium, inplace=True)

print(f"Visium: {adata_visium.shape}")
print(f"Xenium: {adata_xenium.shape}")

ステップ2: 共通遺伝子の選定

# 共通遺伝子の抽出
common_genes = sorted(list(
    set(adata_visium.var_names) & set(adata_xenium.var_names)
))
print(f"共通遺伝子数: {len(common_genes)}")

# 高品質遺伝子の選定
def select_high_quality_genes(adata_v, adata_x, common_genes, 
                              min_cells=100, min_counts=10):
    """
    両プラットフォームで十分な発現がある遺伝子を選定
    """
    # 各遺伝子が何細胞で発現しているか
    v_detection = (adata_v[:, common_genes].X > 0).sum(axis=0).A1
    x_detection = (adata_x[:, common_genes].X > 0).sum(axis=0).A1
    
    # 平均発現量
    v_mean = adata_v[:, common_genes].X.mean(axis=0).A1
    x_mean = adata_x[:, common_genes].X.mean(axis=0).A1
    
    # フィルタリング
    hq_genes = [
        gene for i, gene in enumerate(common_genes)
        if (v_detection[i] >= min_cells and 
            x_detection[i] >= min_cells and
            v_mean[i] >= min_counts and 
            x_mean[i] >= min_counts)
    ]
    
    return hq_genes

high_quality_genes = select_high_quality_genes(
    adata_visium, 
    adata_xenium, 
    common_genes
)
print(f"高品質遺伝子数: {len(high_quality_genes)}")

# データを絞り込み
adata_visium_filtered = adata_visium[:, high_quality_genes].copy()
adata_xenium_filtered = adata_xenium[:, high_quality_genes].copy()

4.2 前処理フェーズ

ステップ3: Xeniumのダウンサンプリング

def spatial_uniform_sampling(adata, n_sample=10000, n_grid=100, seed=42):
    """
    空間的に均等なサンプリング
    """
    np.random.seed(seed)
    coords = adata.obsm['spatial']
    
    # 空間範囲
    x_min, x_max = coords[:, 0].min(), coords[:, 0].max()
    y_min, y_max = coords[:, 1].min(), coords[:, 1].max()
    
    # グリッド分割
    x_bins = np.linspace(x_min, x_max, n_grid + 1)
    y_bins = np.linspace(y_min, y_max, n_grid + 1)
    
    x_idx = np.digitize(coords[:, 0], x_bins) - 1
    y_idx = np.digitize(coords[:, 1], y_bins) - 1
    grid_idx = x_idx * n_grid + y_idx
    
    # 各グリッドから均等にサンプリング
    sample_idx = []
    unique_grids = np.unique(grid_idx)
    cells_per_grid = max(1, n_sample // len(unique_grids))
    
    for gid in unique_grids:
        cells_in_grid = np.where(grid_idx == gid)[0]
        n_to_sample = min(cells_per_grid, len(cells_in_grid))
        sampled = np.random.choice(cells_in_grid, n_to_sample, replace=False)
        sample_idx.extend(sampled)
    
    # 目標数に調整
    if len(sample_idx) > n_sample:
        sample_idx = np.random.choice(sample_idx, n_sample, replace=False)
    
    return adata[sample_idx].copy()

# 実行
adata_xenium_sampled = spatial_uniform_sampling(
    adata_xenium_filtered, 
    n_sample=10000
)
print(f"サンプリング後: {adata_xenium_sampled.shape}")

ステップ4: 空間近傍グラフの構築

import squidpy as sq

# Xeniumの近傍グラフ
sq.gr.spatial_neighbors(
    adata_xenium_sampled,
    coord_type='generic',
    n_neighs=6  # Visiumの6近傍に相当
)

print("空間近傍グラフを構築しました")
print(f"  平均近傍数: {adata_xenium_sampled.obsp['spatial_connectivities'].sum(axis=1).mean():.1f}")

ステップ5: 正規化

# 最小限の正規化
for adata in [adata_visium_filtered, adata_xenium_sampled]:
    sc.pp.normalize_total(adata, target_sum=1e4)
    sc.pp.log1p(adata)

print("正規化完了")

4.3 モデル適用フェーズ

ステップ6: モデルの実行(例: Tangram)

import tangram as tg

# scRNA-seqデータの準備(別途用意)
# ad_sc = sc.read_h5ad('reference_scrna.h5ad')

# 共通遺伝子に絞る
common_genes_final = ad_sc.var_names.intersection(
    adata_xenium_sampled.var_names
)
ad_sc_common = ad_sc[:, common_genes_final].copy()
ad_xenium_common = adata_xenium_sampled[:, common_genes_final].copy()

print(f"最終的な共通遺伝子数: {len(common_genes_final)}")

# Tangram前処理
tg.pp_adatas(ad_sc_common, ad_xenium_common, genes=None)

# マッピング実行
ad_map = tg.map_cells_to_space(
    ad_sc_common,
    ad_xenium_common,
    mode='cells',
    density_prior='rna_count_based',
    num_epochs=500,
    device='cuda:0'
)

print("Tangramマッピング完了")

ステップ7: 結果の可視化と検証

# セルタイプアノテーションの転送
tg.project_cell_annotations(
    ad_map, 
    ad_xenium_common, 
    annotation='cell_type'
)

# 可視化
import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, 1, figsize=(10, 10))
sc.pl.embedding(
    ad_xenium_common,
    basis='spatial',
    color='cell_type',
    ax=ax,
    show=False
)
plt.title('Xenium with Transferred Cell Types')
plt.show()

# 統計サマリー
cell_type_counts = ad_xenium_common.obs['cell_type'].value_counts()
print("\nセルタイプ分布:")
print(cell_type_counts)

4.4 データ保存とエクスポート

# 前処理済みデータの保存
output_dir = 'processed_data'
import os
os.makedirs(output_dir, exist_ok=True)

# AnnData形式で保存
adata_xenium_sampled.write_h5ad(
    f'{output_dir}/xenium_processed_10k.h5ad'
)
adata_visium_filtered.write_h5ad(
    f'{output_dir}/visium_processed.h5ad'
)

# 結果の保存
ad_xenium_common.write_h5ad(
    f'{output_dir}/xenium_with_annotations.h5ad'
)

print(f"データを {output_dir} に保存しました")

5. 成功の鍵(まとめ)

5.1 必須事項(これがないと失敗)

1. ✅ ダウンサンプリング

目標: 10,000細胞程度(元の5%)
方法: 空間的に均等なサンプリング
理由: 計算量を現実的な範囲に

2. ✅ 共通遺伝子の選定

目標: 高品質な遺伝子のみ使用
基準: 両プラットフォームで100+細胞で発現
理由: ノイズを減らし、信頼性を向上

3. ✅ 空間近傍グラフの構築

方法: KNN (k=6) または距離ベース (radius=50µm)
理由: Xeniumは不規則な点群のため
ツール: squidpy.gr.spatial_neighbors()

5.2 推奨事項(やった方が良い)

4. 🔸 空間的均等サンプリング

ランダムサンプリングではなく、空間的に均等に
→ 組織の全領域を代表するサンプルを得る

5. 🔸 正規化の最小化

Visium用の複雑な正規化は避ける
→ log1p程度に留める
→ raw countsを使うモデルも多い

6. 🔸 セグメンテーションQC

低品質細胞を事前に除外
→ 総カウント < 50
→ 検出遺伝子数 < 20
→ 異常な細胞サイズ

5.3 注意事項(知っておくべき)

7. ⚠️ 計算リソース

推奨環境:
  - GPU: NVIDIA (CUDA対応)
  - メモリ: 16GB以上
  - ストレージ: 50GB以上

処理時間の目安:
  - データ読み込み: 1-5分
  - ダウンサンプリング: 1-2分
  - モデル実行: 10-60分(GPU使用時)

8. ⚠️ 結果の解釈

技術的な違いを考慮:
  - Visium: 複数細胞の混合
  - Xenium: 単一細胞(セグメンテーション依存)

→ 完全に一致する結果は期待できない
→ 傾向やパターンの一致を確認

9. ⚠️ バリデーション

複数の手法で結果を確認:
  - Tangram + SpaGCN
  - 異なるパラメータ設定
  - 既知のマーカー遺伝子での検証

5.4 トラブルシューティング

メモリ不足エラー

# 対策1: さらにダウンサンプリング
adata_xenium_sampled = spatial_uniform_sampling(adata_xenium, n_sample=5000)

# 対策2: スパース行列を維持
adata.X = scipy.sparse.csr_matrix(adata.X)

# 対策3: backed modeで読み込み
adata = sc.read_h5ad('data.h5ad', backed='r')

計算時間が長すぎる

# 対策1: GPU使用
device = 'cuda:0'  # Tangramなど

# 対策2: タイリング
tiles = create_spatial_tiles(adata, tile_size=1000)
# 各タイルを並列処理

# 対策3: より小さいサンプルでテスト
adata_test = adata[:1000]  # 1000細胞でテスト

結果が不自然

# チェック項目:
1. 共通遺伝子数は十分か (>1000推奨)
2. セグメンテーション品質は良いか
3. 正規化は適切か
4. パラメータは適切か (density_prior等)

# デバッグ:
# - 既知のマーカー遺伝子で可視化
# - QCメトリクスを再確認
# - より単純なモデルで試す

6. 参考リソース

6.1 主要ツール

ツール 用途 URL
Scanpy 基本的なデータ処理 https://scanpy.readthedocs.io/
Squidpy 空間解析 https://squidpy.readthedocs.io/
Tangram セルタイプマッピング https://github.com/broadinstitute/Tangram
SpaGCN 空間クラスタリング https://github.com/jianhuupenn/SpaGCN

6.2 関連論文

  1. Tangram: "Deep learning and alignment of spatially resolved single-cell transcriptomes with Tangram" (Nature Methods, 2021)
  2. SpaGCN: "SpaGCN: Integrating gene expression, spatial location and histology to identify spatial domains and spatially variable genes by graph convolutional network" (Nature Methods, 2021)
  3. Xenium技術: "Xenium In Situ: High-plex multiomic imaging in tissues" (10x Genomics)
  4. Visium HD技術: "Visium HD Spatial Gene Expression" (10x Genomics)

6.3 10x Genomics ドキュメント


7. まとめ

XeniumとVisiumの本質的な違い

Visium: 「場所」を測定
  - 物理的グリッド
  - 全トランスクリプトーム
  - 低解像度、高カバレッジ

Xenium: 「細胞」を測定
  - 生物学的単位
  - ターゲットパネル
  - 高解像度、限定的カバレッジ

統合解析の鍵

1. ダウンサンプリング(必須)
2. 共通遺伝子の選定(必須)
3. 空間グラフの構築(必須)
4. 適切な正規化(推奨)
5. QCとバリデーション(推奨)

期待できること

✅ セルタイプの空間分布
✅ 組織構造の理解
✅ 細胞間相互作用の推定
✅ マーカー遺伝子の発現パターン

期待できないこと

❌ 完全に一致する定量値
❌ パネル外の遺伝子情報
❌ 全細胞の解析(ダウンサンプリング必須)
❌ リアルタイム処理

このガイドが、XeniumとVisiumデータの統合解析の成功に役立つことを願っています。

質問や追加情報が必要な場合は、関連するNotebook (02_xenium_vs_visium_structure.ipynb) を参照してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?