Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion exact/exact/datasets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from exact.administration.models import Product
from exact.annotations.models import Annotation, AnnotationType
from exact.datasets.forms import DatasetForm, MITOS_WSI_CMCDatasetForm, CATCH_DatasetForm
from exact.images.models import ImageSet, Image
from exact.images.models import AuxiliaryFile, ImageSet, Image
from exact.images.views import view_imageset


Expand Down
3 changes: 2 additions & 1 deletion exact/exact/images/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from django.contrib import admin

# Register your models here.
from .models import Image, ImageSet, SetTag
from .models import Image, ImageSet, SetTag, AuxiliaryFile

admin.site.register(ImageSet)
admin.site.register(Image)
admin.site.register(SetTag)
admin.site.register(AuxiliaryFile)
25 changes: 19 additions & 6 deletions exact/exact/images/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ def filter_annotation_type(self, queryset, field_name, value):
return queryset


class AuxiliaryFileViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
serializer_class = serializers.AuxiliaryFileSerializer


class ImageViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
Expand Down Expand Up @@ -458,13 +462,22 @@ def create(self, request):
path = Path(path)
name = path.name

image = models.Image(
name=name,
image_set=imageset,
checksum=fchecksum)
if (Path(path).suffix.lower().endswith(".csv") or Path(path).suffix.lower().endswith(".txt") or
Path(path).suffix.lower().endswith(".json") or Path(path).suffix.lower().endswith(".sqlite")):
# This is an auxiliary file, not an image.
newFile = models.AuxiliaryFile(image_set=imageset, name=name, filesize=os.path.getsize(path))
images.append(newFile)

else:


image = models.Image(
name=name,
image_set=imageset,
checksum=fchecksum)

image.save_file(path)
images.append(image)
image.save_file(path)
images.append(image)

except Exception as e:
errors.append(e.message)
Expand Down
30 changes: 30 additions & 0 deletions exact/exact/images/migrations/0035_auxiliaryfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.18 on 2025-01-21 09:20

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('images', '0034_alter_framedescription_description_and_more'),
]

operations = [
migrations.CreateModel(
name='AuxiliaryFile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
('filesize', models.IntegerField()),
('location', models.CharField(blank=True, max_length=256, null=True)),
('description', models.TextField(blank=True, max_length=1000, null=True)),
('time', models.DateTimeField(auto_now_add=True)),
('associated_to_image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='images.image')),
('creator', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('image_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='auxiliary_files', to='images.imageset')),
],
),
]
42 changes: 39 additions & 3 deletions exact/exact/images/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,10 @@ def save_file(self, path:Path):
raise

def __str__(self):
return u'Image: {0}'.format(self.name)
return u'Image: {0} (Image Set: {1}, Team: {2})'.format(self.name, self.image_set.name, self.image_set.team.name)

def __repr__(self):
return u'Image: {0}'.format(self.name)
return u'Image: {0} (Image Set: {1}, Team: {2})'.format(self.name, self.image_set.name, self.image_set.team.name)

# Image signals for del the cache
@receiver([post_save, post_delete, m2m_changed], sender=Image)
Expand All @@ -380,6 +380,40 @@ def image_changed_handler(sender, instance, **kwargs):
if hasattr(cache, "delete_pattern"):
cache.delete_pattern(f"*/api/v1/images/image_sets/{instance.image_set_id}/*")

class AuxiliaryFile(models.Model):
name = models.CharField(max_length=256)
filesize = models.IntegerField()
location = models.CharField(max_length=256, null=True, blank=True)
description = models.TextField(max_length=1000, null=True, blank=True)
associated_to_image = models.ForeignKey(Image,
on_delete=models.SET_NULL,
null=True)
time = models.DateTimeField(auto_now_add=True)
image_set = models.ForeignKey(
'ImageSet', on_delete=models.CASCADE, related_name='auxiliary_files')
creator = models.ForeignKey(settings.AUTH_USER_MODEL,
default=None,
on_delete=models.SET_NULL,
null=True,
blank=True)


def path(self):
return os.path.join(self.image_set.root_path(), self.name)

def delete(self, *args, **kwargs):
#self.image_set.zip_state = ImageSet.ZipState.INVALID
#self.image_set.save(update_fields=('zip_state',))
file_path = Path(settings.IMAGE_PATH) / self.path()
if os.path.exists(file_path):
os.remove(file_path)
super(AuxiliaryFile, self).delete(*args, **kwargs)

def __str__(self):
return '%s (ImageSet: %s, Team: %s) ' % (self.name, self.image_set.name, self.image_set.team.name)



class ImageSet(models.Model):
class Meta:
unique_together = [
Expand Down Expand Up @@ -537,7 +571,9 @@ def has_perm(self, permission: str, user: get_user_model()) -> bool:
return permission in self.get_perms(user)

def __str__(self):
return u'Imageset: {0}'.format(self.name)
return u'Imageset: {0} (Team: {1})'.format(self.name, self.team.name)



@property
def prio_symbol(self):
Expand Down
22 changes: 22 additions & 0 deletions exact/exact/images/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ class Meta:
"FrameDescriptions" : ('exact.images.serializers.FrameDescriptionSerializer', {'read_only': True, 'many':True}),
}

class AuxiliaryFileSerializer(FlexFieldsModelSerializer):
class Meta:
model = Image
fields = (
'id',
'name',
'filename',
'description',
'creator',
'associated_to_image',
'time',
'filesize',
'image_set',
)

expandable_fields = {
"image_set": ('exact.images.serializers.ImageSetSerializer', {'read_only': True}),
"associated_to_image": ('exact.images.serializers.ImageSerializer', {'read_only': True}),
}




class SetTagSerializer(FlexFieldsModelSerializer):
class Meta:
Expand Down
18 changes: 18 additions & 0 deletions exact/exact/images/templates/images/imageset.html
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,24 @@ <h3>Computer generated images</h3>
</ul>
</div>
</div>
{% if imageset.auxiliary_files.all %}
<div class="card">
<div class="card-header">
<h3>Auxiliary Files</h3>
</div>
<div class="card-body">
<ul>
{% for auxfile in imageset.auxiliary_files.all %}
<li>
{{ auxfile.name }} <a href="{% url 'images:download_auxfile' auxfile.id %}"><img src="{% static 'images/download.svg' %}"></a>
</li>
{% endfor %}

</ul>
</div>
</div>
{% endif %}

</div>
<div class="col-md-6">
<div class="card">
Expand Down
29 changes: 26 additions & 3 deletions exact/exact/images/templates/images/imageset_v2.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@
{% endverbatim %}
<img class="card-img-top image" src="{% static 'images/upload_failed.svg' %}">
{% verbatim %}
{% } else { %}
{% } else if (file.type == "auxfile") { %}
{% endverbatim %}
<a><img class="card-img-top image" src="{% static 'images/aux_file.svg' %}"></a>
{% verbatim %}
{% } else { %}
{% endverbatim %}
<a href="{% url 'annotations:annotate_without_parameters' %}{% verbatim %}{%=file.id%}/">
<img class="card-img-top image" src="{% endverbatim %}{{ api }}{% verbatim %}v1/images/images/{%=file.id%}/thumbnail">
Expand Down Expand Up @@ -90,7 +94,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="renameModalLabel">Rename Item</h5>
<h5 class="modal-title" id="renameModalLabel">Edit Item</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close">X</button>
</div>
<div class="modal-body">
Expand Down Expand Up @@ -300,8 +304,27 @@ <h4><img src="{% static "images/clipboard-data.svg" %}"> Annotations</h4>
</div>
<p></p>
{% endfor %}
</div>

</div>

<br>
<div>
<div class="card">
<h4><img src="{% static "images/clipboard-data.svg" %}"> Auxiliary Files</h4>
<div class="card">
<ul>
{% for auxfile in imageset.auxiliary_files.all %}
<li>


{{ auxfile.name }} <a href="{% url 'images:download_auxfile' auxfile.id %}"><img src="{% static 'images/download.svg' %}"></a>
</li>
{% endfor %}
</div>

</div>
</div>

</div>

<!-- END: Images tab -->
Expand Down
1 change: 1 addition & 0 deletions exact/exact/images/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
re_path(r'^image/delete/(\d+)/$', views.delete_images, name='delete_images'),
re_path(r'^api/image/delete/(\d+)/$', views.delete_images_api, name='delete images (API)'),
re_path(r'^api/image/download/(\d+)/$', views.download_image_api, name='download_api'),
re_path(r'^api/auxfile/download/(\d+)/$', views.download_auxfile_api, name='download_auxfile'),
re_path(r'^image/copy/(\d+)/(\d+)/$', views.copy_image, name='copy_image'),

re_path(r'^api/image/opened/(\d+)/$', views.image_opened, name='image_opened'),
Expand Down
48 changes: 37 additions & 11 deletions exact/exact/images/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from rest_framework.settings import api_settings

from exact.images.api_views import ImageSetViewSet
from exact.images.serializers import ImageSetSerializer, ImageSerializer, SetTagSerializer, serialize_imageset
from exact.images.serializers import ImageSetSerializer, ImageSerializer, SetTagSerializer, serialize_imageset, AuxiliaryFileSerializer
from exact.images.forms import ImageSetCreationForm, ImageSetCreationFormWT, ImageSetEditForm
from exact.annotations.views import api_copy_annotation
from exact.users.forms import TeamCreationForm
Expand All @@ -38,7 +38,7 @@
from exact.administration.models import Product
from exact.administration.serializers import ProductSerializer

from .models import ImageRegistration, ImageSet, Image, SetTag
from .models import ImageRegistration, ImageSet, Image, SetTag, AuxiliaryFile
from .forms import LabelUploadForm, CopyImageSetForm
from exact.annotations.models import Annotation, Export, ExportFormat, \
AnnotationType, Verification, LogImageAction
Expand Down Expand Up @@ -313,12 +313,21 @@ def upload_image(request, imageset_id):
path = Path(path)
name = path.name

image = Image(
name=name,
image_set=imageset,
checksum=fchecksum)
if (Path(path).suffix.lower().endswith(".csv") or Path(path).suffix.lower().endswith(".txt") or
Path(path).suffix.lower().endswith(".json") or Path(path).suffix.lower().endswith(".sqlite")):
# This is an auxiliary file, not an image.

image.save_file(path)
image = AuxiliaryFile(image_set=imageset, name=name, filesize=os.path.getsize(path), creator = request.user)
image.save()
type='auxfile'

else:
image = Image(
name=name,
image_set=imageset,
checksum=fchecksum)
type='image'
image.save_file(path)
except Exception as e:
import sys
import traceback
Expand Down Expand Up @@ -360,11 +369,8 @@ def upload_image(request, imageset_id):
if errormessage == '':
json_files.append({'name': f.name,
'size': f.size,
'type': type,
'id' : image.id,
# 'url': reverse('images_imageview', args=(image.id, )),
# 'thumbnailUrl': reverse('images_imageview', args=(image.id, )),
# 'deleteUrl': reverse('images_imagedeleteview', args=(image.id, )),
# 'deleteType': "DELETE",
})
else:
json_files.append({'name': f.name,
Expand Down Expand Up @@ -636,6 +642,26 @@ def download_image_api(request, image_id) -> Response:

return response

@api_view(['GET'])
def download_auxfile_api(request, auxfile_id) -> Response:
original_image = request.GET.get("original_image", None)
image = get_object_or_404(AuxiliaryFile, id=auxfile_id)
if not image.image_set.has_perm('read', request.user):
return Response({'message': 'you do not have the permission to access this imageset'
}, status=HTTP_403_FORBIDDEN)

file_path = Path(settings.IMAGE_PATH) / image.path()
if original_image is not None and 'True' == original_image:
file_path = Path(image.original_path())


response = FileResponse(open(str(file_path), 'rb'), content_type='application/octet-stream')

response['Content-Length'] = os.path.getsize(file_path)
response['Content-Disposition'] = "attachment; filename={}".format(file_path.name)

return response

@api_view(['GET'])
def delete_images_api(request, image_id) -> Response:
image = get_object_or_404(Image, id=image_id)
Expand Down
1 change: 0 additions & 1 deletion exact/exact/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ def user(request, user_id):



print('PW Matching: ',passwordmatching)

return render(request, 'users/view_user.html', {
'user': user,
Expand Down
Loading