Skip to content

EEG、ECG 與睡眠分期的時間序列標註

以 30 秒為一幀進行睡眠分期,或在 EDF+ 和 WFDB 記錄上標記區間和事件,再把每位標註者的標籤匯出為 BIDS events.tsv 檔案。

v2.13.0 新增

time_series 顯示類型在瀏覽器中繪製多通道記錄,interval_label 方案在記錄上記錄帶標籤的區間和點事件。睡眠研究者把這兩者配合起來做睡眠分期。睡眠分期為多導睡眠圖的每個 30 秒幀(epoch)判定一個 AASM 分期,即 W、N1、N2、N3 或 REM。腦電(EEG)研究者標記偽跡和癲癇發作,心臟病學研究者審閱來自 PhysioNet 的心電圖(ECG)記錄中的心搏和節律,可穿戴裝置研究者在加速度計資料中標註活動片段,天文學家在光變曲線中標記凌星或耀發。關於一段記錄的其他問題,例如品質評分或備註,放在圖旁邊的普通方案中。

歐洲資料格式(European Data Format,EDF)是交換和儲存睡眠記錄、EEG 記錄等醫學時間序列的標準格式,EDF+ 在此基礎上增加了註釋和帶間斷的記錄。WFDB 是 PhysioNet 的記錄格式。一條 WFDB 記錄由一個 .hea 標頭檔案、若干 .dat 訊號檔案以及可選的註釋檔案(例如 .atr)組成。

  • 格式: EDF 和 EDF+(連續與不連續)、BDF 和 BDF+、WFDB 記錄、帶時間列的 CSV 或 TSV、Parquet,以及一個小型內聯 JSON 物件。EDF 和 BDF 檔案通過記憶體對映讀取,不需要額外的庫。
  • 已有標籤: EDF+ 註釋、WFDB .atr 檔案和腦成像資料結構(Brain Imaging Data Structure,BIDS)的 events.tsv 會作為預標註載入。標註者可以保留、編輯或丟棄這些預標註。
  • 長記錄: 瀏覽器從不下載整段記錄。對於螢幕上顯示的時間範圍,伺服器為每個通道的每個畫素最多傳送兩個值(原始取樣值,或每個畫素內的最小值和最大值),因此 8 小時的記錄開啟起來和 30 秒的記錄差不多快。
  • 離線: 訊號檔案由 Potato 伺服器從媒體目錄或任務目錄中讀取。URL、絕對路徑和 .. 越界路徑一律拒絕。

WFDB 記錄和 Parquet 檔案需要 signals 可選依賴(wfdb 和 pyarrow):

bash
pip install 'potato-annotation[signals]'

配置

下面的配置在一份四通道多導睡眠圖上以 30 秒為一幀進行睡眠分期。第二個方案讓標註者在同一張圖上點選標記覺醒:

yaml
annotation_task_name: "Sleep staging"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/psg.json]
item_properties: {id_key: id, text_key: note}
 
instance_display:
  fields:
    - key: signal               # item field holding the EDF file name
      type: time_series
      label: "PSG"
      display_options:
        window: 30
        channels:
          - {name: "EEG Fpz-Cz", range: [-120, 120]}
          - {name: "EOG", range: [-150, 150]}
          - "EMG chin"
          - "Resp"
 
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: interval_label
    name: stage
    description: "Sleep stage of each 30-second epoch"
    signal_field: signal
    mode: epoch
    epoch_length: 30
    require_all_epochs: true    # counted only while the scheme is required
    label_requirement: {required: true}
    labels:
      - {name: W, key_value: "1"}
      - {name: N1, key_value: "2"}
      - {name: N2, key_value: "3"}
      - {name: N3, key_value: "4"}
      - {name: REM, key_value: "5"}
    preannotation_labels:       # EDF+ hypnogram names -> scheme labels
      "Sleep stage W": W
      "Sleep stage 1": N1
      "Sleep stage 2": N2
      "Sleep stage 3": N3
      "Sleep stage R": REM
      "Arousal": null
 
  - annotation_type: interval_label
    name: arousals
    description: "Arousals (click on the plot)"
    signal_field: signal
    mode: free
    pre_annotations: false
    labels:
      - {name: arousal, key_value: "a", kind: event}

channels 列表決定顯示哪些通道行以及它們的順序,range 以通道自身的單位固定某一行的幅度刻度。固定的範圍讓 EEG 通道行在每一幀中保持相同的刻度。preannotation_labels 對映把 EDF+ 睡眠圖(hypnogram)中的分期名稱改為方案的標籤,並丟棄檔案中的覺醒註釋。第二個方案關閉了 pre_annotations,標註者在這個方案中手動標記覺醒。require_all_epochs 與 label_requirement 同時設定時,每一幀都有分期之前,Next 一直不可用。每個方案的 signal_field 必須等於某個 time_series 欄位的 key,兩者不一致時伺服器拒絕啟動。

每個條目給出相對於 media_directory 的訊號檔案路徑:

json
{"id": "night-01", "note": "Night 1", "signal": "psg/night-01.edf"}

用於 EEG 審閱的自由模式

EEG 審閱使用自由模式。在自由模式下,標註者在圖上拖動來標註一個區間,點選來標記一個點事件。下面這段部分配置替換上面配置中的 annotation_schemes:

yaml
annotation_schemes:
  - annotation_type: interval_label
    name: eeg_events
    description: "Mark artifacts and seizures, and click each spike"
    signal_field: signal
    mode: free
    channel_scope: either       # all channels, or the lane the drag starts on
    labels:
      - {name: artifact, key_value: "1"}
      - {name: seizure, key_value: "2"}
      - {name: spike, key_value: "3", kind: event}

設定 channel_scope: either 後,圖上方會出現一個開關,用來選擇新區間作用於所有通道還是某一個通道行。電極偽跡可能只出現在一個通道行上,而全面性癲癇發作橫跨所有通道。若要在已分期的幀內標記呼吸暫停或覺醒,保留分幀方案,再新增一個 signal_field 相同的自由模式方案,就像上面的睡眠配置對覺醒所做的那樣。兩個方案要使用不同的標籤按鍵。

訊號檔案

條目中所寫檔案的副檔名決定使用哪個讀取器:

副檔名格式依賴
.edfEDF、EDF+(連續與不連續)無
.bdfBioSemi BDF、BDF+(24 位)無
.heaWFDB 記錄(標頭檔案,加上標頭檔案列出的 .dat 檔案)signals 可選依賴
.csv、.tsv一個時間列,加上每個通道一列無
.parquet、.pq相同的佈局,以 Parquet 儲存signals 可選依賴
.json下文的內聯物件,存為檔案(最大 50 MB)無
  • EDF+。 每條有時長的註釋成為一個作用於所有通道的預標註區間,每條沒有時長的註釋成為一個點事件。在 EDF+D 檔案中,每個資料記錄放在其計時註釋給出的起始時刻上,記錄之間的空隙畫成線條的斷開。如果檔案比頭部宣告的短,就讀到最後一個完整的記錄為止,並在圖下方顯示警告。
  • WFDB。 用記錄的 .hea 檔案來指定記錄,訊號檔案放在同一目錄下。<record>.atr 中的心搏註釋成為以其符號(N、V 等)為標籤的點事件,(AFIB 之類的節律註釋成為 AFIB 區間,一直延續到下一次節律變化。取樣值按視窗逐段讀取,長的 Holter 記錄從不整體載入。讀取器不支援多段記錄,多段記錄需要先拼接再載入。
  • CSV 和 Parquet。 設定了 time_column 時以它為時間列;否則取第一個名為 time、t、timestamp、seconds、sec、time_s、datetime、mjd 或 jd 的列;再否則取第一列。其餘每個數值列都是一個通道,表頭中的單位(ax (g)、ay [m/s^2])成為該通道的單位。如果一個序列的所有時間步都落在中位時間步的 0.1% 以內,就按等間隔取樣處理,否則保留每個取樣點自己的時間。CSV 和 Parquet 檔案整體載入,上限為 256 MB。更大的記錄請轉換為 EDF。
  • 內聯 JSON。 小訊號可以直接放在條目裡,所有通道合計最多 500,000 個取樣點。

內聯物件為等間隔取樣給出 sampling_rate,或為不規則序列(例如光變曲線)給出 time 列表,每個取樣點一個時間。這兩個鍵也可以按通道設定。t0 設定第一個取樣點的時間,可以寫在頂層,也可以按通道設定。data 中的 null 表示缺失的取樣點,start_time 給出 t = 0 對應的掛鐘時間,供 time_format: clock 使用:

json
{"id": "demo", "signal": {
  "sampling_rate": 100,
  "start_time": "2024-01-01T22:00:00",
  "channels": [
    {"name": "Fz", "unit": "uV", "data": [0.1, 0.4, 0.2]},
    {"name": "Resp", "unit": "a.u.", "sampling_rate": 10, "data": [0.0, 0.3]}
  ]
}}

對於 EDF、BDF、WFDB 以及帶 sampling_rate 的內聯資料,時間是從記錄開始算起的秒數。對於 CSV 和 Parquet,時間就是時間列中寫入的值(秒、天或 MJD),日期時間列會換算為距第一行的秒數。儲存的標籤使用與檔案相同的時間基準。

顯示選項

選項寫在 display_options 下,或直接寫在欄位上:

time_series 選項預設值含義
window30初始顯示的秒數,也是前進和後退控制元件的步長
channels全部要顯示的通道及其順序,寫成名稱或 {name, label, range: [low, high], color}
layoutstackedstacked 為每個通道畫一行,overlay 把所有通道畫在同一個 y 軸上並附圖例
drawlineline,或用於稀疏、不規則序列的 points
amplituderecordingrecording 按通道在整個檔案上的範圍(第 0.5 到第 99.5 百分位)縮放每一行,window 按螢幕上顯示的內容重新縮放。通道的 range 優先於這兩者
lane_height56堆疊佈局中每行的畫素高度,取值 20 到 400
height由通道行數決定圖的畫素高度,取值 80 到 4000。不設定時,高度為通道行數乘以 lane_height 再加 48,並限制在 160 到 1400 之間
time_formatautoelapsed(h:mm:ss)、clock(由 start_time 推算的掛鐘時間)或 number。auto 在 time_unit 為 s 時表示 elapsed,否則表示 number
time_units時間列的單位,用於以天或 MJD 計時的 CSV 或 Parquet 資料
invert_yfalse在疊加布局中翻轉 y 軸,用於天文星等
time_column自動CSV 或 Parquet 的時間列
annotation_extensionatr作為預標註讀取的 WFDB 註釋檔案,null 表示不讀取
max_file_mb2048可讀取的最大訊號檔案,單位為 MB。更大的檔案會被拒絕
overviewtrue在圖下方顯示一條概覽條,包含整段記錄、已標註的片段和當前視窗。點選或拖動概覽條可以移動視窗

engine 選項只接受 uplot 一個值。由於訊號檔案總是在伺服器上讀取,這個顯示類型拒絕 allow_remote。

方案選項

interval_label 選項預設值含義
signal_field必填要標註的 time_series 欄位的 key
labels必填名稱,或 {name, key_value, color, kind, tooltip}。kind 為 interval(預設)或 event
modefreefree 用於拖動和點選,epoch 用於按順序標註固定視窗
epoch_length30每幀的秒數
channel_scopeallall 把標籤作用於所有通道,channel 作用於拖動開始所在的通道行,either 顯示一個開關
auto_advancetrue在分幀模式下,按下標籤鍵後跳到下一幀
allow_overlap自由模式下為 true,分幀模式下為 false為 false 時,新區間會替換同一通道上其他區間被重疊的部分
next_key、previous_key]、[移動視窗的按鍵
pre_annotationstrue從檔案自帶的註釋(EDF+、WFDB .atr 或內聯)開始
preannotation_field無一個條目欄位,存放 BIDS events.tsv 路徑,或以儲存格式寫出的註釋
preannotation_labels無把檔案中的標籤重新命名為方案標籤,對映到 null 的標籤會被丟棄。重新命名後的註釋只有在方案以相同的 kind 聲明瞭該標籤時才會保留
require_all_epochsfalse在分幀模式下,每一幀都有標籤之前,Next 一直不可用。僅在方案為必填時生效
min_annotations無條目的區間和事件合計達到給定數量之前,Next 一直不可用。僅在方案為必填時生效
instructions無顯示在標籤上方的文本

沒有 key_value 的標籤依次獲得按鍵 1 到 9,然後是 q 到 p。標籤顏色預設使用 Okabe-Ito 調色盤。Okabe-Ito 配色在常見的色覺缺陷下仍然可以區分。

分幀評分與標記區間

在分幀模式下,記錄從開頭起被切成長度為 epoch_length 的視窗,標註者一次看一幀。按分期鍵會標註當前幀並跳到下一幀,[ 和 ](或 Page Up 和 Page Down)只移動不標註,“Clear this epoch”清除當前幀的標籤。概覽條按分期給每個已評分的幀著色,點選概覽條會把視窗移到點選處。

在自由模式下,標註者先按標籤鍵或點選標籤按鈕,然後在圖上拖動來標註一個區間;如果所選標籤是事件標籤,則點選來標記一個點。選中區間標籤時,點選會選中指標下的標記,按標籤鍵會為這個標記更換標籤,Delete 刪除這個標記,Escape 取消選擇。所有標記都列在圖下方,各帶 Show 和 Delete 按鈕,這個列表就是不用滑鼠、只用鍵盤審閱標籤的途徑。

A− 和 A+ 按鈕降低或提高所有通道行的幅度。在獲得焦點的圖上,左右方向鍵移動一個視窗,而不像 Potato 其他地方那樣在條目之間切換;Shift 加方向鍵會把視窗平移十分之一。

條目沒有已儲存的答案時,檔案中的註釋以虛線疊加層顯示,並附一條提示。標註者第一次改動時,或點選“Keep these”時,疊加層會被儲存,“Discard them”則清除疊加層。在分幀模式下,檔案中的一段睡眠圖會被拆分到它覆蓋至少一半的那些幀中。

儲存的答案

每個方案為每個條目儲存一個 JSON 物件:

json
{"intervals": [{"start": 30.0, "end": 60.0, "label": "N2", "channel": null},
               {"start": 41.2, "end": 43.0, "label": "artifact", "channel": "EOG"}],
 "events":    [{"time": 52.25, "label": "arousal", "channel": null, "source": "file"}]}

channel 是通道名稱,null 表示所有通道。來自檔案且未被改動的標記保留 "source": "file"。

匯出

多名標註者可以為同一段記錄評分,人數由 num_annotators_per_item 設定。兩種訊號匯出都把每位標註者的標籤分開儲存:

格式輸出
interval_labelintervals.csv 和 intervals.jsonl,每個區間或事件一行,列為 user_id, instance_id, schema, kind, start, end, duration, label, channel, source。事件的 start 等於 end,時長為 0;標註者新增的標記 source 為 annotator
bids_events每位標註者、每個條目一個 <annotator>/<instance>_events.tsv,列為 onset, duration, trial_type, channel(所有通道記為 n/a),另附一個 events.json 附屬檔案。有多個 interval_label 方案時,每個方案有自己的資料夾
bash
python -m potato.export -c config.yaml -f bids_events -o exports/bids

通用的 CSV 和 JSONL 匯出也包含答案,每個方案佔一個 JSON 單元格。在分幀模式下,每個已評分的幀是一個單獨的區間,因此 intervals.csv 中每位標註者的每一幀各佔一行。Potato 的一致性指標不覆蓋 interval_label 方案。睡眠分期的逐幀統計量,例如 Cohen's kappa,請在匯出後從 intervals.csv 計算。

示例

bash
python potato/flask_server.py start examples/advanced/time-series-intervals/config-epochs.yaml -p 8000

這個示例有兩個條目,指向同一份合成的五分鐘 EDF+ 多導睡眠圖,用於以 30 秒為一幀評分。config-epochs.yaml 旁邊的 config.yaml 在同一段記錄以及一份兩分鐘的腕部加速度計 CSV(附 BIDS events.tsv)上執行自由模式。

用程式設計智慧體搭建研究

potato-skill 1.2.0 面向 Potato 2.13.1,認得 interval_label 和 time_series 這兩個名稱,並指導研究設計這一側,例如向標註者問什麼、每段記錄由幾名標註者評分、培訓和注意力檢查,以及匯出。這個技能目前還沒有記錄訊號檔案格式、顯示選項和 signals 可選依賴。安裝 potato-skill,把研究描述給你的智慧體,再讓智慧體參考本頁的 Markdown 副本 https://www.potatoannotator.com/docs/annotation-types/time-series.md,以瞭解格式和選項。

延伸閱讀

有關實現細節,請參閱源文件。