evaluate.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. import logging
  2. import time
  3. from collections import namedtuple, defaultdict
  4. import datetime
  5. import functools
  6. import socket
  7. from scipy.misc import pilutil
  8. import numpy as np
  9. from concurrent import futures
  10. from utils.path import path
  11. from utils.xml import StringToTree, subelement, TreeToFile, FileToTree
  12. import utils.materials
  13. from utils.volumeimage import VolumeImage
  14. from utils import pos2rgb
  15. import numpy as np
  16. from scipy.misc import pilutil
  17. from PIL import Image, ImageDraw, ImageFont, ImageOps
  18. import localize as localizer
  19. import optimize as optimizer
  20. log = logging.getLogger('lws')
  21. def storeErrors(cachefile, errors):
  22. all_err3d = defaultdict(list)
  23. all_err2d = defaultdict(list)
  24. for pathid in errors:
  25. for runid in errors[pathid]:
  26. for s in ['end', 'seq']:
  27. err3d, err2d = errors[pathid][runid][s]
  28. all_err3d[s].append(err3d)
  29. all_err2d[s].append(err2d)
  30. try:
  31. avg_err2d_end = sum(all_err2d['end']) / len(all_err2d['end'])
  32. avg_err2d_seq = sum(all_err2d['seq']) / len(all_err2d['seq'])
  33. avg_err3d_end = sum(all_err3d['end']) / len(all_err3d['end'])
  34. avg_err3d_seq = sum(all_err3d['seq']) / len(all_err3d['seq'])
  35. except ZeroDivisionError:
  36. avg_err2d_end = avg_err2d_seq = avg_err3d_end = avg_err3d_seq = 0
  37. tree = StringToTree('<errors/>')
  38. a = tree.getroot().attrib
  39. a['avg_err2d_end'] = '%.2f' % avg_err2d_end
  40. a['avg_err2d_seq'] = '%.2f' % avg_err2d_seq
  41. a['avg_err3d_end'] = '%.2f' % avg_err3d_end
  42. a['avg_err3d_seq'] = '%.2f' % avg_err3d_seq
  43. for pathid, runs in errors.items():
  44. pathEl = subelement(tree.getroot(), 'path', attribs={'id': pathid})
  45. for runid, settings in runs.items():
  46. runEl = subelement(pathEl, 'run', attribs={'id': runid})
  47. for s, (err3d, err2d) in settings.items():
  48. subelement(runEl, 'setting', attribs={'id': s, 'err3d': '%.2f' % err3d, 'err2d': '%.2f' % err2d})
  49. TreeToFile(tree, cachefile)
  50. def loadErrors(cachefile):
  51. tree = FileToTree(cachefile)
  52. result = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: (0, 0))))
  53. for pathEl in tree.getroot().xpath('path'):
  54. d1 = result[pathEl.attrib['id']]
  55. for runEl in pathEl.xpath('run'):
  56. d2 = d1[runEl.attrib['id']]
  57. for settingEl in runEl.xpath('setting'):
  58. d2[settingEl.attrib['id']] = (float(settingEl.attrib['err3d']), float(settingEl.attrib['err2d']))
  59. return result
  60. def buildDeviceAdaptationArrays(lws, optrundir):
  61. activeAPs = [apid for apid in lws.config['aps'] if lws.config['aps'][apid]['optimize']]
  62. max_locid = max(lws.known_locations.keys())
  63. cached_vi = {}
  64. for station in lws.config['knownStations']:
  65. log.info('building da arrays for %s' % station)
  66. # initialize with infinity
  67. apid_locid2measurements = np.zeros(shape=(len(activeAPs), max_locid+1)) + np.inf
  68. remainingActive = []
  69. for i, apid in enumerate(activeAPs):
  70. locid2measurement = lws.getMeasurements(station, apid)
  71. if locid2measurement is not None:
  72. #~ locid2measurement_overlay = app.getMeasurements(station, apid, fromMeasurementStore=False)
  73. for locid, (x, y, z, measured, all_rssis) in locid2measurement.items():
  74. apid_locid2measurements[i, locid] = float(measured)
  75. np.save(optrundir.joinpath('%s_apid_locid2measurements.npy' % station), apid_locid2measurements)
  76. apid_locid2estimates = np.zeros(shape=apid_locid2measurements.shape) + np.inf
  77. for i, ap in enumerate(activeAPs):
  78. for j in range(apid_locid2measurements.shape[1]):
  79. if not np.isfinite(apid_locid2measurements[i, j]):
  80. continue
  81. loc = lws.known_locations[j]
  82. datfile = optrundir.joinpath('%s_%s.dat' % (lws.activeScene.name, ap))
  83. if datfile in cached_vi:
  84. vi = cached_vi[datfile]
  85. else:
  86. vi = cached_vi[datfile] = VolumeImage(datfile)
  87. x, y, z = vi.translate(loc.x, loc.y, loc.z)
  88. # fetch unnormalized data - therefore raw_estimates
  89. apid_locid2estimates[i, j] = vi.data[x, y, z]
  90. np.save(optrundir.joinpath('%s_apid_locid2estimates.npy' % station), apid_locid2estimates)
  91. def evaluate(lws, optrun, station, cubewidth, algo, pathids2runids, refresh):
  92. cachefile = lws.config['tmp'].joinpath('errors_%s_%s_%s_%s.xml' % (optrun, station, cubewidth, algo))
  93. if not cachefile.exists() or refresh:
  94. optrundir = lws.config['tmp_apdata'].joinpath(optrun)
  95. locfile = lws.activeScene['locationfile']
  96. objfile = lws.activeScene['objfile']
  97. vip = optrundir.joinpath(lws.activeScene.name + '_%s.dat')
  98. aps = []
  99. for apid in lws.config['aps'].sections:
  100. apcfg = lws.config['aps'][apid]
  101. aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
  102. davalues = defaultdict(list)
  103. #~ davalues = lws.optimizer.getDeviceAdaptionFromOptrun(optrun, station)
  104. #~ buildDeviceAdaptationArrays(lws, optrundir)
  105. #~ apid_locid2measurement = np.load(optrundir.joinpath('%s_apid_locid2measurements.npy' % station))#[:1, :]
  106. #~ apid_locid2estimated = np.load(optrundir.joinpath('%s_apid_locid2estimates.npy' % station))#[:1, :]
  107. #~ davalues, avg_delta = optimizer.optimizeDeviceAdaption({station: apid_locid2measurement}, apid_locid2estimated, 80)
  108. log.info('using davalues: %s' % davalues[station])
  109. reload(localizer)
  110. env = localizer.Environment(objfile=objfile, aps=aps, locationfile=locfile, tmpdir=lws.config['tmp'],
  111. vi_path=vip, bbox2d=lws.activeScene['bbox'][:4],
  112. davalues=davalues[station])
  113. datadir = lws.config['tmp_tracked']
  114. evaluator = Evaluator(optrun, station, env, cubewidth, datadir, freespace_scan=10, algo=algo)
  115. #~ pathids2runids = {'og1_classic': ('05',)}
  116. errors, failures = evaluator.evalAll(pathids2runids)
  117. storeErrors(cachefile, errors)
  118. else:
  119. errors = loadErrors(cachefile)
  120. return errors
  121. #~ pickle.dump(errors, open(cachefile, 'wb'))
  122. BBox = namedtuple('BBox', 'x1 x2 y1 y2 z1 z2')
  123. Resolution = namedtuple('Resolution', 'x y z')
  124. def buildOptrunFromConfig(lws):
  125. optrundir_vidatadir = lws.config['tmp_apdata'].joinpath(optrun)
  126. if optrundir_vidatadir.exists():
  127. for f in optrundir_vidatadir.files():
  128. f.remove()
  129. vip = optrundir_vidatadir.joinpath(lws.activeScene.name + '_%s.dat')
  130. powerids = set()
  131. aps = []
  132. for apid in lws.config['aps'].sections:
  133. apcfg = lws.config['aps'][apid]
  134. aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
  135. powerids.add(apcfg['powerid'])
  136. objfile = lws.activeScene['objfile']
  137. reload(localizer)
  138. # for l in optrun_init:
  139. # if l.startswith('resolution: '):
  140. # resolution = [int(e) for e in l[len('resolution: '):].split(',')]
  141. # elif l.startswith('bbox: '):
  142. # bbox = [float(e) for e in l[len('bbox: '):].split(',')]
  143. # elif l.startswith('scene: '):
  144. # scene_name = l[len('scene: '):].strip()
  145. # elif l.startswith('numphotons: '):
  146. # numphotons = int(l[len('numphotons: '):])
  147. # elif l.startswith('density: '):
  148. # density = float(l[len('density: '):])
  149. # read from lws.config
  150. print lws.config['scenes']['active']
  151. print lws.activeScene['bbox']
  152. print lws.activeScene['resolution']
  153. print lws.activeScene['numphotons']
  154. print lws.activeScene['density']
  155. pass
  156. def buildOptrun(lws, optrun=None):
  157. if not optrun:
  158. buildOptrunFromConfig(lws)
  159. exit
  160. optrun_resultdir = lws.optimizer.tmpdir.joinpath(optrun)
  161. optrun_init = [s for s in optrun_resultdir.joinpath('init.txt').lines() if s.strip()]
  162. optrun_min = [s for s in optrun_resultdir.joinpath('mins.txt').lines() if s.strip()][-1].split()
  163. optrundir_vidatadir = lws.config['tmp_apdata'].joinpath(optrun)
  164. if optrundir_vidatadir.exists():
  165. for f in optrundir_vidatadir.files():
  166. f.remove()
  167. vip = optrundir_vidatadir.joinpath(lws.activeScene.name + '_%s.dat')
  168. powerids = set()
  169. aps = []
  170. for apid in lws.config['aps'].sections:
  171. apcfg = lws.config['aps'][apid]
  172. aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
  173. powerids.add(apcfg['powerid'])
  174. objfile = lws.activeScene['objfile']
  175. reload(localizer)
  176. for l in optrun_init:
  177. if l.startswith('resolution: '):
  178. resolution = [int(e) for e in l[len('resolution: '):].split(',')]
  179. elif l.startswith('bbox: '):
  180. bbox = [float(e) for e in l[len('bbox: '):].split(',')]
  181. elif l.startswith('scene: '):
  182. scene_name = l[len('scene: '):].strip()
  183. elif l.startswith('numphotons: '):
  184. numphotons = int(l[len('numphotons: '):])
  185. elif l.startswith('density: '):
  186. density = float(l[len('density: '):])
  187. #~ aps = lws.mac2cfg.values()
  188. powerid2power = {}
  189. materials = {}
  190. for min_param in optrun_min:
  191. for matname in lws.activeScene['materials'].scalars:
  192. if min_param.startswith('%s:' % matname):
  193. reflect, alpha = min_param[len('%s:' % matname):].split(',')
  194. materials[matname] = utils.materials.Brdf(reflect, alpha)
  195. for pid in powerids:
  196. if min_param.startswith('%s:' % pid):
  197. powerid2power[pid] = float(min_param[len('%s:' % pid):])
  198. ap2power = {ap[0]: powerid2power[lws.config['aps'][ap[0]]['powerid']] for ap in aps}
  199. tx, ty, tz = bbox[0], bbox[2], bbox[4]
  200. dx = round((bbox[1] - bbox[0]) / resolution[0], 6)
  201. dy = round((bbox[3] - bbox[2]) / resolution[1], 6)
  202. dz = round((bbox[5] - bbox[4]) / resolution[2], 6)
  203. di = localizer.Dimensions(tx, ty, tz, dx, dy, dz)
  204. env = localizer.Environment(objfile=objfile, aps=aps, tmpdir=lws.config['tmp'],
  205. di=di, vi_path=vip, bbox2d=lws.activeScene['bbox'][:4],
  206. davalues=[])
  207. BASE_URL = 'http://127.0.0.1:%s' % lws.config['httpPort']
  208. disabledObjects = []
  209. def onResult(*args):
  210. pass
  211. env.buildSimulations(scene_name, materials, ap2power, BBox(*bbox), Resolution(*resolution),
  212. density, numphotons, disabledObjects, BASE_URL, onResult=onResult)
  213. #~ print 123
  214. def getCollectedPathids2runids(station, paths, sourcedir, filter=None):
  215. tree = getCollectedPaths(station, paths, sourcedir)
  216. pathids2runids = defaultdict(list)
  217. for pathEl in tree.getroot().xpath('path'):
  218. if filter is not None and filter.endswith('_r'):
  219. filter = filter[:-2]
  220. if filter is not None and not filter in pathEl.attrib['id']:
  221. continue
  222. for runEl in pathEl.xpath('run'):
  223. pathids2runids[pathEl.attrib['id']].append(runEl.attrib['id'])
  224. return pathids2runids
  225. def getCollectedPaths(station, paths, sourcedir):
  226. tree = StringToTree('<paths/>')
  227. for pathid, loc in paths.items():
  228. locs = ','.join(str(e) for e in loc)
  229. pel = subelement(tree.getroot(), 'path', attribs={'id': pathid, 'locations': locs})
  230. d = sourcedir.joinpath(station, pathid)
  231. if not d.exists():
  232. continue
  233. fnames = sorted(d.files('measurements_*.txt'))
  234. for fname in fnames:
  235. id = fname.name[len('measurements_'):-4]
  236. rel = subelement(pel, 'run', attribs = {'id': id})
  237. subelement(rel, 'measurements').text = fname.text()
  238. locfile = d.joinpath('locations_%s.txt' % id)
  239. subelement(rel, 'locations').text = locfile.text() if locfile.exists() else ''
  240. return tree
  241. STAIR_HACK = False
  242. class Evaluator(object):
  243. def __init__(self, optrun, device, env, cubewidth, sourcedir,
  244. analyze=True, interpolate=True, max_aps=None,
  245. verbose=False, algo='hmm', output_html=True,
  246. errortype='mean', **kwds):
  247. self.env = env
  248. self.cubewidth = cubewidth
  249. if algo == 'lmse':
  250. self.localizer = localizer.LMSELocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps)
  251. if STAIR_HACK:
  252. self.localizer3 = localizer.LMSELocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps)
  253. elif algo == 'hmm':
  254. hmmconfig = kwds.get('hmmconfig', {})
  255. cubewidth2pruning = {1: 0.06, 2: 0.09, 3: 0.06, 4: 0.05}
  256. if not 'prune_to' in hmmconfig:
  257. hmmconfig['prune_to'] = cubewidth2pruning.get(cubewidth, 0.05)
  258. #TODO: use special hmmconfig channel for params
  259. # prune_to=prune_to, num_threads=4, freespace_scan=freespace_scan,
  260. self.localizer = localizer.HMMLocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps, **hmmconfig)
  261. if STAIR_HACK:
  262. self.localizer3 = localizer.HMMLocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps, **hmmconfig)
  263. elif algo == 'pf':
  264. pfconfig = kwds.get('pfconfig', {})
  265. self.localizer = localizer.PFLocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps, **pfconfig)
  266. if STAIR_HACK:
  267. self.localizer3 = localizer.PFLocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps, **pfconfig)
  268. else:
  269. UNSUPPORTED_ALGO
  270. self.algo = algo
  271. self.sourcedir = path(sourcedir) # location of tracked paths
  272. self.tmpdir = self.env.tmpdir.joinpath('evaluator', optrun)
  273. if not self.tmpdir.exists():
  274. self.tmpdir.makedirs()
  275. self.analyze = analyze
  276. self.interpolate = interpolate
  277. self.device = device
  278. self.optrun = optrun
  279. self.output_html = output_html
  280. assert errortype in {'mean', 'median'}
  281. self.errortype = errortype
  282. def evaluate(self, pathid, runid):
  283. c2m = self.env.cube2meter
  284. try:
  285. #~ print 'eval: %s/%s' % (pathid, runid)
  286. cubewidth = self.cubewidth
  287. if 'stairs' in pathid:
  288. cubewidth = 3
  289. measurements = localizer.Measurements.fromTrackedMeasurements(self.env.locid2location, self.sourcedir, self.device, pathid, runid)
  290. #~ measurements = localizer.Measurements()
  291. #~ measurements.load(r'R:\xx.txt')
  292. t = time.time()
  293. if STAIR_HACK and 'stairs' in pathid:
  294. paths, measurements = self.localizer3.evaluateMeasurements(measurements, interpolate=self.interpolate)
  295. else:
  296. paths, measurements = self.localizer.evaluateMeasurements(measurements, interpolate=self.interpolate)
  297. error_3d, error_2d, errors_2d, errors_3d = self.getError(measurements, paths, cubewidth)
  298. if self.analyze:
  299. for pname, psec in paths.items():
  300. fname = '%s_%s_%s_%s_%s_%s' % (self.device, cubewidth, self.algo, pname, pathid, runid)
  301. in_meter = [c2m(x, y, z, cubewidth) for x, y, z in psec]
  302. if self.output_html:
  303. caption = {'end': 'Offline', 'seq': 'Online', 'seq_avg': 'Online/Avg'}[pname]
  304. text = '%s Error 3D: %.2f, %s Error 2D: %.2f' % (caption, error_3d[pname], caption, error_2d[pname])
  305. self.drawLocalizationResult2D(in_meter, measurements, name=fname, text=text)
  306. if self.output_html:
  307. if 'end' in errors_2d:
  308. # HACK: non LMSE (PF + HMM)
  309. fname = '%s_%s_%s_end_%s_%s' % (self.device, cubewidth, self.algo, pathid, runid)
  310. self.localizer.analyzePathSequence(self.tmpdir, paths['end'], path['seq'], measurements, errors_2d['end'], errors_2d['seq'], fname, errors_3d['end'], errors_3d['seq'])
  311. else:
  312. # HACK: LMSE
  313. fname = '%s_%s_%s_end_%s_%s' % (self.device, cubewidth, self.algo, pathid, runid)
  314. self.localizer.analyzePathSequence(self.tmpdir, paths['seq'], path['seq'], measurements, errors_2d['seq'], errors_2d['seq'], fname, errors_3d['seq'], errors_3d['seq'])
  315. #~ print 'decoded path of length %s in %.3f sec' % (len(estimated_path), time.time() - t)
  316. # Bah, has this become ugly
  317. if not 'seq_avg' in error_3d:
  318. if not 'end' in error_3d:
  319. vals = (self.algo, pathid, runid, error_3d['seq'], error_2d['seq'], time.time() - t)
  320. s = '%s-run %s/%s: seq-error-3d: %.1fm, seq-error-2d: %.1fm [%.2f sec]' % vals
  321. else:
  322. vals = (self.algo, pathid, runid, error_3d['end'], error_2d['end'], error_3d['seq'], error_2d['seq'], time.time() - t)
  323. s = '%s-run %s/%s: error-3d: %.1fm, error-2d: %.1fm, seq-error-3d: %.1fm, seq-error-2d: %.1fm [%.2f sec]' % vals
  324. else:
  325. vals = (self.algo, pathid, runid, error_3d['end'], error_2d['end'], error_3d['seq'], error_2d['seq'], error_3d['seq_avg'], error_2d['seq_avg'], time.time() - t)
  326. s = '%s-run %s/%s: end-3d: %.1fm, end-2d: %.1fm, seq-3d: %.1fm, seq-2d: %.1fm seq_avg-3d: %.1fm, seq_avg-2d: %.1fm [%.2f sec]' % vals
  327. log.info(s)
  328. except Exception:
  329. log.exception('error during evaluation of %s/%s' % (pathid, runid))
  330. error_3d = defaultdict(int)
  331. error_2d = defaultdict(int)
  332. return error_3d, error_2d
  333. def evalAll(self, pathid2runids, limit=None):
  334. errors = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: (0, 0))))
  335. failures = []
  336. def got_result(pathid, runid, f):
  337. error_3d, error_2d = f.result()
  338. if len(error_2d) == 0 or len(error_3d) == 0:
  339. failures.append((pathid, runid))
  340. else:
  341. for (pname, e3d) in error_3d.items():
  342. errors[pathid][runid][pname] = (e3d, error_2d[pname])
  343. if self.algo == 'hmm':
  344. if self.cubewidth < 2:
  345. max_workers = 2
  346. else:
  347. max_workers = 6
  348. elif self.algo == 'pf':
  349. max_workers = 8
  350. elif self.algo == 'lmse':
  351. max_workers = 8
  352. # initialize datetime strptime as firstly using in in a thread leads to:
  353. # http://bugs.python.org/issue7980
  354. datetime.datetime.strptime('2000', '%Y')
  355. t = time.time()
  356. with futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
  357. for pathid, runids in pathid2runids.items():
  358. #~ if 'stairs' in pathid or ('og1_eg' in pathid and not 'right' in pathid):
  359. #~ continue
  360. if '95' in pathid:
  361. continue
  362. for runid in runids[:limit]:
  363. f = executor.submit(functools.partial(self.evaluate, pathid, runid))
  364. f.add_done_callback(functools.partial(got_result, pathid, runid))
  365. log.info('evaluate all paths in %.3f secs' % (time.time() - t))
  366. avg2d = defaultdict(list)
  367. for pathid, runs in errors.items():
  368. for runid, settings in runs.items():
  369. for pname, (err3d, err2d) in settings.items():
  370. avg2d[pname].append(err2d)
  371. for pname, _errors in avg2d.items():
  372. if len(avg2d[pname]) > 0:
  373. median_e = list(sorted(_errors))[len(_errors) / 2]
  374. mean_e = sum(_errors) / float(len(_errors))
  375. log.debug('total mean error-%s 2d: %.2f, median error: %.2f' % (pname, mean_e, median_e))
  376. #~ log.debug('total median error-%s 2d: %.2fm' % (pname, ))
  377. return errors, failures
  378. def getError(self, measurements, paths, cubewidth):
  379. c2m = self.env.cube2meter
  380. #~ print seq_estimated_path
  381. assert all(len(measurements) == len(p) for p in paths.values())
  382. errors_3d = defaultdict(list)
  383. errors_2d = defaultdict(list)
  384. error_3d = {}
  385. error_2d = {}
  386. for pname, pseq in paths.items():
  387. for (x, y, z), m in zip(pseq, measurements):
  388. if m.pos != (0, 0, 0):
  389. x, y, z = c2m(x, y, z, cubewidth)
  390. errors_3d[pname].append(((x - m.pos.x)**2 + (y - m.pos.y)**2 + (z - m.pos.z)**2)**0.5)
  391. errors_2d[pname].append(((x - m.pos.x)**2 + (y - m.pos.y)**2)**0.5)
  392. if self.errortype == 'mean':
  393. error_3d[pname] = np.mean(errors_3d[pname]) #sum(errors_3d[pname]) / len(errors_3d[pname])
  394. error_2d[pname] = np.mean(errors_2d[pname]) #sum(errors_2d[pname]) / len(errors_2d[pname])
  395. elif self.errortype == 'median':
  396. error_3d[pname] = np.median(errors_3d[pname]) #list(sorted(errors_3d[pname]))[len(errors_3d[pname]) / 2]
  397. error_2d[pname] = np.median(errors_2d[pname]) #list(sorted(errors_2d[pname]))[len(errors_2d[pname]) / 2]
  398. return error_3d, error_2d, errors_2d, errors_3d
  399. def drawLocalizationResult2D(self, estimated_path, measurements, name='locresult2d', text=''):
  400. zs = [p[2] for p in estimated_path]
  401. avg_z = sum(zs) / len(zs) + 0.5
  402. if avg_z < 0:
  403. avg_z = -2.5
  404. elif avg_z < 2.0:
  405. avg_z = 1.0
  406. else:
  407. avg_z = 3.5
  408. cutfile = self.env.getCutFile(avg_z)
  409. img = Image.open(cutfile)
  410. img = ImageOps.invert(img)
  411. img = img.convert('RGBA')
  412. draw = ImageDraw.Draw(img)
  413. for i, (x1, y1, z1) in enumerate(estimated_path):
  414. m2dx, m2dy = self.env.translateMeterToMesh2dPixel(x1, y1)
  415. hexval = pos2rgb(i, len(estimated_path), saturation=1.0, spread=1.0)
  416. draw.ellipse((m2dx-self.cubewidth, m2dy-self.cubewidth*1.1, m2dx+self.cubewidth*1.1, m2dy+self.cubewidth*1.1), fill=hexval)
  417. if measurements[i].pos != (0, 0, 0):
  418. m2dx2, m2dy2 = self.env.translateMeterToMesh2dPixel(measurements[i].pos.x, measurements[i].pos.y)
  419. draw.line((m2dx, m2dy, m2dx2, m2dy2), fill='#FF5555')
  420. if socket.gethostname() == 'nostromo':
  421. font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 16)
  422. else:
  423. font = ImageFont.load_default()
  424. draw.text((10, 10), text, fill='#CC1111', font=font)
  425. img.save(self.tmpdir.joinpath('%s.png' % name))