Compare commits
3 Commits
a94a9b4ec4
...
c4196503d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4196503d8 | ||
|
|
28d096af44 | ||
|
|
61be734752 |
@ -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)
|
processed = preprocessor.preprocess(antenna_data, visualiser=visualise_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)
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
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)
|
||||||
@ -26,9 +28,14 @@ class Preprocessor:
|
|||||||
output="sos",
|
output="sos",
|
||||||
)
|
)
|
||||||
|
|
||||||
def preprocess(self, h: npt.NDArray[np.complex64]) -> npt.NDArray[np.complex64]:
|
def preprocess(
|
||||||
|
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 = np.where(
|
h_hat: npt.NDArray[np.complex64] = 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,
|
||||||
@ -38,12 +45,33 @@ 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)
|
||||||
|
|||||||
@ -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=10 * config.sample_rate) for host in config.receive_hosts
|
(host, 0): 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}
|
} | {(host, 1): deque(maxlen=15 * 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=config.sample_rate) for host in config.receive_hosts
|
(host, 0): 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}
|
} | {(host, 1): deque(maxlen=2 * config.sample_rate) for host in config.receive_hosts}
|
||||||
|
|
||||||
RSSI_THRESHOLD = 8
|
RSSI_THRESHOLD = 10
|
||||||
|
|
||||||
|
|
||||||
def average(data: Collection[Any]) -> float:
|
def average(data: Collection[Any]) -> float:
|
||||||
|
|||||||
@ -6,4 +6,5 @@ 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
|
||||||
find protos/ -name "*.proto" | xargs npm exec --prefix frontend/ protoc --ts_out frontend/src/grpc -I protos/
|
mkdir -p frontend/src/grpc
|
||||||
|
cd frontend && find ../protos/ -name "*.proto" | xargs npx protoc --ts_out=src/grpc -I../protos/
|
||||||
|
|||||||
@ -11,6 +11,18 @@ 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) {
|
||||||
@ -24,16 +36,12 @@ 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) => {
|
{
|
||||||
return {
|
z: reshape(data.value.figure.heatmap.data, data.value.figure.heatmap.width),
|
||||||
x: line.x,
|
type: 'heatmap' as const,
|
||||||
y: line.y,
|
},
|
||||||
type: 'scatter',
|
]
|
||||||
name: line.label,
|
|
||||||
color: line.color,
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
@ -43,6 +51,7 @@ 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() {
|
||||||
|
|||||||
@ -2,9 +2,8 @@ syntax = "proto3";
|
|||||||
|
|
||||||
message HeatmapData {
|
message HeatmapData {
|
||||||
string uuid = 1;
|
string uuid = 1;
|
||||||
repeated float x = 2;
|
repeated float data = 2;
|
||||||
repeated float y = 3;
|
uint32 width = 3;
|
||||||
uint32 width = 4;
|
uint32 height = 4;
|
||||||
uint32 height = 5;
|
string cmap = 5;
|
||||||
string cmap = 6;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,9 @@ clients_lock = threading.Lock()
|
|||||||
|
|
||||||
class DataType(Enum):
|
class DataType(Enum):
|
||||||
RAW_CSI = 1
|
RAW_CSI = 1
|
||||||
PROCESSED_CSI = 2
|
UNWRAPPED_PHASE = 2
|
||||||
HEATMAP = 3
|
PROCESSED_CSI = 3
|
||||||
|
HEATMAP = 4
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -40,6 +41,12 @@ 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",
|
||||||
@ -48,9 +55,9 @@ figures = {
|
|||||||
),
|
),
|
||||||
"aoa_heatmap": figure_pb2.Figure(
|
"aoa_heatmap": figure_pb2.Figure(
|
||||||
uuid=str(uuid.uuid4()),
|
uuid=str(uuid.uuid4()),
|
||||||
title="Preprocessed CSI Phase",
|
title="AoA Heatmap",
|
||||||
x_label="Subcarrier",
|
x_label="ToF",
|
||||||
y_label="Phase",
|
y_label="AoA",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +111,25 @@ 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:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user