.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2023-06-12 09:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0049_alter_condition_parent_alter_condition_synonym_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='series',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, help_text='Description of stack, for admin organisation, will not be visible when taking'),
|
||||
),
|
||||
]
|
||||
+10
-181
@@ -10,8 +10,6 @@ from django.utils import timezone
|
||||
|
||||
|
||||
import pydicom
|
||||
import dicognito.anonymizer
|
||||
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.conf import settings
|
||||
@@ -43,13 +41,11 @@ from generic.models import (
|
||||
Plane,
|
||||
Contrast,
|
||||
QuestionNote,
|
||||
SeriesBase,
|
||||
)
|
||||
|
||||
# from generic.models import Examination, Site, Condition, Sign
|
||||
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
from easy_thumbnails.exceptions import InvalidImageFormatError
|
||||
|
||||
import pydicom.errors
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
@@ -343,7 +339,6 @@ class SeriesImage(models.Model):
|
||||
|
||||
|
||||
class SeriesFinding(models.Model):
|
||||
|
||||
series = models.ForeignKey(
|
||||
"Series", related_name="findings", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
@@ -364,7 +359,7 @@ class SeriesFinding(models.Model):
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Series(models.Model):
|
||||
class Series(SeriesBase):
|
||||
modality = models.ForeignKey(
|
||||
Modality,
|
||||
related_name="atlas_series_modality",
|
||||
@@ -394,10 +389,6 @@ class Series(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Description of stack",
|
||||
)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
@@ -407,13 +398,6 @@ class Series(models.Model):
|
||||
|
||||
# findings = models.TextField(null=True, blank=True, help_text="Findings on the series / stack")
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a series should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pk}:{self.description}"
|
||||
|
||||
def get_full_str(self):
|
||||
if self.case:
|
||||
case_id = ", ".format([case.pk for case in self.case.all()])
|
||||
@@ -424,167 +408,10 @@ class Series(models.Model):
|
||||
self.pk, self.get_examination_full(), self.description, case_id
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("series:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
# def get_string(self):
|
||||
# examination = self.get_examination_full()
|
||||
# return
|
||||
|
||||
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 = [f"{REMOTE_URL}{i.image.url}" for i in self.images.all()]
|
||||
|
||||
return ",".join(images)
|
||||
|
||||
def get_image_url_array_not_json(self):
|
||||
return self.get_image_url_array(json_output=False)
|
||||
|
||||
def get_image_url_array(self, findings=False, json_output=True):
|
||||
images = [f"{REMOTE_URL}{i.image.url}" for i in self.images.all()]
|
||||
|
||||
if json_output:
|
||||
return json.dumps(images)
|
||||
else:
|
||||
# This is a mess...
|
||||
return format_html('", "'.join(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('<span title="{}">Invalid image url<span>', img), len(
|
||||
images
|
||||
)
|
||||
return format_html('<img src="/media/{}" />', thumbnail), len(images)
|
||||
|
||||
def get_thumbnail_link(self):
|
||||
return format_html(
|
||||
"<a href='{}'>{}<a/>", self.get_absolute_url(), self.get_thumbnail()[0]
|
||||
)
|
||||
|
||||
def get_block(self):
|
||||
examination = self.get_examination_full()
|
||||
thumb, image_number = self.get_thumbnail()
|
||||
return format_html(
|
||||
"<div>{}<br/>{}<br/>Images: {}</div>", 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
|
||||
|
||||
def anonymise_images(self):
|
||||
# NOTE: this will not maintain the correct hashed filename
|
||||
# but that doesn't matter as we will never get the same anonymisation
|
||||
# even with the same dicom...
|
||||
anonymizer = dicognito.anonymizer.Anonymizer()
|
||||
|
||||
for series_image in self.images.all():
|
||||
file_path = os.path.join(settings.MEDIA_ROOT, series_image.image.name)
|
||||
|
||||
try:
|
||||
with pydicom.dcmread(file_path) as dataset:
|
||||
anonymizer.anonymize(dataset)
|
||||
dataset.save_as(file_path)
|
||||
except pydicom.errors.InvalidDicomError:
|
||||
pass
|
||||
|
||||
|
||||
class CaseCollection(models.Model):
|
||||
@@ -647,8 +474,11 @@ class CaseCollection(models.Model):
|
||||
CidUser, blank=True, related_name="casecollection_exams"
|
||||
)
|
||||
|
||||
cid_user_groups = models.ManyToManyField(CidUserGroup, blank=True, help_text="These groups define which candidates are able to be added to the exams/collection.")
|
||||
|
||||
cid_user_groups = models.ManyToManyField(
|
||||
CidUserGroup,
|
||||
blank=True,
|
||||
help_text="These groups define which candidates are able to be added to the exams/collection.",
|
||||
)
|
||||
|
||||
archive = models.BooleanField(default=False)
|
||||
exam_mode = models.BooleanField(default=False)
|
||||
@@ -693,7 +523,6 @@ class CaseCollection(models.Model):
|
||||
return True
|
||||
|
||||
if self.valid_cid_users.exists():
|
||||
|
||||
user = self.valid_cid_users.filter(cid=cid).first()
|
||||
|
||||
if not user or user.passcode != passcode:
|
||||
@@ -771,13 +600,13 @@ class BaseReportAnswer(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class CidReportAnswer(BaseReportAnswer):
|
||||
cid = models.BigIntegerField(
|
||||
blank=False,
|
||||
help_text="Candidate ID",
|
||||
)
|
||||
|
||||
|
||||
class UserReportAnswer(BaseReportAnswer):
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
|
||||
+3
-1
@@ -25,7 +25,9 @@ class AtlasImageColumn(tables.Column):
|
||||
for obj in value.all():
|
||||
blocks.append(obj.get_block())
|
||||
return format_html(
|
||||
"<span class='multi-image-block'>{}</span>".format("".join(blocks))
|
||||
"""<span class='multi-image-block'>
|
||||
{}
|
||||
</span>""".format("".join(blocks))
|
||||
)
|
||||
|
||||
obj = value.first()
|
||||
|
||||
Reference in New Issue
Block a user