1
- # set_p_features and set_d_features use characteristics of the p_signal or d_signal field to fill in other header fields.
2
- # These are separate from another method 'set_defaults' which the user may call to set default header fields
3
-
4
1
import datetime
5
2
import multiprocessing
6
3
import posixpath
@@ -45,8 +42,9 @@ def check_field(self, field, required_channels='all'):
45
42
field : str
46
43
The field name
47
44
required_channels : list, optional
48
- Used for signal specification fields. Species the channels
49
- to check. Other channels can be None.
45
+ Used for signal specification fields. All channels are
46
+ checked for their integrity if present, but channels that do
47
+ not lie in this field may be None.
50
48
51
49
Notes
52
50
-----
@@ -233,6 +231,30 @@ def check_read_inputs(self, sampfrom, sampto, channels, physical,
233
231
if smooth_frames is False :
234
232
raise ValueError ('This package version cannot expand all samples when reading multi-segment records. Must enable frame smoothing.' )
235
233
234
+ def _adjust_datetime (self , sampfrom ):
235
+ """
236
+ Adjust date and time fields to reflect user input if possible.
237
+
238
+ Helper function for the `_arrange_fields` of both Record and
239
+ MultiRecord objects.
240
+ """
241
+ if sampfrom :
242
+ dt_seconds = sampfrom / self .fs
243
+ if self .base_date and self .base_time :
244
+ self .base_datetime = datetime .datetime .combine (self .base_date ,
245
+ self .base_time )
246
+ self .base_datetime += datetime .timedelta (seconds = dt_seconds )
247
+ self .base_date = self .base_datetime .date ()
248
+ self .base_time = self .base_datetime .time ()
249
+ # We can calculate the time even if there is no date
250
+ elif self .base_time :
251
+ tmp_datetime = datetime .datetime .combine (
252
+ datetime .datetime .today ().date (), self .base_time )
253
+ self .base_time = (tmp_datetime
254
+ + datetime .timedelta (seconds = dt_seconds )).time ()
255
+ # Cannot calculate date or time if there is only date
256
+
257
+
236
258
237
259
class Record (BaseRecord , _header .HeaderMixin , _signal .SignalMixin ):
238
260
"""
@@ -389,22 +411,8 @@ def _arrange_fields(self, channels, sampfrom=0, expanded=False):
389
411
self .n_sig = len (channels )
390
412
self .sig_len = self .d_signal .shape [0 ]
391
413
392
- # Set and adjust time and date if possible
393
- if sampfrom :
394
- dt_seconds = sampfrom / self .fs
395
- if self .base_date and self .base_time :
396
- self .base_datetime = datetime .datetime .combine (self .base_date ,
397
- self .base_time )
398
- self .base_datetime += datetime .timedelta (seconds = dt_seconds )
399
- self .base_date = self .base_datetime .date ()
400
- self .base_time = self .base_datetime .time ()
401
- # We can calculate the time even if there is no date
402
- elif self .base_time :
403
- tmp_datetime = datetime .datetime .combine (
404
- datetime .datetime .today ().date (), self .base_time )
405
- self .base_time = (tmp_datetime
406
- + datetime .timedelta (seconds = dt_seconds )).time ()
407
- # Cannot calculate date or time if there is only date
414
+ # Adjust date and time if necessary
415
+ self ._adjust_datetime (sampfrom = sampfrom )
408
416
409
417
410
418
class MultiRecord (BaseRecord , _header .MultiHeaderMixin ):
@@ -473,24 +481,25 @@ def wrsamp(self, write_dir=''):
473
481
for seg in self .segments :
474
482
seg .wrsamp (write_dir = write_dir )
475
483
484
+ def _check_segment_cohesion (self ):
485
+ """
486
+ Check the cohesion of the segments field with other fields used
487
+ to write the record
488
+ """
476
489
477
- # Check the cohesion of the segments field with other fields used to write the record
478
- def checksegmentcohesion (self ):
479
-
480
- # Check that n_seg is equal to the length of the segments field
481
490
if self .n_seg != len (self .segments ):
482
491
raise ValueError ("Length of segments must match the 'n_seg' field" )
483
492
484
- for i in range (0 , n_seg ):
493
+ for i in range (n_seg ):
485
494
s = self .segments [i ]
486
495
487
496
# If segment 0 is a layout specification record, check that its file names are all == '~''
488
- if i == 0 and self .seg_len [0 ] == 0 :
497
+ if i == 0 and self .seg_len [0 ] == 0 :
489
498
for file_name in s .file_name :
490
499
if file_name != '~' :
491
500
raise ValueError ("Layout specification records must have all file_names named '~'" )
492
501
493
- # Check that sampling frequencies all match the one in the master header
502
+ # Sampling frequencies must all match the one in the master header
494
503
if s .fs != self .fs :
495
504
raise ValueError ("The 'fs' in each segment must match the overall record's 'fs'" )
496
505
@@ -505,7 +514,7 @@ def checksegmentcohesion(self):
505
514
506
515
507
516
508
- def _requiresegment_fieldsments (self , sampfrom , sampto , channels ):
517
+ def _required_segments (self , sampfrom , sampto , channels ):
509
518
"""
510
519
Determine the segments and the samples within each segment that
511
520
have to be read in a multi-segment record.
@@ -609,7 +618,7 @@ def _get_required_channels(self, seg_numbers, channels, dirname, pb_dir):
609
618
610
619
return required_channels
611
620
612
- def _arrange_fields (self , seg_numbers , seg_ranges , channels ):
621
+ def _arrange_fields (self , seg_numbers , seg_ranges , channels , sampfrom = 0 ):
613
622
"""
614
623
Arrange/edit object fields to reflect user channel and/or
615
624
signal range inputs.
@@ -639,6 +648,8 @@ def _arrange_fields(self, seg_numbers, seg_ranges, channels):
639
648
# Update number of segments
640
649
self .n_seg = len (self .segments )
641
650
651
+ # Adjust date and time if necessary
652
+ self ._adjust_datetime (sampfrom = sampfrom )
642
653
643
654
def multi_to_single (self , physical , return_res = 64 ):
644
655
"""
@@ -690,6 +701,8 @@ def multi_to_single(self, physical, return_res=64):
690
701
# pass the test.
691
702
if self .layout == 'variable' :
692
703
for seg in self .segments [1 :]:
704
+ if seg is None :
705
+ continue
693
706
segment_channels = get_wanted_channels (fields ['sig_name' ],
694
707
seg .sig_name ,
695
708
pad = True )
@@ -699,6 +712,7 @@ def multi_to_single(self, physical, return_res=64):
699
712
if segment_channels [ch ] is None :
700
713
continue
701
714
if getattr (seg , attr )[segment_channels [ch ]] != fields [attr ][ch ]:
715
+
702
716
raise Exception ('This variable layout multi-segment record cannot be converted to single segment, in digital format.' )
703
717
704
718
sig_attr = 'd_signal'
@@ -1110,10 +1124,11 @@ def rdrecord(record_name, sampfrom=0, sampto='end', channels='all',
1110
1124
pb_dir = pb_dir )
1111
1125
1112
1126
# The segment numbers and samples within each segment to read.
1113
- seg_numbers , seg_ranges = record ._requiresegment_fieldsments (sampfrom , sampto ,
1114
- channels )
1127
+ seg_numbers , seg_ranges = record ._required_segments (sampfrom , sampto ,
1128
+ channels )
1115
1129
# The channels within each segment to read
1116
- seg_channels = record ._get_required_channels (seg_numbers , channels , dirname , pb_dir )
1130
+ seg_channels = record ._get_required_channels (seg_numbers , channels ,
1131
+ dirname , pb_dir )
1117
1132
1118
1133
# Read the desired samples in the relevant segments
1119
1134
for i in range (len (seg_numbers )):
@@ -1128,7 +1143,8 @@ def rdrecord(record_name, sampfrom=0, sampto='end', channels='all',
1128
1143
channels = seg_channels [i ], physical = physical , pb_dir = pb_dir )
1129
1144
1130
1145
# Arrange the fields of the overall object to reflect user input
1131
- record ._arrange_fields (seg_numbers , seg_ranges , channels )
1146
+ record ._arrange_fields (seg_numbers = seg_numbers , seg_ranges = seg_ranges ,
1147
+ channels = channels , sampfrom = sampfrom )
1132
1148
1133
1149
# Convert object into a single segment Record object
1134
1150
if m2s :
@@ -1246,8 +1262,8 @@ def get_wanted_channels(wanted_sig_names, record_sig_names, pad=False):
1246
1262
1247
1263
1248
1264
def wrsamp (record_name , fs , units , sig_name , p_signal = None , d_signal = None ,
1249
- fmt = None , adc_gain = None , baseline = None , comments = None , base_time = None ,
1250
- base_date = None , write_dir = '' ):
1265
+ fmt = None , adc_gain = None , baseline = None , comments = None ,
1266
+ base_time = None , base_date = None , write_dir = '' ):
1251
1267
"""
1252
1268
Write a single segment WFDB record, creating a WFDB header file and any
1253
1269
associated dat files.
@@ -1324,7 +1340,8 @@ def wrsamp(record_name, fs, units, sig_name, p_signal=None, d_signal=None,
1324
1340
if d_signal is not None :
1325
1341
if fmt is None or adc_gain is None or baseline is None :
1326
1342
raise Exception ("When using d_signal, must also specify 'fmt', 'gain', and 'baseline' fields." )
1327
- # Depending on whether d_signal or p_signal was used, set other required features.
1343
+ # Depending on whether d_signal or p_signal was used, set other
1344
+ # required features.
1328
1345
if p_signal is not None :
1329
1346
# Create the Record object
1330
1347
record = Record (record_name = record_name , p_signal = p_signal , fs = fs ,
0 commit comments