輔助肺結節 (Lung Nodule) 報告的 AHK 工具

作為放射科醫師,最磨人的工作之一莫過於處理 Lung Nodule (肺結節) 的追蹤報告。在目前的醫院文化中,許多微小、subpleural(肋膜下)的小點,甚至有些根本不具備結節特徵的影像,都被要求詳盡報告。最痛苦的是:你可能要在前後兩份 CT 影像中比對十幾個點,每顆都要手動標註位置、Series 與 Image 序號。

當你看到前一份報告長得像下面這樣,真的會暈倒:

Tiny nodules/GGOs in RUL (Ser/Img:4/13,14,17,20,23,27), RLL (Ser/Img:4/24,28,29,39,46), LUL (Ser/Img:4/13,17) and LLL (Ser/Img:4/24,50) of lung, up to 7mm in LUL. No significant interval change.

為了找回工作的快樂,我決定用 AutoHotkey (AHK) 開發一個小工具。目標很明確:看到 Nodule 時按一下快速鍵,自動紀錄影像位置,最後一鍵產出格式化的文字。

第一版:Acc (Accessibility) 嘗試

觀察一下 PACS 畫面,我要的數值 Srs:4, Img:19 顯示在右上角,因我近期都改用 UIA 了,所以先試看看能不能找到儲存資料的欄位,用 UIATreeInspector 可以查看程式裡的樹狀結構,可惜沒有找到,不然用 AutomationId 來取值可能是最簡單又穩定的方式。

退而求其次,改從 Microsoft Active Accessibility (MSAA) 來抓取。在 Acc Viewer 裡,可以把左上角的十字拖放到要觀察的視窗元件上,看看能抓出什麼東西。雖然 PACS 畫面的右上角文字沒有 Control 可以抓,但左上角有個下拉選單可以得到影像的張數,如圖,得到 ComboBox10 這個 Control 裡,Value (19) 就是影像張數。

而畫面上可能會有好幾格影像,要先知道現在 focus 在哪個視窗,然後再去找他對應的影像編號 Control。

再觀察一下,發現不同畫面中,影像、張數的路徑是有規律的。例如,當滑鼠 focus 在 4.1.4.8.4 時,影像張數的 path 是 4.1.4.9.4.1.4.2.4,也就是第四層數字 +1,後面再接固定層級結構。

至此,我確定可以根據目前滑鼠游標所在位置,抓到 Img 數值。先做了第一版的 GUI,用不同的快速鍵對應不同的 lobe,把 nodule 位置先記錄下來,例如:

  • Alt + Q (RUL)
  • Alt + A (RML)
  • Alt + Z (RLL)
  • Alt + W (LUL)
  • Alt + S (LLL)

也設計了可以刪除的功能,然後按下 Copy Report 可以把結果統整好輸出:

RUL (Srs/Img: 4/30), LUL (Srs/Img: 4/21,38), and LLL (Srs/Img: 4/58)

第二版:OCR (文字辨識) 救援

其實只有 img 數值不夠,完整版應該是要有 series number,所以還是得想辦法取得。繼續觀察 PACS 畫面,發現右上角有個小小的文字寫 (4) Ch… 感覺可能是 Srs 數值,也用和 Img 相同的方式抓下來,但測試了幾個 case 發現它代表的是 series 在目前 study 裡的順序,不是 series number,也無法通過設定來修改。

後來找到一個設定,可以在視窗標題顯示 Series No。

用 Acc Viewer 可以定位得到,但由於控制項是自定義繪圖,並非標準 Control,即便定位得到也取不到文字(Text 為空),裡面 child 也都不是存這段文字的地方。

既然畫面有文字,便轉向 Windows 內建的 OCR 引擎

在 Windows 10 (Build 10240) 之後,微軟在作業系統內整合了基於 UWP (Universal Windows Platform) 架構的 OCR 引擎,正式名稱為 Windows.Media.Ocr。對於 AHK 開發者或輕量化工具開發者來說,這是一個極其強大的資源,因為它不需要安裝像 Tesseract 這樣肥大的第三方套件,就能擁有極高的辨識速度與多國語言支持。

透過 AHK 的 OCR Wrapper,對準視窗標題的座標進行辨識,可以成功取得 Srs 數值,或許不是 100% 可靠,但測起來基本上都是成功的。

第二版就整合完整的 Srs/Img 數值,經過排序,可以一鍵輸出報告:

RUL (Srs/Img: 2/25; 3/29), RML (Srs/Img: 2/35), RLL (Srs/Img: 2/41), and LUL (Srs/Img: 4/14)

這版還加了一些新的快速鍵,可以直接 copy 畫面所在的單張位置:

  • Alt + Shift + Q → RUL (Srs/Img: 2/25)
  • … RML, RLL, LUL, LLL 以此類推
  • Alt + Shift + C → (Srs/Img: 2/25)

目前已經是基本可用的狀態了。

第三版:極速優化 — ClassNN 定位

在寫這篇文章的時候,再看了一下 PACS 程式結構,發現 ClassNN 或許也是有規律的,這就多了一種新的可能性,因為不管是通過 Acc 或 UIA,效能都有點差,雖不到受不了的慢,但不是那種一按下去就瞬間完成的那種。如果能透過 ClassNN 就不一樣,ControlGetTextControlGetPos 是 Windows 原生 API 的封裝,理論上速度會快非常多,所以又研究了一下 ClassNN 的規律:

Pattern AFocusImgSrs
1st windowAfx:00400000:b:00000000:00000013:000000003ComboBox10AfxWnd140u47
2nd windowAfx:00400000:b:00000000:00000013:000000004ComboBox16AfxWnd140u50
3rd windowAfx:00400000:b:00000000:00000013:000000005ComboBox22AfxWnd140u53

規律是 focus +1, Img +6, Srs +3。不過比較麻煩的是,在不同螢幕、不同視窗排列模式下,至少有兩種起始模式:

Pattern BFocusImgSrs
1st windowAfx:00400000:b:00000000:00000013:000000005ComboBox57AfxWnd140u47
2nd windowAfx:00400000:b:00000000:00000013:000000006ComboBox63AfxWnd140u50
3rd windowAfx:00400000:b:00000000:00000013:000000007ComboBox69AfxWnd140u53

雖然分別表列讓程式碼寫起來比較繁瑣(需要寫多套對應模式),而且末來有沒有可能還有第三種模式也未知,但經過簡單的 benchmark 後,發現改用 ClassNN 實在快太多,還是值得採用:

最後改寫的這個第三版,和舊版 Acc 相比,平均大概可以快 10-30 倍。

現在按下快速鍵,瞬間就可以 copy 完了。爽啊…

成果展示

現在,我的工作流程變成了:

  1. 滾輪轉到 nodule 位置。
  2. 按下 Alt + Shift + Q (RUL) 或對應部位快速鍵。
  3. 工具自動紀錄:RUL (Srs/Img: 4/30)
  4. 全部標註完後,按下 Copy Report 鍵,瞬間生成:
    RUL (Srs/Img: 4/30), LUL (Srs/Img: 4/21,38), and LLL (Srs/Img: 4/58)
  5. 如果不是用在 lung nodule,也可以按下 Alt + Shift + C,直接將現有位置 copy 成 (Srs/Img: 16/28)

程式碼

https://gist.github.com/tsaiid/e44a091e7a6ac393c1ed7a3596a74b9c/ba3a1d171bb9ae3b6fe37aad47789537507bc907

備註

  1. 這個程式是用 AHK v2 開發,會依賴 Acc-v2OCR 這兩個 Lib(我有改名為 Acc.v2.ahk 和 OCR.v2.ahk)。
  2. 因 Windows 內建的 OCR 是在 Win10 後才提供,系統過舊也無法使用。
  3. 目前對應的 PACS viewer 是 Infinitt G3,但 Control 可能會因不同的螢幕和設定有所不同,需自行調整。

Leave a Reply