basic support for bulk series uploading

This commit is contained in:
Ross
2023-08-14 12:39:26 +01:00
parent 9ae1874073
commit d4eda9fdfb
10 changed files with 259 additions and 12 deletions
+2
View File
@@ -13,6 +13,7 @@ from .models import (
Structure,
PathologicalProcess,
Presentation,
UncategorisedDicom,
)
from django.forms import ModelForm
@@ -38,6 +39,7 @@ admin.site.register(Differential)
admin.site.register(PathologicalProcess)
admin.site.register(Presentation)
admin.site.register(CaseDetail)
admin.site.register(UncategorisedDicom)
class DifferentialInline(admin.TabularInline):
+137
View File
@@ -0,0 +1,137 @@
from collections import defaultdict
from typing import List
from django.shortcuts import get_object_or_404
from ninja import ModelSchema, Router, Schema, Field
# from .decorators import user_is_author_or_rapid_checker
from django.core.exceptions import PermissionDenied
from generic.decorators import check_user_in_group
from generic.constants import Group
from ninja.security import django_auth
from ninja import NinjaAPI, File
from ninja.files import UploadedFile
from generic.models import Modality
from .models import Case, DuplicateDicom, Series, SeriesImage, UncategorisedDicom
router = Router()
class SeriesSchema(ModelSchema):
case_id: List[int] = []
class Config:
model = Series
model_fields = ["id", "modality", "examination", "series_instance_uid", "author"]
@staticmethod
def resolve_case_id(obj):
return [i.id for i in obj.case.all()]
@router.post("/upload_dicom", auth=django_auth)
def upload_dicom(request, files: List[UploadedFile] = File(...)):
uploaded = []
duplicate = []
for file in files:
# data = file.read()
try:
ud = UncategorisedDicom(image=file, user=request.user)
ud.save()
uploaded.append(ud)
except DuplicateDicom:
duplicate.append(ud)
pass
return {"uploaded": len(uploaded), "duplicates": len(duplicate)}
@router.get("/clear_dicoms", auth=django_auth)
def clear_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
dicoms.delete()
return True
@router.get("/uncategorised_dicoms", auth=django_auth)
def uncategorised_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = defaultdict(list)
for d in dicoms:
tags = d.get_basic_dicom_tags()
data[tags["SeriesInstanceUID"]].append(tags)
return data
@router.get("/import_dicoms", auth=django_auth)
def import_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = defaultdict(list)
for d in dicoms:
tags = d.get_basic_dicom_tags()
data[tags["SeriesInstanceUID"]].append((d, tags))
for series_uid in data:
modality = get_object_or_404(Modality, short_code=tags["Modality"])
tags = data[series_uid][0][1]
# Check if series with the id already exists (in which case we just add to htat)
try:
series = Series.objects.get(series_instance_uid=tags["SeriesInstanceUID"])
except Series.DoesNotExist:
series = Series(
modality=modality,
description=tags["SeriesDescription"],
series_instance_uid=tags["SeriesInstanceUID"],
)
series.save()
# We might only want to add the author during creation....
series.author.add(request.user)
for dicom, dicom_tags in data[series_uid]:
series_image = SeriesImage(image=dicom.image, series=series)
series_image.save()
dicom.delete()
return {}
@router.get("/orphan_series", auth=django_auth, response=List[SeriesSchema])
def orphan_series(request):
#print(request.user.series.filter(case=None))
return request.user.series.filter(case=None)
@router.get("/series_remove_duplicate_images", auth=django_auth)
def series_remove_duplicate_images(request, series_id: int):
series = get_object_or_404(Series, pk=series_id)
img_ids = set()
dupes = set()
for series_image in series.images.all():
if series_image.image_md5_hash in img_ids:
dupes.add(series_image.id)
img_ids.add(series_image.image_md5_hash)
if dupes:
SeriesImage.objects.filter(id__in=dupes).delete()
return len(dupes)
+11 -1
View File
@@ -30,6 +30,7 @@ from atlas.models import (
Condition,
Structure,
Subspecialty,
UncategorisedDicom,
UserReportAnswer,
)
@@ -589,4 +590,13 @@ class SelfReviewForm(ModelForm):
case_id = kwargs.pop("case_id")
ModelForm.__init__(self, *args, **kwargs)
super(SelfReviewForm, self).__init__(*args, **kwargs)
super(SelfReviewForm, self).__init__(*args, **kwargs)
class UncategorisedDicomForm(ModelForm):
class Meta:
model = UncategorisedDicom
fields = ["image"]#, "user"]
#UncategorisedDicomFormset = formset_factory(UncategorisedDicomForm)
@@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-08-14 09:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0012_seriesimage_image_md5_hash_seriesimage_is_dicom_and_more'),
]
operations = [
migrations.AddField(
model_name='uncategoriseddicom',
name='is_dicom',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,30 @@
# Generated by Django 4.1.4 on 2023-08-14 10:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('generic', '0008_delete_selfreview'),
('atlas', '0013_uncategoriseddicom_is_dicom'),
]
operations = [
migrations.AddField(
model_name='series',
name='series_instance_uid',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='series',
name='examination',
field=models.ForeignKey(blank=True, help_text='Name of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_examination', to='generic.examination'),
),
migrations.AlterField(
model_name='series',
name='modality',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_modality', to='generic.modality'),
),
]
+26 -3
View File
@@ -32,7 +32,7 @@ from sortedm2m.fields import SortedManyToManyField
import string
from collections import defaultdict
from helpers.images import image_as_base64, pretty_print_dicom
from helpers.images import get_image_hash, image_as_base64, pretty_print_dicom
from generic.models import (
@@ -452,6 +452,7 @@ class Series(SeriesBase):
related_name="atlas_series_modality",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
examination = models.ForeignKey(
Examination,
@@ -459,6 +460,7 @@ class Series(SeriesBase):
related_name="atlas_series_examination",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
plane = models.ForeignKey(
Plane,
@@ -483,6 +485,8 @@ class Series(SeriesBase):
related_name="series",
)
series_instance_uid = models.CharField(max_length=255, blank=True, null=True)
# findings = models.TextField(null=True, blank=True, help_text="Findings on the series / stack")
def get_full_str(self):
@@ -811,6 +815,22 @@ class UncategorisedDicom(models.Model):
)
image_md5_hash = models.CharField(max_length=32, null=True, blank=True)
is_dicom = models.BooleanField(default=False)
def save(self, *args, **kwargs):
"""Override save method to add image hash"""
if self.image:
image_hash, is_dicom = get_image_hash(self.image)
self.is_dicom = is_dicom
self.image_md5_hash = image_hash
if UncategorisedDicom.objects.filter(image_md5_hash=image_hash):
raise DuplicateDicom
# Hack for tests
if image_hash != "12345ABCD":
super().save(*args, **kwargs) # Call the "real" save() method.
def get_dicom_info(self):
try:
@@ -837,10 +857,13 @@ class UncategorisedDicom(models.Model):
for tag in to_include:
if tag in ds:
tags[tag] = ds[tag]
tags[tag] = ds[tag].value
else:
tags[tag] = ""
return ds
return tags
except pydicom.errors.InvalidDicomError:
return None
class DuplicateDicom(Exception):
pass
+5 -5
View File
@@ -1882,12 +1882,12 @@ class AddSelfReview(CreateView):
@login_required
def uncategorised_dicoms(request, pk):
dicoms = UncategorisedDicom.objects.filter(user=request.User)
def uncategorised_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = {}
data = []
for d in dicoms:
d[d.image] = d.get_basic_dicom_tags()
data.append((d.image.name, d.get_basic_dicom_tags()))
return JsonResponse(data)
return JsonResponse(data, safe=False)
@@ -0,0 +1,23 @@
# Generated by Django 4.1.4 on 2023-08-14 11:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0008_delete_selfreview'),
]
operations = [
migrations.AddField(
model_name='modality',
name='short_code',
field=models.CharField(max_length=5, null=True, unique=True),
),
migrations.AlterField(
model_name='modality',
name='modality',
field=models.CharField(max_length=200, unique=True),
),
]
+3 -2
View File
@@ -46,10 +46,11 @@ def findMiddle(input_list):
class Modality(models.Model):
modality = models.CharField(max_length=200)
modality = models.CharField(max_length=200, unique=True)
short_code = models.CharField(max_length=5, unique=True, null=True)
def __str__(self):
return self.modality
return f"{self.modality} ({self.short_code})"
class Plane(models.Model):
+4 -1
View File
@@ -5,6 +5,7 @@ from longs.api import router as longs_router
from anatomy.api import router as anatomy_router
from sbas.api import router as sbas_router
from physics.api import router as physics_router
from atlas.api import router as atlas_router
api = NinjaAPI(csrf=True, version="1")
@@ -15,7 +16,9 @@ api.add_router("anatomy/", anatomy_router)
api.add_router("longs/", longs_router)
api.add_router("sbas/", sbas_router)
api.add_router("physics/", physics_router)
api.add_router("atlas/", atlas_router)
@api.get("/hello")
def hello(request):
return "Hello world"
return "Hello world"