a b/preprocessOfApneaECG/mit2Segments.py
1
"""
2
    This file include some functions for converting raw Apnea-ECG database to many txt files, each txt file including
3
     a 60s ECG segment corresponding with labels came from raw Apnea-ECG database.
4
5
    Before run this file, you first set path information.
6
    
7
    If you want to know more information about Apnea-ECG database, please see https://physionet.org/physiobank/database/apnea-ecg/.
8
    
9
"""
10
11
__version__ = '0.2'
12
__time__ = "2019.06.22"
13
__author__ = "zzklove3344"
14
15
import wfdb
16
import os
17
import numpy as np
18
19
# path information
20
# You need to set these file path before you run this file.
21
# Raw apnea-ecg database. You must download firstly.
22
APNEA_ECG_DATABASE_PATH = "G:/Apnea-ecg/raw records/"
23
# Folder for writing apnea-ecg 60s segments
24
SEGMENTS_BASE_PATH = "F:/Apnea-ecg/ecg segments/"
25
26
# The number of segments in train set
27
SEGMENTS_NUMBER_TRAIN = 17045
28
# The number of segments in test set
29
SEGMENTS_NUMBER_TEST = 17268
30
31
APNEA_ECG_TRAIN_FILENAME = [
32
    "a01", "a02", "a03", "a04", "a05", "a06", "a07", "a08", "a09", "a10",
33
    "a11", "a12", "a13", "a14", "a15", "a16", "a17", "a18", "a19", "a20",
34
    "b01", "b02", "b03", "b04", "b05",
35
    "c01", "c02", "c03", "c04", "c05", "c06", "c07", "c08", "c09", "c10"
36
]
37
38
# The number of 60s segments for every subject in a01-a20, b01-b05, c01-c10
39
TRAIN_LABEL_AMOUNT = [489, 528, 519, 492, 454,
40
              510, 511, 501, 495, 517,
41
              466, 577, 495, 509, 510,
42
              482, 485, 489, 502, 510,
43
              487, 517, 441, 429, 433,
44
              484, 502, 454, 482, 466,
45
              468, 429, 513, 468, 431]
46
47
APNEA_ECG_TEST_FILENAME = [
48
    "x01", "x02", "x03", "x04", "x05", "x06", "x07", "x08", "x09", "x10",
49
    "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19", "x20",
50
    "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30",
51
    "x31", "x32", "x33", "x34", "x35"
52
]
53
54
# The number of 60s segments for every subject in x01-x35
55
TEST_LABEL_AMOUNT = [523, 469, 465, 482, 505,
56
              450, 509, 517, 508, 510,
57
              457, 527, 506, 490, 498,
58
              515, 400, 459, 487, 513,
59
              510, 482, 527, 429, 510,
60
              520, 498, 495, 470, 511,
61
              557, 538, 473, 475, 483]
62
63
ECG_RAW_FREQUENCY = 100
64
65
66
class Mit2Segment:
67
    """
68
    Mit to 60s segments.
69
    """
70
    
71
    def __init__(self):
72
        self.raw_ecg_data = None  # list, raw ecg data
73
        self.denoised_ecg_data = None   # list, raw ecg data
74
        r"""basic attributes"""
75
        self.label = None  # int, 0 or 1
76
        self.database_name = None  # string, "apnea ecg"
77
        self.filename = None  # string, like "a01", "x02"
78
        self.local_id = None  # int, the ID in filename, like 101 in "a01"
79
        self.global_id = None  # int, global ID in database(train set or test set)
80
        self.samplefrom = None  # int, sample from where
81
        self.sampleto = None  # int, sample to where
82
        self.base_file_path = None      # string
83
        
84
    def write_ecg_segment(self, rdf):
85
        """
86
        Write minute-by-minute ECG segment to txt file.
87
        :param int rdf: 0 means to write to raw ecg file, 1 means to write to denoised ecg file.
88
        :return: None
89
        """
90
        
91
        # a01-a10, b01-b05 and c01-c10 belong to train set;
92
        # x01-x35 belong to test set.
93
        # if self.filename.find('x') >= 0:
94
        #   file_path = SEGMENTS_BASE_PATH + "test/" + str(self.global_id) + "/"
95
        # else:
96
        #   file_path = SEGMENTS_BASE_PATH + "train/" + str(self.global_id) + "/"
97
        if not os.path.exists(self.base_file_path):
98
            os.makedirs(self.base_file_path)
99
        if rdf == 0:
100
            filename = "raw_ecg_segment_data.txt"
101
            ecg_data = self.raw_ecg_data
102
        elif rdf == 1:
103
            filename = "denosing_ecg_segment_data.txt"
104
            ecg_data = self.denoised_ecg_data
105
        else:
106
            raise Exception("Error rdf value.")
107
        
108
        attr_name = "database_name file_name local_id samplefrom sampleto global_id label\n"
109
        # 将标签转化为数字
110
        if self.label == 'A':
111
            self.label = 1
112
        elif self.label == 'N':
113
            self.label = 0
114
        
115
        with open(self.base_file_path + filename, "w") as f:
116
            r"""attributes name """
117
            f.write(attr_name)
118
            
119
            r"""attributes value"""
120
            f.write(
121
                self.database_name[0] + " " + self.database_name[1] + " "
122
                + self.filename + " " + str(self.local_id) + " "
123
                + str(self.global_id) + " " + str(self.samplefrom) + " "
124
                + str(self.sampleto) + " " + str(self.label) + "\n")
125
            
126
            r"""data"""
127
            for value in ecg_data:
128
                f.write(str(value[0]) + "\n")
129
    
130
    def read_ecg_segment(self, rdf, database_name_or_path):
131
        """
132
        Read Minute-by-minute ECG segment from TXT file
133
        :param string or list database_name_or_path: the database or the file path you want to read
134
        :param int rdf: 0 means to read to raw ecg file, 1 means to read to denoised ecg file.
135
        :return: None
136
        """
137
        
138
        if rdf == 0:
139
            filename = "raw_ecg_segment_data.txt"
140
        elif rdf == 1:
141
            filename = "denosing_ecg_segment_data.txt"
142
        else:
143
            raise Exception("Error rdf value.")
144
        if database_name_or_path == ["apnea-ecg", "train"]:
145
            file_path = SEGMENTS_BASE_PATH + "train/" + str(self.global_id) + "/" + filename
146
        elif database_name_or_path == ["apnea-ecg", "test"]:
147
            file_path = SEGMENTS_BASE_PATH + "test/" + str(self.global_id) + "/" + filename
148
        else:
149
            file_path = database_name_or_path
150
        
151
        with open(file_path) as f:
152
            _ = f.readline()
153
            
154
            # attribute values
155
            attrs_value = f.readline().replace("\n", "").split(" ")
156
            self.database_name = [attrs_value[0], attrs_value[1]]
157
            self.filename = attrs_value[2]
158
            self.local_id = int(attrs_value[3])
159
            self.global_id = int(attrs_value[4])
160
            self.samplefrom = int(attrs_value[5])
161
            self.sampleto = int(attrs_value[6])
162
            self.label = int(attrs_value[7])
163
            self.base_file_path = SEGMENTS_BASE_PATH + self.database_name[1] + "/" + str(self.global_id) + "/"
164
            
165
            # ECG segment data
166
            ecg_data = []
167
            data_value = f.readline().replace("\n", "")
168
            while data_value != "":
169
                ecg_data.append(float(data_value))
170
                data_value = f.readline().replace("\n", "")
171
            if rdf == 0:
172
                self.raw_ecg_data = ecg_data
173
            elif rdf == 1:
174
                self.denoised_ecg_data = ecg_data
175
    
176
    def read_edr(self, flag):
177
        """
178
        flag为0时读取原始edr信号,为1时读取下采样之后的edr信号.
179
        :return: None
180
        """
181
        
182
        edr = []
183
        if self.filename.find('x') >= 0:
184
            if flag == 0:
185
                file_path = SEGMENTS_BASE_PATH + "test/" + str(self.global_id) + "/edr.txt"
186
            elif flag == 1:
187
                file_path = SEGMENTS_BASE_PATH + "test/" + str(self.global_id) + "/downsampling_EDR.txt"
188
            else:
189
                file_path = ""
190
                print("edr file path error....")
191
        else:
192
            if flag == 0:
193
                file_path = SEGMENTS_BASE_PATH + "train/" + str(self.global_id) + "/edr.txt"
194
            elif flag == 1:
195
                file_path = SEGMENTS_BASE_PATH + "train/" + str(self.global_id) + "/downsampling_EDR.txt"
196
            else:
197
                file_path = ""
198
                print("edr file path error....")
199
        
200
        with open(file_path) as f:
201
            data_value = f.readline().replace("\n", "")
202
            while data_value != "":
203
                edr.append(float(data_value))
204
                data_value = f.readline().replace("\n", "")
205
            edr = np.array(edr)
206
        
207
        return edr
208
209
210
def get_ecg_data_annotations(database_name, is_debug=False):
211
    """
212
    Read files in specified database.
213
    :param list database_name: Database you want to read.
214
                                Reserved paras, it must be ["apnea-ecg", "train"] or ["apnea-ecg", "test"] now.
215
    :param bool is_debug: whether is debug mode.
216
    :return list: ecg data and annotations.
217
218
    example: data_set = get_ecg_data_annotations("train", True)
219
    """
220
    
221
    data_annotations_set = []
222
    file_name_set = None
223
    no_apn = None
224
    
225
    if database_name[0] == "apnea-ecg":
226
        root_file_path = APNEA_ECG_DATABASE_PATH
227
        if database_name[1] == "train":
228
            file_name_set = APNEA_ECG_TRAIN_FILENAME
229
            no_apn = False
230
        elif database_name[1] == "test":
231
            file_name_set = APNEA_ECG_TEST_FILENAME
232
            no_apn = True
233
    
234
    # if database name is test, we first read label file
235
    test_label_set = []
236
    if no_apn is True:
237
        # read event-2.txt, which is test label downloading from PhysioNet
238
        test_annotation_path = root_file_path + "event-2.txt"
239
        with open(test_annotation_path) as f:
240
            lines = f.readlines()
241
            for line in lines:
242
                line = line.replace("\n", "")
243
                for index_str in range(len(line)):
244
                    if line[index_str] == "A" or line[index_str] == "N":
245
                        test_label_set.append(line[index_str])
246
    
247
    file_count = 0  # use when the database name is test.
248
    test_label_index = 0  # use when the database name is test.
249
    for name in file_name_set:
250
        if is_debug:
251
            print("process file " + name + "...")
252
        
253
        file_path = root_file_path + name
254
        ecg_data = wfdb.rdrecord(file_path)  # use wfdb.rdrecord to read data
255
        
256
        if no_apn is False:
257
            # use wfdb.rdann to read annotation
258
            annotation = wfdb.rdann(file_path, "apn")
259
            # annotation range
260
            annotation_range_list = annotation.sample
261
            # annotation
262
            annotation_list = annotation.symbol
263
        else:
264
            annotation_range_list = []
265
            annotation_list = []
266
            for index_label in range(TEST_LABEL_AMOUNT[file_count]):
267
                annotation_range_list.append(np.array(index_label * 6000))
268
                annotation_list.append(test_label_set[test_label_index])
269
                test_label_index += 1
270
            file_count += 1
271
            annotation_range_list = np.array(annotation_range_list)
272
        
273
        data_annotations_set.append([ecg_data, annotation_range_list, annotation_list, name])
274
    
275
    return data_annotations_set
276
277
278
def process_ecg_data_segments(database_name, data_annotations_set, is_debug=False):
279
    """
280
    Divide ECG data to minute-by-minute ECG segment.
281
    :param list database_name: name of database.
282
                               Reserved paras, it must be ["apnea-ecg", "train"] or ["apnea-ecg", "test"] now.
283
    :param list data_annotations_set: output of function get_ecg_data_annotations.
284
    :param bool is_debug: whether is debug mode.
285
    :return: None
286
    """
287
    
288
    data_set = []
289
    global_counter = 0  # use for global id
290
    
291
    base_floder_path = None
292
    
293
    if database_name[0] == "apnea-ecg":
294
        if database_name[1] == "train":
295
            base_floder_path = SEGMENTS_BASE_PATH + "/train"
296
        elif database_name[1] == "test":
297
            base_floder_path = SEGMENTS_BASE_PATH + "/test"
298
    
299
    # ecg data segments divide
300
    for data_annotation in data_annotations_set:
301
        segment_amount = len(data_annotation[2])
302
        for index_segment in range(segment_amount):
303
            eds = Mit2Segment()
304
            eds.database_name = database_name
305
            eds.samplefrom = data_annotation[1][index_segment]
306
            if (data_annotation[1][index_segment] + 6000) > len(data_annotation[0].p_signal):
307
                eds.sampleto = len(data_annotation[0].p_signal)
308
            else:
309
                eds.sampleto = data_annotation[1][index_segment] + 6000
310
            eds.raw_ecg_data = data_annotation[0].p_signal[eds.samplefrom:eds.sampleto]
311
            eds.label = data_annotation[2][index_segment]
312
            eds.filename = data_annotation[3]
313
            eds.local_id = index_segment
314
            eds.global_id = global_counter
315
            eds.base_file_path = SEGMENTS_BASE_PATH + "/" + database_name[1] + "/" + str(eds.global_id) + "/"
316
            eds.write_ecg_segment(rdf=0)
317
            global_counter += 1
318
            data_set.append(eds)
319
            if is_debug:
320
                print("---------------------------------------------------")
321
                print(("global id: %s,  file name: %s, local id: %s") % (
322
                    str(eds.global_id), eds.filename, str(eds.local_id)))
323
                print("---------------------------------------------------")
324
    
325
    if not os.path.exists(base_floder_path):
326
        os.makedirs(base_floder_path)
327
    
328
    # extra_info, this file store number of all ECG segments.
329
    with open(base_floder_path + "/extra_info.txt", "w") as f:
330
        f.write("Number of ECG segments\n")
331
        f.write(str(global_counter))
332
    
333
    return data_set
334
335
336
def produce_database(database_name, is_debug):
337
    """
338
    Produce database. It will write many txt files in SEGMENTS_BASE_PATH.
339
    :param list database_name: name of database.
340
                               Reserved paras, it must be ["apnea-ecg", "train"] or ["apnea-ecg", "test"] now.
341
    :param bool is_debug: whether is debug mode.
342
    :return: None
343
    """
344
    
345
    # read files from a01-a35, every file including whole ecg data and the corresponding annotation
346
    data_annotations_set = get_ecg_data_annotations(database_name, is_debug)
347
    # divide ECG data to minute-by-minute ECG segments
348
    _ = process_ecg_data_segments(database_name, data_annotations_set, is_debug)
349
350
351
def produce_all_database(is_debug):
352
    """
353
    Produce train database and test database.
354
    :param bool is_debug: whether is debug mode.
355
    :return: None
356
    """
357
    produce_database(["apnea-ecg", "train"], is_debug)
358
    produce_database(["apnea-ecg", "test"], is_debug)
359
360
361
if __name__ == '__main__':
362
    print("fileIO test statements")
363
    # if you want to generate train database, you can run follow statement.
364
    # produce_database(["apnea-ecg", "train"], is_debug)
365
    
366
    # if you want to generate test database, you can run follow statement.
367
    # produce_database(["apnea-ecg", "test"], is_debug)
368
    
369
    # if you want to generate train and test database, you can run follow statement.
370
    produce_all_database(True)