@@ -392,6 +392,166 @@ def extract_union_types(annotation):
392392 # If we get here, all device config subclasses are properly covered
393393 self .assertTrue (True , "All DeviceConfig subclasses are properly covered in discriminated unions" )
394394
395+ def test_datastream_add_basic (self ):
396+ """Test combining two DataStream objects"""
397+ acq = ephys_acquisition .model_copy ()
398+
399+ stream1 = acq .data_streams [0 ].model_copy ()
400+ stream2 = acq .data_streams [0 ].model_copy ()
401+
402+ stream1 .active_devices = ["Device1" , "Device2" ]
403+ stream2 .active_devices = ["Device3" , "Device4" ]
404+
405+ combined_stream = stream1 + stream2
406+
407+ self .assertIsNotNone (combined_stream )
408+ self .assertEqual (combined_stream .stream_start_time , stream1 .stream_start_time )
409+ self .assertEqual (combined_stream .stream_end_time , stream1 .stream_end_time )
410+ self .assertEqual (len (combined_stream .modalities ), 1 )
411+ self .assertEqual (len (combined_stream .active_devices ), 4 )
412+ self .assertEqual (len (combined_stream .configurations ), 4 )
413+
414+ # Also check that an error is raised if the streams cannot be combined
415+
416+ stream2 .stream_end_time = stream2 .stream_end_time .replace (year = 2100 )
417+
418+ with self .assertRaises (ValueError ):
419+ _ = stream1 + stream2
420+
421+ def test_datastream_add_combines_notes (self ):
422+ """Test that notes are properly merged when combining DataStreams"""
423+ acq = ephys_acquisition .model_copy ()
424+
425+ stream1 = acq .data_streams [0 ].model_copy ()
426+ stream2 = acq .data_streams [0 ].model_copy ()
427+
428+ stream1 .active_devices = ["Device1" , "Device2" ]
429+ stream2 .active_devices = ["Device3" , "Device4" ]
430+ stream1 .notes = "Note 1"
431+ stream2 .notes = "Note 2"
432+
433+ combined_stream = stream1 + stream2
434+
435+ self .assertIn ("Note 1" , combined_stream .notes )
436+ self .assertIn ("Note 2" , combined_stream .notes )
437+
438+ def test_datastream_add_with_duplicate_devices (self ):
439+ """Test that overlapping active devices are logged as warning when combining"""
440+ acq = ephys_acquisition .model_copy ()
441+ stream1 = acq .data_streams [0 ]
442+ stream2 = acq .data_streams [0 ].model_copy ()
443+
444+ combined_stream = stream1 + stream2
445+
446+ self .assertIsNotNone (combined_stream )
447+
448+ def test_datastream_add_combines_connections (self ):
449+ """Test that connections are properly combined"""
450+ acq = ephys_acquisition .model_copy ()
451+
452+ stream1 = acq .data_streams [0 ].model_copy ()
453+ stream2 = acq .data_streams [0 ].model_copy ()
454+
455+ stream1 .active_devices = ["Device1" , "Device2" ]
456+ stream2 .active_devices = ["Device3" , "Device4" ]
457+ stream1 .connections = [Connection (source_device = "Device1" , target_device = "Device2" )]
458+ stream2 .connections = [Connection (source_device = "Device3" , target_device = "Device4" )]
459+
460+ combined_stream = stream1 + stream2
461+
462+ self .assertEqual (len (combined_stream .connections ), 2 )
463+
464+ def test_merge_data_stream_lists_single_streams (self ):
465+ """Test merging lists with single streams"""
466+ acq = ephys_acquisition .model_copy ()
467+ stream1 = acq .data_streams [0 ].model_copy ()
468+ stream2 = acq .data_streams [1 ].model_copy ()
469+
470+ stream1 .active_devices = ["Device1" ]
471+ stream2 .active_devices = ["Device2" ]
472+
473+ merged = Acquisition ._merge_data_streams ([stream1 ] + [stream2 ])
474+
475+ self .assertEqual (len (merged ), 2 )
476+
477+ def test_merge_data_stream_lists_overlapping_streams (self ):
478+ """Test merging streams with overlapping start/end times"""
479+ acq = ephys_acquisition .model_copy ()
480+
481+ stream1 = acq .data_streams [0 ].model_copy ()
482+ stream2 = acq .data_streams [0 ].model_copy ()
483+
484+ stream1 .active_devices = ["Device1" , "Device2" ]
485+ stream2 .active_devices = ["Device3" , "Device4" ]
486+
487+ merged = Acquisition ._merge_data_streams ([stream1 ] + [stream2 ])
488+
489+ self .assertEqual (len (merged ), 1 )
490+ self .assertEqual (len (merged [0 ].active_devices ), 4 )
491+
492+ def test_merge_data_stream_lists_non_overlapping_streams (self ):
493+ """Test merging streams with different start/end times"""
494+ acq = ephys_acquisition .model_copy ()
495+
496+ stream1 = acq .data_streams [1 ].model_copy ()
497+ stream2 = acq .data_streams [0 ].model_copy ()
498+
499+ stream1 .active_devices = ["Device1" ]
500+ stream2 .active_devices = ["Device2" ]
501+
502+ merged = Acquisition ._merge_data_streams ([stream1 ] + [stream2 ])
503+
504+ self .assertEqual (len (merged ), 2 )
505+
506+ def test_merge_data_stream_lists_multiple_overlapping_groups (self ):
507+ """Test merging multiple streams with multiple overlapping groups"""
508+ acq = ephys_acquisition .model_copy ()
509+
510+ stream1 = acq .data_streams [0 ].model_copy ()
511+ stream2 = acq .data_streams [0 ].model_copy ()
512+ stream3 = acq .data_streams [0 ].model_copy ()
513+
514+ stream1 .active_devices = ["DeviceA" , "DeviceB" ]
515+ stream2 .active_devices = ["DeviceC" ]
516+ stream3 .active_devices = ["DeviceD" , "DeviceE" ]
517+
518+ start1 = datetime (year = 2023 , month = 4 , day = 25 , hour = 2 , minute = 0 , second = 0 , tzinfo = timezone .utc )
519+ end1 = datetime (year = 2023 , month = 4 , day = 25 , hour = 2 , minute = 30 , second = 0 , tzinfo = timezone .utc )
520+ stream1 .stream_start_time = start1
521+ stream1 .stream_end_time = end1
522+
523+ start2 = datetime (year = 2023 , month = 4 , day = 25 , hour = 2 , minute = 0 , second = 30 , tzinfo = timezone .utc )
524+ end2 = datetime (year = 2023 , month = 4 , day = 25 , hour = 2 , minute = 29 , second = 30 , tzinfo = timezone .utc )
525+ stream2 .stream_start_time = start2
526+ stream2 .stream_end_time = end2
527+
528+ start3 = datetime (year = 2023 , month = 4 , day = 25 , hour = 3 , minute = 0 , second = 0 , tzinfo = timezone .utc )
529+ end3 = datetime (year = 2023 , month = 4 , day = 25 , hour = 3 , minute = 30 , second = 0 , tzinfo = timezone .utc )
530+ stream3 .stream_start_time = start3
531+ stream3 .stream_end_time = end3
532+
533+ merged = Acquisition ._merge_data_streams ([stream1 ] + [stream2 , stream3 ])
534+
535+ for m in merged :
536+ print (m .stream_start_time , m .stream_end_time , m .active_devices )
537+
538+ self .assertEqual (len (merged ), 2 )
539+ self .assertEqual (len (merged [0 ].active_devices ), 3 )
540+
541+ def test_datastream_add_with_exaspim_example (self ):
542+ """Test combining DataStreams using ExaSPIM example"""
543+ acq = exaspim_acquisition .model_copy ()
544+ stream1 = acq .data_streams [0 ].model_copy ()
545+
546+ stream2 = acq .data_streams [0 ].model_copy ()
547+ stream2 .active_devices = ["Device99" ]
548+
549+ combined_stream = stream1 + stream2
550+
551+ self .assertIsNotNone (combined_stream )
552+ self .assertIn (Modality .SPIM , combined_stream .modalities )
553+ self .assertEqual (len (combined_stream .active_devices ), 3 )
554+
395555
396556if __name__ == "__main__" :
397557 unittest .main ()
0 commit comments