add phase analysis visualisation

This commit is contained in:
Christos Falas 2025-04-02 15:00:37 +01:00
parent 8fe5995c5f
commit 7c9075c9eb
No known key found for this signature in database
3 changed files with 75 additions and 4 deletions

View File

@ -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")

View File

@ -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(

View File

@ -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"),
}