Add download functionality for case DICOMs and metadata as ZIP
This commit is contained in:
@@ -223,6 +223,11 @@
|
|||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">Open in new tab</a>
|
<a class="dropdown-item" target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">Open in new tab</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if can_edit %}
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="{% url 'atlas:case_download' case.pk %}">Download DICOMs + metadata (ZIP)</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-block">
|
<div class="normal-block">
|
||||||
|
|||||||
@@ -525,6 +525,11 @@ urlpatterns = [
|
|||||||
views.SeriesImagesZipView.as_view(),
|
views.SeriesImagesZipView.as_view(),
|
||||||
name="series_download",
|
name="series_download",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"case/<int:pk>/download",
|
||||||
|
views.case_download,
|
||||||
|
name="case_download",
|
||||||
|
),
|
||||||
path("api_tokens/", views.api_tokens_page, name="api_tokens"),
|
path("api_tokens/", views.api_tokens_page, name="api_tokens"),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/order_dicom_instance",
|
"series/<int:pk>/order_dicom_instance",
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ from django.urls.exceptions import NoReverseMatch
|
|||||||
from django.http import Http404, JsonResponse
|
from django.http import Http404, JsonResponse
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
|
||||||
from generic.models import CidUser, CidUserExam, CimarCase, Site
|
from generic.models import CidUser, CidUserExam, CimarCase, Site
|
||||||
from atlas.helpers import get_cases_available_to_user
|
from atlas.helpers import get_cases_available_to_user
|
||||||
@@ -3373,6 +3377,71 @@ class SeriesImagesZipView(SeriesImagesZipViewBase, SuperuserRequiredMixin):
|
|||||||
series_object = Series
|
series_object = Series
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def case_download(request, pk):
|
||||||
|
"""Allow a case author (or superuser) to download all linked series DICOMs
|
||||||
|
and a metadata export as a zip file.
|
||||||
|
"""
|
||||||
|
case = get_object_or_404(Case, pk=pk)
|
||||||
|
|
||||||
|
# Only allow superusers or case authors
|
||||||
|
if not (request.user.is_superuser or case.author.filter(id=request.user.id).exists()):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
temp_file = ContentFile(b"", name=f"case_{case.pk}.zip")
|
||||||
|
with zipfile.ZipFile(temp_file, mode="w", compression=zipfile.ZIP_DEFLATED) as zip_file:
|
||||||
|
metadata = {
|
||||||
|
"case": {
|
||||||
|
"id": case.pk,
|
||||||
|
"title": case.title,
|
||||||
|
"authors": [str(a) for a in case.author.all()],
|
||||||
|
},
|
||||||
|
"series": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterate ordered series if available, otherwise fall back to related series
|
||||||
|
try:
|
||||||
|
ordered_series = case.get_ordered_series()
|
||||||
|
except Exception:
|
||||||
|
ordered_series = list(case.series.all())
|
||||||
|
|
||||||
|
for series in ordered_series:
|
||||||
|
series_entry = {"pk": series.pk, "description": str(series)}
|
||||||
|
# Prefer series-provided dicom JSON if available
|
||||||
|
try:
|
||||||
|
series_entry["dicom_json"] = series.get_series_dicom_json()
|
||||||
|
except Exception:
|
||||||
|
series_entry["dicom_json"] = None
|
||||||
|
|
||||||
|
images = series.images.filter(removed=False).order_by("position")
|
||||||
|
series_entry["images"] = []
|
||||||
|
for img in images:
|
||||||
|
try:
|
||||||
|
file_obj = img.image.file
|
||||||
|
fname = os.path.split(file_obj.name)[-1]
|
||||||
|
arcname = f"series_{series.pk}/{fname}"
|
||||||
|
zip_file.writestr(arcname, file_obj.read())
|
||||||
|
series_entry["images"].append({"filename": fname, "position": img.position})
|
||||||
|
except Exception:
|
||||||
|
# skip missing files
|
||||||
|
continue
|
||||||
|
|
||||||
|
metadata["series"].append(series_entry)
|
||||||
|
|
||||||
|
# Write metadata.json
|
||||||
|
zip_file.writestr("metadata.json", json.dumps(metadata, default=str, indent=2))
|
||||||
|
|
||||||
|
file_size = temp_file.tell()
|
||||||
|
temp_file.seek(0)
|
||||||
|
|
||||||
|
response = HttpResponse(temp_file, content_type="application/zip")
|
||||||
|
response["Content-Disposition"] = (
|
||||||
|
"attachment; filename=%s" % f"case_{case.pk}.zip"
|
||||||
|
)
|
||||||
|
response["Content-Length"] = file_size
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
# At the moment only the series owner / author can delete findings but anyone can create
|
# At the moment only the series owner / author can delete findings but anyone can create
|
||||||
def create_series_findings(request):
|
def create_series_findings(request):
|
||||||
# posts = Post.objects.all()
|
# posts = Post.objects.all()
|
||||||
|
|||||||
Reference in New Issue
Block a user