pre series form update
This commit is contained in:
+1
-1
@@ -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(
|
||||||
|
|||||||
@@ -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 +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 (
|
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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
}));
|
// }));
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user