def mkci(name, cls, project, user, rel=None, class_instance_b=None, save=True): ci = ClassInstance(user=user, project=project, class_column=cls, name=name) if save: ci.save() if rel and class_instance_b: mkcici(ci, rel, class_instance_b, project, user, save) return ci
def create_node(): """ Creates a new node. """ # TODO: Test if class and parent class instance exist # if params['classid'] not in class_map: # raise CatmaidException('Failed to select class.') classification_instance_operation.res_on_err = 'Failed to insert instance of class.' node = ClassInstance( user=request.user, name=params['objname']) node.project_id = workspace_pid node.class_column_id = params['classid'] node.save() class_name = node.class_column.class_name insert_into_log(project_id, request.user.id, "create_%s" % class_name, None, "Created %s with ID %s" % (class_name, params['id'])) # We need to connect the node to its parent, or to root if no valid parent is given. node_parent_id = params['parentid'] # TODO: Test if tis parent exists #if 0 == params['parentid']: # # Find root element # classification_instance_operation.res_on_err = 'Failed to select classification root.' # node_parent_id = ClassInstance.objects.filter( # project=workspace_pid, # class_column=class_map['classification_root'])[0].id #Relation.objects.filter(id=params['relationid']) #if params['relationname'] not in relation_map: # raise CatmaidException('Failed to select relation %s' % params['relationname']) classification_instance_operation.res_on_err = 'Failed to insert CICI-link.' cici = ClassInstanceClassInstance() cici.user = request.user cici.project_id = workspace_pid cici.relation_id = params['relationid'] cici.class_instance_a_id = node.id cici.class_instance_b_id = node_parent_id cici.save() return HttpResponse(json.dumps({'class_instance_id': node.id}))
def create_node(): # Can only create a node if the parent node is owned by the user # or the user is a superuser # Given that the parentid is 0 to signal root (but root has a non-zero id), # this implies that regular non-superusers cannot create nodes under root, # but only in their staging area. can_edit_class_instance_or_fail(request.user, params['parentid']) if params['classname'] not in class_map: raise Exception('Failed to select class.') instance_operation.res_on_err = 'Failed to insert instance of class.' node = ClassInstance( user=request.user, name=params['objname']) node.project_id = project_id node.class_column_id = class_map[params['classname']] node.save() insert_into_log(project_id, request.user.id, "create_%s" % params['classname'], None, "Created %s with ID %s" % (params['classname'], params['id'])) # We need to connect the node to its parent, or to root if no valid parent is given. node_parent_id = params['parentid'] if 0 == params['parentid']: # Find root element instance_operation.res_on_err = 'Failed to select root.' node_parent_id = ClassInstance.objects.filter( project=project_id, class_column=class_map['root'])[0].id if params['relationname'] not in relation_map: instance_operation.res_on_err = '' raise Exception('Failed to select relation %s' % params['relationname']) instance_operation.res_on_err = 'Failed to insert relation.' cici = ClassInstanceClassInstance() cici.user = request.user cici.project_id = project_id cici.relation_id = relation_map[params['relationname']] cici.class_instance_a_id = node.id cici.class_instance_b_id = node_parent_id cici.save() return HttpResponse(json.dumps({'class_instance_id': node.id}))
def lines_add(request, project_id=None): p = Project.objects.get(pk=project_id) # FIXME: for the moment, just hardcode the user ID: user = User.objects.get(pk=3) neuron = get_object_or_404(ClassInstance, pk=request.POST['neuron_id'], project=p) # There's a race condition here, if two people try to add a line # with the same name at the same time. The normal way to deal # with this would be to make the `name` column unique in the # table, but since the class_instance table isn't just for driver # lines, we can't do that. (FIXME) try: line = ClassInstance.objects.get(name=request.POST['line_name']) except ClassInstance.DoesNotExist: line = ClassInstance() line.name = request.POST['line_name'] line.project = p line.user = user line.class_column = Class.objects.get(class_name='driver_line', project=p) line.save() r = Relation.objects.get(relation_name='expresses_in', project=p) cici = ClassInstanceClassInstance() cici.class_instance_a = line cici.class_instance_b = neuron cici.relation = r cici.user = user cici.project = p cici.save() return HttpResponseRedirect( reverse('vncbrowser.views.view', kwargs={ 'neuron_id': neuron.id, 'project_id': p.id }))
def lines_add(request, project_id=None): p = Project.objects.get(pk=project_id) # FIXME: for the moment, just hardcode the user ID: user = User.objects.get(pk=3) neuron = get_object_or_404(ClassInstance, pk=request.POST['neuron_id'], project=p) # There's a race condition here, if two people try to add a line # with the same name at the same time. The normal way to deal # with this would be to make the `name` column unique in the # table, but since the class_instance table isn't just for driver # lines, we can't do that. (FIXME) try: line = ClassInstance.objects.get(name=request.POST['line_name']) except ClassInstance.DoesNotExist: line = ClassInstance() line.name=request.POST['line_name'] line.project = p line.user = user line.class_column = Class.objects.get(class_name='driver_line', project=p) line.save() r = Relation.objects.get(relation_name='expresses_in', project=p) cici = ClassInstanceClassInstance() cici.class_instance_a = line cici.class_instance_b = neuron cici.relation = r cici.user = user cici.project = p cici.save() return HttpResponseRedirect(reverse('vncbrowser.views.view', kwargs={'neuron_id':neuron.id, 'project_id':p.id}))
def _create_treenode(project_id, creator, editor, x, y, z, radius, confidence, neuron_id, parent_id, creation_time=None, neuron_name=None): relation_map = get_relation_to_id_map(project_id) class_map = get_class_to_id_map(project_id) def insert_new_treenode(parent_id=None, skeleton_id=None): """ If the parent_id is not None and the skeleton_id of the parent does not match with the skeleton.id, then the database will throw an error given that the skeleton_id, being defined as foreign key in the treenode table, will not meet the being-foreign requirement. """ new_treenode = Treenode() new_treenode.user = creator new_treenode.editor = editor new_treenode.project_id = project_id if creation_time: new_treenode.creation_time = creation_time new_treenode.location_x = float(x) new_treenode.location_y = float(y) new_treenode.location_z = float(z) new_treenode.radius = int(radius) new_treenode.skeleton_id = skeleton_id new_treenode.confidence = int(confidence) if parent_id: new_treenode.parent_id = parent_id new_treenode.save() return new_treenode def relate_neuron_to_skeleton(neuron, skeleton): return _create_relation(creator, project_id, relation_map['model_of'], skeleton, neuron) response_on_error = '' try: if -1 != int(parent_id): # A root node and parent node exist # Select the parent treenode for update to prevent race condition # updates to its skeleton ID while this node is being created. cursor = connection.cursor() cursor.execute( ''' SELECT t.skeleton_id, t.edition_time FROM treenode t WHERE t.id = %s FOR NO KEY UPDATE OF t ''', (parent_id, )) if cursor.rowcount != 1: raise ValueError('Parent treenode %s does not exist' % parent_id) parent_node = cursor.fetchone() parent_skeleton_id = parent_node[0] parent_edition_time = parent_node[1] # Raise an Exception if the user doesn't have permission to edit # the neuron the skeleton of the treenode is modeling. can_edit_skeleton_or_fail(editor, project_id, parent_skeleton_id, relation_map['model_of']) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(parent_id, parent_skeleton_id) return NewTreenode(new_treenode.id, new_treenode.edition_time, parent_skeleton_id, parent_edition_time) else: # No parent node: We must create a new root node, which needs a # skeleton and a neuron to belong to. response_on_error = 'Could not insert new treenode instance!' new_skeleton = ClassInstance() new_skeleton.user = creator new_skeleton.project_id = project_id new_skeleton.class_column_id = class_map['skeleton'] new_skeleton.name = 'skeleton' new_skeleton.save() new_skeleton.name = 'skeleton %d' % new_skeleton.id new_skeleton.save() if -1 != neuron_id: # Check that the neuron to use exists if 0 == ClassInstance.objects.filter(pk=neuron_id).count(): neuron_id = -1 if -1 != neuron_id: # Raise an Exception if the user doesn't have permission to # edit the existing neuron. can_edit_class_instance_or_fail(editor, neuron_id, 'neuron') # A neuron already exists, so we use it response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(neuron_id, new_skeleton.id) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(None, new_skeleton.id) return NewTreenode(new_treenode.id, new_treenode.edition_time, new_skeleton.id, None) else: # A neuron does not exist, therefore we put the new skeleton # into a new neuron. response_on_error = 'Failed to insert new instance of a neuron.' new_neuron = ClassInstance() new_neuron.user = creator new_neuron.project_id = project_id new_neuron.class_column_id = class_map['neuron'] if neuron_name: # Create a regular expression to find allowed patterns. The # first group is the whole {nX} part, while the second group # is X only. counting_pattern = re.compile(r"(\{n(\d+)\})") # Look for patterns, replace all {n} with {n1} to normalize. neuron_name = neuron_name.replace("{n}", "{n1}") if counting_pattern.search(neuron_name): # Find starting values for each substitution. counts = [ int(m.groups()[1]) for m in counting_pattern.finditer(neuron_name) ] # Find existing matching neurons in database. name_match = counting_pattern.sub( r"(\d+)", neuron_name) name_pattern = re.compile(name_match) matching_neurons = ClassInstance.objects.filter( project_id=project_id, class_column_id=class_map['neuron'], name__regex=name_match).order_by('name') # Increment substitution values based on existing neurons. for n in matching_neurons: for i, (count, g) in enumerate( zip(counts, name_pattern.search(n.name).groups())): if count == int(g): counts[i] = count + 1 # Substitute values. count_ind = 0 m = counting_pattern.search(neuron_name) while m: neuron_name = m.string[:m.start()] + str( counts[count_ind]) + m.string[m.end():] count_ind = count_ind + 1 m = counting_pattern.search(neuron_name) new_neuron.name = neuron_name else: new_neuron.name = 'neuron' new_neuron.save() new_neuron.name = 'neuron %d' % new_neuron.id new_neuron.save() response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(new_neuron.id, new_skeleton.id) response_on_error = 'Failed to insert instance of treenode.' new_treenode = insert_new_treenode(None, new_skeleton.id) response_on_error = 'Failed to write to logs.' new_location = (new_treenode.location_x, new_treenode.location_y, new_treenode.location_z) insert_into_log( project_id, creator.id, 'create_neuron', new_location, 'Create neuron %d and skeleton ' '%d' % (new_neuron.id, new_skeleton.id)) return NewTreenode(new_treenode.id, new_treenode.edition_time, new_skeleton.id, None) except Exception as e: import traceback raise Exception( "%s: %s %s" % (response_on_error, str(e), str(traceback.format_exc())))
def create_treenode(request, project_id=None): """ Add a new treenode to the database ---------------------------------- 1. Add new treenode for a given skeleton id. Parent should not be empty. return: new treenode id If the parent's skeleton has a single node and belongs to the 'Isolated synaptic terminals' group, then reassign ownership of the skeleton and the neuron to the user. The treenode remains property of the original user who created it. 2. Add new treenode (root) and create a new skeleton (maybe for a given neuron) return: new treenode id and skeleton id. If a neuron id is given, use that one to create the skeleton as a model of it. """ params = {} float_values = { 'x': 0, 'y': 0, 'z': 0, 'radius': 0} int_values = { 'confidence': 0, 'useneuron': -1, 'parent_id': -1} string_values = {} for p in float_values.keys(): params[p] = float(request.POST.get(p, float_values[p])) for p in int_values.keys(): params[p] = int(request.POST.get(p, int_values[p])) for p in string_values.keys(): params[p] = request.POST.get(p, string_values[p]) relation_map = get_relation_to_id_map(project_id) class_map = get_class_to_id_map(project_id) def insert_new_treenode(parent_id=None, skeleton=None): """ If the parent_id is not None and the skeleton_id of the parent does not match with the skeleton.id, then the database will throw an error given that the skeleton_id, being defined as foreign key in the treenode table, will not meet the being-foreign requirement. """ new_treenode = Treenode() new_treenode.user = request.user new_treenode.editor = request.user new_treenode.project_id = project_id new_treenode.location_x = float(params['x']) new_treenode.location_y = float(params['y']) new_treenode.location_z = float(params['z']) new_treenode.radius = int(params['radius']) new_treenode.skeleton = skeleton new_treenode.confidence = int(params['confidence']) if parent_id: new_treenode.parent_id = parent_id new_treenode.save() return new_treenode def relate_neuron_to_skeleton(neuron, skeleton): return _create_relation(request.user, project_id, relation_map['model_of'], skeleton, neuron) response_on_error = '' try: if -1 != int(params['parent_id']): # A root node and parent node exist # Raise an Exception if the user doesn't have permission to edit # the neuron the skeleton of the treenode is modeling. can_edit_treenode_or_fail(request.user, project_id, params['parent_id']) parent_treenode = Treenode.objects.get(pk=params['parent_id']) response_on_error = 'Could not insert new treenode!' skeleton = ClassInstance.objects.get(pk=parent_treenode.skeleton_id) new_treenode = insert_new_treenode(params['parent_id'], skeleton) return HttpResponse(json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': skeleton.id })) else: # No parent node: We must create a new root node, which needs a # skeleton and a neuron to belong to. response_on_error = 'Could not insert new treenode instance!' new_skeleton = ClassInstance() new_skeleton.user = request.user new_skeleton.project_id = project_id new_skeleton.class_column_id = class_map['skeleton'] new_skeleton.name = 'skeleton' new_skeleton.save() new_skeleton.name = 'skeleton %d' % new_skeleton.id new_skeleton.save() if -1 == params['useneuron']: # Check that the neuron to use exists if 0 == ClassInstance.objects.filter(pk=params['useneuron']).count(): params['useneuron'] = -1 if -1 != params['useneuron']: # Raise an Exception if the user doesn't have permission to # edit the existing neuron. can_edit_class_instance_or_fail(request.user, params['useneuron'], 'neuron') # A neuron already exists, so we use it response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(params['useneuron'], new_skeleton.id) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(None, new_skeleton) return HttpResponse(json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': new_skeleton.id, 'neuron_id': params['useneuron']})) else: # A neuron does not exist, therefore we put the new skeleton # into a new neuron. response_on_error = 'Failed to insert new instance of a neuron.' new_neuron = ClassInstance() new_neuron.user = request.user new_neuron.project_id = project_id new_neuron.class_column_id = class_map['neuron'] new_neuron.name = 'neuron' new_neuron.save() new_neuron.name = 'neuron %d' % new_neuron.id new_neuron.save() response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(new_neuron.id, new_skeleton.id) response_on_error = 'Failed to insert instance of treenode.' new_treenode = insert_new_treenode(None, new_skeleton) response_on_error = 'Failed to write to logs.' new_location = (new_treenode.location_x, new_treenode.location_y, new_treenode.location_z) insert_into_log(project_id, request.user.id, 'create_neuron', new_location, 'Create neuron %d and skeleton ' '%d' % (new_neuron.id, new_skeleton.id)) return HttpResponse(json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': new_skeleton.id, })) except Exception as e: import traceback raise Exception("%s: %s %s" % (response_on_error, str(e), str(traceback.format_exc())))
def label_update(request, project_id=None, location_id=None, ntype=None): """ location_id is the ID of a treenode or connector. ntype is either 'treenode' or 'connector'. """ labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as') p = get_object_or_404(Project, pk=project_id) # TODO will FAIL when a tag contains a coma by itself new_tags = request.POST['tags'].split(',') delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true' kwargs = { 'relation': labeled_as_relation, 'class_instance__class_column__class_name': 'label' } table = get_link_model(ntype) if 'treenode' == ntype: kwargs['treenode__id'] = location_id node = Treenode.objects.get(id=location_id) elif 'connector' == ntype: kwargs['connector__id'] = location_id node = Connector.objects.get(id=location_id) if not table: raise Http404('Unknown node type: "%s"' % (ntype, )) # Get the existing list of tags for the tree node/connector and delete any # that are not in the new list. existing_labels = table.objects.filter( **kwargs).select_related('class_instance__name') existing_names = set(ele.class_instance.name for ele in existing_labels) duplicate_labels = table.objects.filter(**kwargs).exclude( class_instance__name__in=new_tags).select_related( 'class_instance__name') other_labels = [] deleted_labels = [] if delete_existing_labels: # Iterate over all labels that should get deleted to check permission # on each one. Remember each label that couldn't be deleted in the # other_labels array. for l in duplicate_labels: try: can_edit_or_fail(request.user, l.id, table._meta.db_table) if remove_label(l.id, ntype): deleted_labels.append(l) else: other_labels.append(l) except: other_labels.append(l) # Create change requests for labels associated to the treenode by other users for label in other_labels: change_request_params = { 'type': 'Remove Tag', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'description': "Remove tag '%s'" % label.class_instance.name, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype), 'approve_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(label.id), ntype) } ChangeRequest(**change_request_params).save() # Add any new labels. label_class = Class.objects.get(project=project_id, class_name='label') kwargs = { 'user': request.user, 'project': p, 'relation': labeled_as_relation, ntype: node } new_labels = [] for tag_name in new_tags: if len(tag_name) > 0 and tag_name not in existing_names: # Make sure the tag instance exists existing_tags = tuple( ClassInstance.objects.filter(project=p, name=tag_name, class_column=label_class)) if len(existing_tags) < 1: tag = ClassInstance(project=p, name=tag_name, user=request.user, class_column=label_class) tag.save() else: tag = existing_tags[0] # Associate the tag with the treenode/connector. kwargs['class_instance'] = tag tci = table( **kwargs ) # creates new TreenodeClassInstance or ConnectorClassInstance tci.save() new_labels.append(tag_name) if node.user != request.user: # Inform the owner of the node that the tag was added and give them the option of removing it. change_request_params = { 'type': 'Add Tag', 'description': 'Added tag \'' + tag_name + '\'', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype), 'reject_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(tci.id), ntype) } ChangeRequest(**change_request_params).save() response = { 'message': 'success', 'new_labels': new_labels, 'duplicate_labels': [ l.class_instance.name for l in duplicate_labels if l not in deleted_labels ], 'deleted_labels': [l.class_instance.name for l in deleted_labels], } # Check if any labels on this node violate cardinality restrictions on # its skeleton. if 'treenode' == ntype: limited_labels = { l: SKELETON_LABEL_CARDINALITY[l] for l in new_tags if l in SKELETON_LABEL_CARDINALITY } if limited_labels: ll_names, ll_maxes = zip(*limited_labels.items()) cursor = connection.cursor() cursor.execute( """ SELECT ll.name, COUNT(tci.treenode_id), ll.max FROM class_instance ci, treenode_class_instance tci, treenode tn, unnest(%s::text[], %s::integer[]) AS ll (name, max) WHERE ci.name = ll.name AND ci.project_id = %s AND ci.class_id = %s AND tci.class_instance_id = ci.id AND tci.relation_id = %s AND tn.id = tci.treenode_id AND tn.skeleton_id = %s GROUP BY ll.name, ll.max HAVING COUNT(tci.treenode_id) > ll.max """, (list(ll_names), list(ll_maxes), p.id, label_class.id, labeled_as_relation.id, node.skeleton_id)) if cursor.rowcount: response['warning'] = 'The skeleton has too many of the following tags: ' + \ ', '.join('{0} ({1}, max. {2})'.format(*row) for row in cursor.fetchall()) return JsonResponse(response)
def _create_treenode(project_id, creator, editor, x, y, z, radius, confidence, neuron_id, parent_id, creation_time=None, neuron_name=None): relation_map = get_relation_to_id_map(project_id) class_map = get_class_to_id_map(project_id) def insert_new_treenode(parent_id=None, skeleton_id=None): """ If the parent_id is not None and the skeleton_id of the parent does not match with the skeleton.id, then the database will throw an error given that the skeleton_id, being defined as foreign key in the treenode table, will not meet the being-foreign requirement. """ new_treenode = Treenode() new_treenode.user = creator new_treenode.editor = editor new_treenode.project_id = project_id if creation_time: new_treenode.creation_time = creation_time new_treenode.location_x = float(x) new_treenode.location_y = float(y) new_treenode.location_z = float(z) new_radius = int(radius if (radius and not math.isnan(radius)) else 0) new_treenode.radius = new_radius new_treenode.skeleton_id = skeleton_id new_confidence = int(confidence if not math.isnan(confidence) and (confidence or confidence is 0) else 5) new_treenode.confidence = new_confidence if parent_id: new_treenode.parent_id = parent_id new_treenode.save() return new_treenode def relate_neuron_to_skeleton(neuron, skeleton): return _create_relation(creator, project_id, relation_map['model_of'], skeleton, neuron) response_on_error = '' try: if -1 != int(parent_id): # A root node and parent node exist # Select the parent treenode for update to prevent race condition # updates to its skeleton ID while this node is being created. cursor = connection.cursor() cursor.execute(''' SELECT t.skeleton_id, t.edition_time FROM treenode t WHERE t.id = %s FOR NO KEY UPDATE OF t ''', (parent_id,)) if cursor.rowcount != 1: raise ValueError('Parent treenode %s does not exist' % parent_id) parent_node = cursor.fetchone() parent_skeleton_id = parent_node[0] parent_edition_time = parent_node[1] # Raise an Exception if the user doesn't have permission to edit # the neuron the skeleton of the treenode is modeling. can_edit_skeleton_or_fail(editor, project_id, parent_skeleton_id, relation_map['model_of']) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(parent_id, parent_skeleton_id) return NewTreenode(new_treenode.id, new_treenode.edition_time, parent_skeleton_id, parent_edition_time) else: # No parent node: We must create a new root node, which needs a # skeleton and a neuron to belong to. response_on_error = 'Could not insert new treenode instance!' new_skeleton = ClassInstance() new_skeleton.user = creator new_skeleton.project_id = project_id new_skeleton.class_column_id = class_map['skeleton'] new_skeleton.name = 'skeleton' new_skeleton.save() new_skeleton.name = 'skeleton %d' % new_skeleton.id new_skeleton.save() if -1 != neuron_id: # Check that the neuron to use exists if 0 == ClassInstance.objects.filter(pk=neuron_id).count(): neuron_id = -1 if -1 != neuron_id: # Raise an Exception if the user doesn't have permission to # edit the existing neuron. can_edit_class_instance_or_fail(editor, neuron_id, 'neuron') # A neuron already exists, so we use it response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(neuron_id, new_skeleton.id) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(None, new_skeleton.id) return NewTreenode(new_treenode.id, new_treenode.edition_time, new_skeleton.id, None) else: # A neuron does not exist, therefore we put the new skeleton # into a new neuron. response_on_error = 'Failed to insert new instance of a neuron.' new_neuron = ClassInstance() new_neuron.user = creator new_neuron.project_id = project_id new_neuron.class_column_id = class_map['neuron'] if neuron_name: # Create a regular expression to find allowed patterns. The # first group is the whole {nX} part, while the second group # is X only. counting_pattern = re.compile(r"(\{n(\d+)\})") # Look for patterns, replace all {n} with {n1} to normalize. neuron_name = neuron_name.replace("{n}", "{n1}") if counting_pattern.search(neuron_name): # Find starting values for each substitution. counts = [int(m.groups()[1]) for m in counting_pattern.finditer(neuron_name)] # Find existing matching neurons in database. name_match = counting_pattern.sub(r"(\d+)", neuron_name) name_pattern = re.compile(name_match) matching_neurons = ClassInstance.objects.filter( project_id=project_id, class_column_id=class_map['neuron'], name__regex=name_match).order_by('name') # Increment substitution values based on existing neurons. for n in matching_neurons: for i, (count, g) in enumerate(zip(counts, name_pattern.search(n.name).groups())): if count == int(g): counts[i] = count + 1 # Substitute values. count_ind = 0 m = counting_pattern.search(neuron_name) while m: neuron_name = m.string[:m.start()] + str(counts[count_ind]) + m.string[m.end():] count_ind = count_ind + 1 m = counting_pattern.search(neuron_name) new_neuron.name = neuron_name else: new_neuron.name = 'neuron' new_neuron.save() new_neuron.name = 'neuron %d' % new_neuron.id new_neuron.save() response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(new_neuron.id, new_skeleton.id) response_on_error = 'Failed to insert instance of treenode.' new_treenode = insert_new_treenode(None, new_skeleton.id) response_on_error = 'Failed to write to logs.' new_location = (new_treenode.location_x, new_treenode.location_y, new_treenode.location_z) insert_into_log(project_id, creator.id, 'create_neuron', new_location, 'Create neuron %d and skeleton ' '%d' % (new_neuron.id, new_skeleton.id)) return NewTreenode(new_treenode.id, new_treenode.edition_time, new_skeleton.id, None) except Exception as e: import traceback raise Exception("%s: %s %s" % (response_on_error, str(e), str(traceback.format_exc())))
def label_update(request, project_id=None, location_id=None, ntype=None): """ location_id is the ID of a treenode or connector. ntype is either 'treenode' or 'connector'. """ labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as') p = get_object_or_404(Project, pk=project_id) # TODO will FAIL when a tag contains a coma by itself new_tags = request.POST['tags'].split(',') delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true' kwargs = {'relation': labeled_as_relation, 'class_instance__class_column__class_name': 'label'} table = get_link_model(ntype) if 'treenode' == ntype: kwargs['treenode__id'] = location_id node = Treenode.objects.get(id=location_id) elif 'connector' == ntype: kwargs['connector__id'] = location_id node = Connector.objects.get(id=location_id) if not table: raise Http404('Unknown node type: "%s"' % (ntype,)) # Get the existing list of tags for the tree node/connector and delete any # that are not in the new list. existingLabels = table.objects.filter(**kwargs).select_related('class_instance__name') existing_names = set(ele.class_instance.name for ele in existingLabels) labels_to_delete = table.objects.filter(**kwargs).exclude(class_instance__name__in=new_tags) if delete_existing_labels: # Iterate over all labels that should get deleted to check permission # on each one. Remember each label that couldn't be deleted in the # other_labels array. other_labels = [] deleted_labels = [] for l in labels_to_delete: try: can_edit_or_fail(request.user, l.id, table._meta.db_table) if remove_label(l.id, ntype): deleted_labels.append(l) else: other_labels.append(l) except: other_labels.append(l) # Create change requests for labels associated to the treenode by other users for label in other_labels: change_request_params = { 'type': 'Remove Tag', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'description': "Remove tag '%s'" % label.class_instance.name, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype), 'approve_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(label.id), ntype) } ChangeRequest(**change_request_params).save() # Add any new labels. label_class = Class.objects.get(project=project_id, class_name='label') kwargs = {'user': request.user, 'project': p, 'relation': labeled_as_relation, ntype: node} for tag_name in new_tags: if len(tag_name) > 0 and tag_name not in existing_names: # Make sure the tag instance exists existing_tags = tuple(ClassInstance.objects.filter( project=p, name=tag_name, class_column=label_class)) if len(existing_tags) < 1: tag = ClassInstance( project=p, name=tag_name, user=request.user, class_column=label_class) tag.save() else: tag = existing_tags[0] # Associate the tag with the treenode/connector. kwargs['class_instance'] = tag tci = table(**kwargs) # creates new TreenodeClassInstance or ConnectorClassInstance tci.save() if node.user != request.user: # Inform the owner of the node that the tag was added and give them the option of removing it. change_request_params = { 'type': 'Add Tag', 'description': 'Added tag \'' + tag_name + '\'', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype), 'reject_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(tci.id), ntype) } ChangeRequest(**change_request_params).save() return HttpResponse(json.dumps({'message': 'success'}), content_type='text/json')
def label_update(request, project_id=None, location_id=None, ntype=None): """ location_id is the ID of a treenode or connector. ntype is either 'treenode' or 'connector'. """ labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as') p = get_object_or_404(Project, pk=project_id) # TODO will FAIL when a tag contains a coma by itself new_tags = request.POST['tags'].split(',') delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true' kwargs = {'relation': labeled_as_relation, 'class_instance__class_column__class_name': 'label'} table = get_link_model(ntype) if 'treenode' == ntype: kwargs['treenode__id'] = location_id node = Treenode.objects.get(id=location_id) elif 'connector' == ntype: kwargs['connector__id'] = location_id node = Connector.objects.get(id=location_id) if not table: raise Http404('Unknown node type: "%s"' % (ntype,)) # Get the existing list of tags for the tree node/connector and delete any # that are not in the new list. existing_labels = table.objects.filter(**kwargs).select_related('class_instance') existing_names = set(ele.class_instance.name for ele in existing_labels) duplicate_labels = table.objects.filter(**kwargs).exclude(class_instance__name__in=new_tags).select_related('class_instance') other_labels = [] deleted_labels = [] if delete_existing_labels: # Iterate over all labels that should get deleted to check permission # on each one. Remember each label that couldn't be deleted in the # other_labels array. for l in duplicate_labels: try: can_edit_or_fail(request.user, l.id, table._meta.db_table) if remove_label(l.id, ntype): deleted_labels.append(l) else: other_labels.append(l) except: other_labels.append(l) # Create change requests for labels associated to the treenode by other users for label in other_labels: change_request_params = { 'type': 'Remove Tag', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'description': "Remove tag '%s'" % label.class_instance.name, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype), 'approve_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(label.id), ntype) } ChangeRequest(**change_request_params).save() # Add any new labels. label_class = Class.objects.get(project=project_id, class_name='label') kwargs = {'user': request.user, 'project': p, 'relation': labeled_as_relation, ntype: node} new_labels = [] for tag_name in new_tags: if len(tag_name) > 0 and tag_name not in existing_names: # Make sure the tag instance exists existing_tags = tuple(ClassInstance.objects.filter( project=p, name=tag_name, class_column=label_class)) if len(existing_tags) < 1: tag = ClassInstance( project=p, name=tag_name, user=request.user, class_column=label_class) tag.save() else: tag = existing_tags[0] # Associate the tag with the treenode/connector. kwargs['class_instance'] = tag tci = table(**kwargs) # creates new TreenodeClassInstance or ConnectorClassInstance tci.save() new_labels.append(tag_name) if node.user != request.user: # Inform the owner of the node that the tag was added and give them the option of removing it. change_request_params = { 'type': 'Add Tag', 'description': 'Added tag \'' + tag_name + '\'', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype), 'reject_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(tci.id), ntype) } ChangeRequest(**change_request_params).save() response = { 'message': 'success', 'new_labels': new_labels, 'duplicate_labels': [l.class_instance.name for l in duplicate_labels if l not in deleted_labels], 'deleted_labels': [l.class_instance.name for l in deleted_labels], } # Check if any labels on this node violate cardinality restrictions on # its skeleton. if 'treenode' == ntype: limited_labels = {l: SKELETON_LABEL_CARDINALITY[l] for l in new_tags if l in SKELETON_LABEL_CARDINALITY} if limited_labels: ll_names, ll_maxes = zip(*limited_labels.items()) cursor = connection.cursor() cursor.execute(""" SELECT ll.name, COUNT(tci.treenode_id), ll.max FROM class_instance ci, treenode_class_instance tci, treenode tn, unnest(%s::text[], %s::integer[]) AS ll (name, max) WHERE ci.name = ll.name AND ci.project_id = %s AND ci.class_id = %s AND tci.class_instance_id = ci.id AND tci.relation_id = %s AND tn.id = tci.treenode_id AND tn.skeleton_id = %s GROUP BY ll.name, ll.max HAVING COUNT(tci.treenode_id) > ll.max """, ( list(ll_names), list(ll_maxes), p.id, label_class.id, labeled_as_relation.id, node.skeleton_id)) if cursor.rowcount: response['warning'] = 'The skeleton has too many of the following tags: ' + \ ', '.join('{0} ({1}, max. {2})'.format(*row) for row in cursor.fetchall()) return JsonResponse(response)
def split_skeleton(request, project_id=None): """ The split is only possible if the neuron is not locked or if it is locked by the current user or if the current user belongs to the group of the user who locked it. Of course, the split is also possible if the current user is a super-user. Also, all reviews of the treenodes in the new neuron are updated to refer to the new skeleton. """ treenode_id = int(request.POST['treenode_id']) treenode = Treenode.objects.get(pk=treenode_id) skeleton_id = treenode.skeleton_id upstream_annotation_map = json.loads(request.POST.get('upstream_annotation_map')) downstream_annotation_map = json.loads(request.POST.get('downstream_annotation_map')) cursor = connection.cursor() # Check if the treenode is root! if not treenode.parent: return HttpResponse(json.dumps({'error': 'Can\'t split at the root node: it doesn\'t have a parent.'})) # Check if annotations are valid if not check_annotations_on_split(project_id, skeleton_id, frozenset(upstream_annotation_map.keys()), frozenset(downstream_annotation_map.keys())): raise Exception("Annotation distribution is not valid for splitting. " \ "One part has to keep the whole set of annotations!") skeleton = ClassInstance.objects.select_related('user').get(pk=skeleton_id) project_id=int(project_id) # retrieve neuron of this skeleton neuron = ClassInstance.objects.get( cici_via_b__relation__relation_name='model_of', cici_via_b__class_instance_a_id=skeleton_id) # Make sure the user has permissions to edit can_edit_class_instance_or_fail(request.user, neuron.id, 'neuron') # retrieve the id, parent_id of all nodes in the skeleton # with minimal ceremony cursor.execute(''' SELECT id, parent_id FROM treenode WHERE skeleton_id=%s ''' % skeleton_id) # no need to sanitize # build the networkx graph from it graph = nx.DiGraph() for row in cursor.fetchall(): graph.add_node( row[0] ) if row[1]: # edge from parent_id to id graph.add_edge( row[1], row[0] ) # find downstream nodes starting from target treenode_id # and generate the list of IDs to change, starting at treenode_id (inclusive) change_list = nx.bfs_tree(graph, treenode_id).nodes() if not change_list: # When splitting an end node, the bfs_tree doesn't return any nodes, # which is surprising, because when the splitted tree has 2 or more nodes # the node at which the split is made is included in the list. change_list.append(treenode_id) # create a new skeleton new_skeleton = ClassInstance() new_skeleton.name = 'Skeleton' new_skeleton.project_id = project_id new_skeleton.user = skeleton.user # The same user that owned the skeleton to split new_skeleton.class_column = Class.objects.get(class_name='skeleton', project_id=project_id) new_skeleton.save() new_skeleton.name = 'Skeleton {0}'.format( new_skeleton.id ) # This could be done with a trigger in the database new_skeleton.save() # Create new neuron new_neuron = ClassInstance() new_neuron.name = 'Neuron' new_neuron.project_id = project_id new_neuron.user = skeleton.user new_neuron.class_column = Class.objects.get(class_name='neuron', project_id=project_id) new_neuron.save() new_neuron.name = 'Neuron %s' % str(new_neuron.id) new_neuron.save() # Assign the skeleton to new neuron cici = ClassInstanceClassInstance() cici.class_instance_a = new_skeleton cici.class_instance_b = new_neuron cici.relation = Relation.objects.get(relation_name='model_of', project_id=project_id) cici.user = skeleton.user # The same user that owned the skeleton to split cici.project_id = project_id cici.save() # update skeleton_id of list in treenode table # This creates a lazy QuerySet that, upon calling update, returns a new QuerySet # that is then executed. It does NOT create an update SQL query for every treenode. tns = Treenode.objects.filter(id__in=change_list).update(skeleton=new_skeleton) # update the skeleton_id value of the treenode_connector table tc = TreenodeConnector.objects.filter( relation__relation_name__endswith = 'synaptic_to', treenode__in=change_list, ).update(skeleton=new_skeleton) # setting new root treenode's parent to null Treenode.objects.filter(id=treenode_id).update(parent=None, editor=request.user) # Update annotations of existing neuron to have only over set _update_neuron_annotations(project_id, request.user, neuron.id, upstream_annotation_map) # Update all reviews of the treenodes that are moved to a new neuron to # refer to the new skeleton. Review.objects.filter(treenode_id__in=change_list).update(skeleton=new_skeleton) # Update annotations of under skeleton _annotate_entities(project_id, [new_neuron.id], downstream_annotation_map) # Log the location of the node at which the split was done location = (treenode.location_x, treenode.location_y, treenode.location_z) insert_into_log(project_id, request.user.id, "split_skeleton", location, "Split skeleton with ID {0} (neuron: {1})".format( skeleton_id, neuron.name ) ) return HttpResponse(json.dumps({}), content_type='text/json')
def create_treenode(request, project_id=None): """ Add a new treenode to the database ---------------------------------- 1. Add new treenode for a given skeleton id. Parent should not be empty. return: new treenode id If the parent's skeleton has a single node and belongs to the 'Isolated synaptic terminals' group, then reassign ownership of the skeleton and the neuron to the user. The treenode remains property of the original user who created it. 2. Add new treenode (root) and create a new skeleton (maybe for a given neuron) return: new treenode id and skeleton id. If a neuron id is given, use that one to create the skeleton as a model of it. """ params = {} float_values = {'x': 0, 'y': 0, 'z': 0, 'radius': 0} int_values = {'confidence': 0, 'useneuron': -1, 'parent_id': -1} string_values = {} for p in float_values.keys(): params[p] = float(request.POST.get(p, float_values[p])) for p in int_values.keys(): params[p] = int(request.POST.get(p, int_values[p])) for p in string_values.keys(): params[p] = request.POST.get(p, string_values[p]) relation_map = get_relation_to_id_map(project_id) class_map = get_class_to_id_map(project_id) def insert_new_treenode(parent_id=None, skeleton=None): """ If the parent_id is not None and the skeleton_id of the parent does not match with the skeleton.id, then the database will throw an error given that the skeleton_id, being defined as foreign key in the treenode table, will not meet the being-foreign requirement. """ new_treenode = Treenode() new_treenode.user = request.user new_treenode.editor = request.user new_treenode.project_id = project_id new_treenode.location_x = float(params['x']) new_treenode.location_y = float(params['y']) new_treenode.location_z = float(params['z']) new_treenode.radius = int(params['radius']) new_treenode.skeleton = skeleton new_treenode.confidence = int(params['confidence']) if parent_id: new_treenode.parent_id = parent_id new_treenode.save() return new_treenode def relate_neuron_to_skeleton(neuron, skeleton): return _create_relation(request.user, project_id, relation_map['model_of'], skeleton, neuron) response_on_error = '' try: if -1 != int(params['parent_id']): # A root node and parent node exist # Raise an Exception if the user doesn't have permission to edit # the neuron the skeleton of the treenode is modeling. can_edit_treenode_or_fail(request.user, project_id, params['parent_id']) parent_treenode = Treenode.objects.get(pk=params['parent_id']) response_on_error = 'Could not insert new treenode!' skeleton = ClassInstance.objects.get( pk=parent_treenode.skeleton_id) new_treenode = insert_new_treenode(params['parent_id'], skeleton) return HttpResponse( json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': skeleton.id })) else: # No parent node: We must create a new root node, which needs a # skeleton and a neuron to belong to. response_on_error = 'Could not insert new treenode instance!' new_skeleton = ClassInstance() new_skeleton.user = request.user new_skeleton.project_id = project_id new_skeleton.class_column_id = class_map['skeleton'] new_skeleton.name = 'skeleton' new_skeleton.save() new_skeleton.name = 'skeleton %d' % new_skeleton.id new_skeleton.save() if -1 == params['useneuron']: # Check that the neuron to use exists if 0 == ClassInstance.objects.filter( pk=params['useneuron']).count(): params['useneuron'] = -1 if -1 != params['useneuron']: # Raise an Exception if the user doesn't have permission to # edit the existing neuron. can_edit_class_instance_or_fail(request.user, params['useneuron'], 'neuron') # A neuron already exists, so we use it response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(params['useneuron'], new_skeleton.id) response_on_error = 'Could not insert new treenode!' new_treenode = insert_new_treenode(None, new_skeleton) return HttpResponse( json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': new_skeleton.id, 'neuron_id': params['useneuron'] })) else: # A neuron does not exist, therefore we put the new skeleton # into a new neuron. response_on_error = 'Failed to insert new instance of a neuron.' new_neuron = ClassInstance() new_neuron.user = request.user new_neuron.project_id = project_id new_neuron.class_column_id = class_map['neuron'] new_neuron.name = 'neuron' new_neuron.save() new_neuron.name = 'neuron %d' % new_neuron.id new_neuron.save() response_on_error = 'Could not relate the neuron model to ' \ 'the new skeleton!' relate_neuron_to_skeleton(new_neuron.id, new_skeleton.id) response_on_error = 'Failed to insert instance of treenode.' new_treenode = insert_new_treenode(None, new_skeleton) response_on_error = 'Failed to write to logs.' new_location = (new_treenode.location_x, new_treenode.location_y, new_treenode.location_z) insert_into_log( project_id, request.user.id, 'create_neuron', new_location, 'Create neuron %d and skeleton ' '%d' % (new_neuron.id, new_skeleton.id)) return HttpResponse( json.dumps({ 'treenode_id': new_treenode.id, 'skeleton_id': new_skeleton.id, })) except Exception as e: import traceback raise Exception( "%s: %s %s" % (response_on_error, str(e), str(traceback.format_exc())))
def label_update(request, project_id=None, location_id=None, ntype=None): """ location_id is the ID of a treenode or connector. ntype is either 'treenode' or 'connector'. """ labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as') p = get_object_or_404(Project, pk=project_id) # TODO will FAIL when a tag contains a coma by itself new_tags = request.POST['tags'].split(',') delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true' kwargs = { 'relation': labeled_as_relation, 'class_instance__class_column__class_name': 'label' } table = get_link_model(ntype) if 'treenode' == ntype: kwargs['treenode__id'] = location_id node = Treenode.objects.get(id=location_id) elif 'connector' == ntype: kwargs['connector__id'] = location_id node = Connector.objects.get(id=location_id) if not table: raise Http404('Unknown node type: "%s"' % (ntype, )) # Get the existing list of tags for the tree node/connector and delete any # that are not in the new list. existingLabels = table.objects.filter( **kwargs).select_related('class_instance__name') existing_names = set(ele.class_instance.name for ele in existingLabels) labels_to_delete = table.objects.filter(**kwargs).exclude( class_instance__name__in=new_tags) if delete_existing_labels: # Iterate over all labels that should get deleted to check permission # on each one. Remember each label that couldn't be deleted in the # other_labels array. other_labels = [] deleted_labels = [] for l in labels_to_delete: try: can_edit_or_fail(request.user, l.id, table._meta.db_table) if remove_label(l.id, ntype): deleted_labels.append(l) else: other_labels.append(l) except: other_labels.append(l) # Create change requests for labels associated to the treenode by other users for label in other_labels: change_request_params = { 'type': 'Remove Tag', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'description': "Remove tag '%s'" % label.class_instance.name, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype), 'approve_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(label.id), ntype) } ChangeRequest(**change_request_params).save() # Add any new labels. label_class = Class.objects.get(project=project_id, class_name='label') kwargs = { 'user': request.user, 'project': p, 'relation': labeled_as_relation, ntype: node } for tag_name in new_tags: if len(tag_name) > 0 and tag_name not in existing_names: # Make sure the tag instance exists existing_tags = tuple( ClassInstance.objects.filter(project=p, name=tag_name, class_column=label_class)) if len(existing_tags) < 1: tag = ClassInstance(project=p, name=tag_name, user=request.user, class_column=label_class) tag.save() else: tag = existing_tags[0] # Associate the tag with the treenode/connector. kwargs['class_instance'] = tag tci = table( **kwargs ) # creates new TreenodeClassInstance or ConnectorClassInstance tci.save() if node.user != request.user: # Inform the owner of the node that the tag was added and give them the option of removing it. change_request_params = { 'type': 'Add Tag', 'description': 'Added tag \'' + tag_name + '\'', 'project': p, 'user': request.user, 'recipient': node.user, 'location': Double3D(node.location_x, node.location_y, node.location_z), ntype: node, 'validate_action': 'from catmaid.control.label import label_exists\n' + 'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype), 'reject_action': 'from catmaid.control.label import remove_label\n' + 'remove_label(%s, "%s")' % (str(tci.id), ntype) } ChangeRequest(**change_request_params).save() return HttpResponse(json.dumps({'message': 'success'}), content_type='application/json')