diff --git a/atlas/migrations/0012_seriesimage_image_md5_hash_seriesimage_is_dicom_and_more.py b/atlas/migrations/0012_seriesimage_image_md5_hash_seriesimage_is_dicom_and_more.py
new file mode 100644
index 00000000..dd964264
--- /dev/null
+++ b/atlas/migrations/0012_seriesimage_image_md5_hash_seriesimage_is_dicom_and_more.py
@@ -0,0 +1,37 @@
+# Generated by Django 4.1.4 on 2023-08-14 09:15
+
+import atlas.models
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('atlas', '0011_seriesimage_dicom_tags_ohif'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='seriesimage',
+ name='image_md5_hash',
+ field=models.CharField(blank=True, max_length=32, null=True),
+ ),
+ migrations.AddField(
+ model_name='seriesimage',
+ name='is_dicom',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.CreateModel(
+ name='UncategorisedDicom',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('image', models.FileField(upload_to=atlas.models.uncategorised_dicom_directory_path)),
+ ('image_md5_hash', models.CharField(blank=True, max_length=32, null=True)),
+ ('series', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='imported_dicoms', to='atlas.series')),
+ ('user', models.ForeignKey(help_text='The user that uploaded the file', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/atlas/models.py b/atlas/models.py
index 1074c39a..85a20460 100644
--- a/atlas/models.py
+++ b/atlas/models.py
@@ -77,6 +77,10 @@ def image_directory_path(instance, filename):
return "atlas/picture/{0}".format(filename)
+def uncategorised_dicom_directory_path(instance, filename):
+ return "atlas/dicom/{0}".format(filename)
+
+
def findMiddle(input_list):
middle = float(len(input_list)) / 2
if middle % 2 != 0:
@@ -409,7 +413,7 @@ class SeriesImage(SeriesImageBase):
except InvalidDicomError:
return {}
- #def get_image_dicom_json(self, image_index):
+ # def get_image_dicom_json(self, image_index):
# try:
# with pydicom.dcmread(self.image) as ds:
# return extract_image_dicom_json_from_ds(ds, url=f"{REMOTE_URL}{self.image.url}")
@@ -511,7 +515,6 @@ class Series(SeriesBase):
for series_n, image in enumerate(self.images.all()):
ds = image.get_dicom_data()
if series_n == 0:
-
for tag in to_keep:
if tag in ds:
val = ds[tag].value
@@ -522,9 +525,11 @@ class Series(SeriesBase):
series_json[tag] = val
instances.append(
- extract_image_dicom_json_from_ds(ds, url=f"{REMOTE_URL}{image.image.url}", image_index=series_n)
+ extract_image_dicom_json_from_ds(
+ ds, url=f"{REMOTE_URL}{image.image.url}", image_index=series_n
+ )
)
- #else:
+ # else:
# instances.append(image.get_image_dicom_json(image_index))
description = f"{self.examination} ({self.plane})"
@@ -788,3 +793,54 @@ class SelfReview(models.Model):
id=self.pk,
edit_url=edit_url,
)
+
+
+class UncategorisedDicom(models.Model):
+ #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,
+ help_text="The user that uploaded the file",
+ )
+
+ series = models.ForeignKey(
+ "Series", related_name="imported_dicoms", on_delete=models.SET_NULL, null=True
+ )
+
+ image_md5_hash = models.CharField(max_length=32, null=True, blank=True)
+
+ def get_dicom_info(self):
+ try:
+ ds = pydicom.read_file(self.image)
+ return ds
+ except pydicom.errors.InvalidDicomError:
+ return None
+
+ def get_basic_dicom_tags(self):
+ try:
+ ds = pydicom.read_file(self.image)
+
+ to_include = (
+ "StudyDescription",
+ "Modality",
+ "ProtocolName",
+ "SeriesDescription",
+ "SeriesInstanceUID",
+ "BodyPartExamined",
+ "SliceThickness",
+ )
+
+ tags = {}
+
+ for tag in to_include:
+ if tag in ds:
+ tags[tag] = ds[tag]
+ else:
+ tags[tag] = ""
+
+ return ds
+ except pydicom.errors.InvalidDicomError:
+ return None
diff --git a/atlas/urls.py b/atlas/urls.py
index 31d5685a..b3087a7c 100755
--- a/atlas/urls.py
+++ b/atlas/urls.py
@@ -141,6 +141,7 @@ urlpatterns = [
views.case_dicom_json,
name="case_dicom_json",
),
+ path("uncategorised_dicoms/", views.uncategorised_dicoms, name="uncategorised_dicoms_view"),
path("condition/", views.ConditionView.as_view(), name="condition_view"),
path("categories/", views.categories_list, name="categories_list"),
diff --git a/atlas/views.py b/atlas/views.py
index ca1ab112..5a17b6ea 100755
--- a/atlas/views.py
+++ b/atlas/views.py
@@ -63,6 +63,7 @@ from .models import (
Subspecialty,
SeriesFinding,
SeriesImage,
+ UncategorisedDicom,
UserReportAnswer,
)
from .tables import (
@@ -1877,4 +1878,16 @@ class AddSelfReview(CreateView):
#
# response = super().form_valid(form)
#
-# return response
\ No newline at end of file
+# return response
+
+
+@login_required
+def uncategorised_dicoms(request, pk):
+ dicoms = UncategorisedDicom.objects.filter(user=request.User)
+
+ data = {}
+ for d in dicoms:
+
+ d[d.image] = d.get_basic_dicom_tags()
+
+ return JsonResponse(data)
\ No newline at end of file
diff --git a/generic/models.py b/generic/models.py
index 2778394c..68143ce3 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -26,7 +26,7 @@ from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
-from helpers.images import pretty_print_dicom
+from helpers.images import get_image_hash, pretty_print_dicom
from rad.settings import REMOTE_URL
from django.utils.html import format_html
@@ -187,6 +187,9 @@ class SeriesImageBase(models.Model):
)
dicom_tags_ohif = models.JSONField(help_text="Holds the dicom tags required for the OHIF viewer", blank=True, null=True)
+ image_md5_hash = models.CharField(max_length=32, null=True, blank=True)
+ is_dicom = models.BooleanField(default=False)
+
class Meta:
ordering = ["position"]
abstract = True
@@ -198,6 +201,18 @@ class SeriesImageBase(models.Model):
info = "File is not a dicom."
return info
+ 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
+
+ # Hack for tests
+ if image_hash != "12345ABCD":
+ 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 89af1a00..b600a23c 100644
--- a/helpers/images.py
+++ b/helpers/images.py
@@ -1,4 +1,5 @@
import base64
+import hashlib
import mimetypes
from io import BytesIO
@@ -152,4 +153,40 @@ def print_dicom(dataset, indent=0):
l.append("{0:s} {1:s} = {2:s}".format(indent_string,
data_element.name,
repr_value))
- return "\n
".join(l)
\ No newline at end of file
+ return "\n
".join(l)
+
+def get_image_hash(img) -> (str, bool):
+ is_dicom = False
+ # Try and read the file as a dicom
+ try:
+ # and generate a hash from the pixel data
+ # TODO: improve?
+ dataset = pydicom.dcmread(img)
+ # flatten = dataset.pixel_array.astype(str).flatten()
+ # print("flatteded")
+ # pre_join = ",".join(flatten)
+ # print(pre_join)
+ # hash = hashlib.md5(pre_join.encode()).hexdigest()
+ # ----
+ 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()
+ is_dicom = True
+ # ----
+
+ except pydicom.errors.InvalidDicomError:
+ 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, is_dicom
\ No newline at end of file
diff --git a/longs/migrations/0007_longseriesimage_image_md5_hash_and_more.py b/longs/migrations/0007_longseriesimage_image_md5_hash_and_more.py
new file mode 100644
index 00000000..08ef4a25
--- /dev/null
+++ b/longs/migrations/0007_longseriesimage_image_md5_hash_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.4 on 2023-08-14 09:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('longs', '0006_longseriesimage_dicom_tags_ohif'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='longseriesimage',
+ name='image_md5_hash',
+ field=models.CharField(blank=True, max_length=32, null=True),
+ ),
+ migrations.AddField(
+ model_name='longseriesimage',
+ name='is_dicom',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/rapids/models.py b/rapids/models.py
index bd4dae2e..eca724a0 100644
--- a/rapids/models.py
+++ b/rapids/models.py
@@ -21,7 +21,7 @@ from sortedm2m.fields import SortedManyToManyField
import string
from collections import defaultdict
-from helpers.images import image_as_base64
+from helpers.images import get_image_hash, image_as_base64
from django.contrib.contenttypes.fields import GenericRelation
@@ -575,42 +575,14 @@ class RapidImage(models.Model):
def save(self, *args, **kwargs):
"""Override save method to add image hash"""
- # TODO: consider moving to signal to reuse across apps
if self.image:
- # Try and read the file as a dicom
- try:
- # and generate a hash from the pixel data
- # TODO: improve?
- dataset = pydicom.dcmread(self.image)
- # flatten = dataset.pixel_array.astype(str).flatten()
- # print("flatteded")
- # pre_join = ",".join(flatten)
- # print(pre_join)
- # hash = hashlib.md5(pre_join.encode()).hexdigest()
- # ----
- 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())
+ image_hash, is_dicom = get_image_hash(self.image)
+ self.is_dicom = is_dicom
+ self.image_md5_hash = image_hash
- hash = md5.hexdigest()
- # ----
-
- self.is_dicom = True
- except pydicom.errors.InvalidDicomError:
- try: # This is horrible (but needed for current unit tests)
- # (we use a temporary file that breaks here)
- self.image.file.open()
- hash = hashlib.md5(self.image.read()).hexdigest()
- except AttributeError:
- return
-
- self.image_md5_hash = hash
- 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.
class RapidCreationDefault(models.Model):