8
8
from . import download
9
9
from . import _signal
10
10
11
-
11
+ import pdb
12
12
int_types = (int , np .int64 , np .int32 , np .int16 , np .int8 )
13
13
float_types = int_types + (float , np .float64 , np .float32 )
14
14
15
15
"""
16
- WFDB field specifications for each field.
16
+ WFDB field specifications for each field. The indexes are the field
17
+ names.
17
18
18
19
Parameters
19
20
----------
63
64
index = ['record_name' , 'n_seg' , 'n_sig' , 'fs' , 'counter_freq' ,
64
65
'base_counter' , 'sig_len' , 'base_time' , 'base_date' ],
65
66
columns = _SPECIFICATION_COLUMNS ,
67
+ dtype = 'object' ,
66
68
data = [[(str ,), '' , None , True , None , None ], # record_name
67
69
[int_types , '/' , 'record_name' , True , None , None ], # n_seg
68
70
[int_types , ' ' , 'record_name' , True , None , None ], # n_sig
80
82
'adc_gain' , 'baseline' , 'units' , 'adc_res' , 'adc_zero' ,
81
83
'init_value' , 'checksum' , 'block_size' , 'sig_name' ],
82
84
columns = _SPECIFICATION_COLUMNS ,
85
+ dtype = 'object' ,
83
86
data = [[(str ,), '' , None , True , None , None ], # file_name
84
87
[(str ,), ' ' , 'file_name' , True , None , None ], # fmt
85
88
[int_types , 'x' , 'fmt' , False , 1 , None ], # samps_per_frame
100
103
SEGMENT_SPECS = pd .DataFrame (
101
104
index = ['seg_name' , 'seg_len' ],
102
105
columns = _SPECIFICATION_COLUMNS ,
106
+ dtype = 'object' ,
103
107
data = [[(str ), '' , None , True , None , None ], # seg_name
104
108
[int_types , ' ' , 'seg_name' , True , None , None ], # seg_len
105
109
]
@@ -170,7 +174,7 @@ def get_write_subset(self, spec_type):
170
174
171
175
# Remove the n_seg requirement for single segment items
172
176
if not hasattr (self , 'n_seg' ):
173
- del ( record_specs [ 'n_seg' ] )
177
+ record_specs . drop ( 'n_seg' , inplace = True )
174
178
175
179
for field in record_specs .index [- 1 ::- 1 ]:
176
180
# Continue if the field has already been included
@@ -198,13 +202,13 @@ def get_write_subset(self, spec_type):
198
202
for ch in range (self .n_sig ):
199
203
# The fields needed for this channel
200
204
write_fields_ch = []
201
- for field in signal_specs [- 1 ::- 1 ]:
205
+ for field in signal_specs . index [- 1 ::- 1 ]:
202
206
if field in write_fields_ch :
203
207
continue
204
208
205
209
item = getattr (self , field )
206
210
# If the field is required by default or has been defined by the user
207
- if signal_specs .loc [field , 'write_req ' ] or (item is not None and item [ch ] is not None ):
211
+ if signal_specs .loc [field , 'write_required ' ] or (item is not None and item [ch ] is not None ):
208
212
req_field = field
209
213
# Add the field and its recursive dependencies
210
214
while req_field is not None :
@@ -238,32 +242,43 @@ class HeaderMixin(BaseHeaderMixin):
238
242
239
243
def set_defaults (self ):
240
244
"""
241
- Set defaults for fields needed to write the header if they have defaults.
242
- This is NOT called by rdheader. It is only automatically called by the gateway wrsamp for convenience.
243
- It is also not called by wrhea (this may be changed in the future) since
244
- it is supposed to be an explicit function.
245
+ Set defaults for fields needed to write the header if they have
246
+ defaults.
247
+
248
+ Notes
249
+ -----
250
+ - This is NOT called by `rdheader`. It is only automatically
251
+ called by the gateway `wrsamp` for convenience.
252
+ - This is also not called by `wrheader` since it is supposed to
253
+ be an explicit function.
254
+ - This is not responsible for initializing the attributes. That
255
+ is done by the constructor.
245
256
246
- Not responsible for initializing the
247
- attributes. That is done by the constructor.
248
257
"""
249
258
rfields , sfields = self .get_write_fields ()
250
259
for f in rfields :
251
260
self .set_default (f )
252
261
for f in sfields :
253
262
self .set_default (f )
254
263
255
-
256
264
def wrheader (self , write_dir = '' ):
257
265
"""
258
266
Write a wfdb header file. The signals are not used. Before
259
267
writing:
260
268
- Get the fields used to write the header for this instance.
261
269
- Check each required field.
270
+ - Check that the fields are cohesive with one another.
262
271
263
272
Parameters
264
273
----------
265
274
write_dir : str, optional
266
275
The output directory in which the header is written.
276
+
277
+ Notes
278
+ -----
279
+ This function does NOT call `set_defaults`. Essential fields
280
+ must be set beforehand.
281
+
267
282
"""
268
283
269
284
# Get all the fields used to write the header
@@ -338,29 +353,31 @@ def set_default(self, field):
338
353
339
354
# Record specification fields
340
355
if field in RECORD_SPECS .index :
341
- # Return if no default to set, or if the field is already present.
356
+ # Return if no default to set, or if the field is already
357
+ # present.
342
358
if RECORD_SPECS .loc [field , 'write_default' ] is None or getattr (self , field ) is not None :
343
359
return
344
360
setattr (self , field , RECORD_SPECS .loc [field , 'write_default' ])
345
361
346
362
# Signal specification fields
347
363
# Setting entire list default, not filling in blanks in lists.
348
- elif field in SIGNAL_FIELDS .index :
364
+ elif field in SIGNAL_SPECS .index :
349
365
350
366
# Specific dynamic case
351
367
if field == 'file_name' and self .file_name is None :
352
- self .file_name = self .n_sig * [self .record_name + '.dat' ]
368
+ self .file_name = self .n_sig * [self .record_name + '.dat' ]
353
369
return
354
370
355
371
item = getattr (self , field )
356
372
357
- # Return if no default to set, or if the field is already present.
373
+ # Return if no default to set, or if the field is already
374
+ # present.
358
375
if SIGNAL_SPECS .loc [field , 'write_default' ] is None or item is not None :
359
376
return
360
377
361
378
# Set more specific defaults if possible
362
379
if field == 'adc_res' and self .fmt is not None :
363
- self .adc_res = _signal .wfdbfmtres (self .fmt )
380
+ self .adc_res = _signal .wfdbfmtres (self .fmt )
364
381
return
365
382
366
383
setattr (self , field ,
@@ -403,46 +420,60 @@ def check_field_cohesion(self, rec_write_fields, sig_write_fields):
403
420
raise ValueError ('Each file_name (dat file) specified must have the same byte offset' )
404
421
405
422
406
-
407
423
def wr_header_file (self , rec_write_fields , sig_write_fields , write_dir ):
408
- # Write a header file using the specified fields
409
- header_lines = []
424
+ """
425
+ Write a header file using the specified fields
426
+
427
+ Parameters
428
+ ----------
429
+
430
+ rec_write_fields : list
431
+ List of record specification fields to write
432
+ sig_write_fields : dict
433
+ Dictionary of signal specification fields to write, values
434
+ being equal to a list of channels to write for each field.
435
+ write_dir : str
436
+ The directory in which to write the header file
410
437
438
+ """
411
439
# Create record specification line
412
440
record_line = ''
413
441
# Traverse the ordered dictionary
414
- for field in RECORD_SPECS :
442
+ for field in RECORD_SPECS . index :
415
443
# If the field is being used, add it with its delimiter
416
444
if field in rec_write_fields :
417
445
stringfield = str (getattr (self , field ))
418
446
# If fs is float, check whether it as an integer
419
447
if field == 'fs' and isinstance (self .fs , float ):
420
448
if round (self .fs , 8 ) == float (int (self .fs )):
421
449
stringfield = str (int (self .fs ))
422
- record_line = record_line + RECORD_SPECS [field ].delimiter + stringfield
423
- header_lines .append (record_line )
450
+ record_line += RECORD_SPECS .loc [field , 'delimiter' ] + stringfield
451
+
452
+ header_lines = [record_line ]
424
453
425
454
# Create signal specification lines (if any) one channel at a time
426
- if self .n_sig > 0 :
427
- signallines = self .n_sig * ['' ]
455
+ if self .n_sig > 0 :
456
+ signal_lines = self .n_sig * ['' ]
428
457
for ch in range (self .n_sig ):
429
- # Traverse the ordered dictionary
430
- for field in SIGNAL_FIELDS :
431
- # If the field is being used, add each of its elements with the delimiter to the appropriate line
432
- if field in sig_write_fields and sig_write_fields [field ][ch ]:
433
- signallines [ch ]= signallines [ch ] + SIGNAL_FIELDS [field ].delimiter + str (getattr (self , field )[ch ])
458
+ # Traverse the signal fields
459
+ for field in SIGNAL_SPECS .index :
460
+ # If the field is being used, add each of its
461
+ # elements with the delimiter to the appropriate
462
+ # line
463
+ if field in sig_write_fields and ch in sig_write_fields [field ]:
464
+ signal_lines [ch ] += SIGNAL_SPECS .loc [field , 'delimiter' ] + str (getattr (self , field )[ch ])
434
465
# The 'baseline' field needs to be closed with ')'
435
- if field == 'baseline' :
436
- signallines [ch ]= signallines [ ch ] + ')'
466
+ if field == 'baseline' :
467
+ signal_lines [ch ] += ')'
437
468
438
- header_lines = header_lines + signallines
469
+ header_lines += signal_lines
439
470
440
471
# Create comment lines (if any)
441
472
if 'comments' in rec_write_fields :
442
- comment_lines = ['# ' + comment for comment in self .comments ]
443
- header_lines = header_lines + comment_lines
473
+ comment_lines = ['# ' + comment for comment in self .comments ]
474
+ header_lines += comment_lines
444
475
445
- lines_to_file (self .record_name + '.hea' , write_dir , header_lines )
476
+ lines_to_file (self .record_name + '.hea' , write_dir , header_lines )
446
477
447
478
448
479
class MultiHeaderMixin (BaseHeaderMixin ):
@@ -523,35 +554,38 @@ def check_field_cohesion(self):
523
554
raise ValueError ("The sum of the 'seg_len' fields do not match the 'sig_len' field" )
524
555
525
556
526
- # Write a header file using the specified fields
527
- def wr_header_file (self , write_fields , write_dir ):
528
557
529
- header_lines = []
558
+ def wr_header_file (self , write_fields , write_dir ):
559
+ """
560
+ Write a header file using the specified fields
530
561
562
+ """
531
563
# Create record specification line
532
564
record_line = ''
533
565
# Traverse the ordered dictionary
534
- for field in RECORD_SPECS :
566
+ for field in RECORD_SPECS . index :
535
567
# If the field is being used, add it with its delimiter
536
568
if field in write_fields :
537
- record_line = record_line + RECORD_SPECS [field ].delimiter + str (getattr (self , field ))
538
- header_lines .append (record_line )
569
+ record_line += RECORD_SPECS .loc [field , 'delimiter' ] + str (getattr (self , field ))
570
+
571
+ header_lines = [record_line ]
539
572
540
573
# Create segment specification lines
541
- segmentlines = self .n_seg * ['' ]
542
- # For both fields, add each of its elements with the delimiter to the appropriate line
543
- for field in ['seg_name' , 'seg_name' ]:
544
- for segnum in range (0 , self .n_seg ):
545
- segmentlines [segnum ] = segmentlines [segnum ] + SEGMENT_SPECS [field ].delimiter + str (getattr (self , field )[segnum ])
574
+ segment_lines = self .n_seg * ['' ]
575
+ # For both fields, add each of its elements with the delimiter
576
+ # to the appropriate line
577
+ for field in SEGMENT_SPECS .index :
578
+ for seg_num in range (self .n_seg ):
579
+ segment_lines [seg_num ] += SEGMENT_SPECS .loc [field , 'delimiter' ] + str (getattr (self , field )[seg_num ])
546
580
547
- header_lines = header_lines + segmentlines
581
+ header_lines = header_lines + segment_lines
548
582
549
583
# Create comment lines (if any)
550
584
if 'comments' in write_fields :
551
- comment_lines = ['# ' + comment for comment in self .comments ]
552
- header_lines = header_lines + comment_lines
585
+ comment_lines = ['# ' + comment for comment in self .comments ]
586
+ header_lines += comment_lines
553
587
554
- lines_to_file (self .record_name + '.hea' , header_lines , write_dir )
588
+ lines_to_file (self .record_name + '.hea' , header_lines , write_dir )
555
589
556
590
557
591
def get_sig_segments (self , sig_name = None ):
@@ -662,45 +696,49 @@ def _read_record_line(record_line):
662
696
return record_fields
663
697
664
698
665
- # Extract fields from signal line strings into a dictionary
666
699
def _read_signal_lines (signal_lines ):
700
+ """
701
+ Extract fields from a list of signal line strings into a dictionary.
702
+
703
+ """
704
+ n_sig = len (signal_lines )
667
705
# Dictionary for signal fields
668
706
signal_fields = {}
669
707
670
708
# Each dictionary field is a list
671
- for field in SIGNAL_FIELDS :
672
- signal_fields [field ] = [None ]* len ( signal_lines )
709
+ for field in SIGNAL_SPECS . index :
710
+ signal_fields [field ] = n_sig * [None ]
673
711
674
712
# Read string fields from signal line
675
- for i in range (len ( signal_lines ) ):
676
- (signal_fields ['file_name' ][i ], signal_fields ['fmt' ][i ],
677
- signal_fields ['samps_per_frame' ][i ], signal_fields ['skew' ][i ],
678
- signal_fields ['byte_offset' ][i ], signal_fields ['adc_gain' ][i ],
679
- signal_fields ['baseline' ][i ], signal_fields ['units' ][i ],
680
- signal_fields ['adc_res' ][i ], signal_fields ['adc_zero' ][i ],
681
- signal_fields ['init_value' ][i ], signal_fields ['checksum' ][i ],
682
- signal_fields ['block_size' ][i ],
683
- signal_fields ['sig_name' ][i ]) = _rx_signal .findall (signal_lines [i ])[0 ]
684
-
685
- for field in SIGNAL_FIELDS :
713
+ for ch in range (n_sig ):
714
+ (signal_fields ['file_name' ][ch ], signal_fields ['fmt' ][ch ],
715
+ signal_fields ['samps_per_frame' ][ch ], signal_fields ['skew' ][ch ],
716
+ signal_fields ['byte_offset' ][ch ], signal_fields ['adc_gain' ][ch ],
717
+ signal_fields ['baseline' ][ch ], signal_fields ['units' ][ch ],
718
+ signal_fields ['adc_res' ][ch ], signal_fields ['adc_zero' ][ch ],
719
+ signal_fields ['init_value' ][ch ], signal_fields ['checksum' ][ch ],
720
+ signal_fields ['block_size' ][ch ],
721
+ signal_fields ['sig_name' ][ch ]) = _rx_signal .findall (signal_lines [ch ])[0 ]
722
+
723
+ for field in SIGNAL_SPECS . index :
686
724
# Replace empty strings with their read defaults (which are mostly None)
687
725
# Note: Never set a field to None. [None]* n_sig is accurate, indicating
688
726
# that different channels can be present or missing.
689
- if signal_fields [field ][i ] == '' :
690
- signal_fields [field ][i ] = SIGNAL_FIELDS [field ]. read_default
727
+ if signal_fields [field ][ch ] == '' :
728
+ signal_fields [field ][ch ] = SIGNAL_SPECS . loc [field , ' read_default' ]
691
729
692
730
# Special case: missing baseline defaults to ADCzero if present
693
- if field == 'baseline' and signal_fields ['adc_zero' ][i ] != '' :
694
- signal_fields ['baseline' ][i ] = int (signal_fields ['adc_zero' ][i ])
731
+ if field == 'baseline' and signal_fields ['adc_zero' ][ch ] != '' :
732
+ signal_fields ['baseline' ][ch ] = int (signal_fields ['adc_zero' ][ch ])
695
733
# Typecast non-empty strings for numerical fields
696
734
else :
697
- if SIGNAL_FIELDS [field ]. allowed_types is int_types :
698
- signal_fields [field ][i ] = int (signal_fields [field ][i ])
699
- elif SIGNAL_FIELDS [field ]. allowed_types is float_types :
700
- signal_fields [field ][i ] = float (signal_fields [field ][i ])
701
- # Special case: gain of 0 means 200
702
- if field == 'adc_gain' and signal_fields ['adc_gain' ][i ] == 0 :
703
- signal_fields ['adc_gain' ][i ] = 200.
735
+ if SIGNAL_SPECS . loc [field , ' allowed_types' ] is int_types :
736
+ signal_fields [field ][ch ] = int (signal_fields [field ][ch ])
737
+ elif SIGNAL_SPECS . loc [field , ' allowed_types' ] is float_types :
738
+ signal_fields [field ][ch ] = float (signal_fields [field ][ch ])
739
+ # Special case: adc_gain of 0 means 200
740
+ if field == 'adc_gain' and signal_fields ['adc_gain' ][ch ] == 0 :
741
+ signal_fields ['adc_gain' ][ch ] = 200.
704
742
705
743
return signal_fields
706
744
@@ -714,21 +752,16 @@ def _read_segment_lines(segment_lines):
714
752
segment_fields = {}
715
753
716
754
# Each dictionary field is a list
717
- for field in SEGMENT_SPECS :
718
- segment_fields [field ] = [None ]* len (segment_lines )
755
+ for field in SEGMENT_SPECS . index :
756
+ segment_fields [field ] = [None ] * len (segment_lines )
719
757
720
758
# Read string fields from signal line
721
- for i in range (0 , len (segment_lines )):
759
+ for i in range (len (segment_lines )):
722
760
(segment_fields ['seg_name' ][i ], segment_fields ['seg_len' ][i ]) = _rx_segment .findall (segment_lines [i ])[0 ]
723
761
724
- for field in SEGMENT_SPECS :
725
- # Replace empty strings with their read defaults (which are mostly None)
726
- if segment_fields [field ][i ] == '' :
727
- segment_fields [field ][i ] = SEGMENT_SPECS [field ].read_default
728
- # Typecast non-empty strings for numerical field
729
- else :
730
- if field == 'seg_len' :
731
- segment_fields [field ][i ] = int (segment_fields [field ][i ])
762
+ # Typecast strings for numerical field
763
+ if field == 'seg_len' :
764
+ segment_fields ['seg_len' ][i ] = int (segment_fields ['seg_len' ][i ])
732
765
733
766
return segment_fields
734
767
0 commit comments