start adding uncat dicom
This commit is contained in:
@@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
+60
-4
@@ -77,6 +77,10 @@ def image_directory_path(instance, filename):
|
|||||||
return "atlas/picture/{0}".format(filename)
|
return "atlas/picture/{0}".format(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def uncategorised_dicom_directory_path(instance, filename):
|
||||||
|
return "atlas/dicom/{0}".format(filename)
|
||||||
|
|
||||||
|
|
||||||
def findMiddle(input_list):
|
def findMiddle(input_list):
|
||||||
middle = float(len(input_list)) / 2
|
middle = float(len(input_list)) / 2
|
||||||
if middle % 2 != 0:
|
if middle % 2 != 0:
|
||||||
@@ -409,7 +413,7 @@ class SeriesImage(SeriesImageBase):
|
|||||||
except InvalidDicomError:
|
except InvalidDicomError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
#def get_image_dicom_json(self, image_index):
|
# def get_image_dicom_json(self, image_index):
|
||||||
# try:
|
# try:
|
||||||
# with pydicom.dcmread(self.image) as ds:
|
# with pydicom.dcmread(self.image) as ds:
|
||||||
# return extract_image_dicom_json_from_ds(ds, url=f"{REMOTE_URL}{self.image.url}")
|
# 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()):
|
for series_n, image in enumerate(self.images.all()):
|
||||||
ds = image.get_dicom_data()
|
ds = image.get_dicom_data()
|
||||||
if series_n == 0:
|
if series_n == 0:
|
||||||
|
|
||||||
for tag in to_keep:
|
for tag in to_keep:
|
||||||
if tag in ds:
|
if tag in ds:
|
||||||
val = ds[tag].value
|
val = ds[tag].value
|
||||||
@@ -522,9 +525,11 @@ class Series(SeriesBase):
|
|||||||
series_json[tag] = val
|
series_json[tag] = val
|
||||||
|
|
||||||
instances.append(
|
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))
|
# instances.append(image.get_image_dicom_json(image_index))
|
||||||
|
|
||||||
description = f"{self.examination} ({self.plane})"
|
description = f"{self.examination} ({self.plane})"
|
||||||
@@ -788,3 +793,54 @@ class SelfReview(models.Model):
|
|||||||
id=self.pk,
|
id=self.pk,
|
||||||
edit_url=edit_url,
|
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
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ urlpatterns = [
|
|||||||
views.case_dicom_json,
|
views.case_dicom_json,
|
||||||
name="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("condition/", views.ConditionView.as_view(), name="condition_view"),
|
||||||
path("categories/", views.categories_list, name="categories_list"),
|
path("categories/", views.categories_list, name="categories_list"),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ from .models import (
|
|||||||
Subspecialty,
|
Subspecialty,
|
||||||
SeriesFinding,
|
SeriesFinding,
|
||||||
SeriesImage,
|
SeriesImage,
|
||||||
|
UncategorisedDicom,
|
||||||
UserReportAnswer,
|
UserReportAnswer,
|
||||||
)
|
)
|
||||||
from .tables import (
|
from .tables import (
|
||||||
@@ -1878,3 +1879,15 @@ class AddSelfReview(CreateView):
|
|||||||
# response = super().form_valid(form)
|
# response = super().form_valid(form)
|
||||||
#
|
#
|
||||||
# return response
|
# 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)
|
||||||
+16
-1
@@ -26,7 +26,7 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
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 rad.settings import REMOTE_URL
|
||||||
from django.utils.html import format_html
|
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)
|
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:
|
class Meta:
|
||||||
ordering = ["position"]
|
ordering = ["position"]
|
||||||
abstract = True
|
abstract = True
|
||||||
@@ -198,6 +201,18 @@ class SeriesImageBase(models.Model):
|
|||||||
info = "File is not a dicom."
|
info = "File is not a dicom."
|
||||||
return info
|
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):
|
class SeriesBase(models.Model):
|
||||||
info = models.TextField(
|
info = models.TextField(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import hashlib
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -153,3 +154,39 @@ def print_dicom(dataset, indent=0):
|
|||||||
data_element.name,
|
data_element.name,
|
||||||
repr_value))
|
repr_value))
|
||||||
return "\n<br/>".join(l)
|
return "\n<br/>".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
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
+7
-35
@@ -21,7 +21,7 @@ from sortedm2m.fields import SortedManyToManyField
|
|||||||
|
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
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
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
|
||||||
@@ -575,42 +575,14 @@ class RapidImage(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"""
|
||||||
# TODO: consider moving to signal to reuse across apps
|
|
||||||
if self.image:
|
if self.image:
|
||||||
# Try and read the file as a dicom
|
image_hash, is_dicom = get_image_hash(self.image)
|
||||||
try:
|
self.is_dicom = is_dicom
|
||||||
# and generate a hash from the pixel data
|
self.image_md5_hash = image_hash
|
||||||
# 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())
|
|
||||||
|
|
||||||
hash = md5.hexdigest()
|
# Hack for tests
|
||||||
# ----
|
if image_hash != "12345ABCD":
|
||||||
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
class RapidCreationDefault(models.Model):
|
class RapidCreationDefault(models.Model):
|
||||||
|
|||||||
Reference in New Issue
Block a user