@@ -510,13 +510,6 @@ def check_read_inputs(
510
510
"return_res must be one of the following when physical is True: 64, 32, 16"
511
511
)
512
512
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
-
520
513
def _adjust_datetime (self , sampfrom ):
521
514
"""
522
515
Adjust date and time fields to reflect user input if possible.
@@ -1269,7 +1262,7 @@ def _arrange_fields(
1269
1262
self .n_seg = len (self .segments )
1270
1263
self ._adjust_datetime (sampfrom = sampfrom )
1271
1264
1272
- def multi_to_single (self , physical , return_res = 64 ):
1265
+ def multi_to_single (self , physical , return_res = 64 , expanded = False ):
1273
1266
"""
1274
1267
Create a Record object from the MultiRecord object. All signal
1275
1268
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):
1283
1276
return_res : int, optional
1284
1277
The return resolution of the `p_signal` field. Options are:
1285
1278
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.
1286
1284
1287
1285
Returns
1288
1286
-------
@@ -1300,7 +1298,14 @@ def multi_to_single(self, physical, return_res=64):
1300
1298
# Figure out single segment fields to set for the new Record
1301
1299
if self .layout == "fixed" :
1302
1300
# 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
+ ]:
1304
1309
fields [attr ] = getattr (self .segments [0 ], attr )
1305
1310
else :
1306
1311
# For variable layout records, inspect the segments for the
@@ -1311,9 +1316,14 @@ def multi_to_single(self, physical, return_res=64):
1311
1316
# must have the same fmt, gain, baseline, and units for all
1312
1317
# segments.
1313
1318
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
+
1314
1323
# 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.
1317
1327
1318
1328
# These signal names will be the key
1319
1329
signal_names = self .segments [0 ].sig_name
@@ -1325,6 +1335,7 @@ def multi_to_single(self, physical, return_res=64):
1325
1335
"adc_gain" : n_sig * [None ],
1326
1336
"baseline" : n_sig * [None ],
1327
1337
"units" : n_sig * [None ],
1338
+ "samps_per_frame" : self .segments [0 ].samps_per_frame ,
1328
1339
}
1329
1340
1330
1341
# For physical signals, mismatched fields will not be copied
@@ -1346,7 +1357,16 @@ def multi_to_single(self, physical, return_res=64):
1346
1357
reference_fields [field ][ch ] = item_ch
1347
1358
# mismatch case
1348
1359
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 :
1350
1370
mismatched_fields .append (field )
1351
1371
else :
1352
1372
raise Exception (
@@ -1361,18 +1381,31 @@ def multi_to_single(self, physical, return_res=64):
1361
1381
1362
1382
# Figure out signal attribute to set, and its dtype.
1363
1383
if physical :
1364
- sig_attr = "p_signal"
1384
+ if expanded :
1385
+ sig_attr = "e_p_signal"
1386
+ else :
1387
+ sig_attr = "p_signal"
1365
1388
# Figure out the largest required dtype
1366
1389
dtype = _signal ._np_dtype (return_res , discrete = False )
1367
1390
nan_vals = np .array ([self .n_sig * [np .nan ]], dtype = dtype )
1368
1391
else :
1369
- sig_attr = "d_signal"
1392
+ if expanded :
1393
+ sig_attr = "e_d_signal"
1394
+ else :
1395
+ sig_attr = "d_signal"
1370
1396
# Figure out the largest required dtype
1371
1397
dtype = _signal ._np_dtype (return_res , discrete = True )
1372
1398
nan_vals = np .array ([_signal ._digi_nan (fields ["fmt" ])], dtype = dtype )
1373
1399
1400
+ samps_per_frame = fields ["samps_per_frame" ]
1401
+
1374
1402
# 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 )
1376
1409
1377
1410
# Start and end samples in the overall array to place the
1378
1411
# segment samples into
@@ -1383,9 +1416,16 @@ def multi_to_single(self, physical, return_res=64):
1383
1416
# Copy over the signals directly. Recall there are no
1384
1417
# empty segments in fixed layout records.
1385
1418
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
1389
1429
else :
1390
1430
# Copy over the signals into the matching channels
1391
1431
for i in range (1 , self .n_seg ):
@@ -1396,12 +1436,20 @@ def multi_to_single(self, physical, return_res=64):
1396
1436
segment_channels = _get_wanted_channels (
1397
1437
fields ["sig_name" ], seg .sig_name , pad = True
1398
1438
)
1439
+ signals = getattr (seg , sig_attr )
1399
1440
for ch in range (self .n_sig ):
1400
1441
# Copy over relevant signal
1401
1442
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
1405
1453
1406
1454
# Create the single segment Record object and set attributes
1407
1455
record = Record ()
@@ -1411,9 +1459,9 @@ def multi_to_single(self, physical, return_res=64):
1411
1459
1412
1460
# Use the signal to set record features
1413
1461
if physical :
1414
- record .set_p_features ()
1462
+ record .set_p_features (expanded = expanded )
1415
1463
else :
1416
- record .set_d_features ()
1464
+ record .set_d_features (expanded = expanded )
1417
1465
1418
1466
return record
1419
1467
@@ -4168,6 +4216,7 @@ def rdrecord(
4168
4216
channels = seg_channels [i ],
4169
4217
physical = physical ,
4170
4218
pn_dir = pn_dir ,
4219
+ smooth_frames = smooth_frames ,
4171
4220
return_res = return_res ,
4172
4221
)
4173
4222
@@ -4184,7 +4233,9 @@ def rdrecord(
4184
4233
# Convert object into a single segment Record object
4185
4234
if m2s :
4186
4235
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 ,
4188
4239
)
4189
4240
4190
4241
# Perform dtype conversion if necessary
0 commit comments