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 @@ -
Error: 280844
+
Error: {{solution.bestMatch.dist}}
@@ -140,6 +140,10 @@