start removing lots of print

This commit is contained in:
Ross
2025-06-30 10:39:42 +01:00
parent afa3b0c0ef
commit bc26209f07
14 changed files with 328 additions and 406 deletions
-2
View File
@@ -113,8 +113,6 @@ def get_exam_user_status_cid_user(request, exam_id: int, cid: int):
if not exam.check_user_can_edit(request.user):
raise HttpError(HTTPStatus.UNAUTHORIZED,"Exam author required")
print(exam_id, cid)
return exam.exam_user_status.filter(cid_user_exam__cid_user__cid=cid)
@router.get('/exam/{exam_id}/user_status/{user_id}/user', response=List[ExamStatusSchema])
-1
View File
@@ -492,7 +492,6 @@ class Exam(ExamBase):
return answer
def get_question_user_user_answer(self, question_index, user):
print(user)
question = self.get_question_by_index(question_index)
answer = UserAnswer.objects.filter(question=question, user=user.pk, exam=self)
-2
View File
@@ -194,8 +194,6 @@ def test_exams(db, client):
exam.save()
res = client.get(exam_metadata["url"])
print("PRE ERRER", exam_metadata["url"])
print(res.content)
assert res.status_code == 404
exam.active = True
-17
View File
@@ -646,22 +646,6 @@ class AnatomyQuestionUpdate(UpdateQuestionMixin):
context = self.get_context_data(form=form)
#formset = context["answer_formset"]
return super().form_valid(form)
## logger.debug(formset.is_valid())
#if formset.is_valid():
# response = super().form_valid(form)
# formset.instance = self.object
# formset.save()
# # restore exam orders
# #for exam in self.object.exams.all():
# # if exam in exam_orders and self.object in exam_orders[exam]:
# # print(exam_orders[exam])
# # exam.exam_questions.set(exam_orders[exam])
# # exam.save()
# return response
#else:
# return super().form_invalid(form)
class QuestionClone(AnatomyQuestionCreateBase):
@@ -797,7 +781,6 @@ def question_save_annotation(request, pk):
question = get_object_or_404(AnatomyQuestion, pk=pk)
question.image_annotations = request.POST.get("annotation")
print(question.image_annotations)
question.save()
data = {"status": "success"}
+54 -68
View File
@@ -128,15 +128,10 @@ def uncategorised_dicoms(request):
def import_dicoms_helper(request, case_id: int | None = None):
# TODO: move this out of the api (would be better with HTMX)
# dicoms = UncategorisedDicom.objects.filter(user=request.user)
if "selection" in request.POST:
dicoms = UncategorisedDicom.objects.filter(
series_instance_uid__in=request.POST.getlist("selection")
)
else:
dicoms = UncategorisedDicom.objects.filter(user=request.user)
@@ -145,73 +140,66 @@ def import_dicoms_helper(request, case_id: int | None = None):
else:
order = None
data = defaultdict(list)
for d in dicoms:
# Group dicoms by SeriesInstanceUID, but don't keep all tags in memory
series_map = {}
for d in dicoms.iterator(): # Use iterator() to avoid loading all at once
tags = d.basic_dicom_tags
series_uid = tags["SeriesInstanceUID"]
data[tags["SeriesInstanceUID"]].append((d, tags))
series_list = []
for series_uid in data:
# Hmm probably not a good idea to blinding create a modality...
try:
modality = Modality.objects.get(short_code=tags["Modality"])
except Modality.DoesNotExist:
modality = Modality.objects.create(short_code=tags["Modality"], modality=tags["Modality"])
tags = data[series_uid][0][1]
try:
with transaction.atomic():
series, created = Series.objects.get_or_create(
series_instance_uid=tags["SeriesInstanceUID"],
defaults={
"modality": modality,
"description": tags["SeriesDescription"],
}
)
if created and tags["StudyDescription"]:
examination, created_exam = Examination.objects.get_or_create(
examination=tags["StudyDescription"]
)
if created_exam:
examination.modality = modality
examination.save()
series.examination = examination
series.save()
except IntegrityError:
# If a race condition still occurs, fetch the existing series
pass
if 'series' not in locals():
series = Series.objects.get(series_instance_uid=tags["SeriesInstanceUID"])
# We might only want to add the author during creation....
series.author.add(request.user)
if case_id is not None:
case_object = get_object_or_404(Case, pk=case_id)
if series_uid not in series_map:
# Get or create Series and related objects as needed
try:
series.case.add(case_object)
modality = Modality.objects.get(short_code=tags["Modality"])
except Modality.DoesNotExist:
modality = Modality.objects.create(short_code=tags["Modality"], modality=tags["Modality"])
series_tags = tags # Save the first tag for this series
try:
with transaction.atomic():
series, created = Series.objects.get_or_create(
series_instance_uid=series_uid,
defaults={
"modality": modality,
"description": tags.get("SeriesDescription", ""),
}
)
if created and tags.get("StudyDescription"):
examination, created_exam = Examination.objects.get_or_create(
examination=tags["StudyDescription"]
)
if created_exam:
examination.modality = modality
examination.save()
series.examination = examination
series.save()
except IntegrityError:
# The relation already exists, safe to ignore
pass
series = Series.objects.get(series_instance_uid=series_uid)
for dicom, dicom_tags in data[series_uid]:
series_image = SeriesImage(
image=dicom.image,
series=series,
is_dicom=True,
image_blake3_hash=dicom.image_blake3_hash,
)
series_image.save()
# dicom.image.delete()
# Add author and case if needed
series.author.add(request.user)
if case_id is not None:
case_object = get_object_or_404(Case, pk=case_id)
try:
series.case.add(case_object)
except IntegrityError:
pass
dicom.delete()
series_map[series_uid] = series
else:
series = series_map[series_uid]
# Create and save SeriesImage immediately
series_image = SeriesImage(
image=d.image,
series=series,
is_dicom=True,
image_blake3_hash=d.image_blake3_hash,
)
series_image.save()
d.delete() # Remove UncategorisedDicom immediately
# Optionally, re-order series if needed
for series in series_map.values():
match order:
case "order-series-instance-number":
series.order_by_dicom("InstanceNumber")
@@ -219,10 +207,8 @@ def import_dicoms_helper(request, case_id: int | None = None):
series.order_by_dicom("SliceLocation")
case _:
pass
series_list.append((series, series.get_link()))
return series_list
return [(series, series.get_link()) for series in series_map.values()]
@router.post(
-10
View File
@@ -84,9 +84,6 @@ class CaseSelect(Select):
def create_option(self, *args, **kwargs):
option = super().create_option(*args, **kwargs)
print(option)
return option
def get_context(self, name: str, value, attrs) -> dict[str, Any]:
@@ -694,7 +691,6 @@ class CidReportAnswerMarkForm(ModelForm):
#
# def __init__(self, *args, user, **kwargs):
# super(AddCollectionToCaseForm, self).__init__(*args, **kwargs)
# print("INIT", args, kwargs)
#
# if not user.groups.filter(name="atlas_editor").exists():
# queryset = CaseCollection.objects.filter(author__id=user.id)
@@ -736,7 +732,6 @@ class AddCollectionToCaseForm(Form):
def __init__(self, *args, user, **kwargs):
super(AddCollectionToCaseForm, self).__init__(*args, **kwargs)
print("INIT", args, kwargs)
if not user.groups.filter(name="atlas_editor").exists():
queryset = CaseCollection.objects.filter(author__id=user.id)
@@ -752,21 +747,16 @@ class AddCollectionToCaseForm(Form):
)
def save(self, *args, **kwargs):
print("SAVE", args, kwargs)
instance = super(AddCollectionToCaseForm, self).save(*args, **kwargs)
# Here we save the modified project selection back into the database
instance.casecollection_set.set(self.cleaned_data['casecollection'])
print(f"{instance=}")
return instance
def clean_casecollection(self):
collection_set = self.cleaned_data["casecollection"]
print(f"{collection_set=}")
for col in collection_set:
if col not in self.valid_collections:
raise ValidationError("Invalid collection")
-10
View File
@@ -1215,10 +1215,7 @@ class BaseReportAnswer(models.Model):
if self.question.question_schema is None or not "properties" in self.question.question_schema:
return []
answers = []
print("1", self.json_answer)
print("2", self.question.question_answers)
for name, value in self.question.question_schema["properties"].items():
print(name, value)
try:
user_answer = self.json_answer[name]
@@ -1229,11 +1226,8 @@ class BaseReportAnswer(models.Model):
except KeyError: # If the schema has changed?
correct_answer = ""
print(correct_answer)
match value:
case {"type": "string", "enum": _}:
print("YES", value)
automark = True
answer_is_correct = user_answer == correct_answer
case {"type": "string"}:
@@ -1366,8 +1360,6 @@ class UncategorisedDicom(models.Model):
# duplicate = obj
return obj
print(image_hash)
if obj := SeriesImage.objects.filter(image_blake3_hash=image_hash).first():
duplicate = obj
@@ -1409,8 +1401,6 @@ class UncategorisedDicom(models.Model):
ds = pydicom.dcmread(self.image)
return ds
except pydicom.errors.InvalidDicomError:
print("Error getting dicom dataset")
print(self.image)
return {"SeriesInstanceUID": "1234"}
def get_basic_dicom_tags(self, dataset=None):
-5
View File
@@ -196,11 +196,6 @@ class PopupLinkColumn(tables.Column):
return obj.get_thumbnail()[0]
def create_link(test):
print(test)
return "Hello"
class SeriesTable(tables.Table):
edit = tables.LinkColumn(
"atlas:series_update", text="Edit", args=[A("pk")], orderable=False
+127 -125
View File
@@ -39,8 +39,8 @@
{% endif %}
<details><summary>Upload settings</summary>
<form>
<div>
<fieldset><legend>Select how series should be ordered when uploaded</legend>
<div>
<fieldset><legend>Select how series should be ordered when uploaded</legend>
<div class="helptext">
This will affect how series are displayed when viewing with the built in site viewer. By default when exporting from Insight the order is not maintained so you should choose to order either by slice location or instance number.<br/>
If you are not sure, leave this as the default.
@@ -57,8 +57,8 @@
<input type="radio" id="order-series-none" value="order-series-none" name="order-series" >
<label for="order-series-none">None</label><br/>
</div>
</fieldset>
</div>
</fieldset>
</div>
</form>
</details>
@@ -69,15 +69,15 @@
title="Deleted selected uploads"
>Delete Uploads</button>
<button id="import-dicoms-sequential-button"
type="button"
class="btn btn-primary"
>Import {% if case %}into case{% endif %} (one at a time)</button>
<button id="import-dicoms-sequential-button"
type="button"
class="btn btn-primary"
>Import {% if case %}into case{% endif %} (one at a time)</button>
<div id="import-status"></div>
<div id="import-status"></div>
<div class="indicator"><div class="loader"></div>Loading...</div>
<div id="import-status"></div>
<div id="import-status"></div>
<div class="imported">
@@ -89,136 +89,138 @@
<p><a href="{% url 'atlas:series_view' %}?case=null&sort=-modified_date">View orphan series</a></p>
<script>
document.getElementById('import-dicoms-sequential-button').addEventListener('click', async function() {
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
<script>
const importButton = document.getElementById('import-dicoms-sequential-button');
if (importButton) {
document.getElementById('import-dicoms-sequential-button').addEventListener('click', async function() {
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
// Only include checkboxes that are not disabled (i.e., not already imported)
const selectableCheckboxes = allCheckboxes.filter(cb => !cb.disabled);
const toImport = checkboxes.length
? checkboxes.filter(cb => !cb.disabled)
: selectableCheckboxes;
if (toImport.length === 0) {
alert("Please select at least one series to import (not already imported).");
return;
}
const statusDiv = document.getElementById('import-status');
statusDiv.innerHTML = '';
const orderSeries = document.querySelector('input[name="order-series"]:checked').value;
const csrfToken = '{{ csrf_token }}';
{% if case %}
const importUrl = "{% url 'api-1:import_dicoms_case' case.id %}";
{% else %}
const importUrl = "{% url 'api-1:import_dicoms' %}";
{% endif %}
for (let i = 0; i < toImport.length; i++) {
const seriesId = toImport[i].value;
statusDiv.innerHTML += `<div id="import-status-${seriesId}">Importing series ${seriesId}...</div>`;
try {
const response = await fetch(importUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRFToken": csrfToken,
},
body: `selection=${encodeURIComponent(seriesId)}&order-series=${encodeURIComponent(orderSeries)}`
});
const text = await response.text();
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: ${text}`;
const selectableCheckboxes = allCheckboxes.filter(cb => !cb.disabled);
const toImport = checkboxes.length
? checkboxes.filter(cb => !cb.disabled)
: selectableCheckboxes;
if (toImport.length === 0) {
alert("Please select at least one series to import (not already imported).");
return;
}
const statusDiv = document.getElementById('import-status');
statusDiv.innerHTML = '';
const orderSeries = document.querySelector('input[name="order-series"]:checked').value;
const csrfToken = '{{ csrf_token }}';
{% if case %}
const importUrl = "{% url 'api-1:import_dicoms_case' case.id %}";
{% else %}
const importUrl = "{% url 'api-1:import_dicoms' %}";
{% endif %}
for (let i = 0; i < toImport.length; i++) {
const seriesId = toImport[i].value;
statusDiv.innerHTML += `<div id="import-status-${seriesId}">Importing series ${seriesId}...</div>`;
try {
const response = await fetch(importUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRFToken": csrfToken,
},
body: `selection=${encodeURIComponent(seriesId)}&order-series=${encodeURIComponent(orderSeries)}`
});
const text = await response.text();
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: ${text}`;
// Mark as imported in the UI and make unselectable
const li = document.querySelector(`#series-list input[value="${seriesId}"]`)?.closest('li');
if (li && !li.classList.contains('imported')) {
li.classList.add('imported');
const li = document.querySelector(`#series-list input[value="${seriesId}"]`)?.closest('li');
if (li && !li.classList.contains('imported')) {
li.classList.add('imported');
// Add badge if not already present
if (!li.querySelector('.badge.bg-success')) {
const badge = document.createElement('span');
badge.className = 'badge bg-success ms-2';
badge.textContent = 'Imported';
li.appendChild(badge);
}
if (!li.querySelector('.badge.bg-success')) {
const badge = document.createElement('span');
badge.className = 'badge bg-success ms-2';
badge.textContent = 'Imported';
li.appendChild(badge);
}
// Disable checkbox and prevent further selection
const checkbox = li.querySelector('input[name="selection"]');
if (checkbox) {
checkbox.disabled = true;
checkbox.checked = false;
}
const checkbox = li.querySelector('input[name="selection"]');
if (checkbox) {
checkbox.disabled = true;
checkbox.checked = false;
}
// Remove click handler for selection
li.querySelector('span').onclick = null;
li.style.pointerEvents = "none";
li.style.opacity = "0.7";
}
} catch (e) {
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: <span style="color:red;">Failed</span>`;
li.querySelector('span').onclick = null;
li.style.pointerEvents = "none";
li.style.opacity = "0.7";
}
} catch (e) {
document.getElementById(`import-status-${seriesId}`).innerHTML = `Series ${seriesId}: <span style="color:red;">Failed</span>`;
}
}
statusDiv.innerHTML += "<div>All done.</div>";
});
}
}
statusDiv.innerHTML += "<div>All done.</div>";
});
</script>
</script>
{% endblock %}
{% block css %}
<style>
.indicator {
display: none;
}
<style>
.indicator {
display: none;
}
.indicator.htmx-request {
display: block;
}
.indicator.htmx-request {
display: block;
}
.loader {
width: 50px;
aspect-ratio: 1;
display: grid;
}
.loader::before,
.loader::after {
content:"";
grid-area: 1/1;
--c:no-repeat radial-gradient(farthest-side,#25b09b 92%,#0000);
background:
var(--c) 50% 0,
var(--c) 50% 100%,
var(--c) 100% 50%,
var(--c) 0 50%;
background-size: 12px 12px;
animation: l12 1s infinite;
}
.loader::before {
margin: 4px;
filter: hue-rotate(45deg);
background-size: 8px 8px;
animation-timing-function: linear
}
.loader {
width: 50px;
aspect-ratio: 1;
display: grid;
}
.loader::before,
.loader::after {
content:"";
grid-area: 1/1;
--c:no-repeat radial-gradient(farthest-side,#25b09b 92%,#0000);
background:
var(--c) 50% 0,
var(--c) 50% 100%,
var(--c) 100% 50%,
var(--c) 0 50%;
background-size: 12px 12px;
animation: l12 1s infinite;
}
.loader::before {
margin: 4px;
filter: hue-rotate(45deg);
background-size: 8px 8px;
animation-timing-function: linear
}
@keyframes l12 {
100%{transform: rotate(.5turn)}
}
@keyframes l12 {
100%{transform: rotate(.5turn)}
}
#series-list {
user-select: none;
cursor: pointer;
}
#series-list {
user-select: none;
cursor: pointer;
}
#series-list li {
}
#series-list .selected {
color: white;
border: 1px solid #52057b;
user-select: none;
margin: -1px;
#series-list li {
}
#series-list .selected {
color: white;
border: 1px solid #52057b;
user-select: none;
margin: -1px;
}
.series-block-popup-link {
float: right;
}
#series-list li.imported {
border-color: #e6ffe6;
opacity: 0.7;
}
</style>
}
.series-block-popup-link {
float: right;
}
#series-list li.imported {
border-color: #e6ffe6;
opacity: 0.7;
}
</style>
{% endblock css %}
-1
View File
@@ -37,7 +37,6 @@ def test_condition_tree_structure(db):
assert len(child2.get_parents()) == 1
assert parent in child2.get_parents()
print(parent.get_descendents())
#@pytest.fixture
#def create_basic_user(db, django_user_model):
-7
View File
@@ -568,7 +568,6 @@ class SeriesBase(models.Model):
n = n + 1
def order_by_dicom(self, field="SliceLocation"):
print(f"Ordering by dicom field {field}")
images = self.images.filter(removed=False)
@@ -577,9 +576,6 @@ class SeriesBase(models.Model):
for i in images:
files.append((i, pydicom.dcmread(i.image.path)))
print("images to order")
print(files)
# print("file count: {}".format(len(files)))
# skip files with no SliceLocation (eg scout views)
@@ -594,8 +590,6 @@ class SeriesBase(models.Model):
else:
skipcount = skipcount + 1
print("skipped, no {}: {}".format(field, skipcount))
if not slices:
return
@@ -607,7 +601,6 @@ class SeriesBase(models.Model):
for f in slices:
i = map[f[field].value]
i.position = n
print(f"Setting position {n} for {i}")
i.save()
n = n + 1
+1 -11
View File
@@ -93,38 +93,29 @@ def get_dicom_order(path):
files = []
for fname in glob.glob(path, recursive=False):
print("loading: {}".format(fname))
files.append((fname, pydicom.dcmread(fname)))
print("file count: {}".format(len(files)))
# skip files with no SliceLocation (eg scout views)
slices = []
map = {}
skipcount = 0
for fname, f in files:
print(dir(f))
print(fname)
if hasattr(f, 'SliceLocation'):
slices.append(f)
map[f.SliceLocation] = fname
else:
skipcount = skipcount + 1
print("skipped, no SliceLocation: {}".format(skipcount))
# ensure they are in the correct order
slices = sorted(slices, key=lambda s: s.SliceLocation)
#print(slices)
for i in slices:
print(map[i.SliceLocation])
return slices
#get_dicom_order("/home/ross/Downloads/DICOM HEAD/STD4/SER1/*.dcm")
def pretty_print_dicom(dataset, indent=0):
l = print_dicom(dataset, indent)
print(l)
return "".join(l)
@@ -183,7 +174,6 @@ def get_image_dicom_hash(img, dataset=None, hash_type="md5", direct_pixel_data=T
hasher.update(dataset.PixelData)
except AttributeError as e:
print("Error getting pixel data hash from dataset")
print(dataset)
raise InvalidDicomError("Error getting pixel data hash from dataset")
else:
-1
View File
@@ -11,7 +11,6 @@ if __name__ == "__main__":
if os.environ.get('RUN_MAIN'):
if settings.DEBUG_CONTAINER:
import debugpy
print("DEBUG LISTEN")
debugpy.listen(("0.0.0.0", 3459))
try:
+146 -146
View File
File diff suppressed because one or more lines are too long