diff --git a/ontology/ontology.json b/ontology/ontology.json new file mode 100644 index 0000000..9e4cae4 --- /dev/null +++ b/ontology/ontology.json @@ -0,0 +1,57 @@ +{ + "architecture": { + "rhythmanalysis": { + "circadian": { + "season": {}, + "time of day": {}, + "body rhythm": {}, + "weather": {} + }, + "the everyday": { + "activity": {}, + "animal": {}, + "architectural atmosphere": {}, + "architectural element": {}, + "architectural element action": {}, + "architectural element state": {}, + "bio": {}, + "building material": {}, + "building type": {}, + "camera height and angle": {}, + "camera movement": {}, + "clothing": {}, + "container": {}, + "cookware": {}, + "decor": {}, + "dramatic context": {}, + "element": {}, + "filming direction": {}, + "food and drink": {}, + "furniture": {}, + "homeware": {}, + "lived space": {}, + "location": {}, + "miscellaneous": {}, + "occupancy": {}, + "place": {}, + "scene tone": {}, + "tableware": {}, + "type": {} + } + } + }, + "cinema": { + "mise en scene": { + "dramatic mood": {}, + "character": {} + }, + "mise en cadre": { + "shoot": {}, + "shot type": {}, + "line": {}, + "shape": {}, + "space": {} + } + }, + "other": {} +} diff --git a/ontology/update.py b/ontology/update.py new file mode 100755 index 0000000..47125c6 --- /dev/null +++ b/ontology/update.py @@ -0,0 +1,68 @@ +#!/usr/bin/python3 +import json +import os +from collections import defaultdict + +def find_path(parent, root=None, path=None): + if root is None: + root = ontology + if path == None: + path = [] + for key in root: + if key == parent: + return path + [key] + elif root[key]: + r = find_path(parent, root[key], path + [key]) + if r: + return r + +def get_node(name, children): + node = { + "size": len(children) + 100, + "name": name, + "children": [get_node(child, children[child]) for child in children] + } + if not node['children']: + del node['children'] + return node + + +if __name__ == '__main__': + base = os.path.abspath(os.path.dirname(__file__)) + os.chdir(base) + + keywords = json.load(open('keywords.json')) + ontology = json.load(open('ontology.json')) + + tree = defaultdict(dict) + + for keyword in keywords: + if ': ' not in keyword: + parent = 'other' + child = keyword + else: + parent, child = keyword.split(': ') + path = find_path(parent) + if path: + p = tree + for part in path: + if part not in p: + p[part] = {} + p = p[part] + p[child] = {} + else: + print('missing root - %s: %s' % (parent, child)) + + #print(json.dumps(tree, indent=4, sort_keys=True)) + sized_ontology = { + "size": len(tree), + "name": "CineMuseSpace", + "children": [] + } + for name in tree: + children = tree[name] + child = get_node(name, tree[name]) + sized_ontology['children'].append(child) + + with open('../static/ontology/sized_ontology.json', 'w') as fd: + json.dump(sized_ontology, fd, indent=4, sort_keys=True) diff --git a/ontology/update_keywords.py b/ontology/update_keywords.py new file mode 100755 index 0000000..f0a5b07 --- /dev/null +++ b/ontology/update_keywords.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 + +import getpass +import json +import sys + +import ox +import ox.web.auth + +site = 'pandora.cinemusespace.com' +api = ox.API('https://%s/api/' % site) +update = False +try: + credentials = ox.web.auth.get(site) +except: + credentials = {} + print('Please provide your username and password for %s:' % site) + credentials['username'] = input('Username: ') + credentials['password'] = getpass.getpass('Password: ') + update = True +r = api.signin(**credentials) +if 'errors' in r.get('data', {}): + for kv in r['data']['errors'].items(): + print('%s: %s' % kv) + sys.exit(1) +if update: + ox.web.auth.update(site, credentials) + +keywords = set() +for annotation in api.findAnnotations({ + 'query': { + 'conditions': [{ + 'key': 'layer', + 'value': 'keywords', + 'operator': '==' + }], + 'operator': '&' + }, + 'keys': ['id', 'in', 'out', 'value', 'user', 'created'], + 'range': [0, 500000] +})['data']['items']: + keywords.add(annotation['value']) + + +with open('keywords.json', 'w') as fd: + json.dump(list(sorted(keywords)), fd, indent=4) diff --git a/static/ontology/index.css b/static/ontology/index.css new file mode 100644 index 0000000..e821750 --- /dev/null +++ b/static/ontology/index.css @@ -0,0 +1,16 @@ +.node circle { + fill: #fff; + stroke: steelblue; + stroke-width: 1.5px; +} + +.node { + cursor: pointer; + font: 10px sans-serif; +} + +.link { + fill: none; + stroke: #ccc; + stroke-width: 1.5px; +} diff --git a/static/ontology/index.html b/static/ontology/index.html new file mode 100644 index 0000000..b2e3f28 --- /dev/null +++ b/static/ontology/index.html @@ -0,0 +1,12 @@ + + + + + + + Ontology Tree + + + + + diff --git a/static/ontology/index.js b/static/ontology/index.js new file mode 100644 index 0000000..5ee5a9d --- /dev/null +++ b/static/ontology/index.js @@ -0,0 +1,142 @@ +var margin = {top: 20, right: 120, bottom: 20, left: 120}, + width = 1200 - margin.right - margin.left, + height = 4000 - margin.top - margin.bottom; + +var i = 0, + duration = 750, + root; + +var tree = d3.layout.tree() + .size([height, width]); + +var diagonal = d3.svg.diagonal() + .projection(function(d) { return [d.y, d.x]; }); + +var svg = d3.select("body").append("svg") + .attr("width", width + margin.right + margin.left) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + +d3.json("sized_ontology.json", function(error, flare) { + root = flare; + root.x0 = height / 2; + root.y0 = 0; + + function collapse(d) { + if (d.children) { + if (!d.children[0].children) { + d._children = d.children; + d._children.forEach(collapse); + d.children = null; + } else { + d.children.forEach(collapse); + } + } + } + + root.children.forEach(collapse); + + update(root); +}); + +d3.select(self.frameElement).style("height", "800px"); + +function update(source) { + + // Compute the new tree layout. + var nodes = tree.nodes(root).reverse(), + links = tree.links(nodes); + + // Normalize for fixed-depth. + nodes.forEach(function(d) { d.y = d.depth * 180; }); + + // Update the nodes… + var node = svg.selectAll("g.node") + .data(nodes, function(d) { return d.id || (d.id = ++i); }); + + // Enter any new nodes at the parent's previous position. + var nodeEnter = node.enter().append("g") + .attr("class", "node") + .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) + .on("click", click); + + nodeEnter.append("circle") + .attr("r", 1e-6) + .style("fill", function(d) { return d.children || d._children ? "#fff": "lightsteelblue"; }); + + nodeEnter.append("text") + .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) + .attr("dy", ".35em") + .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) + .text(function(d) { return d.name; }) + .style("fill-opacity", 1e-6); + + // Transition nodes to their new position. + var nodeUpdate = node.transition() + .duration(duration) + .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); + + nodeUpdate.select("circle") + .attr("r", 4.5) + .style("fill", function(d) { return d.children || d._children ? "#fff": "lightsteelblue"; }); + + nodeUpdate.select("text") + .style("fill-opacity", 1); + + // Transition exiting nodes to the parent's new position. + var nodeExit = node.exit().transition() + .duration(duration) + .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) + .remove(); + + nodeExit.select("circle") + .attr("r", 1e-6); + + nodeExit.select("text") + .style("fill-opacity", 1e-6); + + // Update the links… + var link = svg.selectAll("path.link") + .data(links, function(d) { return d.target.id; }); + + // Enter any new links at the parent's previous position. + link.enter().insert("path", "g") + .attr("class", "link") + .attr("d", function(d) { + var o = {x: source.x0, y: source.y0}; + return diagonal({source: o, target: o}); + }); + + // Transition links to their new position. + link.transition() + .duration(duration) + .attr("d", diagonal); + + // Transition exiting nodes to the parent's new position. + link.exit().transition() + .duration(duration) + .attr("d", function(d) { + var o = {x: source.x, y: source.y}; + return diagonal({source: o, target: o}); + }) + .remove(); + + // Stash the old positions for transition. + nodes.forEach(function(d) { + d.x0 = d.x; + d.y0 = d.y; + }); +} + +// Toggle children on click. +function click(d) { + if (d.children) { + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + d._children = null; + } + update(d); +}