Skip to content

Commit 2915a38

Browse files
committed
merged parser in repo
1 parent 7abd7e7 commit 2915a38

15 files changed

Lines changed: 618 additions & 1 deletion

img/calibration_flow.png

149 Bytes
Loading

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ PyQt5 == 5.15.6
66
pyqtgraph == 0.12.3
77
PyQt5-stubs == 5.15.2.0
88
matplotlib == 3.5.0
9-
numpy
9+
numpy
10+
scipy

src/parser.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import matplotlib.pyplot as plt
2+
import pandas as pd
3+
import src.parser.parsing as parse
4+
import src.parser.computeAngle as compute
5+
import src.parser.plotter as p
6+
import os
7+
import src.parser.export as export
8+
import src.parser.AngleCalculatorParameters as param
9+
10+
EXPORT_PDOA = True
11+
12+
dataFolderPath = "C:/Users/huber/USherbrooke/SwarmUs - General/Interlocalisation/Angles/HiveBoard"
13+
if not os.path.exists(dataFolderPath+"/"+"figures"):
14+
os.makedirs(dataFolderPath+"/"+"figures")
15+
16+
dataName = "20211112_1230120p024_2mOfficial2"
17+
usedPairs = [0, 1, 5]
18+
19+
if not os.path.exists(dataFolderPath+"/"+"figures"+"/"+dataName):
20+
os.makedirs(dataFolderPath+"/"+"figures"+"/"+dataName)
21+
figureFolderPath = dataFolderPath+"/"+"figures"+"/"+dataName
22+
#extract data from CSV
23+
dataCsv = pd.read_csv(dataFolderPath + "/"+ dataName + '.csv')
24+
data = parse.remAnormalRx(dataCsv)
25+
dataBundled, angles = parse.bundle(data)
26+
27+
dPDOA = compute.PDOA(dataBundled,[data.columns.values[[3,7,11]]],[data.columns.values[[4,8,12]]],angles)
28+
mdPDOA,sdPDOA = compute.stats(dPDOA)
29+
30+
PDOAscaleFactor = {}
31+
for nb in usedPairs:
32+
PDOAscaleFactor["PDOA"+str(nb)] = p.extractSlopeExtremums(dPDOA,mdPDOA,namePDOA="PDOA"+str(nb))
33+
34+
PDOAscaleFactor["PDOA2"]=0
35+
PDOAscaleFactor["PDOA3"]=0
36+
PDOAscaleFactor["PDOA4"]=0
37+
# recompute PDOA with centering (skew correction)
38+
dPDOA = compute.PDOA(dataBundled,[data.columns.values[[3,7,11]]],[data.columns.values[[4,8,12]]],angles,offset=PDOAscaleFactor)
39+
mdPDOA,sdPDOA = compute.stats(dPDOA)
40+
41+
for nb in usedPairs:
42+
p.plotPDOA(dPDOA,mdPDOA,sdPDOA,namePDOA='PDOA'+str(nb))
43+
# confusion zone
44+
for line in [30, 90]:
45+
plt.plot([0,360],[-line,-line],"--r")
46+
plt.plot([0,360],[line,line],"--r")
47+
plt.show()
48+
49+
# export angle calculator parameters to pickle format
50+
if EXPORT_PDOA:
51+
PDOAlineFit = {}
52+
for nb in usedPairs:
53+
tempDic = p.extractSlopes(dPDOA, mdPDOA, namePDOA="PDOA" + str(nb))
54+
PDOAlineFit.update(tempDic)
55+
56+
exporter = export.Exporter(dataFolderPath)
57+
pairs = {}
58+
for n in usedPairs:
59+
pairs["PDOA" + str(n)] = param.AngleCalculatorParameters()
60+
pairs["PDOA" + str(n)].m_pairID = n
61+
62+
export.setPdoaSlopesCarac(pairs, PDOAlineFit, PDOAscaleFactor)
63+
export.setAntennaPairs(pairs)
64+
65+
exporter.toPickle(pairs)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Structure containing all parameters used to calculate an angle. These parameters are calculated
2+
# using the Python tooling during calibration and then transferred to the board.
3+
4+
class AngleCalculatorParameters:
5+
def __init__(self):
6+
pass
7+
m_pairID = None
8+
# Normalization factors used to stretch PDOA angles between -90 and +90 degrees. (Applied by
9+
# dividing by the normalization after applying the asin() on the PDOA)
10+
m_pdoaNormalizationFactors = None
11+
12+
# Slopes (a in y=ax+b) of the non-reciprocated PDOA functions (calculatedAngle = a*realAngle+b)
13+
# A single (positive) slope is used for all PDOA curves. The sign of the slope is applied at
14+
# runtime.\
15+
m_pdoaSlopes = None
16+
17+
# Intercepts (b in y=ax+b) of the non-reciprocated PDOA functions for every curve
18+
# (calculatedAngle = a*realAngle+b)
19+
m_pdoaIntercepts = None
20+
21+
# Lookup table used to know which antenna IDs are used in each pair ID
22+
# TDOAx = m_antennaPairs[0]-m_antennaPairs[1]
23+
m_antennaPairs = None

src/parser/__init__.py

Whitespace-only changes.

src/parser/computeAngle.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import numpy as np
2+
import src.parser.utils as utils
3+
4+
def minMaxAverage(TDOA,phaseReverse):
5+
return utils.mapDict(_minMaxAverage, TDOA, phaseReverse)
6+
7+
def _minMaxAverage(TDOA,phaseReverse):
8+
mean = []
9+
for i in [0, 1]:
10+
min = int(phaseReverse[i]-5) if (phaseReverse[i]-5) > 0 else 0
11+
max = int(phaseReverse[i]+5) if (phaseReverse[i]+5) < len(TDOA.values()) else int(len(TDOA.values())-1)
12+
13+
mean.append(np.mean(list({list(TDOA)[val]: np.mean(TDOA[list(TDOA)[val]]) for val in
14+
range(min, max)}.values())))
15+
16+
return mean
17+
18+
def PDOA(d,columnsSFD,columnsCIR,angles,offset=None):
19+
columnsSFD = columnsSFD[0]
20+
columnsCIR = columnsCIR[0]
21+
numItr = 2**len(columnsSFD) - 2**(len(columnsSFD)-2)
22+
name = []
23+
for itr in range(numItr):
24+
name.append('PDOA'+str(itr))
25+
26+
27+
PDOA = {}
28+
scaleFactor = {}
29+
i = 0
30+
for col1,_ in enumerate(columnsSFD):
31+
for col2,_ in enumerate(columnsSFD):
32+
if col1 != col2:
33+
PDOA[name[i]] = {}
34+
for a in angles:
35+
sfd1 = np.array(d[a][columnsSFD[col1]])
36+
sfd2 = np.array(d[a][columnsSFD[col2]])
37+
fp1 = np.array(d[a][columnsCIR[col1]])
38+
fp2 = np.array(d[a][columnsCIR[col2]])
39+
40+
part1 = np.add(np.subtract(np.subtract(fp1,sfd1),fp2),sfd2)-np.pi/2
41+
part2 = np.mod(part1,2*np.pi)
42+
if offset is None:
43+
PDOA[name[i]][a] = part2
44+
else:
45+
scaleFactor = 1
46+
theta = np.mod((part2 + offset[name[i]]), 2 * np.pi) - np.pi
47+
# PDOA[name[i]][a] =theta *90/np.pi
48+
PDOA[name[i]][a] = np.arcsin(theta/np.pi) * 180/np.pi * scaleFactor
49+
i = i+1
50+
return PDOA
51+
52+
def stats(d):
53+
mean = {x: {a: np.mean(d[x][a]) for a in d[x].keys()} for x in d.keys()}
54+
stdev = {x: {a: np.std(d[x][a]) for a in d[x].keys()} for x in d.keys()}
55+
return mean,stdev
56+
57+
def _filterSTD(sdPDOA):
58+
rem = []
59+
for d in sdPDOA:
60+
if sdPDOA[d] > 15:
61+
rem.append(d)
62+
return rem
63+
64+
def filterSTD(sdPDOA):
65+
return utils.mapDict(_filterSTD, sdPDOA)
66+
67+
def removeAnormalStdDev(mdPDOA,sdPDOA, mdTDOA, sdTDOA=None):
68+
valOut = filterSTD(sdPDOA)
69+
70+
for i,name in enumerate(valOut):
71+
for rem in valOut[name]:
72+
mdPDOA[name].pop(rem)
73+
sdPDOA[name].pop(rem)
74+
mdTDOA[list(mdTDOA)[i]].pop(rem)
75+
if sdTDOA is not None:
76+
sdTDOA[list(mdTDOA)[i]].pop(rem)
77+
return mdPDOA,sdPDOA,mdTDOA,sdTDOA
78+
79+
def mean(dic):
80+
return utils.mapDict(_mean, dic)
81+
def _mean(val):
82+
return np.mean(list(val.values()))

src/parser/export.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from src.parser.AngleCalculatorParameters import AngleCalculatorParameters
2+
import src.parser.utils as utils
3+
import pickle as pkl
4+
import os
5+
6+
class Exporter:
7+
def __init__(self,exportPath):
8+
self.dataExportPath = exportPath
9+
10+
dataExportPath = None
11+
dataExportPathFolder = None
12+
13+
def toPickle(self,pairs):
14+
if not os.path.exists(self.dataExportPath + "/" + "angleParameters"):
15+
os.makedirs(self.dataExportPath + "/" + "angleParameters")
16+
self.dataExportPathFolder = self.dataExportPath + "/" + "angleParameters"
17+
18+
utils.mapDict(self._toPickle, pairs, {k:k for k in pairs.keys()})
19+
20+
def _toPickle(self,AngleClass: AngleCalculatorParameters,name):
21+
pkl.dump(AngleClass,open(self.dataExportPathFolder+ "/"+str(name)[-1]+"_angleCalculatorParameters.pkl", 'wb'))
22+
23+
def setPdoaNormalizationFactors(pairs, factors):
24+
utils.mapDict(_setPdoaNormalizationFactors, pairs, factors)
25+
def _setPdoaNormalizationFactors(AngleClass: AngleCalculatorParameters, factors):
26+
AngleClass.m_pdoaNormalizationFactors = factors
27+
28+
def setPdoaSlopesCarac(pairs, lineCarac,PDOAscaleFactor):
29+
utils.mapDict(_setPdoaSlopesCarac, pairs, lineCarac, PDOAscaleFactor)
30+
def _setPdoaSlopesCarac(AngleClass: AngleCalculatorParameters, lineCarac,PDOAscaleFactor):
31+
AngleClass.m_pdoaSlopes = lineCarac["m"]
32+
AngleClass.m_pdoaIntercepts = lineCarac["b"]
33+
AngleClass.m_pdoaNormalizationFactors = PDOAscaleFactor
34+
35+
def setAntennaPairs(pairs):
36+
pair = {}
37+
i = 0
38+
for x in range(0, 3):
39+
for y in range(0, 3):
40+
if x != y:
41+
pair['PDOA'+str(i)] = [x, y]
42+
i += 1
43+
44+
utils.mapDict(_setAntennaPairs, pairs, pair)
45+
def _setAntennaPairs(AngleClass: AngleCalculatorParameters, pair):
46+
AngleClass.m_antennaPairs = pair
47+
48+
def testRead(file):
49+
return pkl.load(open(file, 'rb'))

src/parser/fitter/LineBuilder.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# from https://matplotlib.org/stable/users/event_handling.html
2+
from matplotlib import collections as mc
3+
4+
class LineBuilder:
5+
def __init__(self, line,ax):
6+
self.line = [line]
7+
self.ax = ax
8+
self.eventCounter = 0
9+
self.nbLine = 0
10+
self.xs = []
11+
self.ys = []
12+
self.cid = self.line[-1].figure.canvas.mpl_connect('button_press_event', self)
13+
self.ms = []
14+
self.bs = []
15+
16+
17+
def __call__(self, event):
18+
if event.inaxes!=self.line[-1].axes: return
19+
self.xs.append(event.xdata)
20+
self.ys.append(event.ydata)
21+
self.eventCounter += 1
22+
self.line[-1].set_data(self.xs, self.ys)
23+
if self.eventCounter > 1:
24+
return self.addLine()
25+
26+
def addLine(self):
27+
self.eventCounter = 0
28+
m = (self.ys[1 + 2 * self.nbLine] - self.ys[0 + 2 * self.nbLine]) / (
29+
self.xs[1 + 2 * self.nbLine] - self.xs[0 + 2 * self.nbLine])
30+
b = self.ys[1 + 2 * self.nbLine] - m * self.xs[1 + 2 * self.nbLine]
31+
self.ms.append(m)
32+
self.bs.append(b)
33+
self.line[-1].figure.canvas.draw()
34+
lineNew, = self.ax.plot([0], [0],'r')
35+
self.line.append(lineNew)
36+
self.cid = self.line[-1].figure.canvas.mpl_connect('button_press_event', self)
37+
self.xs=[]
38+
self.ys=[]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# from https://matplotlib.org/stable/users/event_handling.html
2+
import matplotlib
3+
from matplotlib import pyplot as plt
4+
import numpy as np
5+
6+
class PointExtracter:
7+
def __init__(self,ax):
8+
self.scat = None
9+
self.ax = ax
10+
self.nbLine = 0
11+
self.xs = []
12+
self.ys = []
13+
self.cid = self.ax.figure.canvas.mpl_connect('button_press_event', self)
14+
self.ms = []
15+
self.ptCnt = 0
16+
17+
18+
def __call__(self, event):
19+
if self.ptCnt < 1:
20+
self.scat = self.ax.scatter([event.xdata],[event.ydata],c='r',zorder=10)
21+
self.ys.append(event.ydata)
22+
self.scat.axes.figure.canvas.draw_idle()
23+
else:
24+
self.addPoint([event.xdata,event.ydata])
25+
self.ys.append(event.ydata)
26+
# return [round(x) for x in self.xs]
27+
28+
def addPoint(self,new_point, c='r'):
29+
old_off = self.scat.get_offsets()
30+
new_off = np.concatenate([old_off, np.array(new_point, ndmin=2)])
31+
old_c = self.scat.get_facecolors()
32+
new_c = np.concatenate([old_c, np.array(matplotlib.colors.to_rgba(c), ndmin=2)])
33+
34+
self.scat.set_offsets(new_off)
35+
self.scat.set_facecolors(new_c)
36+
37+
self.scat.axes.figure.canvas.draw_idle()

src/parser/fitter/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)