diff --git a/PSG_label_2_BCG_label.py b/PSG_label_2_BCG_label.py index 6e38fc0..46c7750 100644 --- a/PSG_label_2_BCG_label.py +++ b/PSG_label_2_BCG_label.py @@ -1,4 +1,3 @@ -import os import pandas as pd import pyedflib from pathlib import Path @@ -10,37 +9,35 @@ def get_time_to_seconds(time_str): base_event = ["Hypopnea", "Central apnea", "Obstructive apnea", "Mixed apnea"] # 输入设置(每次运行此脚本都必须检查) -sampID = 888 -dir_path = r"D:\code\SA_Label\data" +dir_path = Path(r"E:\data_annotation\6SleepApnea_annotation_GUI_demo\data") -# 数据路径设置 -PSG_Data_Path = Path(os.path.join(dir_path, "PSG")) -PSG_Label_Path = Path(os.path.join(dir_path, "PSG_label")) -BCG_Data_Path = Path(os.path.join(dir_path, "BCG")) -BCG_Label_Path = Path(os.path.join(dir_path, "BCG_label")) +PSG_Data_Path = dir_path / "PSG" +PSG_Label_Path = dir_path / "PSG_label" +BCG_Data_Path = dir_path / "BCG" +BCG_Label_Path = dir_path / "BCG_label" -# 读取PSG标签 -df_PSG_label = pd.read_csv(PSG_Label_Path / (f"export" + str(sampID) + ".csv"), encoding="gbk") -df_PSG_label = df_PSG_label.loc[:, ~df_PSG_label.columns.str.contains('^Unnamed')] -df_PSG_label = df_PSG_label[df_PSG_label["Event type"].isin(base_event)] -df_PSG_label['Duration'] = df_PSG_label['Duration'].str.replace(r' \(.*?\)', '', regex=True) +sampIDs = PSG_Label_Path.glob('*.*') +for sampID in sampIDs: + sampID = sampID.name.replace("export", "").replace(".csv", "") + # 读取PSG标签 + df_PSG_label = pd.read_csv(PSG_Label_Path / (f"export" + str(sampID) + ".csv"), encoding="gbk") + df_PSG_label = df_PSG_label.loc[:, ~df_PSG_label.columns.str.contains('^Unnamed')] + df_PSG_label = df_PSG_label[df_PSG_label["Event type"].isin(base_event)] + df_PSG_label['Duration'] = df_PSG_label['Duration'].str.replace(r' \(.*?\)', '', regex=True) -# 读取EDF文件 -edf_File = pyedflib.EdfReader(str(PSG_Data_Path / f"A{str(sampID).rjust(7, '0')}.edf")) + # 读取EDF文件 + edf_File = pyedflib.EdfReader(str(PSG_Data_Path / f"A{str(sampID).rjust(7, '0')}.edf")) -# 获取PSG记录开始时间 -start_time = str(edf_File.getStartdatetime()).split(" ")[1] -start_time_abs = get_time_to_seconds(start_time) + # 获取PSG记录开始时间 + start_time = str(edf_File.getStartdatetime()).split(" ")[1] + start_time_abs = get_time_to_seconds(start_time) -# 计算起始时间秒数和终止时间秒数 -df_PSG_label['Start'] = (df_PSG_label['Time'].apply(get_time_to_seconds) - start_time_abs).apply(lambda x: x + 24 * 3600 if x < 0 else x).astype(int) -df_PSG_label['End'] = df_PSG_label['Start'] + df_PSG_label['Duration'].astype(float).round(0).astype(int) + # 计算起始时间秒数和终止时间秒数 + df_PSG_label['Start'] = (df_PSG_label['Time'].apply(get_time_to_seconds) - start_time_abs).apply(lambda x: x + 24 * 3600 if x < 0 else x).astype(int) + df_PSG_label['End'] = df_PSG_label['Start'] + df_PSG_label['Duration'].astype(float).round(0).astype(int) -# 打印结果 -print(df_PSG_label) + # 写入csv文件 + df_PSG_label.to_csv(dir_path.name + r"\BCG_label\export" + str(sampID) + "_all.csv", index=False, encoding="gbk") -# 写入csv文件 -df_PSG_label.to_csv(dir_path + r"\BCG_label\export" + str(sampID) + "_all.csv", index=False, encoding="gbk") - -# 打印结果 -print("sampID_" + str(sampID) + "写入csv成功") \ No newline at end of file + # 打印结果 + print("sampID_" + str(sampID) + "写入csv成功") \ No newline at end of file diff --git a/README.md b/README.md index 420ebd4..4b43538 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,24 @@ -# SleepApnea_annotation_GUI——睡眠呼吸暂停事件打标程序 +# SA_Label——睡眠呼吸暂停事件打标程序 + +``` +项目作者信息 +@author:Yosa, Marques +@email:2023025086@m.scnu.edu.cn, 2021022362@m.scnu.edu.cn +@time:2025/1/4 +``` 本程序提供符合用户直接的操作界面,用于打标睡眠呼吸暂停事件,对医生所做的原始标注进行修改,亦可对医生忽略的片段进行新增标注,对医生打错的片段进行标注删除。 -## 0 说明 +程序截图: +![0](/img/0.png) + +## 0 项目说明 ### 0.1 程序说明 -本程序的输入是经过对齐处理后的BCG信号和PSG信号,BCG事件的类型、事件起始时间和终止时间,分别对应于BCG_label文件夹中的`Event type`、`Start`、`End`列。 +本程序的输入是经过对齐处理后的BCG信号和PSG信号,BCG事件标签,包括有BCG事件的类型、事件起始时间和终止时间,分别对应于BCG_label文件夹中的`Event type`、`Start`、`End`列。 + +这里的BCG事件标签通过对齐后的PSG标签直接复制粘贴得到。 本程序的输出是标签类型、备注、修正后起始时间、修正后终止时间、修正后事件类型,分别对应于BCG_label文件夹中的`score`、`remark`、`correct_Start`、`correct_End`、`correct_EventTypes`列。 @@ -14,40 +26,71 @@ 本程序在Python3.9的环境下开发,在Intel® Core™ i5-13600K Processor和AMD Ryzen™ 7 8845H平台下都能成功运行。 +### 0.3 文件目录说明 + +``` +.../img # 存放README.md中的图片 +.../logs # 存放软件运行的日志,命名规则为"年月日.log" + |-(YYYYMMDD.log) +.../utils # 存放主程序需要用到的工具函数 + |-Preprocessing.py # 一些信号预处理所用到的函数 + |-Quality_Relabel.py # 算是遗产文件,里面被需要的代码已集成到入口程序中,因此删去此文件并不影响程序的运行 +.../Main_Quality_Relabel.py # 打标程序入口,主函数在里边 +.../MainWindow.py # 对MainWindow.ui执行PyQt的pyuic后生成的ui代码 +.../MainWindow.ui # 使用qtdesigner设计的ui文件 +.../PSG_label_2_BCG_label.py # 将对齐后的PSG_label转化为符合本打标程序执行的BCG_label文件的脚本 +.../README.md # 就是你现在正在查看的说明文档 +.../requirements.txt # 执行本程序所需的依赖库 +``` + ## 1 程序安装与启动 ### 1.1 直接运行可执行文件 -在开发者做出exe可执行文件之前,都需要自行搭建环境来运行程序。 +点击`版本发布`,下载里面的最新版的exe可执行文件,双击exe即可直接运行。 ### 1.2 在开发环境中运行 -推荐使用conda环境,直接在conda控制台中运行如下指令创建python3.9版本的环境。 +推荐使用conda环境,直接在conda控制台中运行如下指令创建python3.9版本的环境。或者也可以使用python虚拟环境。 ``` conda create -n python=3.9 ``` -激活环境后通过conda install命令手动安装如下依赖库,部分需通过pip install命令进行安装。版本不一定要完全对应,当默认安装的版本无法启动时可尝试安装对应版本的依赖库。 +激活环境后通过`pip install -r requirements.txt`来安装依赖库。下表是`requirements.txt`文件中提及的依赖库和对应的版本。 ``` Pack Name | Version -----------|---------- +matplotlib | 3.8.3 numpy | 1.26.4 pandas | 2.2.3 -matplotlib | 3.8.3 -tqdm | 4.66.4 -pyyaml | 6.0.1 +pillow | 11.1.0 +pyEDFlib | 0.1.38 PyQt5 | 5.15.10 -pyedflib | 0.1.38 +PyQt5-Qt5 | 5.15.2 +PyQt5_sip | 12.16.1 +PyYAML | 6.0.1 scipy | 1.15.1 +six | 1.17.0 +openpyxl | 3.1.5 ``` +**对于开发者来说,若通过pyinstaller来build导出exe可执行文件,请跳转到第3节查看相关说明。** + ## 2 使用方法与流程 ### 2.0 界面截图 ![1](/img/1.png) -### 2.1 打开数据 +### 2.1 将事件标签的时间标准化 + +此步骤的输入是PSG_label文件夹中的对应样本ID的事件标签,输出是BCG_label文件夹中对应样本ID的事件标签,其csv文件中再加上`Start`和`End`两列数据。 + +操作方法:修改PSG_label_2_BCG_label.py中的dir_path,是数据存放路径,然后直接运行程序即可。(注意是指所有数据的存放路径,而不是深入到`PSG_label`文件夹) + +**这个步骤只需要操作一遍,建议由睡眠呼吸暂停方向的总负责人执行此步骤后将执行后的数据一同放到对应的数据文件夹中,之后再分发给其他同学进行打标。** + +### 2.2 打开数据 点击左上角的`打开`,点击`数据路径选择`。 @@ -74,7 +117,7 @@ scipy | 1.15.1 ![3](/img/3.png) -### 2.2 开始前的设置 +### 2.3 开始前的设置 当正确操作`打开数据`步骤后,界面截图中的`开始前的设置`中的内容将被允许更改。 @@ -86,10 +129,12 @@ scipy | 1.15.1 ![4](/img/4.png) -### 2.3 打标时的操作 +### 2.4 打标时的操作 按需设置好2.2步骤中的内容后,点击`打标操作`中的`开始打标`,在这之后`开始前的设置`将无法被修改,若需修改,则需要直接关闭程序后重新运行。 +**!!!若在点击`开始打标`后程序崩溃,请检查BCG_label文件夹中该样本ID所对应的csv文件里是否含有`Start`和`End`两列数据,若没有,那就是你忘记了操作2.1步骤。** + 对数据进行初次打标时,程序会在BCG_label的csv文件中新起6列,分别是`score`、`correct_Start`、`correct_End`、`isLabeled`、`remark`、`correct_EventsType`,对应如下所示,`打标操作`中所做的修改将在用户点击`确定达标参数`后即刻写入到BCG_label对应的csv文件中。 用户无需手动输入`修正后起始时间`和`修正后终止时间`的值,程序提供更为方便的打标方法。在`绘图区域`的下面有一行工具栏,我们需要用到的是工具所对应的图标是`房子`、`十字`和`放大镜`,`十字`用于挪动绘图区域中的信号,`放大镜`用于放大所选的区域(仅放大横坐标),当通过这两个工具操作了绘图区域中的信号时,程序将获取此时此刻绘图区域的最左边和最右边所对应的横坐标数值,分别填入`修正后起始时间`和`修正后终止时间`中,若不小心操作了信号导致医生所打的时间初始值被修改时,点击`房子`按钮即可将绘图区域中的信号和`修正后起始时间`和`修正后终止时间`复位到初始状态。 @@ -112,7 +157,7 @@ correct_EventsType | 事件类型,共有4种 导入数据后点击`开始打标`和之后点击`上一个事件`和`下一个事件`,都将读取医生对该事件所做的标注,包括事件类型、起始时间和终止时间,标签类型在事件未被打标的情况下默认为二类,`修正后起始时间`和`修正后终止时间`的初始值都是医生所打的标注,当初始值错误时,用户需对其进行修改。备注默认为空,可根据需要填入内容,点击`快速备注输入`右边的按钮将快速输入对应内容到备注中。`仅检查未确定打标参数的事件`复选框默认未勾选,在勾选的情况下,切换事件时程序将会遍历该事件的`isLabeled`,若值为1,表明该事件已被标注过,程序将会跳过该事件,直到程序遍历到未被标注过的事件为止。 -### 2.4 逐帧检查功能 +### 2.5 逐帧检查功能 此功能的目的是用于检查未被医生标注的片段,当出现医生漏打的情况时,可以通过此功能将对应事件进行标注。 @@ -120,10 +165,10 @@ correct_EventsType | 事件类型,共有4种 标注事件的方法与2.3步骤类似,这里不再赘述。通过此功能标注的事件将被保存到BCG_label的export_addNew.csv文件中。 -### N 其他说明 +### 3 使用pyinstaller构建导出exe可执行文件 -本项目的根目录中的PSG_label_2_BCG_label.py用于获取对齐后的PSG标签并生成符合本项目程序运行的格式的BCG标签。 +在命令行中使用命令`python -m venv `在本地创建新的虚拟环境,``是虚拟环境的名字,由于`.gitignore`已经添加了名为`SaLabel`的文件夹,因此虚拟环境名字建议名为`SaLabel`。 -### TODOLIST +然后使用命令`..\SaLabel\Scripts\activate`切换到这个虚拟环境,通过命令`pip install -r requirements.txt`来安装对应依赖库,获得一个很干净的虚拟环境。 -~~令PSG和BCG事件的duration对齐~~ +再使用命令`pip install pyinstaller`安装pyinstaller,然后使用命令`pyinstaller -F -w Main_Quality_Relabel_GUI.py`就会构建出一个体积比较小的exe文件。 \ No newline at end of file diff --git a/img/0.png b/img/0.png new file mode 100644 index 0000000..60ec7f2 Binary files /dev/null and b/img/0.png differ diff --git a/img/1.png b/img/1.png index 686cb35..92e8df8 100644 Binary files a/img/1.png and b/img/1.png differ diff --git a/img/5.png b/img/5.png index c2a467c..faa7f4e 100644 Binary files a/img/5.png and b/img/5.png differ