Ejemplo n.º 1
0
def generate_points(img, usesourcemethod=True):
    """
    Generate annotation points for the Image img,
    and delete any points that had previously existed.

    Does nothing if the image already has human annotations,
    because we don't want to delete any human work.
    """

    # If there are any human annotations for this image,
    # abort point generation.
    human_annotations = Annotation.objects.filter(image = img).exclude(user = get_robot_user()).exclude(user = get_alleviate_user())
    if human_annotations:
        return

    # Find the annotation area, expressed in pixels.
    d = AnnotationAreaUtils.db_format_to_numbers(img.metadata.annotation_area)
    annoarea_type = d.pop('type')
    if annoarea_type == AnnotationAreaUtils.TYPE_PERCENTAGES:
        annoarea_dict = AnnotationAreaUtils.percentages_to_pixels(width=img.original_width, height=img.original_height, **d)
    elif annoarea_type == AnnotationAreaUtils.TYPE_PIXELS:
        annoarea_dict = d
    else:
        raise ValueError("Can't generate points with annotation area type '{0}'.".format(annoarea_type))

    # Calculate points.
    if usesourcemethod:
        point_gen_method = img.source.default_point_generation_method
    else:
        point_gen_method = img.point_generation_method
    
    new_points = calculate_points(
        img, annotation_area=annoarea_dict,
        **PointGen.db_to_args_format(point_gen_method)
    )

    # Delete old points for this image, if any.
    old_points = Point.objects.filter(image=img)
    for old_point in old_points:
        old_point.delete()

    # Save the newly calculated points.
    for new_point in new_points:
        Point(row=new_point['row'],
              column=new_point['column'],
              point_number=new_point['point_number'],
              image=img,
        ).save()

    # Update image status.
    # Make sure the image goes through the feature-making step again.
    status = img.status
    status.hasRandomPoints = True
    status.save()
Ejemplo n.º 2
0
    def pointgen_check(self, image_id):
        """
        Check that an image had annotation points generated as
        specified in the point generation method field.

        :param image_id: The id of the image to check.
        """
        img = Image.objects.get(pk=image_id)
        img_width = img.original_width
        img_height = img.original_height
        pointgen_args = PointGen.db_to_args_format(img.point_generation_method)

        points = Point.objects.filter(image=img)
        self.assertEqual(points.count(), pointgen_args['simple_number_of_points'])

        # Find the expected annotation area, expressed in pixels.
        d = AnnotationAreaUtils.db_format_to_numbers(img.metadata.annotation_area)
        annoarea_type = d.pop('type')
        if annoarea_type == AnnotationAreaUtils.TYPE_PERCENTAGES:
            area = AnnotationAreaUtils.percentages_to_pixels(width=img_width, height=img_height, **d)
        elif annoarea_type == AnnotationAreaUtils.TYPE_PIXELS:
            area = d
        elif annoarea_type == AnnotationAreaUtils.TYPE_IMPORTED:
            area = dict(min_x=1, max_x=img_width, min_y=1, max_y=img_height)
        else:
            raise ValueError("Unknown annotation area type.")

        if settings.UNIT_TEST_VERBOSITY >= 1:
            print "{pointgen_method}".format(
                pointgen_method=img.point_gen_method_display(),
            )
            print "{annotation_area}".format(
                annotation_area=img.annotation_area_display(),
            )
            print "Image dimensions: {width} x {height} pixels".format(
                width=img_width, height=img_height,
            )
            print "X bounds: ({min_x}, {max_x}) Y bounds: ({min_y}, {max_y})".format(
                **area
            )

        for pt in points:
            self.assertTrue(area['min_x'] <= pt.column)
            self.assertTrue(pt.column <= area['max_x'])
            self.assertTrue(area['min_y'] <= pt.row)
            self.assertTrue(pt.row <= area['max_y'])

            if settings.UNIT_TEST_VERBOSITY >= 1:
                print "({col}, {row})".format(col=pt.column, row=pt.row)
Ejemplo n.º 3
0
    def __init__(self, *args, **kwargs):
        """
        Dynamically generate help text.
        """
        source = kwargs.pop('source')
        super(ImageUploadOptionsForm, self).__init__(*args, **kwargs)

        # Dynamically generate help text.
        # Show the filename format that should be used,
        # and an example of a filename adhering to that format.
        filenameFormatArgs = dict(year='YYYY', month='MM', day='DD')
        filenameExampleArgs = dict(year='2010', month='08', day='23')

        sourceKeys = source.get_key_list()
        exampleSuffixes = ['A', ' 7', ' 2-2', 'C', '1'][0 : len(sourceKeys)]

        filenameFormatArgs['values'] = sourceKeys
        filenameExampleArgs['values'] = [a+b for a,b in zip(sourceKeys, exampleSuffixes)]

        filenameFormatStr = metadata_to_filename(**filenameFormatArgs)
        filenameExampleStr = metadata_to_filename(**filenameExampleArgs) + ".jpg"

        self.fields['specify_metadata'].help_text = \
            "Required filename format: %s" % filenameFormatStr

        # Use JavaScript to show/hide this additional help text
        self.metadata_extra_help_text = (
            "\n"
            "For example, let's say your source has the following location keys: "
            "Site, Depth, Transect Line and Quadrant. "
            "If you want to upload a .jpg image that was taken at "
            "Site: sharkPoint, Depth: 10m, Transect Line: 3, and Quadrant: qu4, "
            "on 14 January 2010, the filename for upload should be:\n\n"

            "sharkPoint_10m_3_qu4_2010-01-14.jpg\n\n"

            "Alternatively, if you also want to store the original filename - say it's "
            "IMG_0032.jpg - you can use:\n\n"

            "sharkPoint_10m_3_qu4_2010-01-14_IMG_0032.jpg\n\n"

            "The original file name is not used by CoralNet, but could be "
            "useful for your own reference."
        )


        self.additional_details = [
            """Annotation points will be automatically generated for your images.
            Your Source's point generation settings: %s
            Your Source's annotation area settings: %s""" % (
                PointGen.db_to_readable_format(source.default_point_generation_method),
                AnnotationAreaUtils.db_format_to_display(source.image_annotation_area)
                )
        ]
Ejemplo n.º 4
0
def source_new(request):
    """
    Page with the form to create a new Source.
    """

    # We can get here one of two ways: either we just got to the form
    # page, or we just submitted the form.  If POST, we submitted; if
    # GET, we just got here.
    if request.method == 'POST':
        # A form bound to the POST data
        sourceForm = ImageSourceForm(request.POST)
        pointGenForm = PointGenForm(request.POST)
        annotationAreaForm = AnnotationAreaPercentsForm(request.POST)

        # is_valid() calls our ModelForm's clean() and checks validity
        source_form_is_valid = sourceForm.is_valid()
        point_gen_form_is_valid = pointGenForm.is_valid()
        annotation_area_form_is_valid = annotationAreaForm.is_valid()

        if source_form_is_valid and point_gen_form_is_valid and annotation_area_form_is_valid:
            # After calling a ModelForm's is_valid(), an instance is created.
            # We can get this instance and add a bit more to it before saving to the DB.
            newSource = sourceForm.instance
            newSource.default_point_generation_method = PointGen.args_to_db_format(**pointGenForm.cleaned_data)
            newSource.image_annotation_area = AnnotationAreaUtils.percentages_to_db_format(**annotationAreaForm.cleaned_data)
            newSource.labelset = LabelSet.getEmptyLabelset()
            newSource.save()

            # Make the user a source admin
            newSource.assign_role(request.user, Source.PermTypes.ADMIN.code)

            # Add a success message
            messages.success(request, 'Source successfully created.')
            
            # Redirect to the source's main page
            return HttpResponseRedirect(reverse('source_main', args=[newSource.id]))
        else:
            # Show the form again, with error message
            messages.error(request, 'Please correct the errors below.')
    else:
        # An unbound form (empty form)
        sourceForm = ImageSourceForm()
        pointGenForm = PointGenForm()
        annotationAreaForm = AnnotationAreaPercentsForm()

    # RequestContext needed for CSRF verification of POST form,
    # and to correctly get the path of the CSS file being used.
    return render_to_response('images/source_new.html', {
        'sourceForm': sourceForm,
        'pointGenForm': pointGenForm,
        'annotationAreaForm': annotationAreaForm,
        },
        context_instance=RequestContext(request)
        )
Ejemplo n.º 5
0
    def __init__(self, *args, **kwargs):

        image = kwargs.pop('image')

        if image.metadata.annotation_area:
            d = AnnotationAreaUtils.db_format_to_numbers(image.metadata.annotation_area)
            annoarea_type = d.pop('type')
            if annoarea_type == AnnotationAreaUtils.TYPE_PERCENTAGES:
                kwargs['initial'] = AnnotationAreaUtils.percentages_to_pixels(width=image.original_width, height=image.original_height, **d)
            elif annoarea_type == AnnotationAreaUtils.TYPE_PIXELS:
                kwargs['initial'] = d
            elif annoarea_type == AnnotationAreaUtils.TYPE_IMPORTED:
                raise ValueError("Points were imported; annotation area should be un-editable.")

        super(AnnotationAreaPixelsForm, self).__init__(*args, **kwargs)

        self.fields['min_x'] = IntegerField(
            label="Left boundary X", required=False,
            min_value=1, max_value=image.original_width,
            widget=TextInput(attrs={'size': 3})
        )
        self.fields['max_x'] = IntegerField(
            label="Right boundary X", required=False,
            min_value=1, max_value=image.original_width,
            widget=TextInput(attrs={'size': 3})
        )
        self.fields['min_y'] = IntegerField(
            label="Top boundary Y", required=False,
            min_value=1, max_value=image.original_height,
            widget=TextInput(attrs={'size': 3})
        )
        self.fields['max_y'] = IntegerField(
            label="Bottom boundary Y", required=False,
            min_value=1, max_value=image.original_height,
            widget=TextInput(attrs={'size': 3})
        )

        self.form_help_text = Metadata._meta.get_field('annotation_area').help_text
Ejemplo n.º 6
0
def source_edit(request, source_id):
    """
    Edit a source: name, visibility, location keys, etc.
    """

    source = get_object_or_404(Source, id=source_id)

    if request.method == 'POST':

        # Cancel
        cancel = request.POST.get('cancel', None)
        if cancel:
            messages.success(request, 'Edit cancelled.')
            return HttpResponseRedirect(reverse('source_main', args=[source_id]))

        # Submit
        sourceForm = ImageSourceForm(request.POST, instance=source)
        pointGenForm = PointGenForm(request.POST)
        annotationAreaForm = AnnotationAreaPercentsForm(request.POST)

        # Make sure is_valid() is called for all forms, so all forms are checked and
        # all relevant error messages appear.
        source_form_is_valid = sourceForm.is_valid()
        point_gen_form_is_valid = pointGenForm.is_valid()
        annotation_area_form_is_valid = annotationAreaForm.is_valid()

        if source_form_is_valid and point_gen_form_is_valid and annotation_area_form_is_valid:
            editedSource = sourceForm.instance
            editedSource.default_point_generation_method = PointGen.args_to_db_format(**pointGenForm.cleaned_data)
            editedSource.image_annotation_area = AnnotationAreaUtils.percentages_to_db_format(**annotationAreaForm.cleaned_data)
            editedSource.save()
            messages.success(request, 'Source successfully edited.')
            return HttpResponseRedirect(reverse('source_main', args=[source_id]))
        else:
            messages.error(request, 'Please correct the errors below.')
    else:
        # Just reached this form page
        sourceForm = ImageSourceForm(instance=source)
        pointGenForm = PointGenForm(source=source)
        annotationAreaForm = AnnotationAreaPercentsForm(source=source)

    return render_to_response('images/source_edit.html', {
        'source': source,
        'editSourceForm': sourceForm,
        'pointGenForm': pointGenForm,
        'annotationAreaForm': annotationAreaForm,
        },
        context_instance=RequestContext(request)
        )
Ejemplo n.º 7
0
    def __init__(self, *args, **kwargs):
        """
        If a Source is passed in as an argument, then get
        the annotation area of that Source,
        and use that to fill the form fields' initial values.
        """
        if kwargs.has_key('source'):
            source = kwargs.pop('source')

            if source.image_annotation_area:
                kwargs['initial'] = AnnotationAreaUtils.db_format_to_percentages(source.image_annotation_area)

        self.form_help_text = Source._meta.get_field('image_annotation_area').help_text

        super(AnnotationAreaPercentsForm, self).__init__(*args, **kwargs)
Ejemplo n.º 8
0
    def test_post_success(self):
        """
        Successful creation of a new source.
        """
        datetime_before_creation = datetime.datetime.now().replace(microsecond=0)

        response = self.client.post(reverse('source_new'), self.source_args)

        source_id = Source.objects.latest('create_date').pk
        self.assertRedirects(response, reverse('source_main',
            kwargs={
                'source_id': source_id,
                }
        ))

        new_source = Source.objects.get(pk=source_id)

        self.assertEqual(new_source.name, self.source_args['name'])
        self.assertEqual(new_source.visibility, self.source_args['visibility'])
        self.assertEqual(new_source.labelset, LabelSet.getEmptyLabelset())
        self.assertEqual(new_source.key1, self.source_args['key1'])
        self.assertEqual(new_source.key2, '')
        self.assertEqual(new_source.key3, '')
        self.assertEqual(new_source.key4, '')
        self.assertEqual(new_source.key5, '')
        self.assertEqual(new_source.default_point_generation_method, PointGen.args_to_db_format(
            point_generation_type=self.source_args['point_generation_type'],
            simple_number_of_points=self.source_args['simple_number_of_points'],
        ))
        self.assertEqual(new_source.image_height_in_cm, self.source_args['image_height_in_cm'])
        self.assertEqual(new_source.image_annotation_area, AnnotationAreaUtils.percentages_to_db_format(
            min_x=self.source_args['min_x'], max_x=self.source_args['max_x'],
            min_y=self.source_args['min_y'], max_y=self.source_args['max_y'],
        ))
        self.assertEqual(new_source.longitude, self.source_args['longitude'])
        self.assertEqual(new_source.latitude, self.source_args['latitude'])

        self.assertEqual(new_source.enable_robot_classifier, True)

        # This check is of limited use since database datetimes (in
        # MySQL 5.1 at least) get truncated to whole seconds. But it still
        # doesn't hurt to check.
        self.assertTrue(datetime_before_creation <= new_source.create_date)
        self.assertTrue(new_source.create_date <= datetime.datetime.now().replace(microsecond=0))
Ejemplo n.º 9
0
def image_upload(request, source_id):
    """
    This view serves the image upload page.  It doesn't actually do any
    upload processing, though; that's left to the Ajax views.
    """

    source = get_object_or_404(Source, id=source_id)

    images_form = MultiImageUploadForm()
    options_form = ImageUploadOptionsForm(source=source)
    csv_import_form = CSVImportForm()
    annotation_import_form = AnnotationImportForm()
    annotation_import_options_form = AnnotationImportOptionsForm(source=source)
    proceed_to_manage_metadata_form = ImageSpecifyForm(
        initial=dict(specify_method='image_ids'),
        source=source,
    )

    auto_generate_points_message = (
        "We will generate points for the images you upload.\n"
        "Your Source's point generation settings: {pointgen}\n"
        "Your Source's annotation area settings: {annoarea}").format(
            pointgen=PointGen.db_to_readable_format(source.default_point_generation_method),
            annoarea=AnnotationAreaUtils.db_format_to_display(source.image_annotation_area,
        ),
    )

    return render_to_response('upload/image_upload.html', {
        'source': source,
        'images_form': images_form,
        'options_form': options_form,
        'csv_import_form': csv_import_form,
        'annotation_import_form': annotation_import_form,
        'annotation_import_options_form': annotation_import_options_form,
        'proceed_to_manage_metadata_form': proceed_to_manage_metadata_form,
        'auto_generate_points_message': auto_generate_points_message,
        },
        context_instance=RequestContext(request)
    )
Ejemplo n.º 10
0
    def test_post_success(self):
        """
        Successful edit of an existing source.
        """
        original_source = Source.objects.get(pk=self.source_id)
        original_create_date = original_source.create_date
        original_enable_robot = original_source.enable_robot_classifier

        response = self.client.post(reverse('source_edit', kwargs={'source_id': self.source_id}),
            self.source_args,
        )

        self.assertRedirects(response, reverse('source_main',
            kwargs={'source_id': self.source_id}
        ))

        edited_source = Source.objects.get(pk=self.source_id)

        self.assertEqual(edited_source.name, self.source_args['name'])
        self.assertEqual(edited_source.visibility, self.source_args['visibility'])
        self.assertEqual(edited_source.create_date, original_create_date)
        self.assertEqual(edited_source.labelset, LabelSet.getEmptyLabelset())
        self.assertEqual(edited_source.key1, self.source_args['key1'])
        self.assertEqual(edited_source.key2, '')
        self.assertEqual(edited_source.key3, '')
        self.assertEqual(edited_source.key4, '')
        self.assertEqual(edited_source.key5, '')
        self.assertEqual(edited_source.default_point_generation_method, PointGen.args_to_db_format(
            point_generation_type=self.source_args['point_generation_type'],
            simple_number_of_points=self.source_args['simple_number_of_points'],
        ))
        self.assertEqual(edited_source.image_height_in_cm, self.source_args['image_height_in_cm'])
        self.assertEqual(edited_source.image_annotation_area, AnnotationAreaUtils.percentages_to_db_format(
            min_x=self.source_args['min_x'], max_x=self.source_args['max_x'],
            min_y=self.source_args['min_y'], max_y=self.source_args['max_y'],
        ))
        self.assertEqual(edited_source.longitude, self.source_args['longitude'])
        self.assertEqual(edited_source.latitude, self.source_args['latitude'])
        self.assertEqual(edited_source.enable_robot_classifier, original_enable_robot)
Ejemplo n.º 11
0
def annotation_area_edit(request, image_id):
    """
    Edit an image's annotation area.
    """

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

    old_annotation_area = metadata.annotation_area

    if request.method == 'POST':

        # Cancel
        cancel = request.POST.get('cancel', None)
        if cancel:
            messages.success(request, 'Edit cancelled.')
            return HttpResponseRedirect(reverse('image_detail', args=[image.id]))

        # Submit
        annotationAreaForm = AnnotationAreaPixelsForm(request.POST, image=image)

        if annotationAreaForm.is_valid():
            metadata.annotation_area = AnnotationAreaUtils.pixels_to_db_format(**annotationAreaForm.cleaned_data)
            metadata.save()

            if metadata.annotation_area != old_annotation_area:
                generate_points(image, usesourcemethod=False)
                image.after_annotation_area_change()

            messages.success(request, 'Annotation area successfully edited.')
            return HttpResponseRedirect(reverse('image_detail', args=[image.id]))
        else:
            messages.error(request, 'Please correct the errors below.')
    else:
        # Just reached this form page
        annotationAreaForm = AnnotationAreaPixelsForm(image=image)

    # Scale down the image to have a max width of 800 pixels.
    MAX_DISPLAY_WIDTH = 800

    # jQuery UI resizing with containment isn't subpixel-precise, so
    # the display height is rounded to an int.  Thus, need to track
    # width/height scaling factors separately for accurate calculations.
    display_width = min(MAX_DISPLAY_WIDTH, image.original_width)
    width_scale_factor = float(display_width) / image.original_width
    display_height = int(round(image.original_height * width_scale_factor))
    height_scale_factor = float(display_height) / image.original_height

    dimensions = dict(
        displayWidth = display_width,
        displayHeight = display_height,
        fullWidth = image.original_width,
        fullHeight = image.original_height,
        widthScaleFactor = width_scale_factor,
        heightScaleFactor = height_scale_factor,
    )
    thumbnail_dimensions = (display_width, display_height)

    return render_to_response('annotations/annotation_area_edit.html', {
        'source': source,
        'image': image,
        'dimensions': simplejson.dumps(dimensions),
        'thumbnail_dimensions': thumbnail_dimensions,
        'annotationAreaForm': annotationAreaForm,
        },
        context_instance=RequestContext(request)
    )
Ejemplo n.º 12
0
 def annotation_area_display(self):
     """
     Display the annotation area parameters in templates.
     Usage: {{ myimage.annotation_area_display }}
     """
     return AnnotationAreaUtils.db_format_to_display(self.metadata.annotation_area)
Ejemplo n.º 13
0
 def image_annotation_area_display(self):
     """
     Display the annotation-area parameters in templates.
     Usage: {{ mysource.annotation_area_display }}
     """
     return AnnotationAreaUtils.db_format_to_display(self.image_annotation_area)
Ejemplo n.º 14
0
def source_new(request):
    """
    Page with the form to create a new Source.
    """

    # We can get here one of two ways: either we just got to the form
    # page, or we just submitted the form.  If POST, we submitted; if
    # GET, we just got here.
    if request.method == 'POST':
        # Bind the forms to the submitted POST data.
        sourceForm = ImageSourceForm(request.POST)
        location_key_form = LocationKeyForm(request.POST)
        pointGenForm = PointGenForm(request.POST)
        annotationAreaForm = AnnotationAreaPercentsForm(request.POST)

        # <form>.is_valid() calls <form>.clean() and checks field validity.
        # Make sure is_valid() is called for all forms, so all forms are checked and
        # all relevant error messages appear.
        source_form_is_valid = sourceForm.is_valid()
        location_key_form_is_valid = location_key_form.is_valid()
        point_gen_form_is_valid = pointGenForm.is_valid()
        annotation_area_form_is_valid = annotationAreaForm.is_valid()

        if source_form_is_valid and location_key_form_is_valid \
           and point_gen_form_is_valid and annotation_area_form_is_valid:

            # Since sourceForm is a ModelForm, after calling sourceForm's
            # is_valid(), a Source instance is created.  We retrieve this
            # instance and add the other values to it before saving to the DB.
            newSource = sourceForm.instance

            for key_field in ['key1', 'key2', 'key3', 'key4', 'key5']:
                if key_field in location_key_form.cleaned_data:
                    setattr(newSource, key_field, location_key_form.cleaned_data[key_field])

            newSource.default_point_generation_method = PointGen.args_to_db_format(**pointGenForm.cleaned_data)
            newSource.image_annotation_area = AnnotationAreaUtils.percentages_to_db_format(**annotationAreaForm.cleaned_data)
            newSource.labelset = LabelSet.getEmptyLabelset()
            newSource.save()

            # Make the current user an admin of the new source
            newSource.assign_role(request.user, Source.PermTypes.ADMIN.code)

            # Add a success message
            messages.success(request, 'Source successfully created.')
            
            # Redirect to the source's main page
            return HttpResponseRedirect(reverse('source_main', args=[newSource.id]))
        else:
            # Show the form again, with error message
            messages.error(request, 'Please correct the errors below.')
    else:
        # Unbound (empty) forms
        sourceForm = ImageSourceForm()
        location_key_form = LocationKeyForm()
        pointGenForm = PointGenForm()
        annotationAreaForm = AnnotationAreaPercentsForm()

    # RequestContext is needed for CSRF verification of the POST form,
    # and to correctly get the path of the CSS file being used.
    return render_to_response('images/source_new.html', {
        'sourceForm': sourceForm,
        'location_key_form': location_key_form,
        'pointGenForm': pointGenForm,
        'annotationAreaForm': annotationAreaForm,
        },
        context_instance=RequestContext(request)
    )