@@ -510,13 +510,6 @@ def check_read_inputs(
510510 "return_res must be one of the following when physical is True: 64, 32, 16"
511511 )
512512
513- # Cannot expand multiple samples/frame for multi-segment records
514- if isinstance (self , MultiRecord ):
515- if not smooth_frames :
516- raise ValueError (
517- "This package version cannot expand all samples when reading multi-segment records. Must enable frame smoothing."
518- )
519-
520513 def _adjust_datetime (self , sampfrom ):
521514 """
522515 Adjust date and time fields to reflect user input if possible.
@@ -1269,7 +1262,7 @@ def _arrange_fields(
12691262 self .n_seg = len (self .segments )
12701263 self ._adjust_datetime (sampfrom = sampfrom )
12711264
1272- def multi_to_single (self , physical , return_res = 64 ):
1265+ def multi_to_single (self , physical , return_res = 64 , expanded = False ):
12731266 """
12741267 Create a Record object from the MultiRecord object. All signal
12751268 segments will be combined into the new object's `p_signal` or
@@ -1283,6 +1276,11 @@ def multi_to_single(self, physical, return_res=64):
12831276 return_res : int, optional
12841277 The return resolution of the `p_signal` field. Options are:
12851278 64, 32, and 16.
1279+ expanded : bool, optional
1280+ If false, combine the sample data from `p_signal` or `d_signal`
1281+ into a single two-dimensional array. If true, combine the
1282+ sample data from `e_p_signal` or `e_d_signal` into a list of
1283+ one-dimensional arrays.
12861284
12871285 Returns
12881286 -------
@@ -1300,7 +1298,14 @@ def multi_to_single(self, physical, return_res=64):
13001298 # Figure out single segment fields to set for the new Record
13011299 if self .layout == "fixed" :
13021300 # Get the fields from the first segment
1303- for attr in ["fmt" , "adc_gain" , "baseline" , "units" , "sig_name" ]:
1301+ for attr in [
1302+ "fmt" ,
1303+ "adc_gain" ,
1304+ "baseline" ,
1305+ "units" ,
1306+ "sig_name" ,
1307+ "samps_per_frame" ,
1308+ ]:
13041309 fields [attr ] = getattr (self .segments [0 ], attr )
13051310 else :
13061311 # For variable layout records, inspect the segments for the
@@ -1311,9 +1316,14 @@ def multi_to_single(self, physical, return_res=64):
13111316 # must have the same fmt, gain, baseline, and units for all
13121317 # segments.
13131318
1319+ # For either physical or digital conversion, all signals
1320+ # of the same name must have the same samps_per_frame,
1321+ # which must match the value in the layout header.
1322+
13141323 # The layout header should be updated at this point to
1315- # reflect channels. We can depend on it for sig_name, but
1316- # not for fmt, adc_gain, units, and baseline.
1324+ # reflect channels. We can depend on it for sig_name and
1325+ # samps_per_frame, but not for fmt, adc_gain, units, and
1326+ # baseline.
13171327
13181328 # These signal names will be the key
13191329 signal_names = self .segments [0 ].sig_name
@@ -1325,6 +1335,7 @@ def multi_to_single(self, physical, return_res=64):
13251335 "adc_gain" : n_sig * [None ],
13261336 "baseline" : n_sig * [None ],
13271337 "units" : n_sig * [None ],
1338+ "samps_per_frame" : self .segments [0 ].samps_per_frame ,
13281339 }
13291340
13301341 # For physical signals, mismatched fields will not be copied
@@ -1346,7 +1357,16 @@ def multi_to_single(self, physical, return_res=64):
13461357 reference_fields [field ][ch ] = item_ch
13471358 # mismatch case
13481359 elif reference_fields [field ][ch ] != item_ch :
1349- if physical :
1360+ if field == "samps_per_frame" :
1361+ expected = reference_fields [field ][ch ]
1362+ raise ValueError (
1363+ f"Incorrect samples per frame "
1364+ f"({ item_ch } != { expected } ) "
1365+ f"for signal { signal_names [ch ]} "
1366+ f"in segment { seg .record_name } "
1367+ f"of { self .record_name } "
1368+ )
1369+ elif physical :
13501370 mismatched_fields .append (field )
13511371 else :
13521372 raise Exception (
@@ -1361,18 +1381,31 @@ def multi_to_single(self, physical, return_res=64):
13611381
13621382 # Figure out signal attribute to set, and its dtype.
13631383 if physical :
1364- sig_attr = "p_signal"
1384+ if expanded :
1385+ sig_attr = "e_p_signal"
1386+ else :
1387+ sig_attr = "p_signal"
13651388 # Figure out the largest required dtype
13661389 dtype = _signal ._np_dtype (return_res , discrete = False )
13671390 nan_vals = np .array ([self .n_sig * [np .nan ]], dtype = dtype )
13681391 else :
1369- sig_attr = "d_signal"
1392+ if expanded :
1393+ sig_attr = "e_d_signal"
1394+ else :
1395+ sig_attr = "d_signal"
13701396 # Figure out the largest required dtype
13711397 dtype = _signal ._np_dtype (return_res , discrete = True )
13721398 nan_vals = np .array ([_signal ._digi_nan (fields ["fmt" ])], dtype = dtype )
13731399
1400+ samps_per_frame = fields ["samps_per_frame" ]
1401+
13741402 # Initialize the full signal array
1375- combined_signal = np .repeat (nan_vals , self .sig_len , axis = 0 )
1403+ if expanded :
1404+ combined_signal = []
1405+ for nan_val , spf in zip (nan_vals [0 ], samps_per_frame ):
1406+ combined_signal .append (np .repeat (nan_val , spf * self .sig_len ))
1407+ else :
1408+ combined_signal = np .repeat (nan_vals , self .sig_len , axis = 0 )
13761409
13771410 # Start and end samples in the overall array to place the
13781411 # segment samples into
@@ -1383,9 +1416,16 @@ def multi_to_single(self, physical, return_res=64):
13831416 # Copy over the signals directly. Recall there are no
13841417 # empty segments in fixed layout records.
13851418 for i in range (self .n_seg ):
1386- combined_signal [start_samps [i ] : end_samps [i ], :] = getattr (
1387- self .segments [i ], sig_attr
1388- )
1419+ signals = getattr (self .segments [i ], sig_attr )
1420+ if expanded :
1421+ for ch in range (self .n_sig ):
1422+ start = start_samps [i ] * samps_per_frame [ch ]
1423+ end = end_samps [i ] * samps_per_frame [ch ]
1424+ combined_signal [ch ][start :end ] = signals [ch ]
1425+ else :
1426+ start = start_samps [i ]
1427+ end = end_samps [i ]
1428+ combined_signal [start :end , :] = signals
13891429 else :
13901430 # Copy over the signals into the matching channels
13911431 for i in range (1 , self .n_seg ):
@@ -1396,12 +1436,20 @@ def multi_to_single(self, physical, return_res=64):
13961436 segment_channels = _get_wanted_channels (
13971437 fields ["sig_name" ], seg .sig_name , pad = True
13981438 )
1439+ signals = getattr (seg , sig_attr )
13991440 for ch in range (self .n_sig ):
14001441 # Copy over relevant signal
14011442 if segment_channels [ch ] is not None :
1402- combined_signal [
1403- start_samps [i ] : end_samps [i ], ch
1404- ] = getattr (seg , sig_attr )[:, segment_channels [ch ]]
1443+ if expanded :
1444+ signal = signals [segment_channels [ch ]]
1445+ start = start_samps [i ] * samps_per_frame [ch ]
1446+ end = end_samps [i ] * samps_per_frame [ch ]
1447+ combined_signal [ch ][start :end ] = signal
1448+ else :
1449+ signal = signals [:, segment_channels [ch ]]
1450+ start = start_samps [i ]
1451+ end = end_samps [i ]
1452+ combined_signal [start :end , ch ] = signal
14051453
14061454 # Create the single segment Record object and set attributes
14071455 record = Record ()
@@ -1411,9 +1459,9 @@ def multi_to_single(self, physical, return_res=64):
14111459
14121460 # Use the signal to set record features
14131461 if physical :
1414- record .set_p_features ()
1462+ record .set_p_features (expanded = expanded )
14151463 else :
1416- record .set_d_features ()
1464+ record .set_d_features (expanded = expanded )
14171465
14181466 return record
14191467
@@ -4168,6 +4216,7 @@ def rdrecord(
41684216 channels = seg_channels [i ],
41694217 physical = physical ,
41704218 pn_dir = pn_dir ,
4219+ smooth_frames = smooth_frames ,
41714220 return_res = return_res ,
41724221 )
41734222
@@ -4184,7 +4233,9 @@ def rdrecord(
41844233 # Convert object into a single segment Record object
41854234 if m2s :
41864235 record = record .multi_to_single (
4187- physical = physical , return_res = return_res
4236+ physical = physical ,
4237+ expanded = (not smooth_frames ),
4238+ return_res = return_res ,
41884239 )
41894240
41904241 # Perform dtype conversion if necessary
0 commit comments