Skip to content

Signal Generators

Signal Generation Utilities for PyEyesWeb Testing

This module provides signal generation capabilities for testing PyEyesWeb features. It includes various signal types from simple periodic waves to complex modulated and stochastic signals.

Author: PyEyesWeb Development Team

SignalGenerator

Signal generator for testing and analysis.

This class provides methods to generate various types of signals commonly used in signal processing, neuroscience, and movement analysis.

Source code in pyeyesweb/utils/signal_generators.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
class SignalGenerator:
    """
    Signal generator for testing and analysis.

    This class provides methods to generate various types of signals commonly
    used in signal processing, neuroscience, and movement analysis.
    """

    # Registry of available signal generators
    _generators: Dict[str, Callable] = {}

    def __init__(self, sampling_rate: float = 100.0):
        """
        Initialize the signal generator.

        Args:
            sampling_rate: Default sampling rate in Hz
        """
        self.sampling_rate = sampling_rate
        self._register_generators()

    def _register_generators(self):
        """Register all available signal generators."""
        self._generators = {
            # Basic waveforms
            'sine': self.sine_wave,
            'cosine': self.cosine_wave,
            'square': self.square_wave,
            'sawtooth': self.sawtooth_wave,
            'triangle': self.triangle_wave,

            # Noise signals
            'random': self.random_signal,
            'gaussian': self.gaussian_noise,
            'pink': self.pink_noise,
            'brown': self.brownian_motion,
            'white': self.white_noise,

            # Complex signals
            'chirp': self.chirp_signal,
            'chirp_exp': self.exponential_chirp,
            'chirp_hyperbolic': self.hyperbolic_chirp,
            'multisine': self.multi_sine,
            'complex': self.complex_signal,

            # Modulated signals
            'am': self.amplitude_modulated,
            'fm': self.frequency_modulated,
            'pm': self.phase_modulated,
            'pwm': self.pulse_width_modulated,

            # Transient signals
            'impulse': self.impulse_train,
            'step': self.step_function,
            'ramp': self.ramp_function,
            'exponential': self.exponential_decay,
            'damped_sine': self.damped_sine,

            # Biological/Movement signals
            'ecg': self.ecg_like,
            'emg': self.emg_like,
            'tremor': self.tremor_signal,
            'gait': self.gait_pattern,
            'breathing': self.breathing_pattern,

            # Special signals
            'lorenz': self.lorenz_attractor,
            'chaos': self.logistic_map,
            'fractal': self.fractal_noise,
            'burst': self.burst_signal,
            'spike_train': self.spike_train,

            # Combined signals
            'noisy_sine': self.noisy_sine,
            'drift_sine': self.sine_with_drift,
            'intermittent': self.intermittent_signal,
            'switched': self.switched_signal,
        }

    def generate(self, signal_type: str, length: int = 1000, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict[str, Any]]:
        """
        Generate a signal based on type and parameters.

        Args:
            signal_type: Type of signal to generate
            length: Number of samples
            **kwargs: Additional parameters specific to each signal type

        Returns:
            Tuple of (time_array, signal_array, metadata_dict)
        """
        if signal_type not in self._generators:
            available = ', '.join(sorted(self._generators.keys()))
            raise ValueError(f"Unknown signal type: '{signal_type}'. Available types: {available}")

        # Override sampling rate if provided
        if 'sampling_rate' in kwargs:
            self.sampling_rate = kwargs['sampling_rate']

        return self._generators[signal_type](length, **kwargs)

    @property
    def available_signals(self) -> List[str]:
        """Get list of available signal types."""
        return sorted(list(self._generators.keys()))

    @property
    def signal_info(self) -> Dict[str, Dict[str, str]]:
        """Get detailed information about all signal types.

        Returns
        -------
        dict
            Dictionary mapping signal names to their metadata including
            description and category.
        """
        return {
            # Basic Waveforms
            'sine': {'description': 'Sine wave', 'category': 'Basic Waveforms'},
            'cosine': {'description': 'Cosine wave', 'category': 'Basic Waveforms'},
            'square': {'description': 'Square wave', 'category': 'Basic Waveforms'},
            'sawtooth': {'description': 'Sawtooth wave', 'category': 'Basic Waveforms'},
            'triangle': {'description': 'Triangle wave', 'category': 'Basic Waveforms'},

            # Noise Signals
            'random': {'description': 'Random uniform noise', 'category': 'Noise Signals'},
            'gaussian': {'description': 'Gaussian noise', 'category': 'Noise Signals'},
            'white': {'description': 'White noise', 'category': 'Noise Signals'},
            'pink': {'description': 'Pink (1/f) noise', 'category': 'Noise Signals'},
            'brown': {'description': 'Brownian motion (random walk)', 'category': 'Noise Signals'},

            # Chirp Signals
            'chirp': {'description': 'Linear chirp signal', 'category': 'Chirp Signals'},
            'chirp_exp': {'description': 'Exponential chirp signal', 'category': 'Chirp Signals'},
            'chirp_hyperbolic': {'description': 'Hyperbolic chirp signal', 'category': 'Chirp Signals'},

            # Modulated
            'am': {'description': 'Amplitude modulated signal', 'category': 'Modulated'},
            'fm': {'description': 'Frequency modulated signal', 'category': 'Modulated'},
            'pm': {'description': 'Phase modulated signal', 'category': 'Modulated'},
            'pwm': {'description': 'Pulse width modulated signal', 'category': 'Modulated'},

            # Transient
            'impulse': {'description': 'Impulse train', 'category': 'Transient'},
            'step': {'description': 'Step function', 'category': 'Transient'},
            'ramp': {'description': 'Ramp function', 'category': 'Transient'},
            'exponential': {'description': 'Exponential decay', 'category': 'Transient'},
            'damped_sine': {'description': 'Damped sine wave', 'category': 'Transient'},

            # Biological
            'ecg': {'description': 'ECG-like biological signal', 'category': 'Biological'},
            'emg': {'description': 'EMG-like muscle activity signal', 'category': 'Biological'},
            'tremor': {'description': 'Tremor signal (slow + fast oscillation)', 'category': 'Biological'},
            'gait': {'description': 'Gait pattern signal', 'category': 'Biological'},
            'breathing': {'description': 'Breathing pattern signal', 'category': 'Biological'},

            # Complex
            'complex': {'description': 'Complex multi-frequency signal', 'category': 'Complex'},
            'multisine': {'description': 'Multiple sine waves combined', 'category': 'Complex'},
            'noisy_sine': {'description': 'Sine wave with noise', 'category': 'Complex'},
            'drift_sine': {'description': 'Sine wave with linear drift', 'category': 'Complex'},
            'intermittent': {'description': 'Intermittent signal', 'category': 'Complex'},
            'switched': {'description': 'Frequency-switching signal', 'category': 'Complex'},

            # Chaotic
            'lorenz': {'description': 'Lorenz attractor (chaotic)', 'category': 'Chaotic'},
            'chaos': {'description': 'Logistic map (chaotic)', 'category': 'Chaotic'},
            'fractal': {'description': 'Fractal noise (fBm)', 'category': 'Chaotic'},
            'burst': {'description': 'Burst signal', 'category': 'Chaotic'},
            'spike_train': {'description': 'Neural spike train', 'category': 'Chaotic'},
        }

    def get_signals_by_category(self) -> Dict[str, List[str]]:
        """Get signals organized by category.

        Returns
        -------
        dict
            Dictionary mapping category names to lists of signal types.
        """
        categories = {}
        for signal_name, info in self.signal_info.items():
            category = info['category']
            if category not in categories:
                categories[category] = []
            categories[category].append(signal_name)
        return categories

    # ========================================================================
    # Basic Waveforms
    # ========================================================================

    def sine_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                  phase: float = 0.0, dc_offset: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a sine wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * np.sin(2 * np.pi * freq * t + phase) + dc_offset
        metadata = {
            'type': 'sine',
            'frequency': freq,
            'amplitude': amplitude,
            'phase': phase,
            'dc_offset': dc_offset,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def cosine_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                    phase: float = 0.0, dc_offset: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a cosine wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * np.cos(2 * np.pi * freq * t + phase) + dc_offset
        metadata = {
            'type': 'cosine',
            'frequency': freq,
            'amplitude': amplitude,
            'phase': phase,
            'dc_offset': dc_offset,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def square_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                   duty_cycle: float = 0.5, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a square wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * sp_signal.square(2 * np.pi * freq * t, duty=duty_cycle)
        metadata = {
            'type': 'square',
            'frequency': freq,
            'amplitude': amplitude,
            'duty_cycle': duty_cycle,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def sawtooth_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                     width: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a sawtooth wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * sp_signal.sawtooth(2 * np.pi * freq * t, width=width)
        metadata = {
            'type': 'sawtooth',
            'frequency': freq,
            'amplitude': amplitude,
            'width': width,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def triangle_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                     **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a triangle wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * sp_signal.sawtooth(2 * np.pi * freq * t, width=0.5)
        metadata = {
            'type': 'triangle',
            'frequency': freq,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Noise Signals
    # ========================================================================

    def random_signal(self, length: int, seed: Optional[int] = None,
                     min_val: float = -1.0, max_val: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate random uniform noise."""
        if seed is not None:
            np.random.seed(seed)
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.random.uniform(min_val, max_val, length)
        metadata = {
            'type': 'random',
            'distribution': 'uniform',
            'range': [min_val, max_val],
            'seed': seed,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def gaussian_noise(self, length: int, mean: float = 0.0, std: float = 1.0,
                      seed: Optional[int] = None, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate Gaussian (white) noise."""
        if seed is not None:
            np.random.seed(seed)
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.random.normal(mean, std, length)
        metadata = {
            'type': 'gaussian',
            'mean': mean,
            'std': std,
            'seed': seed,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def white_noise(self, length: int, power: float = 1.0, seed: Optional[int] = None,
                   **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate white noise with specified power."""
        if seed is not None:
            np.random.seed(seed)
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.random.normal(0, np.sqrt(power), length)
        metadata = {
            'type': 'white_noise',
            'power': power,
            'seed': seed,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def pink_noise(self, length: int, amplitude: float = 1.0, seed: Optional[int] = None,
                  **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate pink (1/f) noise."""
        if seed is not None:
            np.random.seed(seed)

        # Generate white noise
        white = np.random.randn(length)

        # Apply 1/f filter in frequency domain
        fft = np.fft.rfft(white)
        freqs = np.fft.rfftfreq(length)
        freqs[0] = 1  # Avoid division by zero
        fft = fft / np.sqrt(freqs)
        signal = np.fft.irfft(fft, length)

        # Normalize
        signal = amplitude * signal / np.std(signal)

        t = np.linspace(0, length / self.sampling_rate, length)
        metadata = {
            'type': 'pink_noise',
            'amplitude': amplitude,
            'seed': seed,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def brownian_motion(self, length: int, std: float = 0.1, seed: Optional[int] = None,
                       **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate Brownian motion (random walk)."""
        if seed is not None:
            np.random.seed(seed)
        t = np.linspace(0, length / self.sampling_rate, length)
        steps = np.random.normal(0, std, length)
        signal = np.cumsum(steps)
        metadata = {
            'type': 'brownian_motion',
            'step_std': std,
            'seed': seed,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Complex Signals
    # ========================================================================

    def chirp_signal(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                    amplitude: float = 1.0, method: str = 'linear',
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a chirp signal (linear frequency sweep)."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * sp_signal.chirp(t, freq_start, t[-1], freq_end, method=method)
        metadata = {
            'type': 'chirp',
            'freq_start': freq_start,
            'freq_end': freq_end,
            'method': method,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def exponential_chirp(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                         amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an exponential chirp signal."""
        return self.chirp_signal(length, freq_start, freq_end, amplitude, method='exponential')

    def hyperbolic_chirp(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                        amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a hyperbolic chirp signal."""
        return self.chirp_signal(length, freq_start, freq_end, amplitude, method='hyperbolic')

    def multi_sine(self, length: int, frequencies: List[float] = None,
                  amplitudes: List[float] = None, phases: List[float] = None,
                  **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate multiple sine waves combined."""
        if frequencies is None:
            frequencies = [1.0, 3.0, 5.0]
        if amplitudes is None:
            amplitudes = [1.0] * len(frequencies)
        if phases is None:
            phases = [0.0] * len(frequencies)

        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)

        for freq, amp, phase in zip(frequencies, amplitudes, phases):
            signal += amp * np.sin(2 * np.pi * freq * t + phase)

        metadata = {
            'type': 'multi_sine',
            'frequencies': frequencies,
            'amplitudes': amplitudes,
            'phases': phases,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def complex_signal(self, length: int, components: List[Dict] = None,
                      **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a complex signal with multiple frequency components."""
        if components is None:
            components = [
                {'freq': 5, 'amp': 1.0, 'phase': 0},
                {'freq': 10, 'amp': 0.5, 'phase': np.pi/4},
                {'freq': 20, 'amp': 0.3, 'phase': np.pi/2}
            ]

        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)

        for comp in components:
            signal += comp['amp'] * np.sin(2 * np.pi * comp['freq'] * t + comp.get('phase', 0))

        metadata = {
            'type': 'complex',
            'components': components,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Modulated Signals
    # ========================================================================

    def amplitude_modulated(self, length: int, carrier_freq: float = 10.0,
                          modulation_freq: float = 1.0, modulation_index: float = 0.5,
                          **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an amplitude modulated signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        carrier = np.sin(2 * np.pi * carrier_freq * t)
        modulator = 1 + modulation_index * np.sin(2 * np.pi * modulation_freq * t)
        signal = modulator * carrier
        metadata = {
            'type': 'amplitude_modulated',
            'carrier_freq': carrier_freq,
            'modulation_freq': modulation_freq,
            'modulation_index': modulation_index,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def frequency_modulated(self, length: int, carrier_freq: float = 10.0,
                          modulation_freq: float = 1.0, modulation_index: float = 5.0,
                          **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a frequency modulated signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        modulator = modulation_index * np.sin(2 * np.pi * modulation_freq * t)
        phase = 2 * np.pi * carrier_freq * t + modulator
        signal = np.sin(phase)
        metadata = {
            'type': 'frequency_modulated',
            'carrier_freq': carrier_freq,
            'modulation_freq': modulation_freq,
            'modulation_index': modulation_index,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def phase_modulated(self, length: int, carrier_freq: float = 10.0,
                       modulation_freq: float = 1.0, modulation_index: float = np.pi,
                       **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a phase modulated signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        phase_modulation = modulation_index * np.sin(2 * np.pi * modulation_freq * t)
        signal = np.sin(2 * np.pi * carrier_freq * t + phase_modulation)
        metadata = {
            'type': 'phase_modulated',
            'carrier_freq': carrier_freq,
            'modulation_freq': modulation_freq,
            'modulation_index': modulation_index,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def pulse_width_modulated(self, length: int, carrier_freq: float = 10.0,
                            modulation_freq: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a pulse width modulated signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        duty_cycle = 0.5 + 0.4 * np.sin(2 * np.pi * modulation_freq * t)
        signal = np.zeros(length)

        # Generate PWM signal
        phase = (carrier_freq * t) % 1
        for i in range(length):
            signal[i] = 1 if phase[i] < duty_cycle[i] else -1

        metadata = {
            'type': 'pulse_width_modulated',
            'carrier_freq': carrier_freq,
            'modulation_freq': modulation_freq,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Transient Signals
    # ========================================================================

    def impulse_train(self, length: int, period: int = 100, amplitude: float = 1.0,
                     jitter: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an impulse train with optional jitter."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)

        if jitter > 0:
            positions = np.arange(0, length, period)
            positions += np.random.uniform(-jitter * period, jitter * period, len(positions))
            positions = np.clip(positions.astype(int), 0, length - 1)
            signal[positions] = amplitude
        else:
            signal[::period] = amplitude

        metadata = {
            'type': 'impulse_train',
            'period': period,
            'amplitude': amplitude,
            'jitter': jitter,
            'num_impulses': np.sum(signal != 0),
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def step_function(self, length: int, step_time: float = 0.5, amplitude: float = 1.0,
                     **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a step function."""
        t = np.linspace(0, 1, length)
        signal = np.ones(length) * amplitude
        signal[t < step_time] = 0
        metadata = {
            'type': 'step',
            'step_time': step_time,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t * length / self.sampling_rate, signal, metadata

    def ramp_function(self, length: int, slope: float = 1.0, start_value: float = 0.0,
                     **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a ramp function."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = start_value + slope * t
        metadata = {
            'type': 'ramp',
            'slope': slope,
            'start_value': start_value,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def exponential_decay(self, length: int, amplitude: float = 1.0, decay_rate: float = 1.0,
                        **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an exponential decay signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = amplitude * np.exp(-decay_rate * t)
        metadata = {
            'type': 'exponential_decay',
            'amplitude': amplitude,
            'decay_rate': decay_rate,
            'time_constant': 1 / decay_rate,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def damped_sine(self, length: int, freq: float = 5.0, amplitude: float = 1.0,
                   damping: float = 0.1, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a damped sine wave."""
        t = np.linspace(0, length / self.sampling_rate, length)
        envelope = amplitude * np.exp(-damping * t)
        signal = envelope * np.sin(2 * np.pi * freq * t)
        metadata = {
            'type': 'damped_sine',
            'frequency': freq,
            'amplitude': amplitude,
            'damping': damping,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Biological/Movement Signals
    # ========================================================================

    def ecg_like(self, length: int, heart_rate: float = 60.0, amplitude: float = 1.0,
                **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an ECG-like signal."""
        t = np.linspace(0, length / self.sampling_rate, length)
        beat_period = 60.0 / heart_rate  # Period in seconds
        n_beats = int(t[-1] / beat_period)

        signal = np.zeros(length)

        # Simple ECG model with P, QRS, T waves
        for beat in range(n_beats):
            beat_start = beat * beat_period
            beat_indices = np.where((t >= beat_start) & (t < beat_start + beat_period))[0]

            if len(beat_indices) > 0:
                beat_t = t[beat_indices] - beat_start
                # P wave
                p_wave = 0.2 * amplitude * np.exp(-((beat_t - 0.15) ** 2) / (2 * 0.01))
                # QRS complex
                qrs = amplitude * np.exp(-((beat_t - 0.2) ** 2) / (2 * 0.001))
                # T wave
                t_wave = 0.3 * amplitude * np.exp(-((beat_t - 0.4) ** 2) / (2 * 0.02))
                signal[beat_indices] = p_wave + qrs + t_wave

        metadata = {
            'type': 'ecg_like',
            'heart_rate': heart_rate,
            'amplitude': amplitude,
            'n_beats': n_beats,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def emg_like(self, length: int, burst_frequency: float = 2.0, burst_duration: float = 0.2,
                amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an EMG-like signal with bursts of activity."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)

        # Generate burst envelope
        burst_period = 1.0 / burst_frequency
        for burst_start in np.arange(0, t[-1], burst_period):
            burst_mask = (t >= burst_start) & (t < burst_start + burst_duration)
            # High-frequency noise during burst
            signal[burst_mask] = amplitude * np.random.normal(0, 1, np.sum(burst_mask))

        # Add baseline noise
        signal += 0.05 * amplitude * np.random.normal(0, 1, length)

        metadata = {
            'type': 'emg_like',
            'burst_frequency': burst_frequency,
            'burst_duration': burst_duration,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def tremor_signal(self, length: int, tremor_freq: float = 5.0, base_freq: float = 0.5,
                     tremor_amplitude: float = 0.3, base_amplitude: float = 1.0,
                     **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a tremor-like signal (slow movement with superimposed tremor)."""
        t = np.linspace(0, length / self.sampling_rate, length)
        base_movement = base_amplitude * np.sin(2 * np.pi * base_freq * t)
        tremor = tremor_amplitude * np.sin(2 * np.pi * tremor_freq * t)
        signal = base_movement + tremor
        metadata = {
            'type': 'tremor',
            'tremor_freq': tremor_freq,
            'base_freq': base_freq,
            'tremor_amplitude': tremor_amplitude,
            'base_amplitude': base_amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def gait_pattern(self, length: int, step_frequency: float = 2.0, amplitude: float = 1.0,
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a gait-like pattern."""
        t = np.linspace(0, length / self.sampling_rate, length)
        # Double bump pattern for heel strike and toe-off
        signal = amplitude * (np.sin(2 * np.pi * step_frequency * t) +
                             0.3 * np.sin(4 * np.pi * step_frequency * t))
        # Add some variability
        signal += 0.05 * amplitude * np.random.normal(0, 1, length)
        metadata = {
            'type': 'gait_pattern',
            'step_frequency': step_frequency,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def breathing_pattern(self, length: int, breathing_rate: float = 12.0,
                        inhale_ratio: float = 0.4, amplitude: float = 1.0,
                        **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a breathing-like pattern."""
        t = np.linspace(0, length / self.sampling_rate, length)
        breathing_period = 60.0 / breathing_rate  # Period in seconds
        signal = np.zeros(length)

        for breath_start in np.arange(0, t[-1], breathing_period):
            inhale_end = breath_start + inhale_ratio * breathing_period
            exhale_end = breath_start + breathing_period

            # Inhale phase (rising)
            inhale_mask = (t >= breath_start) & (t < inhale_end)
            if np.any(inhale_mask):
                inhale_t = (t[inhale_mask] - breath_start) / (inhale_ratio * breathing_period)
                signal[inhale_mask] = amplitude * (1 - np.cos(np.pi * inhale_t)) / 2

            # Exhale phase (falling)
            exhale_mask = (t >= inhale_end) & (t < exhale_end)
            if np.any(exhale_mask):
                exhale_t = (t[exhale_mask] - inhale_end) / ((1 - inhale_ratio) * breathing_period)
                signal[exhale_mask] = amplitude * (1 + np.cos(np.pi * exhale_t)) / 2

        metadata = {
            'type': 'breathing_pattern',
            'breathing_rate': breathing_rate,
            'inhale_ratio': inhale_ratio,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Special Signals
    # ========================================================================

    def lorenz_attractor(self, length: int, sigma: float = 10.0, rho: float = 28.0,
                        beta: float = 8/3, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate signal from Lorenz attractor (chaotic system)."""
        dt = 0.01
        t = np.arange(0, length * dt, dt)[:length]

        # Initialize
        x, y, z = 1.0, 1.0, 1.0
        signal = np.zeros(length)

        # Integrate Lorenz equations
        for i in range(length):
            dx = sigma * (y - x) * dt
            dy = (x * (rho - z) - y) * dt
            dz = (x * y - beta * z) * dt
            x += dx
            y += dy
            z += dz
            signal[i] = x  # Use x coordinate as signal

        # Normalize
        signal = (signal - np.mean(signal)) / np.std(signal)

        metadata = {
            'type': 'lorenz_attractor',
            'sigma': sigma,
            'rho': rho,
            'beta': beta,
            'sampling_rate': self.sampling_rate
        }
        return t * self.sampling_rate, signal, metadata

    def logistic_map(self, length: int, r: float = 3.8, x0: float = 0.5,
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate signal from logistic map (chaotic for r > 3.57)."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)
        x = x0

        for i in range(length):
            x = r * x * (1 - x)
            signal[i] = x

        metadata = {
            'type': 'logistic_map',
            'r': r,
            'x0': x0,
            'chaotic': r > 3.57,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def fractal_noise(self, length: int, hurst: float = 0.5, amplitude: float = 1.0,
                     **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate fractal noise using fractional Brownian motion."""
        from scipy.fft import fft, ifft, fftfreq

        # Generate frequencies
        freqs = fftfreq(length, 1/self.sampling_rate)
        freqs[0] = 1  # Avoid division by zero

        # Generate random phases
        phases = np.random.uniform(0, 2*np.pi, length)

        # Create power spectrum with 1/f^(2H+1) scaling
        power = np.abs(freqs) ** -(2 * hurst + 1)
        power[0] = 0  # Remove DC component

        # Generate signal in frequency domain
        fft_signal = np.sqrt(power) * np.exp(1j * phases)

        # Transform to time domain
        signal = np.real(ifft(fft_signal))
        signal = amplitude * signal / np.std(signal)

        t = np.linspace(0, length / self.sampling_rate, length)
        metadata = {
            'type': 'fractal_noise',
            'hurst': hurst,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def burst_signal(self, length: int, burst_freq: float = 2.0, burst_duration: float = 0.1,
                    carrier_freq: float = 20.0, amplitude: float = 1.0,
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate burst signal (periodic bursts of oscillation)."""
        t = np.linspace(0, length / self.sampling_rate, length)
        carrier = amplitude * np.sin(2 * np.pi * carrier_freq * t)

        # Create burst envelope
        burst_period = 1.0 / burst_freq
        envelope = np.zeros(length)
        for burst_start in np.arange(0, t[-1], burst_period):
            burst_mask = (t >= burst_start) & (t < burst_start + burst_duration)
            envelope[burst_mask] = 1.0

        signal = carrier * envelope
        metadata = {
            'type': 'burst_signal',
            'burst_freq': burst_freq,
            'burst_duration': burst_duration,
            'carrier_freq': carrier_freq,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def spike_train(self, length: int, spike_rate: float = 10.0, refractory_period: float = 0.002,
                   amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a neural spike train."""
        t = np.linspace(0, length / self.sampling_rate, length)
        dt = 1.0 / self.sampling_rate
        signal = np.zeros(length)

        # Generate spikes with refractory period
        last_spike_time = -refractory_period
        spike_times = []

        for i, time in enumerate(t):
            if time - last_spike_time > refractory_period:
                if np.random.random() < spike_rate * dt:
                    signal[i] = amplitude
                    last_spike_time = time
                    spike_times.append(time)

        metadata = {
            'type': 'spike_train',
            'spike_rate': spike_rate,
            'refractory_period': refractory_period,
            'amplitude': amplitude,
            'num_spikes': len(spike_times),
            'actual_rate': len(spike_times) / (t[-1] - t[0]) if t[-1] > t[0] else 0,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    # ========================================================================
    # Combined Signals
    # ========================================================================

    def noisy_sine(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                  noise_level: float = 0.1, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a sine wave with added noise."""
        t, clean_signal, _ = self.sine_wave(length, freq, amplitude)
        noise = np.random.normal(0, noise_level, length)
        signal = clean_signal + noise
        metadata = {
            'type': 'noisy_sine',
            'frequency': freq,
            'amplitude': amplitude,
            'noise_level': noise_level,
            'snr': 20 * np.log10(amplitude / noise_level) if noise_level > 0 else np.inf,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def sine_with_drift(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                       drift_rate: float = 0.01, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a sine wave with linear drift."""
        t, sine_signal, _ = self.sine_wave(length, freq, amplitude)
        drift = drift_rate * t
        signal = sine_signal + drift
        metadata = {
            'type': 'sine_with_drift',
            'frequency': freq,
            'amplitude': amplitude,
            'drift_rate': drift_rate,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def intermittent_signal(self, length: int, active_ratio: float = 0.3,
                          signal_freq: float = 5.0, amplitude: float = 1.0,
                          **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate an intermittent signal (randomly switches on/off)."""
        t = np.linspace(0, length / self.sampling_rate, length)
        base_signal = amplitude * np.sin(2 * np.pi * signal_freq * t)

        # Create random on/off pattern
        switch_period = int(self.sampling_rate / 2)  # Switch every 0.5 seconds
        n_switches = length // switch_period + 1
        switches = np.random.random(n_switches) < active_ratio
        gate = np.repeat(switches, switch_period)[:length]

        signal = base_signal * gate
        metadata = {
            'type': 'intermittent_signal',
            'active_ratio': active_ratio,
            'signal_freq': signal_freq,
            'amplitude': amplitude,
            'actual_active_ratio': np.mean(gate),
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

    def switched_signal(self, length: int, switch_period: int = 200,
                       freq1: float = 2.0, freq2: float = 8.0, amplitude: float = 1.0,
                       **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
        """Generate a signal that switches between two frequencies."""
        t = np.linspace(0, length / self.sampling_rate, length)
        signal = np.zeros(length)

        for i in range(0, length, switch_period * 2):
            # First period: freq1
            end1 = min(i + switch_period, length)
            signal[i:end1] = amplitude * np.sin(2 * np.pi * freq1 * t[i:end1])

            # Second period: freq2
            start2 = end1
            end2 = min(start2 + switch_period, length)
            if start2 < length:
                signal[start2:end2] = amplitude * np.sin(2 * np.pi * freq2 * t[start2:end2])

        metadata = {
            'type': 'switched_signal',
            'switch_period': switch_period,
            'freq1': freq1,
            'freq2': freq2,
            'amplitude': amplitude,
            'sampling_rate': self.sampling_rate
        }
        return t, signal, metadata

available_signals property

Get list of available signal types.

signal_info property

Get detailed information about all signal types.

Returns:

Type Description
dict

Dictionary mapping signal names to their metadata including description and category.

__init__(sampling_rate=100.0)

Initialize the signal generator.

Args: sampling_rate: Default sampling rate in Hz

Source code in pyeyesweb/utils/signal_generators.py
def __init__(self, sampling_rate: float = 100.0):
    """
    Initialize the signal generator.

    Args:
        sampling_rate: Default sampling rate in Hz
    """
    self.sampling_rate = sampling_rate
    self._register_generators()

amplitude_modulated(length, carrier_freq=10.0, modulation_freq=1.0, modulation_index=0.5, **kwargs)

Generate an amplitude modulated signal.

Source code in pyeyesweb/utils/signal_generators.py
def amplitude_modulated(self, length: int, carrier_freq: float = 10.0,
                      modulation_freq: float = 1.0, modulation_index: float = 0.5,
                      **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an amplitude modulated signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    carrier = np.sin(2 * np.pi * carrier_freq * t)
    modulator = 1 + modulation_index * np.sin(2 * np.pi * modulation_freq * t)
    signal = modulator * carrier
    metadata = {
        'type': 'amplitude_modulated',
        'carrier_freq': carrier_freq,
        'modulation_freq': modulation_freq,
        'modulation_index': modulation_index,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

breathing_pattern(length, breathing_rate=12.0, inhale_ratio=0.4, amplitude=1.0, **kwargs)

Generate a breathing-like pattern.

Source code in pyeyesweb/utils/signal_generators.py
def breathing_pattern(self, length: int, breathing_rate: float = 12.0,
                    inhale_ratio: float = 0.4, amplitude: float = 1.0,
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a breathing-like pattern."""
    t = np.linspace(0, length / self.sampling_rate, length)
    breathing_period = 60.0 / breathing_rate  # Period in seconds
    signal = np.zeros(length)

    for breath_start in np.arange(0, t[-1], breathing_period):
        inhale_end = breath_start + inhale_ratio * breathing_period
        exhale_end = breath_start + breathing_period

        # Inhale phase (rising)
        inhale_mask = (t >= breath_start) & (t < inhale_end)
        if np.any(inhale_mask):
            inhale_t = (t[inhale_mask] - breath_start) / (inhale_ratio * breathing_period)
            signal[inhale_mask] = amplitude * (1 - np.cos(np.pi * inhale_t)) / 2

        # Exhale phase (falling)
        exhale_mask = (t >= inhale_end) & (t < exhale_end)
        if np.any(exhale_mask):
            exhale_t = (t[exhale_mask] - inhale_end) / ((1 - inhale_ratio) * breathing_period)
            signal[exhale_mask] = amplitude * (1 + np.cos(np.pi * exhale_t)) / 2

    metadata = {
        'type': 'breathing_pattern',
        'breathing_rate': breathing_rate,
        'inhale_ratio': inhale_ratio,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

brownian_motion(length, std=0.1, seed=None, **kwargs)

Generate Brownian motion (random walk).

Source code in pyeyesweb/utils/signal_generators.py
def brownian_motion(self, length: int, std: float = 0.1, seed: Optional[int] = None,
                   **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate Brownian motion (random walk)."""
    if seed is not None:
        np.random.seed(seed)
    t = np.linspace(0, length / self.sampling_rate, length)
    steps = np.random.normal(0, std, length)
    signal = np.cumsum(steps)
    metadata = {
        'type': 'brownian_motion',
        'step_std': std,
        'seed': seed,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

burst_signal(length, burst_freq=2.0, burst_duration=0.1, carrier_freq=20.0, amplitude=1.0, **kwargs)

Generate burst signal (periodic bursts of oscillation).

Source code in pyeyesweb/utils/signal_generators.py
def burst_signal(self, length: int, burst_freq: float = 2.0, burst_duration: float = 0.1,
                carrier_freq: float = 20.0, amplitude: float = 1.0,
                **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate burst signal (periodic bursts of oscillation)."""
    t = np.linspace(0, length / self.sampling_rate, length)
    carrier = amplitude * np.sin(2 * np.pi * carrier_freq * t)

    # Create burst envelope
    burst_period = 1.0 / burst_freq
    envelope = np.zeros(length)
    for burst_start in np.arange(0, t[-1], burst_period):
        burst_mask = (t >= burst_start) & (t < burst_start + burst_duration)
        envelope[burst_mask] = 1.0

    signal = carrier * envelope
    metadata = {
        'type': 'burst_signal',
        'burst_freq': burst_freq,
        'burst_duration': burst_duration,
        'carrier_freq': carrier_freq,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

chirp_signal(length, freq_start=1.0, freq_end=10.0, amplitude=1.0, method='linear', **kwargs)

Generate a chirp signal (linear frequency sweep).

Source code in pyeyesweb/utils/signal_generators.py
def chirp_signal(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                amplitude: float = 1.0, method: str = 'linear',
                **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a chirp signal (linear frequency sweep)."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * sp_signal.chirp(t, freq_start, t[-1], freq_end, method=method)
    metadata = {
        'type': 'chirp',
        'freq_start': freq_start,
        'freq_end': freq_end,
        'method': method,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

complex_signal(length, components=None, **kwargs)

Generate a complex signal with multiple frequency components.

Source code in pyeyesweb/utils/signal_generators.py
def complex_signal(self, length: int, components: List[Dict] = None,
                  **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a complex signal with multiple frequency components."""
    if components is None:
        components = [
            {'freq': 5, 'amp': 1.0, 'phase': 0},
            {'freq': 10, 'amp': 0.5, 'phase': np.pi/4},
            {'freq': 20, 'amp': 0.3, 'phase': np.pi/2}
        ]

    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)

    for comp in components:
        signal += comp['amp'] * np.sin(2 * np.pi * comp['freq'] * t + comp.get('phase', 0))

    metadata = {
        'type': 'complex',
        'components': components,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

cosine_wave(length, freq=1.0, amplitude=1.0, phase=0.0, dc_offset=0.0, **kwargs)

Generate a cosine wave.

Source code in pyeyesweb/utils/signal_generators.py
def cosine_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                phase: float = 0.0, dc_offset: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a cosine wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * np.cos(2 * np.pi * freq * t + phase) + dc_offset
    metadata = {
        'type': 'cosine',
        'frequency': freq,
        'amplitude': amplitude,
        'phase': phase,
        'dc_offset': dc_offset,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

damped_sine(length, freq=5.0, amplitude=1.0, damping=0.1, **kwargs)

Generate a damped sine wave.

Source code in pyeyesweb/utils/signal_generators.py
def damped_sine(self, length: int, freq: float = 5.0, amplitude: float = 1.0,
               damping: float = 0.1, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a damped sine wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    envelope = amplitude * np.exp(-damping * t)
    signal = envelope * np.sin(2 * np.pi * freq * t)
    metadata = {
        'type': 'damped_sine',
        'frequency': freq,
        'amplitude': amplitude,
        'damping': damping,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

ecg_like(length, heart_rate=60.0, amplitude=1.0, **kwargs)

Generate an ECG-like signal.

Source code in pyeyesweb/utils/signal_generators.py
def ecg_like(self, length: int, heart_rate: float = 60.0, amplitude: float = 1.0,
            **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an ECG-like signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    beat_period = 60.0 / heart_rate  # Period in seconds
    n_beats = int(t[-1] / beat_period)

    signal = np.zeros(length)

    # Simple ECG model with P, QRS, T waves
    for beat in range(n_beats):
        beat_start = beat * beat_period
        beat_indices = np.where((t >= beat_start) & (t < beat_start + beat_period))[0]

        if len(beat_indices) > 0:
            beat_t = t[beat_indices] - beat_start
            # P wave
            p_wave = 0.2 * amplitude * np.exp(-((beat_t - 0.15) ** 2) / (2 * 0.01))
            # QRS complex
            qrs = amplitude * np.exp(-((beat_t - 0.2) ** 2) / (2 * 0.001))
            # T wave
            t_wave = 0.3 * amplitude * np.exp(-((beat_t - 0.4) ** 2) / (2 * 0.02))
            signal[beat_indices] = p_wave + qrs + t_wave

    metadata = {
        'type': 'ecg_like',
        'heart_rate': heart_rate,
        'amplitude': amplitude,
        'n_beats': n_beats,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

emg_like(length, burst_frequency=2.0, burst_duration=0.2, amplitude=1.0, **kwargs)

Generate an EMG-like signal with bursts of activity.

Source code in pyeyesweb/utils/signal_generators.py
def emg_like(self, length: int, burst_frequency: float = 2.0, burst_duration: float = 0.2,
            amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an EMG-like signal with bursts of activity."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)

    # Generate burst envelope
    burst_period = 1.0 / burst_frequency
    for burst_start in np.arange(0, t[-1], burst_period):
        burst_mask = (t >= burst_start) & (t < burst_start + burst_duration)
        # High-frequency noise during burst
        signal[burst_mask] = amplitude * np.random.normal(0, 1, np.sum(burst_mask))

    # Add baseline noise
    signal += 0.05 * amplitude * np.random.normal(0, 1, length)

    metadata = {
        'type': 'emg_like',
        'burst_frequency': burst_frequency,
        'burst_duration': burst_duration,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

exponential_chirp(length, freq_start=1.0, freq_end=10.0, amplitude=1.0, **kwargs)

Generate an exponential chirp signal.

Source code in pyeyesweb/utils/signal_generators.py
def exponential_chirp(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                     amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an exponential chirp signal."""
    return self.chirp_signal(length, freq_start, freq_end, amplitude, method='exponential')

exponential_decay(length, amplitude=1.0, decay_rate=1.0, **kwargs)

Generate an exponential decay signal.

Source code in pyeyesweb/utils/signal_generators.py
def exponential_decay(self, length: int, amplitude: float = 1.0, decay_rate: float = 1.0,
                    **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an exponential decay signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * np.exp(-decay_rate * t)
    metadata = {
        'type': 'exponential_decay',
        'amplitude': amplitude,
        'decay_rate': decay_rate,
        'time_constant': 1 / decay_rate,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

fractal_noise(length, hurst=0.5, amplitude=1.0, **kwargs)

Generate fractal noise using fractional Brownian motion.

Source code in pyeyesweb/utils/signal_generators.py
def fractal_noise(self, length: int, hurst: float = 0.5, amplitude: float = 1.0,
                 **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate fractal noise using fractional Brownian motion."""
    from scipy.fft import fft, ifft, fftfreq

    # Generate frequencies
    freqs = fftfreq(length, 1/self.sampling_rate)
    freqs[0] = 1  # Avoid division by zero

    # Generate random phases
    phases = np.random.uniform(0, 2*np.pi, length)

    # Create power spectrum with 1/f^(2H+1) scaling
    power = np.abs(freqs) ** -(2 * hurst + 1)
    power[0] = 0  # Remove DC component

    # Generate signal in frequency domain
    fft_signal = np.sqrt(power) * np.exp(1j * phases)

    # Transform to time domain
    signal = np.real(ifft(fft_signal))
    signal = amplitude * signal / np.std(signal)

    t = np.linspace(0, length / self.sampling_rate, length)
    metadata = {
        'type': 'fractal_noise',
        'hurst': hurst,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

frequency_modulated(length, carrier_freq=10.0, modulation_freq=1.0, modulation_index=5.0, **kwargs)

Generate a frequency modulated signal.

Source code in pyeyesweb/utils/signal_generators.py
def frequency_modulated(self, length: int, carrier_freq: float = 10.0,
                      modulation_freq: float = 1.0, modulation_index: float = 5.0,
                      **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a frequency modulated signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    modulator = modulation_index * np.sin(2 * np.pi * modulation_freq * t)
    phase = 2 * np.pi * carrier_freq * t + modulator
    signal = np.sin(phase)
    metadata = {
        'type': 'frequency_modulated',
        'carrier_freq': carrier_freq,
        'modulation_freq': modulation_freq,
        'modulation_index': modulation_index,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

gait_pattern(length, step_frequency=2.0, amplitude=1.0, **kwargs)

Generate a gait-like pattern.

Source code in pyeyesweb/utils/signal_generators.py
def gait_pattern(self, length: int, step_frequency: float = 2.0, amplitude: float = 1.0,
                **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a gait-like pattern."""
    t = np.linspace(0, length / self.sampling_rate, length)
    # Double bump pattern for heel strike and toe-off
    signal = amplitude * (np.sin(2 * np.pi * step_frequency * t) +
                         0.3 * np.sin(4 * np.pi * step_frequency * t))
    # Add some variability
    signal += 0.05 * amplitude * np.random.normal(0, 1, length)
    metadata = {
        'type': 'gait_pattern',
        'step_frequency': step_frequency,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

gaussian_noise(length, mean=0.0, std=1.0, seed=None, **kwargs)

Generate Gaussian (white) noise.

Source code in pyeyesweb/utils/signal_generators.py
def gaussian_noise(self, length: int, mean: float = 0.0, std: float = 1.0,
                  seed: Optional[int] = None, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate Gaussian (white) noise."""
    if seed is not None:
        np.random.seed(seed)
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.random.normal(mean, std, length)
    metadata = {
        'type': 'gaussian',
        'mean': mean,
        'std': std,
        'seed': seed,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

generate(signal_type, length=1000, **kwargs)

Generate a signal based on type and parameters.

Args: signal_type: Type of signal to generate length: Number of samples **kwargs: Additional parameters specific to each signal type

Returns: Tuple of (time_array, signal_array, metadata_dict)

Source code in pyeyesweb/utils/signal_generators.py
def generate(self, signal_type: str, length: int = 1000, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict[str, Any]]:
    """
    Generate a signal based on type and parameters.

    Args:
        signal_type: Type of signal to generate
        length: Number of samples
        **kwargs: Additional parameters specific to each signal type

    Returns:
        Tuple of (time_array, signal_array, metadata_dict)
    """
    if signal_type not in self._generators:
        available = ', '.join(sorted(self._generators.keys()))
        raise ValueError(f"Unknown signal type: '{signal_type}'. Available types: {available}")

    # Override sampling rate if provided
    if 'sampling_rate' in kwargs:
        self.sampling_rate = kwargs['sampling_rate']

    return self._generators[signal_type](length, **kwargs)

get_signals_by_category()

Get signals organized by category.

Returns:

Type Description
dict

Dictionary mapping category names to lists of signal types.

Source code in pyeyesweb/utils/signal_generators.py
def get_signals_by_category(self) -> Dict[str, List[str]]:
    """Get signals organized by category.

    Returns
    -------
    dict
        Dictionary mapping category names to lists of signal types.
    """
    categories = {}
    for signal_name, info in self.signal_info.items():
        category = info['category']
        if category not in categories:
            categories[category] = []
        categories[category].append(signal_name)
    return categories

hyperbolic_chirp(length, freq_start=1.0, freq_end=10.0, amplitude=1.0, **kwargs)

Generate a hyperbolic chirp signal.

Source code in pyeyesweb/utils/signal_generators.py
def hyperbolic_chirp(self, length: int, freq_start: float = 1.0, freq_end: float = 10.0,
                    amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a hyperbolic chirp signal."""
    return self.chirp_signal(length, freq_start, freq_end, amplitude, method='hyperbolic')

impulse_train(length, period=100, amplitude=1.0, jitter=0.0, **kwargs)

Generate an impulse train with optional jitter.

Source code in pyeyesweb/utils/signal_generators.py
def impulse_train(self, length: int, period: int = 100, amplitude: float = 1.0,
                 jitter: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an impulse train with optional jitter."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)

    if jitter > 0:
        positions = np.arange(0, length, period)
        positions += np.random.uniform(-jitter * period, jitter * period, len(positions))
        positions = np.clip(positions.astype(int), 0, length - 1)
        signal[positions] = amplitude
    else:
        signal[::period] = amplitude

    metadata = {
        'type': 'impulse_train',
        'period': period,
        'amplitude': amplitude,
        'jitter': jitter,
        'num_impulses': np.sum(signal != 0),
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

intermittent_signal(length, active_ratio=0.3, signal_freq=5.0, amplitude=1.0, **kwargs)

Generate an intermittent signal (randomly switches on/off).

Source code in pyeyesweb/utils/signal_generators.py
def intermittent_signal(self, length: int, active_ratio: float = 0.3,
                      signal_freq: float = 5.0, amplitude: float = 1.0,
                      **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate an intermittent signal (randomly switches on/off)."""
    t = np.linspace(0, length / self.sampling_rate, length)
    base_signal = amplitude * np.sin(2 * np.pi * signal_freq * t)

    # Create random on/off pattern
    switch_period = int(self.sampling_rate / 2)  # Switch every 0.5 seconds
    n_switches = length // switch_period + 1
    switches = np.random.random(n_switches) < active_ratio
    gate = np.repeat(switches, switch_period)[:length]

    signal = base_signal * gate
    metadata = {
        'type': 'intermittent_signal',
        'active_ratio': active_ratio,
        'signal_freq': signal_freq,
        'amplitude': amplitude,
        'actual_active_ratio': np.mean(gate),
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

logistic_map(length, r=3.8, x0=0.5, **kwargs)

Generate signal from logistic map (chaotic for r > 3.57).

Source code in pyeyesweb/utils/signal_generators.py
def logistic_map(self, length: int, r: float = 3.8, x0: float = 0.5,
                **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate signal from logistic map (chaotic for r > 3.57)."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)
    x = x0

    for i in range(length):
        x = r * x * (1 - x)
        signal[i] = x

    metadata = {
        'type': 'logistic_map',
        'r': r,
        'x0': x0,
        'chaotic': r > 3.57,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

lorenz_attractor(length, sigma=10.0, rho=28.0, beta=8 / 3, **kwargs)

Generate signal from Lorenz attractor (chaotic system).

Source code in pyeyesweb/utils/signal_generators.py
def lorenz_attractor(self, length: int, sigma: float = 10.0, rho: float = 28.0,
                    beta: float = 8/3, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate signal from Lorenz attractor (chaotic system)."""
    dt = 0.01
    t = np.arange(0, length * dt, dt)[:length]

    # Initialize
    x, y, z = 1.0, 1.0, 1.0
    signal = np.zeros(length)

    # Integrate Lorenz equations
    for i in range(length):
        dx = sigma * (y - x) * dt
        dy = (x * (rho - z) - y) * dt
        dz = (x * y - beta * z) * dt
        x += dx
        y += dy
        z += dz
        signal[i] = x  # Use x coordinate as signal

    # Normalize
    signal = (signal - np.mean(signal)) / np.std(signal)

    metadata = {
        'type': 'lorenz_attractor',
        'sigma': sigma,
        'rho': rho,
        'beta': beta,
        'sampling_rate': self.sampling_rate
    }
    return t * self.sampling_rate, signal, metadata

multi_sine(length, frequencies=None, amplitudes=None, phases=None, **kwargs)

Generate multiple sine waves combined.

Source code in pyeyesweb/utils/signal_generators.py
def multi_sine(self, length: int, frequencies: List[float] = None,
              amplitudes: List[float] = None, phases: List[float] = None,
              **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate multiple sine waves combined."""
    if frequencies is None:
        frequencies = [1.0, 3.0, 5.0]
    if amplitudes is None:
        amplitudes = [1.0] * len(frequencies)
    if phases is None:
        phases = [0.0] * len(frequencies)

    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)

    for freq, amp, phase in zip(frequencies, amplitudes, phases):
        signal += amp * np.sin(2 * np.pi * freq * t + phase)

    metadata = {
        'type': 'multi_sine',
        'frequencies': frequencies,
        'amplitudes': amplitudes,
        'phases': phases,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

noisy_sine(length, freq=1.0, amplitude=1.0, noise_level=0.1, **kwargs)

Generate a sine wave with added noise.

Source code in pyeyesweb/utils/signal_generators.py
def noisy_sine(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
              noise_level: float = 0.1, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a sine wave with added noise."""
    t, clean_signal, _ = self.sine_wave(length, freq, amplitude)
    noise = np.random.normal(0, noise_level, length)
    signal = clean_signal + noise
    metadata = {
        'type': 'noisy_sine',
        'frequency': freq,
        'amplitude': amplitude,
        'noise_level': noise_level,
        'snr': 20 * np.log10(amplitude / noise_level) if noise_level > 0 else np.inf,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

phase_modulated(length, carrier_freq=10.0, modulation_freq=1.0, modulation_index=np.pi, **kwargs)

Generate a phase modulated signal.

Source code in pyeyesweb/utils/signal_generators.py
def phase_modulated(self, length: int, carrier_freq: float = 10.0,
                   modulation_freq: float = 1.0, modulation_index: float = np.pi,
                   **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a phase modulated signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    phase_modulation = modulation_index * np.sin(2 * np.pi * modulation_freq * t)
    signal = np.sin(2 * np.pi * carrier_freq * t + phase_modulation)
    metadata = {
        'type': 'phase_modulated',
        'carrier_freq': carrier_freq,
        'modulation_freq': modulation_freq,
        'modulation_index': modulation_index,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

pink_noise(length, amplitude=1.0, seed=None, **kwargs)

Generate pink (1/f) noise.

Source code in pyeyesweb/utils/signal_generators.py
def pink_noise(self, length: int, amplitude: float = 1.0, seed: Optional[int] = None,
              **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate pink (1/f) noise."""
    if seed is not None:
        np.random.seed(seed)

    # Generate white noise
    white = np.random.randn(length)

    # Apply 1/f filter in frequency domain
    fft = np.fft.rfft(white)
    freqs = np.fft.rfftfreq(length)
    freqs[0] = 1  # Avoid division by zero
    fft = fft / np.sqrt(freqs)
    signal = np.fft.irfft(fft, length)

    # Normalize
    signal = amplitude * signal / np.std(signal)

    t = np.linspace(0, length / self.sampling_rate, length)
    metadata = {
        'type': 'pink_noise',
        'amplitude': amplitude,
        'seed': seed,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

pulse_width_modulated(length, carrier_freq=10.0, modulation_freq=1.0, **kwargs)

Generate a pulse width modulated signal.

Source code in pyeyesweb/utils/signal_generators.py
def pulse_width_modulated(self, length: int, carrier_freq: float = 10.0,
                        modulation_freq: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a pulse width modulated signal."""
    t = np.linspace(0, length / self.sampling_rate, length)
    duty_cycle = 0.5 + 0.4 * np.sin(2 * np.pi * modulation_freq * t)
    signal = np.zeros(length)

    # Generate PWM signal
    phase = (carrier_freq * t) % 1
    for i in range(length):
        signal[i] = 1 if phase[i] < duty_cycle[i] else -1

    metadata = {
        'type': 'pulse_width_modulated',
        'carrier_freq': carrier_freq,
        'modulation_freq': modulation_freq,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

ramp_function(length, slope=1.0, start_value=0.0, **kwargs)

Generate a ramp function.

Source code in pyeyesweb/utils/signal_generators.py
def ramp_function(self, length: int, slope: float = 1.0, start_value: float = 0.0,
                 **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a ramp function."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = start_value + slope * t
    metadata = {
        'type': 'ramp',
        'slope': slope,
        'start_value': start_value,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

random_signal(length, seed=None, min_val=-1.0, max_val=1.0, **kwargs)

Generate random uniform noise.

Source code in pyeyesweb/utils/signal_generators.py
def random_signal(self, length: int, seed: Optional[int] = None,
                 min_val: float = -1.0, max_val: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate random uniform noise."""
    if seed is not None:
        np.random.seed(seed)
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.random.uniform(min_val, max_val, length)
    metadata = {
        'type': 'random',
        'distribution': 'uniform',
        'range': [min_val, max_val],
        'seed': seed,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

sawtooth_wave(length, freq=1.0, amplitude=1.0, width=1.0, **kwargs)

Generate a sawtooth wave.

Source code in pyeyesweb/utils/signal_generators.py
def sawtooth_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                 width: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a sawtooth wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * sp_signal.sawtooth(2 * np.pi * freq * t, width=width)
    metadata = {
        'type': 'sawtooth',
        'frequency': freq,
        'amplitude': amplitude,
        'width': width,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

sine_wave(length, freq=1.0, amplitude=1.0, phase=0.0, dc_offset=0.0, **kwargs)

Generate a sine wave.

Source code in pyeyesweb/utils/signal_generators.py
def sine_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
              phase: float = 0.0, dc_offset: float = 0.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a sine wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * np.sin(2 * np.pi * freq * t + phase) + dc_offset
    metadata = {
        'type': 'sine',
        'frequency': freq,
        'amplitude': amplitude,
        'phase': phase,
        'dc_offset': dc_offset,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

sine_with_drift(length, freq=1.0, amplitude=1.0, drift_rate=0.01, **kwargs)

Generate a sine wave with linear drift.

Source code in pyeyesweb/utils/signal_generators.py
def sine_with_drift(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                   drift_rate: float = 0.01, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a sine wave with linear drift."""
    t, sine_signal, _ = self.sine_wave(length, freq, amplitude)
    drift = drift_rate * t
    signal = sine_signal + drift
    metadata = {
        'type': 'sine_with_drift',
        'frequency': freq,
        'amplitude': amplitude,
        'drift_rate': drift_rate,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

spike_train(length, spike_rate=10.0, refractory_period=0.002, amplitude=1.0, **kwargs)

Generate a neural spike train.

Source code in pyeyesweb/utils/signal_generators.py
def spike_train(self, length: int, spike_rate: float = 10.0, refractory_period: float = 0.002,
               amplitude: float = 1.0, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a neural spike train."""
    t = np.linspace(0, length / self.sampling_rate, length)
    dt = 1.0 / self.sampling_rate
    signal = np.zeros(length)

    # Generate spikes with refractory period
    last_spike_time = -refractory_period
    spike_times = []

    for i, time in enumerate(t):
        if time - last_spike_time > refractory_period:
            if np.random.random() < spike_rate * dt:
                signal[i] = amplitude
                last_spike_time = time
                spike_times.append(time)

    metadata = {
        'type': 'spike_train',
        'spike_rate': spike_rate,
        'refractory_period': refractory_period,
        'amplitude': amplitude,
        'num_spikes': len(spike_times),
        'actual_rate': len(spike_times) / (t[-1] - t[0]) if t[-1] > t[0] else 0,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

square_wave(length, freq=1.0, amplitude=1.0, duty_cycle=0.5, **kwargs)

Generate a square wave.

Source code in pyeyesweb/utils/signal_generators.py
def square_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
               duty_cycle: float = 0.5, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a square wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * sp_signal.square(2 * np.pi * freq * t, duty=duty_cycle)
    metadata = {
        'type': 'square',
        'frequency': freq,
        'amplitude': amplitude,
        'duty_cycle': duty_cycle,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

step_function(length, step_time=0.5, amplitude=1.0, **kwargs)

Generate a step function.

Source code in pyeyesweb/utils/signal_generators.py
def step_function(self, length: int, step_time: float = 0.5, amplitude: float = 1.0,
                 **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a step function."""
    t = np.linspace(0, 1, length)
    signal = np.ones(length) * amplitude
    signal[t < step_time] = 0
    metadata = {
        'type': 'step',
        'step_time': step_time,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t * length / self.sampling_rate, signal, metadata

switched_signal(length, switch_period=200, freq1=2.0, freq2=8.0, amplitude=1.0, **kwargs)

Generate a signal that switches between two frequencies.

Source code in pyeyesweb/utils/signal_generators.py
def switched_signal(self, length: int, switch_period: int = 200,
                   freq1: float = 2.0, freq2: float = 8.0, amplitude: float = 1.0,
                   **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a signal that switches between two frequencies."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.zeros(length)

    for i in range(0, length, switch_period * 2):
        # First period: freq1
        end1 = min(i + switch_period, length)
        signal[i:end1] = amplitude * np.sin(2 * np.pi * freq1 * t[i:end1])

        # Second period: freq2
        start2 = end1
        end2 = min(start2 + switch_period, length)
        if start2 < length:
            signal[start2:end2] = amplitude * np.sin(2 * np.pi * freq2 * t[start2:end2])

    metadata = {
        'type': 'switched_signal',
        'switch_period': switch_period,
        'freq1': freq1,
        'freq2': freq2,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

tremor_signal(length, tremor_freq=5.0, base_freq=0.5, tremor_amplitude=0.3, base_amplitude=1.0, **kwargs)

Generate a tremor-like signal (slow movement with superimposed tremor).

Source code in pyeyesweb/utils/signal_generators.py
def tremor_signal(self, length: int, tremor_freq: float = 5.0, base_freq: float = 0.5,
                 tremor_amplitude: float = 0.3, base_amplitude: float = 1.0,
                 **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a tremor-like signal (slow movement with superimposed tremor)."""
    t = np.linspace(0, length / self.sampling_rate, length)
    base_movement = base_amplitude * np.sin(2 * np.pi * base_freq * t)
    tremor = tremor_amplitude * np.sin(2 * np.pi * tremor_freq * t)
    signal = base_movement + tremor
    metadata = {
        'type': 'tremor',
        'tremor_freq': tremor_freq,
        'base_freq': base_freq,
        'tremor_amplitude': tremor_amplitude,
        'base_amplitude': base_amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

triangle_wave(length, freq=1.0, amplitude=1.0, **kwargs)

Generate a triangle wave.

Source code in pyeyesweb/utils/signal_generators.py
def triangle_wave(self, length: int, freq: float = 1.0, amplitude: float = 1.0,
                 **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate a triangle wave."""
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = amplitude * sp_signal.sawtooth(2 * np.pi * freq * t, width=0.5)
    metadata = {
        'type': 'triangle',
        'frequency': freq,
        'amplitude': amplitude,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

white_noise(length, power=1.0, seed=None, **kwargs)

Generate white noise with specified power.

Source code in pyeyesweb/utils/signal_generators.py
def white_noise(self, length: int, power: float = 1.0, seed: Optional[int] = None,
               **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """Generate white noise with specified power."""
    if seed is not None:
        np.random.seed(seed)
    t = np.linspace(0, length / self.sampling_rate, length)
    signal = np.random.normal(0, np.sqrt(power), length)
    metadata = {
        'type': 'white_noise',
        'power': power,
        'seed': seed,
        'sampling_rate': self.sampling_rate
    }
    return t, signal, metadata

generate_signal(signal_type, length=1000, **kwargs)

Quick signal generation without instantiating the class.

Args: signal_type: Type of signal to generate length: Number of samples **kwargs: Additional parameters

Returns: Tuple of (time_array, signal_array, metadata_dict)

Source code in pyeyesweb/utils/signal_generators.py
def generate_signal(signal_type: str, length: int = 1000, **kwargs) -> Tuple[np.ndarray, np.ndarray, Dict]:
    """
    Quick signal generation without instantiating the class.

    Args:
        signal_type: Type of signal to generate
        length: Number of samples
        **kwargs: Additional parameters

    Returns:
        Tuple of (time_array, signal_array, metadata_dict)
    """
    generator = SignalGenerator()
    return generator.generate(signal_type, length, **kwargs)

list_available_signals()

Get list of all available signal types.

Source code in pyeyesweb/utils/signal_generators.py
def list_available_signals() -> List[str]:
    """Get list of all available signal types."""
    generator = SignalGenerator()
    return generator.available_signals