pre series form update
This commit is contained in:
+1
-1
@@ -78,7 +78,7 @@ class AnatomyQuestion(models.Model):
|
||||
|
||||
image = models.ImageField(
|
||||
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(
|
||||
|
||||
@@ -280,6 +280,12 @@ button a {
|
||||
border: 1px dashed white;
|
||||
}
|
||||
|
||||
.series-drop {
|
||||
position: sticky;
|
||||
height: 600px;
|
||||
bottom: -500px;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/home/ross/dv/dicomViewer.css
|
||||
@@ -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;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ from django.shortcuts import get_object_or_404
|
||||
from atlas.models import (
|
||||
Case,
|
||||
CaseCollection,
|
||||
CaseDetail,
|
||||
CidReportAnswer,
|
||||
Differential,
|
||||
Finding,
|
||||
@@ -39,6 +40,7 @@ from tinymce.widgets import TinyMCE
|
||||
|
||||
from dal import autocomplete
|
||||
|
||||
import logging
|
||||
|
||||
class ConditionForm(ModelForm):
|
||||
class Meta:
|
||||
@@ -240,9 +242,20 @@ class CaseForm(ModelForm):
|
||||
js = ["jsi18n.js", "tesseract.min.js"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
logging.info("LOG")
|
||||
logging.debug(kwargs)
|
||||
self.user = kwargs.pop(
|
||||
"user"
|
||||
) # 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"):
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
@@ -251,6 +264,7 @@ class CaseForm(ModelForm):
|
||||
# a list of primary key for the selected data.
|
||||
# initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
|
||||
super(CaseForm, self).__init__(*args, **kwargs)
|
||||
@@ -307,6 +321,9 @@ class CaseForm(ModelForm):
|
||||
# Get the unsaved Case instance
|
||||
instance = ModelForm.save(self, False)
|
||||
|
||||
|
||||
|
||||
|
||||
# Prepare a 'save_m2m' method for the form,
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
@@ -324,6 +341,18 @@ class CaseForm(ModelForm):
|
||||
instance.save()
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -117,8 +117,8 @@
|
||||
Add collection</button>
|
||||
<div id="collection-form"></div>
|
||||
<ul>
|
||||
{% for p in case.casecollection_set.all %}
|
||||
<li>{{p.name}}</li>
|
||||
{% for collection in case.casecollection_set.all %}
|
||||
<li><a href="{% url 'atlas:collection_detail' pk=collection.pk %}">{{collection.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
{% block content %}
|
||||
<h2>Submit Case</h2>
|
||||
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">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<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_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">
|
||||
<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>
|
||||
|
||||
@@ -486,8 +486,8 @@
|
||||
</table>
|
||||
|
||||
<h3>Images:</h3>
|
||||
<input type="button" value="Add More Images" id="add_more_images">
|
||||
<div id="drop-container" class="">Drop images here (or use the buttons below). Dropping images here will overwrite
|
||||
<input type="button" value="Add More Images (manually)" id="add_more_images">
|
||||
<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
|
||||
maintained. Note: dicom images are often not exported in order (although their order can be automatically
|
||||
detected once uploaded)
|
||||
|
||||
@@ -127,6 +127,7 @@ urlpatterns = [
|
||||
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/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/<int:pk>/create",
|
||||
|
||||
+14
-7
@@ -645,13 +645,20 @@ class AtlasCreateBase(RevisionMixin, LoginRequiredMixin):
|
||||
# @login_required
|
||||
class AtlasCreate(AtlasCreateBase, CreateView):
|
||||
def get_initial(self):
|
||||
# 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
|
||||
if "pk" in self.kwargs:
|
||||
initial = super().get_initial()
|
||||
exam = get_object_or_404(CaseCollection, pk=self.kwargs["pk"])
|
||||
|
||||
initial["exams"] = [exam.id]
|
||||
|
||||
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(
|
||||
|
||||
@@ -6,38 +6,38 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
let normal = false;
|
||||
let monitor_directory_handler = false;
|
||||
let monitor_interval_id = false;
|
||||
//let monitor_directory_handler = false;
|
||||
//let monitor_interval_id = false;
|
||||
|
||||
// set to hold the files that are still being processed
|
||||
let active_file_inputs = new Set();
|
||||
let load_viewer = false;
|
||||
|
||||
async function listAllFilesAndDirs(dirHandle, files_only) {
|
||||
const files = [];
|
||||
for await (let [name, handle] of dirHandle) {
|
||||
const {
|
||||
kind
|
||||
} = handle;
|
||||
if (handle.kind === 'directory') {
|
||||
if (files_only != true) {
|
||||
files.push({
|
||||
name,
|
||||
handle,
|
||||
kind
|
||||
});
|
||||
}
|
||||
files.push(...await listAllFilesAndDirs(handle));
|
||||
} else {
|
||||
files.push({
|
||||
name,
|
||||
handle,
|
||||
kind
|
||||
});
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
// async function listAllFilesAndDirs(dirHandle, files_only) {
|
||||
// const files = [];
|
||||
// for await (let [name, handle] of dirHandle) {
|
||||
// const {
|
||||
// kind
|
||||
// } = handle;
|
||||
// if (handle.kind === 'directory') {
|
||||
// if (files_only != true) {
|
||||
// files.push({
|
||||
// name,
|
||||
// handle,
|
||||
// kind
|
||||
// });
|
||||
// }
|
||||
// files.push(...await listAllFilesAndDirs(handle));
|
||||
// } else {
|
||||
// files.push({
|
||||
// name,
|
||||
// handle,
|
||||
// kind
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// return files;
|
||||
// }
|
||||
|
||||
function showEditPopup(url) {
|
||||
var win = window.open(url, "Edit",
|
||||
@@ -243,56 +243,56 @@
|
||||
};
|
||||
|
||||
|
||||
$('#directory-picker-button').click(() => {
|
||||
window.showDirectoryPicker().then((handler) => {
|
||||
monitor_directory_handler = handler;
|
||||
monitor_interval_id = setInterval(function () {
|
||||
listAllFilesAndDirs(monitor_directory_handler).then(async function (
|
||||
items) {
|
||||
files_to_add = new Set();
|
||||
current_files = getCurrentFiles();
|
||||
current_files_tuple = new Set()
|
||||
for (let elem of current_files) {
|
||||
current_files_tuple.add(
|
||||
`${elem.size}, ${elem.name}, ${elem.lastModified}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(current_files_tuple)
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
|
||||
f = await items[i].handle.getFile()
|
||||
console.log("f", f)
|
||||
if (!current_files_tuple.has(
|
||||
`${f.size}, ${f.name}, ${f.lastModified}`
|
||||
)) {
|
||||
files_to_add.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
// file objects differ enough that the following does not work
|
||||
//let distinct_set = new Set([...files_to_add].filter(
|
||||
// x => !current_files.has(x)));
|
||||
|
||||
|
||||
extendInputs(files_to_add.size);
|
||||
files_to_add.forEach((file) => {
|
||||
addFile(file, false);
|
||||
});
|
||||
});
|
||||
}, 5000);
|
||||
$("#directory-picker-display").text(monitor_directory_handler.name)
|
||||
.after(
|
||||
$(
|
||||
" <span>[clear]</span>").click((evt) => {
|
||||
$(evt.target).remove();
|
||||
$("#directory-picker-display").text("");
|
||||
monitor_directory_handler = false;
|
||||
clearInterval(monitor_interval_id);
|
||||
//const files = await listAllFilesAndDirs(directoryHandle);
|
||||
}));
|
||||
});
|
||||
});
|
||||
// $('#directory-picker-button').click(() => {
|
||||
// window.showDirectoryPicker().then((handler) => {
|
||||
// monitor_directory_handler = handler;
|
||||
// monitor_interval_id = setInterval(function () {
|
||||
// listAllFilesAndDirs(monitor_directory_handler).then(async function (
|
||||
// items) {
|
||||
// files_to_add = new Set();
|
||||
// current_files = getCurrentFiles();
|
||||
// current_files_tuple = new Set()
|
||||
// for (let elem of current_files) {
|
||||
// current_files_tuple.add(
|
||||
// `${elem.size}, ${elem.name}, ${elem.lastModified}`
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// console.log(current_files_tuple)
|
||||
// for (var i = 0; i < items.length; i++) {
|
||||
//
|
||||
// f = await items[i].handle.getFile()
|
||||
// console.log("f", f)
|
||||
// if (!current_files_tuple.has(
|
||||
// `${f.size}, ${f.name}, ${f.lastModified}`
|
||||
// )) {
|
||||
// files_to_add.add(f);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // file objects differ enough that the following does not work
|
||||
// //let distinct_set = new Set([...files_to_add].filter(
|
||||
// // x => !current_files.has(x)));
|
||||
//
|
||||
//
|
||||
// extendInputs(files_to_add.size);
|
||||
// files_to_add.forEach((file) => {
|
||||
// addFile(file, false);
|
||||
// });
|
||||
// });
|
||||
// }, 5000);
|
||||
// $("#directory-picker-display").text(monitor_directory_handler.name)
|
||||
// .after(
|
||||
// $(
|
||||
// " <span>[clear]</span>").click((evt) => {
|
||||
// $(evt.target).remove();
|
||||
// $("#directory-picker-display").text("");
|
||||
// monitor_directory_handler = false;
|
||||
// clearInterval(monitor_interval_id);
|
||||
// //const files = await listAllFilesAndDirs(directoryHandle);
|
||||
// }));
|
||||
// });
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user