☰
3D Slicer DICOM 模块开发者指南:数据库、显示字段、插件架构与网络开发
2026/10/14 1:46:36 网站建设 项目流程
  • 医疗健康
  • 图像处理
  • 数据可视化
  • 桌面应用
  • 3D渲染

【免费下载链接】Slicer

Multi-platform, free open source software for visualization and image computing.

项目地址:https://gitcode.com/gh_mirrors/sl/Slicer
点击查看免费下载

本篇面向 Slicer 开发者,聚焦DICOM模块的底层实现与二次开发要点:理解 CTK 数据库 / DIMSE 网络层与 DICOM 插件架构的分工,掌握自定义浏览器表格列、显示字段生成规则、注册自定义 DICOM 插件的方法,并能调用 DCMTK 命令行工具、dicomweb-client与DICOMLib脚本库完成导入、标签访问与网络收发。读完你应能独立完成 DICOM 插件扩展、浏览器定制与 PACS 交互脚本。

Slicer 用户指南中的 DICOM 章节 面向使用者介绍了标准、数据库、插件与网络功能的全景;本文以开发者视角切入,围绕 DICOM 模块开发文档 展开,并用仓库源码印证每一处机制。

一、总体架构:CTK 数据库与 DIMSE 网络层,加上 DICOM 插件钩子

Slicer 中的 DICOM 实现由两部分代码构成,这一分工决定了开发时“数据库 / 网络”与“数据解释 / 场景接入”各自落在哪里:

  • CTK 代码:负责 DICOM 数据库(基于 SQLite 的本地索引)与 DIMSE 网络层的实现,用 C++ 编写,遵循 Qt 风格。数据库中缓存了每个文件的顶层 DICOM 标签,使得后续按标签检索无需重新解析文件头。
  • DICOM 模块:把 CTK 能力暴露给 Slicer 用户,并提供“钩子(hooks)”,让其他模块可以注册 DICOM 插件来处理特定 DICOM 数据对象到对应 MRML 表示的转换。数据一旦进入 Slicer,就可以通过标准 Slicer 机制(MRML 节点、Subject Hierarchy、IO)继续操作。

从源码结构看,这一架构在仓库中的落点很清晰:

  • 模块主体是脚本模块 DICOM.py。其中initializeDICOMDatabase()把slicer.app.dicomDatabase()赋给slicer.dicomDatabase,并聚合所有已注册插件的标签用于预缓存——这是 DICOM 快速加载的关键:导入时未预缓存的标签会在 Examine 阶段被逐个读取,性能差多个数量级。相关代码遍历slicer.modules.dicomPlugins并把每个插件的plugin.tags值并入slicer.dicomDatabase.tagsToPrecache。
  • 插件基类、加载/导出容器、数据库/网络工具集中在脚本库 DICOMLib,插件实现位于 DICOMPlugins。

数据库位置与打开

从initializeDICOMDatabase()可以看到:数据库目录按 CTK 的schemaVersion()存入 QSettings(键为DatabaseDirectory_<schemaVersion>),数据库文件固定为<目录>/ctkDICOM.sql;自动测试时则使用独立的临时数据库目录。这也解释了用户指南中“数据库只是本地磁盘上的一个目录(含若干 SQLite 文件与子目录),不要手工修改”这一说法。

二、Python 脚本开发可用的三类 DICOM 工具

原文档指出,除模块本身外,脚本化开发还可用三套工具,下面逐一给出源码级入口。

1. DCMTK 命令行工具(DICOMCommand)

随 Slicer 安装的 DCMTK 命令行工具可在脚本模块中同步调用。其封装在 DICOMProcesses.py 的DICOMCommand类中:构造时拼接exeDir下的可执行文件名,start()会追加 TLS 参数、启动进程并waitForFinished(),阻塞直到工具返回,最后返回标准输出;进程崩溃或退出码非 0 会抛出UserWarning。典型用法(把 PNG 转为 DICOM):

from DICOMLib import DICOMCommand command = DICOMCommand('img2dcm', ['image.png', 'output.dcm']) stdout = command.start() # 同步运行,阻塞直到 img2dcm 返回

2. dicomweb-client(DICOMweb 网络)

Python 模块dicomweb-client内置于 Slicer,用于 DICOMweb 网络。仓库中它被DICOMUtils.importFromDICOMWeb()直接驱动(见下文网络小节)。

3. DICOMLib 脚本模块

Slicer 特有的 DICOM 管理与网络实现集中在 DICOMLib。其中最常用的 DICOMUtils 提供了导入、按 UID/姓名/PatientID 加载、临时数据库、DICOMweb 导入、标签批量读取等函数,是本节和后续插件示例的基础。

三、自定义 DICOM 浏览器的表格列

DICOM 浏览器(Patient / Study / Series 三张表)的列可以重命名、重排、显隐。改动会被写入数据库的ColumnDisplayProperties表,因此每个数据库只需做一次定制即可持久生效。

以下脚本来自脚本仓库,展示如何取得浏览器、列出可用列并调整顺序 / 可见性 / 名称 / 宽度,最后关闭再重开数据库以强制刷新视图:

# 取浏览器与数据库 dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser dicomDatabase = dicomBrowser.database() # 打印可用列 print(dicomDatabase.patientFieldNames) print(dicomDatabase.studyFieldNames) print(dicomDatabase.seriesFieldNames) # 调整列顺序(weight) dicomDatabase.setWeightForField("Series", "SeriesDescription", 7) dicomDatabase.setWeightForField("Studies", "StudyDescription", 6) # 调整列可见性 dicomDatabase.setVisibilityForField("Patients", "PatientsBirthDate", False) dicomDatabase.setVisibilityForField("Patients", "PatientsComments", True) dicomDatabase.setWeightForField("Patients", "PatientsComments", 8) # 重命名列 dicomDatabase.setDisplayedNameForField("Series", "DisplayedCount", "Number of images") # 手动调整某列宽度(interactive = 可拖动) dicomDatabase.setFormatForField("Series", "SeriesDescription", '{"resizeMode":"interactive"}') # 定制 DICOM 浏览器内的表格管理器 dicomTableManager = dicomBrowser.dicomTableManager() dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection dicomTableManager.autoSelectSeries = False # 强制刷新数据库视图 dicomDatabase.closeDatabase() dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)

完整说明与更多变体见 脚本仓库 DICOM 部分。

四、自定义 DICOM 浏览器“显示内容”(显示字段生成规则)

浏览器表格里每个字段如何从原始 DICOM 标签生成,由**显示字段生成规则(displayed field generator rules)决定。这些规则是ctkDICOMDisplayedFieldGeneratorAbstractRule的子类,并且必须注册(register)**到 displayed field generator 才会参与生成。

机制要点(对照 CTK 头文件ctkDICOMDisplayedFieldGenerator.h/...AbstractRule.h/ctkDICOMDatabase.h):

  • 何时更新:显示字段在 1) 文件加入数据库,或 2) 数据库 schema 更新(用新版本 Slicer 打开旧数据库时会触发)时被更新。当前实现中只处理“从未生成过显示字段”的那些文件(实例)。
  • 规则职责声明:每条规则用数据库里缓存的 DICOM 标签来声明它负责哪些字段;标签通过规则的getRequiredDICOMTags请求缓存。
  • 逐实例生成再合并:更新显示字段时,先对每条规则调用getDisplayedFieldsForInstance(从原始标签生成自定义值),再对所有规则调用mergeDisplayedFieldsForInstance合并结果。每个字段可指定合并策略:
    • expect same value:只取唯一非空值,遇到冲突值时抛出警告;
    • concatenate:直接把各规则的显示字段值拼接起来。
  • 参考实现:CTK 内置了两条可直接参考的规则——default规则和RT(放射治疗序列描述)规则。

对开发者而言,扩展浏览器显示内容的正确姿势是:继承该抽象规则类、在getRequiredDICOMTags声明所需标签、在getDisplayedFieldsForInstance生成字段,并注册进 generator,从而参与合并。

五、插件架构:把 DICOM 数据对象映射为 MRML

DICOM 标准信息对象种类很多,全部放进 Slicer 核心并不现实(参见 用户指南中的 DICOM 插件说明)。因此扩展可以基于DICOMLib.DICOMPlugin实现自定义导入/导出类,并加入slicer.modules.dicomPlugins字典;DICOM 模块会依据这些插件决定可加载(loadable)与可导出(exportable)项。

5.1 基类与核心方法

DICOMPlugin 定义了插件需要覆盖的接口,以及一组可复用的辅助方法:

  • examineForImport(fileList)/examineFiles(files):审查一组文件并返回可加载项列表。默认实现examineForImport会按文件组做缓存(用文件列表的 MD5 作键,见getCachedLoadables/cacheLoadables/hashFiles),子类可覆盖examineFiles()复用缓存,或覆盖examineForImport()绕过缓存。旧的examine()仅为向后兼容。
  • load(loadable):把选中的DICOMLoadable转成 MRML 节点,返回节点;子类必须覆盖。
  • examineForExport(subjectHierarchyItemID)/export(exportable):导出方向,返回/处理DICOMExportable。
  • addSeriesInSubjectHierarchy(loadable, dataNode):加载后把 series 挂到 Subject Hierarchy 的 Patient / Study / Series 三层,并写入DICOM.*属性(modality、seriesNumber、frameOfReferenceUID、日期、患者信息等)。
  • mapSOPClassUIDToModality(sopClassUID)与mapSOPClassUIDToDICOMQuantityAndUnits(sopClassUID):把 SOP Class UID 映射为模态(MR/CT/PT)及带编码的测量量与单位(如 CT 的 Hounsfield unit、MR 信号强度),供结构化报告 / 参数化图等使用。
  • isDetailedLogging():读取DICOM/detailedLogging设置,允许插件在开启详细日志时输出更多调试信息。

5.2 加载项与导出项

  • 加载项容器DICOMLoadable携带files、name、tooltip、warning、selected、confidence等字段。confidence取值 0~1,表示该插件“这是最可能想要的解释”的把握;当多个插件对同一 series 都标记 selected 时,置信度最高者被默认选中,平手则都选中。选择逻辑见 DICOMUtils.selectHighestConfidenceLoadables。
  • 导出项DICOMExportable在 C++ 侧由 vtkSlicerDICOMExportable 实现,字段包括Name、Tooltip、SubjectHierarchyItemID、PluginClass、Directory、Confidence,以及一张**伪标签(pseudo-tags)**表Tags:这些“标签”在导出时才会被插件翻译为真实 DICOM 标签,并允许插件暴露压缩、命名约定等额外导出选项。

5.3 内置插件与注册模式

内置脚本插件在 DICOMPlugins/CMakeLists.txt 中列明:DICOMScalarVolumePlugin、DICOMSegmentationPlugin、DICOMParametricMapPlugin、DICOMVolumeSequencePlugin、DICOMImageSequencePlugin、DICOMEnhancedUSVolumePlugin、DICOMGeAbusPlugin、DICOMM3DPlugin、DICOMSlicerDataBundlePlugin、DICOMTID1500Plugin,以及(在多卷支持开启时)MultiVolumeImporterPlugin。

每个插件文件末尾都会把自己登记进slicer.modules.dicomPlugins,这是“扩展如何接入模块”的关键动作,例如 DICOMScalarVolumePlugin.py:

if not hasattr(slicer.modules, "dicomPlugins"): slicer.modules.dicomPlugins = {} slicer.modules.dicomPlugins["DICOMScalarVolumePlugin"] = DICOMScalarVolumePluginClass

DICOMScalarVolumePlugin还展示了插件如何向“应用程序设置 / DICOM”面板贡献配置项:settingsPanelEntry()注册了DICOM/ScalarVolume/ReaderApproach(GDCM / DCMTK / GDCM with DCMTK fallback / Archetype,其中 fallback 为默认)与DICOM/ScalarVolume/AcquisitionGeometryRegularization(default / none / transform / hardenTransform,后者需重启生效)。

扩展生态方面,DICOMExtensions.json 声明了若干外部扩展提供的插件能力及其匹配的Modality/SOPClassUID标签值(如 SlicerRT 的RTDOSE/RTSTRUCT/RTPLAN/RTIMAGE、SlicerHeart 的超声 SOP、PETDICOMExtension 的PT/RWVM)。

5.4 一次“加载”的完整调用链

从 DICOMUtils 可拼出脚本化加载的完整链路:

  1. importDicom(dicomDataDir, dicomDatabase):用ctkDICOMIndexer把目录加入数据库并waitForImportFinished()。
  2. getLoadablesFromFileLists(fileLists, ...):对每个启用的插件实例化并调用examineForImport,产出loadablesByPlugin。
  3. selectHighestConfidenceLoadables(...):按 series 选出最高置信度可加载项。
  4. loadLoadables(loadablesByPlugin):对每个 selected loadable 调plugin.load(loadable),并观察场景NodeAddedEvent/NodeRemovedEvent收集新节点 ID,把结果写回loadable.loadSuccess。

对外的高层便捷函数loadPatientByUID/loadPatientByName/loadPatientByPatientID/loadSeriesByUID/loadByInstanceUID都收敛到这条链。

六、DICOM 网络开发

Slicer 结合 CTK、DCMTK 与dicomweb-client提供 DICOM 网络能力:存储在远程 PACS 的图像可被查询、取回、在 Slicer 中标注后再写回。

6.1 本地测试环境

原文档建议开发者为不同需求搭建本地测试环境:

  • DIMSE 测试:用随 Slicer 分发的 DCMTKstorescp测试异步 C-STORE 请求。在 shell 中运行:

    "path/to/Slicer x.y.z/bin/storescp.exe" <port-number>

    然后在 Slicer 里把 DICOM 数据发送到localhost:<port-number>。

  • DICOMweb 测试:可本地部署开源 PACS(如 Orthanc、DCM4CHE)在隔离环境中验证。

6.2 仓库中的网络实现

  • C-STORE SCP(接收):DICOM.py 的startListener()用 DICOMListener 启动接收器;底层 DICOMStoreSCPProcess 运行 DCMTK 的storescp,默认端口 11112(StoragePort),带--accept-all、--output-directory与“收到即回调dcmdump打印摘要”的逻辑,并在启动前清理已存在的storescp进程。
  • 发送(C-STORE SCU):DICOMSender 支持两种协议:
    • DIMSE:逐文件调用 DCMTKstorescu(分段对象使用storescu-seg.cfg配置);
    • DICOMweb:通过dicomweb_client的 STOW-RS 发送,可对 Kheops 分享链接做特殊解析,并接受requests.auth.AuthBase认证。
    • 注意:HTTP 认证(auth)仅对 DICOMweb 有效,DIMSE 会给出警告。delayed=True时不立即发送,构造后手动send()。
  • 查询 / 检索(C-FIND / C-MOVE / C-GET):脚本中可用ctk.ctkDICOMQuery+ctk.ctkDICOMRetrieve组合完成查询与取回;ctkDICOMVisualBrowser与ctkDICOMScheduler(无 UI)也提供了面向服务器列表的查询/检索与任务跟踪能力。
  • DICOMweb 导入:DICOMUtils.importFromDICOMWeb 用dicomweb_client.api.DICOMwebClient先search_for_series找到 series,再逐 seriesretrieve_series_metadata判断是否已导入(按 SOP Instance UID 去重),随后iter_series(bulk)或逐实例retrieve_instance下载,写入数据库目录下的DICOMweb/<时间戳>临时目录并importDicom。支持accessToken(Bearer)或auth(AuthBase,如HTTPBasicAuth),二者都给定时优先用 AuthBase。

6.3 常用网络脚本片段

以下来自 脚本仓库 DICOM 部分,可直接粘贴到 Python 控制台。

从文件夹加载到场景(用临时数据库,不影响当前数据库):

dicomDataDir = "c:/my/folder/with/dicom-files" loadedNodeIDs = [] from DICOMLib import DICOMUtils with DICOMUtils.TemporaryDICOMDatabase() as db: DICOMUtils.importDicom(dicomDataDir, db) for patientUID in db.patients(): loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))

用 DICOMweb 从 PACS 导入(含访问令牌 / 基础认证 / 全局认证):

slicer.util.selectModule("DICOM") # 确保数据库已初始化 slicer.app.processEvents() from DICOMLib import DICOMUtils DICOMUtils.importFromDICOMWeb( dicomWebEndpoint="https://demo.kheops.online/api", studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819") # 使用 requests.HTTPBasicAuth 作为替代认证 # DICOMUtils.importFromDICOMWeb(..., auth=requests.HTTPBasicAuth('<user>','<password>')) # 配置跨会话记忆的全局认证 qt.QSettings().setValue(DICOMUtils.GLOBAL_DICOMWEB_USER_KEY, '<user>') qt.QSettings().setValue(DICOMUtils.GLOBAL_DICOMWEB_PASSWORD_KEY, '<pwd>') DICOMUtils.importFromDICOMWeb( dicomWebEndpoint="https://remote-url/", studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819", auth=DICOMUtils.getGlobalDICOMAuth())

用经典 DIMSE 从 PACS 查询并检索(useCGET=True用 C-GET,简单但很多临床 PACS 不开放;关闭后 PACS 会用 C-STORE 把数据推给 Slicer,需要 Slicer 的 C-STORE SCP 在运行且 AE Title 已在 PACS 侧配置):

dicomQuery = ctk.ctkDICOMQuery() dicomQuery.callingAETitle = "SLICER" dicomQuery.calledAETitle = "ANYAE" dicomQuery.host = "dicomserver.co.uk" dicomQuery.port = 11112 dicomQuery.setFilters({"Name": "A", "Modalities": "MR"}) tempDb = ctk.ctkDICOMDatabase() tempDb.openDatabase("") dicomQuery.query(tempDb) useCGET = True dicomRetrieve = ctk.ctkDICOMRetrieve() dicomRetrieve.callingAETitle = dicomQuery.callingAETitle dicomRetrieve.calledAETitle = dicomQuery.calledAETitle dicomRetrieve.host = dicomQuery.host dicomRetrieve.port = dicomQuery.port dicomRetrieve.setDatabase(slicer.dicomDatabase) for study, series in dicomQuery.studyAndSeriesInstanceUIDQueried: slicer.app.processEvents() if useCGET: success = dicomRetrieve.getSeries(study, series) else: dicomRetrieve.moveDestinationAETitle = dicomQuery.callingAETitle success = dicomRetrieve.moveSeries(study, series) slicer.dicomDatabase.updateDisplayedFields()

发送数据到 PACS(DIMSE 与 DICOMweb):

from DICOMLib import DICOMSender # DIMSE sender = DICOMSender(files=['path/to/0.dcm', 'path/to/1.dcm'], address='dicomserver.co.uk:9999', protocol="DIMSE", delayed=True) sender.send() # DICOMweb sender = DICOMSender(files=['path/to/0.dcm', 'path/to/1.dcm'], address='dicomserver.co.uk:9999', protocol="DICOMweb", auth=DICOMUtils.getGlobalDICOMAuth(), delayed=True) sender.send()

七、标签访问与导出脚本

7.1 读取标签(顶层与序列内嵌套)

db.fileValue读取的是缓存的顶层标签(不含嵌套序列);嵌套标签需要用pydicom.dcmread重新读文件头。

db = slicer.dicomDatabase patientList = db.patients() studyList = db.studiesForPatient(patientList[0]) seriesList = db.seriesForStudy(studyList[0]) fileList = db.filesForSeries(seriesList[0]) print(db.fileValue(fileList[0], "0020,0032")) # 顶层标签 import pydicom ds = pydicom.dcmread(fileList[0]) ds.CTExposureSequence[0].ExposureModulationType # 序列内嵌套标签

从已加载节点反查标签(不同插件保存 UID 的方式不同):DICOMScalarVolumePlugin把SOP Instance UIDs存到 volume 节点的DICOM.instanceUIDs属性;其他插件可能只把Series Instance UID存到 Subject Hierarchy 项上,需再经instancesForSeries换取实例。脚本仓库给出了两类反查示例(按节点GetAttribute("DICOM.instanceUIDs")与按shNode.GetItemUID(itemID, 'DICOM'))。

7.2 导出:体数据与分割对象

导出走“Subject Hierarchy 项 →examineForExport→ 设置directory与伪标签 →export”的路径,与加载侧的 loadable 对称:

# 导出标量体到 DICOM 文件 volumeNode = slicer.util.getNode("CTChest") outputFolder = "c:/tmp/dicom-output" shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient") studyItemID = shNode.CreateStudyItem(patientItemID, "test study") volumeShItemID = shNode.GetItemByDataNode(volumeNode) shNode.SetItemParent(volumeShItemID, studyItemID) import DICOMScalarVolumePlugin exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass() exportables = exporter.examineForExport(volumeShItemID) for exp in exportables: exp.directory = outputFolder exp.setTag('PatientID', "test patient") exp.setTag('StudyID', "test study") exporter.export(exportables)

分割对象导出到 DICOM Segmentation 的写法见 脚本仓库(关联分割节点与参考体节点到同一 study 项,再examineForExport+ 设置directory+export)。批量把整个数据库导出为 NRRD/NIfTI 的脚本也在其中,可按DICOM.PatientID/DICOM.StudyDate/DICOM.SeriesNumber/DICOM.Modality等 Subject Hierarchy 属性构造文件名。

八、参考与延伸阅读

  • DICOM 实现内部细节以 CTK 与 DCMTK 为准(ctkDICOMDatabase.h、ctkDICOMDisplayedFieldGenerator*.h、ctkDICOMDisplayedFieldGeneratorAbstractRule.h等头文件为权威定义)。
  • 常见 DICOM 操作示例(导入、标签访问、导出、查询/检索、DCMTK 工具调用)汇总在 脚本仓库 DICOM 部分。
  • 使用者侧功能与面板说明见 DICOM 用户指南。

本文所有源码路径均相对仓库根目录,便于按路径继续深入对应实现。

  • 医疗健康
  • 图像处理
  • 数据可视化
  • 桌面应用
  • 3D渲染

【免费下载链接】Slicer

Multi-platform, free open source software for visualization and image computing.

项目地址:https://gitcode.com/gh_mirrors/sl/Slicer
点击查看免费下载
上一篇:【免费下载】 NCMConverter:您的音频格式转换得力助手
下一篇:6款Mac免费开源游戏推荐:awesome-macOS仓库帮你按场景挑好了

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询