一覧へ

ウェスタンブロットの定量化:NumPy画像配列を用いて、バンドの強度を再現性よく測定する

ゲル画像からウェスタンブロットのバンド強度をNumpyを使って自動で測定し、GAPDHをローディングコントロールとして正規化する。ImageJの代わりにPythonを使用する実践的なワークフロー。

中級
|
90
|
検証済み (2026-07)
ウェスタンブロット骨密度測定バンドの定量化読み込み制御GAPDHによる正規化タンパク質定量ROIの選択
進捗0/12 (0%)

ウェスタンブロット定量 — NumPyイメージ配列からバンド強度を再現性よく測定

このトピックを終えると

教科書で学んだ NumPy配列相関例外処理を組み合わせて、ウェスタンブロットゲル画像からバンド強度を自動で測定し、ローディングコントロールで正規化するツールを自分で作成できるようになります。ImageJのクリック操作をPythonスクリプトに置き換えることで、再現性を確保します。

この記事は、教育用一般的な例です。実際のデンシトメトリーでは、画像の自動バンド検出、背景補正、飽和検出など、より高度な処理が求められます。


「ImageJで100回クリック」—手作業による定量化の落とし穴

あなたが大学院3年次に最初にウェスタンブロッティングの定量化を学んだ方法は、おそらく以下のようなものでしょう。

  1. ImageJで画像を開く
  2. Rectangleツールでバンドを囲む
  3. Analyze > Measure をクリック
  4. 結果ウィンドウからIntDenの値(積分値)をコピー
  5. Excelに貼り付ける
  6. 次のバンド...を繰り返す

1つのゲルに8つのレーン×3つの抗体=24回。ゲルが10枚あれば240回。手作業なので、時間がかかり、間違いも起こりやすい

この手法の実際の問題点:

問題1: 再現性。あなたが描いた長方形の位置が正確に記録されません。「このバンドの強度をどのように測定しましたか?」と査読者が質問した場合、答える方法がありません。

問題2: 背景補正の恣意性。背景をどこにするかによって、最終的な強度が大きく異なります。ImageJの背景除去オプションを毎回同じ値で使用するのは困難です。

問題3: 規模。論文の締め切り前日に、20枚のゲルの画像が追加された場合、手作業では対応できません。

真の解決策は、Pythonスクリプトで定量化パイプラインを作成することです。各レーンの位置を座標で明示し、バンド領域のピクセル値をnumpyで計算し、ローディングコントロールで正規化し、結果をCSVに保存します。完全に同じスクリプトで、完全に同じ結果を再現できます。


ブラックボックスからパーツへ

パーツ 1: 画像 = NumPy 2D配列

ゲル画像は、各ピクセルが明るさの値を持つ2次元配列です。Pythonでは、PILimageioで読み込み、NumPy配列に変換します。

python
import numpy as np
from PIL import Image
def load_blot_image(path: str) -> np.ndarray:
img = Image.open(path).convert("L") # グレースケールに変換
return np.array(img)
image = load_blot_image("western_blot_01.png")
print(image.shape) # 例: (400, 800) — 高さ400、幅800ピクセル
print(image.dtype) # uint8 — 0〜255の明るさ

注意: 8ビット画像は、明るさ0〜255です。暗いバンドは低い値になります。デンスィトメトリーでは通常、反転させて使用します。つまり、明るさの代わりに密度 = 255 - 明るさを使用します。

python
density = 255 - image

パーツ 2: ROI(関心領域)の選択

バンドがある長方形の領域を座標で指定します。

python
def measure_band(density: np.ndarray, roi: tuple[int, int, int, int]) -> float:
"""
roi = (top, left, bottom, right)
返り値:領域内のピクセル密度の合計(積分密度)
"""
top, left, bottom, right = roi
region = density[top:bottom, left:right]
return float(region.sum())
band_intensity = measure_band(density, (100, 200, 130, 260))
print(f"バンド強度: {band_intensity}")

この関数は、ImageJのIntDen(積分密度)と同じ値を正確に計算します。

パーツ 3: 背景補正

バンドの近くにある背景領域も測定し、減算します。

python
def measure_band_with_background(
density: np.ndarray,
band_roi: tuple[int, int, int, int],
bg_roi: tuple[int, int, int, int]
) -> float:
band = measure_band(density, band_roi)
bg_area = (bg_roi[2] - bg_roi[0]) * (bg_roi[3] - bg_roi[1])
band_area = (band_roi[2] - band_roi[0]) * (band_roi[3] - band_roi[1])
bg_density_per_pixel = measure_band(density, bg_roi) / bg_area
background_contribution = bg_density_per_pixel * band_area
return band - background_contribution

この関数は、バンド領域の総密度から、同じサイズの背景の予想密度を引きます。背景はバンドよりも広く設定できるため、ピクセルあたりの密度で正規化した後、掛けます。

パーツ 4: ローディングコントロールによる正規化

ウェスタンブロットでは、**ローディングコントロール(通常はGAPDHまたはβ-アクチン)**の強度で正規化します。これは、各レーンにロードされた総タンパク質の量のわずかな違いを補正するためです。

python
def normalize_to_control(target: float, control: float) -> float:
if control == 0:
raise ValueError("Loading control cannot be zero")
return target / control

複数のレーンを一度に処理する:

python
def quantify_gel(
density: np.ndarray,
target_rois: list[tuple[int, int, int, int]],
control_rois: list[tuple[int, int, int, int]],
background_roi: tuple[int, int, int, int]
) -> list[float]:
"""
各レーンごとにターゲット強度とコントロール強度を測定し、
正規化された値を返します。
"""
if len(target_rois) != len(control_rois):
raise ValueError("Target and control ROIs must have same length")
normalized = []
for target_roi, control_roi in zip(target_rois, control_rois):
target = measure_band_with_background(density, target_roi, background_roi)
control = measure_band_with_background(density, control_roi, background_roi)
normalized.append(normalize_to_control(target, control))
return normalized

実戦検証 — ローディングコントロールとの相関関係

正規化が適切に行われたかどうかを確認する方法があります。ローディングコントロールの強度がお互いに大きく異なる場合、各レーンのロード量が大きく異なっていることを示します。正規化を行わずにターゲット強度をそのまま使用すると、この違いが結果を歪めてしまいます。

python
def check_loading_uniformity(control_intensities: list[float]) -> dict:
arr = np.array(control_intensities)
mean = float(arr.mean())
std = float(arr.std())
cv = std / mean if mean > 0 else float("inf")
return {
"mean": mean,
"std": std,
"cv": cv, # 変動係数
"acceptable": cv < 0.2 # 20%以下が安全範囲
}

CV(変動係数)が20%以上の場合、ゲルのロードが大きく不均一であったことを示します。この場合、実験をやり直すか、正規化後も結果の解釈に注意が必要です。

ターゲットとコントロールの相関関係も確認します。強い正の相関関係がある場合、ロード量の違いがターゲットの測定値を支配していることを意味し、これは正規化の必要性を正当化します。

python
def check_target_control_correlation(
targets: list[float],
controls: list[float]
) -> float:
t_arr = np.array(targets)
c_arr = np.array(controls)
return float(np.corrcoef(t_arr, c_arr)[0, 1])

値が0.7以上の場合、強い正規化効果があります。値が低い場合、ロード量の違いが大きくなく、正規化が結果に大きな影響を与えません。


フェーディング — 埋めるべき3つの空白

空白1:自動レーン検出

レーンの位置を手動で指定する代わりに、画像の下端を走査して明るさプロファイルからレーンを自動検出します。

python
def detect_lanes(density: np.ndarray, num_lanes: int) -> list[int]:
"""
画像の各縦位置の密度合計を計算し、
ピーク位置をレーンの中心として返します。
"""
column_profile = density.sum(axis=0)
# TODO: 上位のnum_lanes個のピークを見つけて、ソートされた位置のリストを返します
# ヒント: scipy.signal.find_peaksを使用できます
pass

ヒント: from scipy.signal import find_peaks; peaks, _ = find_peaks(column_profile, distance=min_lane_spacing).

空白2:バンド飽和の検出

バンドが強すぎてピクセルの値が255に達した場合、実際の強度がどの程度であるかを把握できません。

python
def check_saturation(
image: np.ndarray,
band_roi: tuple[int, int, int, int],
saturation_threshold: float = 0.05
) -> bool:
"""
ROI内のピクセルの中で、値が0(=完全に暗いバンド、反転すると255)である割合。
閾値を超えた場合、飽和していると判断します。
"""
top, left, bottom, right = band_roi
region = image[top:bottom, left:right]
# TODO: region内で値が0以下であるピクセルの割合を計算し、
# 閾値と比較してブール値を返します。
pass

ヒント: saturated_pixels = (region <= 0).sum(); ratio = saturated_pixels / region.size.

空白3:例外安全な定量化パイプライン

複数のゲル画像をバッチ処理する場合、1つの画像でエラーが発生しても、残りの画像は処理を継続する必要があります。

python
def batch_quantify(
image_paths: list[str],
roi_config: dict
) -> dict:
"""
各画像を定量化します。失敗した場合は、エラーとともに記録します。
返り値: {"results": [...], "errors": [...]}
"""
results = []
errors = []
for path in image_paths:
try:
# TODO: 画像をロードし、定量化し、結果をresultsにappendします。
pass
except Exception as e:
# TODO: どのファイルの、どのようなエラーが発生したかをerrorsに記録します。
pass
return {"results": results, "errors": errors}

ヒント: except (FileNotFoundError, ValueError) as e: で複数の例外の種類を捕捉するか、上位の Exception を捕捉し、詳細なログを出力します。

考察 — 実用的なデンシトメトリーツールとの違い

サブピクセル精度: 実用的なツールは、バンドの中心をサブピクセル精度で決定します。一方、あなたのROIは整数ピクセル単位です。

曲線的な背景: ゲルの画像は通常、均一ではなく、グラデーションがあります。実用的なツールは、ローリングボール背景除去などのアルゴリズムを使用して、曲線的な背景を除去します。これは、ImageJの基本的なオプションの一つです。

飽和の自動検出: あなたのアプローチは単純な閾値に基づいています。一方、実用的なツールは、ヒストグラム分析を使用して、より高度に飽和を判定します。

定量的なダイナミックレンジ: 化学発光ウェスタンブロットは、ダイナミックレンジが狭く、定量的な信頼区間は数倍程度です。実用的なツールは、標準曲線とともにロードし、各バンドがこの信頼区間内にあるかどうかを確認します。

蛍光ウェスタンブロット: LI-COR Odysseyのような蛍光システムは、より広いダイナミックレンジと正確な定量を提供します。原理は同じですが、画像の特徴が異なります。


拡張プロジェクト

1. matplotlibによる自動可視化: 各画像にレーンROIを重ねて保存。事後検証用。

2. 標準曲線: 既知の濃度の組換えタンパク質をいくつかのレーンにロードした後、標準曲線を作成。未知試料の絶対量を推定。

3. Streamlitアプリ: 既存のパイプラインをWeb UIでラップ。画像アップロード → ROI調整 → 結果ダウンロード。

4. 複数の抗体処理: 同じブロットを洗浄し、複数の抗体で再プローブしたデータ。各抗体ごとに個別のファイルを作成し、それらをマッチングさせて総合レポートを作成。


この章の構成要素

  • [F] NumPy 2次元配列: 画像 = 配列。スライスでROIを抽出し、sum()で強度を計算。
  • [F] 相関関係: np.corrcoef でターゲットとコントロールの相関を確認。正規化の必要性を検証。
  • [F] 例外処理: バッチ処理中に失敗したファイルを隔離。try-except の実戦的な使用。
  • [W] ファイル I/O · matplotlib: 画像のロードと結果の可視化(完成スクリプト)。

[F] = 自分で実装 / [W] = 完成コードとして提供。

💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...