33
33
'description' :(list , np .ndarray ),
34
34
'custom_labels' : (pd .DataFrame , list , tuple )}
35
35
36
- str_types = (str , np .str_ )
36
+ STR_TYPES = (str , np .str_ )
37
37
38
38
# Standard WFDB annotation file extensions
39
39
ANN_EXTENSIONS = pd .DataFrame (data = [
@@ -244,49 +244,48 @@ def contained_data_fields(self):
244
244
"""
245
245
return [f for f in ANN_DATA_FIELDS if hasattr (self , f )]
246
246
247
- def wrann (self , write_fs = False , write_dir = '' ):
247
+ def wrann (self , label_field = 'symbol' , write_fs = False , write_dir = '' ):
248
248
"""
249
249
Write a WFDB annotation file from this object.
250
250
251
- !!! Should we force an argument to choose the label field used?
252
-
253
251
Parameters
254
252
----------
253
+ label_field : str, optional
254
+ The field used to write the label information. The
255
+ annotation object must have this attribute defined.
256
+ Must be either 'label_store' or 'symbol'. If 'symbol' is
257
+ chosen, that attribute will be used to calculate label_store
258
+ which will be written to the file.
255
259
write_fs : bool, optional
256
260
Whether to write the `fs` attribute to the file.
257
-
258
- Notes
259
- -----
260
- The label_store field will be generated if necessary
261
+ write_dir : str, optional
262
+ The directory in which to write the annotation file
261
263
262
264
"""
265
+ # Validate input parameters
266
+ if label_field not in ['label_store' , 'symbol' ]:
267
+ raise Exception ("'label_field' must be set to either 'symbol' or 'label_store'." )
263
268
264
- # Check the presence of vital fields
265
- contained_label_fields = self ._contained_label_fields ()
266
- if not contained_label_fields :
267
- raise Exception ('At least one annotation label field is required to write the annotation: ' , ANN_LABEL_FIELDS )
268
- for field in ['record_name' , 'extension' ]:
269
- if getattr (self , field ) is None :
270
- raise Exception ('Missing required field for writing annotation file: ' ,field )
271
-
272
- # Check the validity of individual fields
273
- self .check_fields ()
269
+ # Check the validity of individual fields used to write the file
270
+ self .check_write_fields (label_field = label_field )
274
271
275
272
# Standardize the format of the custom_labels field
276
273
self ._custom_labels_to_df ()
277
274
275
+ # Check the cohesion of the fields
276
+ self .check_field_cohesion ()
277
+
278
278
# Create the label map used in this annotaion
279
279
self ._create_label_map ()
280
280
281
- # Set the label_store field if necessary
282
- if 'label_store' not in contained_label_fields :
283
- self .convert_label_attribute (source_field = contained_label_fields [ 0 ] ,
281
+ # Set the label_store field if needed. Requires the label map.
282
+ if label_field == 'symbol' :
283
+ self .convert_label_attribute (source_field = 'symbol' ,
284
284
target_field = 'label_store' )
285
285
286
- # Check the cohesion of the fields
287
- self .check_field_cohesion ()
288
286
289
- # Write the header file using the specified fields
287
+
288
+ # Write the annotation file using the specified fields
290
289
self .wr_ann_file (write_fs = write_fs , write_dir = write_dir )
291
290
292
291
def _contained_label_fields (self ):
@@ -295,13 +294,27 @@ def _contained_label_fields(self):
295
294
"""
296
295
return [field for field in ANN_LABEL_FIELDS if getattr (self , field )]
297
296
298
- def check_fields (self ):
297
+ def check_write_fields (self , label_field ):
299
298
"""
300
- Check the set fields of the annotation object
299
+ Check all fields of this object that may be used to write an
300
+ annotation file.
301
+
302
+ Mandatory fields will be checked. Optional fields will be
303
+ checked if they are defined, and hence liable to affect the
304
+ output file.
305
+
301
306
"""
302
307
for field in ANN_FIELDS :
303
- if hasattr (self , field ):
304
- self .check_field (field )
308
+ # Mandatory check
309
+ if field in ('sample' , 'record_name' , 'extension' , label_field ):
310
+ if getattr (self , field ) is not None :
311
+ self .check_field (field )
312
+ else :
313
+ raise Exception ('Missing required field for writing annotation file: ' , field )
314
+ # Check if defined
315
+ else :
316
+ if getattr (self , field ) is not None :
317
+ self .check_field (field )
305
318
306
319
def check_field (self , field ):
307
320
"""
@@ -380,13 +393,13 @@ def check_field(self, field):
380
393
if not hasattr (label_store [i ], '__index__' ):
381
394
raise TypeError ('The label_store values of the ' + field + ' field must be integer-like' )
382
395
383
- if not isinstance (symbol [i ], str_types ) or len (symbol [i ]) not in [1 ,2 ,3 ]:
396
+ if not isinstance (symbol [i ], STR_TYPES ) or len (symbol [i ]) not in [1 ,2 ,3 ]:
384
397
raise ValueError ('The symbol values of the ' + field + ' field must be strings of length 1 to 3' )
385
398
386
399
if bool (re .search ('[ \t \n \r \f \v ]' , symbol [i ])):
387
400
raise ValueError ('The symbol values of the ' + field + ' field must not contain whitespace characters' )
388
401
389
- if not isinstance (description [i ], str_types ):
402
+ if not isinstance (description [i ], STR_TYPES ):
390
403
raise TypeError ('The description values of the ' + field + ' field must be strings' )
391
404
392
405
# Would be good to enfore this but existing garbage annotations have tabs and newlines...
@@ -398,7 +411,7 @@ def check_field(self, field):
398
411
uniq_elements = set (item )
399
412
400
413
for e in uniq_elements :
401
- if not isinstance (e , str_types ):
414
+ if not isinstance (e , STR_TYPES ):
402
415
raise TypeError ("Subelements of the '{}' field must be strings" .format (field ))
403
416
404
417
if field == 'symbol' :
@@ -552,7 +565,7 @@ def _get_available_label_stores(self):
552
565
else :
553
566
raise ValueError ('No label fields are defined. At least one of the following is required: ' , ANN_LABEL_FIELDS )
554
567
555
- # We are using 'label_store', the steps are slightly different.
568
+ # If we are using 'label_store', the steps are slightly different.
556
569
557
570
# Get the unused label_store values
558
571
if usefield == 'label_store' :
@@ -840,9 +853,8 @@ def _compact_fields(self):
840
853
841
854
842
855
def sym_to_aux (self ):
843
- # Move non-encoded symbol elements into the aux_note field
856
+ " Move non-encoded symbol elements into the aux_note field"
844
857
self .check_field ('symbol' )
845
-
846
858
# Non-encoded symbols
847
859
label_table_map = self ._create_label_map (inplace = False )
848
860
external_syms = set (self .symbol ) - set (label_table_map ['symbol' ].values )
@@ -880,7 +892,7 @@ def get_contained_labels(self):
880
892
if self .custom_labels :
881
893
self ._custom_labels_to_df ()
882
894
883
- self .check_field_cohesion ()
895
+ # self.check_field_cohesion()
884
896
885
897
# Merge the standard wfdb labels with the custom labels.
886
898
# custom labels values overwrite standard wfdb if overlap.
@@ -1013,18 +1025,15 @@ def convert_label_attribute(self, source_field, target_field):
1013
1025
The destination label attribute
1014
1026
1015
1027
"""
1016
- if inplace and not overwrite :
1017
- if getattr (self , target_field ) is not None :
1018
- return
1028
+ self ._create_label_map ()
1019
1029
1020
- label_map = self ._create_label_map (inplace = False )
1021
- label_map .set_index (source_field , inplace = True )
1022
1030
1023
- target_item = label_map .loc [getattr (self , source_field ), target_field ].values
1031
+ target_item = self . __label_map__ .loc [getattr (self , source_field ), target_field ].values
1024
1032
1025
- if target_field != 'label_store' :
1026
- # Should already be int64 dtype if target is label_store
1027
- target_item = list (target_item )
1033
+ # Shouldn't need this? Just leave as list.
1034
+ # if target_field != 'label_store':
1035
+ # # Should already be int64 dtype if target is label_store
1036
+ # target_item = list(target_item)
1028
1037
1029
1038
setattr (self , target_field , target_item )
1030
1039
@@ -1042,12 +1051,9 @@ def to_df(self, fields=None):
1042
1051
Create a pandas DataFrame from the Annotation object
1043
1052
1044
1053
"""
1045
- fields = fields or ANN_DATA_FIELDS [: 6 ]
1054
+ fields = fields or ANN_DATA_FIELDS
1046
1055
1047
- df = pd .DataFrame (data = {'sample' :self .sample , 'symbol' :self .symbol ,
1048
- 'subtype' :self .subtype , 'chan' :self .chan , 'num' :self .num ,
1049
- 'aux_note' :self .aux_note }, columns = ['sample' , 'symbol' , 'subtype' ,
1050
- 'chan' , 'aux_note' ])
1056
+ df = pd .DataFrame (data = {field :getattr (self , field ) for field in fields })
1051
1057
return df
1052
1058
1053
1059
@@ -1284,17 +1290,20 @@ def wrann(record_name, extension, sample, symbol=None, subtype=None, chan=None,
1284
1290
custom_labels = custom_labels )
1285
1291
1286
1292
# Find out which input field describes the labels
1287
- if symbol is None :
1288
- if label_store is None :
1289
- raise Exception ("Either the 'symbol' field or the 'label_store' field must be set" )
1290
- else :
1291
- if label_store is None :
1292
- annotation .sym_to_aux ()
1293
- else :
1293
+ if symbol :
1294
+ if label_store :
1294
1295
raise Exception ("Only one of the 'symbol' and 'label_store' fields may be input, for describing annotation labels" )
1296
+ else :
1297
+ label_field = 'symbol'
1298
+ annotation .sym_to_aux ()
1299
+ else :
1300
+ if not label_store :
1301
+ raise Exception ("Either the 'symbol' field or the 'label_store' field must be set" )
1302
+ label_field = 'label_store'
1295
1303
1296
1304
# Perform field checks and write the annotation file
1297
- annotation .wrann (write_fs = True , write_dir = write_dir )
1305
+ annotation .wrann (label_field = label_field , write_fs = True ,
1306
+ write_dir = write_dir )
1298
1307
1299
1308
1300
1309
def show_ann_labels ():
0 commit comments