add phase analysis visualisation
This commit is contained in:
parent
8fe5995c5f
commit
7c9075c9eb
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
from queue import Queue
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
@ -63,4 +64,50 @@ def heatmap() -> None:
|
||||
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")
|
||||
|
||||
@ -30,7 +30,7 @@ class FigureServer(figure_pb2_grpc.FigureServiceServicer):
|
||||
self, request: figure_pb2.FigureRequest, context: grpc.ServicerContext
|
||||
) -> Generator[figure_pb2.Figure, None, None]:
|
||||
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
|
||||
|
||||
def GetFigureUpdate(
|
||||
|
||||
@ -58,10 +58,17 @@ class SimpleLineChart:
|
||||
)
|
||||
|
||||
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:
|
||||
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])
|
||||
]
|
||||
return {
|
||||
@ -156,12 +163,28 @@ class HeatmapFigure(SpecificFigure):
|
||||
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):
|
||||
RAW_CSI = 0
|
||||
UNWRAPPED_PHASE = 1
|
||||
PROCESSED_CSI = 2
|
||||
MUSIC_EIGENVALUES = 3
|
||||
AOA_HEATMAP = 4
|
||||
PHASE_ANALYSIS = 5
|
||||
|
||||
def figure_class(self) -> SpecificFigure:
|
||||
return all_figures[self]
|
||||
@ -187,4 +210,5 @@ all_figures = {
|
||||
),
|
||||
Figure.MUSIC_EIGENVALUES: MusicEigenvalueHistogram(),
|
||||
Figure.AOA_HEATMAP: HeatmapFigure(),
|
||||
Figure.PHASE_ANALYSIS: EmpiricalCDF("Phase Analysis", "Phase"),
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user