Compare commits

..

No commits in common. "c4196503d85fbbd31aeeb84d0b28aa8aecca4a0f" and "a94a9b4ec47e52f64d6bff16b8f51642b0ed93be" have entirely different histories.

7 changed files with 31 additions and 93 deletions

View File

@ -52,7 +52,7 @@ def heatmap() -> None:
def callback(antenna_data: npt.NDArray[np.complex64]) -> None: def callback(antenna_data: npt.NDArray[np.complex64]) -> None:
logger.info(f"Got final CSI data with shape {antenna_data.shape}") logger.info(f"Got final CSI data with shape {antenna_data.shape}")
visualise_data(antenna_data, visualise.DataType.RAW_CSI) visualise_data(antenna_data, visualise.DataType.RAW_CSI)
processed = preprocessor.preprocess(antenna_data, visualiser=visualise_data) processed = preprocessor.preprocess(antenna_data)
visualise_data(processed, visualise.DataType.PROCESSED_CSI) visualise_data(processed, visualise.DataType.PROCESSED_CSI)
logger.info(f"Processed CSI data with shape {processed.shape}") logger.info(f"Processed CSI data with shape {processed.shape}")
processed_tensor = torch.tensor(processed, device=device) processed_tensor = torch.tensor(processed, device=device)

View File

@ -1,13 +1,11 @@
import logging import logging
from queue import Queue from queue import Queue
from typing import Any, Callable
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
from scipy.signal import butter, correlate, sosfilt, sosfilt_zi from scipy.signal import butter, correlate, sosfilt, sosfilt_zi
from ..config import config from ..config import config
from ..visualise import server as visualise
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@ -28,14 +26,9 @@ class Preprocessor:
output="sos", output="sos",
) )
def preprocess( def preprocess(self, h: npt.NDArray[np.complex64]) -> npt.NDArray[np.complex64]:
self,
h: npt.NDArray[np.complex64],
visualiser: None
| Callable[[npt.NDArray[Any], visualise.DataType], None] = None,
) -> npt.NDArray[np.complex64]:
# CSI data is not available for pilot subcarriers. # CSI data is not available for pilot subcarriers.
h_hat: npt.NDArray[np.complex64] = np.where( h_hat = np.where(
np.expand_dims(h[:, 0, 0] == 0, axis=(1, 2)), np.expand_dims(h[:, 0, 0] == 0, axis=(1, 2)),
correlate(h, [[[1 / 2]], [[0]], [[1 / 2]]], mode="same"), correlate(h, [[[1 / 2]], [[0]], [[1 / 2]]], mode="same"),
h, h,
@ -45,33 +38,12 @@ class Preprocessor:
h_hat = h_hat[:: config.preprocessing.subcarrier_step, :, :] h_hat = h_hat[:: config.preprocessing.subcarrier_step, :, :]
# logger.info(f"CSI shape: {h_hat.shape}") # logger.info(f"CSI shape: {h_hat.shape}")
# h_hat = np.multiply(h_hat, h_hat.conj() / abs(h_hat.conj())) h_hat = np.multiply(h_hat, h_hat.conj() / abs(h_hat.conj()))
h_hat = np.nan_to_num(h_hat) h_hat = np.nan_to_num(h_hat)
# h_hat = correlate(h_hat, np.ones((3, 1, 1)) / 3) # h_hat = correlate(h_hat, np.ones((3, 1, 1)) / 3)
# h_hat = correlate(h_hat, [[[1 / 4]], [[1 / 2]], [[1 / 4]]], mode="valid") h_hat = correlate(h_hat, [[[1 / 4]], [[1 / 2]], [[1 / 4]]], mode="valid")
# Unwrap phase and remove linear fit
print(h_hat.shape)
unwrapped = np.unwrap(np.angle(h_hat[:, :, 0]), axis=0).reshape(
h_hat.shape[0], h_hat.shape[1], 1
)
if visualiser:
visualiser(unwrapped, visualise.DataType.UNWRAPPED_PHASE)
for antenna in range(h_hat.shape[1]):
tau, rho = np.linalg.lstsq(
np.vstack([np.arange(h_hat.shape[0]), np.ones(h_hat.shape[0])]).T,
unwrapped[:, antenna, 0],
)[0]
h_hat[:, antenna, 0] = np.abs(h_hat[:, antenna, 0]) * np.exp(
1j
* (
np.angle(h_hat[:, antenna, 0])
- (tau * np.arange(h_hat.shape[0]) + rho)
)
)
return h_hat
# Assume that all csi matrices will have the same shape # Assume that all csi matrices will have the same shape
if self.long_term_avg.shape != h_hat.shape: if self.long_term_avg.shape != h_hat.shape:
self.long_term_avg = np.zeros(h_hat.shape, dtype=np.complex64) self.long_term_avg = np.zeros(h_hat.shape, dtype=np.complex64)

View File

@ -11,13 +11,13 @@ AntennaIdentifier = tuple[ingest.Host, int]
order: list[AntennaIdentifier] = [] order: list[AntennaIdentifier] = []
prev_unplugged: set[AntennaIdentifier] = set() prev_unplugged: set[AntennaIdentifier] = set()
long_antenna_average: dict[AntennaIdentifier, deque[int]] = { long_antenna_average: dict[AntennaIdentifier, deque[int]] = {
(host, 0): deque(maxlen=15 * config.sample_rate) for host in config.receive_hosts (host, 0): deque(maxlen=10 * config.sample_rate) for host in config.receive_hosts
} | {(host, 1): deque(maxlen=15 * config.sample_rate) for host in config.receive_hosts} } | {(host, 1): deque(maxlen=10 * config.sample_rate) for host in config.receive_hosts}
short_antenna_average: dict[AntennaIdentifier, deque[int]] = { short_antenna_average: dict[AntennaIdentifier, deque[int]] = {
(host, 0): deque(maxlen=2 * config.sample_rate) for host in config.receive_hosts (host, 0): deque(maxlen=config.sample_rate) for host in config.receive_hosts
} | {(host, 1): deque(maxlen=2 * config.sample_rate) for host in config.receive_hosts} } | {(host, 1): deque(maxlen=config.sample_rate) for host in config.receive_hosts}
RSSI_THRESHOLD = 10 RSSI_THRESHOLD = 8
def average(data: Collection[Any]) -> float: def average(data: Collection[Any]) -> float:

View File

@ -6,5 +6,4 @@ server/generated: protos/*.proto
find protos/ -type f -name "*.proto" | xargs uv run protol --create-package --in-place --python-out server/generated/ protoc --proto-path=protos/ find protos/ -type f -name "*.proto" | xargs uv run protol --create-package --in-place --python-out server/generated/ protoc --proto-path=protos/
frontend/src/grpc: protos/*.proto frontend/src/grpc: protos/*.proto
mkdir -p frontend/src/grpc find protos/ -name "*.proto" | xargs npm exec --prefix frontend/ protoc --ts_out frontend/src/grpc -I protos/
cd frontend && find ../protos/ -name "*.proto" | xargs npx protoc --ts_out=src/grpc -I../protos/

View File

@ -11,18 +11,6 @@ import { computed } from 'vue'
const { figure, paused = false } = defineProps<{ figure: Figure; paused?: boolean }>() const { figure, paused = false } = defineProps<{ figure: Figure; paused?: boolean }>()
const cancel = ref<boolean>(false) const cancel = ref<boolean>(false)
function reshape(data: number[], width: number) {
if (data.length % width != 0) {
throw new Error('Data length is not divisible by width')
}
const height = data.length / width
const result: number[][] = new Array(height)
for (let i = 0; i < height; i++) {
result[i] = data.slice(i * width, (i + 1) * width)
}
return result
}
const data = ref<FigureData | null>(null) const data = ref<FigureData | null>(null)
const plotData = computed(() => { const plotData = computed(() => {
if (data.value) { if (data.value) {
@ -36,12 +24,16 @@ const plotData = computed(() => {
color: line.color ? line.color : undefined, color: line.color ? line.color : undefined,
})) }))
case 'heatmap': case 'heatmap':
return [ return []
{ /* data.value.figure.heatmap.lines.map((line) => {
z: reshape(data.value.figure.heatmap.data, data.value.figure.heatmap.width), return {
type: 'heatmap' as const, x: line.x,
}, y: line.y,
] type: 'scatter',
name: line.label,
color: line.color,
}
})*/
} }
} }
return [] return []
@ -51,7 +43,6 @@ const layout = {
title: { text: figure.title }, title: { text: figure.title },
xaxis: { title: { text: figure.xLabel } }, xaxis: { title: { text: figure.xLabel } },
yaxis: { title: { text: figure.yLabel } }, yaxis: { title: { text: figure.yLabel } },
height: 700,
} }
function updateGraph() { function updateGraph() {

View File

@ -2,8 +2,9 @@ syntax = "proto3";
message HeatmapData { message HeatmapData {
string uuid = 1; string uuid = 1;
repeated float data = 2; repeated float x = 2;
uint32 width = 3; repeated float y = 3;
uint32 height = 4; uint32 width = 4;
string cmap = 5; uint32 height = 5;
string cmap = 6;
} }

View File

@ -23,9 +23,8 @@ clients_lock = threading.Lock()
class DataType(Enum): class DataType(Enum):
RAW_CSI = 1 RAW_CSI = 1
UNWRAPPED_PHASE = 2 PROCESSED_CSI = 2
PROCESSED_CSI = 3 HEATMAP = 3
HEATMAP = 4
@dataclass @dataclass
@ -41,12 +40,6 @@ figures = {
x_label="Subcarrier", x_label="Subcarrier",
y_label="Phase", y_label="Phase",
), ),
"unwrapped_phase": figure_pb2.Figure(
uuid=str(uuid.uuid4()),
title="Unwrapped CSI Phase",
x_label="Subcarrier",
y_label="Unwrapped phase",
),
"processed_phase": figure_pb2.Figure( "processed_phase": figure_pb2.Figure(
uuid=str(uuid.uuid4()), uuid=str(uuid.uuid4()),
title="Preprocessed CSI Phase", title="Preprocessed CSI Phase",
@ -55,9 +48,9 @@ figures = {
), ),
"aoa_heatmap": figure_pb2.Figure( "aoa_heatmap": figure_pb2.Figure(
uuid=str(uuid.uuid4()), uuid=str(uuid.uuid4()),
title="AoA Heatmap", title="Preprocessed CSI Phase",
x_label="ToF", x_label="Subcarrier",
y_label="AoA", y_label="Phase",
), ),
} }
@ -111,25 +104,7 @@ def add_data(dtype: DataType, new_data: npt.NDArray[np.complex128]) -> None:
for q in clients.get(uuid, []): for q in clients.get(uuid, []):
q.put(figure_pb2.FigureData(uuid=uuid, line=linechart)) q.put(figure_pb2.FigureData(uuid=uuid, line=linechart))
case DataType.HEATMAP: case DataType.HEATMAP:
uuid = figures["aoa_heatmap"].uuid
heatmap = heatmap_pb2.HeatmapData(
uuid=uuid,
data=new_data.flatten(),
width=new_data.shape[1],
height=new_data.shape[0],
)
for q in clients.get(uuid, []):
q.put(figure_pb2.FigureData(uuid=uuid, heatmap=heatmap))
pass pass
case DataType.UNWRAPPED_PHASE:
uuid = figures["unwrapped_phase"].uuid
lines = [
line_pb2.LineChartData.Line(y=new_data[:, i, 0], label=f"Antenna {i}")
for i in range(new_data.shape[1])
]
linechart = line_pb2.LineChartData(lines=lines)
for q in clients.get(uuid, []):
q.put(figure_pb2.FigureData(uuid=uuid, line=linechart))
def listen_for_data(data_queue: "mp.Queue[VisualiserData]") -> None: def listen_for_data(data_queue: "mp.Queue[VisualiserData]") -> None: