diff --git a/atlas/admin.py b/atlas/admin.py index 9a99f7a0..cf38b305 100755 --- a/atlas/admin.py +++ b/atlas/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Case, Series, SeriesImage +from .models import Case, Series, SeriesImage, Condition import tagulous.admin @@ -11,13 +11,13 @@ from django.forms.widgets import RadioSelect from tinymce.widgets import TinyMCE # 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. # admin.site.register(Examination) # admin.site.register(Site) admin.site.register(SeriesImage) - +admin.site.register(Condition) class AtlasAdminForm(ModelForm): @@ -37,10 +37,11 @@ class AtlasAdminForm(ModelForm): # "normal": RadioSelect( # choices=[(True, 'Yes'), # (False, 'No')]) - "findings" : TinyMCE(attrs={'cols': 80, 'rows': 30}), - "mark_scheme" : TinyMCE(attrs={'cols': 80, 'rows': 30}), + "findings": TinyMCE(attrs={"cols": 80, "rows": 30}), + "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}), } + class AtlasAdmin(VersionAdmin): form = AtlasAdminForm @@ -55,4 +56,5 @@ admin.site.register(Case, AtlasAdmin) class SeriesAdmin(VersionAdmin): pass -admin.site.register(Series, SeriesAdmin) \ No newline at end of file + +admin.site.register(Series, SeriesAdmin) diff --git a/atlas/migrations/0004_auto_20211125_2216.py b/atlas/migrations/0004_auto_20211125_2216.py new file mode 100644 index 00000000..4f2b44ad --- /dev/null +++ b/atlas/migrations/0004_auto_20211125_2216.py @@ -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'), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 55235cab..0b1e2d76 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -28,7 +28,15 @@ from helpers.images import image_as_base64, pretty_print_dicom 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 @@ -66,20 +74,26 @@ def findMiddle(input_list): 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 class Case(models.Model): class SubspecialtyChoices(models.TextChoices): - BREAST = 'BR', _('Breast') - CARDIAC = 'CA', _('Cardiac') - GASTRO = 'GI', _('Gastrointestinal and hepatobiliary') - HEADNECK = 'HN', _('Head and Neck') - MSK = 'MS', _('Musculoskeletal') - NEURO = 'NE', _('Neuroradiology') - OBSGYN = 'OG', _('Obstectric and Gynaecological') - PAED = 'PA', _('Paediatric') - URO = 'UR', _('Uroradiology') - VASC = 'VA', _('Vascular') - HAEMONC = 'HA', _('Haemotology and Oncology') + BREAST = "BR", _("Breast") + CARDIAC = "CA", _("Cardiac") + GASTRO = "GI", _("Gastrointestinal and hepatobiliary") + HEADNECK = "HN", _("Head and Neck") + MSK = "MS", _("Musculoskeletal") + NEURO = "NE", _("Neuroradiology") + OBSGYN = "OG", _("Obstectric and Gynaecological") + PAED = "PA", _("Paediatric") + URO = "UR", _("Uroradiology") + VASC = "VA", _("Vascular") + HAEMONC = "HA", _("Haemotology and Oncology") title = models.CharField(max_length=255, help_text="Title of the case", default="") # 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) - condition = tagulous.models.TagField( - 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 "..."', - ) + condition = models.ManyToManyField(Condition, blank=True) sign = tagulous.models.TagField( to=Sign, blank=True, help_text="Radiological signs in the question" @@ -127,7 +137,7 @@ class Case(models.Model): 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): return reverse("atlas:case_detail", kwargs={"pk": self.pk}) @@ -158,11 +168,19 @@ class Case(models.Model): def get_image_urls(self): 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): - 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): @@ -174,20 +192,23 @@ class SeriesImage(models.Model): ) class Meta: - ordering = ['position'] + ordering = ["position"] def get_dicom_info(self): try: info = pretty_print_dicom(pydicom.read_file(self.image)) except pydicom.errors.InvalidDicomError: info = "File is not a dicom." - return(info) + return info @reversion.register class Series(models.Model): 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, @@ -227,15 +248,14 @@ class Series(models.Model): help_text="If a question should be freely available to browse", default=True ) - def __str__(self): if self.case: case_id = ", ".format([case.pk for case in self.case.all()]) - #case_id = self.case.pk + # case_id = self.case.pk else: case_id = "None" - return "{}/{} : {} [{}]".format(self.pk, - self.get_examination(), self.description, case_id + return "{}/{} : {} [{}]".format( + self.pk, self.get_examination(), self.description, case_id ) def get_author_objects(self): @@ -295,7 +315,9 @@ class Series(models.Model): thumbnailer = get_thumbnailer(img) thumbnail = thumbnailer["exam-list"] except InvalidImageFormatError: - return format_html('Invalid image url', img), len(images) + return format_html('Invalid image url', img), len( + images + ) return format_html('', thumbnail), len(images) def get_block(self): @@ -323,8 +345,6 @@ class Series(models.Model): i.position = n i.save() n = n + 1 - - def order_by_dicom(self, field="SliceLocation"): images = self.images.all() @@ -334,7 +354,7 @@ class Series(models.Model): for i in images: 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) slices = [] @@ -343,7 +363,7 @@ class Series(models.Model): for i, f in files: if hasattr(f, field): slices.append(f) - #map[f.SliceLocation] = i + # map[f.SliceLocation] = i map[f[field].value] = i else: skipcount = skipcount + 1 @@ -353,8 +373,7 @@ class Series(models.Model): # ensure they are in the correct order slices = sorted(slices, key=lambda s: s[field].value) - - #print(slices) + # print(slices) n = 1 for f in slices: i = map[f[field].value]