[docs]classOFDMRadar(DuplexJCASOperator[OFDMWaveform],Serializable):"""A joint communication and sensing approach estimating a range-power profile from OFDM symbols. Refer to :footcite:p:`2009:sturm` for the original publication. """yaml_tag="OFDMRadar"@propertydefmax_range(self)->float:"""Maximum range detectable by OFDM radar. Defined by equation (12) in :footcite:p:`2009:sturm` as .. math:: d_\\mathrm{max} = \\frac{c_0}{2 \\Delta f} \\quad \\text{.} Returns: Maximum range in m. """ifself.waveformisNone:return0.0returnspeed_of_light/(2*self.waveform.subcarrier_spacing)@propertydefrange_resolution(self)->float:"""Range resolution achievable by OFDM radar. Defined by equation (13) in :footcite:p:`2009:sturm` as .. math:: \\Delta r = \\frac{c_0}{2 B} = \\frac{c_0}{2 N \\Delta f} = \\frac{d_{\\mathrm{max}}}{N} \\quad \\text{.} """ifself.waveformisNone:return0.0returnself.max_range/self.waveform.num_subcarriers@propertydefmax_relative_doppler(self)->float:"""The maximum relative doppler shift detectable by the OFDM radar in Hz."""# The maximum velocity is the wavelength divided by four times the pulse repetition intervalmax_doppler=1/(4*self.frame_duration)returnmax_doppler@propertydefrelative_doppler_resolution(self)->float:"""The relative doppler resolution achievable by the OFDM radar in Hz."""# The doppler resolution is the inverse of twice the frame durationresolution=1/(2*self.frame_duration)returnresolution@propertydefpower(self)->float:return0.0ifself.waveformisNoneelseself.waveform.powerdef_transmit(self,duration:float=-1)->JCASTransmission:communication_transmission=TransmittingModemBase._transmit(self,duration=duration)jcas_transmission=JCASTransmission(communication_transmission)returnjcas_transmissiondef__estimate_range(self,transmitted_symbols:Symbols,received_signal:Signal)->np.ndarray:"""Estiamte the range-power profile of the received signal. Args: transmitted_symbols (Symbols): The originally transmitted OFDM symbols. received_signal (Signal): The received OFDM base-band signal samples. Returns: np.ndarray: The range-power profile of the received signal. """# Demodulate the signal received from an angle of interestreceived_symbols=self.waveform.demodulate(received_signal[0,:])# Normalize received demodulated symbols equation (8)normalized_symbols=np.divide(received_symbols.raw,transmitted_symbols.raw,np.zeros_like(received_symbols.raw),where=np.abs(transmitted_symbols.raw)!=0.0,)# Estimate range-power profile by equation (10)power_profile=ifftshift(fft(ifft(normalized_symbols[0,::],axis=1),axis=0),axes=0)returnnp.abs(power_profile)def_receive(self,signal:Signal)->JCASReception:# Retrieve previous transmissiontransmission:JCASTransmission|None=self.transmissioniftransmissionisNone:raiseRuntimeError("Unable to receive ")# Retrieve the transmitted symbolstransmitted_symbols=self.waveform.place(transmission.symbols)# Run the normal communication reception processingcommunication_reception=ReceivingModemBase._receive(self,signal)# Build a radar cubeangles_of_interest,beamformed_samples=self._receive_beamform(signal)range_bins=np.arange(self.waveform.num_subcarriers)*self.range_resolutiondoppler_bins=(np.arange(self.waveform.words_per_frame)*self.relative_doppler_resolution-self.max_relative_doppler)cube_data=np.empty((len(angles_of_interest),len(doppler_bins),len(range_bins)),dtype=float)forangle_idx,lineinenumerate(beamformed_samples):# Process the single angular line by the waveform generatorline_signal=signal.from_ndarray(line)line_estimate=self.__estimate_range(transmitted_symbols,line_signal)cube_data[angle_idx,::]=line_estimate# Create radar cube objectcube=RadarCube(cube_data,angles_of_interest,doppler_bins,range_bins,self.carrier_frequency)# Infer the point cloud, if a detector has been configuredcloud=Noneifself.detectorisNoneelseself.detector.detect(cube)# Generate reception objectradar_reception=RadarReception(signal,cube,cloud)returnJCASReception(communication_reception,radar_reception)