Example #1
0
def delete_link(request, project_id=None):
    connector_id = int(request.POST.get('connector_id', 0))
    treenode_id = int(request.POST.get('treenode_id', 0))

    cursor = connection.cursor()
    # Make sure the back-end is in the expected state
    state.validate_state([treenode_id, connector_id], request.POST.get('state'),
            multinode=True, lock=True, cursor=cursor)

    links = TreenodeConnector.objects.filter(
        connector=connector_id,
        treenode=treenode_id).select_related('relation')

    if links.count() == 0:
        raise ValueError('Couldn\'t find link between connector {} '
                'and node {}'.format(connector_id, treenode_id))

    link = links[0]

    # Could be done by filtering above when obtaining the links,
    # but then one cannot distinguish between the link not existing
    # and the user_id not matching or not being superuser.
    can_edit_or_fail(request.user, link.id, 'treenode_connector')

    deleted_link_id = link.id
    link.delete()
    return JsonResponse({
        'link_id': deleted_link_id,
        'link_type_id': link.relation.id,
        'link_type': link.relation.relation_name,
        'result': 'Removed treenode to connector link'
    })
Example #2
0
def delete_link(request, project_id=None):
    connector_id = int(request.POST.get('connector_id', 0))
    treenode_id = int(request.POST.get('treenode_id', 0))

    cursor = connection.cursor()
    # Make sure the back-end is in the expected state
    state.validate_state([treenode_id, connector_id],
                         request.POST.get('state'),
                         multinode=True,
                         lock=True,
                         cursor=cursor)

    links = TreenodeConnector.objects.filter(
        connector=connector_id,
        treenode=treenode_id).select_related('relation')

    if links.count() == 0:
        raise ValueError('Couldn\'t find link between connector {} '
                         'and node {}'.format(connector_id, treenode_id))

    link = links[0]

    # Could be done by filtering above when obtaining the links,
    # but then one cannot distinguish between the link not existing
    # and the user_id not matching or not being superuser.
    can_edit_or_fail(request.user, link.id, 'treenode_connector')

    deleted_link_id = link.id
    link.delete()
    return JsonResponse({
        'link_id': deleted_link_id,
        'link_type_id': link.relation.id,
        'link_type': link.relation.relation_name,
        'result': 'Removed treenode to connector link'
    })
Example #3
0
def delete_connector(request, project_id=None):
    connector_id = int(request.POST.get("connector_id", 0))
    can_edit_or_fail(request.user, connector_id, 'connector')
    Connector.objects.filter(id=connector_id).delete()
    return HttpResponse(json.dumps({
        'message': 'Removed connector and class_instances',
        'connector_id': connector_id}))
Example #4
0
def check_new_annotations(project_id, user, entity_id, annotation_set):
    """ With respect to annotations, the new annotation set is only valid if the
    user doesn't remove annotations for which (s)he has no permissions.
    """
    # Get current annotation links
    annotation_links = ClassInstanceClassInstance.objects.filter(
            project_id=project_id,
            class_instance_b__class_column__class_name='annotation',
            relation__relation_name='annotated_with',
            class_instance_a_id=entity_id).values_list(
                    'class_instance_b__name', 'id', 'user')

    # Build annotation name indexed dict to the link's id and user
    annotations = {l[0]:(l[1], l[2]) for l in annotation_links}
    current_annotation_set = frozenset(annotations.keys())

    # If the current annotation set is not included completely in the new
    # set, we have to check if the user has permissions to edit the missing
    # annotations.
    removed_annotations = current_annotation_set - annotation_set
    for rl in removed_annotations:
        try:
            can_edit_or_fail(user, annotations[rl][0],
                        'class_instance_class_instance')
        except:
            return False

    # Otherwise, everything is fine
    return True
Example #5
0
    def delete(self, request, project_id, landmarkgroup_id):
        """Delete one particular landmark group.
        ---
        parameters:
        - name: project_id
          description: The project the landmark group is part of.
          type: integer
          paramType: path
          required: true
        - name: landmarkgroup_id
          description: The ID of the landmark group to delete.
          required: true
          type: integer
          paramType: path
        """
        can_edit_or_fail(request.user, landmarkgroup_id, 'class_instance')

        landmarkgroup_class = Class.objects.get(project_id=project_id,
                                                class_name='landmarkgroup')
        landmarkgroup = get_object_or_404(ClassInstance,
                                          pk=landmarkgroup_id,
                                          project_id=project_id,
                                          class_column=landmarkgroup_class)
        landmarkgroup.delete()

        landmarkgroup.id = None

        serializer = BasicClassInstanceSerializer(landmarkgroup)
        return Response(serializer.data)
Example #6
0
def remove_annotation(request, project_id=None, annotation_id=None):
    """ Removes an annotation from one or more entities.
    """
    p = get_object_or_404(Project, pk=project_id)

    entity_ids = [
        int(v) for k, v in request.POST.iteritems()
        if k.startswith('entity_ids[')
    ]

    # Get CICI instance representing the link
    cici_n_a = ClassInstanceClassInstance.objects.filter(
        project=p,
        class_instance_a__id__in=entity_ids,
        class_instance_b__id=annotation_id)
    # Make sure the current user has permissions to remove the annotation.
    missed_cicis = []
    cicis_to_delete = []
    for cici in cici_n_a:
        try:
            can_edit_or_fail(request.user, cici.id,
                             'class_instance_class_instance')
            cicis_to_delete.append(cici)
        except Exception:
            # Remember links for which permissions are missing
            missed_cicis.append(cici)

    # Remove link between entity and annotation for all links on which the user
    # the necessary permissions has.
    if cicis_to_delete:
        ClassInstanceClassInstance.objects \
                .filter(id__in=[cici.id for cici in cicis_to_delete]) \
                .delete()

    if len(cicis_to_delete) > 1:
        message = "Removed annotation from %s entities." % len(cicis_to_delete)
    elif len(cicis_to_delete) == 1:
        message = "Removed annotation from one entity."
    else:
        message = "No annotation removed."

    if missed_cicis:
        message += " Couldn't de-annotate %s entities, due to the lack of " \
                "permissions." % len(missed_cicis)

    # Remove the annotation class instance, regardless of the owner, if there
    # are no more links to it
    annotation_links = ClassInstanceClassInstance.objects.filter(
        project=p, class_instance_b__id=annotation_id)
    num_annotation_links = annotation_links.count()
    if num_annotation_links == 0:
        ClassInstance.objects.get(pk=annotation_id).delete()
        message += " Also removed annotation instance, because it isn't used " \
                "anywhere else."
    else:
        message += " There are %s links left to this annotation." \
                % num_annotation_links

    return HttpResponse(json.dumps({'message': message}),
                        content_type='text/json')
Example #7
0
    def post(self, request: HttpRequest, project_id,
             sampler_id) -> JsonResponse:
        """Set fields of a particular sampler.
        ---
        parameters:
         - name: project_id
           description: The project to operate in.
           type: integer
           paramType: path
           required: false
         - name: sampler_id
           description: The sampler to return.
           type: integer
           paramType: path
           required: false
         - name: leaf_handling_mode
           description: Optional flag to include all domains of all result sampler results.
           type: boolean
           paramType: form
           required: false
        """
        sampler_id = int(sampler_id)
        can_edit_or_fail(request.user, sampler_id, 'catmaid_sampler')

        sampler = Sampler.objects.get(pk=sampler_id)

        leaf_handling_mode = request.POST.get('leaf_handling_mode')
        if leaf_handling_mode and leaf_handling_mode in known_leaf_modes:
            sampler.leaf_segment_handling = leaf_handling_mode
            sampler.save()

        return JsonResponse(serialize_sampler(sampler))
Example #8
0
def delete_connector(request:HttpRequest, project_id=None) -> JsonResponse:
    connector_id = int(request.POST.get("connector_id", 0))
    can_edit_or_fail(request.user, connector_id, 'connector')

    # Check provided state
    cursor = connection.cursor()
    state.validate_state(connector_id, request.POST.get('state'),
            node=True, c_links=True, lock=True, cursor=cursor)

    # Get connector and partner information
    connectors = list(Connector.objects.filter(id=connector_id).prefetch_related(
            'treenodeconnector_set', 'treenodeconnector_set__relation'))
    if 1 != len(connectors):
        raise ValueError("Couldn't find exactly one connector with ID #" +
                str(connector_id))
    connector = connectors[0]
    # TODO: Check how many queries here are generated
    partners = [{
        'id': p.treenode_id,
        'edition_time': p.treenode.edition_time,
        'rel': p.relation.relation_name,
        'rel_id': p.relation.id,
        'confidence': p.confidence,
        'link_id': p.id
    } for p in connector.treenodeconnector_set.all()]
    connector.delete()
    return JsonResponse({
        'message': 'Removed connector and class_instances',
        'connector_id': connector_id,
        'confidence': connector.confidence,
        'x': connector.location_x,
        'y': connector.location_y,
        'z': connector.location_z,
        'partners': partners
    })
Example #9
0
def delete_connector(request, project_id=None):
    connector_id = int(request.POST.get("connector_id", 0))
    can_edit_or_fail(request.user, connector_id, 'connector')

    # Check provided state
    cursor = connection.cursor()
    state.validate_state(connector_id, request.POST.get('state'),
            node=True, c_links=True, lock=True, cursor=cursor)

    # Get connector and partner information
    connectors = list(Connector.objects.filter(id=connector_id).prefetch_related(
            'treenodeconnector_set', 'treenodeconnector_set__relation'))
    if 1 != len(connectors):
        raise ValueError("Couldn't find exactly one connector with ID #" +
                connector_id)
    connector = connectors[0]
    # TODO: Check how many queries here are generated
    partners = [{
        'id': p.treenode_id,
        'edition_time': p.treenode.edition_time,
        'rel': p.relation.relation_name,
        'rel_id': p.relation.id,
        'confidence': p.confidence,
        'link_id': p.id
    } for p in connector.treenodeconnector_set.all()]
    connector.delete()
    return JsonResponse({
        'message': 'Removed connector and class_instances',
        'connector_id': connector_id,
        'confidence': connector.confidence,
        'x': connector.location_x,
        'y': connector.location_y,
        'z': connector.location_z,
        'partners': partners
    })
Example #10
0
def delete_sampler(request, project_id, sampler_id):
    """Delete a sampler if permissions allow it.
    """
    can_edit_or_fail(request.user, sampler_id, "catmaid_sampler")
    sampler = Sampler.objects.get(id=sampler_id)
    sampler.delete()

    return JsonResponse({'deleted_sampler_id': sampler_id})
Example #11
0
def delete_connector(request, project_id=None):
    connector_id = int(request.POST.get("connector_id", 0))
    can_edit_or_fail(request.user, connector_id, 'connector')
    Connector.objects.filter(id=connector_id).delete()
    return HttpResponse(
        json.dumps({
            'message': 'Removed connector and class_instances',
            'connector_id': connector_id
        }))
Example #12
0
def remove_annotation(request, project_id=None, annotation_id=None):
    """ Removes an annotation from one or more entities.
    """
    p = get_object_or_404(Project, pk=project_id)

    entity_ids = [int(v) for k,v in request.POST.iteritems()
            if k.startswith('entity_ids[')]

    # Get CICI instance representing the link
    cici_n_a = ClassInstanceClassInstance.objects.filter(project=p,
            class_instance_a__id__in=entity_ids,
            class_instance_b__id=annotation_id)
    # Make sure the current user has permissions to remove the annotation.
    missed_cicis = []
    cicis_to_delete = []
    for cici in cici_n_a:
        try:
            can_edit_or_fail(request.user, cici.id,
                             'class_instance_class_instance')
            cicis_to_delete.append(cici)
        except Exception:
            # Remember links for which permissions are missing
            missed_cicis.append(cici)

    # Remove link between entity and annotation for all links on which the user
    # the necessary permissions has.
    if cicis_to_delete:
        ClassInstanceClassInstance.objects \
                .filter(id__in=[cici.id for cici in cicis_to_delete]) \
                .delete()

    if len(cicis_to_delete) > 1:
        message = "Removed annotation from %s entities." % len(cicis_to_delete)
    elif len(cicis_to_delete) == 1:
        message = "Removed annotation from one entity."
    else:
        message = "No annotation removed."

    if missed_cicis:
        message += " Couldn't de-annotate %s entities, due to the lack of " \
                "permissions." % len(missed_cicis)

    # Remove the annotation class instance, regardless of the owner, if there
    # are no more links to it
    annotation_links = ClassInstanceClassInstance.objects.filter(project=p,
            class_instance_b__id=annotation_id)
    num_annotation_links = annotation_links.count()
    if num_annotation_links == 0:
        ClassInstance.objects.get(pk=annotation_id).delete()
        message += " Also removed annotation instance, because it isn't used " \
                "anywhere else."
    else:
        message += " There are %s links left to this annotation." \
                % num_annotation_links

    return HttpResponse(json.dumps({'message': message}), content_type='text/json')
Example #13
0
def recompute_config(request, project_id, config_id):
    """Recompute the similarity matrix of the passed in NBLAST configuration.
    """
    can_edit_or_fail(request.user, config_id, 'nblast_config')
    task = compute_nblast_config.delay(config_id, request.user.id)

    return JsonResponse({
        'status': 'queued',
        'task_id': task.task_id
    })
Example #14
0
def delete_sampler(request, project_id, sampler_id):
    """Delete a sampler if permissions allow it.
    """
    can_edit_or_fail(request.user, sampler_id, "catmaid_sampler")
    sampler = Sampler.objects.get(id=sampler_id)
    sampler.delete()

    return JsonResponse({
        'deleted_sampler_id': sampler_id
    })
Example #15
0
 def delete(self, request: Request, project_id, link_id) -> Response:
     """Delete a deep-links available to the client.
     ---
     serializer: DeepLinkSerializer
     """
     try:
         deep_link = DeepLink.objects.get(project_id=project_id, id=link_id)
         can_edit_or_fail(request.user, deep_link.id, 'catmaid_deep_link')
         deep_link.delete()
         return Response({'deleted_id': link_id})
     except DeepLink.DoesNotExist:
         return Response('Link not found', status=status.HTTP_404_NOT_FOUND)
Example #16
0
def recompute_similarity(request, project_id, similarity_id):
    """Recompute the similarity matrix of the passed in NBLAST configuration.
    """
    simplify = get_request_bool(request.GET, 'simplify', True)
    required_branches = int(request.GET.get('required_branches', '10'))
    can_edit_or_fail(request.user, similarity_id, 'nblast_similarity')
    task = compute_nblast.delay(project_id, request.user.id, similarity_id,
            remove_target_duplicates=True, simplify=simplify,
            required_branches=required_branches)

    return JsonResponse({
        'status': 'queued',
        'task_id': task.task_id
    })
Example #17
0
    def delete(self, request, project_id, similarity_id):
        """Delete a NBLAST similarity task.
        """
        can_edit_or_fail(request.user, similarity_id, 'nblast_similarity')
        similarity = NblastSimilarity.objects.get(pk=similarity_id, project_id=project_id)

        cursor = connection.cursor()
        cursor.execute("""
            DELETE FROM nblast_similarity
            WHERE project_id=%s AND id = %s
        """, [project_id, similarity.id])

        return JsonResponse({
            'deleted': True,
            'config_id': similarity.id
        })
Example #18
0
    def delete(self, request, project_id, config_id):
        """Delete a NBLAST configuration.
        """
        can_edit_or_fail(request.user, config_id, 'nblast_config')
        config = NblastConfig.objects.get(pk=config_id, project_id=project_id)

        cursor = connection.cursor()
        cursor.execute("""
            DELETE FROM nblast_config
            WHERE project_id=%s AND id = %s
        """, [project_id, config_id])

        return JsonResponse({
            'deleted': True,
            'config_id': config_id
        })
Example #19
0
    def delete(self, request: HttpRequest, project_id,
               pointcloud_id) -> JsonResponse:
        """Delete a point cloud.
        """
        can_edit_or_fail(request.user, pointcloud_id, 'pointcloud')
        pointcloud = PointCloud.objects.get(pk=pointcloud_id,
                                            project_id=project_id)

        cursor = connection.cursor()
        cursor.execute(
            """
            DELETE FROM pointcloud
            CASCADE
            WHERE project_id=%s AND id = %s
        """, [project_id, pointcloud_id])

        return JsonResponse({'deleted': True, 'pointcloud_id': pointcloud.id})
Example #20
0
    def delete(self, request, project_id, pointcloud_id):
        """Delete a point cloud.
        """
        can_edit_or_fail(request.user, pointcloud_id, 'pointcloud')
        pointcloud = PointCloud.objects.get(pk=pointcloud_id, project_id=project_id)

        cursor = connection.cursor()
        cursor.execute("""
            DELETE FROM pointcloud
            CASCADE
            WHERE project_id=%s AND id = %s
        """, [project_id, pointcloud_id])

        return JsonResponse({
            'deleted': True,
            'pointcloud_id': pointcloud.id
        })
Example #21
0
def _remove_annotation(user, project_id, entity_ids, annotation_id):
    """Remove an annotation made by a certain user in a given project on a set
    of entities (usually neurons and annotations). Returned is a 4-tuple which
    holds the deleted annotation links, the list of links that couldn't be
    deleted due to lack of permission, if the annotation itself was removed
    (because it wasn't used anymore) and how many uses of this annotation are
    left.
    """
    p = get_object_or_404(Project, pk=project_id)
    relations = dict(
        Relation.objects.filter(project_id=project_id).values_list(
            'relation_name', 'id'))

    # Get CICI instance representing the link
    cici_n_a = ClassInstanceClassInstance.objects.filter(
        project=p,
        relation_id=relations['annotated_with'],
        class_instance_a__id__in=entity_ids,
        class_instance_b__id=annotation_id)
    # Make sure the current user has permissions to remove the annotation.
    missed_cicis = []
    cicis_to_delete = []
    for cici in cici_n_a:
        try:
            can_edit_or_fail(user, cici.id, 'class_instance_class_instance')
            cicis_to_delete.append(cici)
        except Exception:
            # Remember links for which permissions are missing
            missed_cicis.append(cici)

    # Remove link between entity and annotation for all links on which the user
    # the necessary permissions has.
    if cicis_to_delete:
        ClassInstanceClassInstance.objects \
                .filter(id__in=[cici.id for cici in cicis_to_delete]) \
                .delete()

    # Remove the annotation class instance, regardless of the owner, if there
    # are no more links to it
    annotated_with = Relation.objects.get(project_id=project_id,
                                          relation_name='annotated_with')
    deleted, num_left = delete_annotation_if_unused(project_id, annotation_id,
                                                    annotated_with)

    return cicis_to_delete, missed_cicis, deleted, num_left
Example #22
0
def delete_link(request, project_id=None):
    connector_id = int(request.POST.get('connector_id', 0))
    treenode_id = int(request.POST.get('treenode_id', 0))

    links = TreenodeConnector.objects.filter(
        connector=connector_id,
        treenode=treenode_id)

    if links.count() == 0:
        return HttpResponse(json.dumps({'error': 'Failed to delete connector #%s from geometry domain.' % connector_id}))

    # Could be done by filtering above when obtaining the links,
    # but then one cannot distinguish between the link not existing
    # and the user_id not matching or not being superuser.
    can_edit_or_fail(request.user, links[0].id, 'treenode_connector')

    links[0].delete()
    return HttpResponse(json.dumps({'result': 'Removed treenode to connector link'}))
Example #23
0
def delete_link(request, project_id=None):
    connector_id = int(request.POST.get('connector_id', 0))
    treenode_id = int(request.POST.get('treenode_id', 0))

    links = TreenodeConnector.objects.filter(
        connector=connector_id,
        treenode=treenode_id)

    if links.count() == 0:
        return HttpResponse(json.dumps({'error': 'Failed to delete connector #%s from geometry domain.' % connector_id}))

    # Could be done by filtering above when obtaining the links,
    # but then one cannot distinguish between the link not existing
    # and the user_id not matching or not being superuser.
    can_edit_or_fail(request.user, links[0].id, 'treenode_connector')

    links[0].delete()
    return HttpResponse(json.dumps({'result': 'Removed treenode to connector link'}))
Example #24
0
    def delete(self, request, project_id, landmarkgroup_id, location_id):
        """Remove the link between a location and a landmark group.
        ---
        parameters:
          - name: project_id
            description: Project of landmark group
            type: integer
            paramType: path
            required: true
          - name: landmarkgroup_id
            description: The landmark group to link
            type: integer
            paramType: path
            required: true
          - name: location_id
            description: Existing location ID
            type: integer
            paramType: path
            required: true
        """
        point = Point.objects.get(project_id=project_id, pk=location_id)
        landmarkgroup = ClassInstance.objects.get(
            project_id=project_id,
            pk=landmarkgroup_id,
            class_column=Class.objects.get(project_id=project_id,
                                           class_name="landmarkgroup"))

        pci = PointClassInstance.objects.get(
            point=point,
            user=request.user,
            class_instance=landmarkgroup,
            project_id=project_id,
            relation=Relation.objects.get(project_id=project_id,
                                          relation_name="annotated_with"))
        can_edit_or_fail(request.user, pci.id, 'point_class_instance')
        pci_id = pci.id
        pci.delete()

        return Response({
            'link_id': pci_id,
            'point_id': point.id,
            'landmarkgroup_id': landmarkgroup.id
        })
Example #25
0
def _remove_annotation(user, project_id, entity_ids, annotation_id):
    """Remove an annotation made by a certain user in a given project on a set
    of entities (usually neurons and annotations). Returned is a 4-tuple which
    holds the deleted annotation links, the list of links that couldn't be
    deleted due to lack of permission, if the annotation itself was removed
    (because it wasn't used anymore) and how many uses of this annotation are
    left.
    """
    p = get_object_or_404(Project, pk=project_id)
    relations = dict(Relation.objects.filter(
        project_id=project_id).values_list('relation_name', 'id'))

    # Get CICI instance representing the link
    cici_n_a = ClassInstanceClassInstance.objects.filter(project=p,
            relation_id=relations['annotated_with'],
            class_instance_a__id__in=entity_ids,
            class_instance_b__id=annotation_id)
    # Make sure the current user has permissions to remove the annotation.
    missed_cicis = []
    cicis_to_delete = []
    for cici in cici_n_a:
        try:
            can_edit_or_fail(user, cici.id, 'class_instance_class_instance')
            cicis_to_delete.append(cici)
        except Exception:
            # Remember links for which permissions are missing
            missed_cicis.append(cici)

    # Remove link between entity and annotation for all links on which the user
    # the necessary permissions has.
    if cicis_to_delete:
        ClassInstanceClassInstance.objects \
                .filter(id__in=[cici.id for cici in cicis_to_delete]) \
                .delete()

    # Remove the annotation class instance, regardless of the owner, if there
    # are no more links to it
    annotated_with = Relation.objects.get(project_id=project_id,
            relation_name='annotated_with')
    deleted, num_left = delete_annotation_if_unused(project_id, annotation_id,
                                                    annotated_with)

    return cicis_to_delete, missed_cicis, deleted, num_left
Example #26
0
    def post(self, request, project_id, landmark_id):
        """Update an existing landmark.

        Currently, only the name can be updated.
        ---
        parameters:
        - name: project_id
          description: The project the landmark is part of.
          type: integer
          paramType: path
          required: true
        - name: landmark_id
          description: The ID of the landmark.
          required: true
          type: integer
          paramType: path
        - name: name
          description: The name of the landmark.
          required: false
          type: string
          paramType: form
        """
        can_edit_or_fail(request.user, landmark_id, 'class_instance')
        name = request.data.get('name')
        if not name:
            raise ValueError('Need name for update')

        landmark_class = Class.objects.get(project_id=project_id,
                                           class_name="landmark")
        landmark = get_object_or_404(ClassInstance,
                                     pk=landmark_id,
                                     project_id=project_id,
                                     class_column=landmark_class)
        landmark.name = name
        landmark.save()

        landmark.id = None

        serializer = BasicClassInstanceSerializer(landmark)
        return Response(serializer.data)
Example #27
0
    def delete(request, project_id, point_id):
        """Delete one particular point.
        ---
        parameters:
          - name: project_id
            description: Project point is part of
            type: integer
            paramType: path
            required: true
          - name: point_id
            description: ID of point
            type: integer
            paramType: path
            required: true
        """
        can_edit_or_fail(request.user, point_id, 'point')

        point = get_object_or_404(Point, pk=point_id, project_id=project_id)
        point.delete()

        point.id = None

        serializer = PointSerializer(point)
        return Response(serializer.data)
Example #28
0
    def delete(request, project_id, point_id):
        """Delete one particular point.
        ---
        parameters:
          - name: project_id
            description: Project point is part of
            type: integer
            paramType: path
            required: true
          - name: point_id
            description: ID of point
            type: integer
            paramType: path
            required: true
        """
        can_edit_or_fail(request.user, point_id, 'point')

        point = get_object_or_404(Point, pk=point_id, project_id=project_id)
        point.delete()

        point.id = None

        serializer = PointSerializer(point)
        return Response(serializer.data)
Example #29
0
    def delete(self, request, project_id, landmark_id, location_id):
        """Delete the link between a location and a landmark. If the last link
        to a location is deleted, the location is removed as well.
        ---
        parameters:
          - name: project_id
            description: Project of landmark group
            type: integer
            paramType: path
            required: true
          - name: landmark_id
            description: The landmark to unlink
            type: integer
            paramType: path
            required: true
          - name: location_id
            description: The location to unlink
            paramType: path
            type: integer
            required: true
        """
        can_edit_or_fail(request.user, landmark_id, 'class_instance')
        landmark = ClassInstance.objects.get(project_id=project_id,
                                             pk=int(landmark_id))

        pci = PointClassInstance.objects.get(
            project_id=project_id,
            class_instance=landmark,
            point_id=int(location_id),
            relation=Relation.objects.get(project_id=project_id,
                                          relation_name='annotated_with'))
        can_edit_or_fail(request.user, pci.id, 'point_class_instance')
        pci_id = pci.id
        pci.delete()

        deleted_point = False
        remaining_pci = PointClassInstance.objects.filter(
            point_id=int(location_id))
        if remaining_pci.count() == 0:
            try:
                can_edit_or_fail(request.user, point.id, 'point')
                Point.objects.get(pk=int(location_id)).delete()
                deleted_point = True
            except:
                pass

        return Response({
            'link_id': pci_id,
            'landmark_id': pci.class_instance_id,
            'point_id': pci.point_id,
            'deleted_point': deleted_point
        })
Example #30
0
    def post(self, request, project_id, landmarkgroup_id):
        """Update an existing landmark group.

        Currently, only the name and group members can be updated. Edit
        permissions are only needed when removing group members.
        ---
        parameters:
        - name: project_id
          description: The project the landmark group is part of.
          type: integer
          paramType: path
          required: true
        - name: landmark_id
          description: The ID of the landmark group.
          required: true
          type: integer
          paramType: path
        - name: name
          description: The new name of the landmark group.
          required: false
          type: string
          paramType: form
        - name: members
          description: The new members of the landmark group.
          required: false
          type: array
          items:
            type: integer
          paramType: form
        """
        needs_edit_permissions = False
        project_id = int(project_id)
        if not project_id:
            raise ValueError("Need project ID")
        landmarkgroup_id = int(landmarkgroup_id)
        if not landmarkgroup_id:
            raise ValueError("Need landmark group ID")
        name = request.data.get('name')
        if request.data.get('members') == 'none':
            members = []
        else:
            members = get_request_list(request.data, 'members', map_fn=int)

        if not name and members == None:
            raise ValueError('Need name or members parameter for update')

        landmarkgroup_class = Class.objects.get(project_id=project_id,
                                                class_name='landmarkgroup')
        landmarkgroup = get_object_or_404(ClassInstance,
                                          pk=landmarkgroup_id,
                                          project_id=project_id,
                                          class_column=landmarkgroup_class)
        if name:
            landmarkgroup.name = name
            landmarkgroup.save()

        if members is not None:
            # Find out which members need to be added and which existing ones
            # need to be removed.
            current_members = set(
                get_landmark_group_members(project_id, [landmarkgroup_id]).get(
                    landmarkgroup_id, []))
            new_members = set(members)
            to_add = new_members - current_members
            to_remove = current_members - new_members

            if to_remove:
                needs_edit_permissions = True

            part_of = Relation.objects.get(project_id=project_id,
                                           relation_name='part_of')
            ClassInstanceClassInstance.objects.filter(
                project_id=project_id,
                class_instance_a__in=to_remove,
                class_instance_b_id=landmarkgroup_id,
                relation=part_of).delete()

            for landmark_id in to_add:
                ClassInstanceClassInstance.objects.create(
                    project_id=project_id,
                    class_instance_a_id=landmark_id,
                    class_instance_b_id=landmarkgroup_id,
                    relation=part_of,
                    user=request.user)

        if needs_edit_permissions:
            can_edit_or_fail(request.user, landmarkgroup_id, 'class_instance')

        serializer = BasicClassInstanceSerializer(landmarkgroup)
        return Response(serializer.data)
Example #31
0
    def post(self, request, project_id):
        """Import and link landmarks, landmark groups and locations.

        The passed in <data> parameter is a list of two-element lists, each
        representing a group along with its linked landmark and locations. The
        group is represented by its name and the members are a list of
        four-element lists, containing the landmark name and the location. This
        results in the following format:

        [[group_1_name, [[landmark_1_name, x, y, z], [landmark_2_name, x, y, z]]], ...]

        Note that this parameter has to be transmitted as a JSON encoded string.

        ---
        parameters:
        - name: project_id
          description: The project the landmark group is part of.
          type: integer
          paramType: path
          required: true
        - name: data
          description: The data to import.
          required: true
          type: string
          paramType: form
        - name: reuse_existing_groups
          description: Whether existing groups should be reused.
          type: boolean
          paramType: form
          defaultValue: false
          required: false
        - name: reuse_existing_landmarks
          description: Whether existing landmarks should be reused.
          type: boolean
          paramType: form
          defaultValue: false
          required: false
        - name: create_non_existing_groups
          description: Whether non-existing groups should be created.
          type: boolean
          paramType: form
          defaultValue: true
          required: false
        - name: create_non_existing_landmarks
          description: Whether non-existing landmarks should be created.
          type: boolean
          paramType: form
          defaultValue: true
          required: false
        """
        project_id = int(project_id)
        if not project_id:
            raise ValueError("Need project ID")
        reuse_existing_groups = request.data.get('reuse_existing_groups',
                                                 'false') == 'true'
        reuse_existing_landmarks = request.data.get('reuse_existing_landmarks',
                                                    'false') == 'true'
        create_non_existing_groups = request.data.get(
            'create_non_existing_groups', 'true') == 'true'
        create_non_existing_landmarks = request.data.get(
            'create_non_existing_landmarks', 'true') == 'true'

        # Make sure the data to import matches our expectations
        data = request.data.get('data')
        if not data:
            raise ValueError("Need data to import")
        data = json.loads(data)
        for n, (group_name, landmarks) in enumerate(data):
            if not group_name:
                raise ValueError("The {}. group doesn't have a name".format(n))
            if not landmarks:
                raise ValueError(
                    "Group {} doesn't contain any landmarks".format(
                        group_name))
            for m, link in enumerate(landmarks):
                if not link or len(link) != 4:
                    raise ValueError("The {}. link of the {}. group ({}) " \
                        "doesn't conform to the [ID, X, Y, Z] format.".format(m,
                        n, group_name))
                for ci in (1, 2, 3):
                    coordinate = link[ci]
                    value = float(coordinate)
                    if math.isnan(value):
                        raise ValueError("The {}. link of the {}. group ({}) " \
                            "doesn't have a valid {}. coordinate: {}.".format(
                            m, n, group_name, ci, coordinate))
                    link[ci] = value

        classes = get_class_to_id_map(project_id)
        relations = get_relation_to_id_map(project_id)
        landmark_class = classes['landmark']
        landmarkgroup_class = classes['landmarkgroup']
        part_of_relation = relations['part_of']
        annotated_with_relation = relations['annotated_with']

        landmarks = dict(
            (k.lower(), v) for k, v in ClassInstance.objects.filter(
                project_id=project_id,
                class_column=landmark_class).values_list('name', 'id'))
        landmarkgroups = dict(
            (k.lower(), v) for k, v in ClassInstance.objects.filter(
                project_id=project_id,
                class_column=landmarkgroup_class).values_list('name', 'id'))

        imported_groups = []

        # Keep track of which landmarks have been seen and were accepted.
        seen_landmarks = set()

        for n, (group_name, linked_landmarks) in enumerate(data):
            # Test if group exists already and raise error if they do and are
            # prevented from being reused (option).
            existing_group_id = landmarkgroups.get(group_name.lower())
            if existing_group_id:
                if n == 0:
                    if not reuse_existing_groups:
                        raise ValueError("Group \"{}\" exists already ({}).  Please" \
                                "remove it or enable group re-use.".format(
                                group_name, existing_group_id))
                    can_edit_or_fail(request.user, existing_group_id,
                                     'class_instance')
            elif create_non_existing_groups:
                group = ClassInstance.objects.create(
                    project_id=project_id,
                    class_column_id=landmarkgroup_class,
                    user=request.user,
                    name=group_name)
                existing_group_id = group.id
                landmarkgroups[group_name.lower()] = group.id
            else:
                raise ValueError("Group \"{}\" does not exist. Please create " \
                        "it or enable automatic creation/".format(group_name))

            imported_landmarks = []
            imported_group = {
                'id': existing_group_id,
                'name': group_name,
                'members': imported_landmarks
            }
            imported_groups.append(imported_group)

            for m, link in enumerate(linked_landmarks):
                landmark_name = link[0]
                x, y, z = link[1], link[2], link[3]
                existing_landmark_id = landmarks.get(landmark_name.lower())
                if existing_landmark_id:
                    # Test only on first look at landmark
                    if existing_landmark_id not in seen_landmarks:
                        if not reuse_existing_landmarks:
                            raise ValueError("Landmark \"{}\" exists already. " \
                                        "Please remove it or enable re-use of " \
                                        "existing landmarks.".format(landmark_name))
                        can_edit_or_fail(request.user, existing_landmark_id,
                                         'class_instance')
                elif create_non_existing_landmarks:
                    landmark = ClassInstance.objects.create(
                        project_id=project_id,
                        class_column_id=landmark_class,
                        user=request.user,
                        name=landmark_name)
                    existing_landmark_id = landmark.id
                    landmarks[landmark_name.lower()] = landmark.id
                else:
                    raise ValueError("Landmark \"{}\" does not exist. Please " \
                            "create it or enable automatic creation.".format(
                            landmark_name))
                seen_landmarks.add(existing_landmark_id)

                # Make sure the landmark is linked to the group
                landmark_link = ClassInstanceClassInstance.objects.get_or_create(
                    project_id=project_id,
                    relation_id=part_of_relation,
                    class_instance_a_id=existing_landmark_id,
                    class_instance_b_id=existing_group_id,
                    defaults={'user': request.user})

                # With an existing group and landmark in place, the location can
                # be linked (to both).
                point = Point.objects.create(project_id=project_id,
                                             user=request.user,
                                             editor=request.user,
                                             location_x=x,
                                             location_y=y,
                                             location_z=z)
                point_landmark = PointClassInstance.objects.create(
                    point=point,
                    user=request.user,
                    class_instance_id=existing_landmark_id,
                    project_id=project_id,
                    relation_id=annotated_with_relation)
                point_landmark_group = PointClassInstance.objects.create(
                    point=point,
                    user=request.user,
                    class_instance_id=existing_group_id,
                    project_id=project_id,
                    relation_id=annotated_with_relation)

                imported_landmarks.append({
                    'id': existing_landmark_id,
                    'name': landmark_name,
                    'x': x,
                    'y': y,
                    'z': z
                })

        return Response(imported_groups)
Example #32
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))
Example #33
0
    def post(request, project_id, point_id):
        """Update one particular point.

        Requires at least one field to change.
        ---
        parameters:
          - name: project_id
            description: Project point is part of
            type: integer
            paramType: path
            required: true
          - name: point_id
            description: ID of point
            type: integer
            paramType: path
            required: true
          - name: location_x
            description: X coordinate
            type: float
            paramType: form
            required: false
          - name: location_y
            description: Y coordinate
            type: float
            paramType: form
            required: false
          - name: location_z
            description: Z coordinate
            type: float
            paramType: form
            required: false
          - name: radius
            description: Optional radius
            type: float
            paramType: form
            required: false
          - name: confidence
            description: Optional confidence in [0,5]
            type: integer
            paramType: form
            required: false
        """
        can_edit_or_fail(request.user, point_id, 'point')

        updated_fields = {}
        if request.POST.has('x'):
            updated_fields['location_x'] = float(request.POST.get('x'))
        if request.POST.has('y'):
            updated_fields['location_y'] = float(request.POST.get('y'))
        if request.POST.has('z'):
            updated_fields['location_z'] = float(request.POST.get('z'))
        if request.POST.has('radius'):
            updated_fields['radius'] = float(request.POST.get('radius'))
        if request.POST.has('confidence'):
            confidence = max(min(int(request.POST.get('confidence')), 5), 0)
            updated_fields('confidence', confidence)

        if not updated_fields:
            raise ValueError('No field to modify provided')

        point = get_object_or_404(Point, pk=point_id, project_id=project_id)
        point.update(**updated_fields)
        point.save()

        serializer = PointSerializer(point)
        return Response(serializer.data)
Example #34
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)

        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))
Example #35
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))
Example #36
0
def label_update(request, project_id=None, location_id=None, ntype=None):
    """ location_id is the ID of a treenode or connector.
        ntype is either 'treenode' or 'connector'. """
    labeled_as_relation = Relation.objects.get(project=project_id,
                                               relation_name='labeled_as')
    p = get_object_or_404(Project, pk=project_id)

    # TODO will FAIL when a tag contains a coma by itself
    new_tags = request.POST['tags'].split(',')
    delete_existing_labels = request.POST.get('delete_existing',
                                              'true') == 'true'

    kwargs = {
        'relation': labeled_as_relation,
        'class_instance__class_column__class_name': 'label'
    }

    table = get_link_model(ntype)
    if 'treenode' == ntype:
        kwargs['treenode__id'] = location_id
        node = Treenode.objects.get(id=location_id)
    elif 'connector' == ntype:
        kwargs['connector__id'] = location_id
        node = Connector.objects.get(id=location_id)

    if not table:
        raise Http404('Unknown node type: "%s"' % (ntype, ))

    # Get the existing list of tags for the tree node/connector and delete any
    # that are not in the new list.
    existing_labels = table.objects.filter(
        **kwargs).select_related('class_instance__name')
    existing_names = set(ele.class_instance.name for ele in existing_labels)
    duplicate_labels = table.objects.filter(**kwargs).exclude(
        class_instance__name__in=new_tags).select_related(
            'class_instance__name')

    other_labels = []
    deleted_labels = []
    if delete_existing_labels:
        # Iterate over all labels that should get deleted to check permission
        # on each one. Remember each label that couldn't be deleted in the
        # other_labels array.
        for l in duplicate_labels:
            try:
                can_edit_or_fail(request.user, l.id, table._meta.db_table)
                if remove_label(l.id, ntype):
                    deleted_labels.append(l)
                else:
                    other_labels.append(l)
            except:
                other_labels.append(l)

        # Create change requests for labels associated to the treenode by other users
        for label in other_labels:
            change_request_params = {
                'type':
                'Remove Tag',
                'project':
                p,
                'user':
                request.user,
                'recipient':
                node.user,
                'location':
                Double3D(node.location_x, node.location_y, node.location_z),
                ntype:
                node,
                'description':
                "Remove tag '%s'" % label.class_instance.name,
                'validate_action':
                'from catmaid.control.label import label_exists\n' +
                'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype),
                'approve_action':
                'from catmaid.control.label import remove_label\n' +
                'remove_label(%s, "%s")' % (str(label.id), ntype)
            }
            ChangeRequest(**change_request_params).save()

    # Add any new labels.
    label_class = Class.objects.get(project=project_id, class_name='label')
    kwargs = {
        'user': request.user,
        'project': p,
        'relation': labeled_as_relation,
        ntype: node
    }

    new_labels = []
    for tag_name in new_tags:
        if len(tag_name) > 0 and tag_name not in existing_names:
            # Make sure the tag instance exists
            existing_tags = tuple(
                ClassInstance.objects.filter(project=p,
                                             name=tag_name,
                                             class_column=label_class))
            if len(existing_tags) < 1:
                tag = ClassInstance(project=p,
                                    name=tag_name,
                                    user=request.user,
                                    class_column=label_class)
                tag.save()
            else:
                tag = existing_tags[0]

            # Associate the tag with the treenode/connector.
            kwargs['class_instance'] = tag
            tci = table(
                **kwargs
            )  # creates new TreenodeClassInstance or ConnectorClassInstance
            tci.save()
            new_labels.append(tag_name)

            if node.user != request.user:
                # Inform the owner of the node that the tag was added and give them the option of removing it.
                change_request_params = {
                    'type':
                    'Add Tag',
                    'description':
                    'Added tag \'' + tag_name + '\'',
                    'project':
                    p,
                    'user':
                    request.user,
                    'recipient':
                    node.user,
                    'location':
                    Double3D(node.location_x, node.location_y,
                             node.location_z),
                    ntype:
                    node,
                    'validate_action':
                    'from catmaid.control.label import label_exists\n' +
                    'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype),
                    'reject_action':
                    'from catmaid.control.label import remove_label\n' +
                    'remove_label(%s, "%s")' % (str(tci.id), ntype)
                }
                ChangeRequest(**change_request_params).save()

    response = {
        'message':
        'success',
        'new_labels':
        new_labels,
        'duplicate_labels': [
            l.class_instance.name for l in duplicate_labels
            if l not in deleted_labels
        ],
        'deleted_labels': [l.class_instance.name for l in deleted_labels],
    }

    # Check if any labels on this node violate cardinality restrictions on
    # its skeleton.
    if 'treenode' == ntype:
        limited_labels = {
            l: SKELETON_LABEL_CARDINALITY[l]
            for l in new_tags if l in SKELETON_LABEL_CARDINALITY
        }

        if limited_labels:
            ll_names, ll_maxes = zip(*limited_labels.items())
            cursor = connection.cursor()
            cursor.execute(
                """
                SELECT
                  ll.name,
                  COUNT(tci.treenode_id),
                  ll.max
                FROM
                  class_instance ci,
                  treenode_class_instance tci,
                  treenode tn,
                  unnest(%s::text[], %s::integer[]) AS ll (name, max)
                WHERE ci.name = ll.name
                  AND ci.project_id = %s
                  AND ci.class_id = %s
                  AND tci.class_instance_id = ci.id
                  AND tci.relation_id = %s
                  AND tn.id = tci.treenode_id
                  AND tn.skeleton_id = %s
                GROUP BY
                  ll.name, ll.max
                HAVING
                  COUNT(tci.treenode_id) > ll.max
            """, (list(ll_names), list(ll_maxes), p.id, label_class.id,
                  labeled_as_relation.id, node.skeleton_id))

            if cursor.rowcount:
                response['warning'] = 'The skeleton has too many of the following tags: ' + \
                    ', '.join('{0} ({1}, max. {2})'.format(*row) for row in cursor.fetchall())

    return JsonResponse(response)
Example #37
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)

        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))
Example #38
0
def label_update(request, project_id=None, location_id=None, ntype=None):
    """ location_id is the ID of a treenode or connector.
        ntype is either 'treenode' or 'connector'. """
    labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as')
    p = get_object_or_404(Project, pk=project_id)

    # TODO will FAIL when a tag contains a coma by itself
    new_tags = request.POST['tags'].split(',')
    delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true'

    kwargs = {'relation': labeled_as_relation,
              'class_instance__class_column__class_name': 'label'}

    table = get_link_model(ntype)
    if 'treenode' == ntype:
        kwargs['treenode__id'] = location_id
        node = Treenode.objects.get(id=location_id)
    elif 'connector' == ntype:
        kwargs['connector__id'] = location_id
        node = Connector.objects.get(id=location_id)

    if not table:
        raise Http404('Unknown node type: "%s"' % (ntype,))

    # Get the existing list of tags for the tree node/connector and delete any
    # that are not in the new list.
    existing_labels = table.objects.filter(**kwargs).select_related('class_instance')
    existing_names = set(ele.class_instance.name for ele in existing_labels)
    duplicate_labels = table.objects.filter(**kwargs).exclude(class_instance__name__in=new_tags).select_related('class_instance')

    other_labels = []
    deleted_labels = []
    if delete_existing_labels:
        # Iterate over all labels that should get deleted to check permission
        # on each one. Remember each label that couldn't be deleted in the
        # other_labels array.
        for l in duplicate_labels:
            try:
                can_edit_or_fail(request.user, l.id, table._meta.db_table)
                if remove_label(l.id, ntype):
                    deleted_labels.append(l)
                else:
                    other_labels.append(l)
            except:
                other_labels.append(l)

        # Create change requests for labels associated to the treenode by other users
        for label in other_labels:
            change_request_params = {
                'type': 'Remove Tag',
                'project': p,
                'user': request.user,
                'recipient': node.user,
                'location': Double3D(node.location_x, node.location_y, node.location_z),
                ntype: node,
                'description': "Remove tag '%s'" % label.class_instance.name,
                'validate_action': 'from catmaid.control.label import label_exists\n' +
                                   'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype),
                'approve_action': 'from catmaid.control.label import remove_label\n' +
                                  'remove_label(%s, "%s")' % (str(label.id), ntype)
            }
            ChangeRequest(**change_request_params).save()

    # Add any new labels.
    label_class = Class.objects.get(project=project_id, class_name='label')
    kwargs = {'user': request.user,
              'project': p,
              'relation': labeled_as_relation,
              ntype: node}

    new_labels = []
    for tag_name in new_tags:
        if len(tag_name) > 0 and tag_name not in existing_names:
            # Make sure the tag instance exists
            existing_tags = tuple(ClassInstance.objects.filter(
                project=p,
                name=tag_name,
                class_column=label_class))
            if len(existing_tags) < 1:
                tag = ClassInstance(
                    project=p,
                    name=tag_name,
                    user=request.user,
                    class_column=label_class)
                tag.save()
            else:
                tag = existing_tags[0]

            # Associate the tag with the treenode/connector.
            kwargs['class_instance'] = tag
            tci = table(**kwargs) # creates new TreenodeClassInstance or ConnectorClassInstance
            tci.save()
            new_labels.append(tag_name)

            if node.user != request.user:
                # Inform the owner of the node that the tag was added and give them the option of removing it.
                change_request_params = {
                    'type': 'Add Tag',
                    'description': 'Added tag \'' + tag_name + '\'',
                    'project': p,
                    'user': request.user,
                    'recipient': node.user,
                    'location': Double3D(node.location_x, node.location_y, node.location_z),
                    ntype: node,
                    'validate_action': 'from catmaid.control.label import label_exists\n' +
                                       'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype),
                    'reject_action': 'from catmaid.control.label import remove_label\n' +
                                     'remove_label(%s, "%s")' % (str(tci.id), ntype)
                }
                ChangeRequest(**change_request_params).save()

    response = {
        'message': 'success',
        'new_labels': new_labels,
        'duplicate_labels': [l.class_instance.name for l in duplicate_labels
                             if l not in deleted_labels],
        'deleted_labels': [l.class_instance.name for l in deleted_labels],
    }

    # Check if any labels on this node violate cardinality restrictions on
    # its skeleton.
    if 'treenode' == ntype:
        limited_labels = {l: SKELETON_LABEL_CARDINALITY[l] for l in new_tags if l in SKELETON_LABEL_CARDINALITY}

        if limited_labels:
            ll_names, ll_maxes = zip(*limited_labels.items())
            cursor = connection.cursor()
            cursor.execute("""
                SELECT
                  ll.name,
                  COUNT(tci.treenode_id),
                  ll.max
                FROM
                  class_instance ci,
                  treenode_class_instance tci,
                  treenode tn,
                  unnest(%s::text[], %s::integer[]) AS ll (name, max)
                WHERE ci.name = ll.name
                  AND ci.project_id = %s
                  AND ci.class_id = %s
                  AND tci.class_instance_id = ci.id
                  AND tci.relation_id = %s
                  AND tn.id = tci.treenode_id
                  AND tn.skeleton_id = %s
                GROUP BY
                  ll.name, ll.max
                HAVING
                  COUNT(tci.treenode_id) > ll.max
            """, (
                list(ll_names),
                list(ll_maxes),
                p.id,
                label_class.id,
                labeled_as_relation.id,
                node.skeleton_id))

            if cursor.rowcount:
                response['warning'] = 'The skeleton has too many of the following tags: ' + \
                    ', '.join('{0} ({1}, max. {2})'.format(*row) for row in cursor.fetchall())

    return JsonResponse(response)
Example #39
0
    def post(request, project_id, point_id):
        """Update one particular point.

        Requires at least one field to change.
        ---
        parameters:
          - name: project_id
            description: Project point is part of
            type: integer
            paramType: path
            required: true
          - name: point_id
            description: ID of point
            type: integer
            paramType: path
            required: true
          - name: location_x
            description: X coordinate
            type: float
            paramType: form
            required: false
          - name: location_y
            description: Y coordinate
            type: float
            paramType: form
            required: false
          - name: location_z
            description: Z coordinate
            type: float
            paramType: form
            required: false
          - name: radius
            description: Optional radius
            type: float
            paramType: form
            required: false
          - name: confidence
            description: Optional confidence in [0,5]
            type: integer
            paramType: form
            required: false
        """
        can_edit_or_fail(request.user, point_id, 'point')

        updated_fields = {}
        if request.POST.has('x'):
            updated_fields['location_x'] = float(request.POST.get('x'))
        if request.POST.has('y'):
            updated_fields['location_y'] = float(request.POST.get('y'))
        if request.POST.has('z'):
            updated_fields['location_z'] = float(request.POST.get('z'))
        if request.POST.has('radius'):
            updated_fields['radius'] = float(request.POST.get('radius'))
        if request.POST.has('confidence'):
            confidence = max(min(int(request.POST.get('confidence')), 5), 0)
            updated_fields('confidence', confidence)

        if not updated_fields:
            raise ValueError('No field to modify provided')

        point = get_object_or_404(Point, pk=point_id, project_id=project_id)
        point.update(**updated_fields)
        point.save()

        serializer = PointSerializer(point)
        return Response(serializer.data)
Example #40
0
def delete_sampler(request, project_id, sampler_id):
    """Delete a sampler if permissions allow it.

    If the sampler was created with allowing the creation of new boundary nodes,
    these nodes are removed by default if they have not been modified since
    their insertion. This can optionally be disabled using the
    <delete_created_nodes> parameter.
    ---
    parameters:
     - name: delete_created_nodes
       description: |
         Optional flag to disable automatic removal of untouched
         nodes created for this sampler's intervals.
       type: boolean
       default: true
       paramType: form
       required: false
    """
    can_edit_or_fail(request.user, sampler_id, "catmaid_sampler")
    sampler = Sampler.objects.get(id=sampler_id)

    n_deleted_nodes = 0
    delete_created_nodes = get_request_bool(request.POST, 'delete_created_nodes', True)
    if delete_created_nodes and sampler.create_interval_boundaries:
        labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as')
        label_class = Class.objects.get(project=project_id, class_name='label')
        label_class_instance = ClassInstance.objects.get(project=project_id,
                class_column=label_class, name=SAMPLER_CREATED_CLASS)
        # If the sampler was parameterized to created interval boundary nodes,
        # these nodes can now be removed if they are still collinear with their
        # child and parent node and have not been touched. These nodes are all
        # nodes that are referenced by intervals of this sampler that have the
        # SAMPLER_CREATED_CLASS tag with their creation time being the same as the
        # edition time. Such nodes can only be sampler interval start/end nodes.
        params = {
            'project_id': project_id,
            'sampler_id': sampler_id,
            'labeled_as_rel': labeled_as_relation.id,
            'label_class': label_class.id,
            'label_class_instance': label_class_instance.id
        }
        cursor = connection.cursor()

        # Get all created sampler interval boundary treenodes that have been
        # created during sampler creation. The result will also contain parent
        # and child locations. We need to set extra_float_digits to get enough
        # precision for the location data to do a collinearity test.
        cursor.execute("""
            SET extra_float_digits = 3;

            WITH sampler_treenode AS (
                -- Get all treenodes linked to intervals of this sampler. Only
                -- select those nodes that are referenced by no other sampler
                -- (using an anti join).
                SELECT DISTINCT all_added_nodes.id
                FROM (
                    SELECT DISTINCT UNNEST(ARRAY[i.start_node_id, i.end_node_id]) AS id
                    FROM catmaid_samplerinterval i
                    JOIN catmaid_samplerdomain d
                        ON i.domain_id = d.id
                    WHERE d.sampler_id = %(sampler_id)s
                ) all_added_nodes
                JOIN catmaid_samplerinterval csi
                    ON csi.start_node_id = all_added_nodes.id
                    OR csi.end_node_id = all_added_nodes.id
                JOIN catmaid_samplerdomain csd
                    ON csd.id = csi.domain_id
                GROUP BY all_added_nodes.id
                HAVING COUNT(DISTINCT csd.sampler_id) = 1
            ), sampler_created_treenode AS (
                -- Find all treenodes that were created by the sampler and are
                -- undmodified.
                SELECT st.id
                FROM sampler_treenode st
                JOIN treenode_class_instance tci
                    ON st.id = tci.treenode_id
                WHERE tci.relation_id = %(labeled_as_rel)s
                AND tci.class_instance_id = %(label_class_instance)s
            )
            SELECT
                t.id, t.location_x, t.location_y, t.location_z,
                c.id, c.location_x, c.location_y, c.location_z,
                p.id, p.location_x, p.location_y, p.location_z
            FROM (
                -- Make sure we look only at nodes that don't have multiple nodes.
                SELECT st.id
                FROM treenode tt
                JOIN sampler_created_treenode st
                    ON tt.parent_id = st.id
                GROUP BY st.id
                HAVING count(*) = 1

            ) non_branch_treenodes(id)
            JOIN treenode t
                ON t.id = non_branch_treenodes.id
            JOIN treenode p
                ON p.id = t.parent_id
            JOIN treenode c
                ON c.parent_id = t.id
            WHERE t.project_id = %(project_id)s;
        """, params)

        created_treenodes = [r for r in cursor.fetchall()]

        if created_treenodes:
            added_node_index = dict((n[0], n) for n in created_treenodes)
            # Find those created treenodes that are collinear with their parent and
            # child node. If they are, remove those nodes. Ideally, we would move
            # the collinearity test into SQL as well.
            nodes_to_remove = []
            parents_to_fix = []
            child, node, parent = Point3D(0, 0, 0), Point3D(0, 0, 0), Point3D(0, 0, 0)
            for n in created_treenodes:
                n_id, node.x, node.y, node.z = n[0], n[1], n[2], n[3]
                c_id, child.x, child.y, child.z = n[4], n[5], n[6], n[7]
                p_id, parent.x, parent.y, parent.z = n[8], n[9], n[10], n[11]

                child_is_original_node = c_id not in added_node_index
                if is_collinear(child, parent, node, True, 1.0):
                    nodes_to_remove.append(n_id)
                    # Only update nodes that don't get deleted anyway
                    if child_is_original_node:
                        parents_to_fix.append((c_id, p_id))
                else:
                    parents_to_fix.append((n_id, p_id))

            # Update parent in formation in parent relation updates. If present
            # parent IDs point to a removed node, the next real parent will be
            # used instead.
            parent_update = []
            for n, (c_id, p_id) in enumerate(parents_to_fix):
                parent_is_persistent = p_id not in added_node_index
                if parent_is_persistent:
                    parent_update.append((c_id, p_id))
                else:
                    # Find next existing node upstream
                    new_parent_id = p_id
                    while not parent_is_persistent:
                        parent_is_persistent = new_parent_id not in nodes_to_remove
                        node_data = added_node_index.get(new_parent_id)
                        # An added node would be used if it is not removed, e.g.
                        # du to not being collinear anymore.
                        if node_data and not parent_is_persistent:
                            new_parent_id = node_data[8]
                        else:
                            parent_update.append((c_id, new_parent_id))

            if nodes_to_remove:
                query_parts = []
                params = []
                if parent_update:
                    update_nodes_template = ",".join("(%s, %s)" for _ in parent_update)
                    update_nodes_flattened = list(chain.from_iterable(parent_update))
                    query_parts.append("""
                        UPDATE treenode
                        SET parent_id = nodes_to_update.parent_id
                        FROM (VALUES {}) nodes_to_update(child_id, parent_id)
                        WHERE treenode.id = nodes_to_update.child_id;
                    """.format(update_nodes_template))
                    params = update_nodes_flattened

                delete_nodes_template = ",".join("(%s)" for _ in nodes_to_remove)
                query_parts.append("""
                    DELETE
                    FROM treenode
                    WHERE id IN (
                        SELECT t.id
                        FROM treenode t
                        JOIN (VALUES {}) to_delete(id)
                            ON t.id = to_delete.id
                    )
                    RETURNING id;
                """.format(delete_nodes_template))
                params = params + nodes_to_remove

                cursor.execute("\n".join(query_parts), params)
                deleted_node_ids = [r[0] for r in cursor.fetchall()]
                n_deleted_nodes = len(deleted_node_ids)

    sampler.delete()

    return JsonResponse({
        'deleted_sampler_id': sampler_id,
        'deleted_interval_nodes': n_deleted_nodes
    })
Example #41
0
def label_update(request, project_id=None, location_id=None, ntype=None):
    """ location_id is the ID of a treenode or connector.
        ntype is either 'treenode' or 'connector'. """
    labeled_as_relation = Relation.objects.get(project=project_id, relation_name='labeled_as')
    p = get_object_or_404(Project, pk=project_id)

    # TODO will FAIL when a tag contains a coma by itself
    new_tags = request.POST['tags'].split(',')
    delete_existing_labels = request.POST.get('delete_existing', 'true') == 'true'

    kwargs = {'relation': labeled_as_relation,
              'class_instance__class_column__class_name': 'label'}

    table = get_link_model(ntype)
    if 'treenode' == ntype:
        kwargs['treenode__id'] = location_id
        node = Treenode.objects.get(id=location_id)
    elif 'connector' == ntype:
        kwargs['connector__id'] = location_id
        node = Connector.objects.get(id=location_id)

    if not table:
        raise Http404('Unknown node type: "%s"' % (ntype,))

    # Get the existing list of tags for the tree node/connector and delete any
    # that are not in the new list.
    existingLabels = table.objects.filter(**kwargs).select_related('class_instance__name')
    existing_names = set(ele.class_instance.name for ele in existingLabels)
    labels_to_delete = table.objects.filter(**kwargs).exclude(class_instance__name__in=new_tags)

    if delete_existing_labels:
        # Iterate over all labels that should get deleted to check permission
        # on each one. Remember each label that couldn't be deleted in the
        # other_labels array.
        other_labels = []
        deleted_labels = []
        for l in labels_to_delete:
            try:
                can_edit_or_fail(request.user, l.id, table._meta.db_table)
                if remove_label(l.id, ntype):
                    deleted_labels.append(l)
                else:
                    other_labels.append(l)
            except:
                other_labels.append(l)

        # Create change requests for labels associated to the treenode by other users
        for label in other_labels:
            change_request_params = {
                'type': 'Remove Tag',
                'project': p,
                'user': request.user,
                'recipient': node.user,
                'location': Double3D(node.location_x, node.location_y, node.location_z),
                ntype: node,
                'description': "Remove tag '%s'" % label.class_instance.name,
                'validate_action': 'from catmaid.control.label import label_exists\n' +
                                   'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype),
                'approve_action': 'from catmaid.control.label import remove_label\n' +
                                  'remove_label(%s, "%s")' % (str(label.id), ntype)
            }
            ChangeRequest(**change_request_params).save()

    # Add any new labels.
    label_class = Class.objects.get(project=project_id, class_name='label')
    kwargs = {'user': request.user,
              'project': p,
              'relation': labeled_as_relation,
              ntype: node}

    for tag_name in new_tags:
        if len(tag_name) > 0 and tag_name not in existing_names:
            # Make sure the tag instance exists
            existing_tags = tuple(ClassInstance.objects.filter(
                project=p,
                name=tag_name,
                class_column=label_class))
            if len(existing_tags) < 1:
                tag = ClassInstance(
                    project=p,
                    name=tag_name,
                    user=request.user,
                    class_column=label_class)
                tag.save()
            else:
                tag = existing_tags[0]

            # Associate the tag with the treenode/connector.
            kwargs['class_instance'] = tag
            tci = table(**kwargs) # creates new TreenodeClassInstance or ConnectorClassInstance
            tci.save()

            if node.user != request.user:
                # Inform the owner of the node that the tag was added and give them the option of removing it.
                change_request_params = {
                    'type': 'Add Tag',
                    'description': 'Added tag \'' + tag_name + '\'',
                    'project': p,
                    'user': request.user,
                    'recipient': node.user,
                    'location': Double3D(node.location_x, node.location_y, node.location_z),
                    ntype: node,
                    'validate_action': 'from catmaid.control.label import label_exists\n' +
                                       'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype),
                    'reject_action': 'from catmaid.control.label import remove_label\n' +
                                     'remove_label(%s, "%s")' % (str(tci.id), ntype)
                }
                ChangeRequest(**change_request_params).save()


    return HttpResponse(json.dumps({'message': 'success'}), content_type='text/json')
Example #42
0
def label_update(request, project_id=None, location_id=None, ntype=None):
    """ location_id is the ID of a treenode or connector.
        ntype is either 'treenode' or 'connector'. """
    labeled_as_relation = Relation.objects.get(project=project_id,
                                               relation_name='labeled_as')
    p = get_object_or_404(Project, pk=project_id)

    # TODO will FAIL when a tag contains a coma by itself
    new_tags = request.POST['tags'].split(',')
    delete_existing_labels = request.POST.get('delete_existing',
                                              'true') == 'true'

    kwargs = {
        'relation': labeled_as_relation,
        'class_instance__class_column__class_name': 'label'
    }

    table = get_link_model(ntype)
    if 'treenode' == ntype:
        kwargs['treenode__id'] = location_id
        node = Treenode.objects.get(id=location_id)
    elif 'connector' == ntype:
        kwargs['connector__id'] = location_id
        node = Connector.objects.get(id=location_id)

    if not table:
        raise Http404('Unknown node type: "%s"' % (ntype, ))

    # Get the existing list of tags for the tree node/connector and delete any
    # that are not in the new list.
    existingLabels = table.objects.filter(
        **kwargs).select_related('class_instance__name')
    existing_names = set(ele.class_instance.name for ele in existingLabels)
    labels_to_delete = table.objects.filter(**kwargs).exclude(
        class_instance__name__in=new_tags)

    if delete_existing_labels:
        # Iterate over all labels that should get deleted to check permission
        # on each one. Remember each label that couldn't be deleted in the
        # other_labels array.
        other_labels = []
        deleted_labels = []
        for l in labels_to_delete:
            try:
                can_edit_or_fail(request.user, l.id, table._meta.db_table)
                if remove_label(l.id, ntype):
                    deleted_labels.append(l)
                else:
                    other_labels.append(l)
            except:
                other_labels.append(l)

        # Create change requests for labels associated to the treenode by other users
        for label in other_labels:
            change_request_params = {
                'type':
                'Remove Tag',
                'project':
                p,
                'user':
                request.user,
                'recipient':
                node.user,
                'location':
                Double3D(node.location_x, node.location_y, node.location_z),
                ntype:
                node,
                'description':
                "Remove tag '%s'" % label.class_instance.name,
                'validate_action':
                'from catmaid.control.label import label_exists\n' +
                'is_valid = label_exists(%s, "%s")' % (str(label.id), ntype),
                'approve_action':
                'from catmaid.control.label import remove_label\n' +
                'remove_label(%s, "%s")' % (str(label.id), ntype)
            }
            ChangeRequest(**change_request_params).save()

    # Add any new labels.
    label_class = Class.objects.get(project=project_id, class_name='label')
    kwargs = {
        'user': request.user,
        'project': p,
        'relation': labeled_as_relation,
        ntype: node
    }

    for tag_name in new_tags:
        if len(tag_name) > 0 and tag_name not in existing_names:
            # Make sure the tag instance exists
            existing_tags = tuple(
                ClassInstance.objects.filter(project=p,
                                             name=tag_name,
                                             class_column=label_class))
            if len(existing_tags) < 1:
                tag = ClassInstance(project=p,
                                    name=tag_name,
                                    user=request.user,
                                    class_column=label_class)
                tag.save()
            else:
                tag = existing_tags[0]

            # Associate the tag with the treenode/connector.
            kwargs['class_instance'] = tag
            tci = table(
                **kwargs
            )  # creates new TreenodeClassInstance or ConnectorClassInstance
            tci.save()

            if node.user != request.user:
                # Inform the owner of the node that the tag was added and give them the option of removing it.
                change_request_params = {
                    'type':
                    'Add Tag',
                    'description':
                    'Added tag \'' + tag_name + '\'',
                    'project':
                    p,
                    'user':
                    request.user,
                    'recipient':
                    node.user,
                    'location':
                    Double3D(node.location_x, node.location_y,
                             node.location_z),
                    ntype:
                    node,
                    'validate_action':
                    'from catmaid.control.label import label_exists\n' +
                    'is_valid = label_exists(%s, "%s")' % (str(tci.id), ntype),
                    'reject_action':
                    'from catmaid.control.label import remove_label\n' +
                    'remove_label(%s, "%s")' % (str(tci.id), ntype)
                }
                ChangeRequest(**change_request_params).save()

    return HttpResponse(json.dumps({'message': 'success'}),
                        content_type='application/json')
Example #43
0
    def delete(self, request, project_id):
        """Delete a list of landmarks including the linked locations, if they
        are not used by other landmarks.
        ---
        parameters:
        - name: project_id
          description: The project the landmark is part of.
          type: integer
          paramType: path
          required: true
        - name: landmark_ids
          description: The landmarks to remove.
          required: true
          type: integer
          paramType: form
        - name: keep_points
          description: Don't delete points.
          required: false
          type: boolean
          defaultValue: false
          paramType: form
        """
        keep_points = request.query_params.get('keep_points',
                                               'false') == 'true'
        landmark_ids = get_request_list(request.query_params,
                                        'landmark_ids',
                                        map_fn=int)
        for l in landmark_ids:
            can_edit_or_fail(request.user, l, 'class_instance')

        annotated_with_relation = Relation.objects.get(
            project_id=project_id, relation_name='annotated_with')

        point_ids = set()
        if not keep_points:
            point_landmark_links = PointClassInstance.objects.filter(
                project_id=project_id,
                class_instance_id__in=landmark_ids,
                relation=annotated_with_relation)

            # These are the landmark's lined points
            point_ids = set(pll.point_id for pll in point_landmark_links)

        landmark_class = Class.objects.get(project_id=project_id,
                                           class_name="landmark")
        landmarks = ClassInstance.objects.filter(pk__in=landmark_ids,
                                                 project_id=project_id,
                                                 class_column=landmark_class)

        if len(landmark_ids) != len(landmarks):
            raise ValueError("Could not find all landmark IDs")

        landmarks.delete()

        if not keep_points:
            remaining_pll = set(
                PointClassInstance.objects.filter(
                    project_id=project_id,
                    point_id__in=point_ids,
                    relation=annotated_with_relation).values_list('point_id',
                                                                  flat=True))
            points_to_delete = point_ids - remaining_pll
            Point.objects.filter(project_id=project_id,
                                 pk__in=points_to_delete).delete()

        serializer = BasicClassInstanceSerializer(landmarks, many=True)
        return Response(serializer.data)