diff --git a/.boring b/.boring
--- a/.boring
+++ b/.boring
@@ -153,3 +153,4 @@
# temporary!
rgbled/build-nano328/
+^show/dance..../capture
diff --git a/bin/paintserver b/bin/paintserver
--- a/bin/paintserver
+++ b/bin/paintserver
@@ -20,23 +20,26 @@ from light9.namespaces import RDF, L9, D
class Solve(PrettyErrorHandler, cyclone.web.RequestHandler):
def post(self):
painting = json.loads(self.request.body)
- reload(light9.paint.solve)
- solver = light9.paint.solve.Solver(self.settings.graph)
- solver.loadSamples()
with self.settings.stats.solve.time():
- img = solver.draw(painting)
- sample = solver.bestMatch(img)
+ img = self.settings.solver.draw(painting)
+ sample, sampleDist = self.settings.solver.bestMatch(img)
with self.settings.graph.currentState() as g:
- bestPath = 'show/dance2017/cam/test/%s' % g.value(sample, L9['path'])
+ bestPath = g.value(sample, L9['imagePath']).replace(L9[''], '')
#out = solver.solve(painting)
#layers = solver.simulationLayers(out)
self.write(json.dumps({
- 'bestMatch': {'uri': sample, 'path': bestPath},
+ 'bestMatch': {'uri': sample, 'path': bestPath, 'dist': sampleDist},
# 'layers': layers,
# 'out': out,
}))
+ def reloadSolver(self):
+ reload(light9.paint.solve)
+ self.settings.solver = light9.paint.solve.Solver(self.settings.graph)
+ self.settings.solver.loadSamples()
+
+
class App(object):
def __init__(self, show, session):
self.show = show
@@ -44,17 +47,22 @@ class App(object):
self.graph = SyncedGraph(networking.rdfdb.url, "paintServer")
self.graph.initiallySynced.addCallback(self.launch)
+
+ self.stats = scales.collection('/', scales.PmfStat('solve'),
+ )
+
+ def launch(self, *args):
- self.stats = scales.collection('/',
- scales.PmfStat('solve'),
- )
- def launch(self, *args):
+ self.solver = light9.paint.solve.Solver(self.graph)
+ self.solver.loadSamples()
+
self.cycloneApp = cyclone.web.Application(handlers=[
(r'/stats', StatsForCyclone),
(r'/solve', Solve),
],
debug=True,
graph=self.graph,
+ solver=self.solver,
stats=self.stats)
reactor.listenTCP(networking.paintServer.port, self.cycloneApp)
log.info("listening on %s" % networking.paintServer.port)
diff --git a/light9/paint/capture.py b/light9/paint/capture.py
--- a/light9/paint/capture.py
+++ b/light9/paint/capture.py
@@ -15,6 +15,7 @@ def writeCaptureDescription(graph, ctx,
(uri, RDF.type, L9['LightSample'], ctx),
(uri, L9['imagePath'], URIRef('/'.join([showconfig.showUri(), relOutPath])), ctx),
]))
+ graph.suggestPrefixes('cap', URIRef(uri.rsplit('/', 1)[0] + '/'))
class CaptureLoader(object):
def __init__(self, graph):
diff --git a/light9/paint/solve.py b/light9/paint/solve.py
--- a/light9/paint/solve.py
+++ b/light9/paint/solve.py
@@ -47,12 +47,30 @@ def colorRatio(col1, col2):
def brightest(img):
return numpy.amax(img, axis=(0, 1))
+
+class ImageDist(object):
+ def __init__(self, img1):
+ self.a = img1.reshape((-1,))
+ self.d = 255 * 255 * self.a.shape[0]
+ def distanceTo(self, img2):
+ b = img2.reshape((-1,))
+ return 1 - numpy.dot(self.a, b) / self.d
+class ImageDistAbs(object):
+ def __init__(self, img1):
+ self.a = img1
+ self.maxDist = img1.shape[0] * img1.shape[1] * img1.shape[2] * 255
+
+ def distanceTo(self, img2):
+ return numpy.sum(numpy.absolute(self.a - img2), axis=None) / self.maxDist
+
+
class Solver(object):
- def __init__(self, graph):
+ def __init__(self, graph, imgSize=(100, 75)):
self.graph = graph
- self.samples = {} # uri: Image array
+ self.imgSize = imgSize
+ self.samples = {} # uri: Image array (float 0-255)
self.fromPath = {} # imagePath: image array
self.blurredSamples = {}
self.sampleSettings = {} # (uri, path): DeviceSettings
@@ -63,17 +81,18 @@ class Solver(object):
with self.graph.currentState() as g:
for samp in g.subjects(RDF.type, L9['LightSample']):
pathUri = g.value(samp, L9['imagePath'])
- self.samples[samp] = self.fromPath[pathUri] = loadNumpy(pathUri.replace(L9[''], ''))
+ self.samples[samp] = self.fromPath[pathUri] = loadNumpy(pathUri.replace(L9[''], '')).astype(float)
self.blurredSamples[samp] = self._blur(self.samples[samp])
key = (samp, pathUri)
self.sampleSettings[key] = DeviceSettings.fromResource(self.graph, samp)
-
+ log.info('loaded %s samples', len(self.samples))
+
def _blur(self, img):
return scipy.ndimage.gaussian_filter(img, 10, 0, mode='nearest')
def draw(self, painting):
- return self._draw(painting, 100, 48)
+ return self._draw(painting, self.imgSize[0], self.imgSize[1])
def _draw(self, painting, w, h):
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
@@ -92,24 +111,25 @@ class Solver(object):
ctx.set_source_rgb(r / 255, g / 255, b / 255)
ctx.stroke()
- surface.write_to_png('/tmp/surf.png')
+ #surface.write_to_png('/tmp/surf.png')
return numpyFromCairo(surface)
-
- def _imgDist(self, a, b):
- return numpy.sum(numpy.absolute(a - b), axis=None)
-
def bestMatch(self, img):
"""the one sample that best matches this image"""
+ #img = self._blur(img)
results = []
- for uri, img2 in self.samples.iteritems():
- results.append((self._imgDist(img, img2), uri, img2))
+ dist = ImageDist(img)
+ for uri, img2 in sorted(self.samples.items()):
+ if img.shape != img2.shape:
+ continue
+ results.append((dist.distanceTo(img2), uri, img2))
results.sort()
- log.info('results:')
- for d,u,i in results:
- log.info('%s %g', u, d)
- saveNumpy('/tmp/bestsamp.png', results[0][2])
- return results[0][1]
+ topDist, topUri, topImg = results[0]
+
+ #saveNumpy('/tmp/best_in.png', img)
+ #saveNumpy('/tmp/best_out.png', topImg)
+ #saveNumpy('/tmp/mult.png', topImg / 255 * img)
+ return topUri, topDist
def solve(self, painting):
"""
diff --git a/light9/paint/solve_test.py b/light9/paint/solve_test.py
--- a/light9/paint/solve_test.py
+++ b/light9/paint/solve_test.py
@@ -10,7 +10,7 @@ class TestSolve(unittest.TestCase):
def setUp(self):
self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
'show/dance2017/cam/test/bg.n3'])
- self.solver = solve.Solver(self.graph)
+ self.solver = solve.Solver(self.graph, imgSize=(100, 48))
self.solver.loadSamples()
self.solveMethod = self.solver.solve
@@ -39,7 +39,7 @@ class TestSimulationLayers(unittest.Test
def setUp(self):
self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
'show/dance2017/cam/test/bg.n3'])
- self.solver = solve.Solver(self.graph)
+ self.solver = solve.Solver(self.graph, imgSize=(100, 48))
self.solver.loadSamples()
def testBlack(self):
@@ -79,7 +79,7 @@ class TestCombineImages(unittest.TestCas
def setUp(self):
graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
'show/dance2017/cam/test/bg.n3'])
- self.solver = solve.Solver(graph)
+ self.solver = solve.Solver(graph, imgSize=(100, 48))
self.solver.loadSamples()
def test(self):
out = self.solver.combineImages(layers=[
@@ -94,12 +94,13 @@ class TestBestMatch(unittest.TestCase):
def setUp(self):
graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
'show/dance2017/cam/test/bg.n3'])
- self.solver = solve.Solver(graph)
+ self.solver = solve.Solver(graph, imgSize=(100, 48))
self.solver.loadSamples()
def testRightSide(self):
drawingOnRight = {"strokes":[{"pts":[[0.875,0.64],[0.854,0.644]],
"color":"#aaaaaa"}]}
drawImg = self.solver.draw(drawingOnRight)
- match = self.solver.bestMatch(drawImg)
+ match, dist = self.solver.bestMatch(drawImg)
self.assertEqual(L9['sample5'], match)
+ self.assertAlmostEqual(0.06678758, dist)
diff --git a/light9/rdfdb/currentstategraphapi.py b/light9/rdfdb/currentstategraphapi.py
--- a/light9/rdfdb/currentstategraphapi.py
+++ b/light9/rdfdb/currentstategraphapi.py
@@ -14,6 +14,9 @@ class ReadOnlyConjunctiveGraph(object):
return getattr(self.graph, attr)
raise TypeError("can't access %r of read-only graph" % attr)
+ def __len__(self):
+ return len(self.graph)
+
class CurrentStateGraphApi(object):
"""
diff --git a/light9/web/paint/paint-report-elements.html b/light9/web/paint/paint-report-elements.html
--- a/light9/web/paint/paint-report-elements.html
+++ b/light9/web/paint/paint-report-elements.html
@@ -22,7 +22,7 @@