pre series form update

This commit is contained in:
Ross
2022-07-24 11:11:58 +01:00
parent fde658c71c
commit 8f5c878bab
11 changed files with 286 additions and 91 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ class AnatomyQuestion(models.Model):
image = models.ImageField( image = models.ImageField(
upload_to=image_directory_path, upload_to=image_directory_path,
help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.As a preference", help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.",
) )
image_annotations = models.TextField( image_annotations = models.TextField(
+6
View File
@@ -280,6 +280,12 @@ button a {
border: 1px dashed white; border: 1px dashed white;
} }
.series-drop {
position: sticky;
height: 600px;
bottom: -500px;
}
.submit-button { .submit-button {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
-1
View File
@@ -1 +0,0 @@
/home/ross/dv/dicomViewer.css
+148
View File
@@ -0,0 +1,148 @@
.canvas-panel {
height: 100%;
position: relative;
color: white;
}
#dicom-overlay {
position: absolute;
bottom: 0;
}
#image-thumbs {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
overflow-y: auto;
max-height: 100%;
max-width: 30%;
user-select: none;
}
.thumbnail {
pointer-events: none;
display: block;
margin: auto;
}
.thumb {
position: relative;
border: 1px dotted blue;
touch-action: none;
user-select: none;
}
.thumb-active {
border: 5px dotted blue;
}
.thumb span {
position: absolute;
right: 0;
color: lightblue;
}
.cornerstone-element {
position: relative;
height: 100%;
}
.dicom-button {
font-size: 40px;
z-index: 99;
opacity: 20%;
color: gray;
top: 0;
}
.dicom-button:hover {
opacity: 100%;
}
.dicom-button-highlight {
opacity: 50%;
color: lightblue;
}
#dicom-toggle-mode-button {
position: absolute;
left: 0;
}
#dicom-settings-button {
position: absolute;
right: 0;
}
#dicom-fullscreen-button {
position: absolute;
transform: translate(50%, -20%);
right: 50%;
}
#dicom-collapse-button {
position: absolute;
transform: translate(0, -50%);
top: 50%;
right: 0;
}
#dicom-info-button {
position: absolute;
transform: translate(0, -75%);
right: 0;
top: 100%;
}
#dicom-settings-panel {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding-left: 30px;
padding-right: 30px;
padding-bottom: 20px;
background-color: rgba(0, 0, 0, 0.8); /* Black w/ opacity */
max-width: 100%;
max-height: 100%;
z-index: 999;
}
#dicom-log-panel {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding-left: 30px;
padding-right: 30px;
padding-bottom: 20px;
background-color: rgba(0, 0, 0, 0.8); /* Black w/ opacity */
width: 90%;
height: 90%;
z-index: 999;
}
#dicom-window-panel {
position: absolute;
top: 100%;
transform: translate(0, -100%);
opacity: 0.0;
}
#dicom-window-panel:hover {
opacity: 1;
}
#dicom-summary-panel {
position: relative;
z-index: 999;
max-width: 100%;
height: 100%;
color: red;
top: -100%;
overflow: scroll;
display: none;
}
+29
View File
@@ -16,6 +16,7 @@ from django.shortcuts import get_object_or_404
from atlas.models import ( from atlas.models import (
Case, Case,
CaseCollection, CaseCollection,
CaseDetail,
CidReportAnswer, CidReportAnswer,
Differential, Differential,
Finding, Finding,
@@ -39,6 +40,7 @@ from tinymce.widgets import TinyMCE
from dal import autocomplete from dal import autocomplete
import logging
class ConditionForm(ModelForm): class ConditionForm(ModelForm):
class Meta: class Meta:
@@ -240,9 +242,20 @@ class CaseForm(ModelForm):
js = ["jsi18n.js", "tesseract.min.js"] js = ["jsi18n.js", "tesseract.min.js"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
logging.info("LOG")
logging.debug(kwargs)
self.user = kwargs.pop( self.user = kwargs.pop(
"user" "user"
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
# This is populated if we create the case from an exam/collection
self.collection = None
if "exams" in kwargs["initial"]:
collections = kwargs["initial"].pop("exams")
logging.debug(collections)
self.collection = get_object_or_404(CaseCollection, pk=collections[0])
if kwargs.get("instance"): if kwargs.get("instance"):
# We get the 'initial' keyword argument or initialize it # We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist. # as a dict if it didn't exist.
@@ -251,6 +264,7 @@ class CaseForm(ModelForm):
# a list of primary key for the selected data. # a list of primary key for the selected data.
# initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()] # initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
ModelForm.__init__(self, *args, **kwargs) ModelForm.__init__(self, *args, **kwargs)
super(CaseForm, self).__init__(*args, **kwargs) super(CaseForm, self).__init__(*args, **kwargs)
@@ -307,6 +321,9 @@ class CaseForm(ModelForm):
# Get the unsaved Case instance # Get the unsaved Case instance
instance = ModelForm.save(self, False) instance = ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form, # Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m old_save_m2m = self.save_m2m
@@ -324,6 +341,18 @@ class CaseForm(ModelForm):
instance.save() instance.save()
self.save_m2m() self.save_m2m()
logging.debug(f"{self.collection=}")
if self.collection is not None:
logging.debug(f"{self.collection=}")
case_no = self.collection.cases.count() + 1
logging.debug(f"{case_no=}")
logging.debug(f"{instance=}")
# Create through model
#cd = CaseDetail(case=instance, collection=exam, sort_order=case_no)
self.collection.cases.add(instance, through_defaults={"sort_order": case_no})
return instance return instance
@@ -117,8 +117,8 @@
Add collection</button> Add collection</button>
<div id="collection-form"></div> <div id="collection-form"></div>
<ul> <ul>
{% for p in case.casecollection_set.all %} {% for collection in case.casecollection_set.all %}
<li>{{p.name}}</li> <li><a href="{% url 'atlas:collection_detail' pk=collection.pk %}">{{collection.name}}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</p> </p>
+4
View File
@@ -51,6 +51,10 @@
{% block content %} {% block content %}
<h2>Submit Case</h2> <h2>Submit Case</h2>
Use this form to create a atlas case. Existing associated image sets can be added using this form. Use this form to create a atlas case. Existing associated image sets can be added using this form.
{% if form.collection %}
<div class="alert alert-info" role="alert">Creating a case in the collection: <a href="{% url 'atlas:collection_detail' pk=form.collection.pk %}">{{form.collection.name}}</a></div>
{% endif %}
<form action="" method="post" enctype="multipart/form-data" id="atlas-form"> <form action="" method="post" enctype="multipart/form-data" id="atlas-form">
{% csrf_token %} {% csrf_token %}
@@ -3,7 +3,8 @@
<br/>Collection: {{collection.name}}-> <a href="{% url 'atlas:collection_detail' pk=collection.pk %}">Overview</a> / <br/>Collection: {{collection.name}}-> <a href="{% url 'atlas:collection_detail' pk=collection.pk %}">Overview</a> /
<a href="{% url 'atlas:collection_mark_overview' collection.pk %}">Mark</a> / <a href="{% url 'atlas:collection_mark_overview' collection.pk %}">Mark</a> /
<a href="{% url 'atlas:collection_scores_cid' collection.pk %}">Scores</a> / <a href="{% url 'atlas:collection_scores_cid' collection.pk %}">Scores</a> /
<a href="{% url 'atlas:exam_cids' collection.pk %}">Candidates</a> <a href="{% url 'atlas:exam_cids' collection.pk %}">Candidates</a> /
<a href="{% url 'atlas:atlas_create_exam' pk=collection.pk %}">Add New Case</a>
<div class="floating-header"> <div class="floating-header">
<a href="{% url 'atlas:collection_update' collection.id %}" title="Edit the Collection">Edit</a> <a href="{% url 'atlas:collection_update' collection.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:collection_delete' collection.id %}" title="Delete the Collection">Delete</a> \ <a href="{% url 'atlas:collection_delete' collection.id %}" title="Delete the Collection">Delete</a>
+2 -2
View File
@@ -486,8 +486,8 @@
</table> </table>
<h3>Images:</h3> <h3>Images:</h3>
<input type="button" value="Add More Images" id="add_more_images"> <input type="button" value="Add More Images (manually)" id="add_more_images">
<div id="drop-container" class="">Drop images here (or use the buttons below). Dropping images here will overwrite <div id="drop-container" class="series-drop">Drop images here (or use the buttons below). Dropping images here will overwrite
existing images in the series. When drag and dropping make sure to drag the first image or the order will not be existing images in the series. When drag and dropping make sure to drag the first image or the order will not be
maintained. Note: dicom images are often not exported in order (although their order can be automatically maintained. Note: dicom images are often not exported in order (although their order can be automatically
detected once uploaded) detected once uploaded)
+1
View File
@@ -127,6 +127,7 @@ urlpatterns = [
path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"), path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"),
path("case/<int:pk>/delete", views.CaseDelete.as_view(), name="case_delete"), path("case/<int:pk>/delete", views.CaseDelete.as_view(), name="case_delete"),
path("case/create/", views.AtlasCreate.as_view(), name="case_create"), path("case/create/", views.AtlasCreate.as_view(), name="case_create"),
path("case/collection/<int:pk>/create", views.AtlasCreate.as_view(), name="atlas_create_exam"),
path("series/create", views.SeriesCreate.as_view(), name="series_create"), path("series/create", views.SeriesCreate.as_view(), name="series_create"),
path( path(
"series/<int:pk>/create", "series/<int:pk>/create",
+14 -7
View File
@@ -645,13 +645,20 @@ class AtlasCreateBase(RevisionMixin, LoginRequiredMixin):
# @login_required # @login_required
class AtlasCreate(AtlasCreateBase, CreateView): class AtlasCreate(AtlasCreateBase, CreateView):
def get_initial(self): def get_initial(self):
# There has to be a better way... if "pk" in self.kwargs:
try: initial = super().get_initial()
s = (i.pk for i in self.request.user.atlas_default.site.all()) exam = get_object_or_404(CaseCollection, pk=self.kwargs["pk"])
self.initial.update({"site": s})
except AttributeError: initial["exams"] = [exam.id]
pass
return self.initial return initial
## There has to be a better way...
#try:
# s = (i.pk for i in self.request.user.atlas_default.site.all())
# self.initial.update({"site": s})
#except AttributeError:
# pass
#return self.initial
class AtlasUpdate( class AtlasUpdate(
+77 -77
View File
@@ -6,38 +6,38 @@
<script type="text/javascript"> <script type="text/javascript">
let normal = false; let normal = false;
let monitor_directory_handler = false; //let monitor_directory_handler = false;
let monitor_interval_id = false; //let monitor_interval_id = false;
// set to hold the files that are still being processed // set to hold the files that are still being processed
let active_file_inputs = new Set(); let active_file_inputs = new Set();
let load_viewer = false; let load_viewer = false;
async function listAllFilesAndDirs(dirHandle, files_only) { // async function listAllFilesAndDirs(dirHandle, files_only) {
const files = []; // const files = [];
for await (let [name, handle] of dirHandle) { // for await (let [name, handle] of dirHandle) {
const { // const {
kind // kind
} = handle; // } = handle;
if (handle.kind === 'directory') { // if (handle.kind === 'directory') {
if (files_only != true) { // if (files_only != true) {
files.push({ // files.push({
name, // name,
handle, // handle,
kind // kind
}); // });
} // }
files.push(...await listAllFilesAndDirs(handle)); // files.push(...await listAllFilesAndDirs(handle));
} else { // } else {
files.push({ // files.push({
name, // name,
handle, // handle,
kind // kind
}); // });
} // }
} // }
return files; // return files;
} // }
function showEditPopup(url) { function showEditPopup(url) {
var win = window.open(url, "Edit", var win = window.open(url, "Edit",
@@ -243,56 +243,56 @@
}; };
$('#directory-picker-button').click(() => { // $('#directory-picker-button').click(() => {
window.showDirectoryPicker().then((handler) => { // window.showDirectoryPicker().then((handler) => {
monitor_directory_handler = handler; // monitor_directory_handler = handler;
monitor_interval_id = setInterval(function () { // monitor_interval_id = setInterval(function () {
listAllFilesAndDirs(monitor_directory_handler).then(async function ( // listAllFilesAndDirs(monitor_directory_handler).then(async function (
items) { // items) {
files_to_add = new Set(); // files_to_add = new Set();
current_files = getCurrentFiles(); // current_files = getCurrentFiles();
current_files_tuple = new Set() // current_files_tuple = new Set()
for (let elem of current_files) { // for (let elem of current_files) {
current_files_tuple.add( // current_files_tuple.add(
`${elem.size}, ${elem.name}, ${elem.lastModified}` // `${elem.size}, ${elem.name}, ${elem.lastModified}`
); // );
} // }
//
console.log(current_files_tuple) // console.log(current_files_tuple)
for (var i = 0; i < items.length; i++) { // for (var i = 0; i < items.length; i++) {
//
f = await items[i].handle.getFile() // f = await items[i].handle.getFile()
console.log("f", f) // console.log("f", f)
if (!current_files_tuple.has( // if (!current_files_tuple.has(
`${f.size}, ${f.name}, ${f.lastModified}` // `${f.size}, ${f.name}, ${f.lastModified}`
)) { // )) {
files_to_add.add(f); // files_to_add.add(f);
} // }
} // }
//
// file objects differ enough that the following does not work // // file objects differ enough that the following does not work
//let distinct_set = new Set([...files_to_add].filter( // //let distinct_set = new Set([...files_to_add].filter(
// x => !current_files.has(x))); // // x => !current_files.has(x)));
//
//
extendInputs(files_to_add.size); // extendInputs(files_to_add.size);
files_to_add.forEach((file) => { // files_to_add.forEach((file) => {
addFile(file, false); // addFile(file, false);
}); // });
}); // });
}, 5000); // }, 5000);
$("#directory-picker-display").text(monitor_directory_handler.name) // $("#directory-picker-display").text(monitor_directory_handler.name)
.after( // .after(
$( // $(
" <span>[clear]</span>").click((evt) => { // " <span>[clear]</span>").click((evt) => {
$(evt.target).remove(); // $(evt.target).remove();
$("#directory-picker-display").text(""); // $("#directory-picker-display").text("");
monitor_directory_handler = false; // monitor_directory_handler = false;
clearInterval(monitor_interval_id); // clearInterval(monitor_interval_id);
//const files = await listAllFilesAndDirs(directoryHandle); // //const files = await listAllFilesAndDirs(directoryHandle);
})); // }));
}); // });
}); // });