心電圖存成 DICOM 之後,要用什麼打開?
本篇是「ECG 心電圖小常識」系列的第 3 / 4 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。
起因
原本只是要確認某個匯出的心電圖 DICOM 檔裡,那段診斷文字是不是正確的,但我用手邊的 DICOM viewer 打開,畫面一片空白。檔案其實沒壞,是工具不對 XD
DICOM 不是「一種」檔案
副檔名都是 .dcm,裡面可能是完全不同的東西。決定它是什麼的是 SOP Class UID:
| SOP Class UID | 這是什麼 | 資料長什麼樣 |
|---|---|---|
1.2.840.10008.5.1.4.1.1.7 | Secondary Capture | 像素。通常是報告 PDF 轉成的圖 |
1.2.840.10008.5.1.4.1.1.9.1.1 | 12-Lead ECG Waveform | 沒有任何像素,只有一連串電壓取樣值 |
12-Lead ECG Waveform 的 PixelData 是空的,內容躺在 WaveformSequence 裡:12 個導程,每個導程數千個 16-bit 整數。
「12 導程」是哪 12 個、為什麼身上只接 10 條線,見本系列的為什麼心電圖要用 10 條導線?。
大部分 DICOM viewer 是為影像設計的(CT、MR、X 光)。丟一個沒有像素的檔案給它,得到空白畫面是預期行為,不是缺陷。
Weasis
Weasis 把波形當一等公民,作者 Nicolas Roduit,EPL 2.0 / Apache 2.0 雙授權。
確認它有獨立的波形模組,而不只是宣稱支援 —— 列出 OSGi bundle:
for j in $(find ~/.weasis/cache-*/ -name "bundle.jar"); do
unzip -p "$j" META-INF/MANIFEST.MF | awk '/^Bundle-SymbolicName:/{print $2}'
done | sort -u | grep dicom
weasis-dicom-codec
weasis-dicom-explorer
weasis-dicom-rt
weasis-dicom-sr
weasis-dicom-viewer2d
weasis-dicom-viewer3d
weasis-dicom-wave ← 波形模組
weasis-dicom-wave 是獨立 bundle,不是塞進影像檢視器的附加功能。
3 分鐘快速上手
裝與開
brew install --cask weasis
open -a Weasis your-ecg.dcm
也可以把檔案拖進視窗,或用 File → Open。
畫面長這樣

以下是 v4.7.2 實際畫面上的元件:
- 左側 DICOM Explorer — 載入的資料照 Patient / Study / Series 階層列出,縮圖標示為
ECG。上方有 Search tags 可搜欄位。 - 中央波形區 — 截圖的版面是 4 欄 × 3 列(I/aVR/V1/V4、II/aVL/V2/V5、III/aVF/V3/V6),最下方一條 II 導程的 rhythm strip。游標移到某導程時,上方會顯示該導程的 Minimum / Maximum 電壓。
- 右側 Measurements 面板 — 分成 Markers(游標量測結果)與 Annotations(Tag / Value 表格)兩區。
頂部三個下拉
| 控制項 | 畫面上的值 | 作用 |
|---|---|---|
Zoom 第一個下拉 | auto mm/s | X 軸走紙速度 |
Zoom 第二個下拉 | auto mm/mV | Y 軸增益 |
Display format | 4x2.5 Seconds with rhythm | 導程版面配置 |
4x2.5 Seconds with rhythm 指 4 欄、每欄 2.5 秒,外加一條 rhythm strip。這和本系列第一篇談的 6x2 格式是不同的版面慣例,下拉裡可以切換。
另外還有一個獨立的 Zoom: 100% 滑桿,等比縮放不改變上面兩個刻度。
Annotations 面板裡有什麼
這是這次真正要看的地方。表格內容直接來自 DICOM 的 WaveformAnnotationSequence:
| Tag | Value |
|---|---|
| Filter Low Frequency | 0.05 Hz |
| Filter High Frequency | 150.0 Hz |
| Notch Filter Frequency | 60.0 Hz |
| Text | Heart rate (<60 bpm)… |
| Ventricular Heart Rate | 62.0 {H.B.}/min |
| PR Interval | 167.0 ms |
| QRS Duration | 127.0 ms |
| QT Interval | 418.0 ms |
| QTc Interval | 428.0 ms |
| P Axis / QRS Axis / T Axis | 56.0 / 74.0 / 44.0 deg |
Text 那列就是 (0070,0006) UnformattedTextValue —— 判讀文字。其餘幾列則是各自帶 ConceptNameCodeSequence 的數值型 annotation。
什麼時候用 GUI,什麼時候用程式
這次的任務是「確認一段文字精確等於什麼」。GUI 做得到,但表格會截斷(截圖裡就顯示成 Heart rate (<60 bpm)Q…),而且看不出不可見字元。
三行 Python 更適合:
import pydicom
ds = pydicom.dcmread("your-ecg.dcm")
for item in ds.WaveformAnnotationSequence:
if "UnformattedTextValue" in item:
print(repr(item.UnformattedTextValue))
用 repr() 而不是 print()。這次要抓的是文字裡混進 HTML 跳脫字元(< 而不是 <),這種差異在 GUI 上、甚至在一般 print 輸出裡都容易看漏,repr() 會連跳脫字元和 \r\n 一起攤開。
適用場景:
| 要驗什麼 | 用什麼 |
|---|---|
| 波形長相、雜訊、間期量測 | Weasis |
| 某欄位的值精確是什麼 | pydicom + repr() |
| 檔案格式合不合規(SOP Class、必填欄位) | pydicom |
兩者驗的東西不同,不能互相取代。
截圖前的去識別化
要把畫面放進文件或簡報前,先把病患欄位換掉:
ds = pydicom.dcmread(src)
ds.PatientName = "DOE^JOHN"
ds.PatientID = "DEMO-0001"
ds.save_as(dst)
Weasis 的 DICOM Explorer 和分頁標籤都會顯示 Patient ID,截圖時很容易連帶入鏡。
另外,測試用的 .dcm 如果放在 git repo 目錄下,記得確認 .gitignore 有蓋到 —— *.dcm 適合預設 ignore。
重點整理
.dcm副檔名不代表同一種檔案,先看 SOP Class UID- 影像用的 viewer 打不開波形,是設計範圍問題不是缺陷
- Weasis 有獨立的
weasis-dicom-wave模組,判讀文字顯示在 Measurements → Annotations 的Text列 - 檢視波形用 GUI,驗證欄位值用 pydicom