288 lines
9.2 KiB
Python
Executable File
288 lines
9.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
交互式Gold Standard标注工具
|
||
通过问答方式完成标注,无需写代码
|
||
"""
|
||
|
||
import sys
|
||
sys.path.insert(0, '/home/xzq/gene-family-mas-v2')
|
||
|
||
from evaluation.gold_standard_annotator import GoldStandardAnnotator
|
||
import json
|
||
|
||
|
||
def print_header(text):
|
||
print("\n" + "="*60)
|
||
print(text)
|
||
print("="*60)
|
||
|
||
|
||
def get_input(prompt, default=None):
|
||
"""获取用户输入"""
|
||
if default:
|
||
full_prompt = f"{prompt} [{default}]: "
|
||
else:
|
||
full_prompt = f"{prompt}: "
|
||
|
||
value = input(full_prompt).strip()
|
||
return value if value else default
|
||
|
||
|
||
def select_from_list(prompt, options):
|
||
"""从列表中选择"""
|
||
print(f"\n{prompt}")
|
||
for i, opt in enumerate(options, 1):
|
||
print(f" {i}. {opt}")
|
||
|
||
while True:
|
||
choice = input(f"选择 (1-{len(options)}): ").strip()
|
||
try:
|
||
idx = int(choice) - 1
|
||
if 0 <= idx < len(options):
|
||
return options[idx]
|
||
except:
|
||
pass
|
||
print("无效选择,请重试")
|
||
|
||
|
||
def yes_no(prompt, default=True):
|
||
"""是/否问题"""
|
||
default_str = "Y/n" if default else "y/N"
|
||
while True:
|
||
answer = input(f"{prompt} [{default_str}]: ").strip().lower()
|
||
if not answer:
|
||
return default
|
||
if answer in ['y', 'yes']:
|
||
return True
|
||
if answer in ['n', 'no']:
|
||
return False
|
||
print("请输入 y 或 n")
|
||
|
||
|
||
def interactive_annotate():
|
||
"""交互式标注主流程"""
|
||
|
||
print_header("GeneFamily-RAG Gold Standard 交互式标注")
|
||
print("\n这个工具会引导你完成论文的人工标注")
|
||
print("标注结果将用于评估自动提取的准确率\n")
|
||
|
||
# ========== 步骤1:论文信息 ==========
|
||
print_header("步骤1:论文基本信息")
|
||
|
||
doc_id = get_input("论文ID(如pmid31286919)", "pmid31286919")
|
||
pmid = get_input("PMID", "31286919")
|
||
doi = get_input("DOI", "10.1186/s12870-019-1916-6")
|
||
species = get_input("物种拉丁名", "Fagopyrum tataricum")
|
||
species_cn = get_input("物种中文名", "苦荞")
|
||
family = get_input("基因家族", "SPL")
|
||
year = get_input("发表年份", "2019")
|
||
|
||
# ========== 步骤2:读取论文文本 ==========
|
||
print_header("步骤2:论文文本")
|
||
|
||
text_source = select_from_list(
|
||
"选择文本来源",
|
||
["从PDF自动提取", "手动粘贴文本", "从文件读取"]
|
||
)
|
||
|
||
text = ""
|
||
if text_source == "从PDF自动提取":
|
||
pdf_path = get_input("PDF文件路径",
|
||
"/home/xzq/gene-family-mas-v2/paper/test_v2_reproduce/input/苦荞SPL2019BMCplantbios12870-019-1916-6.pdf")
|
||
try:
|
||
import fitz
|
||
doc = fitz.open(pdf_path)
|
||
# 只提取前3页(Abstract + Results开头)
|
||
for page in doc[:3]:
|
||
text += page.get_text()
|
||
doc.close()
|
||
print(f"✓ 已提取 {len(text)} 字符")
|
||
except Exception as e:
|
||
print(f"✗ 提取失败: {e}")
|
||
text = get_input("请手动粘贴论文文本(Abstract + Results开头)")
|
||
|
||
elif text_source == "手动粘贴文本":
|
||
print("请粘贴论文文本(Abstract + Results开头,按Ctrl+D结束):")
|
||
lines = []
|
||
try:
|
||
while True:
|
||
line = input()
|
||
lines.append(line)
|
||
except EOFError:
|
||
pass
|
||
text = "\n".join(lines)
|
||
|
||
else: # 从文件读取
|
||
file_path = get_input("文本文件路径")
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
text = f.read()
|
||
|
||
if len(text) < 100:
|
||
print("✗ 文本太短,请重新运行")
|
||
return
|
||
|
||
print(f"✓ 文本长度: {len(text)} 字符")
|
||
print(f"预览: {text[:200]}...")
|
||
|
||
# ========== 步骤3:创建标注器 ==========
|
||
annotator = GoldStandardAnnotator(
|
||
doc_id=doc_id,
|
||
text=text,
|
||
title=get_input("论文标题", "Genome-wide identification of SPL gene family")
|
||
)
|
||
|
||
annotator.set_metadata(
|
||
pmid=pmid,
|
||
doi=doi,
|
||
species=species,
|
||
family=family
|
||
)
|
||
|
||
# ========== 步骤4:标注实体 ==========
|
||
print_header("步骤3:标注实体")
|
||
print("请从论文中识别关键实体(基因、家族、结构域、组织等)\n")
|
||
|
||
entity_types = [
|
||
"gene", "protein", "gene_family", "protein_domain", "motif",
|
||
"organism", "tissue", "developmental_stage", "treatment",
|
||
"phenotype", "assay", "method"
|
||
]
|
||
|
||
entity_count = 0
|
||
while yes_no("是否添加新实体?", True):
|
||
entity_count += 1
|
||
print(f"\n--- 实体 #{entity_count} ---")
|
||
|
||
entity_id = get_input("实体ID(如FtSPL1、SPL、PF03110)")
|
||
if not entity_id:
|
||
break
|
||
|
||
entity_name = get_input("实体名称", entity_id)
|
||
entity_type = select_from_list("实体类型", entity_types)
|
||
|
||
entity_species = None
|
||
if entity_type in ['gene', 'protein']:
|
||
entity_species = get_input("所属物种", species)
|
||
|
||
primary_id = get_input("稳定外部ID(如UniProt、PFAM编号,可选)", "")
|
||
description = get_input("描述(可选)", "")
|
||
|
||
# 添加到标注器
|
||
annotator.add_entity(
|
||
id=entity_id,
|
||
name=entity_name,
|
||
type=entity_type,
|
||
species=entity_species,
|
||
primary_id=primary_id if primary_id else None,
|
||
description=description if description else None
|
||
)
|
||
|
||
print(f"✓ 已添加: {entity_id} ({entity_type})")
|
||
|
||
print(f"\n共标注 {len(annotator.entities)} 个实体")
|
||
|
||
# ========== 步骤5:标注关系 ==========
|
||
print_header("步骤4:标注关系")
|
||
print("请标注实体之间的生物学关系\n")
|
||
|
||
relation_types = [
|
||
"member_of", "contains", "expressed_in", "involved_in",
|
||
"activates", "inhibits", "regulates", "orthologous_to",
|
||
"paralogous_to", "classified_into", "belongs_to"
|
||
]
|
||
|
||
# 显示已标注的实体供选择
|
||
print("已标注的实体:")
|
||
for e in annotator.entities:
|
||
print(f" - {e.id} ({e.type})")
|
||
|
||
relation_count = 0
|
||
while yes_no("是否添加新关系?", relation_count < 3):
|
||
relation_count += 1
|
||
print(f"\n--- 关系 #{relation_count} ---")
|
||
|
||
source = get_input("源实体ID")
|
||
target = get_input("目标实体ID")
|
||
rel_type = select_from_list("关系类型", relation_types)
|
||
confidence = select_from_list("置信度", ["high", "medium", "low"])
|
||
|
||
# 证据信息
|
||
print("\n证据信息(原文片段):")
|
||
snippet = get_input("原文关键句子(50字以内)")
|
||
section = select_from_list("所在章节",
|
||
["abstract", "introduction", "results", "discussion", "figure_legend"])
|
||
|
||
evidence_level = "experiment"
|
||
if confidence in ["high", "medium"]:
|
||
evidence_level = select_from_list("证据等级",
|
||
["experiment", "omics", "prediction", "curation"])
|
||
|
||
method = get_input("实验/分析方法(如qRT-PCR、RNA-seq)", "")
|
||
|
||
# 添加到标注器
|
||
annotator.add_relation(
|
||
source=source,
|
||
target=target,
|
||
type=rel_type,
|
||
confidence=confidence,
|
||
text_snippet=snippet,
|
||
section=section,
|
||
evidence_level=evidence_level,
|
||
method=method if method else None
|
||
)
|
||
|
||
print(f"✓ 已添加: {source} --[{rel_type}]--> {target}")
|
||
|
||
print(f"\n共标注 {len(annotator.relations)} 个关系")
|
||
|
||
# ========== 步骤6:关键发现 ==========
|
||
print_header("步骤5:关键发现")
|
||
print("请总结论文的主要发现(一句话一条)\n")
|
||
|
||
finding_count = 0
|
||
while yes_no("是否添加关键发现?", finding_count < 3):
|
||
finding_count += 1
|
||
finding = get_input(f"关键发现 #{finding_count}")
|
||
if finding:
|
||
annotator.add_key_finding(finding)
|
||
print(f"✓ 已添加")
|
||
|
||
# ========== 步骤7:验证和保存 ==========
|
||
print_header("步骤6:验证和保存")
|
||
|
||
# 显示摘要
|
||
annotator.print_summary()
|
||
|
||
# 验证
|
||
issues = annotator.validate()
|
||
if issues:
|
||
print("\n⚠️ 发现问题:")
|
||
for issue in issues:
|
||
print(f" - {issue}")
|
||
if not yes_no("是否仍要保存?"):
|
||
print("已取消保存")
|
||
return
|
||
|
||
# 保存
|
||
default_output = f"/home/xzq/gene-family-mas-v2/evaluation/gold_standard/{doc_id}_manual.json"
|
||
output_path = get_input("保存路径", default_output)
|
||
|
||
annotator.save(output_path)
|
||
|
||
print_header("标注完成!")
|
||
print(f"✓ Gold Standard已保存: {output_path}")
|
||
print(f"\n下一步:")
|
||
print(f" 1. 运行自动提取: python extract_with_llm.py ...")
|
||
print(f" 2. 对比评估: python evaluation/evaluate.py --gold {output_path} --pred <auto_result>")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
interactive_annotate()
|
||
except KeyboardInterrupt:
|
||
print("\n\n已中断")
|
||
except Exception as e:
|
||
print(f"\n✗ 错误: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|