def test_transmit_receive(self) -> None: num_delay_samples = 10 signal, _, _ = self.joint.transmit() delay_offset = Signal(np.zeros((1, num_delay_samples), dtype=complex), signal.sampling_rate) delay_offset.append_samples(signal) self.joint._receiver.cache_reception(delay_offset) signal, _, _, cube = self.joint.receive() self.assertTrue(10, cube.data.argmax)
def test_probe_validation(self) -> None: """Probe routine should raise exceptions on invalid configurations""" with self.assertRaises(RuntimeError): self.beamformer.probe(Signal(np.zeros((1, 10), dtype=complex), 1.)) self.operator.device = None with self.assertRaises(FloatingError): self.beamformer.probe(Signal(np.zeros((2, 10), dtype=complex), 1.)) self.beamformer.operator = None with self.assertRaises(FloatingError): self.beamformer.probe(Signal(np.zeros((2, 10), dtype=complex), 1.))
def setUp(self) -> None: self.device = Mock() self.device.num_antennas = 1 self.device.carrier_frequency = 0. self.waveform = Mock() self.waveform.frame_duration = 1e-5 self.waveform.sampling_rate = speed_of_light self.waveform.bits_per_frame = 0 self.waveform.symbols_per_frame = 0 self.waveform.samples_in_frame = 5 self.waveform.map.return_value = Symbols(np.empty(0, dtype=complex)) self.waveform.unmap.return_value = np.empty(0, dtype=complex) self.waveform.modulate.return_value = Signal( np.ones((1, 5), dtype=complex), sampling_rate=self.waveform.sampling_rate) self.waveform.demodulate.return_value = Symbols( np.empty(0, dtype=complex)), ChannelStateInformation.Ideal( 0, 1, 1), np.empty(0, dtype=float) self.waveform.synchronization.synchronize.return_value = [(np.ones( (1, 5), dtype=complex), ChannelStateInformation.Ideal(5))] self.range_resolution = 10 self.joint = MatchedFilterJcas(max_range=10) self.joint.device = self.device self.joint.waveform_generator = self.waveform
def convert(self, input_signal: Signal) -> Signal: output_signal = input_signal.copy() self.gain.multiply_signal(output_signal) output_signal.samples = self._quantize(output_signal.samples) self.gain.divide_signal(output_signal) return output_signal
def test_doppler_shift(self) -> None: """A signal being propagated over the channel should be frequency shifted according to the doppler effect""" self.channel.num_clusters = 1 self.receiver.velocity = np.array([10., 0., 0.]) sampling_rate = 1e2 signal_frequency = .25 * sampling_rate signal = np.outer(np.ones(4, dtype=complex), np.exp(2j * pi * .25 * np.arange(200))) expected_doppler_shift = np.linalg.norm( self.receiver.velocity - self.transmitter.velocity ) * self.transmitter.carrier_frequency / speed_of_light frequency_resolution = sampling_rate / 200 shifted_signal, _, _ = self.channel.propagate( Signal(signal, sampling_rate)) input_freq = np.fft.fft(signal[0, :]) output_freq = np.fft.fft(shifted_signal[0].samples[0, :].flatten()) self.assertAlmostEqual( expected_doppler_shift, (np.argmax(output_freq) - np.argmax(input_freq)) * frequency_resolution, delta=1)
def modulate(self, data_symbols: Symbols) -> Signal: frame = np.zeros(self.symbol_samples_in_frame, dtype=complex) # Set preamble symbols num_preamble_samples = self.oversampling_factor * self.num_preamble_symbols frame[:num_preamble_samples:self. oversampling_factor] = self.pilot_symbols( self.num_preamble_symbols) # Set data symbols num_data_samples = self.oversampling_factor * self.__num_data_symbols data_start_idx = num_preamble_samples data_stop_idx = data_start_idx + num_data_samples frame[data_start_idx:data_stop_idx:self. oversampling_factor] = data_symbols.raw # Set postamble symbols num_postamble_samples = self.oversampling_factor * self.num_postamble_symbols postamble_start_idx = data_stop_idx postamble_stop_idx = postamble_start_idx + num_postamble_samples frame[postamble_start_idx:postamble_stop_idx:self. oversampling_factor] = 1. # Generate waveforms by treating the frame as a comb and convolving with the impulse response output_signal = self.tx_filter.filter(frame) return Signal(output_signal, self.sampling_rate)
def test_doppler_shift(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given time variant delays on account of movement """ velocity = 100 self.transmitter.velocity = np.array([0., 0., .5 * velocity]) self.channel.target_velocity = .5 * velocity num_samples = 100000 sinewave_frequency = .25 * self.transmitter.sampling_rate doppler_shift = 2 * velocity / speed_of_light * self.transmitter.carrier_frequency time = np.arange(num_samples) / self.transmitter.sampling_rate input_signal = np.sin(2 * np.pi * sinewave_frequency * time) output, _, _ = self.channel.propagate(Signal(input_signal[np.newaxis, :], self.transmitter.sampling_rate)) input_freq = np.fft.fft(input_signal) output_freq = np.fft.fft(output[0].samples.flatten()[-num_samples:]) freq_resolution = self.transmitter.sampling_rate / num_samples freq_in = np.argmax(np.abs(input_freq[:int(num_samples/2)])) * freq_resolution freq_out = np.argmax(np.abs(output_freq[:int(num_samples/2)])) * freq_resolution self.assertAlmostEqual(freq_out - freq_in, doppler_shift, delta=np.abs(doppler_shift)*.01)
def test_propagation_delay_doppler(self) -> None: """ Test if the received signal corresponds to a frequency-shifted version of the transmitted signal with the expected Doppler shift """ samples_per_symbol = 50 num_pulses = 100 initial_delay_in_samples = 1000 expected_range = speed_of_light * initial_delay_in_samples / self.transmitter.sampling_rate / 2 velocity = 10 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) initial_delay = initial_delay_in_samples / self.transmitter.sampling_rate timestamps_impulses = np.arange(num_pulses) * samples_per_symbol / self.transmitter.sampling_rate traveled_distances = velocity * timestamps_impulses delays = initial_delay + 2 * traveled_distances / speed_of_light expected_peaks = timestamps_impulses + delays peaks_in_samples = np.around(expected_peaks * self.transmitter.sampling_rate).astype(int) straddle_delay = expected_peaks - peaks_in_samples / self.transmitter.sampling_rate relative_straddle_delay = straddle_delay * self.transmitter.sampling_rate expected_straddle_amplitude = np.sinc(relative_straddle_delay) * expected_amplitude input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) self.channel.target_range = expected_range self.channel.target_velocity = velocity output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) assert_array_almost_equal(np.abs(output[0].samples[0, peaks_in_samples].flatten()), expected_straddle_amplitude)
def test_cdl(self): num_samples = 1000 signal_samples = np.tile(np.exp(2j * pi * self.frequency * np.arange(num_samples) / self.sampling_rate), (1, 1)) signal = Signal(signal_samples, sampling_rate=self.sampling_rate, carrier_frequency=self.carrier_frequency) reception_a, _, csi = self.channel.propagate(signal) samples = reception_a[0].samples num_angle_candidates = 80 zenith_angles = np.linspace(0, pi, num_angle_candidates) azimuth_angles = np.linspace(0, 2 * pi, num_angle_candidates) dictionary = np.empty((self.antennas.num_antennas, num_angle_candidates ** 2), dtype=complex) for i, (aoa, zoa) in enumerate(product(azimuth_angles, zenith_angles)): dictionary[:, i] = self.device_a.antennas.spherical_response(self.frequency, aoa, zoa) beamformer = np.linalg.norm(dictionary.T @ samples, axis=1, keepdims=False).reshape((num_angle_candidates, num_angle_candidates)) import matplotlib.pyplot as plt fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) X, Y = np.meshgrid(azimuth_angles, zenith_angles) ax.pcolormesh(X, Y, beamformer.T, shading='nearest') ax.plot(azimuth_angles, zenith_angles, color='k', ls='none') ax.grid() #plt.show() return
def test_receive(self) -> None: """Receive routine should correctly envoke the encode subroutine""" expected_signal = Signal(np.ones((2, 10), dtype=complex), 1.) focus = np.ones((2, self.beamformer.num_receive_focus_angles), dtype=float) steered_signal = self.beamformer.receive(expected_signal, focus) assert_array_equal(expected_signal.samples, steered_signal.samples)
def test_probe(self) -> None: """Probe routine should correctly envoke the encode subroutine""" expected_samples = np.ones((2, 10), dtype=complex) expected_signal = Signal(expected_samples, 1.) focus = np.ones((1, 2, self.beamformer.num_receive_focus_angles), dtype=float) steered_signal = self.beamformer.probe(expected_signal, focus) assert_array_equal(expected_samples[np.newaxis, ::], steered_signal)
def equalize_channel( self, signal: Signal, csi: ChannelStateInformation, snr: float = float('inf')) -> Signal: signal = signal.copy() signal.samples /= (csi.state[0, 0, :signal.num_samples, 0] + 1 / snr) return signal
def pilot_signal(self) -> Signal: filter_delay = self.tx_filter.delay_in_samples pilot = np.zeros(filter_delay + self.oversampling_factor * self.num_preamble_symbols, dtype=complex) pilot[filter_delay::self.oversampling_factor] = self.pilot_symbols( self.num_preamble_symbols) return Signal(pilot, sampling_rate=self.sampling_rate)
def test_receive_no_beamformer(self) -> None: """Receiving without a beamformer should result in a valid radar cube""" self.device.antennas.num_antennas = 1 self.radar._receiver.cache_reception( Signal(np.zeros((1, 5)), self.waveform.sampling_rate)) self.radar.receive_beamformer = None cube, = self.radar.receive() self.assertEqual(1, len(cube.angle_bins))
def demodulate( self, baseband_signal: np.ndarray, channel_state: ChannelStateInformation, noise_variance: float = 0. ) -> Tuple[Symbols, ChannelStateInformation, np.ndarray]: # Filter the signal and csi filter_delay = self.tx_filter.delay_in_samples + self.rx_filter.delay_in_samples + 0 filtered_signal = self.rx_filter.filter(baseband_signal) filter_states = np.zeros( (channel_state.state.shape[0], channel_state.state.shape[1], filter_delay, channel_state.state.shape[3]), dtype=complex) filter_states[:, :, :, 0] = 1. channel_state.state = np.append(filter_states, channel_state.state, axis=2) # Extract preamble symbols num_preamble_samples = self.oversampling_factor * self.num_preamble_symbols preamble_start_idx = filter_delay preamble_stop_idx = preamble_start_idx + num_preamble_samples # preamble = filtered_signal[preamble_start_idx:preamble_stop_idx:self.oversampling_factor] # Re-build signal model signal = Signal(filtered_signal, sampling_rate=self.sampling_rate) # Estimate the channel estimated_csi = self.channel_estimation.estimate_channel( signal, channel_state) # Equalize the signal snr = self.power / noise_variance if noise_variance > 0. else float( 'inf') equalized_signal = self.channel_equalization.equalize_channel( signal, estimated_csi, snr) # Extract data symbols num_data_samples = self.oversampling_factor * self.__num_data_symbols data_start_idx = preamble_stop_idx data_stop_idx = data_start_idx + num_data_samples data = equalized_signal.samples[ 0, data_start_idx:data_stop_idx:self.oversampling_factor] data_state = estimated_csi[:, :, data_start_idx:data_stop_idx:self. oversampling_factor, :] # Extract postamble symbols # num_postamble_samples = self.oversampling_factor * self.num_postamble_symbols # postamble_start_idx = data_stop_idx # postamble_stop_idx = postamble_start_idx + num_postamble_samples # postamble = filtered_signal[postamble_start_idx:postamble_stop_idx:self.oversampling_factor] noise = np.repeat(noise_variance, len(data)) return Symbols(data), data_state, noise
def test_spatial_properties(self) -> None: """Direction of arrival estimation should result in the correct angle estimation of impinging devices""" self.channel.num_clusters = 1 self.transmitter.antennas = UniformArray( IdealAntenna(), .5 * speed_of_light / self.carrier_frequency, (1, )) self.transmitter.orientation = np.zeros(3, dtype=float) self.receiver.position = np.zeros(3, dtype=float) self.receiver.antennas = UniformArray( IdealAntenna(), .5 * speed_of_light / self.carrier_frequency, (8, 8)) angle_candidates = [ (.25 * pi, 0), (.25 * pi, .25 * pi), (.25 * pi, .5 * pi), (.5 * pi, 0), (.5 * pi, .25 * pi), (.5 * pi, .5 * pi), ] range = 1e3 steering_codebook = np.empty((8**2, len(angle_candidates)), dtype=complex) for a, (zenith, azimuth) in enumerate(angle_candidates): steering_codebook[:, a] = self.receiver.antennas.spherical_response( self.carrier_frequency, azimuth, zenith) probing_signal = Signal(np.exp(2j * pi * .25 * np.arange(100)), sampling_rate=1e3, carrier_frequency=self.carrier_frequency) for a, (zenith, azimuth) in enumerate(angle_candidates): self.channel.set_seed(1) self.transmitter.position = range * np.array([ cos(azimuth) * sin(zenith), sin(azimuth) * sin(zenith), cos(zenith) ], dtype=float) received_signal, _, _ = self.channel.propagate(probing_signal) beamformer = np.linalg.norm( steering_codebook.T.conj() @ received_signal[0].samples, 2, axis=1, keepdims=False) self.assertEqual(a, np.argmax(beamformer))
def receive(self) -> Tuple[Signal, Symbols, np.ndarray, RadarCube]: # There must be a recent transmission being cached in order to correlate if self.__transmission is None: raise RuntimeError( "Receiving from a matched filter joint must be preceeded by a transmission" ) # Receive information _, symbols, bits = Modem.receive(self) # Re-sample communication waveform signal = self._receiver.signal.resample(self.sampling_rate) resolution = self.range_resolution num_propagated_samples = int(2 * self.max_range / resolution) # Append additional samples if the signal is too short required_num_received_samples = self.__transmission.num_samples + num_propagated_samples if signal.num_samples < required_num_received_samples: signal.append_samples( Signal( np.zeros( (1, required_num_received_samples - signal.num_samples), dtype=complex), self.sampling_rate, signal.carrier_frequency)) # Remove possible overhead samples if signal is too long # resampled_signal.samples = resampled_signal.samples[:, :num_samples] correlation = abs( correlate( signal.samples, self.__transmission.samples, mode='valid', method='fft').flatten()) / self.__transmission.num_samples lags = correlation_lags(signal.num_samples, self.__transmission.num_samples, mode='valid') # Append zeros for correct depth estimation #num_appended_zeros = max(0, num_samples - resampled_signal.num_samples) #correlation = np.append(correlation, np.zeros(num_appended_zeros)) angle_bins = np.array([0.]) velocity_bins = np.array([0.]) range_bins = .5 * lags * resolution cube_data = np.array([[correlation]], dtype=float) cube = RadarCube(cube_data, angle_bins, velocity_bins, range_bins) return signal, symbols, bits, cube
def test_no_echo(self) -> None: """ Test if no echos are observed if target_exists is set to False """ samples_per_symbol = 500 num_pulses = 15 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) self.channel.target_exists = False output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) assert_array_almost_equal(output[0].samples, np.zeros(output[0].samples.shape))
def Propagate(signal: Signal, impulse_response: np.ndarray, delay: float) -> Signal: """Propagate a single signal model given a specific channel impulse response. Args: signal (Signal): Signal model to be propagated. impulse_response (np.ndarray): The impulse response by which to propagate the signal model. delay (float): Additional delays, for example synchronization offsets. Returns: propagated_signal (Signal): Propagated signal model. """ # The maximum delay in samples is modeled by the last impulse response dimension num_signal_samples = signal.num_samples num_delay_samples = impulse_response.shape[3] - 1 num_tx_streams = impulse_response.shape[2] num_rx_streams = impulse_response.shape[1] # Propagate the signal propagated_samples = np.zeros((impulse_response.shape[1], signal.num_samples + num_delay_samples), dtype=complex) for delay_index in range(num_delay_samples + 1): for tx_idx, rx_idx in product(range(num_tx_streams), range(num_rx_streams)): delayed_signal = impulse_response[:num_signal_samples, rx_idx, tx_idx, delay_index] * signal.samples[ tx_idx, :] propagated_samples[rx_idx, delay_index:delay_index + num_signal_samples] += delayed_signal return Signal(propagated_samples, sampling_rate=signal.sampling_rate, carrier_frequency=signal.carrier_frequency, delay=signal.delay + delay)
def receive(self, signal: Signal, focus_points: Optional[np.ndarray] = None, focus_mode: FocusMode = FocusMode.SPHERICAL) -> Signal: """Focus a signal model towards a certain target. Args: signal (Signal): The signal to be steered. focus_points (np.ndarray, optional): Focus point of the steered signal power. Two-dimensional numpy array with the first dimension representing the number of points and the second dimension representing the point values. focus_mode (FocusMode, optional): Type of focus points. By default, spherical coordinates are expected. Returns: Signal focused towards the requested focus points. """ if self.operator is None: raise FloatingError( "Unable to steer a signal over a floating beamformer") if self.operator.device is None: raise FloatingError( "Unable to steer a signal over a floating operator") if signal.num_streams != self.num_receive_input_streams: raise RuntimeError( f"The provided signal contains {signal.num_streams}, but the beamformer requires {self.num_receive_input_streams} streams" ) carrier_frequency = signal.carrier_frequency samples = signal.samples.copy() focus_angles = self.receive_focus[0][ np.newaxis, ::] if focus_points is None else focus_points[ np.newaxis, ::] beamformed_samples = self._decode(samples, carrier_frequency, focus_angles) return Signal(beamformed_samples[0, ::], signal.sampling_rate)
def test_synchronize(self) -> None: """Synchronization should properly order pilot sections into frames""" pilot_sequence = Signal(np.ones(20, dtype=complex), 1.) waveform_generator = Mock() waveform_generator.pilot_signal = pilot_sequence waveform_generator.samples_in_frame = 20 self.synchronization.waveform_generator = waveform_generator shifted_sequence = np.append(np.zeros((1, 10), dtype=complex), pilot_sequence.samples, axis=1) shifted_csi = ChannelStateInformation.Ideal(30) frames = self.synchronization.synchronize(shifted_sequence, shifted_csi) self.assertEqual(1, len(frames)) assert_array_equal(pilot_sequence.samples, frames[0][0])
def transmit(self, duration: float = 0.) -> Tuple[Signal]: if not self.waveform: raise RuntimeError("Radar waveform not specified") if not self.device: raise RuntimeError( "Error attempting to transmit over a floating radar operator") # Generate the radar waveform signal = self.waveform.ping() # If the device has more than one antenna, a beamforming strategy is required if self.device.antennas.num_antennas > 1: # If no beamformer is configured, only the first antenna will transmit the ping if self.transmit_beamformer is None: additional_streams = Signal( np.zeros((self.device.antennas.num_antennas - signal.num_streams, signal.num_samples), dtype=complex), signal.sampling_rate) signal.append_streams(additional_streams) elif self.transmit_beamformer.num_transmit_input_streams != 1: raise RuntimeError( "Only transmit beamformers requiring a single input stream are supported by radar operators" ) elif self.transmit_beamformer.num_transmit_output_streams != self.device.antennas.num_antennas: raise RuntimeError( "Radar operator transmit beamformers are required to consider the full number of antennas" ) else: signal = self.transmit_beamformer.transmit(signal) # Transmit signal over the occupied device slot (if the radar is attached to a device) if self._transmitter.attached: self._transmitter.slot.add_transmission(self._transmitter, signal) return signal,
def test_propagation_delay_integer_num_samples(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given that the delay is a multiple of the sampling interval. """ samples_per_symbol = 1000 num_pulses = 10 delay_in_samples = 507 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) expected_range = speed_of_light * delay_in_samples / self.transmitter.sampling_rate / 2 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) self.channel.target_range = expected_range output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) expected_output = np.hstack((np.zeros((1, delay_in_samples)), input_signal)) * expected_amplitude assert_array_almost_equal(abs(expected_output), np.abs(output[0].samples[:, :expected_output.size]))
def transmit(self, signal: Signal, focus: Optional[np.ndarray] = None) -> Signal: """Focus a signal model towards a certain target. Args: signal (Signal): The signal to be steered. focus (np.ndarray, optional): Focus point of the steered signal power. Returns: Samples of the focused signal. """ if self.operator is None: raise FloatingError( "Unable to steer a signal over a floating beamformer") if self.operator.device is None: raise FloatingError( "Unable to steer a signal over a floating operator") if signal.num_streams != self.num_transmit_input_streams: raise RuntimeError( f"The provided signal contains {signal.num_streams}, but the beamformer requires {self.num_transmit_input_streams} streams" ) carrier_frequency = signal.carrier_frequency samples = signal.samples.copy() focus = self.transmit_focus[0] if focus is None else focus steered_samples = self._encode(samples, carrier_frequency, focus) return Signal(steered_samples, sampling_rate=signal.sampling_rate, carrier_frequency=signal.carrier_frequency)
def test_propagation_delay_noninteger_num_samples(self) -> None: """ Test if the received signal corresponds to the expected delayed version, given that the delay falls in the middle of two sampling instants. """ samples_per_symbol = 800 num_pulses = 20 delay_in_samples = 312 input_signal = self._create_impulse_train(samples_per_symbol, num_pulses) expected_range = speed_of_light * (delay_in_samples + .5) / self.transmitter.sampling_rate / 2 expected_amplitude = ((speed_of_light / self.transmitter.carrier_frequency) ** 2 * self.radar_cross_section / (4 * pi) ** 3 / expected_range ** 4) self.channel.target_range = expected_range output, _, _ = self.channel.propagate(Signal(input_signal, self.transmitter.sampling_rate)) straddle_loss = np.sinc(.5) peaks = np.abs(output[0].samples[:, delay_in_samples:input_signal.size:samples_per_symbol]) assert_array_almost_equal(peaks, expected_amplitude * straddle_loss * np.ones(peaks.shape))
def receive(self, device_signals: Union[List[Signal], np.ndarray], snr: Optional[float] = None, snr_type: SNRType = SNRType.EBN0) -> Signal: """Receive signals at this device. Args: device_signals (Union[List[Signal], np.ndarray]): List of signal models arriving at the device. May also be a two-dimensional numpy object array where the first dimension indicates the link and the second dimension contains the transmitted signal as the first element and the link channel as the second element. snr (float, optional): Signal to noise power ratio. Infinite by default, meaning no noise will be added to the received signals. snr_type (SNRType, optional): Type of signal to noise ratio. Returns: baseband_signal (Signal): Baseband signal sampled after hardware-modeling. """ # Default to the device's configured SNR if no snr argument was provided snr = self.snr if snr is None else snr # Mix arriving signals mixed_signal = Signal.empty(sampling_rate=self.sampling_rate, num_streams=self.num_antennas, num_samples=0, carrier_frequency=self.carrier_frequency) # Tranform list arguments to matrix arguments if isinstance(device_signals, list): propagation_matrix = np.empty(1, dtype=object) propagation_matrix[0] = (device_signals, None) device_signals = propagation_matrix # Superimpose transmit signals for signals, _ in device_signals: if signals is not None: for signal in signals: mixed_signal.superimpose(signal) # Model radio-frequency chain during transmission baseband_signal = self.rf_chain.receive(mixed_signal) baseband_signal = self.adc.convert(baseband_signal) # Cache received signal at receiver slots for receiver in self.receivers: # Collect the reference channel if a reference transmitter has been specified if receiver.reference_transmitter is not None and self.attached: reference_device = receiver.reference_transmitter.device reference_device_idx = self.scenario.devices.index(reference_device) reference_csi = device_signals[reference_device_idx][1] if isinstance(device_signals[reference_device_idx], (tuple, list, np.ndarray)) else None if self.operator_separation: reference_transmitter_idx = receiver.slot_index receiver_signal = device_signals[reference_device_idx][0][reference_transmitter_idx].copy() else: receiver_signal = baseband_signal.copy() else: reference_csi = None receiver_signal = baseband_signal.copy() # Add noise to the received signal according to the selected ratio noise_power = receiver.energy / snr self.__noise.add(receiver_signal, noise_power) # Cache reception receiver.cache_reception(receiver_signal, reference_csi) return baseband_signal
def multiply_signal(self, input_signal: Signal) -> None: input_signal.samples = input_signal.samples * self.gain
def divide_signal(self, input_signal: Signal) -> None: input_signal.samples = input_signal.samples / self.gain
def receive(self) -> Tuple[RadarCube]: if not self.waveform: raise RuntimeError("Radar waveform not specified") if not self.device: raise RuntimeError( "Error attempting to transmit over a floating radar operator") # Retrieve signal from receiver slot signal = self._receiver.signal.resample(self.waveform.sampling_rate) # If the device has more than one antenna, a beamforming strategy is required if self.device.antennas.num_antennas > 1: if self.receive_beamformer is None: raise RuntimeError( "Receiving over a device with more than one antenna requires a beamforming configuration" ) if self.receive_beamformer.num_receive_output_streams != 1: raise RuntimeError( "Only receive beamformers generating a single output stream are supported by radar operators" ) if self.receive_beamformer.num_receive_input_streams != self.device.antennas.num_antennas: raise RuntimeError( "Radar operator receive beamformers are required to consider the full number of antenna streams" ) beamformed_samples = self.receive_beamformer.probe(signal)[:, 0, :] else: beamformed_samples = signal.samples # Build the radar cube by generating a beam-forming line over all angles of interest angles_of_interest = np.array( [[0., 0.]], dtype=float ) if self.receive_beamformer is None else self.receive_beamformer.probe_focus_points[:, 0, :] range_bins = self.waveform.range_bins velocity_bins = self.waveform.velocity_bins cube_data = np.empty( (len(angles_of_interest), len(velocity_bins), len(range_bins)), dtype=float) for angle_idx, line in enumerate(beamformed_samples): # Process the single angular line by the waveform generator line_signal = Signal(line, signal.sampling_rate, carrier_frequency=signal.carrier_frequency) line_estimate = self.waveform.estimate(line_signal) cube_data[angle_idx, ::] = line_estimate # Create radar cube object cube = RadarCube(cube_data, angles_of_interest, velocity_bins, range_bins) return cube,
def ping(self) -> Signal: return Signal( np.exp(2j * np.pi * self.rng.uniform(0, 1, size=(1, self.num_samples))), self.sampling_rate)