Version one of Alles Wat ik Voel

This commit is contained in:
Ruben 2017-03-06 21:11:00 +01:00
commit 528f4016b0
7 changed files with 1293 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
__pycache__/
env/

89
colour.py Normal file
View File

@ -0,0 +1,89 @@
import struct
import scipy
import scipy.misc
import scipy.cluster
import codecs
# from IPython.display import Markdown, display, HTML
import colorsys
import math
import numpy as np
NUM_CLUSTERS = 64
def getColourAsHex(colour):
return '#' + ''.join(format(c, '02x') for c in colour.astype(int))
def getColoursForImageByClusters(image):
"""
Adapted on answers by
Peter Hansen (http://stackoverflow.com/a/3244061)
& Johan Mickos (http://stackoverflow.com/a/34140327)
"""
im = image.copy().resize((150, 150)) # optional, to reduce time
ar = scipy.misc.fromimage(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2])
# print( 'finding clusters')
codes, dist = scipy.cluster.vq.kmeans(ar.astype(float), NUM_CLUSTERS)
# print ('cluster centres:\n', codes)
vecs, dist = scipy.cluster.vq.vq(ar, codes) # assign codes
counts, bins = scipy.histogram(vecs, len(codes)) # count occurrences
# When only looking for single color:
# index_max = scipy.argmax(counts) # find most frequent
# peak = codes[index_max]
# colour = ''.join(chr(c) for c in peak).encode('hex')
# print( 'most frequent is %s (#%s)' % (peak, colour))
percentages = 100 * counts / sum(counts)
# print("Percentages", percentages)
# colours = [ in codes]
# print(colours)
return list(zip(codes, percentages))
def getColoursForImageByPxAvg(image):
im = image.copy().resize((8, 8))
pixels = np.concatenate(scipy.misc.fromimage(im))
# colours = ['#' + ''.join(format(c, '02x') for c in color.astype(int)) for color in pixels]
percentages = np.zeros(len(pixels)) + (100 / len(pixels))
return list(zip(pixels, percentages))
def getColoursAsHTML(colours):
return " ".join(['<span style="background:%s">%s - (%s %%)</span>' % (getColourAsHex(colour[0]), getColourAsHex(colour[0]), colour[1]) for colour in colours]);
def loadColoursFromDbImages(images):
return [i.colours for i in images]
def getSvgFromDbImages(images, elId = ""):
# sum concatenates all colour arrays
allColours = []
for c in loadColoursFromDbImages(images):
allColours += c
# box 160, because center or circle = 100 => +/- 50 => + r of colour circle (max: 10) => 160
svg = '<svg viewBox="-160 -160 320 320" xmlns="http://www.w3.org/2000/svg" id="%s">' % elId
radius = 100
for colour in allColours:
rgb, percentage = colour
rgbNorm = rgb/255
hsv = colorsys.rgb_to_hsv(rgbNorm[0], rgbNorm[1], rgbNorm[2])
# find position on circle
radians = 2 * math.pi * hsv[0]
x = math.cos(radians)
y = math.sin(radians)
# based on saturation, we move inwards/outwards
# min = 0.5, max = 1.5 (dus + 0.5)
pos = np.array([x,y]) * (0.5 + hsv[1]) * radius
# Posibilitiy: determine position based on avg(saturation, value) => dark & grey inside, shiney and colourful outside
# pos = np.array([x,y]) * (0.5 + (hsv[1]+hsv[2])/2) * radius
r = max(1,-10/percentage+10) # as r, we converge to maximum radius 10, but don't want to get smaller radi then 1
c = '<circle cx="%s" cy="%s" r="%s" style="fill:%s" />' % (pos[0], pos[1], r, getColourAsHex(rgb))
svg += c
svg += "</svg>"
return svg

BIN
images.db Normal file

Binary file not shown.

49
models.py Normal file
View File

@ -0,0 +1,49 @@
from peewee import *
from playhouse.sqlite_ext import SqliteExtDatabase
import datetime
import json
import numpy as np
def coloursToJson(colours):
colours2 = [(list(colour[0]), colour[1]) for colour in colours]
return json.dumps(colours2)
def jsonToColours(string):
data = json.loads(string)
return [(np.array(d[0]), d[1]) for d in data]
db = SqliteExtDatabase('images.db')
class ColoursField(TextField):
# db_field = 'colour'
def db_value(self, value):
return coloursToJson(value)
def python_value(self, value):
return jsonToColours(value) # convert str to UUID
class BaseModel(Model):
class Meta:
database = db
class Emotion(BaseModel):
name = CharField(unique=True)
class Group(BaseModel):
name = CharField(unique=True)
class Artwork(BaseModel):
author = CharField()
age = SmallIntegerField(index=True)
gender = FixedCharField(max_length=1) # we should not really use this one
group = ForeignKeyField(Group, related_name='artworks', index=True)
emotion = ForeignKeyField(Emotion, related_name='artworks', index=True)
created_date = DateTimeField(default=datetime.datetime.now)
filename = CharField()
colours = ColoursField() # serialised colours + percentages: [([r,g,b], percentage), ...]
def getAges():
r = Artwork.select(fn.Distinct(Artwork.age)).dicts()
return [a['age'] for a in r]

55
server.py Normal file
View File

@ -0,0 +1,55 @@
import models
import os
import tornado.ioloop
import tornado.web
import colour
models.db.connect()
curpath = os.path.dirname(os.path.abspath(__file__))
tplpath = os.path.join(curpath, "templates");
loader = tornado.template.Loader(tplpath, autoescape=None)
config = {
"port": 8881,
"conn": models.db
}
emotions = [ e for e in models.Emotion.select()]
class MainHandler(tornado.web.RequestHandler):
def get(self):
loader.reset()
html = loader.load('index.html').generate(host=self.request.host, emotions=emotions, ages=models.getAges(), groups=models.Group.select())
self.write(html)
class ColourHandler(tornado.web.RequestHandler):
def get(self):
req_group = self.get_query_argument("group", default=None)
req_age = self.get_query_argument("age", default=None)
req_emotion = self.get_query_argument("emotion", default=None)
req_elId = self.get_query_argument("elId", default="")
q = models.Artwork.select()
if req_group is not None:
group = models.Group.get(models.Group.id == req_group)
q = q.where(models.Artwork.group == group)
elif req_age is not None:
q = q.where(models.Artwork.age == req_age)
elif req_emotion is not None:
emotion = models.Emotion.get(models.Emotion.id == req_emotion)
q = q.where(models.Artwork.emotion == emotion)
images = q
svg = colour.getSvgFromDbImages(images, elId = req_elId)
self.write(svg)
if __name__ == "__main__":
print("Start server", config)
app = tornado.web.Application([
(r"/", MainHandler),
(r"/colours", ColourHandler),
])
app.listen(config['port'])
tornado.ioloop.IOLoop.current().start()

173
templates/index.html Normal file
View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
body{
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QAdwB3AHctbh3qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QMFFQkN1CxOCgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAXElEQVRo3u3YQRHAIAxFwVAT1L85gopgoNdc6D4DmZ1/y8hcFc3N+XafiCcuCQQEBAQEBAQEBAQEBAQEBATkV5BRVe0Pur3TIiAgICAgICAgICAgICAgICAgIB8dr84LFaZAYuYAAAAASUVORK5CYII=');
font-family: monospace;
font-size: 12pt;
}
body.loading #colourImage{
opacity: 0;
transition: opacity .4s;
}
#main svg{
z-index: -1;
position: absolute;
left:0;top:0;right:0;bottom: 0;
}
aside ul{
list-style: none;
}
aside ul li{
padding: 0.1em;
cursor: pointer;
}
aside ul li span{
background:#ff0;
transition: max-width 0.5s;
max-width: 0%;
display: inline-block;
overflow: visible;
}
aside ul li:hover span{
max-width: 100%;
}
aside ul li.selected span{
max-width: 100%;
}
aside#selects{
float:left;
}
aside#stats{
float:right;
}
#spinner{
display:none;
}
body.loading #spinner{
display: block;
animation: rotate 3s infinite linear;
}
/* Standard syntax */
@keyframes rotate {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
</style>
</head>
<body>
<aside id='selects'>
<h3>Toon op basis van...</h3>
<h4>groep</h4>
<ul>
{% for group in groups %}
<li data-param="group" data-id="{{group.id}}"><span>{{group.name.replace(" ", "&nbsp;")}}</span></li>
{% end %}
</ul>
<h4>leeftijd</h4>
<ul>
{% for age in ages %}
<li data-param="age" data-id="{{age}}"><span>{{age}}&nbsp;jaar</span></li>
{% end %}
</ul>
<h4>emotie</h4>
<ul>
{% for emotion in emotions %}
<li data-param="emotion" data-id="{{emotion.id}}"><span>{{emotion.name}}</span></li>
{% end %}
</ul>
<ul>
<li data-param="no" data-id="thing"><span>toon&nbsp;alles</span></li>
</ul>
<!-- <h4>Kleur???</h4> -->
</aside>
<aside id='works'>
</aside>
<aside id='stats'>
<h4>Emoties</h4>
<h4>Licht/donker</h4>
</aside>
<div id='main'>
<svg viewBox="-160 -160 320 320" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%">
<defs>
<mask id="mask1" x="0" y="0" width="100" height="100" >
<circle r='155' cx="0" cy="0" style='fill:white;' />
<circle r='50' cx="0" cy="0" style="fill:black" />
</mask>
</defs>
<circle r='155' cx="0" cy="0" style='stroke:none;fill:rgba(0,0,0,0.1);mask: url(#mask1);' />
</svg>
<svg id="spinner" version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1000 1000" style="width:5%;height:5%;top:47.5%;left:47.5%;">
<g><path d="M500,964.2c-130.9,0-253.9-51-346.5-143.5C61,728.1,10,605.1,10,474.2c0-92.7,26-182.9,75.2-261C133.1,137.3,200.7,76,280.8,35.8l41.2,82.1c-65.1,32.6-120.1,82.5-159,144.2c-40,63.4-61.1,136.6-61.1,212c0,219.5,178.6,398.1,398.1,398.1c219.5,0,398.1-178.6,398.1-398.1c0-75.3-21.1-148.6-61.1-212c-38.9-61.7-93.9-111.6-159-144.2l41.2-82.1C799.3,76,866.9,137.3,914.8,213.2c49.2,78.1,75.2,168.3,75.2,261c0,130.9-51,253.9-143.5,346.5C753.9,913.2,630.9,964.2,500,964.2z"/></g>
</svg>
<svg viewBox="-160 -160 320 320" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%" id="colourImage">
</svg>
</div>
<script type="text/javascript">
function updateProgress(evt)
{
console.log(evt);
}
function transferComplete(evt, r)
{
// console.log(evt);
var div = document.createElement('div');
div.innerHTML = evt.target.response;
document.getElementById('colourImage').innerHTML = div.children[0].innerHTML;
document.body.classList.remove('loading');
}
function transferFailed(evt)
{
console.log(evt);
}
function transferCanceled(evt)
{
console.log(evt);
}
var links = document.getElementById('selects').getElementsByTagName("li");
for(let link of links)
{
link.onclick = function() {
document.body.classList.add('loading');
// document.getElementById('colourImage').innerHTML = "";
console.log('click',this.dataset.param, this.dataset.id);
var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load", transferComplete);
oReq.addEventListener("error", transferFailed);
oReq.addEventListener("abort", transferCanceled);
oReq.open("GET", "/colours?"+this.dataset.param+"="+this.dataset.id+"&elId=colourImage");
oReq.send()
for(let unsetLink of links) {
unsetLink.classList.remove('selected');
}
this.classList.add('selected');
}.bind(link)
}
// var colourImage = document.getElementById('colourImage');
// var i = 0;
// for(let circle of colourImage.children) {
// i++;
// // console.log(circle);
// setTimeout(function() {
// this.remove();
// }.bind(circle), i * 3);
// }
</script>
</body>
</html>

923
ways_of_seeing_images.ipynb Normal file

File diff suppressed because one or more lines are too long