def test_is_collinear_big_numbers(self): from catmaid.util import Point3D, is_collinear p1 = Point3D(-299924.0, 505016.0, 40.0) p2 = Point3D(-319905.0, 505904.0, 40.0) p3 = Point3D(-309915.0, 505460.0, 40.0) self.assertTrue(is_collinear(p1, p2, p3)) self.assertTrue(is_collinear(p1, p2, p3, True))
def test_is_collinear_diagonal(self): from catmaid.util import Point3D, is_collinear p1 = Point3D(-1.0, 1.0, 1.0) p2 = Point3D(1.0, 2.0, 3.0) p3 = Point3D(-2.0, 0.5, 0.0) self.assertTrue(is_collinear(p1, p2, p3)) self.assertFalse(is_collinear(p1, p2, p3, True))
def test_is_collinear_on_z_axis(self): from catmaid.util import Point3D, is_collinear p1 = Point3D(0.0, 0.0, 0.0) p2 = Point3D(0.0, 0.0, 1.0) p3 = Point3D(0.0, 0.0, 1.5) self.assertTrue(is_collinear(p1, p2, p3)) self.assertFalse(is_collinear(p1, p2, p3, True))
def test_is_collinear_precision(self): from catmaid.util import Point3D, is_collinear p1 = Point3D(0.0, 0.0, 0.0) p2 = Point3D(1.0, 0.001, 0.0) p3 = Point3D(1.5, 0.0, 0.0) self.assertTrue(is_collinear(p1, p2, p3)) self.assertFalse(is_collinear(p1, p2, p3, True))
def link_added_node(data, new_nodes): # The target node has to be a new node n = new_nodes[data[0]] # Find child and parent of new treenode. Check first if they have been # created as newly as well. child = new_nodes.get(data[1]) if not child: child = Treenode.objects.get(pk=data[1]) parent = new_nodes.get(data[2]) if not parent: parent = Treenode.objects.get(pk=data[2]) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') x, y, z = n.location_x, n.location_y, n.location_z new_node_loc = Point3D(x, y, z) child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) if not is_collinear(child_loc, parent_loc, new_node_loc, True, epsilon): raise ValueError( 'New node location has to be collinear with child ' + f'and parent. Child: {child_loc}, New Node: {new_node_loc}, Parent: {parent_loc}' ) # Tag new treenode with SAMPLER_CREATED_CLASS label, _ = ClassInstance.objects.get_or_create( project_id=project_id, name=SAMPLER_CREATED_CLASS, class_column=label_class, defaults={'user': request.user}) TreenodeClassInstance.objects.create(project_id=project_id, user=request.user, relation=labeled_as, treenode=n, class_instance=label) # Update child node. Reviews don't need to be updated, because they are # only reset of a node's location changes. child.parent_id = n.id child.save()
def link_added_node(data, new_nodes): # The target node has to be a new node n = new_nodes[data[0]] # Find child and parent of new treenode. Check first if they have been # created as newly as well. child = new_nodes.get(data[1]) if not child: child = Treenode.objects.get(pk=data[1]) parent = new_nodes.get(data[2]) if not parent: parent = Treenode.objects.get(pk=data[2]) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') x, y, z = n.location_x, n.location_y, n.location_z new_node_loc = Point3D(x, y, z) child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) if not is_collinear(child_loc, parent_loc, new_node_loc, True, epsilon): raise ValueError('New node location has to be collinear with child and parent') # Tag new treenode with SAMPLER_CREATED_CLASS label, _ = ClassInstance.objects.get_or_create(project_id=project_id, name=SAMPLER_CREATED_CLASS, class_column=label_class, defaults={ 'user': request.user }) TreenodeClassInstance.objects.create(project_id=project_id, user=request.user, relation=labeled_as, treenode=n, class_instance=label) # Update child node. Reviews don't need to be updated, because they are # only reset of a node's location changes. child.parent_id = n.id child.save()
def insert_treenode(request, project_id=None): """ Create a new treenode between two existing nodes. Its creator and creation_date information will be set to information of child node. No node will be created, if the node on the edge between the given child and parent node. """ # Use creation time, if part of parameter set params = {} float_values = {'x': 0, 'y': 0, 'z': 0, 'radius': 0} int_values = {'confidence': 0, 'parent_id': -1, 'child_id': -1} 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])) # If siblings should be taken over, all children of the parent node will be # come children of the inserted node. This requires extra state # information: the child state for the paren. takeover_child_ids = get_request_list(request.POST, 'takeover_child_ids', None, int) # Get optional initial links to connectors, expect each entry to be a list # of connector ID and relation ID. try: links = get_request_list(request.POST, 'links', [], int) except Exception as e: raise ValueError("Couldn't parse list parameter: {}".format(e)) # Make sure the back-end is in the expected state if the node should have a # parent and will therefore become part of another skeleton. parent_id = params.get('parent_id') child_id = params.get('child_id') if parent_id not in (-1, None): s = request.POST.get('state') # Testing egular edge insertion is assumed if a child ID is provided partial_child_checks = [] if child_id in (-1, None) else [child_id] if takeover_child_ids: partial_child_checks.extend(takeover_child_ids) state.validate_state(parent_id, s, node=True, children=partial_child_checks or False, lock=True), # Find child and parent of new treenode child = Treenode.objects.get(pk=params['child_id']) parent = Treenode.objects.get(pk=params['parent_id']) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') # Make sure the requested location for the new node is on the edge between # both existing nodes if the user has no edit permissions on the neuron. try: can_edit_treenode_or_fail(request.user, project_id, parent.id) user, time = request.user, None except: child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) new_node_loc = Point3D(params['x'], params['y'], params['z']) if not is_collinear(child_loc, parent_loc, new_node_loc, True, 0.001): raise ValueError( 'New node location has to be between child and parent') # Use creator and creation time for neighboring node that was created last. if child.creation_time < parent.creation_time: user, time = parent.user, parent.creation_time else: user, time = child.user, child.creation_time # Create new treenode new_treenode = _create_treenode(project_id, user, request.user, params['x'], params['y'], params['z'], params['radius'], params['confidence'], -1, params['parent_id'], time) # Update parent of child to new treenode, do this in raw SQL to also get the # updated edition time Update also takeover children cursor = connection.cursor() params = [new_treenode.treenode_id, child.id] if takeover_child_ids: params.extend(takeover_child_ids) child_template = ",".join(("%s", ) * (len(takeover_child_ids) + 1)) else: child_template = "%s" cursor.execute( """ UPDATE treenode SET parent_id = %s WHERE id IN ({}) RETURNING id, edition_time """.format(child_template), params) result = cursor.fetchall() if not result or (len(params) - 1) != len(result): raise ValueError("Couldn't update parent of inserted node's child: " + child.id) child_edition_times = [[k, v] for k, v in result] # Create all initial links if links: created_links = create_connector_link(project_id, request.user.id, new_treenode.treenode_id, new_treenode.skeleton_id, links) else: created_links = [] return JsonResponse({ 'treenode_id': new_treenode.treenode_id, 'skeleton_id': new_treenode.skeleton_id, 'edition_time': new_treenode.edition_time, 'parent_edition_time': new_treenode.parent_edition_time, 'child_edition_times': child_edition_times, 'created_links': created_links })
def insert_treenode(request, project_id=None): """ Create a new treenode between two existing nodes. Its creator and creation_date information will be set to information of child node. No node will be created, if the node on the edge between the given child and parent node. """ # Use creation time, if part of parameter set params = {} float_values = { 'x': 0, 'y': 0, 'z': 0, 'radius': 0 } int_values = { 'confidence': 0, 'parent_id': -1, 'child_id': -1 } 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])) # If siblings should be taken over, all children of the parent node will be # come children of the inserted node. This requires extra state # information: the child state for the paren. takeover_child_ids = get_request_list(request.POST, 'takeover_child_ids', None, int) # Get optional initial links to connectors, expect each entry to be a list # of connector ID and relation ID. try: links = get_request_list(request.POST, 'links', [], int) except Exception as e: raise ValueError("Couldn't parse list parameter: {}".format(e)) # Make sure the back-end is in the expected state if the node should have a # parent and will therefore become part of another skeleton. parent_id = params.get('parent_id') child_id = params.get('child_id') if parent_id not in (-1, None): s = request.POST.get('state') # Testing egular edge insertion is assumed if a child ID is provided partial_child_checks = [] if child_id in (-1, None) else [child_id] if takeover_child_ids: partial_child_checks.extend(takeover_child_ids) state.validate_state(parent_id, s, node=True, children=partial_child_checks or False, lock=True), # Find child and parent of new treenode child = Treenode.objects.get(pk=params['child_id']) parent = Treenode.objects.get(pk=params['parent_id']) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') # Make sure the requested location for the new node is on the edge between # both existing nodes if the user has no edit permissions on the neuron. try: can_edit_treenode_or_fail(request.user, project_id, parent.id) user, time = request.user, None except: child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) new_node_loc = Point3D(params['x'], params['y'], params['z']) if not is_collinear(child_loc, parent_loc, new_node_loc, True, 0.001): raise ValueError('New node location has to be between child and parent') # Use creator and creation time for neighboring node that was created last. if child.creation_time < parent.creation_time: user, time = parent.user, parent.creation_time else: user, time = child.user, child.creation_time # Create new treenode new_treenode = _create_treenode(project_id, user, request.user, params['x'], params['y'], params['z'], params['radius'], params['confidence'], -1, params['parent_id'], time) # Update parent of child to new treenode, do this in raw SQL to also get the # updated edition time Update also takeover children cursor = connection.cursor() params = [new_treenode.treenode_id, child.id] if takeover_child_ids: params.extend(takeover_child_ids) child_template = ",".join(("%s",) * (len(takeover_child_ids) + 1)) else: child_template = "%s" cursor.execute(""" UPDATE treenode SET parent_id = %s WHERE id IN ({}) RETURNING id, edition_time """.format(child_template), params) result = cursor.fetchall() if not result or (len(params) - 1) != len(result): raise ValueError("Couldn't update parent of inserted node's child: " + child.id) child_edition_times = [[k,v] for k,v in result] # Create all initial links if links: created_links = create_connector_link(project_id, request.user.id, new_treenode.treenode_id, new_treenode.skeleton_id, links) else: created_links = [] return JsonResponse({ 'treenode_id': new_treenode.treenode_id, 'skeleton_id': new_treenode.skeleton_id, 'edition_time': new_treenode.edition_time, 'parent_edition_time': new_treenode.parent_edition_time, 'child_edition_times': child_edition_times, 'created_links': created_links })
def delete_sampler(request, project_id, sampler_id): """Delete a sampler if permissions allow it. If the sampler was created with allowing the creation of new boundary nodes, these nodes are removed by default if they have not been modified since their insertion. This can optionally be disabled using the <delete_created_nodes> parameter. --- parameters: - name: delete_created_nodes description: | Optional flag to disable automatic removal of untouched nodes created for this sampler's intervals. type: boolean default: true paramType: form required: false """ can_edit_or_fail(request.user, sampler_id, "catmaid_sampler") sampler = Sampler.objects.get(id=sampler_id) n_deleted_nodes = 0 delete_created_nodes = get_request_bool(request.POST, 'delete_created_nodes', True) if delete_created_nodes and sampler.create_interval_boundaries: labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as') label_class = Class.objects.get(project=project_id, class_name='label') label_class_instance = ClassInstance.objects.get(project=project_id, class_column=label_class, name=SAMPLER_CREATED_CLASS) # If the sampler was parameterized to created interval boundary nodes, # these nodes can now be removed if they are still collinear with their # child and parent node and have not been touched. These nodes are all # nodes that are referenced by intervals of this sampler that have the # SAMPLER_CREATED_CLASS tag with their creation time being the same as the # edition time. Such nodes can only be sampler interval start/end nodes. params = { 'project_id': project_id, 'sampler_id': sampler_id, 'labeled_as_rel': labeled_as_relation.id, 'label_class': label_class.id, 'label_class_instance': label_class_instance.id } cursor = connection.cursor() # Get all created sampler interval boundary treenodes that have been # created during sampler creation. The result will also contain parent # and child locations. We need to set extra_float_digits to get enough # precision for the location data to do a collinearity test. cursor.execute(""" SET extra_float_digits = 3; WITH sampler_treenode AS ( -- Get all treenodes linked to intervals of this sampler. Only -- select those nodes that are referenced by no other sampler -- (using an anti join). SELECT DISTINCT all_added_nodes.id FROM ( SELECT DISTINCT UNNEST(ARRAY[i.start_node_id, i.end_node_id]) AS id FROM catmaid_samplerinterval i JOIN catmaid_samplerdomain d ON i.domain_id = d.id WHERE d.sampler_id = %(sampler_id)s ) all_added_nodes JOIN catmaid_samplerinterval csi ON csi.start_node_id = all_added_nodes.id OR csi.end_node_id = all_added_nodes.id JOIN catmaid_samplerdomain csd ON csd.id = csi.domain_id GROUP BY all_added_nodes.id HAVING COUNT(DISTINCT csd.sampler_id) = 1 ), sampler_created_treenode AS ( -- Find all treenodes that were created by the sampler and are -- undmodified. SELECT st.id FROM sampler_treenode st JOIN treenode_class_instance tci ON st.id = tci.treenode_id WHERE tci.relation_id = %(labeled_as_rel)s AND tci.class_instance_id = %(label_class_instance)s ) SELECT t.id, t.location_x, t.location_y, t.location_z, c.id, c.location_x, c.location_y, c.location_z, p.id, p.location_x, p.location_y, p.location_z FROM ( -- Make sure we look only at nodes that don't have multiple nodes. SELECT st.id FROM treenode tt JOIN sampler_created_treenode st ON tt.parent_id = st.id GROUP BY st.id HAVING count(*) = 1 ) non_branch_treenodes(id) JOIN treenode t ON t.id = non_branch_treenodes.id JOIN treenode p ON p.id = t.parent_id JOIN treenode c ON c.parent_id = t.id WHERE t.project_id = %(project_id)s; """, params) created_treenodes = [r for r in cursor.fetchall()] if created_treenodes: added_node_index = dict((n[0], n) for n in created_treenodes) # Find those created treenodes that are collinear with their parent and # child node. If they are, remove those nodes. Ideally, we would move # the collinearity test into SQL as well. nodes_to_remove = [] parents_to_fix = [] child, node, parent = Point3D(0, 0, 0), Point3D(0, 0, 0), Point3D(0, 0, 0) for n in created_treenodes: n_id, node.x, node.y, node.z = n[0], n[1], n[2], n[3] c_id, child.x, child.y, child.z = n[4], n[5], n[6], n[7] p_id, parent.x, parent.y, parent.z = n[8], n[9], n[10], n[11] child_is_original_node = c_id not in added_node_index if is_collinear(child, parent, node, True, 1.0): nodes_to_remove.append(n_id) # Only update nodes that don't get deleted anyway if child_is_original_node: parents_to_fix.append((c_id, p_id)) else: parents_to_fix.append((n_id, p_id)) # Update parent in formation in parent relation updates. If present # parent IDs point to a removed node, the next real parent will be # used instead. parent_update = [] for n, (c_id, p_id) in enumerate(parents_to_fix): parent_is_persistent = p_id not in added_node_index if parent_is_persistent: parent_update.append((c_id, p_id)) else: # Find next existing node upstream new_parent_id = p_id while not parent_is_persistent: parent_is_persistent = new_parent_id not in nodes_to_remove node_data = added_node_index.get(new_parent_id) # An added node would be used if it is not removed, e.g. # du to not being collinear anymore. if node_data and not parent_is_persistent: new_parent_id = node_data[8] else: parent_update.append((c_id, new_parent_id)) if nodes_to_remove: query_parts = [] params = [] if parent_update: update_nodes_template = ",".join("(%s, %s)" for _ in parent_update) update_nodes_flattened = list(chain.from_iterable(parent_update)) query_parts.append(""" UPDATE treenode SET parent_id = nodes_to_update.parent_id FROM (VALUES {}) nodes_to_update(child_id, parent_id) WHERE treenode.id = nodes_to_update.child_id; """.format(update_nodes_template)) params = update_nodes_flattened delete_nodes_template = ",".join("(%s)" for _ in nodes_to_remove) query_parts.append(""" DELETE FROM treenode WHERE id IN ( SELECT t.id FROM treenode t JOIN (VALUES {}) to_delete(id) ON t.id = to_delete.id ) RETURNING id; """.format(delete_nodes_template)) params = params + nodes_to_remove cursor.execute("\n".join(query_parts), params) deleted_node_ids = [r[0] for r in cursor.fetchall()] n_deleted_nodes = len(deleted_node_ids) sampler.delete() return JsonResponse({ 'deleted_sampler_id': sampler_id, 'deleted_interval_nodes': n_deleted_nodes })
def insert_treenode(request, project_id=None): """ Create a new treenode between two existing nodes. Its creator and creation_date information will be set to information of child node. No node will be created, if the node on the edge between the given child and parent node. """ # Use creation time, if part of parameter set params = {} float_values = { 'x': 0, 'y': 0, 'z': 0, 'radius': 0 } int_values = { 'confidence': 0, 'parent_id': -1, 'child_id': -1 } 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])) # Find child and parent of new treenode child = Treenode.objects.get(pk=params['child_id']) parent = Treenode.objects.get(pk=params['parent_id']) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') # Make sure the requested location for the new node is on the edge between # both existing nodes if the user has no edit permissions on the neuron. try: can_edit_treenode_or_fail(request.user, project_id, parent.id) except: child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) new_node_loc = Point3D(params['x'], params['y'], params['z']) if not is_collinear(child_loc, parent_loc, new_node_loc, True): raise ValueError('New node location has to be between child and parent') # Use creator and creation time for neighboring node that was created last. if child.creation_time < parent.creation_time: user, time = parent.user, parent.creation_time else: user, time = child.user, child.creation_time # Create new treenode treenode_id, skeleton_id = _create_treenode(project_id, user, request.user, params['x'], params['y'], params['z'], params['radius'], params['confidence'], -1, params['parent_id'], time) # Update parent of child to new treenode child.parent_id = treenode_id child.save() return HttpResponse(json.dumps({ 'treenode_id': treenode_id, 'skeleton_id': skeleton_id }))
# Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') # Make sure the requested location for the new node is on the edge between # both existing nodes if the user has no edit permissions on the neuron. try: can_edit_treenode_or_fail(request.user, project_id, parent.id) except: child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) new_node_loc = Point3D(params['x'], params['y'], params['z']) if not is_collinear(child_loc, parent_loc, new_node_loc, True): raise ValueError( 'New node location has to be between child and parent') # Use creator and creation time for neighboring node that was created last. if child.creation_time < parent.creation_time: user, time = parent.user, parent.creation_time else: user, time = child.user, child.creation_time # Create new treenode new_treenode = _create_treenode(project_id, user, request.user, params['x'], params['y'], params['z'], params['radius'], params['confidence'], -1, params['parent_id'], time)
child = Treenode.objects.get(pk=params['child_id']) parent = Treenode.objects.get(pk=params['parent_id']) # Make sure both nodes are actually child and parent if not child.parent == parent: raise ValueError('The provided nodes need to be child and parent') # Make sure the requested location for the new node is on the edge between # both existing nodes if the user has no edit permissions on the neuron. try: can_edit_treenode_or_fail(request.user, project_id, parent.id) except: child_loc = Point3D(child.location_x, child.location_y, child.location_z) parent_loc = Point3D(parent.location_x, parent.location_y, parent.location_z) new_node_loc = Point3D(params['x'], params['y'], params['z']) if not is_collinear(child_loc, parent_loc, new_node_loc, True): raise ValueError('New node location has to be between child and parent') # Use creator and creation time for neighboring node that was created last. if child.creation_time < parent.creation_time: user, time = parent.user, parent.creation_time else: user, time = child.user, child.creation_time # Create new treenode new_treenode = _create_treenode(project_id, user, request.user, params['x'], params['y'], params['z'], params['radius'], params['confidence'], -1, params['parent_id'], time) # Update parent of child to new treenode, do this in raw SQL to also get the # updated edition time Update also takeover children