fix image hashes (possible)
This commit is contained in:
+47
-20
@@ -21,17 +21,25 @@ from .models import Case, DuplicateDicom, Series, SeriesImage, UncategorisedDico
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class SeriesSchema(ModelSchema):
|
class SeriesSchema(ModelSchema):
|
||||||
case_id: List[int] = []
|
case_id: List[int] = []
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
model = Series
|
model = Series
|
||||||
model_fields = ["id", "modality", "examination", "series_instance_uid", "author"]
|
model_fields = [
|
||||||
|
"id",
|
||||||
|
"modality",
|
||||||
|
"examination",
|
||||||
|
"series_instance_uid",
|
||||||
|
"author",
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_case_id(obj):
|
def resolve_case_id(obj):
|
||||||
return [i.id for i in obj.case.all()]
|
return [i.id for i in obj.case.all()]
|
||||||
|
|
||||||
|
|
||||||
class CaseSchema(ModelSchema):
|
class CaseSchema(ModelSchema):
|
||||||
class Config:
|
class Config:
|
||||||
model = Case
|
model = Case
|
||||||
@@ -51,6 +59,7 @@ def upload_dicom(request, files: List[UploadedFile] = File(...)):
|
|||||||
|
|
||||||
uploaded.append((file.name, ud.image_md5_hash))
|
uploaded.append((file.name, ud.image_md5_hash))
|
||||||
except DuplicateDicom:
|
except DuplicateDicom:
|
||||||
|
print(ud.check_for_duplicates(ud.image_md5_hash))
|
||||||
duplicate.append((file.name, ud.image_md5_hash))
|
duplicate.append((file.name, ud.image_md5_hash))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -75,10 +84,12 @@ def uncategorised_dicoms(request):
|
|||||||
tags = d.get_basic_dicom_tags()
|
tags = d.get_basic_dicom_tags()
|
||||||
|
|
||||||
data[tags["SeriesInstanceUID"]].append(tags)
|
data[tags["SeriesInstanceUID"]].append(tags)
|
||||||
|
data["image_hash"].append(d.image_md5_hash)
|
||||||
|
|
||||||
return data
|
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)
|
dicoms = UncategorisedDicom.objects.filter(user=request.user)
|
||||||
|
|
||||||
data = defaultdict(list)
|
data = defaultdict(list)
|
||||||
@@ -89,7 +100,6 @@ def import_dicoms_helper(request, case_id: int | None=None):
|
|||||||
|
|
||||||
series_list = []
|
series_list = []
|
||||||
for series_uid in data:
|
for series_uid in data:
|
||||||
|
|
||||||
modality = get_object_or_404(Modality, short_code=tags["Modality"])
|
modality = get_object_or_404(Modality, short_code=tags["Modality"])
|
||||||
tags = data[series_uid][0][1]
|
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)
|
series.case.add(case_id)
|
||||||
|
|
||||||
for dicom, dicom_tags in data[series_uid]:
|
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()
|
series_image.save()
|
||||||
#dicom.image.delete()
|
# dicom.image.delete()
|
||||||
|
|
||||||
dicom.delete()
|
dicom.delete()
|
||||||
series_list.append(series)
|
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])
|
@router.get("/import_dicoms", auth=django_auth, response=List[SeriesSchema])
|
||||||
def import_dicoms(request):
|
def import_dicoms(request):
|
||||||
|
|
||||||
return import_dicoms_helper(request)
|
return import_dicoms_helper(request)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema])
|
@router.get("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema])
|
||||||
def import_dicoms_case(request, case_id: int):
|
def import_dicoms_case(request, case_id: int):
|
||||||
|
|
||||||
return import_dicoms_helper(request, case_id=case_id)
|
return import_dicoms_helper(request, case_id=case_id)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/orphan_series", auth=django_auth, response=List[SeriesSchema])
|
@router.get("/orphan_series", auth=django_auth, response=List[SeriesSchema])
|
||||||
def orphan_series(request):
|
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)
|
return request.user.series.filter(case=None)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/series_remove_duplicate_images", auth=django_auth)
|
@router.get("/series_remove_duplicate_images", auth=django_auth)
|
||||||
def series_remove_duplicate_images(request, series_id: int):
|
def series_remove_duplicate_images(request, series_id: int):
|
||||||
|
|
||||||
series = get_object_or_404(Series, pk=series_id)
|
series = get_object_or_404(Series, pk=series_id)
|
||||||
|
|
||||||
img_ids = set()
|
img_ids = set()
|
||||||
@@ -154,21 +168,34 @@ def series_remove_duplicate_images(request, series_id: int):
|
|||||||
|
|
||||||
return len(dupes)
|
return len(dupes)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/get_cases_user", auth=django_auth, response=List[CaseSchema])
|
@router.get("/get_cases_user", auth=django_auth, response=List[CaseSchema])
|
||||||
def get_cases_user(request):
|
def get_cases_user(request):
|
||||||
print("HELLO")
|
print("HELLO")
|
||||||
return Case.objects.filter(author=request.user)
|
return Case.objects.filter(author=request.user)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/check_image_hash/{hash}", auth=django_auth)
|
@router.get("/check_image_hash/{hash}", auth=django_auth)
|
||||||
def check_image_hash(request, hash: str):
|
def check_image_hash(request, hash: str):
|
||||||
try:
|
try:
|
||||||
series_image = SeriesImage.objects.get(image_md5_hash=hash)
|
print(hash)
|
||||||
data = {
|
series_image = SeriesImage.objects.get(image_md5_hash=hash)
|
||||||
"status": "success",
|
data = {
|
||||||
"id": series_image.pk,
|
"status": "success",
|
||||||
"url": series_image.series.get_absolute_url(),
|
"id": series_image.pk,
|
||||||
}
|
"url": series_image.series.get_absolute_url(),
|
||||||
return data
|
}
|
||||||
except SeriesImage.DoesNotExist:
|
return data
|
||||||
data = {"status": "success", "id": False}
|
except SeriesImage.DoesNotExist:
|
||||||
return data
|
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)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
+27
-12
@@ -32,7 +32,7 @@ from sortedm2m.fields import SortedManyToManyField
|
|||||||
|
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
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 (
|
from generic.models import (
|
||||||
@@ -801,10 +801,9 @@ class SelfReview(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class UncategorisedDicom(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)
|
image = models.FileField(upload_to=uncategorised_dicom_directory_path)
|
||||||
|
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -816,24 +815,39 @@ class UncategorisedDicom(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
image_md5_hash = models.CharField(max_length=32, null=True, blank=True)
|
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):
|
def save(self, *args, **kwargs):
|
||||||
"""Override save method to add image hash"""
|
"""Override save method to add image hash"""
|
||||||
if self.image:
|
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
|
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
|
raise DuplicateDicom
|
||||||
|
|
||||||
if SeriesImage.objects.filter(image_md5_hash=image_hash):
|
|
||||||
raise DuplicateDicom
|
|
||||||
|
|
||||||
# Hack for tests
|
# Hack for tests
|
||||||
if image_hash != "12345ABCD":
|
if image_hash != "1234":
|
||||||
super().save(*args, **kwargs) # Call the "real" save() method.
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
||||||
|
|
||||||
def get_dicom_info(self):
|
def get_dicom_info(self):
|
||||||
@@ -869,5 +883,6 @@ class UncategorisedDicom(models.Model):
|
|||||||
except pydicom.errors.InvalidDicomError:
|
except pydicom.errors.InvalidDicomError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class DuplicateDicom(Exception):
|
class DuplicateDicom(Exception):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -69,14 +69,14 @@
|
|||||||
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
||||||
</div>
|
</div>
|
||||||
{% for image in series.images.all %}
|
{% 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 %}
|
{% if image.image %}
|
||||||
[{{image.image.size|filesizeformat}}]
|
[{{image.image.size|filesizeformat}}]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{image.image_md5_hash}}
|
{{image.image_md5_hash}} ({{image.is_dicom}})
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
||||||
|
|||||||
+9
-6
@@ -205,15 +205,18 @@ class SeriesImageBase(models.Model):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""Override save method to add image hash"""
|
"""Override save method to add image hash"""
|
||||||
if self.image:
|
if self.image:
|
||||||
image_hash, is_dicom = get_image_hash(self.image)
|
|
||||||
|
|
||||||
self.is_dicom = is_dicom
|
if not self.image_md5_hash:
|
||||||
self.image_md5_hash = image_hash
|
image_hash, is_dicom = get_image_hash(self.image)
|
||||||
|
|
||||||
|
self.is_dicom = is_dicom
|
||||||
|
self.image_md5_hash = image_hash
|
||||||
|
|
||||||
# Hack for tests
|
# Hack for tests
|
||||||
if image_hash != "12345ABCD":
|
if image_hash != "12345ABCD":
|
||||||
super().save(*args, **kwargs) # Call the "real" save() method.
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
||||||
|
|
||||||
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
||||||
|
|
||||||
class SeriesBase(models.Model):
|
class SeriesBase(models.Model):
|
||||||
info = models.TextField(
|
info = models.TextField(
|
||||||
|
|||||||
+17
-2
@@ -155,9 +155,24 @@ def print_dicom(dataset, indent=0):
|
|||||||
repr_value))
|
repr_value))
|
||||||
return "\n<br/>".join(l)
|
return "\n<br/>".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):
|
def get_image_hash(img) -> (str, bool):
|
||||||
is_dicom = False
|
is_dicom = False
|
||||||
# Try and read the file as a dicom
|
# Try and read the file as a dicom
|
||||||
|
hash = "1234"
|
||||||
try:
|
try:
|
||||||
# and generate a hash from the pixel data
|
# and generate a hash from the pixel data
|
||||||
# TODO: improve?
|
# TODO: improve?
|
||||||
@@ -181,12 +196,12 @@ def get_image_hash(img) -> (str, bool):
|
|||||||
is_dicom = True
|
is_dicom = True
|
||||||
# ----
|
# ----
|
||||||
|
|
||||||
except pydicom.errors.InvalidDicomError:
|
except pydicom.errors.InvalidDicomError as e:
|
||||||
try: # This is horrible (but needed for current unit tests)
|
try: # This is horrible (but needed for current unit tests)
|
||||||
# (we use a temporary file that breaks here)
|
# (we use a temporary file that breaks here)
|
||||||
img.file.open()
|
img.file.open()
|
||||||
hash = hashlib.md5(img.read()).hexdigest()
|
hash = hashlib.md5(img.read()).hexdigest()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return "12345ABCD", False
|
return hash, False
|
||||||
|
|
||||||
return hash, is_dicom
|
return hash, is_dicom
|
||||||
Reference in New Issue
Block a user