import json from rad.settings import REMOTE_URL from django.db.models.fields.files import ImageField from django.db.models.fields.related import ForeignKey from django.db import models from django.shortcuts import get_object_or_404 from django.utils import timezone import tagulous import tagulous.models from django.core.files.storage import FileSystemStorage from django.conf import settings from django.utils.html import format_html from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.utils.html import mark_safe from django.core.exceptions import ValidationError from sortedm2m.fields import SortedManyToManyField import string from collections import defaultdict 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, Site, Condition, Sign from easy_thumbnails.files import get_thumbnailer from easy_thumbnails.exceptions import InvalidImageFormatError import pydicom import pydicom.errors import datetime from django.utils import timezone import reversion from django.contrib.contenttypes.fields import GenericRelation image_storage = FileSystemStorage( # Physical file location ROOT location=u"{0}atlas/".format(settings.MEDIA_ROOT), # Url for file base_url=u"{0}atlas/".format(settings.MEDIA_URL), ) def image_directory_path(instance, filename): return u"atlas/picture/{0}".format(filename) def findMiddle(input_list): middle = float(len(input_list)) / 2 if middle % 2 != 0: return input_list[int(middle - 0.5)] else: return input_list[int(middle)] return (input_list[int(middle)], input_list[int(middle - 1)]) @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') title = models.CharField(max_length=255, help_text="Title of the case", default="") # author = models.ForeignKey('auth.User', on_delete=models.CASCADE) # image = models.ImageField() description = models.TextField( blank=True, help_text="Description of the case, for admin organisation, will not be visible when taking", ) history = models.TextField(null=True, blank=True) findings = models.TextField(null=True, blank=True) 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 "..."', ) sign = tagulous.models.TagField( to=Sign, blank=True, help_text="Radiological signs in the question" ) verified = models.BooleanField(default=False) created_date = models.DateTimeField(default=timezone.now) published_date = models.DateTimeField(blank=True, null=True) author = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, help_text="Author of question", related_name="atlas_authored_questions", ) scrapped = models.BooleanField( default=False, help_text="Question has been scrapped and will not be shown" ) open_access = models.BooleanField( help_text="If a question should be freely available to browse", default=True ) series = SortedManyToManyField("Series", related_name="case") notes = GenericRelation(QuestionNote) #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}) def get_authors(self): """Returns a comma seperated text list of authors""" authors = ", ".join([i.username for i in self.author.all()]) return authors def get_author_objects(self): """Returns a comma seperated text list of authors""" authors = [i for i in self.author.all()] return authors def __str__(self): examinations = [series.get_examination() for series in self.series.all()] return "{}/{} : {}".format(self.pk, self.description, ", ".join(examinations)) def get_images(self, findings=False): qs = self.images.all() if not findings: images = [i.image for i in qs if not i.findings_image] else: images = [i.image for i in qs] return images def get_image_urls(self): return ",".join( ["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()]) class SeriesImage(models.Model): image = models.FileField(upload_to=image_directory_path) position = models.IntegerField(default=0) upload_filename = models.CharField(max_length=255, blank=True) series = models.ForeignKey( "Series", related_name="images", on_delete=models.SET_NULL, null=True ) class Meta: 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) @reversion.register class Series(models.Model): modality = models.ForeignKey( Modality, related_name="atlas_series_modality", on_delete=models.SET_NULL, null=True ) examination = models.ForeignKey( Examination, help_text="Name of the examination", related_name="atlas_series_examination", on_delete=models.SET_NULL, null=True, ) plane = models.ForeignKey( Plane, help_text="Plane of the examination", related_name="atlas_series_plane", on_delete=models.SET_NULL, null=True, blank=True, ) contrast = models.ForeignKey( Contrast, help_text="MRI / CT contrast", related_name="atlas_series_contrast", on_delete=models.SET_NULL, null=True, blank=True, ) description = models.TextField( blank=True, help_text="Description of stack, for admin organisation, will not be visible when taking", ) author = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="series", ) open_access = models.BooleanField( 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 else: case_id = "None" return "{}/{} : {} [{}]".format(self.pk, self.get_examination(), self.description, case_id ) def get_author_objects(self): """Returns a comma seperated text list of authors""" if self.author: return self.author.all() else: return ["None"] def get_author_display(self): return ", ".join([i.username for i in self.get_author_objects()]) def get_examination(self): """Returns a comma seperated text list of regions""" return str(self.examination) def get_examination_full(self): examination = "" plane = "" contrast = "" if self.examination: examination = self.examination if self.plane: plane = " {}".format(self.plane) if self.contrast: contrast = " {}".format(self.contrast) return "{}{}{}".format(examination, plane, contrast) def get_absolute_url(self): return reverse("atlas:series_detail", kwargs={"pk": self.pk}) def get_image_urls(self, findings=False): images = [ "https://www.penracourses.org.uk{}".format(i.image.url) for i in self.images.all() ] return ",".join(images) def get_image_url_array(self, findings=False): images = [ "https://www.penracourses.org.uk{}".format(i.image.url) for i in self.images.all() ] return json.dumps(images) def get_thumbnail(self): images = self.images.all() if len(images) < 1: return "No images", 0 img = findMiddle(images).image try: thumbnailer = get_thumbnailer(img) thumbnail = thumbnailer["exam-list"] except InvalidImageFormatError: return format_html('Invalid image url', img), len(images) return format_html('', thumbnail), len(images) def get_block(self): examination = self.get_examination_full() thumb, image_number = self.get_thumbnail() return format_html( "
{}
{}
Images: {}
", examination, thumb, image_number ) def order_by_upload_filename(self): images = self.images.all() filenames = [] map = {} for i in images: filenames.append(i.upload_filename) map[i.upload_filename] = i filenames = sorted(filenames) n = 1 for f in filenames: i = map[f] i.position = n i.save() n = n + 1 def order_by_dicom(self, field="SliceLocation"): images = self.images.all() files = [] for i in images: files.append((i, pydicom.dcmread(i.image.path))) #print("file count: {}".format(len(files))) # skip files with no SliceLocation (eg scout views) slices = [] map = {} skipcount = 0 for i, f in files: if hasattr(f, field): slices.append(f) #map[f.SliceLocation] = i map[f[field].value] = i else: skipcount = skipcount + 1 print("skipped, no {}: {}".format(field, skipcount)) # ensure they are in the correct order slices = sorted(slices, key=lambda s: s[field].value) #print(slices) n = 1 for f in slices: i = map[f[field].value] i.position = n i.save() n = n + 1 def get_total_image_size(self): images = self.images.all() size = 0 for i in images: size += i.image.size return size