Beispiel #1
0
def rename_neuron(request: HttpRequest,
                  project_id=None,
                  neuron_id=None) -> JsonResponse:
    """Rename a neuron.

    If a neuron is not locked by a user on which the current user has no
    permission, the name of neuron can be changed through this endpoint. Neuron
    names are currently not allowed to contain pipe characters ("|").
    ---
    parameters:
        - name: neuron_id
          description: ID of neuron to rename
          required: true
          type: integer
          paramType: path
        - name: name
          description: New name of the neuron
          required: true
          type: string
          paramType: form
    type:
      success:
        description: If renaming was successful
        type: boolean
        required: true
      renamed_neuron:
        description: ID of the renamed neuron
        type: integer
        required: true
      old_name:
        description: Old name of the renamed neuron
        type: string
        required: true
    """
    # Make sure the user can edit the neuron
    can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron')
    new_name = request.POST.get('name', None)
    if not new_name:
        raise ValueError("No name specified")
    # Do not allow '|' in name because it is used as string separator in NeuroHDF export
    if '|' in new_name:
        raise ValueError('New name should not contain pipe character')

    # Update neuron name
    neuron = ClassInstance.objects.get(id=neuron_id)
    old_name = neuron.name
    neuron.name = new_name
    neuron.save()

    # Insert log entry and return successfully
    insert_into_log(
        project_id, request.user.id, "rename_neuron", None,
        "Renamed neuron with ID %s from %s to %s" %
        (neuron.id, old_name, new_name))

    return JsonResponse({
        'success': True,
        'renamed_neuron': neuron.id,
        'old_name': old_name
    })
Beispiel #2
0
def update_confidence(request, project_id=None, node_id=0):
    tnid = int(node_id)
    can_edit_treenode_or_fail(request.user, project_id, tnid)

    new_confidence = int(request.POST.get('new_confidence', 0))
    if new_confidence < 1 or new_confidence > 5:
        return HttpResponse(json.dumps({'error': 'Confidence not in range 1-5 inclusive.'}))
    to_connector = request.POST.get('to_connector', 'false') == 'true'
    if to_connector:
        # Could be more than one. The GUI doesn't allow for specifying to which one.
        rows_affected = TreenodeConnector.objects.filter(treenode=tnid).update(confidence=new_confidence)
    else:
        rows_affected = Treenode.objects.filter(id=tnid).update(confidence=new_confidence,editor=request.user)

    if rows_affected > 0:
        location = Location.objects.filter(id=tnid).values_list('location_x',
                'location_y', 'location_z')[0]
        insert_into_log(project_id, request.user.id, "change_confidence", location, "Changed to %s" % new_confidence)
        return HttpResponse(json.dumps({'message': 'success'}), content_type='text/json')

    # Else, signal error
    if to_connector:
        return HttpResponse(json.dumps({'error': 'Failed to update confidence between treenode %s and connector.' % tnid}))
    else:
        return HttpResponse(json.dumps({'error': 'Failed to update confidence at treenode %s.' % tnid}))
Beispiel #3
0
def rename_neuron(request, project_id=None, neuron_id=None):
    """Rename a neuron if it is not locked by a user on which the current user
    has no permission.
    """
    # Make sure the user can edit the neuron
    can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron')
    new_name = request.POST.get('name', None)
    if not new_name:
        raise ValueError("No name specified")
    # Do not allow '|' in name because it is used as string separator in NeuroHDF export
    if '|' in new_name:
        raise ValueError('New name should not contain pipe character')

    # Update neuron name
    neuron = ClassInstance.objects.get(id=neuron_id)
    old_name = neuron.name
    neuron.name=new_name
    neuron.save()

    # Insert log entry and return successfully
    insert_into_log(project_id, request.user.id, "rename_neuron", None,
                    "Renamed neuron with ID %s from %s to %s" % (neuron.id , old_name, new_name))

    return HttpResponse(json.dumps({
        'success': True,
        'renamed_neuron': neuron.id
    }))
Beispiel #4
0
def rename_neuron(request, project_id=None, neuron_id=None):
    """Rename a neuron if it is not locked by a user on which the current user
    has no permission.
    """
    # Make sure the user can edit the neuron
    can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron')
    new_name = request.POST.get('name', None)
    if not new_name:
        raise ValueError("No name specified")
    # Do not allow '|' in name because it is used as string separator in NeuroHDF export
    if '|' in new_name:
        raise ValueError('New name should not contain pipe character')

    # Update neuron name
    neuron = ClassInstance.objects.get(id=neuron_id)
    old_name = neuron.name
    neuron.name = new_name
    neuron.save()

    # Insert log entry and return successfully
    insert_into_log(
        project_id, request.user.id, "rename_neuron", None,
        "Renamed neuron with ID %s from %s to %s" %
        (neuron.id, old_name, new_name))

    return HttpResponse(
        json.dumps({
            'success': True,
            'renamed_neuron': neuron.id
        }))
Beispiel #5
0
def update_confidence(request, project_id=None, node_id=0):
    tnid = int(node_id)
    can_edit_treenode_or_fail(request.user, project_id, tnid)

    new_confidence = int(request.POST.get('new_confidence', 0))
    if new_confidence < 1 or new_confidence > 5:
        return HttpResponse(json.dumps({'error': 'Confidence not in range 1-5 inclusive.'}))
    to_connector = request.POST.get('to_connector', 'false') == 'true'
    if to_connector:
        # Could be more than one. The GUI doesn't allow for specifying to which one.
        rows_affected = TreenodeConnector.objects.filter(treenode=tnid).update(confidence=new_confidence)
    else:
        rows_affected = Treenode.objects.filter(id=tnid).update(confidence=new_confidence,editor=request.user)

    if rows_affected > 0:
        location = Location.objects.filter(id=tnid).values_list('location_x',
                'location_y', 'location_z')[0]
        insert_into_log(project_id, request.user.id, "change_confidence", location, "Changed to %s" % new_confidence)
        return HttpResponse(json.dumps({'message': 'success'}), content_type='text/json')

    # Else, signal error
    if to_connector:
        return HttpResponse(json.dumps({'error': 'Failed to update confidence between treenode %s and connector.' % tnid}))
    else:
        return HttpResponse(json.dumps({'error': 'Failed to update confidence at treenode %s.' % tnid}))
Beispiel #6
0
def rename_neuron(request, project_id=None, neuron_id=None):
    """Rename a neuron.

    If a neuron is not locked by a user on which the current user has no
    permission, the name of neuron can be changed through this endpoint. Neuron
    names are currently not allowed to contain pipe characters ("|").
    ---
    parameters:
        - name: neuron_id
          description: ID of neuron to rename
          required: true
          type: integer
          paramType: path
        - name: name
          description: New name of the neuron
          required: true
          type: string
          paramType: form
    type:
      success:
        description: If renaming was successful
        type: boolean
        required: true
      renamed_neuron:
        description: ID of the renamed neuron
        type: integer
        required: true
      old_name:
        description: Old name of the renamed neuron
        type: string
        required: true
    """
    # Make sure the user can edit the neuron
    can_edit_class_instance_or_fail(request.user, neuron_id, "neuron")
    new_name = request.POST.get("name", None)
    if not new_name:
        raise ValueError("No name specified")
    # Do not allow '|' in name because it is used as string separator in NeuroHDF export
    if "|" in new_name:
        raise ValueError("New name should not contain pipe character")

    # Update neuron name
    neuron = ClassInstance.objects.get(id=neuron_id)
    old_name = neuron.name
    neuron.name = new_name
    neuron.save()

    # Insert log entry and return successfully
    insert_into_log(
        project_id,
        request.user.id,
        "rename_neuron",
        None,
        "Renamed neuron with ID %s from %s to %s" % (neuron.id, old_name, new_name),
    )

    return HttpResponse(json.dumps({"success": True, "renamed_neuron": neuron.id, "old_name": old_name}))
Beispiel #7
0
            def delete_node( node ):
                # Find and delete children
                classification_instance_operation.res_on_err \
                    = 'Failed to delete relation from instance table.'
                cici = ClassInstanceClassInstance.objects.filter(class_instance_b=node.id)
                for rel in cici:
                    # Delete children
                    delete_node( rel.class_instance_a )

                # Delete class instance
                node.delete()

                # Log
                insert_into_log(project_id, request.user.id, 'remove_element', None,
                    'Removed classification with ID %s and name %s' % (params['id'],
                        params['title']))
Beispiel #8
0
    def rename_node():
        can_edit_class_instance_or_fail(request.user, params['id'])
        # Do not allow '|' in name because it is used as string separator in NeuroHDF export
        if '|' in params['title']:
            raise Exception('Name should not contain pipe character!')

        instance_operation.res_on_err = 'Failed to update class instance.'
        nodes_to_rename = ClassInstance.objects.filter(id=params['id'])
        node_ids = [node.id for node in nodes_to_rename]
        if len(node_ids) > 0:
            old_name = ",".join([n.name for n in nodes_to_rename])
            nodes_to_rename.update(name=params['title'])
            insert_into_log(project_id, request.user.id, "rename_%s" % params['classname'], None, "Renamed %s with ID %s from %s to %s" % (params['classname'], params['id'], old_name, params['title']))
            return HttpResponse(json.dumps({'class_instance_ids': node_ids}))
        else:
            instance_operation.res_on_err = ''
            raise Exception('Could not find any node with ID %s' % params['id'])
Beispiel #9
0
    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}))
Beispiel #10
0
def reroot_skeleton(request, project_id=None):
    """ Any user with an Annotate role can reroot any skeleton.
    """
    treenode_id = request.POST.get('treenode_id', None)
    treenode = _reroot_skeleton(treenode_id, project_id)
    response_on_error = ''
    try:
        if treenode:
            response_on_error = 'Failed to log reroot.'
            location = (treenode.location_x, treenode.location_y, treenode.location_z)
            insert_into_log(project_id, request.user.id, 'reroot_skeleton',
                            location, 'Rerooted skeleton for '
                            'treenode with ID %s' % treenode.id)
            return HttpResponse(json.dumps({'newroot': treenode.id}))
        # Else, already root
        return HttpResponse(json.dumps({'error': 'Node #%s is already '
                                                 'root!' % treenode_id}))
    except Exception as e:
        raise Exception(response_on_error + ':' + str(e))
Beispiel #11
0
    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}))
Beispiel #12
0
    def move_node():
        # Can only move the node if the user owns the node and the target node,
        # or the user is a superuser
        can_edit_class_instance_or_fail(request.user, params['src'], 'node') # node to move
        can_edit_class_instance_or_fail(request.user, params['ref'], 'node') # new parent node
        #
        if 0 == params['src'] or 0 == params['ref']:
            raise Exception('src (%s) or ref (%s) not set.' % (params['src'], params['ref']))

        relation_type = 'part_of'
        if 'skeleton' == params['classname']:  # Special case for model_of relationship
            relation_type = 'model_of'

        instance_operation.res_on_err = 'Failed to update %s relation.' % relation_type
        ClassInstanceClassInstance.objects.filter(
                project=project_id,
                relation=relation_map[relation_type],
                class_instance_a=params['src']).update(class_instance_b=params['ref'])

        insert_into_log(project_id, request.user.id, 'move_%s' % params['classname'], None, 'Moved %s with ID %s to %s with ID %s' % (params['classname'], params['id'], params['targetname'], params['ref']))
        return HttpResponse(json.dumps({'message': 'Success.'}))
Beispiel #13
0
    def remove_node():
        # Can only remove the node if the user owns it or the user is a superuser
        can_edit_class_instance_or_fail(request.user, params['id'])
        # Check if node is a skeleton. If so, we have to remove its treenodes as well!
        if 0 == params['rel']:
            raise Exception('No relation given!')

        elif 'skeleton' == params['rel']:
            remove_skeletons([params['id']])
            insert_into_log(project_id, request.user.id, 'remove_skeleton', None, 'Removed skeleton with ID %s and name %s' % (params['id'], params['title']))
            return HttpResponse(json.dumps({'status': 1, 'message': 'Removed skeleton successfully.'}))

        elif 'neuron' == params['rel']:
            instance_operation.res_on_err = 'Failed to retrieve node skeleton relations.'
            skeleton_relations = ClassInstanceClassInstance.objects.filter(
                    project=project_id,
                    relation=relation_map['model_of'],
                    class_instance_b=params['id'])
            remove_skeletons([s.class_instance_a_id for s in skeleton_relations])
            instance_operation.res_on_err = 'Failed to delete node from instance table.'
            node_to_delete = ClassInstance.objects.filter(id=params['id'])
            if node_to_delete.count() > 0:
                node_to_delete.delete()
                insert_into_log(project_id, request.user.id, 'remove_neuron', None, 'Removed neuron with ID %s and name %s' % (params['id'], params['title']))
                return HttpResponse(json.dumps({'status': 1, 'message': 'Removed neuron successfully.'}))
            else:
                instance_operation.res_on_err = ''
                raise Exception('Could not find any node with ID %s' % params['id'])

        else:
            instance_operation.res_on_err = 'Failed to delete node from instance table.'
            node_to_delete = ClassInstance.objects.filter(id=params['id'])
            if node_to_delete.count() > 0:
                node_to_delete.delete()
                return HttpResponse(json.dumps({'status': 1, 'message': 'Removed node successfully.'}))
            else:
                instance_operation.res_on_err = ''
                raise Exception('Could not find any node with ID %s' % params['id'])
Beispiel #14
0
def delete_treenode(request, project_id=None):
    """ Deletes a treenode. If the skeleton has a single node, deletes the
    skeleton and its neuron. Returns the parent_id, if any."""
    treenode_id = int(request.POST.get('treenode_id', -1))
    # Raise an exception if the user doesn't have permission to edit the
    # treenode.
    can_edit_or_fail(request.user, treenode_id, 'treenode')
    # 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, treenode_id)
    # Make sure the back-end is in the expected state
    state.validate_state(treenode_id,
                         request.POST.get('state'),
                         lock=True,
                         neighborhood=True)

    treenode = Treenode.objects.get(pk=treenode_id)
    parent_id = treenode.parent_id

    # Get information about linked connectors
    links = list(
        TreenodeConnector.objects.filter(project_id=project_id,
                                         treenode_id=treenode_id).values_list(
                                             'id', 'relation_id',
                                             'connector_id', 'confidence'))

    n_sampler_refs = SamplerInterval.objects.filter(start_node=treenode).count() + \
            SamplerInterval.objects.filter(end_node=treenode).count()
    if (n_sampler_refs > 0):
        raise ValueError(
            "Can't delete node, it is used in at least one sampler interval")

    response_on_error = ''
    deleted_neuron = False
    try:
        if not parent_id:
            children = []
            # This treenode is root.
            response_on_error = 'Could not retrieve children for ' \
                'treenode #%s' % treenode_id
            n_children = Treenode.objects.filter(parent=treenode).count()
            response_on_error = "Could not delete root node"
            if n_children > 0:
                # TODO yes you can, the new root is the first of the children,
                # and other children become independent skeletons
                raise Exception("You can't delete the root node when it "
                                "has children.")
            # Get the neuron before the skeleton is deleted. It can't be
            # accessed otherwise anymore.
            neuron = ClassInstance.objects.get(
                project_id=project_id,
                cici_via_b__relation__relation_name='model_of',
                cici_via_b__class_instance_a=treenode.skeleton)
            # Remove the original skeleton. It is OK to remove it if it only had
            # one node, even if the skeleton's user does not match or the user
            # is not superuser. Delete the skeleton, which triggers deleting
            # the ClassInstanceClassInstance relationship with neuron_id
            response_on_error = 'Could not delete skeleton.'
            # Extra check for errors, like having two root nodes
            count = Treenode.objects.filter(skeleton_id=treenode.skeleton_id) \
                .count()
            if 1 == count:
                # deletes as well treenodes that refer to the skeleton
                ClassInstance.objects.filter(pk=treenode.skeleton_id) \
                    .delete()
            else:
                return JsonResponse({"error": "Can't delete " \
                    "isolated node: erroneously, its skeleton contains more " \
                    "than one treenode! Check for multiple root nodes."})

            # If the neuron modeled by the skeleton of the treenode is empty,
            # delete it.
            response_on_error = 'Could not delete neuron #%s' % neuron.id
            deleted_neuron = _delete_if_empty(neuron.id)

            if deleted_neuron:
                # Insert log entry for neuron deletion
                insert_into_log(
                    project_id, request.user.id, 'remove_neuron',
                    (treenode.location_x, treenode.location_y,
                     treenode.location_z),
                    'Deleted neuron %s and skeleton(s) %s.' %
                    (neuron.id, treenode.skeleton_id))

        else:
            # Treenode is not root, it has a parent and perhaps children.
            # Reconnect all the children to the parent.
            response_on_error = 'Could not update parent id of children nodes'
            cursor = connection.cursor()
            cursor.execute(
                """
                UPDATE treenode SET parent_id = %s
                WHERE project_id = %s AND parent_id = %s
                RETURNING id, edition_time
            """, (treenode.parent_id, project_id, treenode.id))
            # Children will be a list of two-element lists, just what we want to
            # return as child info.
            children = cursor.fetchall()

        # Remove treenode
        response_on_error = 'Could not delete treenode.'
        Treenode.objects.filter(project_id=project_id, pk=treenode_id).delete()
        return JsonResponse({
            'x': treenode.location_x,
            'y': treenode.location_y,
            'z': treenode.location_z,
            'parent_id': parent_id,
            'children': children,
            'links': links,
            'radius': treenode.radius,
            'confidence': treenode.confidence,
            'skeleton_id': treenode.skeleton_id,
            'deleted_neuron': deleted_neuron,
            'success': "Removed treenode successfully."
        })

    except Exception as e:
        raise Exception(response_on_error + ': ' + str(e))
Beispiel #15
0
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())))
Beispiel #16
0
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())))
Beispiel #17
0
def delete_neuron(request, project_id=None, neuron_id=None):
    """ Deletes a neuron if and only if two things are the case: 1. The user
    ownes all treenodes of the skeleton modeling the neuron in question and
    2. The neuron is not annotated by other users.
    """
    # Make sure the user can edit the neuron in general
    can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron')

    # Create class and relation dictionaries
    classes = dict(
        Class.objects.filter(project_id=project_id).values_list(
            'class_name', 'id'))
    relations = dict(
        Relation.objects.filter(project_id=project_id).values_list(
            'relation_name', 'id'))

    # Make sure the user has permission to edit all treenodes of all skeletons
    skeleton_ids = ClassInstanceClassInstance.objects.filter(
        class_instance_b=neuron_id,
        relation_id=relations['model_of']).values_list('class_instance_a',
                                                       flat=True)
    for skid in skeleton_ids:
        others_nodes = Treenode.objects.filter(skeleton_id=skid).exclude(
            user_id=request.user.id).values_list('id', flat=True)
        if others_nodes:
            try:
                can_edit_all_or_fail(request.user, others_nodes, 'treenode')
            except Exception:
                raise Exception("You don't have permission to remove all " \
                        "treenodes of skeleton %s modeling this neuron. The " \
                        "neuron won't be deleted." % skid)

    # Make sure the user has permission to edit all annotations of this neuron
    annotation_ids = set(
        ClassInstanceClassInstance.objects.filter(
            class_instance_a_id=neuron_id,
            relation_id=relations['annotated_with']).values_list('id',
                                                                 flat=True))
    if annotation_ids:
        try:
            can_edit_all_or_fail(request.user, annotation_ids,
                                 'class_instance_class_instance')
        except Exception:
            raise Exception("You don't have permission to remove all " \
                    "annotations linked to this neuron. The neuron won't " \
                    "be deleted.")

    # Try to get the root node to have a valid location for a log entry
    if skeleton_ids:
        try:
            root_node = Treenode.objects.get(skeleton_id=skeleton_ids[0],
                                             parent=None)
            root_location = (root_node.location_x, root_node.location_y,
                             root_node.location_z)
        except (Treenode.DoesNotExist, Treenode.MultipleObjectsReturned):
            root_location = None
    else:
        root_location = None

    # Delete neuron (and implicitely all annotation links due to Django's
    # cascading deletion)
    neuron = get_object_or_404(ClassInstance, pk=neuron_id)
    neuron.delete()

    # Delete all annotations that are not used anymore
    used_annotation_ids = set(
        ClassInstanceClassInstance.objects.filter(
            class_instance_b_id__in=annotation_ids,
            relation_id=relations['annotated_with']).values_list('id',
                                                                 flat=True))
    unused_annotation_ids = annotation_ids.difference(used_annotation_ids)
    ClassInstance.objects.filter(id__in=unused_annotation_ids).delete()

    # Delete the skeletons (and their treenodes through cascading delete)
    cursor = connection.cursor()
    for skid in skeleton_ids:
        # Because there are constraints used in the database that Django is not
        # aware of, it's emulation of cascading deletion doesn't work.
        # Therefore, raw SQL needs to be used to use true cascading deletion.
        cursor.execute(
            '''
        BEGIN;
        DELETE FROM change_request WHERE treenode_id IN (
            SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM change_request WHERE connector_id IN (
            SELECT id FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM treenode_class_instance WHERE treenode_id IN (
            SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM treenode WHERE skeleton_id=%s AND project_id=%s;
        DELETE FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s;
        DELETE FROM class_instance WHERE id=%s AND project_id=%s;
        DELETE FROM review WHERE skeleton_id=%s AND project_id=%s;
        COMMIT;
        ''', (skid, project_id) * 7)

    # Insert log entry and refer to position of the first skeleton's root node
    insert_into_log(
        project_id, request.user.id, 'remove_neuron', root_location,
        'Deleted neuron %s and skeleton(s) %s.' %
        (neuron_id, ', '.join([str(s) for s in skeleton_ids])))

    return HttpResponse(json.dumps({
        'skeleton_ids': list(skeleton_ids),
        'success': "Deleted neuron #%s as well as its skeletons and " \
                "annotations." % neuron_id}))
Beispiel #18
0
def delete_treenode(request, project_id=None):
    """ Deletes a treenode. If the skeleton has a single node, deletes the
    skeleton and its neuron. Returns the parent_id, if any."""
    treenode_id = int(request.POST.get('treenode_id', -1))
    # Raise an exception if the user doesn't have permission to edit the
    # treenode.
    can_edit_or_fail(request.user, treenode_id, 'treenode')
    # 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, treenode_id)
    # Make sure the back-end is in the expected state
    state.validate_state(treenode_id, request.POST.get('state'), lock=True,
            neighborhood=True)

    treenode = Treenode.objects.get(pk=treenode_id)
    parent_id = treenode.parent_id

    # Get information about linked connectors
    links = list(TreenodeConnector.objects.filter(project_id=project_id,
            treenode_id=treenode_id).values_list('id', 'relation_id',
            'connector_id', 'confidence'))

    n_sampler_refs = SamplerInterval.objects.filter(start_node=treenode).count() + \
            SamplerInterval.objects.filter(end_node=treenode).count()
    if (n_sampler_refs > 0):
        raise ValueError("Can't delete node, it is used in at least one sampler interval")

    response_on_error = ''
    deleted_neuron = False
    cursor = connection.cursor()
    try:
        if not parent_id:
            children = []
            # This treenode is root.
            response_on_error = 'Could not retrieve children for ' \
                'treenode #%s' % treenode_id
            n_children = Treenode.objects.filter(parent=treenode).count()
            response_on_error = "Could not delete root node"
            if n_children > 0:
                # TODO yes you can, the new root is the first of the children,
                # and other children become independent skeletons
                raise Exception("You can't delete the root node when it "
                                "has children.")
            # Get the neuron before the skeleton is deleted. It can't be
            # accessed otherwise anymore.
            neuron = ClassInstance.objects.get(project_id=project_id,
                        cici_via_b__relation__relation_name='model_of',
                        cici_via_b__class_instance_a=treenode.skeleton)
            # Remove the original skeleton. It is OK to remove it if it only had
            # one node, even if the skeleton's user does not match or the user
            # is not superuser. Delete the skeleton, which triggers deleting
            # the ClassInstanceClassInstance relationship with neuron_id
            response_on_error = 'Could not delete skeleton.'
            # Extra check for errors, like having two root nodes
            count = Treenode.objects.filter(skeleton_id=treenode.skeleton_id) \
                .count()
            if 1 == count:
                # deletes as well treenodes that refer to the skeleton
                ClassInstance.objects.filter(pk=treenode.skeleton_id) \
                    .delete()
            else:
                return JsonResponse({"error": "Can't delete " \
                    "isolated node: erroneously, its skeleton contains more " \
                    "than one treenode! Check for multiple root nodes."})

            # If the neuron modeled by the skeleton of the treenode is empty,
            # delete it.
            response_on_error = 'Could not delete neuron #%s' % neuron.id
            deleted_neuron = _delete_if_empty(neuron.id)

            if deleted_neuron:
                # Insert log entry for neuron deletion
                insert_into_log(project_id, request.user.id, 'remove_neuron',
                               (treenode.location_x, treenode.location_y, treenode.location_z),
                               'Deleted neuron %s and skeleton(s) %s.' % (neuron.id, treenode.skeleton_id))

        else:
            # Treenode is not root, it has a parent and perhaps children.
            # Reconnect all the children to the parent.
            response_on_error = 'Could not update parent id of children nodes'
            cursor.execute("""
                UPDATE treenode SET parent_id = %s
                WHERE project_id = %s AND parent_id = %s
                RETURNING id, edition_time
            """, (treenode.parent_id, project_id, treenode.id))
            # Children will be a list of two-element lists, just what we want to
            # return as child info.
            children = cursor.fetchall()

        # Remove treenode. Set the current user name in a transaction local
        # variable. This is done to communicate the current user to the trigger
        # that updates the skeleton summary table.
        response_on_error = 'Could not delete treenode.'
        cursor.execute("SET LOCAL catmaid.user_id=%(user_id)s", {
            'user_id': request.user.id,
        })
        Treenode.objects.filter(project_id=project_id, pk=treenode_id).delete()
        return JsonResponse({
            'x': treenode.location_x,
            'y': treenode.location_y,
            'z': treenode.location_z,
            'parent_id': parent_id,
            'children': children,
            'links': links,
            'radius': treenode.radius,
            'confidence': treenode.confidence,
            'skeleton_id': treenode.skeleton_id,
            'deleted_neuron': deleted_neuron,
            'success': "Removed treenode successfully."
        })

    except Exception as e:
        raise Exception(response_on_error + ': ' + str(e))
Beispiel #19
0
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())))
Beispiel #20
0
def delete_treenode(request, project_id=None):
    """ Deletes a treenode. If the skeleton has a single node, deletes the
    skeleton and its neuron. Returns the parent_id, if any."""
    treenode_id = int(request.POST.get('treenode_id', -1))
    # Raise an exception if the user doesn't have permission to edit the
    # treenode.
    can_edit_or_fail(request.user, treenode_id, 'treenode')
    # 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, treenode_id)

    treenode = Treenode.objects.get(pk=treenode_id)
    parent_id = treenode.parent_id

    response_on_error = ''
    deleted_neuron = False
    try:
        if not parent_id:
            # This treenode is root.
            response_on_error = 'Could not retrieve children for ' \
                'treenode #%s' % treenode_id
            n_children = Treenode.objects.filter(parent=treenode).count()
            response_on_error = "Could not delete root node"
            if n_children > 0:
                # TODO yes you can, the new root is the first of the children,
                # and other children become independent skeletons
                raise Exception("You can't delete the root node when it "
                                "has children.")
            # Get the neuron before the skeleton is deleted. It can't be
            # accessed otherwise anymore.
            neuron = ClassInstance.objects.get(project_id=project_id,
                        cici_via_b__relation__relation_name='model_of',
                        cici_via_b__class_instance_a=treenode.skeleton)
            # Remove the original skeleton. It is OK to remove it if it only had
            # one node, even if the skeleton's user does not match or the user
            # is not superuser. Delete the skeleton, which triggers deleting
            # the ClassInstanceClassInstance relationship with neuron_id
            response_on_error = 'Could not delete skeleton.'
            # Extra check for errors, like having two root nodes
            count = Treenode.objects.filter(skeleton_id=treenode.skeleton_id) \
                .count()
            if 1 == count:
                # deletes as well treenodes that refer to the skeleton
                ClassInstance.objects.filter(pk=treenode.skeleton_id) \
                    .delete()
            else:
                return HttpResponse(json.dumps({"error": "Can't delete " \
                    "isolated node: erroneously, its skeleton contains more " \
                    "than one treenode! Check for multiple root nodes."}))

            # If the neuron modeled by the skeleton of the treenode is empty,
            # delete it.
            response_on_error = 'Could not delete neuron #%s' % neuron.id
            deleted_neuron = _delete_if_empty(neuron.id)

            if deleted_neuron:
                # Insert log entry for neuron deletion
                insert_into_log(project_id, request.user.id, 'remove_neuron',
                               (treenode.location_x, treenode.location_y, treenode.location_z),
                               'Deleted neuron %s and skeleton(s) %s.' % (neuron.id, treenode.skeleton_id))

        else:
            # Treenode is not root, it has a parent and perhaps children.
            # Reconnect all the children to the parent.
            response_on_error = 'Could not update parent id of children nodes'
            Treenode.objects.filter(parent=treenode) \
                .update(parent=treenode.parent)

        # Remove treenode
        response_on_error = 'Could not delete treenode.'
        Treenode.objects.filter(pk=treenode_id).delete()
        return HttpResponse(json.dumps({
            'deleted_neuron': deleted_neuron,
            'parent_id': parent_id,
            'skeleton_id': treenode.skeleton_id,
            'success': "Removed treenode successfully."
        }))

    except Exception as e:
        raise Exception(response_on_error + ': ' + str(e))
Beispiel #21
0
def delete_neuron(request, project_id=None, neuron_id=None):
    """ Deletes a neuron if and only if two things are the case: 1. The user
    ownes all treenodes of the skeleton modeling the neuron in question and
    2. The neuron is not annotated by other users.
    """
    # Make sure the user can edit the neuron in general
    can_edit_class_instance_or_fail(request.user, neuron_id, 'neuron')

    # Create class and relation dictionaries
    classes = dict(Class.objects.filter(
            project_id=project_id).values_list('class_name', 'id'))
    relations = dict(Relation.objects.filter(
            project_id=project_id).values_list('relation_name', 'id'))

    # Make sure the user has permission to edit all treenodes of all skeletons
    skeleton_ids = ClassInstanceClassInstance.objects.filter(
            class_instance_b=neuron_id,
            relation_id=relations['model_of']).values_list(
                    'class_instance_a', flat=True)
    for skid in skeleton_ids:
        others_nodes = Treenode.objects.filter(skeleton_id=skid).exclude(
                user_id=request.user.id).values_list('id', flat=True)
        if others_nodes:
            try:
                can_edit_all_or_fail(request.user, others_nodes, 'treenode')
            except Exception:
                raise Exception("You don't have permission to remove all " \
                        "treenodes of skeleton %s modeling this neuron. The " \
                        "neuron won't be deleted." % skid)

    # Make sure the user has permission to edit all annotations of this neuron
    annotation_ids = set(ClassInstanceClassInstance.objects.filter(
            class_instance_a_id=neuron_id,
            relation_id=relations['annotated_with']).values_list(
                    'id', flat=True))
    if annotation_ids:
        try:
            can_edit_all_or_fail(request.user, annotation_ids,
                    'class_instance_class_instance')
        except Exception:
            raise Exception("You don't have permission to remove all " \
                    "annotations linked to this neuron. The neuron won't " \
                    "be deleted.")

    # Try to get the root node to have a valid location for a log entry
    if skeleton_ids:
        try:
            root_node = Treenode.objects.get(
                    skeleton_id=skeleton_ids[0], parent=None)
            root_location = (root_node.location_x, root_node.location_y,
                             root_node.location_z)
        except (Treenode.DoesNotExist, Treenode.MultipleObjectsReturned):
            root_location = None
    else:
        root_location = None

    # Delete neuron (and implicitely all annotation links due to Django's
    # cascading deletion)
    neuron = get_object_or_404(ClassInstance, pk=neuron_id)
    neuron.delete()

    # Delete all annotations that are not used anymore
    used_annotation_ids = set(ClassInstanceClassInstance.objects.filter(
            class_instance_b_id__in=annotation_ids,
            relation_id=relations['annotated_with']).values_list(
                    'id', flat=True))
    unused_annotation_ids = annotation_ids.difference(used_annotation_ids)
    ClassInstance.objects.filter(id__in=unused_annotation_ids).delete()

    # Delete the skeletons (and their treenodes through cascading delete)
    cursor = connection.cursor()
    for skid in skeleton_ids:
        # Because there are constraints used in the database that Django is not
        # aware of, it's emulation of cascading deletion doesn't work.
        # Therefore, raw SQL needs to be used to use true cascading deletion.
        cursor.execute('''
        BEGIN;
        DELETE FROM change_request WHERE treenode_id IN (
            SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM change_request WHERE connector_id IN (
            SELECT id FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM treenode_class_instance WHERE treenode_id IN (
            SELECT id FROM treenode WHERE skeleton_id=%s AND project_id=%s);
        DELETE FROM treenode WHERE skeleton_id=%s AND project_id=%s;
        DELETE FROM treenode_connector WHERE skeleton_id=%s AND project_id=%s;
        DELETE FROM class_instance WHERE id=%s AND project_id=%s;
        DELETE FROM review WHERE skeleton_id=%s AND project_id=%s;
        COMMIT;
        ''', (skid, project_id) * 7)

    # Insert log entry and refer to position of the first skeleton's root node
    insert_into_log(project_id, request.user.id, 'remove_neuron', root_location,
            'Deleted neuron %s and skeleton(s) %s.' % (neuron_id,
                    ', '.join([str(s) for s in skeleton_ids])))

    return HttpResponse(json.dumps({
        'skeleton_ids': list(skeleton_ids),
        'success': "Deleted neuron #%s as well as its skeletons and " \
                "annotations." % neuron_id}))
Beispiel #22
0
def _join_skeleton(user, from_treenode_id, to_treenode_id, project_id,
        annotation_map):
    """ Take the IDs of two nodes, each belonging to a different skeleton, and
    make to_treenode be a child of from_treenode, and join the nodes of the
    skeleton of to_treenode into the skeleton of from_treenode, and delete the
    former skeleton of to_treenode. All annotations in annotation_set will be
    linked to the skeleton of to_treenode. It is expected that <annotation_map>
    is a dictionary, mapping an annotation to an annotator ID. Also, all
    reviews of the skeleton that changes ID are changed to refer to the new
    skeleton ID.
    """
    if from_treenode_id is None or to_treenode_id is None:
        raise Exception('Missing arguments to _join_skeleton')

    response_on_error = ''
    try:
        from_treenode_id = int(from_treenode_id)
        to_treenode_id = int(to_treenode_id)

        try:
            from_treenode = Treenode.objects.get(pk=from_treenode_id)
        except Treenode.DoesNotExist:
            raise Exception("Could not find a skeleton for treenode #%s" % from_treenode_id)

        try:
            to_treenode = Treenode.objects.get(pk=to_treenode_id)
        except Treenode.DoesNotExist:
            raise Exception("Could not find a skeleton for treenode #%s" % to_treenode_id)

        from_skid = from_treenode.skeleton_id
        from_neuron = _get_neuronname_from_skeletonid( project_id, from_skid )

        to_skid = to_treenode.skeleton_id
        to_neuron = _get_neuronname_from_skeletonid( project_id, to_skid )

        # Make sure the user has permissions to edit both neurons
        can_edit_class_instance_or_fail(
                user, from_neuron['neuronid'], 'neuron')
        can_edit_class_instance_or_fail(
                user, to_neuron['neuronid'], 'neuron')

        # Check if annotations are valid
        if not check_annotations_on_join(project_id, user,
                from_neuron['neuronid'], to_neuron['neuronid'],
                frozenset(annotation_map.keys())):
            raise Exception("Annotation distribution is not valid for joining. " \
              "Annotations for which you don't have permissions have to be kept!")

        if from_skid == to_skid:
            raise Exception('Cannot join treenodes of the same skeleton, this would introduce a loop.')

        # Reroot to_skid at to_treenode if necessary
        response_on_error = 'Could not reroot at treenode %s' % to_treenode_id
        _reroot_skeleton(to_treenode_id, project_id)

        # The target skeleton is removed and its treenode assumes
        # the skeleton id of the from-skeleton.

        response_on_error = 'Could not update Treenode table with new skeleton id for joined treenodes.'
        Treenode.objects.filter(skeleton=to_skid).update(skeleton=from_skid)

        response_on_error = 'Could not update TreenodeConnector table.'
        TreenodeConnector.objects.filter(
            skeleton=to_skid).update(skeleton=from_skid)

        # Update reviews from 'losing' neuron to now belong to the new neuron
        response_on_error = 'Couldn not update reviews with new skeleton IDs for joined treenodes.'
        Review.objects.filter(skeleton_id=to_skid).update(skeleton=from_skid)

        # Remove skeleton of to_id (deletes cicic part_of to neuron by cascade,
        # leaving the parent neuron dangling in the object tree).
        response_on_error = 'Could not delete skeleton with ID %s.' % to_skid
        ClassInstance.objects.filter(pk=to_skid).delete()

        # Remove the 'losing' neuron if it is empty
        _delete_if_empty(to_neuron['neuronid'])

        # Update the parent of to_treenode.
        response_on_error = 'Could not update parent of treenode with ID %s' % to_treenode_id
        Treenode.objects.filter(id=to_treenode_id).update(parent=from_treenode_id, editor=user)

        # Update linked annotations of neuron
        response_on_error = 'Could not update annotations of neuron ' \
                'with ID %s' % from_neuron['neuronid']
        _update_neuron_annotations(project_id, user, from_neuron['neuronid'],
                annotation_map)

        from_location = (from_treenode.location_x, from_treenode.location_y,
                         from_treenode.location_z)
        insert_into_log(project_id, user.id, 'join_skeleton',
                from_location, 'Joined skeleton with ID %s (neuron: ' \
                '%s) into skeleton with ID %s (neuron: %s, annotations: %s)' % \
                (to_skid, to_neuron['neuronname'], from_skid,
                        from_neuron['neuronname'], ', '.join(annotation_map.keys())))

    except Exception as e:
        raise Exception(response_on_error + ':' + str(e))
Beispiel #23
0
def rename_neurons(request: HttpRequest, project_id=None) -> JsonResponse:
    """Rename a neuron.

    If a neuron is not locked by a user on which the current user has no
    permission, the name of neuron can be changed through this endpoint. Neuron
    names are currently not allowed to contain pipe characters ("|").
    ---
    parameters:
        - name: neuron_names
          description: A list of two-element tuples, containing of neuron ID and new name each.
          required: true
          type: array
          paramType: form
    type:
      success:
        description: If renaming was successful
        type: boolean
        required: true
      renamed_neurons:
        description: IDs of the renamed neurons
        type: integer
        required: true
      old_names:
        description: Old names of the renamed neurons
        type: string
        required: true
    """
    neuron_name_pairs = get_request_list(request.POST, 'names')
    if not neuron_name_pairs:
        raise ValueError('Need at least one neuron ID / name mapping')
    neuron_ids = [int(k) for k, _ in neuron_name_pairs]
    neuron_names = [v for _, v in neuron_name_pairs]

    # Make sure the user can edit the neuron
    can_edit_all_or_fail(request.user, neuron_ids, 'class_instance')
    # Do not allow '|' in name because it is used as string separator in NeuroHDF export
    for new_name in neuron_names:
        if '|' in new_name:
            raise ValueError('New name should not contain pipe character')

    cursor = connection.cursor()
    cursor.execute(
        """
        UPDATE class_instance ci
        SET name = new_data.name
        FROM UNNEST(
            %(neuron_ids)s::bigint[],
            %(neuron_names)s::text[]) new_data(id, name)
        JOIN class_instance ci_old
            ON ci_old.id = new_data.id
        WHERE ci.id = new_data.id
            AND ci.project_id = %(project_id)s
        RETURNING ci.id, ci_old.name, ci.name
    """, {
            'project_id': project_id,
            'neuron_ids': neuron_ids,
            'neuron_names': neuron_names,
        })

    rows = cursor.fetchall()
    updated_ids = [r[0] for r in rows]
    old_names = [r[1] for r in rows]
    new_names = [r[2] for r in rows]

    # Insert log entry and return successfully
    if len(updated_ids) == 1:
        insert_into_log(
            project_id, request.user.id, "rename_neuron", None,
            f"Renamed neuron with ID {neuron_ids[0]} from {old_names[0]} to {neuron_names[0]}"
        )
    else:
        insert_into_log(
            project_id, request.user.id, "rename_neuron", None,
            f"Renamed {len(neuron_ids)} neurons. IDs: {', '.join(str(uid) for uid in updated_ids)} Old names: {', '.join(old_names)} New names: {', '.join(new_names)}"
        )

    return JsonResponse({
        'success': True,
        'renamed_neurons': updated_ids,
        'old_names': old_names
    })
Beispiel #24
0
def update_confidence(request, project_id=None, treenode_id=None):
    """Update confidence of edge between a node to either its parent or its
    connectors.

    The connection between a node and its parent or the connectors it is linked
    to can be rated with a confidence value in the range 1-5. If connector links
    should be updated, one can limit the affected connections to a specific
    connector. Returned is an object, mapping updated partners to their old
    confidences.
    ---
    parameters:
      - name: new_confidence
        description: New confidence, value in range 1-5
        type: integer
        required: true
      - name: to_connector
        description: Whether all linked connectors instead of parent should be updated
        type: boolean
        required: false
      - name: partner_ids
        description: Limit update to a set of connectors if to_connector is true
        type: array
        items: integer
        required: false
      - name: partner_confidences
        description: Set different confidences to connectors in <partner_ids>
        type: array
        items: integer
        required: false
    type:
        message:
            type: string
            required: true
        updated_partners:
            type: object
            required: true
    """
    tnid = int(treenode_id)
    can_edit_treenode_or_fail(request.user, project_id, tnid)
    cursor = connection.cursor()

    state.validate_state(tnid,
                         request.POST.get('state'),
                         node=True,
                         lock=True,
                         cursor=cursor)

    to_connector = request.POST.get('to_connector', 'false') == 'true'
    partner_ids = get_request_list(request.POST, 'partner_ids', None, int)
    partner_confidences = get_request_list(request.POST, 'partner_confidences',
                                           None, int)

    new_confidence = int(request.POST.get('new_confidence', 0))

    # If partner confidences are specified, make sure there are exactly as many
    # as there are partners. Otherwise validate passed in confidence
    if partner_ids and partner_confidences:
        if len(partner_confidences) != len(partner_ids):
            raise ValueError("There have to be as many partner confidences as"
                             "there are partner IDs")
    else:
        if new_confidence < 1 or new_confidence > 5:
            raise ValueError('Confidence not in range 1-5 inclusive.')
        if partner_ids:
            # Prepare new confidences for connector query
            partner_confidences = (new_confidence, ) * len(partner_ids)

    if to_connector:
        if partner_ids:
            partner_template = ",".join(("(%s,%s)", ) * len(partner_ids))
            partner_data = [
                p for v in zip(partner_ids, partner_confidences) for p in v
            ]
            cursor.execute(
                '''
                UPDATE treenode_connector tc
                SET confidence = target.new_confidence
                FROM (SELECT x.id, x.confidence AS old_confidence,
                             new_values.confidence AS new_confidence
                      FROM treenode_connector x
                      JOIN (VALUES {}) new_values(cid, confidence)
                      ON x.connector_id = new_values.cid
                      WHERE x.treenode_id = %s) target
                WHERE tc.id = target.id
                RETURNING tc.connector_id, tc.edition_time, target.old_confidence
            '''.format(partner_template), partner_data + [tnid])
        else:
            cursor.execute(
                '''
                UPDATE treenode_connector tc
                SET confidence = %s
                FROM (SELECT x.id, x.confidence AS old_confidence
                      FROM treenode_connector x
                      WHERE treenode_id = %s) target
                WHERE tc.id = target.id
                RETURNING tc.connector_id, tc.edition_time, target.old_confidence
            ''', (new_confidence, tnid))
    else:
        cursor.execute(
            '''
            UPDATE treenode t
            SET confidence = %s, editor_id = %s
            FROM (SELECT x.id, x.confidence AS old_confidence
                  FROM treenode x
                  WHERE id = %s) target
            WHERE t.id = target.id
            RETURNING t.parent_id, t.edition_time, target.old_confidence
        ''', (new_confidence, request.user.id, tnid))

    updated_partners = cursor.fetchall()
    if len(updated_partners) > 0:
        location = Location.objects.filter(id=tnid).values_list(
            'location_x', 'location_y', 'location_z')[0]
        insert_into_log(project_id, request.user.id, "change_confidence",
                        location, "Changed to %s" % new_confidence)
        return JsonResponse({
            'message': 'success',
            'updated_partners': {
                r[0]: {
                    'edition_time': r[1],
                    'old_confidence': r[2]
                }
                for r in updated_partners
            }
        })

    # Else, signal error
    if to_connector:
        raise ValueError('Failed to update confidence between treenode %s and '
                         'connector.' % tnid)
    else:
        raise ValueError('Failed to update confidence at treenode %s.' % tnid)
Beispiel #25
0
def update_confidence(request, project_id=None, treenode_id=None):
    """Update confidence of edge between a node to either its parent or its
    connectors.

    The connection between a node and its parent or the connectors it is linked
    to can be rated with a confidence value in the range 1-5. If connector links
    should be updated, one can limit the affected connections to a specific
    connector. Returned is an object, mapping updated partners to their old
    confidences.
    ---
    parameters:
      - name: new_confidence
        description: New confidence, value in range 1-5
        type: integer
        required: true
      - name: to_connector
        description: Whether all linked connectors instead of parent should be updated
        type: boolean
        required: false
      - name: partner_ids
        description: Limit update to a set of connectors if to_connector is true
        type: array
        items: integer
        required: false
      - name: partner_confidences
        description: Set different confidences to connectors in <partner_ids>
        type: array
        items: integer
        required: false
    type:
        message:
            type: string
            required: true
        updated_partners:
            type: object
            required: true
    """
    tnid = int(treenode_id)
    can_edit_treenode_or_fail(request.user, project_id, tnid)
    cursor = connection.cursor()

    state.validate_state(tnid, request.POST.get('state'),
            node=True, lock=True, cursor=cursor)

    to_connector = get_request_bool(request.POST, 'to_connector', False)
    partner_ids = get_request_list(request.POST, 'partner_ids', None, int)
    partner_confidences = get_request_list(request.POST, 'partner_confidences',
            None, int)

    new_confidence = int(request.POST.get('new_confidence', 0))

    # If partner confidences are specified, make sure there are exactly as many
    # as there are partners. Otherwise validate passed in confidence
    if partner_ids and partner_confidences:
        if len(partner_confidences) != len(partner_ids):
            raise ValueError("There have to be as many partner confidences as"
                             "there are partner IDs")
    else:
        if new_confidence < 1 or new_confidence > 5:
            raise ValueError('Confidence not in range 1-5 inclusive.')
        if partner_ids:
            # Prepare new confidences for connector query
            partner_confidences = (new_confidence,) * len(partner_ids)

    if to_connector:
        if partner_ids:
            partner_template = ",".join(("(%s,%s)",) * len(partner_ids))
            partner_data = [p for v in zip(partner_ids, partner_confidences) for p in v]
            cursor.execute('''
                UPDATE treenode_connector tc
                SET confidence = target.new_confidence
                FROM (SELECT x.id, x.confidence AS old_confidence,
                             new_values.confidence AS new_confidence
                      FROM treenode_connector x
                      JOIN (VALUES {}) new_values(cid, confidence)
                      ON x.connector_id = new_values.cid
                      WHERE x.treenode_id = %s) target
                WHERE tc.id = target.id
                RETURNING tc.connector_id, tc.edition_time, target.old_confidence
            '''.format(partner_template), partner_data + [tnid])
        else:
            cursor.execute('''
                UPDATE treenode_connector tc
                SET confidence = %s
                FROM (SELECT x.id, x.confidence AS old_confidence
                      FROM treenode_connector x
                      WHERE treenode_id = %s) target
                WHERE tc.id = target.id
                RETURNING tc.connector_id, tc.edition_time, target.old_confidence
            ''', (new_confidence, tnid))
    else:
        cursor.execute('''
            UPDATE treenode t
            SET confidence = %s, editor_id = %s
            FROM (SELECT x.id, x.confidence AS old_confidence
                  FROM treenode x
                  WHERE id = %s) target
            WHERE t.id = target.id
            RETURNING t.parent_id, t.edition_time, target.old_confidence
        ''', (new_confidence, request.user.id, tnid))

    updated_partners = cursor.fetchall()
    if len(updated_partners) > 0:
        location = Location.objects.filter(id=tnid).values_list(
                'location_x', 'location_y', 'location_z')[0]
        insert_into_log(project_id, request.user.id, "change_confidence",
                location, "Changed to %s" % new_confidence)
        return JsonResponse({
            'message': 'success',
            'updated_partners': {
                r[0]: {
                    'edition_time': r[1],
                    'old_confidence': r[2]
                } for r in updated_partners
            }
        })

    # Else, signal error
    if to_connector:
        raise ValueError('Failed to update confidence between treenode %s and '
                'connector.' % tnid)
    else:
        raise ValueError('Failed to update confidence at treenode %s.' % tnid)
Beispiel #26
0
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')
Beispiel #27
0
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())))