AI病理分析:结构化证据提取链路怎么搭,才能真正进入科研流程
病理数字切片进入 AI 分析后工程难点往往不在“跑一次模型”而在切片切块、模型推理、区域聚合、证据沉淀和人工复核能否串成稳定链路。本文只讨论技术架构和工程流程示例不提供诊断、治疗、分诊或用药建议文中阈值和规则均为可配置示例真实项目应由医疗专业人员和机构规范确认。背景科研流程需要的是证据链不只是预测值在病理分析项目里单张 WSI 文件可能达到 GB 级别。后端服务如果直接把整张切片送进模型通常会遇到显存不足、推理耗时不可控、结果无法定位、复核人员看不到证据来源等问题。更适合科研工具的做法是把 WSI 拆成可追踪的小块每个 patch 产生模型输出再把 patch 级结果聚合为区域级、切片级证据。最终保存的不应只有labelpositive而应包含坐标、倍率、模型版本、置信度、聚合规则、复核状态和导出记录。技术目标与约束这类后端服务建议先明确 5 个目标WSI 原文件进入 object storage数据库只保存元数据和索引。OpenSlide 负责按倍率和坐标读取 patch避免一次性加载整图。PyTorch 模型只处理标准化 patch并记录模型版本。PostgreSQL 保存任务、patch、region、evidence、review 等结构化数据。FastAPI 暴露任务提交、状态查询、证据导出、复核回写接口。同时要避免把模型输出直接写死为科研结论。系统输出的是“可复核证据”不是自动结论。任何风险分层、升级规则或阈值都应实现为配置项并在项目落地时由机构规范确认。总体架构从 WSI 到 Evidence JSON下面是一个后端服务的简化链路WSI UploadObject StorageSlide MetadataTile SchedulerOpenSlide Patch ReaderPyTorch InferencePatch Result StoreRegion AggregatorEvidence JSONResearch ExportReview Feedback核心思想是让每一步都可重跑、可追踪、可审计。比如模型升级后只需要基于同一批 patch 重新推理聚合规则变更后只需要重算 region 和 evidence不必重复上传和解析 WSI。数据模型先把证据字段设计清楚我通常会把数据拆成 4 层slides切片级元数据如文件地址、扫描倍率、尺寸、hash。patches切块索引如 x、y、level、width、height、tissue_ratio。patch_predictions模型输出如 class、score、model_version、created_at。evidences区域聚合结果如 bbox、summary、rule_version、review_status。一个证据 JSON 可以长这样{slide_id:S20260524001,region_id:R-0007,bbox:{x:18432,y:9216,w:4096,h:3072},model_version:path-ai-v0.3.2,rule_version:agg-rule-2026-05-demo,summary:{positive_patch_count:18,mean_score:0.82,max_score:0.94},example_rule:mean_score configurable_threshold,review_status:pending}这里的configurable_threshold只是示例规则占位不代表任何通用医学标准。工程上要保留规则版本否则后续科研统计时很难解释不同批次结果的差异。实现步骤FastAPI 提交任务后台执行切块和推理下面代码展示一个最小可运行思路上传任务只登记 WSI 地址实际切块和推理由后台任务执行。生产环境建议把BackgroundTasks替换为 Celery、RQ 或 Kafka 消费者。fromfastapiimportFastAPI,BackgroundTasksfrompydanticimportBaseModelfromtypingimportList,Dictimportuuidimportopenslideimporttorch appFastAPI(titlePathology Evidence Pipeline Demo)classAnalyzeRequest(BaseModel):slide_uri:strslide_id:strlevel:int0patch_size:int512stride:int512model_version:strpath-ai-v0.3.2classEvidence(BaseModel):slide_id:strregion_id:strbbox:Dict[str,int]model_version:strsummary:Dict[str,float]review_status:strpendingdefload_model(model_version:str):modeltorch.nn.Sequential(torch.nn.Flatten(),torch.nn.Linear(512*512*3,2))model.eval()returnmodeldefinfer_patch(model,patch_image):# 示例真实项目需加入颜色标准化、组织区域过滤和模型预处理score0.8return{class_name:example_positive,score:score}defaggregate_predictions(slide_id:str,predictions:List[Dict],model_version:str):selected[pforpinpredictionsifp[score]0.75]ifnotselected:return[]xs[p[x]forpinselected]ys[p[y]forpinselected]scores[p[score]forpinselected]evidenceEvidence(slide_idslide_id,region_idfR-{uuid.uuid4().hex[:8]},bbox{x:min(xs),y:min(ys),w:max(xs)-min(xs)512,h:max(ys)-min(ys)512},model_versionmodel_version,summary{positive_patch_count:float(len(selected)),mean_score:float(sum(scores)/len(scores)),max_score:float(max(scores))})return[evidence.model_dump()]defrun_pipeline(req:AnalyzeRequest):slideopenslide.OpenSlide(req.slide_uri)width,heightslide.level_dimensions[req.level]modelload_model(req.model_version)predictions[]foryinrange(0,height-req.patch_size,req.stride):forxinrange(0,width-req.patch_size,req.stride):patchslide.read_region((x,y),req.level,(req.patch_size,req.patch_size)).convert(RGB)resultinfer_patch(model,patch)predictions.append({x:x,y:y,score:result[score],class_name:result[class_name]})evidencesaggregate_predictions(req.slide_id,predictions,req.model_version)# 示例生产环境应写入 PostgreSQL并记录任务状态、耗时和异常print({slide_id:req.slide_id,evidence_count:len(evidences)})app.post(/analysis/jobs)defcreate_job(req:AnalyzeRequest,bg:BackgroundTasks):job_iduuid.uuid4().hexbg.add_task(run_pipeline,req)return{job_id:job_id,status:submitted}这段代码没有追求模型真实性而是强调链路边界任务提交、OpenSlide 切块、patch 推理、区域聚合、证据输出。实际项目中应把模型加载、预处理、数据库写入、对象存储读取拆成独立模块。PostgreSQL 与对象存储怎么分工WSI 原文件、缩略图、patch 缓存适合放对象存储。PostgreSQL 更适合保存可查询的结构化索引例如任务状态、切块坐标、推理结果、证据区域和复核记录。一个常见错误是把 patch 图像二进制直接塞进数据库短期方便后期备份、迁移、查询都会变重。更稳妥的方式是数据库保存object_key、坐标和 hash需要展示时再从对象存储按需读取。证据表建议至少包含这些字段slide_id、region_id、bbox、model_version、rule_version、summary_json、review_status、reviewer_id、review_comment、updated_at。科研流程中复核回写和模型输出同等重要。踩坑记录工程化时最容易断的地方第一坐标系混乱。OpenSlide 的 level 坐标和原始 level 0 坐标要统一记录前端叠加标注时尤其容易偏移。建议 evidence 一律保存 level 0 坐标展示端自行换算。第二patch 数量失控。全量滑窗会让任务时间膨胀通常要先做组织区域过滤再进入模型推理。过滤规则可以从简单的背景比例开始但阈值必须作为配置项保存。第三模型版本和规则版本缺失。科研分析需要复现不记录版本就无法解释结果差异。每次导出 evidence 时应带上模型权重版本、推理参数和聚合规则版本。第四复核结果无法回写。只导出 CSV 而没有 review API会导致人工修订散落在表格里。建议提供PATCH /evidences/{id}/review把复核状态、备注和操作者写回系统。扩展方向让证据输出进入科研闭环当基础链路稳定后可以继续做三类扩展增加任务队列和 GPU worker实现多切片并行推理。增加 evidence 导出格式如 JSONL、CSV 和项目内部标注格式。增加复核一致性统计用于评估模型版本和规则版本的变化影响。需要强调的是系统的价值不止在于识别结果而在于把“结果来自哪里、由哪个模型产生、经过什么规则聚合、谁复核过”记录下来。只有证据输出和复核回写都闭合AI 病理分析才更容易进入可追踪、可复现的科研流程。本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】。