diff --git a/atlas/api.py b/atlas/api.py index 991188ac..81644e96 100644 --- a/atlas/api.py +++ b/atlas/api.py @@ -29,7 +29,6 @@ from loguru import logger router = Router() - class SeriesSchema(ModelSchema): case_id: List[int] = [] @@ -59,6 +58,7 @@ def upload_dicom(request, files: List[UploadedFile] = File(...)): uploaded = [] duplicate = [] failed = [] + duplicate_series = set() for file in files: # data = file.read() try: @@ -67,26 +67,41 @@ def upload_dicom(request, files: List[UploadedFile] = File(...)): ud.save() uploaded.append((file.name, ud.image_blake3_hash)) - except DuplicateDicom: + except DuplicateDicom as e: duplicate.append((file.name, ud.image_blake3_hash)) + + duplicate_series.add(e.duplicate.get_series().get_absolute_url()) + print(duplicate_series) pass except InvalidDicomError: failed.append(file.name) - return {"uploaded": uploaded, "duplicates": duplicate, "failed": failed} + return { + "uploaded": uploaded, + "duplicates": duplicate, + "failed": failed, + "duplicate_series": list(duplicate_series), + } + @router.post("/generate_image_hash", auth=django_auth) -def generate_image_hash(request, id:int): +def generate_image_hash(request, id: int): s = SeriesImage.objects.get(pk=id) s.generate_hashes() - return {"md5": s.image_md5_hash, "blake3": s.image_blake3_hash, "is_dicom": s.is_dicom} + return { + "md5": s.image_md5_hash, + "blake3": s.image_blake3_hash, + "is_dicom": s.is_dicom, + } + @router.post("/clear_dicoms", auth=django_auth) def clear_dicoms(request): - if "selection" in request.POST: - dicoms = UncategorisedDicom.objects.filter(series_instance_uid__in=request.POST.getlist("selection")) + dicoms = UncategorisedDicom.objects.filter( + series_instance_uid__in=request.POST.getlist("selection") + ) else: dicoms = UncategorisedDicom.objects.filter(user=request.user) @@ -110,15 +125,17 @@ def uncategorised_dicoms(request): return data + def import_dicoms_helper(request, case_id: int | None = None): - #dicoms = UncategorisedDicom.objects.filter(user=request.user) + # dicoms = UncategorisedDicom.objects.filter(user=request.user) if "selection" in request.POST: - dicoms = UncategorisedDicom.objects.filter(series_instance_uid__in=request.POST.getlist("selection")) + dicoms = UncategorisedDicom.objects.filter( + series_instance_uid__in=request.POST.getlist("selection") + ) else: dicoms = UncategorisedDicom.objects.filter(user=request.user) - if "order-series" in request.POST: order = request.POST["order-series"] else: @@ -136,7 +153,9 @@ def import_dicoms_helper(request, case_id: int | None = None): tags = data[series_uid][0][1] # Check if series with the id already exists (in which case we just add to htat) - if Series.objects.filter(series_instance_uid=tags["SeriesInstanceUID"]).exists(): + if Series.objects.filter( + series_instance_uid=tags["SeriesInstanceUID"] + ).exists(): series = Series.objects.get(series_instance_uid=tags["SeriesInstanceUID"]) else: series = Series( @@ -146,16 +165,16 @@ def import_dicoms_helper(request, case_id: int | None = None): ) if tags["StudyDescription"]: - examination, created = Examination.objects.get_or_create(examination=tags["StudyDescription"]) + examination, created = Examination.objects.get_or_create( + examination=tags["StudyDescription"] + ) if created: examination.modality = modality examination.save() series.examination = examination - series.save() - match order: case "order-series-instance-number": series.order_by_dicom("InstanceNumber") @@ -188,12 +207,18 @@ def import_dicoms_helper(request, case_id: int | None = None): return series_list -@router.post("/import_dicoms", auth=django_auth, response=List[Tuple[ SeriesSchema, str ]]) +@router.post( + "/import_dicoms", auth=django_auth, response=List[Tuple[SeriesSchema, str]] +) def import_dicoms(request): return import_dicoms_helper(request) -@router.post("/import_dicoms/{case_id}", auth=django_auth, response=List[Tuple[ SeriesSchema, str ]]) +@router.post( + "/import_dicoms/{case_id}", + auth=django_auth, + response=List[Tuple[SeriesSchema, str]], +) def import_dicoms_case(request, case_id: int): return import_dicoms_helper(request, case_id=case_id) @@ -220,8 +245,9 @@ def series_remove_duplicate_images(request, series_id: int): return len(dupes) + @router.get("/series_truncate/{series_id}/{start}/{end}/", auth=django_auth) -def series_truncate(request, series_id: int, start: int, end:int): +def series_truncate(request, series_id: int, start: int, end: int): print(start, end) series = get_object_or_404(Series, pk=series_id) @@ -230,11 +256,11 @@ def series_truncate(request, series_id: int, start: int, end:int): images_removed = [] for n, image in enumerate(series.get_images()): - if n >= start and n <=end: + if n >= start and n <= end: continue else: print(n, image) - image.removed=True + image.removed = True image.image = None image.save() images_removed.append(image.pk) @@ -261,10 +287,11 @@ def check_image_hash(request, hash: str): data = {"status": "success", "id": False} return data + @router.post("/check_image_hashes/", auth=django_auth) def check_images_hashes(request, hashes: List[str]): """Checks a list of image hashes and returns the series id / url if found - + Return format { "hash_id": {"id": "series_id|false", "url": "series_url|false"}, ...} """ @@ -278,21 +305,28 @@ def check_images_hashes(request, hashes: List[str]): data = { "id": series_image.pk, "url": series_image.series.get_absolute_url(), - "type": "series" + "type": "series", } except SeriesImage.DoesNotExist: try: - uncategorised_dicom = UncategorisedDicom.objects.get(image_blake3_hash=hash) - data = { "id":uncategorised_dicom.pk, "url": reverse("atlas:user_uploads"), "type": "uncategorised"} + uncategorised_dicom = UncategorisedDicom.objects.get( + image_blake3_hash=hash + ) + data = { + "id": uncategorised_dicom.pk, + "url": reverse("atlas:user_uploads"), + "type": "uncategorised", + } except UncategorisedDicom.DoesNotExist: - data = { "id": False, "url": False} + data = {"id": False, "url": False} hash_status[hash] = data return hash_status -#@router.get("/generate_image_hash/{id}", auth=django_auth) -#def generate_image_hash(request, id: int): + +# @router.get("/generate_image_hash/{id}", auth=django_auth) +# def generate_image_hash(request, id: int): # series_image = SeriesImage.objects.get(pk=id) # # series_image.image_blake3_hash = "" @@ -301,21 +335,22 @@ def check_images_hashes(request, hashes: List[str]): # # print(series_image) + @router.get("/view_dicom_tags/{hash}", auth=django_auth) def view_dicom_tags(request, hash: str): - item = SeriesImage.objects.get(image_blake3_hash=hash) return item.get_dicom_json() + @router.get("/series_split_by_dicom_tag/{series_id}/{dicom_tag}", auth=django_auth) -def series_split_by_tag(request, series_id: int, dicom_tag:str): +def series_split_by_tag(request, series_id: int, dicom_tag: str): series = get_object_or_404(Series, pk=series_id) if not series.check_user_can_edit(request.user): return {"status": "permission denied"} if dicom_tag.startswith("(") and dicom_tag.endswith(")"): - dicom_tag = tuple([hex(int(i.strip(),16)) for i in dicom_tag[1:-1].split(",")]) + dicom_tag = tuple([hex(int(i.strip(), 16)) for i in dicom_tag[1:-1].split(",")]) image_map = defaultdict(list) @@ -324,7 +359,7 @@ def series_split_by_tag(request, series_id: int, dicom_tag:str): print(image) ds = image.get_dicom_data() - #print("2a", ds[dicom_tag]) + # print("2a", ds[dicom_tag]) if dicom_tag in ds: val = ds[dicom_tag].value @@ -339,7 +374,7 @@ def series_split_by_tag(request, series_id: int, dicom_tag:str): new_series = [] case = series.case.all() authors = series.author.all() - + for n, option in enumerate(image_map): if n == 0: series.images.set(image_map[option]) @@ -353,12 +388,11 @@ def series_split_by_tag(request, series_id: int, dicom_tag:str): new_series.append(series.pk) - return new_series @router.get("/split_order_by_dicom_tag/{series_id}/{dicom_tag}", auth=django_auth) -def series_order_by_tag(request, series_id: int, dicom_tag:str): +def series_order_by_tag(request, series_id: int, dicom_tag: str): series = get_object_or_404(Series, pk=series_id) series.order_by_dicom(dicom_tag) diff --git a/atlas/forms.py b/atlas/forms.py index ca256912..71a68f42 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -143,7 +143,7 @@ class StructureForm(ModelForm): class SeriesFindingForm(ModelForm): class Meta: model = SeriesFinding - exclude = ["series", "annotation_json", "viewport_json"] + exclude = ["series", "annotation_json", "viewport_json", "current_image_id_index"] widgets = { "findings": autocomplete.ModelSelect2Multiple( diff --git a/atlas/migrations/0060_seriesfinding_current_image_id_index.py b/atlas/migrations/0060_seriesfinding_current_image_id_index.py new file mode 100644 index 00000000..80e58cb2 --- /dev/null +++ b/atlas/migrations/0060_seriesfinding_current_image_id_index.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-09-23 12:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0059_alter_casecollection_markers'), + ] + + operations = [ + migrations.AddField( + model_name='seriesfinding', + name='current_image_id_index', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 9b0d54d8..96e044f5 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -570,6 +570,9 @@ class SeriesImage(SeriesImageBase): except InvalidDicomError: return {} + def get_series(self): + return self.series + # def get_image_dicom_json(self, image_index): # try: # with pydicom.dcmread(self.image) as ds: @@ -594,6 +597,7 @@ class SeriesFinding(models.Model): conditions = models.ManyToManyField(Condition, blank=True) annotation_json = models.TextField(null=True, blank=True) viewport_json = models.TextField(null=True, blank=True) + current_image_id_index = models.IntegerField(null=True, blank=True) def __str__(self) -> str: findings = self.findings.all().values_list("name") @@ -1273,9 +1277,12 @@ class UncategorisedDicom(models.Model): basic_dicom_tags = models.JSONField(null=True, blank=True) - def check_for_duplicates(self, image_hash): + def check_for_duplicates(self, image_hash=None|str): duplicate = None + if image_hash is None: + image_hash = self.image_blake3_hash + if obj := UncategorisedDicom.objects.filter( image_blake3_hash=image_hash ).first(): @@ -1310,7 +1317,8 @@ class UncategorisedDicom(models.Model): if duplicate is not None: if duplicate != self: - raise DuplicateDicom + raise DuplicateDicom(duplicate) + # Hack for tests if image_blake3_hash != "1234": @@ -1349,9 +1357,13 @@ class UncategorisedDicom(models.Model): except pydicom.errors.InvalidDicomError: return None + def get_series(self): + return self.series + class DuplicateDicom(Exception): - pass + def __init__(self, duplicate): + self.duplicate = duplicate class Resource(models.Model, AuthorMixin): diff --git a/atlas/templates/atlas/new_uploads.html b/atlas/templates/atlas/new_uploads.html index 15b1cf1b..abc5142d 100644 --- a/atlas/templates/atlas/new_uploads.html +++ b/atlas/templates/atlas/new_uploads.html @@ -41,6 +41,7 @@