import logging
import time
from collections import namedtuple, defaultdict
import datetime
import functools
import socket
from scipy.misc import pilutil
import numpy as np
from concurrent import futures
from utils.path import path
from utils.xml import StringToTree, subelement, TreeToFile, FileToTree
import utils.materials
from utils.volumeimage import VolumeImage
from utils import pos2rgb
import numpy as np
from scipy.misc import pilutil
from PIL import Image, ImageDraw, ImageFont, ImageOps
import localize as localizer
import optimize as optimizer
log = logging.getLogger('lws')
def storeErrors(cachefile, errors):
all_err3d = defaultdict(list)
all_err2d = defaultdict(list)
for pathid in errors:
for runid in errors[pathid]:
for s in ['end', 'seq']:
err3d, err2d = errors[pathid][runid][s]
all_err3d[s].append(err3d)
all_err2d[s].append(err2d)
try:
avg_err2d_end = sum(all_err2d['end']) / len(all_err2d['end'])
avg_err2d_seq = sum(all_err2d['seq']) / len(all_err2d['seq'])
avg_err3d_end = sum(all_err3d['end']) / len(all_err3d['end'])
avg_err3d_seq = sum(all_err3d['seq']) / len(all_err3d['seq'])
except ZeroDivisionError:
avg_err2d_end = avg_err2d_seq = avg_err3d_end = avg_err3d_seq = 0
tree = StringToTree('')
a = tree.getroot().attrib
a['avg_err2d_end'] = '%.2f' % avg_err2d_end
a['avg_err2d_seq'] = '%.2f' % avg_err2d_seq
a['avg_err3d_end'] = '%.2f' % avg_err3d_end
a['avg_err3d_seq'] = '%.2f' % avg_err3d_seq
for pathid, runs in errors.items():
pathEl = subelement(tree.getroot(), 'path', attribs={'id': pathid})
for runid, settings in runs.items():
runEl = subelement(pathEl, 'run', attribs={'id': runid})
for s, (err3d, err2d) in settings.items():
subelement(runEl, 'setting', attribs={'id': s, 'err3d': '%.2f' % err3d, 'err2d': '%.2f' % err2d})
TreeToFile(tree, cachefile)
def loadErrors(cachefile):
tree = FileToTree(cachefile)
result = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: (0, 0))))
for pathEl in tree.getroot().xpath('path'):
d1 = result[pathEl.attrib['id']]
for runEl in pathEl.xpath('run'):
d2 = d1[runEl.attrib['id']]
for settingEl in runEl.xpath('setting'):
d2[settingEl.attrib['id']] = (float(settingEl.attrib['err3d']), float(settingEl.attrib['err2d']))
return result
def buildDeviceAdaptationArrays(lws, optrundir):
activeAPs = [apid for apid in lws.config['aps'] if lws.config['aps'][apid]['optimize']]
max_locid = max(lws.known_locations.keys())
cached_vi = {}
for station in lws.config['knownStations']:
log.info('building da arrays for %s' % station)
# initialize with infinity
apid_locid2measurements = np.zeros(shape=(len(activeAPs), max_locid+1)) + np.inf
remainingActive = []
for i, apid in enumerate(activeAPs):
locid2measurement = lws.getMeasurements(station, apid)
if locid2measurement is not None:
#~ locid2measurement_overlay = app.getMeasurements(station, apid, fromMeasurementStore=False)
for locid, (x, y, z, measured, all_rssis) in locid2measurement.items():
apid_locid2measurements[i, locid] = float(measured)
np.save(optrundir.joinpath('%s_apid_locid2measurements.npy' % station), apid_locid2measurements)
apid_locid2estimates = np.zeros(shape=apid_locid2measurements.shape) + np.inf
for i, ap in enumerate(activeAPs):
for j in range(apid_locid2measurements.shape[1]):
if not np.isfinite(apid_locid2measurements[i, j]):
continue
loc = lws.known_locations[j]
datfile = optrundir.joinpath('%s_%s.dat' % (lws.activeScene.name, ap))
if datfile in cached_vi:
vi = cached_vi[datfile]
else:
vi = cached_vi[datfile] = VolumeImage(datfile)
x, y, z = vi.translate(loc.x, loc.y, loc.z)
# fetch unnormalized data - therefore raw_estimates
apid_locid2estimates[i, j] = vi.data[x, y, z]
np.save(optrundir.joinpath('%s_apid_locid2estimates.npy' % station), apid_locid2estimates)
def evaluate(lws, optrun, station, cubewidth, algo, pathids2runids, refresh):
cachefile = lws.config['tmp'].joinpath('errors_%s_%s_%s_%s.xml' % (optrun, station, cubewidth, algo))
if not cachefile.exists() or refresh:
optrundir = lws.config['tmp_apdata'].joinpath(optrun)
locfile = lws.activeScene['locationfile']
objfile = lws.activeScene['objfile']
vip = optrundir.joinpath(lws.activeScene.name + '_%s.dat')
aps = []
for apid in lws.config['aps'].sections:
apcfg = lws.config['aps'][apid]
aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
davalues = defaultdict(list)
#~ davalues = lws.optimizer.getDeviceAdaptionFromOptrun(optrun, station)
#~ buildDeviceAdaptationArrays(lws, optrundir)
#~ apid_locid2measurement = np.load(optrundir.joinpath('%s_apid_locid2measurements.npy' % station))#[:1, :]
#~ apid_locid2estimated = np.load(optrundir.joinpath('%s_apid_locid2estimates.npy' % station))#[:1, :]
#~ davalues, avg_delta = optimizer.optimizeDeviceAdaption({station: apid_locid2measurement}, apid_locid2estimated, 80)
log.info('using davalues: %s' % davalues[station])
reload(localizer)
env = localizer.Environment(objfile=objfile, aps=aps, locationfile=locfile, tmpdir=lws.config['tmp'],
vi_path=vip, bbox2d=lws.activeScene['bbox'][:4],
davalues=davalues[station])
datadir = lws.config['tmp_tracked']
evaluator = Evaluator(optrun, station, env, cubewidth, datadir, freespace_scan=10, algo=algo)
#~ pathids2runids = {'og1_classic': ('05',)}
errors, failures = evaluator.evalAll(pathids2runids)
storeErrors(cachefile, errors)
else:
errors = loadErrors(cachefile)
return errors
#~ pickle.dump(errors, open(cachefile, 'wb'))
BBox = namedtuple('BBox', 'x1 x2 y1 y2 z1 z2')
Resolution = namedtuple('Resolution', 'x y z')
def buildOptrunFromConfig(lws):
optrundir_vidatadir = lws.config['tmp_apdata'].joinpath(optrun)
if optrundir_vidatadir.exists():
for f in optrundir_vidatadir.files():
f.remove()
vip = optrundir_vidatadir.joinpath(lws.activeScene.name + '_%s.dat')
powerids = set()
aps = []
for apid in lws.config['aps'].sections:
apcfg = lws.config['aps'][apid]
aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
powerids.add(apcfg['powerid'])
objfile = lws.activeScene['objfile']
reload(localizer)
# for l in optrun_init:
# if l.startswith('resolution: '):
# resolution = [int(e) for e in l[len('resolution: '):].split(',')]
# elif l.startswith('bbox: '):
# bbox = [float(e) for e in l[len('bbox: '):].split(',')]
# elif l.startswith('scene: '):
# scene_name = l[len('scene: '):].strip()
# elif l.startswith('numphotons: '):
# numphotons = int(l[len('numphotons: '):])
# elif l.startswith('density: '):
# density = float(l[len('density: '):])
# read from lws.config
print lws.config['scenes']['active']
print lws.activeScene['bbox']
print lws.activeScene['resolution']
print lws.activeScene['numphotons']
print lws.activeScene['density']
pass
def buildOptrun(lws, optrun=None):
if not optrun:
buildOptrunFromConfig(lws)
exit
optrun_resultdir = lws.optimizer.tmpdir.joinpath(optrun)
optrun_init = [s for s in optrun_resultdir.joinpath('init.txt').lines() if s.strip()]
optrun_min = [s for s in optrun_resultdir.joinpath('mins.txt').lines() if s.strip()][-1].split()
optrundir_vidatadir = lws.config['tmp_apdata'].joinpath(optrun)
if optrundir_vidatadir.exists():
for f in optrundir_vidatadir.files():
f.remove()
vip = optrundir_vidatadir.joinpath(lws.activeScene.name + '_%s.dat')
powerids = set()
aps = []
for apid in lws.config['aps'].sections:
apcfg = lws.config['aps'][apid]
aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z']))
powerids.add(apcfg['powerid'])
objfile = lws.activeScene['objfile']
reload(localizer)
for l in optrun_init:
if l.startswith('resolution: '):
resolution = [int(e) for e in l[len('resolution: '):].split(',')]
elif l.startswith('bbox: '):
bbox = [float(e) for e in l[len('bbox: '):].split(',')]
elif l.startswith('scene: '):
scene_name = l[len('scene: '):].strip()
elif l.startswith('numphotons: '):
numphotons = int(l[len('numphotons: '):])
elif l.startswith('density: '):
density = float(l[len('density: '):])
#~ aps = lws.mac2cfg.values()
powerid2power = {}
materials = {}
for min_param in optrun_min:
for matname in lws.activeScene['materials'].scalars:
if min_param.startswith('%s:' % matname):
reflect, alpha = min_param[len('%s:' % matname):].split(',')
materials[matname] = utils.materials.Brdf(reflect, alpha)
for pid in powerids:
if min_param.startswith('%s:' % pid):
powerid2power[pid] = float(min_param[len('%s:' % pid):])
ap2power = {ap[0]: powerid2power[lws.config['aps'][ap[0]]['powerid']] for ap in aps}
tx, ty, tz = bbox[0], bbox[2], bbox[4]
dx = round((bbox[1] - bbox[0]) / resolution[0], 6)
dy = round((bbox[3] - bbox[2]) / resolution[1], 6)
dz = round((bbox[5] - bbox[4]) / resolution[2], 6)
di = localizer.Dimensions(tx, ty, tz, dx, dy, dz)
env = localizer.Environment(objfile=objfile, aps=aps, tmpdir=lws.config['tmp'],
di=di, vi_path=vip, bbox2d=lws.activeScene['bbox'][:4],
davalues=[])
BASE_URL = 'http://127.0.0.1:%s' % lws.config['httpPort']
disabledObjects = []
def onResult(*args):
pass
env.buildSimulations(scene_name, materials, ap2power, BBox(*bbox), Resolution(*resolution),
density, numphotons, disabledObjects, BASE_URL, onResult=onResult)
#~ print 123
def getCollectedPathids2runids(station, paths, sourcedir, filter=None):
tree = getCollectedPaths(station, paths, sourcedir)
pathids2runids = defaultdict(list)
for pathEl in tree.getroot().xpath('path'):
if filter is not None and filter.endswith('_r'):
filter = filter[:-2]
if filter is not None and not filter in pathEl.attrib['id']:
continue
for runEl in pathEl.xpath('run'):
pathids2runids[pathEl.attrib['id']].append(runEl.attrib['id'])
return pathids2runids
def getCollectedPaths(station, paths, sourcedir):
tree = StringToTree('')
for pathid, loc in paths.items():
locs = ','.join(str(e) for e in loc)
pel = subelement(tree.getroot(), 'path', attribs={'id': pathid, 'locations': locs})
d = sourcedir.joinpath(station, pathid)
if not d.exists():
continue
fnames = sorted(d.files('measurements_*.txt'))
for fname in fnames:
id = fname.name[len('measurements_'):-4]
rel = subelement(pel, 'run', attribs = {'id': id})
subelement(rel, 'measurements').text = fname.text()
locfile = d.joinpath('locations_%s.txt' % id)
subelement(rel, 'locations').text = locfile.text() if locfile.exists() else ''
return tree
STAIR_HACK = False
class Evaluator(object):
def __init__(self, optrun, device, env, cubewidth, sourcedir,
analyze=True, interpolate=True, max_aps=None,
verbose=False, algo='hmm', output_html=True,
errortype='mean', **kwds):
self.env = env
self.cubewidth = cubewidth
if algo == 'lmse':
self.localizer = localizer.LMSELocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps)
if STAIR_HACK:
self.localizer3 = localizer.LMSELocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps)
elif algo == 'hmm':
hmmconfig = kwds.get('hmmconfig', {})
cubewidth2pruning = {1: 0.06, 2: 0.09, 3: 0.06, 4: 0.05}
if not 'prune_to' in hmmconfig:
hmmconfig['prune_to'] = cubewidth2pruning.get(cubewidth, 0.05)
#TODO: use special hmmconfig channel for params
# prune_to=prune_to, num_threads=4, freespace_scan=freespace_scan,
self.localizer = localizer.HMMLocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps, **hmmconfig)
if STAIR_HACK:
self.localizer3 = localizer.HMMLocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps, **hmmconfig)
elif algo == 'pf':
pfconfig = kwds.get('pfconfig', {})
self.localizer = localizer.PFLocalizer(env, cubewidth=cubewidth, verbose=verbose, max_aps=max_aps, **pfconfig)
if STAIR_HACK:
self.localizer3 = localizer.PFLocalizer(env, cubewidth=3, verbose=verbose, max_aps=max_aps, **pfconfig)
else:
UNSUPPORTED_ALGO
self.algo = algo
self.sourcedir = path(sourcedir) # location of tracked paths
self.tmpdir = self.env.tmpdir.joinpath('evaluator', optrun)
if not self.tmpdir.exists():
self.tmpdir.makedirs()
self.analyze = analyze
self.interpolate = interpolate
self.device = device
self.optrun = optrun
self.output_html = output_html
assert errortype in {'mean', 'median'}
self.errortype = errortype
def evaluate(self, pathid, runid):
c2m = self.env.cube2meter
try:
#~ print 'eval: %s/%s' % (pathid, runid)
cubewidth = self.cubewidth
if 'stairs' in pathid:
cubewidth = 3
measurements = localizer.Measurements.fromTrackedMeasurements(self.env.locid2location, self.sourcedir, self.device, pathid, runid)
#~ measurements = localizer.Measurements()
#~ measurements.load(r'R:\xx.txt')
t = time.time()
if STAIR_HACK and 'stairs' in pathid:
paths, measurements = self.localizer3.evaluateMeasurements(measurements, interpolate=self.interpolate)
else:
paths, measurements = self.localizer.evaluateMeasurements(measurements, interpolate=self.interpolate)
error_3d, error_2d, errors_2d, errors_3d = self.getError(measurements, paths, cubewidth)
if self.analyze:
for pname, psec in paths.items():
fname = '%s_%s_%s_%s_%s_%s' % (self.device, cubewidth, self.algo, pname, pathid, runid)
in_meter = [c2m(x, y, z, cubewidth) for x, y, z in psec]
if self.output_html:
caption = {'end': 'Offline', 'seq': 'Online', 'seq_avg': 'Online/Avg'}[pname]
text = '%s Error 3D: %.2f, %s Error 2D: %.2f' % (caption, error_3d[pname], caption, error_2d[pname])
self.drawLocalizationResult2D(in_meter, measurements, name=fname, text=text)
if self.output_html:
if 'end' in errors_2d:
# HACK: non LMSE (PF + HMM)
fname = '%s_%s_%s_end_%s_%s' % (self.device, cubewidth, self.algo, pathid, runid)
self.localizer.analyzePathSequence(self.tmpdir, paths['end'], path['seq'], measurements, errors_2d['end'], errors_2d['seq'], fname, errors_3d['end'], errors_3d['seq'])
else:
# HACK: LMSE
fname = '%s_%s_%s_end_%s_%s' % (self.device, cubewidth, self.algo, pathid, runid)
self.localizer.analyzePathSequence(self.tmpdir, paths['seq'], path['seq'], measurements, errors_2d['seq'], errors_2d['seq'], fname, errors_3d['seq'], errors_3d['seq'])
#~ print 'decoded path of length %s in %.3f sec' % (len(estimated_path), time.time() - t)
# Bah, has this become ugly
if not 'seq_avg' in error_3d:
if not 'end' in error_3d:
vals = (self.algo, pathid, runid, error_3d['seq'], error_2d['seq'], time.time() - t)
s = '%s-run %s/%s: seq-error-3d: %.1fm, seq-error-2d: %.1fm [%.2f sec]' % vals
else:
vals = (self.algo, pathid, runid, error_3d['end'], error_2d['end'], error_3d['seq'], error_2d['seq'], time.time() - t)
s = '%s-run %s/%s: error-3d: %.1fm, error-2d: %.1fm, seq-error-3d: %.1fm, seq-error-2d: %.1fm [%.2f sec]' % vals
else:
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)
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
log.info(s)
except Exception:
log.exception('error during evaluation of %s/%s' % (pathid, runid))
error_3d = defaultdict(int)
error_2d = defaultdict(int)
return error_3d, error_2d
def evalAll(self, pathid2runids, limit=None):
errors = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: (0, 0))))
failures = []
def got_result(pathid, runid, f):
error_3d, error_2d = f.result()
if len(error_2d) == 0 or len(error_3d) == 0:
failures.append((pathid, runid))
else:
for (pname, e3d) in error_3d.items():
errors[pathid][runid][pname] = (e3d, error_2d[pname])
if self.algo == 'hmm':
if self.cubewidth < 2:
max_workers = 2
else:
max_workers = 6
elif self.algo == 'pf':
max_workers = 8
elif self.algo == 'lmse':
max_workers = 8
# initialize datetime strptime as firstly using in in a thread leads to:
# http://bugs.python.org/issue7980
datetime.datetime.strptime('2000', '%Y')
t = time.time()
with futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
for pathid, runids in pathid2runids.items():
#~ if 'stairs' in pathid or ('og1_eg' in pathid and not 'right' in pathid):
#~ continue
if '95' in pathid:
continue
for runid in runids[:limit]:
f = executor.submit(functools.partial(self.evaluate, pathid, runid))
f.add_done_callback(functools.partial(got_result, pathid, runid))
log.info('evaluate all paths in %.3f secs' % (time.time() - t))
avg2d = defaultdict(list)
for pathid, runs in errors.items():
for runid, settings in runs.items():
for pname, (err3d, err2d) in settings.items():
avg2d[pname].append(err2d)
for pname, _errors in avg2d.items():
if len(avg2d[pname]) > 0:
median_e = list(sorted(_errors))[len(_errors) / 2]
mean_e = sum(_errors) / float(len(_errors))
log.debug('total mean error-%s 2d: %.2f, median error: %.2f' % (pname, mean_e, median_e))
#~ log.debug('total median error-%s 2d: %.2fm' % (pname, ))
return errors, failures
def getError(self, measurements, paths, cubewidth):
c2m = self.env.cube2meter
#~ print seq_estimated_path
assert all(len(measurements) == len(p) for p in paths.values())
errors_3d = defaultdict(list)
errors_2d = defaultdict(list)
error_3d = {}
error_2d = {}
for pname, pseq in paths.items():
for (x, y, z), m in zip(pseq, measurements):
if m.pos != (0, 0, 0):
x, y, z = c2m(x, y, z, cubewidth)
errors_3d[pname].append(((x - m.pos.x)**2 + (y - m.pos.y)**2 + (z - m.pos.z)**2)**0.5)
errors_2d[pname].append(((x - m.pos.x)**2 + (y - m.pos.y)**2)**0.5)
if self.errortype == 'mean':
error_3d[pname] = np.mean(errors_3d[pname]) #sum(errors_3d[pname]) / len(errors_3d[pname])
error_2d[pname] = np.mean(errors_2d[pname]) #sum(errors_2d[pname]) / len(errors_2d[pname])
elif self.errortype == 'median':
error_3d[pname] = np.median(errors_3d[pname]) #list(sorted(errors_3d[pname]))[len(errors_3d[pname]) / 2]
error_2d[pname] = np.median(errors_2d[pname]) #list(sorted(errors_2d[pname]))[len(errors_2d[pname]) / 2]
return error_3d, error_2d, errors_2d, errors_3d
def drawLocalizationResult2D(self, estimated_path, measurements, name='locresult2d', text=''):
zs = [p[2] for p in estimated_path]
avg_z = sum(zs) / len(zs) + 0.5
if avg_z < 0:
avg_z = -2.5
elif avg_z < 2.0:
avg_z = 1.0
else:
avg_z = 3.5
cutfile = self.env.getCutFile(avg_z)
img = Image.open(cutfile)
img = ImageOps.invert(img)
img = img.convert('RGBA')
draw = ImageDraw.Draw(img)
for i, (x1, y1, z1) in enumerate(estimated_path):
m2dx, m2dy = self.env.translateMeterToMesh2dPixel(x1, y1)
hexval = pos2rgb(i, len(estimated_path), saturation=1.0, spread=1.0)
draw.ellipse((m2dx-self.cubewidth, m2dy-self.cubewidth*1.1, m2dx+self.cubewidth*1.1, m2dy+self.cubewidth*1.1), fill=hexval)
if measurements[i].pos != (0, 0, 0):
m2dx2, m2dy2 = self.env.translateMeterToMesh2dPixel(measurements[i].pos.x, measurements[i].pos.y)
draw.line((m2dx, m2dy, m2dx2, m2dy2), fill='#FF5555')
if socket.gethostname() == 'nostromo':
font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 16)
else:
font = ImageFont.load_default()
draw.text((10, 10), text, fill='#CC1111', font=font)
img.save(self.tmpdir.joinpath('%s.png' % name))