PageRenderTime 25ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/django/applications/catmaid/control/analytics.py

https://bitbucket.org/fernandoamat/catmaid_5d_visualization_annotation
Python | 265 lines | 262 code | 3 blank | 0 comment | 3 complexity | 609395596d6a013627f14f6938e5af2f MD5 | raw file
  1. from django.db import connection
  2. from django.http import HttpResponse
  3. from catmaid.control.authentication import requires_user_role
  4. from catmaid.models import UserRole
  5. from collections import namedtuple, defaultdict
  6. from itertools import chain, islice
  7. from functools import partial
  8. from networkx import Graph, single_source_shortest_path
  9. import json
  10. @requires_user_role(UserRole.Annotate)
  11. def analyze_skeletons(request, project_id=None):
  12. project_id = int(project_id)
  13. skids = [int(v) for k,v in request.POST.iteritems() if k.startswith('skeleton_ids[')]
  14. s_skids = ",".join(str(skid) for skid in skids)
  15. extra = int(request.POST.get('extra', 0))
  16. adjacents = int(request.POST.get('adjacents', 0))
  17. cursor = connection.cursor()
  18. query = '''
  19. SELECT tc2.skeleton_id
  20. FROM treenode_connector tc1,
  21. treenode_connector tc2,
  22. relation r1,
  23. relation r2
  24. WHERE tc1.skeleton_id IN (%s)
  25. AND tc1.relation_id = r1.id
  26. AND %s
  27. AND tc1.connector_id = tc2.connector_id
  28. AND tc2.relation_id = r2.id
  29. AND %s
  30. GROUP BY tc2.skeleton_id'''
  31. if 0 == extra:
  32. # Just skids
  33. pass
  34. elif 1 == extra:
  35. # Include downstream skeletons
  36. cursor.execute(query % (s_skids, "r1.relation_name = 'presynaptic_to'", "r2.relation_name = 'postsynaptic_to'"))
  37. skids.extend([s[0] for s in cursor.fetchall()])
  38. elif 2 == extra:
  39. # Include upstream skeletons
  40. cursor.execute(query % (s_skids, "r1.relation_name = 'postsynaptic_to'", "r2.relation_name = 'presynaptic_to'"))
  41. skids.extend([s[0] for s in cursor.fetchall()])
  42. elif 3 == extra:
  43. # Include both upstream and downstream skeletons
  44. cursor.execute(query % (s_skids, "(r1.relation_name = 'presynaptic_to' OR r1.relation_name = 'postsynaptic_to')", "(r2.relation_name = 'presynaptic_to' OR r2.relation_name = 'postsynaptic_to')"))
  45. skids.extend([s[0] for s in cursor.fetchall()])
  46. # Obtain neuron names
  47. cursor.execute('''
  48. SELECT cici.class_instance_a, ci.name
  49. FROM class_instance_class_instance cici,
  50. class_instance ci,
  51. relation r
  52. WHERE cici.class_instance_a IN (%s)
  53. AND cici.class_instance_b = ci.id
  54. AND cici.relation_id = r.id
  55. AND r.relation_name = 'model_of'
  56. ''' % ",".join(str(skid) for skid in skids))
  57. blob = {'issues': tuple((skid, _analyze_skeleton(project_id, skid, adjacents)) for skid in skids),
  58. 'names': dict(cursor.fetchall()),
  59. 0: "Autapse",
  60. 1: "Two or more times postsynaptic to the same connector",
  61. 2: "Connector without postsynaptic targets",
  62. 3: "Connector without presynaptic skeleton",
  63. 4: "Duplicated synapse?",
  64. 5: "End node without tag",
  65. 6: "TODO tag",
  66. 7: "End-node tag in a non-end node."}
  67. return HttpResponse(json.dumps(blob))
  68. def _analyze_skeleton(project_id, skeleton_id, adjacents):
  69. """ Takes a skeleton and returns a list of potentially problematic issues,
  70. as a list of tuples of two values: issue type and treenode ID.
  71. adjacents: the number of nodes in the paths starting at a node when checking for duplicated connectors.
  72. """
  73. project_id = int(project_id)
  74. skeleton_id = int(skeleton_id)
  75. cursor = connection.cursor()
  76. PRE = 'presynaptic_to'
  77. POST = 'postsynaptic_to'
  78. # Retrieve relation IDs vs names
  79. cursor.execute('''
  80. SELECT id, relation_name
  81. FROM relation
  82. WHERE project_id = %s
  83. AND (relation_name = '%s'
  84. OR relation_name = '%s')
  85. ''' % (project_id, PRE, POST))
  86. relations = {} # both ways
  87. for row in cursor.fetchall():
  88. relations[row[0]] = row[1]
  89. relations[row[1]] = row[0]
  90. # Transform strings to integer IDs
  91. PRE = relations[PRE]
  92. POST = relations[POST]
  93. # Retrieve all connectors and their associated pre- or postsynaptic treenodes,
  94. # plus the parent treenodes of these.
  95. cursor.execute('''
  96. SELECT tc1.connector_id,
  97. tc1.relation_id,
  98. t1.id,
  99. t1.skeleton_id,
  100. tc2.relation_id,
  101. t2.id,
  102. t2.skeleton_id
  103. FROM treenode_connector tc1,
  104. treenode_connector tc2,
  105. treenode t1,
  106. treenode t2
  107. WHERE tc1.skeleton_id = %s
  108. AND tc1.connector_id = tc2.connector_id
  109. AND tc1.treenode_id = t1.id
  110. AND tc2.treenode_id = t2.id
  111. AND (tc1.relation_id = %s OR tc1.relation_id = %s)
  112. AND (tc2.relation_id = %s OR tc2.relation_id = %s)
  113. ''' % (skeleton_id,
  114. str(PRE), str(POST),
  115. str(PRE), str(POST)))
  116. Treenode = namedtuple('Treenode', ['id', 'skeleton_id'])
  117. # Map of connector_id vs {pre: {Treenode, ...}, post: {Treenode, ...}}
  118. connectors = defaultdict(partial(defaultdict, set))
  119. # Condense rows to connectors represented by a map with two entries (PRE and POST),
  120. # each containing as value a set of Treenode:
  121. for row in cursor.fetchall():
  122. s = connectors[row[0]]
  123. s[row[1]].add(Treenode(row[2], row[3]))
  124. # The 'other' could be null
  125. if row[4]:
  126. s[row[4]].add(Treenode(row[5], row[6]))
  127. issues = []
  128. # Set of IDs of outgoing connectors
  129. pre_connector_ids = set()
  130. for connector_id, connector in connectors.iteritems():
  131. pre = connector[PRE]
  132. post = connector[POST]
  133. if pre and post:
  134. for a in pre:
  135. for b in post:
  136. if a.skeleton_id == b.skeleton_id:
  137. # Type 0: autapse
  138. issues.append((0, a.id if a.skeleton_id == skeleton_id else b.id))
  139. if not post:
  140. # Type 2: presynaptic connector without postsynaptic treenodes
  141. issues.append((2, iter(pre).next().id))
  142. if not pre:
  143. # Type 3: postsynaptic connector without presynaptic treenode
  144. issues.append((3, iter(post).next().id))
  145. else:
  146. if iter(pre).next().skeleton_id != skeleton_id:
  147. repeats = tuple(t.id for t in post if t.skeleton_id == skeleton_id)
  148. if len(repeats) > 1:
  149. # Type 1: two or more times postsynaptic to the same connector
  150. issues.append((1, repeats[0]))
  151. else:
  152. pre_connector_ids.add(connector_id)
  153. # Fetch data for type 4 and 5: all treenode, with tags if any
  154. cursor.execute('''
  155. SELECT treenode.id,
  156. treenode.parent_id,
  157. class_instance.name
  158. FROM treenode
  159. LEFT OUTER JOIN
  160. (treenode_class_instance INNER JOIN relation ON (treenode_class_instance.relation_id = relation.id AND relation.relation_name = 'labeled_as') INNER JOIN class_instance ON (treenode_class_instance.class_instance_id = class_instance.id))
  161. ON (treenode_class_instance.treenode_id = treenode.id)
  162. WHERE treenode.skeleton_id = %s
  163. ''' % skeleton_id)
  164. # Collapse repeated rows into nodes with none or more tags
  165. nodes = {}
  166. parents = set()
  167. for row in cursor.fetchall():
  168. node = nodes.get(row[0])
  169. if node:
  170. # Append tag
  171. node[1].append(row[2])
  172. else:
  173. nodes[row[0]] = (row[1], [row[2]])
  174. if row[1]:
  175. parents.add(row[0])
  176. # Type 4: potentially duplicated synapses (or triplicated, etc):
  177. # Check if two or more connectors share pre treenodes and post skeletons,
  178. # or pre skeletons and post treenodes,
  179. # considering the treenode and its parent as a group.
  180. if adjacents > 0:
  181. graph = Graph()
  182. for node_id, props in nodes.iteritems():
  183. if props[0]:
  184. # Nodes are added automatically
  185. graph.add_edge(props[0], node_id)
  186. else:
  187. graph = None
  188. Connector = namedtuple("Connector", ['id', 'treenode_id', 'treenodes', 'skeletons'])
  189. # Check if there are any duplicated presynaptic connectors
  190. pre_connectors = []
  191. for connector_id in pre_connector_ids:
  192. c = connectors[connector_id]
  193. treenode_id = iter(c[PRE]).next().id
  194. pre_treenodes = set(chain.from_iterable(single_source_shortest_path(graph, treenode_id, adjacents).values()))
  195. post_skeletons = set(t.skeleton_id for t in c[POST])
  196. pre_connectors.append(Connector(connector_id, treenode_id, pre_treenodes, post_skeletons))
  197. def issue4s(cs):
  198. for i, c1 in enumerate(cs):
  199. for c2 in islice(cs, i+1, None):
  200. if (c1.treenodes & c2.treenodes) and (c1.skeletons & c2.skeletons):
  201. # Type 4: potentially duplicated connector
  202. issues.append((4, c1.treenode_id))
  203. if c1.treenode_id != c2.treenode_id:
  204. issues.append((4, c2.treenode_id))
  205. issue4s(pre_connectors)
  206. # Check if there are any duplicated postsynaptic connectors
  207. post_connectors = []
  208. for connector_id, c in connectors.iteritems():
  209. if connector_id in pre_connector_ids:
  210. continue
  211. treenode_id = (t.id for t in c[POST] if t.skeleton_id == skeleton_id).next()
  212. pre_skeletons = set(t.skeleton_id for t in c[PRE])
  213. post_treenodes = set(chain.from_iterable(single_source_shortest_path(graph, treenode_id, adjacents).values()))
  214. post_connectors.append(Connector(connector_id, treenode_id, post_treenodes, pre_skeletons))
  215. issue4s(post_connectors)
  216. # Type 5: end node without a tag
  217. # Type 6: node with a TODO tag
  218. # Type 7: root, slab or branch node with a tag like 'ends', 'not a branch', 'uncertain end', or 'uncertain continuation'
  219. end_labels = set(['ends', 'not a branch', 'uncertain end', 'uncertain continuation'])
  220. for node_id, props in nodes.iteritems():
  221. labels = set(props[1])
  222. if node_id not in parents:
  223. if not (labels & end_labels):
  224. # Type 5: node is a leaf without an end-node label
  225. issues.append((5, node_id))
  226. elif labels & end_labels:
  227. # Type 7: node is not a leaf but has an end-node label
  228. issues.append((7, node_id))
  229. if 'TODO' in labels:
  230. # Type 6: node with a tag containing the string 'TODO'
  231. issues.append((6, node_id))
  232. return issues