diff --git a/atlas/api.py b/atlas/api.py index 5938cceb..4ed20578 100644 --- a/atlas/api.py +++ b/atlas/api.py @@ -21,17 +21,25 @@ from .models import Case, DuplicateDicom, Series, SeriesImage, UncategorisedDico router = Router() + class SeriesSchema(ModelSchema): case_id: List[int] = [] class Config: model = Series - model_fields = ["id", "modality", "examination", "series_instance_uid", "author"] + model_fields = [ + "id", + "modality", + "examination", + "series_instance_uid", + "author", + ] @staticmethod def resolve_case_id(obj): return [i.id for i in obj.case.all()] + class CaseSchema(ModelSchema): class Config: model = Case @@ -51,6 +59,7 @@ def upload_dicom(request, files: List[UploadedFile] = File(...)): uploaded.append((file.name, ud.image_md5_hash)) except DuplicateDicom: + print(ud.check_for_duplicates(ud.image_md5_hash)) duplicate.append((file.name, ud.image_md5_hash)) pass @@ -75,10 +84,12 @@ def uncategorised_dicoms(request): tags = d.get_basic_dicom_tags() data[tags["SeriesInstanceUID"]].append(tags) + data["image_hash"].append(d.image_md5_hash) return data -def import_dicoms_helper(request, case_id: int | None=None): + +def import_dicoms_helper(request, case_id: int | None = None): dicoms = UncategorisedDicom.objects.filter(user=request.user) data = defaultdict(list) @@ -89,7 +100,6 @@ def import_dicoms_helper(request, case_id: int | None=None): series_list = [] for series_uid in data: - modality = get_object_or_404(Modality, short_code=tags["Modality"]) tags = data[series_uid][0][1] @@ -111,9 +121,14 @@ def import_dicoms_helper(request, case_id: int | None=None): series.case.add(case_id) for dicom, dicom_tags in data[series_uid]: - series_image = SeriesImage(image=dicom.image, series=series) + series_image = SeriesImage( + image=dicom.image, + series=series, + is_dicom=True, + image_md5_hash=dicom.image_md5_hash, + ) series_image.save() - #dicom.image.delete() + # dicom.image.delete() dicom.delete() series_list.append(series) @@ -123,23 +138,22 @@ def import_dicoms_helper(request, case_id: int | None=None): @router.get("/import_dicoms", auth=django_auth, response=List[SeriesSchema]) def import_dicoms(request): - return import_dicoms_helper(request) + @router.get("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema]) def import_dicoms_case(request, case_id: int): - return import_dicoms_helper(request, case_id=case_id) + @router.get("/orphan_series", auth=django_auth, response=List[SeriesSchema]) def orphan_series(request): - #print(request.user.series.filter(case=None)) + # 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() @@ -154,21 +168,34 @@ def series_remove_duplicate_images(request, series_id: int): return len(dupes) + @router.get("/get_cases_user", auth=django_auth, response=List[CaseSchema]) def get_cases_user(request): print("HELLO") return Case.objects.filter(author=request.user) + @router.get("/check_image_hash/{hash}", auth=django_auth) def check_image_hash(request, hash: str): - try: - series_image = SeriesImage.objects.get(image_md5_hash=hash) - data = { - "status": "success", - "id": series_image.pk, - "url": series_image.series.get_absolute_url(), - } - return data - except SeriesImage.DoesNotExist: - data = {"status": "success", "id": False} - return data \ No newline at end of file + try: + print(hash) + series_image = SeriesImage.objects.get(image_md5_hash=hash) + data = { + "status": "success", + "id": series_image.pk, + "url": series_image.series.get_absolute_url(), + } + return data + except SeriesImage.DoesNotExist: + data = {"status": "success", "id": False} + return data + +@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_md5_hash = "" + + series_image.save() + + print(series_image) diff --git a/atlas/migrations/0016_remove_uncategoriseddicom_is_dicom.py b/atlas/migrations/0016_remove_uncategoriseddicom_is_dicom.py new file mode 100644 index 00000000..28b07780 --- /dev/null +++ b/atlas/migrations/0016_remove_uncategoriseddicom_is_dicom.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.4 on 2023-08-21 12:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0015_alter_seriesimage_series'), + ] + + operations = [ + migrations.RemoveField( + model_name='uncategoriseddicom', + name='is_dicom', + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 020fc61c..157f5b42 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -32,7 +32,7 @@ from sortedm2m.fields import SortedManyToManyField import string from collections import defaultdict -from helpers.images import get_image_hash, image_as_base64, pretty_print_dicom +from helpers.images import get_image_dicom_hash, get_image_hash, image_as_base64, pretty_print_dicom from generic.models import ( @@ -801,10 +801,9 @@ class SelfReview(models.Model): class UncategorisedDicom(models.Model): - #We use image to maintain consitency across apps + # We use image to maintain consitency across apps image = models.FileField(upload_to=uncategorised_dicom_directory_path) - user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, @@ -816,24 +815,39 @@ class UncategorisedDicom(models.Model): ) image_md5_hash = models.CharField(max_length=32, null=True, blank=True) - is_dicom = models.BooleanField(default=False) + + def check_for_duplicates(self, image_hash): + duplicate = None + + if obj := UncategorisedDicom.objects.filter(image_md5_hash=image_hash).first(): + duplicate = obj + + print(image_hash) + + if obj := SeriesImage.objects.filter(image_md5_hash=image_hash).first(): + duplicate = obj + + + return duplicate def save(self, *args, **kwargs): """Override save method to add image hash""" if self.image: - image_hash, is_dicom = get_image_hash(self.image) + # if self.check_for_duplicates(): + # return DuplicateDicom + image_hash = get_image_dicom_hash(self.image) + - self.is_dicom = is_dicom self.image_md5_hash = image_hash - if UncategorisedDicom.objects.filter(image_md5_hash=image_hash): + duplicate = self.check_for_duplicates(image_hash) + + if duplicate is not None: raise DuplicateDicom - if SeriesImage.objects.filter(image_md5_hash=image_hash): - raise DuplicateDicom - + # Hack for tests - if image_hash != "12345ABCD": + if image_hash != "1234": super().save(*args, **kwargs) # Call the "real" save() method. def get_dicom_info(self): @@ -869,5 +883,6 @@ class UncategorisedDicom(models.Model): except pydicom.errors.InvalidDicomError: return None + class DuplicateDicom(Exception): - pass \ No newline at end of file + pass diff --git a/atlas/templates/atlas/series_viewer.html b/atlas/templates/atlas/series_viewer.html index febfafa1..781e4506 100755 --- a/atlas/templates/atlas/series_viewer.html +++ b/atlas/templates/atlas/series_viewer.html @@ -69,14 +69,14 @@ title="orders dicom by uploaded filename">Order by uploaded filename {% for image in series.images.all %} - {{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} + [{{ image.id }}] {{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} {% if image.image %} [{{image.image.size|filesizeformat}}] {% endif %} - {{image.image_md5_hash}} + {{image.image_md5_hash}} ({{image.is_dicom}})
{% comment %} {{image.get_dicom_info|safe}}
{% endcomment %} diff --git a/generic/models.py b/generic/models.py index d43ed70f..a9f745b7 100644 --- a/generic/models.py +++ b/generic/models.py @@ -205,15 +205,18 @@ class SeriesImageBase(models.Model): 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 not self.image_md5_hash: + image_hash, is_dicom = get_image_hash(self.image) + + self.is_dicom = is_dicom + self.image_md5_hash = image_hash - # Hack for tests - if image_hash != "12345ABCD": - super().save(*args, **kwargs) # Call the "real" save() method. + # Hack for tests + if image_hash != "12345ABCD": + super().save(*args, **kwargs) # Call the "real" save() method. + super().save(*args, **kwargs) # Call the "real" save() method. class SeriesBase(models.Model): info = models.TextField( diff --git a/helpers/images.py b/helpers/images.py index b600a23c..482c6df4 100644 --- a/helpers/images.py +++ b/helpers/images.py @@ -155,9 +155,24 @@ def print_dicom(dataset, indent=0): repr_value)) return "\n
".join(l) +def get_image_dicom_hash(img) -> (str, bool): + dataset = pydicom.dcmread(img) + md5 = hashlib.md5() + first = True + for i in dataset.pixel_array.astype(str).flatten(): + if first: + first = False + md5.update(f"{i}".encode()) + else: + md5.update(f",{i}".encode()) + + hash = md5.hexdigest() + return hash + def get_image_hash(img) -> (str, bool): is_dicom = False # Try and read the file as a dicom + hash = "1234" try: # and generate a hash from the pixel data # TODO: improve? @@ -181,12 +196,12 @@ def get_image_hash(img) -> (str, bool): is_dicom = True # ---- - except pydicom.errors.InvalidDicomError: + except pydicom.errors.InvalidDicomError as e: try: # This is horrible (but needed for current unit tests) # (we use a temporary file that breaks here) img.file.open() hash = hashlib.md5(img.read()).hexdigest() except AttributeError: - return "12345ABCD", False + return hash, False return hash, is_dicom \ No newline at end of file