Example #1
0
def classify_image(image_id):
    image = Image.objects.get(pk=image_id)

    # if annotated by Human, or if the previous step is not complete
    if image.status.annotatedByHuman or not image.status.featuresExtracted:
        return 1

    # Get last robot for this source
    latestRobot = image.source.get_latest_robot()

    if latestRobot == None:
        return 1

    # Check if this image has been previously annotated by a robot.
    if image.status.annotatedByRobot:
        # now, compare this version number to the latest_robot_annotator field for image.
        if (not (latestRobot.version > image.latest_robot_annotator.version)):
            return 1

    ####### EVERYTHING OK: START THE CLASSIFICATION ##########
    logging.info('Classifying image{id} from source{sid}: {sname}'.format(id = image_id, sid = image.source_id, sname = image.source.name))
    
    #builds args for matlab script
    featureFile = os.path.join(FEATURES_DIR, str(image_id) + "_" + image.get_process_date_short_str() + ".dat")
    labelFile = os.path.join(CLASSIFY_DIR, str(image_id) + "_" + image.get_process_date_short_str() + ".txt")

    task_helpers.coralnet_classify(
        featureFile=featureFile,
        modelFile=latestRobot.path_to_model,
        labelFile=labelFile,
        logFile=CV_LOG,
        errorLogfile=CLASSIFY_ERROR_LOG,
    )

    if os.path.isfile(CLASSIFY_ERROR_LOG):
        logging.info('ERROR classifying image{id} from source{sid}: {sname}'.format(id = image_id, sid = image.source_id, sname = image.source.name))
        mail_admins('CoralNet Backend Error', 'in Classify')
        return 0
    else:
        #update image status
        image.status.annotatedByRobot = True
        image.status.save()
        image.latest_robot_annotator = latestRobot
        image.save()

    ####### IMPORT CLASSIFICATION RESULT TO DATABASE ##########
    user = get_robot_user()

    # Get the label probabilities that we just generated
    label_probabilities = task_utils.get_label_probabilities_for_image(image_id)

    if len(label_probabilities) == 0:
        mail_admins('Classify error', 'Classification output for image{id} from source{sid}: {sname} was empty.'.format(id = image_id, sid = image.source_id, sname = image.source.name))

    # Go through each point and update/create the annotation as appropriate
    for point_number, probs in label_probabilities.iteritems():
        pt = Point.objects.get(image=image, point_number=point_number)

        probs_descending_order = sorted(probs, key=operator.itemgetter('score'), reverse=True)
        top_prob_label_code = probs_descending_order[0]['label']
        label = Label.objects.get(code=top_prob_label_code)

        # If there's an existing annotation for this point, get it.
        # Otherwise, create a new annotation.
        #
        # (Assumption: there's at most 1 Annotation per Point, never multiple.
        # If there are multiple, we'll get a MultipleObjectsReturned exception.)
        try:
            anno = Annotation.objects.get(image=image, point=pt)

        except Annotation.DoesNotExist:
            # No existing annotation. Create a new one.
            new_anno = Annotation(
                image=image, label=label, point=pt,
                user=user, robot_version=latestRobot, source=image.source
            )
            new_anno.save()

        else:
            # Got an existing annotation.
            if is_robot_user(anno.user):
                # It's an existing robot annotation. Update it as necessary.
                if anno.label.id != label.id:
                    anno.label = label
                    anno.robot_version = latestRobot
                    anno.save()

            # Else, it's an existing confirmed annotation, and we don't want
            # to overwrite it. So do nothing in this case.

    logging.info('Classified {npts} points in image{id} from source{sid}: {sname}'.format(npts = len(label_probabilities), id = image_id, sid = image.source_id, sname = image.source.name))
    return 1
Example #2
0
def annotation_tool(request, image_id):
    """
    View for the annotation tool.
    """

    image = get_object_or_404(Image, id=image_id)
    source = image.source
    metadata = image.metadata


    # Image navigation history.
    # Establish default values for the history, first.
    nav_history_form =\
        annotations_forms.AnnotationToolNavHistoryForm(
            initial=dict(back="[]", forward="[]", from_image_id=image_id)
        )
    nav_history = dict(
        form=nav_history_form,
        back=None,
        forward=None,
    )
    # We made a POST request if the user is going to another image,
    # going back, or going forward.
    # (It's non-POST if they came from a non annotation tool page, or
    # came via URL typing.)
    if request.method == 'POST':
        nav_history_form_submitted =\
            annotations_forms.AnnotationToolNavHistoryForm(request.POST)

        if nav_history_form_submitted.is_valid():
            # Nav history is a serialized list of image ids.
            # For example: "[258,259,268,109]"
            form_data = nav_history_form_submitted.cleaned_data
            back_submitted_list = json.loads(form_data['back'])
            forward_submitted_list = json.loads(form_data['forward'])
            from_image_id = form_data['from_image_id']

            # Construct new back and forward lists based on
            # where we're navigating.
            if request.POST.get('nav_next', None):
                back_list = back_submitted_list + [from_image_id]
                forward_list = []
            elif request.POST.get('nav_back', None):
                back_list = back_submitted_list[:-1]
                forward_list = [from_image_id] + forward_submitted_list
            else:  # 'nav_forward'
                back_list = back_submitted_list + [from_image_id]
                forward_list = forward_submitted_list[1:]

            limit = 10
            nav_history_form = \
                annotations_forms.AnnotationToolNavHistoryForm(
                    initial=dict(
                        back=json.dumps(back_list[-limit:]),
                        forward=json.dumps(forward_list[:limit]),
                        from_image_id=image_id,
                    )
                )

            if len(back_list) > 0:
                back = Image.objects.get(pk=back_list[-1])
            else:
                back = None
            if len(forward_list) > 0:
                forward = Image.objects.get(pk=forward_list[0])
            else:
                forward = None
            nav_history = dict(
                form=nav_history_form,
                back=back,
                forward=forward,
            )
        else:
            # Invalid form for some reason.
            # Fail silently, I guess? That is, use an empty history.
            pass


    # Get the settings object for this user.
    # If there is no such settings object, then create it.
    settings_obj, created = AnnotationToolSettings.objects.get_or_create(user=request.user)
    settings_form = AnnotationToolSettingsForm(instance=settings_obj)


    # Get all labels, ordered first by functional group, then by short code.
    labels = source.labelset.labels.all().order_by('group', 'code')
    # Get labels in the form {'code': <short code>, 'group': <functional group>, 'name': <full name>}.
    # Convert from a ValuesQuerySet to a list to make the structure JSON-serializable.
    labelValues = list(labels.values('code', 'group', 'name'))

    error_message = []
    # Get the machine's label probabilities, if applicable.
    if not settings_obj.show_machine_annotations:
        label_probabilities = None
    elif not image.status.annotatedByRobot:
        label_probabilities = None
    else:
        label_probabilities = task_utils.get_label_probabilities_for_image(image_id)
        # label_probabilities can still be None here if something goes wrong.
        # But if not None, apply Alleviate.
        if label_probabilities:
            annotations_utils.apply_alleviate(image_id, label_probabilities)
        else:
            error_message.append('Woops! Could not read the label probabilities. Manual annotation still works.')


    # Get points and annotations.
    form = AnnotationForm(
        image=image,
        user=request.user,
        show_machine_annotations=settings_obj.show_machine_annotations
    )

    pointValues = Point.objects.filter(image=image).values(
        'point_number', 'row', 'column')
    annotationValues = Annotation.objects.filter(image=image).values(
        'point__point_number', 'label__name', 'label__code')

    # annotationsDict
    # keys: point numbers
    # values: dicts containing the values in pointValues and
    #         annotationValues (if the point has an annotation) above
    annotationsDict = dict()
    for p in pointValues:
        annotationsDict[p['point_number']] = p
    for a in annotationValues:
        annotationsDict[a['point__point_number']].update(a)

    # Get a list of the annotationsDict values (the keys are discarded)
    # Sort by point_number
    annotations = list(annotationsDict.values())
    annotations.sort(key=lambda x:x['point_number'])

    # Now we've gotten all the relevant points and annotations
    # from the database, in a list of dicts:
    # [{'point_number':1, 'row':294, 'column':749, 'label__name':'Porites', 'label__code':'Porit', 'user_is_robot':False},
    #  {'point_number':2, ...},
    #  ...]
    # TODO: Are we even using anything besides row, column, and point_number?  If not, discard the annotation fields to avoid confusion.


    # Image tools form (brightness, contrast, etc.)
    image_options_form = AnnotationImageOptionsForm()


    # Image dimensions.
    IMAGE_AREA_WIDTH = 850
    IMAGE_AREA_HEIGHT = 650

    source_images = dict(full=dict(
        url=image.original_file.url,
        width=image.original_file.width,
        height=image.original_file.height,
    ))
    if image.original_width > IMAGE_AREA_WIDTH:
        # Set scaled image's dimensions (Specific width, height that keeps the aspect ratio)
        thumbnail_dimensions = (IMAGE_AREA_WIDTH, 0)

        # Generate the thumbnail if it doesn't exist, and get the thumbnail's URL and dimensions.
        thumbnailer = get_thumbnailer(image.original_file)
        thumb = thumbnailer.get_thumbnail(dict(size=thumbnail_dimensions))
        source_images.update(dict(scaled=dict(
            url=thumb.url,
            width=thumb.width,
            height=thumb.height,
        )))


    # Get the next image to annotate.
    # This'll be the next image that needs annotation;
    # or if we're at the last image, wrap around to the first image.
    next_image_to_annotate = get_next_image(image, dict(status__annotatedByHuman=False))

    if next_image_to_annotate is None:
        next_image_to_annotate = get_first_image(
            image.source, dict(status__annotatedByHuman=False)
        )
        # Don't allow getting the current image as the next image to annotate.
        if next_image_to_annotate is not None and next_image_to_annotate.id == image.id:
            next_image_to_annotate = None


    # Record this access of the annotation tool page.
    access = AnnotationToolAccess(image=image, source=source, user=request.user)
    access.save()

    return render_to_response('annotations/annotation_tool.html', {
        'source': source,
        'image': image,
        'next_image_to_annotate': next_image_to_annotate,
        'nav_history': nav_history,
        'metadata': metadata,
        'labels': labelValues,
        'form': form,
        'settings_form': settings_form,
        'image_options_form': image_options_form,
        'annotations': annotations,
        'annotationsJSON': simplejson.dumps(annotations),
        'label_probabilities': label_probabilities,
        'IMAGE_AREA_WIDTH': IMAGE_AREA_WIDTH,
        'IMAGE_AREA_HEIGHT': IMAGE_AREA_HEIGHT,
        'source_images': source_images,
        'num_of_points': len(annotations),
        'num_of_annotations': len(annotationValues),
        'messages': error_message,
        },
        context_instance=RequestContext(request)
    )