This commit is contained in:
Ross
2021-11-25 22:16:42 +00:00
parent 2ed684baed
commit 141265d37a
3 changed files with 87 additions and 40 deletions
+7 -5
View File
@@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import Case, Series, SeriesImage from .models import Case, Series, SeriesImage, Condition
import tagulous.admin import tagulous.admin
@@ -11,13 +11,13 @@ from django.forms.widgets import RadioSelect
from tinymce.widgets import TinyMCE from tinymce.widgets import TinyMCE
# from generic.models import Examination, Sign, Site, Condition # from generic.models import Examination, Sign, Site, Condition
from generic.models import Examination, Sign, Condition from generic.models import Examination, Sign
# Register your models here. # Register your models here.
# admin.site.register(Examination) # admin.site.register(Examination)
# admin.site.register(Site) # admin.site.register(Site)
admin.site.register(SeriesImage) admin.site.register(SeriesImage)
admin.site.register(Condition)
class AtlasAdminForm(ModelForm): class AtlasAdminForm(ModelForm):
@@ -37,10 +37,11 @@ class AtlasAdminForm(ModelForm):
# "normal": RadioSelect( # "normal": RadioSelect(
# choices=[(True, 'Yes'), # choices=[(True, 'Yes'),
# (False, 'No')]) # (False, 'No')])
"findings" : TinyMCE(attrs={'cols': 80, 'rows': 30}), "findings": TinyMCE(attrs={"cols": 80, "rows": 30}),
"mark_scheme" : TinyMCE(attrs={'cols': 80, 'rows': 30}), "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
} }
class AtlasAdmin(VersionAdmin): class AtlasAdmin(VersionAdmin):
form = AtlasAdminForm form = AtlasAdminForm
@@ -55,4 +56,5 @@ admin.site.register(Case, AtlasAdmin)
class SeriesAdmin(VersionAdmin): class SeriesAdmin(VersionAdmin):
pass pass
admin.site.register(Series, SeriesAdmin) admin.site.register(Series, SeriesAdmin)
@@ -0,0 +1,26 @@
# Generated by Django 3.2.8 on 2021-11-25 22:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0003_rename_subpecialty_case_subspecialty'),
]
operations = [
migrations.CreateModel(
name='Condition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('synonym', models.ManyToManyField(blank=True, related_name='_atlas_condition_synonym_+', to='atlas.Condition')),
],
),
migrations.AlterField(
model_name='case',
name='condition',
field=models.ManyToManyField(blank=True, to='atlas.Condition'),
),
]
+53 -34
View File
@@ -28,7 +28,15 @@ from helpers.images import image_as_base64, pretty_print_dicom
from anatomy.models import Modality from anatomy.models import Modality
from generic.models import Examination, Condition, Sign, ExamBase, Plane, Contrast, QuestionNote from generic.models import (
Examination,
Condition,
Sign,
ExamBase,
Plane,
Contrast,
QuestionNote,
)
# from generic.models import Examination, Site, Condition, Sign # from generic.models import Examination, Site, Condition, Sign
@@ -66,20 +74,26 @@ def findMiddle(input_list):
return (input_list[int(middle)], input_list[int(middle - 1)]) return (input_list[int(middle)], input_list[int(middle - 1)])
class Condition(models.Model):
name = models.CharField(max_length=255)
synonym = models.ManyToManyField("self", blank=True)
@reversion.register @reversion.register
class Case(models.Model): class Case(models.Model):
class SubspecialtyChoices(models.TextChoices): class SubspecialtyChoices(models.TextChoices):
BREAST = 'BR', _('Breast') BREAST = "BR", _("Breast")
CARDIAC = 'CA', _('Cardiac') CARDIAC = "CA", _("Cardiac")
GASTRO = 'GI', _('Gastrointestinal and hepatobiliary') GASTRO = "GI", _("Gastrointestinal and hepatobiliary")
HEADNECK = 'HN', _('Head and Neck') HEADNECK = "HN", _("Head and Neck")
MSK = 'MS', _('Musculoskeletal') MSK = "MS", _("Musculoskeletal")
NEURO = 'NE', _('Neuroradiology') NEURO = "NE", _("Neuroradiology")
OBSGYN = 'OG', _('Obstectric and Gynaecological') OBSGYN = "OG", _("Obstectric and Gynaecological")
PAED = 'PA', _('Paediatric') PAED = "PA", _("Paediatric")
URO = 'UR', _('Uroradiology') URO = "UR", _("Uroradiology")
VASC = 'VA', _('Vascular') VASC = "VA", _("Vascular")
HAEMONC = 'HA', _('Haemotology and Oncology') HAEMONC = "HA", _("Haemotology and Oncology")
title = models.CharField(max_length=255, help_text="Title of the case", default="") title = models.CharField(max_length=255, help_text="Title of the case", default="")
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE) # author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
@@ -95,11 +109,7 @@ class Case(models.Model):
subspecialty = models.CharField(max_length=2, choices=SubspecialtyChoices.choices) subspecialty = models.CharField(max_length=2, choices=SubspecialtyChoices.choices)
condition = tagulous.models.TagField( condition = models.ManyToManyField(Condition, blank=True)
to=Condition,
blank=True,
help_text='Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes "..."',
)
sign = tagulous.models.TagField( sign = tagulous.models.TagField(
to=Sign, blank=True, help_text="Radiological signs in the question" to=Sign, blank=True, help_text="Radiological signs in the question"
@@ -127,7 +137,7 @@ class Case(models.Model):
notes = GenericRelation(QuestionNote) notes = GenericRelation(QuestionNote)
#question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True) # question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("atlas:case_detail", kwargs={"pk": self.pk}) return reverse("atlas:case_detail", kwargs={"pk": self.pk})
@@ -158,11 +168,19 @@ class Case(models.Model):
def get_image_urls(self): def get_image_urls(self):
return ",".join( return ",".join(
["https://www.penracourses.org.uk{}".format(i.url) for i in self.get_images()] [
"https://www.penracourses.org.uk{}".format(i.url)
for i in self.get_images()
]
) )
def get_image_url_array(self): def get_image_url_array(self):
return json.dumps(["https://www.penracourses.org.uk{}".format(i.url) for i in self.get_images()]) return json.dumps(
[
"https://www.penracourses.org.uk{}".format(i.url)
for i in self.get_images()
]
)
class SeriesImage(models.Model): class SeriesImage(models.Model):
@@ -174,20 +192,23 @@ class SeriesImage(models.Model):
) )
class Meta: class Meta:
ordering = ['position'] ordering = ["position"]
def get_dicom_info(self): def get_dicom_info(self):
try: try:
info = pretty_print_dicom(pydicom.read_file(self.image)) info = pretty_print_dicom(pydicom.read_file(self.image))
except pydicom.errors.InvalidDicomError: except pydicom.errors.InvalidDicomError:
info = "File is not a dicom." info = "File is not a dicom."
return(info) return info
@reversion.register @reversion.register
class Series(models.Model): class Series(models.Model):
modality = models.ForeignKey( modality = models.ForeignKey(
Modality, related_name="atlas_series_modality", on_delete=models.SET_NULL, null=True Modality,
related_name="atlas_series_modality",
on_delete=models.SET_NULL,
null=True,
) )
examination = models.ForeignKey( examination = models.ForeignKey(
Examination, Examination,
@@ -227,15 +248,14 @@ class Series(models.Model):
help_text="If a question should be freely available to browse", default=True help_text="If a question should be freely available to browse", default=True
) )
def __str__(self): def __str__(self):
if self.case: if self.case:
case_id = ", ".format([case.pk for case in self.case.all()]) case_id = ", ".format([case.pk for case in self.case.all()])
#case_id = self.case.pk # case_id = self.case.pk
else: else:
case_id = "None" case_id = "None"
return "{}/{} : {} [{}]".format(self.pk, return "{}/{} : {} [{}]".format(
self.get_examination(), self.description, case_id self.pk, self.get_examination(), self.description, case_id
) )
def get_author_objects(self): def get_author_objects(self):
@@ -295,7 +315,9 @@ class Series(models.Model):
thumbnailer = get_thumbnailer(img) thumbnailer = get_thumbnailer(img)
thumbnail = thumbnailer["exam-list"] thumbnail = thumbnailer["exam-list"]
except InvalidImageFormatError: except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', img), len(images) return format_html('<span title="{}">Invalid image url<span>', img), len(
images
)
return format_html('<img src="/media/{}" />', thumbnail), len(images) return format_html('<img src="/media/{}" />', thumbnail), len(images)
def get_block(self): def get_block(self):
@@ -324,8 +346,6 @@ class Series(models.Model):
i.save() i.save()
n = n + 1 n = n + 1
def order_by_dicom(self, field="SliceLocation"): def order_by_dicom(self, field="SliceLocation"):
images = self.images.all() images = self.images.all()
@@ -334,7 +354,7 @@ class Series(models.Model):
for i in images: for i in images:
files.append((i, pydicom.dcmread(i.image.path))) files.append((i, pydicom.dcmread(i.image.path)))
#print("file count: {}".format(len(files))) # print("file count: {}".format(len(files)))
# skip files with no SliceLocation (eg scout views) # skip files with no SliceLocation (eg scout views)
slices = [] slices = []
@@ -343,7 +363,7 @@ class Series(models.Model):
for i, f in files: for i, f in files:
if hasattr(f, field): if hasattr(f, field):
slices.append(f) slices.append(f)
#map[f.SliceLocation] = i # map[f.SliceLocation] = i
map[f[field].value] = i map[f[field].value] = i
else: else:
skipcount = skipcount + 1 skipcount = skipcount + 1
@@ -353,8 +373,7 @@ class Series(models.Model):
# ensure they are in the correct order # ensure they are in the correct order
slices = sorted(slices, key=lambda s: s[field].value) slices = sorted(slices, key=lambda s: s[field].value)
# print(slices)
#print(slices)
n = 1 n = 1
for f in slices: for f in slices:
i = map[f[field].value] i = map[f[field].value]