Skip to content

Commit

Permalink
Merge branch '177-ofdm-radar-new' into 'master'
Browse files Browse the repository at this point in the history
Introduce OFDM Radar

Closes #177

See merge request barkhauseninstitut/wicon/hermespy!158
  • Loading branch information
adlerjan committed Mar 12, 2024
2 parents 95be946 + 430b3cd commit 0b0e948
Show file tree
Hide file tree
Showing 31 changed files with 859 additions and 354 deletions.
12 changes: 12 additions & 0 deletions docssource/api/jcas.jcas.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=====================
Duplex JCAS Operator
=====================

.. inheritance-diagram:: hermespy.jcas.jcas.DuplexJCASOperator hermespy.jcas.jcas.JCASTransmission hermespy.jcas.jcas.JCASReception
:parts: 1

.. autoclass:: hermespy.jcas.jcas.DuplexJCASOperator

.. autoclass:: hermespy.jcas.jcas.JCASTransmission

.. autoclass:: hermespy.jcas.jcas.JCASReception
9 changes: 8 additions & 1 deletion docssource/api/jcas.matched_filtering.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.. automodule:: hermespy.jcas.matched_filtering
=================
Matched Filtering
=================

.. inheritance-diagram:: hermespy.jcas.matched_filtering.MatchedFilterJcas
:parts: 1

.. autoclass:: hermespy.jcas.matched_filtering.MatchedFilterJcas

.. footbibliography::
14 changes: 14 additions & 0 deletions docssource/api/jcas.ofdm_radar.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
==========
OFDM Radar
==========


.. inheritance-diagram:: hermespy.jcas.ofdm_radar.OFDMRadar
:parts: 1

A radar implementation estimation range-doppler maps from OFDM symbols
as suggested by :footcite:p:`2009:sturm`.

.. autoclass:: hermespy.jcas.ofdm_radar.OFDMRadar

.. footbibliography::
4 changes: 3 additions & 1 deletion docssource/api/jcas.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ Currently, an exemplary matched filter approach is implemented:

.. toctree::

jcas.matched_filtering
jcas.matched_filtering
jcas.ofdm_radar
jcas.jcas
2 changes: 2 additions & 0 deletions docssource/api/modem.modem.ReceivingModem.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ The barebone configuration can be extend by additional components such as
:lines: 36-56

.. autoclass:: hermespy.modem.modem.ReceivingModem

.. autoclass:: hermespy.modem.modem.ReceivingModemBase
:private-members: _receive

.. footbibliography::
3 changes: 2 additions & 1 deletion docssource/api/modem.modem.TransmittingModem.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ This barebone configuration can be extended by adding additional components such
:lines: 28-39

.. autoclass:: hermespy.modem.modem.TransmittingModem

.. autoclass:: hermespy.modem.modem.TransmittingModemBase
:private-members: _transmit
:show-inheritance:

.. footbibliography::
2 changes: 1 addition & 1 deletion docssource/api/modem.waveform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The following waveforms are currently supported:

.. autoclass:: hermespy.modem.waveform.ConfigurablePilotWaveform

.. autoclass:: hermespy.modem.waveform.WaveformType
.. autoclass:: hermespy.modem.waveform.CWT

.. toctree::
:maxdepth: 1
Expand Down
5 changes: 5 additions & 0 deletions docssource/api/radar.radar.Radar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
Radar
=====

.. inheritance-diagram:: hermespy.radar.radar.Radar hermespy.radar.radar.RadarBase
:parts: 1

.. autoclass:: hermespy.radar.radar.Radar

.. autoclass:: hermespy.radar.radar.RadarBase
:private-members: _transmit, _receive
:exclude-members: reference

Expand Down
30 changes: 21 additions & 9 deletions docssource/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -439,12 +439,24 @@ @INPROCEEDINGS{2014:burkhardt
}

@ARTICLE{1954:kahn,
author={Kahn, Leanoard},
journal={Proceedings of the IRE},
title={Ratio Squarer},
year={1954},
volume={42},
number={11},
pages={1698-1704},
doi={10.1109/JRPROC.1954.274666}}
}
author={Kahn, Leanoard},
journal={Proceedings of the IRE},
title={Ratio Squarer},
year={1954},
volume={42},
number={11},
pages={1698-1704},
doi={10.1109/JRPROC.1954.274666}
}

@INPROCEEDINGS{2009:sturm,
author={Sturm, Christian and Zwick, Thomas and Wiesbeck, Werner},
booktitle={VTC Spring 2009 - IEEE 69th Vehicular Technology Conference},
title={An OFDM System Concept for Joint Radar and Communications Operations},
year={2009},
volume={},
number={},
pages={1-5},
keywords={Radar;OFDM modulation;Phase shift keying;Phase modulation;Intelligent transportation systems;Mathematical model;Baseband;Communication standards;Robustness;Fading},
doi={10.1109/VETECS.2009.5073387}
}
33 changes: 33 additions & 0 deletions docssource/scripts/examples/jcas_ofdm_radar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import matplotlib.pyplot as plt

from hermespy.channel import SingleTargetRadarChannel
from hermespy.jcas import OFDMRadar
from hermespy.modem import FrameResource, FrameSymbolSection, FrameElement, ElementType
from hermespy.radar import MaxDetector
from hermespy.simulation import SimulatedDevice


carrier_frequency = 24e9
oversampling_factor = 4
subcarrier_spacing = 90.909e3
num_subcarriers = 1024
prefix_ratio = 1.375 / 12.375
modulation_order = 16

device = SimulatedDevice(carrier_frequency=carrier_frequency)
resources = [FrameResource(64, prefix_ratio=prefix_ratio, elements=[FrameElement(ElementType.DATA, 15), FrameElement(ElementType.REFERENCE, 1)])]
structure = [FrameSymbolSection(11, [0])]
radar = OFDMRadar(oversampling_factor=oversampling_factor, modulation_order=modulation_order, subcarrier_spacing=subcarrier_spacing, resources=resources, structure=structure, device=device)
radar.detector = MaxDetector()

radar_channel = SingleTargetRadarChannel(.75 * radar.waveform.max_range, 1., velocity=4 * radar.velocity_resolution, attenuate=False, alpha_device=device, beta_device=device)

transmission = device.transmit()
propagation = radar_channel.propagate(transmission, device, device)
reception = device.receive(propagation)

radar.reception.cube.plot_range()
radar.reception.cube.plot_range_velocity(scale='velocity')
radar.reception.cloud.plot()

plt.show()
8 changes: 4 additions & 4 deletions hermespy/core/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ def to_HDF(self, group: Group) -> None:
group.attrs["num_operator_receptions"] = self.num_operator_receptions


class MixingOperator(Generic[SlotType], Operator[SlotType], ABC):
class MixingOperator(ABC, Generic[SlotType], Operator[SlotType]):
"""Base class for operators performing mixing operations."""

__carrier_frequency: Optional[float] # Carrier frequency
Expand Down Expand Up @@ -806,7 +806,7 @@ def carrier_frequency(self, value: Optional[float]) -> None:
self.__carrier_frequency = value


class Receiver(RandomNode, MixingOperator["ReceiverSlot"], Generic[ReceptionType]):
class Receiver(MixingOperator["ReceiverSlot"], Generic[ReceptionType], RandomNode):
"""Operator receiving from a device."""

__reference: Device | None
Expand Down Expand Up @@ -1274,7 +1274,7 @@ def __len__(self) -> int:
"""Type variable of a :class:`Transmission`."""


class Transmitter(Generic[TransmissionType], RandomNode, MixingOperator["TransmitterSlot"]):
class Transmitter(MixingOperator["TransmitterSlot"], Generic[TransmissionType], RandomNode):
"""Operator transmitting over a device."""

__transmission: TransmissionType | None
Expand Down Expand Up @@ -1346,7 +1346,7 @@ def transmit(self, duration: float = 0.0, cache: bool = True) -> TransmissionTyp
return transmission

@abstractmethod
def _transmit(self, duration: float) -> TransmissionType:
def _transmit(self, duration: float = -1.0) -> TransmissionType:
"""Generate information to be transmitted.
Subroutine of the public :meth:`transmit<Transmitter.transmit>` method that performs the pipeline-specific transmit-processing
Expand Down
2 changes: 1 addition & 1 deletion hermespy/core/duplex.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@


class DuplexOperator(
Transmitter[TransmissionType], Receiver[ReceptionType], Generic[TransmissionType, ReceptionType]
Generic[TransmissionType, ReceptionType], Transmitter[TransmissionType], Receiver[ReceptionType]
):
"""Operator binding to both transmit and receive slots of any device."""

Expand Down
5 changes: 4 additions & 1 deletion hermespy/jcas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-

from .matched_filtering import JCASTransmission, JCASReception, MatchedFilterJcas
from .ofdm_radar import OFDMRadar

__author__ = "Jan Adler"
__copyright__ = "Copyright 2023, Barkhausen Institut gGmbH"
Expand All @@ -9,4 +12,4 @@
__email__ = "[email protected]"
__status__ = "Prototype"

__all__ = ["JCASTransmission", "JCASReception", "MatchedFilterJcas"]
__all__ = ["JCASTransmission", "JCASReception", "MatchedFilterJcas", "OFDMRadar"]
125 changes: 125 additions & 0 deletions hermespy/jcas/jcas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-

from __future__ import annotations
from abc import abstractmethod
from typing import Generic, Type

from h5py import Group

from hermespy.core import Device, SNRType, Signal
from hermespy.modem.modem import TransmittingModemBase, ReceivingModemBase
from hermespy.modem import BaseModem, CommunicationTransmission, CommunicationReception, CWT
from hermespy.radar import RadarBase, RadarTransmission, RadarReception

__author__ = "Jan Adler"
__copyright__ = "Copyright 2023, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "Jan Adler"
__version__ = "1.1.0"
__maintainer__ = "Jan Adler"
__email__ = "[email protected]"
__status__ = "Prototype"


class JCASTransmission(CommunicationTransmission, RadarTransmission):
"""Information generated by transmitting over a joint communication and sensing operator."""

def __init__(self, transmission: CommunicationTransmission) -> None:
CommunicationTransmission.__init__(
self, signal=transmission.signal, frames=transmission.frames
)
RadarTransmission.__init__(self, signal=transmission.signal)

@classmethod
def from_HDF(cls: Type[JCASTransmission], group: Group) -> JCASTransmission:
return JCASTransmission(CommunicationTransmission.from_HDF(group))


class JCASReception(CommunicationReception, RadarReception):
"""Information generated by receiving over a joint communication and sensing operator."""

def __init__(self, communication: CommunicationReception, radar: RadarReception) -> None:
CommunicationReception.__init__(
self, signal=communication.signal, frames=communication.frames
)
RadarReception.__init__(self, radar.signal, radar.cube, radar.cloud)

@classmethod
def from_HDF(cls: Type[JCASReception], group: Group) -> JCASReception:
communication_reception = CommunicationReception.from_HDF(group)
radar_reception = RadarReception.from_HDF(group)

return JCASReception(communication_reception, radar_reception)

def to_HDF(self, group: Group) -> None:
CommunicationReception.to_HDF(self, group)
RadarReception.to_HDF(self, group)


class DuplexJCASOperator(
Generic[CWT],
RadarBase[JCASTransmission, JCASReception],
TransmittingModemBase[CWT],
ReceivingModemBase[CWT],
):
"""Base class for duplex joint communication and sensing operators.
Duplex joint communication and sensing operators transmit a modulated waveform while simultaneously deriving a radar
cube from the received backscattered power.
"""

def __init__(self, device: Device | None = None, waveform: CWT | None = None, **kwargs) -> None:
"""
Args:
device (Device, optional):
Device this operator operating.
Operator is considered floating by default.
waveform (CWT, optional):
Communication waveform emitted by this operator.
"""

# Initialize base classes
TransmittingModemBase.__init__(self)
ReceivingModemBase.__init__(self, **kwargs)
RadarBase.__init__(self, device=device)

# Initialize class attributes
self.device = device
self.waveform = waveform

@property
def transmitting_device(self) -> Device | None:
return self.device

@property
def receiving_device(self) -> Device | None:
return self.device

@property
def sampling_rate(self) -> float:
if self.waveform is None:
return 1.0
return self.waveform.sampling_rate

@property
def frame_duration(self) -> float:
if self.waveform is None:
return 0.0
return self.waveform.frame_duration

@abstractmethod
def _transmit(self, duration: float = -1.0) -> JCASTransmission: ... # pragma: no cover

@abstractmethod
def _receive(self, signal: Signal) -> JCASReception: ... # pragma: no cover

def _recall_transmission(self, group: Group) -> JCASTransmission:
return JCASTransmission.from_HDF(group)

def _recall_reception(self, group: Group) -> JCASReception:
return JCASReception.from_HDF(group)

def _noise_power(self, strength: float, snr_type: SNRType) -> float:
return BaseModem._noise_power(self, strength, snr_type)
Loading

0 comments on commit 0b0e948

Please sign in to comment.