###### tags: `DICOM` # DICOM To FHIR ImagingStudy - Node.js # 所需知識 - FHIR Resource文件查看以及組成能力 - 基本的程式能力 - JSON資料格式 # 使用工具 - [Weasis](https://nroduit.github.io/en/getting-started/#try-weasis-now)或[RadiAnt DICOM Viewer](https://www.radiantviewer.com/download/?src=mbst&f=setup) (查看DICOM用) - [Visual Studio Code](https://code.visualstudio.com/Download) (撰寫程式用) # 程式語言 - [Node.js](https://nodejs.org/en/download/) # DICOM 架構 這邊使用TCIA,編號`0522c0001`的部分檔案做範例,[點我下載](https://yrouka-my.sharepoint.com/:u:/g/personal/a5566qq123_my365_com_tw/Eejy-0hUEOtPoL7IJ7l-rtwB-0xUOKPOfMJRPQBi9xKXCA?e=SVzR8f) 首先快速了解DICOM檔案的架構。 1個病人有多個Study(報告),Study內有多個Series(部位),Series當中有多個Instance(影像實例)。 ![image alt](https://dicom.nema.org/medical/dicom/current/output/chtml/part04/figures/PS3.4_C.6-1.svg) 以JSON來看大概會長這樣 ```json= { "study": { "series" : [ { "instance": [ ] } ] } } ``` 如果太過抽象,可以透過DICOM Viewer開啟提供的範例檔。 這裡使用Weasis開啟其中一個資料中所有的檔案。 ![](https://i.imgur.com/KiPe8FZ.png) - 藍色,以日期為標題框住的範圍為Study - 綠色,單一個正方形預覽圖為Series - 紅色,於正方形左下角之數字為此Series含有的Instance的數量 # DICOM欄位資訊 我們可以透過Weasis上的按鈕,來查看DICOM的欄位資訊,打開後請記得切換到`All DICOM attributes`。 ![](https://i.imgur.com/hpOmqZu.png) 圖中可以看到我們常用的`病人名稱`欄位,由以下幾項組成: - (0010,0010) -> 欄位標籤,解析DICOM時會用到 > 補充: [DICOM PS3.6 2021e - Data Dictionary 5 Conventions](https://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_5.html) A Data Element Tag is represented as (gggg,eeee), where gggg equates to the Group Number and eeee equates to the Element Number within that Group. - [PN] -> 欄位資料型態 - PatientName -> 欄位名稱 - 0552c0001 -> 數值 ![](https://i.imgur.com/1CXW4N0.png) 如果想要快速查詢`欄位標籤`可以使用以下網站輔助: - [DICOM Library](https://www.dicomlibrary.com/dicom/dicom-tags/) - [DICOM Standard Browser](https://dicom.innolitics.com/ciods) ## 如何知道DICOM檔是同個Study - DICOM欄位有三個UID,分別是`StudyInstanceUID`、`SeriesInstanceUID`、`SOPInstanceUID (InstanceUID)`。 - 只要是**相同**`StudyInstanceUID`的檔案,就代表是同個Study。 # 使用程式讀取DICOM 此範例將示範如何取出DICOM檔案某個欄位標籤的數值。 - 創建資料夾 - 用Visual Studio Code開啟資料夾 - 新增一個新的`Terminal` - 初始化專案 ``` npm init -y ``` - 安裝dicom-parser ``` npm i dicom-parser ``` - 開寫程式碼 ```javascript= const fs = require('fs'); const dicomParser = require('dicom-parser'); //讀取檔案 let fileBuffer = fs.readFileSync("./1.3.6.1.4.1.14519.5.2.1.5099.8010.199920086920823171706454903251/1.3.6.1.4.1.14519.5.2.1.5099.8010.260151978148957514594497217760/000001.dcm"); //將讀取後的Buffer轉成Uint8Array let uint8FileBuffer = new Uint8Array(fileBuffer); //使用DICOM Parser讀取DICOM檔 let dataSet = dicomParser.parseDicom(uint8FileBuffer); //取出 Patient Name 0010,0010的數值 let patientName = dataSet.string("x00100010"); //印出取得的數值 console.log("Patient Name: " + patientName); ``` # FHIR ImagingStudy 以上大致了解了如何讀取DICOM後,現在來了解FHIR ImagingStudy。 從[FHIR ImagingStudy官方文件](https://www.hl7.org/fhir/imagingstudy.html)以及下圖來看,ImagingStudy採用與DICOM一樣的架構來儲存資料(Study->Series->Instance)。 ![](https://i.imgur.com/UV6VrYx.png) ## Mappings 官方文件也提供了DICOM轉換成FHIR ImagingStudy的Mappings,可以說是非常地友善。 [ImagingStudy Mappings](https://www.hl7.org/fhir/imagingstudy-mappings.html) 使用此表格,我們就可以輕易地將DICOM轉成FHIR的格式。 ![](https://i.imgur.com/8Fri4q5.png) # DICOM轉成ImagingStudy ## 撰寫Data Types的Class 為方便使用及管理程式碼,首先要做的是幫 ImagingStudy 使用到的 Data Types 寫成 Class 以利組成 ImagingStudy。 - 創建`FHIRDataTypes.js`檔案並把 Class 都寫在此檔案 - 以`Identifier` type為例 ![](https://i.imgur.com/NQNtLoW.png) :::warning 注意 Identifier 底下還包含了幾種需要建成 Class 的 Type: - CodeableConcept - Period - Reference ::: ```javascript= //Coding class Coding { constructor() { this.system = undefined; this.version = undefined; this.code = undefined; this.display = undefined; this.userSelected = undefined; } } //CodeableConcept class CodeableConcept { constructor() { this.Coding = undefined; this.text = undefined; } } //Period class Period { constructor() { this.start = undefined; this.end = undefined; } } //Reference class Reference { constructor() { this.reference = undefined; //(Literal reference, Relative, internal or absolute URL)(string) this.type = undefined; //string this.identifier = undefined; this.display = undefined; } } //Identifier class Identifier { constructor() { this.use = undefined; this.type = new CodeableConcept(); this.system = undefined; this.value = undefined; this.period = new Period(); } } ``` 最後在一步一步把 ImagingStudy 的 Class 都寫完。 完整的FHIR Data Types Class: [Github 點我看程式碼](https://github.com/chinlin-example/dicom-to-fhir/blob/62df074ccc7bfe1702d0069ffebc4d6393dad744/nodejs/src/FHIRDataTypes.js) # 轉換主程式 上面已經把會用到的Data Types轉成 Class了,現在開始撰寫轉換的程式。 思路如下 - 讀取 DICOM 檔案 - 搭配[ImagingStudy Mappings](https://www.hl7.org/fhir/imagingstudy-mappings.html)對照 DICOM Tags 轉換到FHIR的欄位 - 使用dicom-parser讀取數值 - 利用剛剛寫的 Class 把數值填入對應的 Class - 把 ImagingStudy 組合起來 ## 套件 - dicom-parser (解析dicom用) - flat - lodash - moment ```bash= npm install dicom-parser flat lodash moment ``` 完整程式碼: [Github 點我看程式碼](https://github.com/chinlin-example/dicom-to-fhir/blob/62df074ccc7bfe1702d0069ffebc4d6393dad744/nodejs/src/DICOM2FHIRImagingStudy.js) ## 轉換結果 ```json= { "resourceType": "ImagingStudy", "id": "1.2.826.0.1.3680043.8.1055.1.20111102150758591.92402465.76095170", "identifier": [ { "use": "official", "system": "urn:dicom:uid", "value": "urn:oid:1.2.826.0.1.3680043.8.1055.1.20111102150758591.92402465.76095170" } ], "status": "unknown", "subject": { "reference": "Patient/0", "type": "Patient", "identifier": { "use": "usual", "value": "0" } }, "started": "2006-10-12T01:02:58.000Z", "description": "CT1 abdomen", "series": [ { "uid": "1.2.826.0.1.3680043.8.1055.1.20111102150758591.96842950.07877442", "number": 6168, "modality": { "system": "http://dicom.nema.org/resources/ontology/DCM", "code": "CT" }, "description": "ARTERIELLE", "instance": [ { "uid": "1.2.826.0.1.3680043.8.1055.1.20111102150758591.03296050.69180943", "sopClass": { "system": "urn:ietf:rfc:3986", "code": "urn:oid:1.2.840.10008.5.1.4.1.1.2" }, "number": 1, "title": "ORIGINAL\\PRIMARY\\AXIAL\\HELIX" } ] } ] } ``` # Support Me 文件創作花費了很多心血製作,如果你覺得很有幫助 不妨贊助我一下喝杯咖啡唄,[Support Me](https://portaly.cc/Li070/support)