emdb-database について
emdb-databaseは、EBIの電子顕微鏡データバンクEMDBを検索するスキルで、構造生物学の研究やデータ整理に利用できます。キーワード検索とエントリー単位のJSONから、マップのメタデータ、対応するPDBモデル、引用情報を取得します。
主な機能
- 01キーワードによるEMDBエントリー検索
- 02分解能・手法・サンプル情報の取得
- 03密度マップのファイル名とヘッダー情報の参照
- 04フィッティング済みPDB原子モデルの確認
- 05引用および出版情報の抽出
活用シーン
- 01タンパク質や複合体のクライオ電子顕微鏡マップ検索
- 02生物種や分解能によるエントリーの絞り込み
- 03ダウンロード用マップファイル情報の取得
- 04構造メタデータと引用情報の整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill emdb-databaseインストール後、Claude Code で/emdb-databaseを入力して読み込まれたか確認します。
詳細
EMDB データベース
概要
EBI の Electron Microscopy Data Bank(EMDB)は、高分子複合体(リボソーム、膜タンパク質、ウイルス、大規模複合体など)の 3D 電子顕微鏡密度マップ(主にクライオ電子顕微鏡法およびクライオ電子線トモグラフィー法によるもの、30,000 件以上)をアーカイブしています。アクセス先は 2 つのサービスに分かれています。
- EMDB Entry API(
https://www.ebi.ac.uk/emdb/api/entry/{EMD-XXXXX})— メタデータ、マップヘッダー、フィッティングされた PDB の一覧、引用情報を含む、エントリーごとの正規の JSON です。/map、/fitted、/publicationsのようなサブエンドポイントは存在しません。これらのデータはすべて、単一のエントリーレスポンス内にあります。 - EBI Search WS(
https://www.ebi.ac.uk/ebisearch/ws/rest/emdb)— 実際のキーワード検索バックエンドです(https://www.ebi.ac.uk/emdb/api/search/のみのエンドポイントはクエリを無視し、最新のエントリーを返すだけです)。
認証や API キーは不要です。
使用する場面
- キーワードでクライオ電子顕微鏡密度マップを検索する(例:「スパイクタンパク質」、「リボソーム 70S」)
- ChimeraX / PyMOL で使用する
.map.gz密度ファイルのダウンロード URL を取得する - EMDB マップにフィッティングされた PDB 原子モデルを特定する(およびその逆)
- 解像度、再構成方法、生物種、試料などのエントリーメタデータを取得する
- 生物種または解像度のしきい値で絞り込んでクライオ電子顕微鏡構造を一覧表示する
- EMDB エントリーの主要な引用情報(ジャーナル、DOI、PubMed ID)を取得する
- 実験で決定された原子座標が必要な場合は、代わりに
pdb-databaseを使用してください - AI が予測した構造には
alphafold-database-accessを使用してください。EMDB は実験による電子顕微鏡マップのみを対象としています
前提条件
- Python パッケージ:
requests、pandas、matplotlib - データ要件:EMDB エントリー ID(
EMD-XXXXX)、キーワード検索文字列、または相互参照用の PDB ID - 環境:インターネット接続。API キーは不要です
- レート制限:公式に公開された制限はありません。バッチループでリクエストの間に
time.sleep(0.2)を追加し、適切なアクセスを心がけてください
pip install requests pandas matplotlib
クイックスタート
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
# Keyword search via EBI Search WS (NOT /emdb/api/search/, which ignores q=)
r = requests.get(EBI_SEARCH,
params={"query": "sars-cov-2 spike", "size": 5, "format": "json",
"fields": "id,name,resolution,em_method,organism"},
timeout=30)
r.raise_for_status()
res = r.json()
print(f"Total hits: {res['hitCount']}")
for e in res["entries"]:
f = e["fields"]
name = (f.get("name") or [""])[0][:60]
resol = (f.get("resolution") or ["?"])[0]
print(f" {e['id']}: {name} ({resol} Å)")
コア API
クエリ 1:キーワード検索(EBI Search WS)
エントリーごとに指定された fields を含む、EMDB ID をキーとしたページ分けされた検索結果一覧を返します。
import requests, pandas as pd
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
def emdb_search(query, size=20, start=0,
fields="id,name,resolution,em_method,organism"):
r = requests.get(EBI_SEARCH,
params={"query": query, "size": size, "start": start,
"format": "json", "fields": fields},
timeout=30)
r.raise_for_status()
return r.json()
data = emdb_search("ribosome 70S", size=10)
rows = []
for e in data["entries"]:
f = e["fields"]
rows.append({
"emdb_id": e["id"],
"name": (f.get("name") or [""])[0],
"resolution_A": float((f.get("resolution") or [0])[0] or 0) or None,
"em_method": (f.get("em_method") or [""])[0],
"organism": (f.get("organism") or [""])[0],
})
df = pd.DataFrame(rows)
print(f"hitCount={data['hitCount']}; first {len(df)} rows:")
print(df.to_string(index=False))
# Paged retrieval — iterate `start` until exhausting hitCount
def emdb_search_all(query, size=100, max_pages=5,
fields="id,name,resolution,em_method"):
out = []
for page in range(max_pages):
d = emdb_search(query, size=size, start=page * size, fields=fields)
if not d["entries"]:
break
out.extend(d["entries"])
if len(out) >= d["hitCount"]:
break
return out
hits = emdb_search_all("ferritin", size=50, max_pages=2)
print(f"Pulled {len(hits)} ferritin entries")
クエリ 2:エントリーメタデータ
単一のエンドポイントから、すべてのメタデータ、マップヘッダー、フィッティングされた PDB の一覧、引用情報を含むエントリーが返されます。フィールドのパスは慎重に確認してください。大半のフィールドはネストされています。
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
def emdb_entry(emdb_id):
r = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30)
r.raise_for_status()
return r.json()
e = emdb_entry("EMD-30210") # nsp12-nsp7-nsp8 + Remdesivir (RdRp)
print(f"Title : {e['admin']['title']}")
print(f"Status : {e['admin']['current_status']}")
print(f"Key dates : {e['admin'].get('key_dates')}")
sd = e["structure_determination_list"]["structure_determination"][0]
ip = sd["image_processing"][0]
res = ip["final_reconstruction"]["resolution"]
print(f"Method : {sd['method']}")
print(f"Resolution : {res['valueOf_']} {res['units']} (type: {res['res_type']})")
クエリ 3:マップヘッダー/ダウンロード情報
entry["map"] には、ファイル名、形式、ボクセルグリッド、軸の順序、単位格子、等高線レベル、推奨表示しきい値が含まれています。
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
m = e["map"]
print(f"Map file : {m.get('file')}") # e.g. emd_30210.map.gz
print(f"Format : {m.get('format')}") # 'CCP4'
print(f"Dimensions : {m.get('dimensions')}") # {'col': ..., 'row': ..., 'sec': ...}
print(f"Axis order : {m.get('axis_order')}") # {'fast': 'X', 'medium': 'Y', 'slow': 'Z'}
print(f"Contour list : {m.get('contour_list')}") # recommended threshold(s)
# Conventional FTP/HTTPS download URL pattern (mirror at EBI):
EMDB_FTP = "https://ftp.ebi.ac.uk/pub/databases/emdb/structures"
emdb_num = "EMD-30210"
print(f"Download URL : {EMDB_FTP}/{emdb_num}/map/{m['file']}")
クエリ 4:フィッティングされた PDB 原子モデル
EMDB では、マップにフィッティングされた PDB エントリーを相互参照できます。各項目には PDB ID と関係を示すタグ(例:FULLOVERLAP)があります。
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
pdblist = e.get("crossreferences", {}).get("pdb_list", {})
pdb_refs = pdblist.get("pdb_reference", []) if isinstance(pdblist, dict) else []
print(f"Fitted PDB entries: {len(pdb_refs)}")
for ref in pdb_refs:
in_frame = (ref.get("relationship") or {}).get("in_frame")
print(f" {ref['pdb_id'].upper():6s} relationship={in_frame}")
# 7BV2 relationship=FULLOVERLAP
クエリ 5:引用情報と出版物
主要な引用情報は entry["crossreferences"]["citation_list"] にあります。形式は引用の種類(ジャーナルかプレプリントか)によって異なります。
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
citations = e.get("crossreferences", {}).get("citation_list", {})
primary = citations.get("primary_citation", {})
ct = primary.get("citation_type") or primary
title = (ct.get("title") or "").strip()
journal = ct.get("journal") or ct.get("book_title")
year = ct.get("year")
authors = [a.get("name") or a.get("name_str")
for a in (ct.get("author") or ct.get("author_order") or [])
if a]
xrefs = ct.get("external_references", []) or ct.get("xref", [])
doi = next((x.get("valueOf_") for x in xrefs if x.get("type") == "DOI"), None)
pmid = next((x.get("valueOf_") for x in xrefs if x.get("type") == "PUBMED"), None)
print(f"Title : {title[:80]}")
print(f"Journal : {journal} ({year})")
print(f"Authors : {', '.join(a for a in authors[:5] if a)}{'...' if len(authors) > 5 else ''}")
print(f"DOI : {doi}")
print(f"PMID : {pmid}")
クエリ 6:試料と生物種
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
sample = e.get("sample", {})
sm_list = (sample.get("supramolecule_list") or {}).get("supramolecule", [])
for sm in sm_list:
ns = sm.get("natural_source") or sm.get("natural_source_list", {}).get("natural_source") or []
if isinstance(ns, dict):
ns = [ns]
for n in ns:
org = (n.get("organism") or {}).get("valueOf_")
print(f" Supramolecule '{sm.get('name', {}).get('valueOf_', '?')}': {org}")
主要な概念
フィールドパスマップ(/api/entry/{id} ドキュメント)
| 項目 | パス |
|---|---|
| タイトル | admin.title |
| 公開日 | admin.key_dates |
| 著者 | admin.authors_list.author[*].name/name_str |
| 電子顕微鏡法 | structure_determination_list.structure_determination[0].method(例:singleParticle、tomography) |
| 解像度 | structure_determination_list.structure_determination[0].image_processing[0].final_reconstruction.resolution.valueOf_(文字列、Å 単位。float にキャストします) |
| マップファイル名 | map.file |
| マップの形式/寸法 | map.format / map.dimensions |
| 等高線レベル | map.contour_list |
| フィッティングされた PDB | crossreferences.pdb_list.pdb_reference[*].pdb_id |
| 引用情報 | crossreferences.citation_list.primary_citation.citation_type(ジャーナル/年/タイトル/著者/外部参照) |
| DOI/PubMed | type が {DOI, PUBMED} の …citation_type.external_references[] |
| 生物種 | sample.supramolecule_list.supramolecule[*].natural_source[*].organism.valueOf_ |
検索とエントリー
- キーワード検索 → EBI Search WS(
/ebisearch/ws/rest/emdb)。query=、size=、start=、fields=の選択にはこちらを使用してください。 /emdb/api/search/は信頼できません —q=を黙って無視し、最新のリリース済みエントリーを返します。これには依存しないでください。- エントリーの詳細 →
/emdb/api/entry/{EMD-XXXXX}。/map、/fitted、/publicationsのサブエンドポイントはありません — すべてエントリードキュメント内にあります。
一般的なワークフロー
ワークフロー 1:トピックの解像度フィルター
目標:高解像度のSARS-CoV-2スパイクエントリーを検索し、フィッティング済みのPDBモデルを報告します。
import requests, time, pandas as pd
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
# Step 1: keyword search via EBI Search
hits = []
for start in (0, 50):
r = requests.get(EBI_SEARCH,
params={"query": "sars-cov-2 spike", "size": 50, "start": start,
"format": "json", "fields": "id,name,resolution"},
timeout=30)
r.raise_for_status()
hits.extend(r.json()["entries"])
time.sleep(0.2)
# Step 2: filter to resolution ≤ 3.0 Å (skip entries with missing field)
rows = []
for h in hits:
f = h["fields"]
name = (f.get("name") or [""])[0]
try:
resol = float((f.get("resolution") or [None])[0])
except (TypeError, ValueError):
continue
if resol <= 3.0:
rows.append({"emdb_id": h["id"], "resolution_A": resol, "name": name[:60]})
df = pd.DataFrame(rows).sort_values("resolution_A")
print(f"Spike entries ≤ 3.0 Å: {len(df)}")
print(df.head(10).to_string(index=False))
# Step 3: pull fitted PDB ids for the top 5
for emdb_id in df["emdb_id"].head(5):
e = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30).json()
pdblist = e.get("crossreferences", {}).get("pdb_list", {}) or {}
pdbs = [p["pdb_id"].upper() for p in pdblist.get("pdb_reference", [])]
print(f" {emdb_id} -> PDB: {', '.join(pdbs) if pdbs else '(none fitted)'}")
time.sleep(0.2)
ワークフロー 2:コホートのメタデータテーブルを作成する
目標:クエリに対して、手法、解像度、生物種、マップのダウンロードURLを含むDataFrameを返します。サーベイ論文に便利です。
import requests, time, pandas as pd
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
EMDB_FTP = "https://ftp.ebi.ac.uk/pub/databases/emdb/structures"
def cohort_table(query, size=10):
r = requests.get(EBI_SEARCH,
params={"query": query, "size": size, "format": "json", "fields": "id"},
timeout=30)
r.raise_for_status()
rows = []
for h in r.json()["entries"]:
emdb_id = h["id"]
e = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30).json()
sd = e["structure_determination_list"]["structure_determination"][0]
ip = sd["image_processing"][0]
try:
resol = float(ip["final_reconstruction"]["resolution"]["valueOf_"])
except (KeyError, ValueError, TypeError):
resol = None
m = e["map"]
rows.append({
"emdb_id": emdb_id,
"title": e["admin"]["title"][:60],
"method": sd.get("method"),
"resolution_A": resol,
"map_url": f"{EMDB_FTP}/{emdb_id}/map/{m['file']}" if m.get("file") else None,
})
time.sleep(0.2)
return pd.DataFrame(rows)
df = cohort_table("ribosome 70S bacterial", size=6)
print(df.to_string(index=False))
df.to_csv("emdb_cohort.csv", index=False)
主なパラメーター
| パラメーター | エンドポイント | デフォルト | 範囲/オプション | 効果 |
|---|---|---|---|---|
query | EBI Search /emdb | 必須 | テキスト検索文字列 | 一致させるキーワード |
size | EBI Search /emdb | 15 | 1–100 | 1ページあたりのヒット数 |
start | EBI Search /emdb | 0 | 0以上の整数 | ページネーションのオフセット |
fields | EBI Search /emdb | (サブセット) | カンマ区切りのEBI Searchフィールド | ヒットごとに返すフィールド(id,name,resolution,em_method,organismなど) |
format | EBI Search /emdb | json | json、xml | レスポンス形式 |
(パス){EMD-XXXXX} | /api/entry/{id} | 必須 | EMDBアクセッション | 単一エントリーの詳細 |
ベストプラクティス
- キーワード検索には必ずEBI Search WSを使用してください。
https://www.ebi.ac.uk/emdb/api/search/はq=を無視して最新のリリースだけを返すため、これに依存すると、気付かないうちに誤ったコホートが生成されます。 - サブエンドポイントはありません。
/api/entry/{id}/map、/fitted、/publications、/api/statistics/は呼び出さないでください — すべて404(または/statistics/の場合はHTML)を返します。情報はすべて単一のエントリードキュメントから読み取ってください。 resolution.valueOf_は明示的にfloatにキャストしてください。 このフィールドは"2.5"のような文字列です。数値フィルターには明示的なキャストが必要です(値がないエントリーに備えてtry/exceptを使用してください)。- EBI Searchのフィールド値はリストとして返されます。
nameのような単一値のフィールドも{"name": ["..."]}として返されます — 必ず[0]でインデックスするか、結合してください。 - エントリーを1件ずつ処理するループには
time.sleep(0.2)を追加してください。 レート制限は公開されていませんが、APIは共有サービス上でホストされています。適切な間隔を空けることで、一時的な502エラーを回避できます。 - マップのダウンロードURLは
https://ftp.ebi.ac.uk/pub/databases/emdb/structures/{EMDB_ID}/map/{file}の形式です —entry["map"]["file"]から導出し、ハードコードしないでください。
よく使うレシピ
レシピ:PDBからEMDBへの相互参照
import requests
# Given an EMDB ID, get its fitted PDB; given a PDB ID, you'd query
# RCSB PDB /rest/v2/entry/{pdb_id} and read `rcsb_external_references.emdb_id`.
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
pdblist = e.get("crossreferences", {}).get("pdb_list") or {}
print({"emdb": e["emdb_id"],
"pdbs": [p["pdb_id"].upper() for p in pdblist.get("pdb_reference", [])]})
レシピ:ある生物種の上位の解像度
import requests, time, pandas as pd
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
def best_by_organism(organism, size=50):
r = requests.get(EBI_SEARCH,
params={"query": f'organism:"{organism}"', "size": size,
"format": "json", "fields": "id,name,resolution"},
timeout=30)
r.raise_for_status()
rows = []
for h in r.json()["entries"]:
f = h["fields"]
try:
resol = float((f.get("resolution") or [None])[0])
except (TypeError, ValueError):
continue
rows.append({"emdb_id": h["id"], "resolution_A": resol,
"name": (f.get("name") or [""])[0][:60]})
return pd.DataFrame(rows).sort_values("resolution_A").reset_index(drop=True)
df = best_by_organism("Saccharomyces cerevisiae", size=20)
print(df.head(8).to_string(index=False))
レシピ:引用情報のエクスポート(BibTeX対応)
import requests
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
ct = (e.get("crossreferences", {})
.get("citation_list", {})
.get("primary_citation", {})
.get("citation_type")) or {}
authors = [a.get("name") or a.get("name_str")
for a in (ct.get("author") or ct.get("author_order") or [])]
xrefs = ct.get("external_references", []) or ct.get("xref", [])
doi = next((x.get("valueOf_") for x in xrefs if x.get("type") == "DOI"), "")
print({"title": (ct.get("title") or "").strip(),
"journal": ct.get("journal"),
"year": ct.get("year"),
"authors_n": len(authors),
"doi": doi})
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
EMDB 検索で KeyError: 'results' または 'numFound' が発生する | /api/search/ は生の JSON 配列を返し、q= を無視します | EBI Search WS(/ebisearch/ws/rest/emdb)を使用します。ラッパーは {hitCount, entries, facets} です |
/api/entry/{id}/map(または /fitted、/publications)で HTTP 404 が発生する | これらのサブエンドポイントは存在しません | 単一エントリーのレスポンスから entry["map"]、entry["crossreferences"]["pdb_list"]、entry["crossreferences"]["citation_list"] を読み取ります |
| 分解能が文字列として返される | EMDB では数値フィールドが文字列として保存されています | float(entry[...]['resolution']['valueOf_']) を使用し、try/except で囲みます |
| EBI Search のフィールドが要素数 1 のリストに見える | EBI Search は複数値フィールドをリストとして返します | f["name"][0] を読み取ります(または (... or [""])[0] にフォールバックします) |
EBI Search の entries が空になる | フィールド修飾子が間違っているか、タイプミスがあります | フィールド修飾子を外してプレーンテキストで検索します。https://www.ebi.ac.uk/ebisearch/ で確認してください |
/api/statistics/ から HTML レスポンスが返される | /statistics/ は JSON ではなく HTML を返します | このエンドポイントはウェブ UI 用です。プログラムから呼び出さないでください |
エントリーの pdb_list が {} になる | マップに当てはめられた原子モデルがありません | これは正しい状態です。トモグラムやサブトモグラム平均の多くには、当てはめられた PDB がありません |
関連スキル
pdb-database— EMDB の相互参照における原子モデル側の RCSB PDBalphafold-database-access— AI 予測構造(実験的な EMDB マップを補完)uniprot-protein-database— EMDB サンプルの生物種/配列の背景情報を特定cellxgene-census— 構造調査を補完する組織/細胞型の発現データ
参考資料
- EBI の EMDB — 閲覧、ダウンロード、統計、登録
- EMDB エントリー API — エントリー JSON の例
- EBI Search WS — EMDB — キーワード検索のドキュメント
- EMDB FTP ミラー — 密度マップとメタデータのダウンロード
- Lawson CL et al.「EMDB — 電子顕微鏡データバンク」 Nucleic Acids Research 52(D1): D456–D465(2024)。https://doi.org/10.1093/nar/gkad1019