feat: implement multidistance rssi->distance model parameter estimation
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
defmodule Localiser.Localisation.Calibration do
|
||||
@moduledoc false
|
||||
|
||||
# Returns [{rssi, is_outlier}] using Tukey IQR fences on the full sample list.
|
||||
def classify_outliers([]), do: []
|
||||
|
||||
def classify_outliers(samples) do
|
||||
{lo, hi} = iqr_fences(samples)
|
||||
Enum.map(samples, fn rssi -> {rssi, rssi < lo or rssi > hi} end)
|
||||
end
|
||||
|
||||
# Live hint: is this single reading an outlier relative to the samples collected so far?
|
||||
# Returns false when fewer than 5 existing samples (not enough signal).
|
||||
def outlier?(_reading, existing) when length(existing) < 5, do: false
|
||||
|
||||
def outlier?(reading, existing) do
|
||||
{lo, hi} = iqr_fences(existing)
|
||||
reading < lo or reading > hi
|
||||
end
|
||||
|
||||
# OLS regression for RSSI = A - 10n * log10(d).
|
||||
# stages :: [%{distance: float, mean_rssi: float}]
|
||||
# Requires at least 2 stages with distinct distances.
|
||||
# Returns {:ok, {rssi_ref :: integer, path_loss_exp :: float}} or {:error, :insufficient_data}.
|
||||
def least_squares(stages) when length(stages) < 2, do: {:error, :insufficient_data}
|
||||
|
||||
def least_squares(stages) do
|
||||
points = Enum.map(stages, fn %{distance: d, mean_rssi: rssi} ->
|
||||
{:math.log10(d), rssi}
|
||||
end)
|
||||
|
||||
xs = Enum.map(points, &elem(&1, 0))
|
||||
ys = Enum.map(points, &elem(&1, 1))
|
||||
|
||||
x_bar = mean(xs)
|
||||
y_bar = mean(ys)
|
||||
|
||||
cov_xy = xs |> Enum.zip(ys) |> Enum.reduce(0.0, fn {x, y}, acc ->
|
||||
acc + (x - x_bar) * (y - y_bar)
|
||||
end)
|
||||
|
||||
var_x = Enum.reduce(xs, 0.0, fn x, acc -> acc + (x - x_bar) * (x - x_bar) end)
|
||||
|
||||
if var_x == 0.0 do
|
||||
{:error, :insufficient_data}
|
||||
else
|
||||
beta = cov_xy / var_x
|
||||
a = y_bar - beta * x_bar
|
||||
|
||||
path_loss_exp = -beta / 10.0
|
||||
rssi_ref = round(a)
|
||||
|
||||
{:ok, {rssi_ref, path_loss_exp}}
|
||||
end
|
||||
end
|
||||
|
||||
# --- Private ---
|
||||
|
||||
defp iqr_fences(samples) do
|
||||
sorted = Enum.sort(samples)
|
||||
n = length(sorted)
|
||||
|
||||
q1 = percentile(sorted, n, 0.25)
|
||||
q3 = percentile(sorted, n, 0.75)
|
||||
iqr = q3 - q1
|
||||
|
||||
{q1 - 1.5 * iqr, q3 + 1.5 * iqr}
|
||||
end
|
||||
|
||||
defp percentile(sorted, n, p) do
|
||||
idx = p * (n - 1)
|
||||
lo = floor(idx)
|
||||
hi = ceil(idx)
|
||||
|
||||
if lo == hi do
|
||||
Enum.at(sorted, lo) * 1.0
|
||||
else
|
||||
frac = idx - lo
|
||||
Enum.at(sorted, lo) * (1 - frac) + Enum.at(sorted, hi) * frac
|
||||
end
|
||||
end
|
||||
|
||||
defp mean(list) do
|
||||
Enum.sum(list) / length(list)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user