-
-
- Do you really want to permanently delete this image from the imageset?
-
-
-{% endif %}
+ {% endif %}
{% endblock %}
diff --git a/imagetagger/imagetagger/annotations/views.py b/imagetagger/imagetagger/annotations/views.py
index 883f30e1..8d4090f7 100644
--- a/imagetagger/imagetagger/annotations/views.py
+++ b/imagetagger/imagetagger/annotations/views.py
@@ -1,4 +1,5 @@
import datetime
+import json
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -21,6 +22,7 @@
from imagetagger.annotations.serializers import AnnotationSerializer, AnnotationTypeSerializer
from imagetagger.images.models import Image, ImageSet
from imagetagger.users.models import Team
+from imagetagger.images.forms import ImageMetadataForm
def export_auth(request, export_id):
@@ -32,15 +34,19 @@ def export_auth(request, export_id):
@login_required
def annotate(request, image_id):
selected_image = get_object_or_404(Image, id=image_id)
+ if selected_image is not None:
+ selected_image.metadata = json.loads(selected_image.metadata)
imageset_perms = selected_image.image_set.get_perms(request.user)
if 'read' in imageset_perms:
set_images = selected_image.image_set.images.all().order_by('name')
annotation_types = AnnotationType.objects.filter(active=True) # for the dropdown option
imageset_lock = selected_image.image_set.image_lock
+ metadataForm = ImageMetadataForm()
return render(request, 'annotations/annotate.html', {
'selected_image': selected_image,
'imageset_perms': imageset_perms,
'imageset_lock': imageset_lock,
+ 'metadata_form': metadataForm,
'set_images': set_images,
'annotation_types': annotation_types,
})
@@ -295,6 +301,7 @@ def export_format(export_format_name, imageset):
'%%imagewidth': image.width,
'%%imageheight': image.height,
'%%imagename': image.name,
+ '%%imagemetadata': json.loads(image.metadata),
'%%type': annotation.annotation_type.name,
'%%veriamount': annotation.verification_difference,
'%%vector': formatted_vector,
@@ -332,6 +339,7 @@ def export_format(export_format_name, imageset):
'%%imagewidth': image.width,
'%%imageheight': image.height,
'%%imagename': image.name,
+ '%%imagemetadata': json.loads(image.metadata),
'%%annotations': annotation_content,
'%%annoamount': annotations.count(),
}
@@ -387,6 +395,7 @@ def export_format(export_format_name, imageset):
'%%imagewidth': annotation.image.width,
'%%imageheight': annotation.image.height,
'%%imagename': annotation.image.name,
+ '%%imagemetadata': json.loads(annotation.image.metadata),
'%%type': annotation.annotation_type.name,
'%%veriamount': annotation.verification_difference,
'%%vector': formatted_vector,
@@ -624,7 +633,6 @@ def load_annotations(request) -> Response:
return Response({
'detail': 'permission for reading this image set missing.',
}, status=HTTP_403_FORBIDDEN)
-
serializer = AnnotationSerializer(
image.annotations.select_related().filter(annotation_type__active=True).order_by('annotation_type__name'),
context={
@@ -633,6 +641,7 @@ def load_annotations(request) -> Response:
many=True)
return Response({
'annotations': serializer.data,
+ 'metadata': json.loads(image.metadata)
}, status=HTTP_200_OK)
diff --git a/imagetagger/imagetagger/images/forms.py b/imagetagger/imagetagger/images/forms.py
index 8da7d9b1..a63a81e6 100644
--- a/imagetagger/imagetagger/images/forms.py
+++ b/imagetagger/imagetagger/images/forms.py
@@ -3,6 +3,16 @@
from imagetagger.images.models import ImageSet
+class ImageMetadataForm(forms.Form):
+ name = forms.CharField(
+ widget=forms.TextInput(attrs={'class': 'form-control'})
+ )
+ value = forms.CharField(
+ widget=forms.TextInput(attrs={'class': 'form-control'})
+ )
+ image = forms.HiddenInput()
+
+
class ImageSetCreationForm(forms.ModelForm):
class Meta:
model = ImageSet
diff --git a/imagetagger/imagetagger/images/migrations/0018_auto_20181210_0711.py b/imagetagger/imagetagger/images/migrations/0018_auto_20181210_0711.py
new file mode 100644
index 00000000..07a9ebee
--- /dev/null
+++ b/imagetagger/imagetagger/images/migrations/0018_auto_20181210_0711.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.0.9 on 2018-12-10 06:11
+
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('images', '0017_imageset_zip_state'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='image',
+ name='metadata',
+ field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
+ ),
+ migrations.AddField(
+ model_name='imageset',
+ name='metadata',
+ field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
+ ),
+ ]
diff --git a/imagetagger/imagetagger/images/models.py b/imagetagger/imagetagger/images/models.py
index 0f17c340..b96b1af0 100644
--- a/imagetagger/imagetagger/images/models.py
+++ b/imagetagger/imagetagger/images/models.py
@@ -2,6 +2,7 @@
from django.conf import settings
from django.contrib.auth import get_user_model
+from django.contrib.postgres.fields import JSONField
from django.db import models
import os
@@ -17,6 +18,7 @@ class Image(models.Model):
checksum = models.BinaryField()
width = models.IntegerField(default=800)
height = models.IntegerField(default=600)
+ metadata = JSONField(default=dict)
def path(self):
return os.path.join(self.image_set.root_path(), self.filename)
@@ -90,6 +92,7 @@ class ZipState:
)
pinned_by = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='pinned_sets')
zip_state = models.IntegerField(choices=ZIP_STATES, default=ZipState.INVALID)
+ metadata = JSONField(default=dict)
def root_path(self):
return os.path.join(settings.IMAGE_PATH, self.path)
diff --git a/imagetagger/imagetagger/images/urls.py b/imagetagger/imagetagger/images/urls.py
index 4832a7b4..a0044802 100644
--- a/imagetagger/imagetagger/images/urls.py
+++ b/imagetagger/imagetagger/images/urls.py
@@ -9,6 +9,8 @@
url(r'^image/setfree/(\d+)/$', views.set_free, name='setfree_imageset'),
url(r'^image/upload/(\d+)/$', views.upload_image, name='upload_image'),
url(r'^image/(\d+)/$', views.view_image, name='view_image'),
+ url(r'^image/metadata/create/$', views.metadata_create, name='metadata_create'),
+ url(r'^image/metadata/delete/(\d+)/$', views.metadata_delete, name='metadata_delete'),
url(r'^imagelist/(\d+)/$', views.list_images, name='list_images'),
url(r'^imageset/(\d+)/label-upload/$', views.label_upload, name='label_upload'),
url(r'^imageset/create/$', views.create_imageset, name='create_imageset'),
diff --git a/imagetagger/imagetagger/images/views.py b/imagetagger/imagetagger/images/views.py
index 66697ed5..8a714d03 100644
--- a/imagetagger/imagetagger/images/views.py
+++ b/imagetagger/imagetagger/images/views.py
@@ -5,7 +5,7 @@
from django.db import transaction
from django.db.models import Count, Q
from django.db.models.expressions import F
-from django.views.decorators.http import require_http_methods
+from django.views.decorators.http import require_http_methods, require_POST
from django.urls import reverse
from django.http import HttpResponseForbidden, HttpResponse, HttpResponseBadRequest, JsonResponse, \
FileResponse
@@ -19,9 +19,10 @@
from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK, \
HTTP_201_CREATED, HTTP_202_ACCEPTED, HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND
from PIL import Image as PIL_Image
+from PIL.ExifTags import TAGS
from imagetagger.images.serializers import ImageSetSerializer, ImageSerializer, SetTagSerializer
-from imagetagger.images.forms import ImageSetCreationForm, ImageSetCreationFormWT, ImageSetEditForm
+from imagetagger.images.forms import ImageSetCreationForm, ImageSetCreationFormWT, ImageSetEditForm, ImageMetadataForm
from imagetagger.users.forms import TeamCreationForm
from imagetagger.users.models import User, Team
from imagetagger.tagger_messages.forms import TeamMessageCreationForm
@@ -43,6 +44,39 @@
from datetime import date, timedelta
+@login_required
+def metadata_create(request):
+ if request.method == 'POST':
+ form = ImageMetadataForm(request.POST)
+ if form.is_valid():
+ data = form.cleaned_data
+ img = get_object_or_404(Image, pk=data['image'])
+ metadata = json.loads(img.metadata)
+ metadata[data['name']] = data['value']
+ img.metadata = json.dumps(metadata)
+ img.save()
+ messages.info(request,
+ _("Successfully updated \'{}\' in metadata".format(data['name'])))
+ return redirect(reverse('annotations:annotate', args=(img.pk,)))
+ return redirect(reverse('annotations:annotate', args=(request.POST['image'],)))
+
+
+@require_POST
+def metadata_delete(request, image_id):
+ img = get_object_or_404(Image, id=image_id)
+ metadata = json.loads(img.metadata)
+ if request.POST['key'] in metadata:
+ metadata.pop(request.POST['key'])
+ messages.info(request,
+ _("Successfully deleted \'{}\' in metadata".format(request.POST['key'])))
+ else:
+ messages.info(request,
+ _("Error! \'{}\' was not found in metadata!".format(request.POST['key'])))
+ img.metadata = json.dumps(metadata)
+ img.save()
+ return redirect(reverse('annotations:annotate', args=(image_id,)))
+
+
@login_required
def explore_imageset(request):
imagesets = ImageSet.objects.select_related('team').order_by(
@@ -173,6 +207,13 @@ def index(request):
@login_required
@require_http_methods(["POST", ])
def upload_image(request, imageset_id):
+ def extract_metadata(img):
+ metadata = dict()
+ if img._getexif() is not None:
+ for (tag, value) in img._getexif().items():
+ metadata[TAGS.get(tag)] = value
+ return json.dumps(metadata, default=str)
+
imageset = get_object_or_404(ImageSet, id=imageset_id)
if request.method == 'POST' \
and imageset.has_perm('edit_set', request.user) \
@@ -235,6 +276,8 @@ def upload_image(request, imageset_id):
try:
with PIL_Image.open(file_path) as image:
width, height = image.size
+ # extracting metadata info
+ metadata = extract_metadata(image)
file_new_path = os.path.join(imageset.root_path(), img_fname)
shutil.move(file_path, file_new_path)
shutil.chown(file_new_path, group=settings.UPLOAD_FS_GROUP)
@@ -243,7 +286,8 @@ def upload_image(request, imageset_id):
filename=img_fname,
checksum=fchecksum,
width=width,
- height=height
+ height=height,
+ metadata=metadata
)
new_image.save()
except (OSError, IOError):
@@ -265,7 +309,7 @@ def upload_image(request, imageset_id):
fchecksum.update(chunk)
fchecksum = fchecksum.digest()
# tests for duplicats in imageset
- if Image.objects.filter(checksum=fchecksum, image_set=imageset)\
+ if Image.objects.filter(checksum=fchecksum, image_set=imageset) \
.count() == 0:
fname = ('_'.join(fname[:-1]) + '_' +
''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
@@ -283,8 +327,11 @@ def upload_image(request, imageset_id):
try:
with PIL_Image.open(image.path()) as image_file:
width, height = image_file.size
+ # extracting metadata info
+ metadata = extract_metadata(image_file)
image.height = height
image.width = width
+ image.metadata = metadata
image.save()
except (OSError, IOError):
error['damaged'] = True
@@ -360,11 +407,12 @@ def view_image(request, image_id):
response = HttpResponse(content_type='image')
response['X-Accel-Redirect'] = "/ngx_static_dn/{}".format(image.relative_path())
else:
- response = FileResponse(open(file_path, 'rb'), content_type="image")
+ response = FileResponse(open(file_path, 'rb'), content_type="image")
response["Content-Length"] = os.path.getsize(file_path)
return response
+
@login_required
def list_images(request, image_set_id):
imageset = get_object_or_404(ImageSet, id=image_set_id)
@@ -472,7 +520,7 @@ def create_imageset(request):
form = ImageSetCreationForm(request.POST)
if form.is_valid():
- if team.image_sets\
+ if team.image_sets \
.filter(name=form.cleaned_data.get('name')).exists():
form.add_error(
'name',
@@ -634,7 +682,8 @@ def label_upload(request, imageset_id):
vector = json.loads(line_frags[2])
except JSONDecodeError:
report_list.append("In image \"{}\" the annotation:"
- " \"{}\" was not accepted as valid JSON".format(line_frags[0], line_frags[2]))
+ " \"{}\" was not accepted as valid JSON".format(line_frags[0],
+ line_frags[2]))
if annotation_type.validate_vector(vector):
if not Annotation.similar_annotations(vector, image, annotation_type):
@@ -662,7 +711,7 @@ def label_upload(request, imageset_id):
report_list.append(
'For the image ' + line_frags[0] + ' the annotation ' +
line_frags[2] + ' was not a valid vector or '
- 'bounding box for the annotation type'
+ 'bounding box for the annotation type'
)
else:
error_count += 1
@@ -692,8 +741,8 @@ def label_upload(request, imageset_id):
def dl_script(request):
return TemplateResponse(request, 'images/download.py', context={
- 'base_url': settings.DOWNLOAD_BASE_URL,
- }, content_type='text/plain')
+ 'base_url': settings.DOWNLOAD_BASE_URL,
+ }, content_type='text/plain')
def download_imageset_zip(request, image_set_id):
@@ -853,7 +902,8 @@ def autocomplete_image_set_tag(request) -> Response:
except (KeyError, TypeError, ValueError):
raise ParseError
tag_suggestions = list(SetTag.objects.filter(name__startswith=tag_name_query))
- tag_suggestions.extend(list(SetTag.objects.filter(~Q(name__startswith=tag_name_query) & Q(name__contains=tag_name_query))))
+ tag_suggestions.extend(
+ list(SetTag.objects.filter(~Q(name__startswith=tag_name_query) & Q(name__contains=tag_name_query))))
tag_suggestions = [tag_suggestion.name for tag_suggestion in tag_suggestions]
print(tag_suggestions)