add phase analysis visualisation
This commit is contained in:
parent
8fe5995c5f
commit
7c9075c9eb
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
from queue import Queue
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
@ -63,4 +64,50 @@ def heatmap() -> None:
|
|||||||
logger.info("Finished processing CSI data")
|
logger.info("Finished processing CSI data")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def phase_analysis(
|
||||||
|
subcarrier: int = 0, rx_antenna: int = 0, tx_antenna: int = 0
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Visualise the phase information in the CSI data received from the antennas.
|
||||||
|
|
||||||
|
The data goes through the same preprocessing steps as the heatmap command, but
|
||||||
|
instead of going through the AoA estimation, we simply analyse the phase of the
|
||||||
|
selected subcarrier and antenna.
|
||||||
|
"""
|
||||||
|
|
||||||
|
preprocessor = Preprocessor()
|
||||||
|
|
||||||
|
# Start webapp in background process
|
||||||
|
webapp_queue: "mp.Queue[visualise.VisualiserData]" = mp.Queue(config.sample_rate)
|
||||||
|
webapp = mp.Process(target=visualise.start, args=(webapp_queue,))
|
||||||
|
webapp.start()
|
||||||
|
|
||||||
|
subcarrier_phase: Queue[float] = Queue(config.sample_rate)
|
||||||
|
|
||||||
|
def visualise_data(data: npt.NDArray[Any], dtype: visualise.figures.Figure) -> None:
|
||||||
|
if not webapp_queue.full():
|
||||||
|
webapp_queue.put(visualise.VisualiserData(data, dtype))
|
||||||
|
|
||||||
|
def callback(antenna_data: npt.NDArray[np.complex64]) -> None:
|
||||||
|
logger.info(f"Got final CSI data with shape {antenna_data.shape}")
|
||||||
|
visualise_data(antenna_data, visualise.figures.Figure.RAW_CSI)
|
||||||
|
|
||||||
|
processed = preprocessor.preprocess(antenna_data, visualiser=visualise_data)
|
||||||
|
visualise_data(processed, visualise.figures.Figure.PROCESSED_CSI)
|
||||||
|
|
||||||
|
phase = cast(float, np.angle(processed[subcarrier, rx_antenna, tx_antenna]))
|
||||||
|
|
||||||
|
if subcarrier_phase.full():
|
||||||
|
subcarrier_phase.get()
|
||||||
|
subcarrier_phase.put(phase)
|
||||||
|
|
||||||
|
visualise_data(
|
||||||
|
np.array(subcarrier_phase), visualise.figures.Figure.PHASE_ANALYSIS
|
||||||
|
)
|
||||||
|
|
||||||
|
globals.csi_producer(csi_callback=callback)
|
||||||
|
logger.info("Finished processing CSI data")
|
||||||
|
|
||||||
|
|
||||||
app.add_typer(file.app, name="file", help="Commands for working with CSI files")
|
app.add_typer(file.app, name="file", help="Commands for working with CSI files")
|
||||||
|
|||||||
@ -30,7 +30,7 @@ class FigureServer(figure_pb2_grpc.FigureServiceServicer):
|
|||||||
self, request: figure_pb2.FigureRequest, context: grpc.ServicerContext
|
self, request: figure_pb2.FigureRequest, context: grpc.ServicerContext
|
||||||
) -> Generator[figure_pb2.Figure, None, None]:
|
) -> Generator[figure_pb2.Figure, None, None]:
|
||||||
for figure_group in figures.Figure:
|
for figure_group in figures.Figure:
|
||||||
for figure in figures.all_figures[figure_group].figures:
|
for figure in figure_group.figure_class().figures:
|
||||||
yield figure
|
yield figure
|
||||||
|
|
||||||
def GetFigureUpdate(
|
def GetFigureUpdate(
|
||||||
|
|||||||
@ -58,10 +58,17 @@ class SimpleLineChart:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self, new_data: npt.NDArray[np.complex128], labels: Sequence[str]
|
self,
|
||||||
|
new_data: npt.NDArray[np.complex128],
|
||||||
|
labels: Sequence[str],
|
||||||
|
x_values: None | npt.NDArray[np.float64] = None,
|
||||||
) -> FigureUpdate:
|
) -> FigureUpdate:
|
||||||
lines = [
|
lines = [
|
||||||
line_pb2.LineChartData.Line(y=new_data[i], label=labels[i])
|
line_pb2.LineChartData.Line(
|
||||||
|
y=new_data[i],
|
||||||
|
label=labels[i],
|
||||||
|
x=x_values[i] if x_values is not None else None,
|
||||||
|
)
|
||||||
for i in range(new_data.shape[0])
|
for i in range(new_data.shape[0])
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
@ -156,12 +163,28 @@ class HeatmapFigure(SpecificFigure):
|
|||||||
return {self.figure.uuid: figure_pb2.FigureData(heatmap=heatmap)}
|
return {self.figure.uuid: figure_pb2.FigureData(heatmap=heatmap)}
|
||||||
|
|
||||||
|
|
||||||
|
class EmpiricalCDF(SpecificFigure):
|
||||||
|
"""Helper class to create a graph of the empirical cumulative distribution function
|
||||||
|
of a set of observations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, title: str, x_label: str) -> None:
|
||||||
|
self.chart = SimpleLineChart(title, x_label, "Cumulative Probability")
|
||||||
|
self.figures = [self.chart.figure]
|
||||||
|
|
||||||
|
def update(self, new_data: npt.NDArray[np.complex128]) -> FigureUpdate:
|
||||||
|
new_data = np.expand_dims(np.sort(new_data), 0)
|
||||||
|
y_values = np.expand_dims(np.linspace(0, 1, new_data.size), 0)
|
||||||
|
return self.chart.update(y_values, labels=["Frequency"], x_values=new_data)
|
||||||
|
|
||||||
|
|
||||||
class Figure(Enum):
|
class Figure(Enum):
|
||||||
RAW_CSI = 0
|
RAW_CSI = 0
|
||||||
UNWRAPPED_PHASE = 1
|
UNWRAPPED_PHASE = 1
|
||||||
PROCESSED_CSI = 2
|
PROCESSED_CSI = 2
|
||||||
MUSIC_EIGENVALUES = 3
|
MUSIC_EIGENVALUES = 3
|
||||||
AOA_HEATMAP = 4
|
AOA_HEATMAP = 4
|
||||||
|
PHASE_ANALYSIS = 5
|
||||||
|
|
||||||
def figure_class(self) -> SpecificFigure:
|
def figure_class(self) -> SpecificFigure:
|
||||||
return all_figures[self]
|
return all_figures[self]
|
||||||
@ -187,4 +210,5 @@ all_figures = {
|
|||||||
),
|
),
|
||||||
Figure.MUSIC_EIGENVALUES: MusicEigenvalueHistogram(),
|
Figure.MUSIC_EIGENVALUES: MusicEigenvalueHistogram(),
|
||||||
Figure.AOA_HEATMAP: HeatmapFigure(),
|
Figure.AOA_HEATMAP: HeatmapFigure(),
|
||||||
|
Figure.PHASE_ANALYSIS: EmpiricalCDF("Phase Analysis", "Phase"),
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user