.
This commit is contained in:
+7
-5
@@ -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
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user