improve cases series handling in collection

This commit is contained in:
Ross
2026-06-01 12:57:35 +01:00
parent 92d4ece061
commit 213ee17328
10 changed files with 378 additions and 71 deletions
+14 -1
View File
@@ -953,7 +953,13 @@ CaseCollectionCaseFormSet = inlineformset_factory(
CaseCollection, CaseCollection,
Case.casecollection_set.through, Case.casecollection_set.through,
form=CaseCollectionCaseForm, form=CaseCollectionCaseForm,
exclude=["question_schema", "question_answers"], exclude=[
"question_schema",
"question_answers",
"series_visibility_config",
"learner_comment",
"learner_review_comment",
],
can_delete=True, can_delete=True,
extra=0, extra=0,
max_num=50, max_num=50,
@@ -1370,6 +1376,8 @@ class CaseDetailForm(ModelForm):
fields = [ fields = [
"redact_history", "redact_history",
"override_history", "override_history",
"learner_comment",
"learner_review_comment",
"question_time_limit_override", "question_time_limit_override",
"answer_entry_grace_period_override", "answer_entry_grace_period_override",
] ]
@@ -1443,6 +1451,11 @@ class CaseDetailForm(ModelForm):
HTML(history_block), HTML(history_block),
Field("override_history"), Field("override_history"),
Field("redact_history"), Field("redact_history"),
Fieldset(
"Learner Comments",
Field("learner_comment"),
Field("learner_review_comment"),
),
Fieldset( Fieldset(
"Case Timing Overrides", "Case Timing Overrides",
Field("question_time_limit_override"), Field("question_time_limit_override"),
@@ -0,0 +1,28 @@
# Generated by Django 6.0.1 on 2026-06-01 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0105_case_study_date'),
]
operations = [
migrations.AddField(
model_name='casedetail',
name='learner_comment',
field=models.TextField(blank=True, default='', help_text='Comment shown to the learner while taking the case and on review.'),
),
migrations.AddField(
model_name='casedetail',
name='learner_review_comment',
field=models.TextField(blank=True, default='', help_text='Comment shown to the learner only on review.'),
),
migrations.AddField(
model_name='casedetail',
name='series_visibility_config',
field=models.JSONField(blank=True, default=dict, help_text='Per-series visibility map for this case in this collection.'),
),
]
+80 -4
View File
@@ -2217,6 +2217,29 @@ class CaseDetail(models.Model):
help_text="Optional per-case override for additional answer-only time in seconds after case view lock.", help_text="Optional per-case override for additional answer-only time in seconds after case view lock.",
) )
class SeriesVisibility(models.TextChoices):
ALWAYS = "AL", _("Show all the time")
REVIEW = "RE", _("Show on review")
HIDDEN = "NO", _("Do not show")
series_visibility_config = models.JSONField(
default=dict,
blank=True,
help_text="Per-series visibility map for this case in this collection.",
)
learner_comment = models.TextField(
blank=True,
default="",
help_text="Comment shown to the learner while taking the case and on review.",
)
learner_review_comment = models.TextField(
blank=True,
default="",
help_text="Comment shown to the learner only on review.",
)
class Meta: class Meta:
ordering = ("sort_order",) ordering = ("sort_order",)
@@ -2270,23 +2293,72 @@ class CaseDetail(models.Model):
return self.override_history return self.override_history
return self.case.history or "No history provided" return self.case.history or "No history provided"
def get_case_series_nested(self, include_priors=True): def get_series_visibility_map(self) -> dict[str, str]:
case_series_images = self.case.get_series_images_nested(as_json=False) raw_map = self.series_visibility_config or {}
if not isinstance(raw_map, dict):
return {}
valid_values = {
self.SeriesVisibility.ALWAYS,
self.SeriesVisibility.REVIEW,
self.SeriesVisibility.HIDDEN,
}
cleaned = {}
for key, value in raw_map.items():
key_str = str(key)
value_str = str(value)
if value_str in valid_values:
cleaned[key_str] = value_str
return cleaned
def get_series_visibility_for(self, series, review_mode: bool = False) -> str:
visibility = self.get_series_visibility_map().get(
str(series.pk), self.SeriesVisibility.ALWAYS
)
if visibility == self.SeriesVisibility.REVIEW and not review_mode:
return self.SeriesVisibility.HIDDEN
return visibility
def get_visible_case_series(self, review_mode: bool = False):
visible = []
for series in self.case.get_ordered_series():
visibility = self.get_series_visibility_for(series, review_mode=review_mode)
if visibility != self.SeriesVisibility.HIDDEN:
visible.append(series)
return visible
def get_case_series_nested(self, include_priors=True, review_mode: bool = False):
case_series_images = []
for series in self.get_visible_case_series(review_mode=review_mode):
case_series_images.append([
image.image.url
for image in series.images.filter(removed=False)
if image.image
])
if include_priors: if include_priors:
logger.debug(f"Checking for prior cases for case {self.case}") logger.debug(f"Checking for prior cases for case {self.case}")
logger.debug(f"Found {self.case.prior_case.count()} prior cases for case {self.case}") logger.debug(f"Found {self.case.prior_case.count()} prior cases for case {self.case}")
for prior in self.caseprior_set.all(): for prior in self.caseprior_set.all():
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
continue
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review_mode:
continue
logger.debug(f"Adding prior case {prior.prior_case} to case {self.case}") logger.debug(f"Adding prior case {prior.prior_case} to case {self.case}")
case_series_images.extend(prior.prior_case.get_series_images_nested(as_json=False)) case_series_images.extend(prior.prior_case.get_series_images_nested(as_json=False))
return json.dumps(case_series_images) return json.dumps(case_series_images)
def get_case_named_stacks(self, include_priors=True): def get_case_named_stacks(self, include_priors=True, review_mode: bool = False):
def build_stacks_for(case_obj, prefix=None): def build_stacks_for(case_obj, prefix=None):
stacks = [] stacks = []
for series in case_obj.get_ordered_series(): if case_obj == self.case:
series_iterable = self.get_visible_case_series(review_mode=review_mode)
else:
series_iterable = case_obj.get_ordered_series()
for series in series_iterable:
series_images = list(series.get_images()) series_images = list(series.get_images())
series_images_with_urls = [(img, f"{REMOTE_URL}{img.image.url}") for img in series_images] series_images_with_urls = [(img, f"{REMOTE_URL}{img.image.url}") for img in series_images]
images = [url for _, url in series_images_with_urls] images = [url for _, url in series_images_with_urls]
@@ -2316,6 +2388,10 @@ class CaseDetail(models.Model):
# include priors as separate entries # include priors as separate entries
if include_priors: if include_priors:
for prior in self.caseprior_set.all(): for prior in self.caseprior_set.all():
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
continue
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review_mode:
continue
prior_case = prior.prior_case prior_case = prior.prior_case
results.append( results.append(
{ {
@@ -14,13 +14,54 @@
<form method="POST" class="post-form"> <form method="POST" class="post-form">
{% csrf_token %} {% csrf_token %}
{% crispy form form.helper %} {% crispy form form.helper %}
<div class="card mb-3">
<div class="card-header">
<strong>Series Visibility</strong>
</div>
<div class="card-body">
<p class="small text-muted mb-3">Choose how each series is shown for this case in this collection.</p>
{% if series_visibility_rows %}
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Series</th>
<th>Visibility</th>
</tr>
</thead>
<tbody>
{% for row in series_visibility_rows %}
<tr>
<td>
<div class="fw-semibold">Series {{ forloop.counter }}</div>
<div class="small text-muted">{{ row.series }}</div>
</td>
<td>
<select class="form-select form-select-sm" name="series_visibility_{{ row.series.pk }}">
{% for value, label in visibility_choices %}
<option value="{{ value }}" {% if row.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-muted">No series linked to this case.</div>
{% endif %}
</div>
</div>
<button type="submit" value="answer" name="submit" class="btn btn-primary">Save Changes</button> <button type="submit" value="answer" name="submit" class="btn btn-primary">Save Changes</button>
</form> </form>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script> <script>
</script> </script>
<style></style> <style></style>
{% endblock %} {% endblock %}
@@ -101,6 +101,18 @@
</div> </div>
{% endif %} {% endif %}
{% if casedetail.learner_comment %}
<div class="alert alert-info small">
<strong>Collection note:</strong> {{ casedetail.learner_comment|linebreaks }}
</div>
{% endif %}
{% if question_completed and casedetail.learner_review_comment %}
<div class="alert alert-secondary small">
<strong>Review note:</strong> {{ casedetail.learner_review_comment|linebreaks }}
</div>
{% endif %}
{% if collection.show_ohif_viewer or collection.show_ohif_viewer_link or collection.show_built_in_viewer %} {% if collection.show_ohif_viewer or collection.show_ohif_viewer_link or collection.show_built_in_viewer %}
<div id="viewer-panel" class="mb-3"> <div id="viewer-panel" class="mb-3">
{% if collection.show_built_in_viewer %} {% if collection.show_built_in_viewer %}
@@ -118,7 +130,7 @@
<div id="viewer-container" class="viewer-resizable-container"> <div id="viewer-container" class="viewer-resizable-container">
<div id="main_viewer" class="dicom-viewer-root viewer-frame-standard" <div id="main_viewer" class="dicom-viewer-root viewer-frame-standard"
data-auto-cache-stack="false" data-auto-cache-stack="false"
data-named-stacks='{{casedetail.get_case_named_stacks}}' data-named-stacks='{{ named_stacks_json }}'
></div> ></div>
</div> </div>
<div id="viewer-resize-handle" class="viewer-resize-handle" title="Drag to resize viewer"> <div id="viewer-resize-handle" class="viewer-resize-handle" title="Drag to resize viewer">
@@ -4,6 +4,16 @@
{% block content %} {% block content %}
<h2>Case: {{ case.title }}</h2> <h2>Case: {{ case.title }}</h2>
{% if casedetail.learner_comment %}
<div class="alert alert-info small">
<strong>Collection note:</strong> {{ casedetail.learner_comment|linebreaks }}
</div>
{% endif %}
{% if casedetail.learner_review_comment %}
<div class="alert alert-secondary small">
<strong>Review note:</strong> {{ casedetail.learner_review_comment|linebreaks }}
</div>
{% endif %}
{{ casedetail.question_schema }} {{ casedetail.question_schema }}
@@ -56,7 +56,7 @@
<details class="series-detail"> <details class="series-detail">
<summary class="fw-semibold">Series</summary> <summary class="fw-semibold">Series</summary>
<div class="mt-2 d-flex flex-column gap-2"> <div class="mt-2 d-flex flex-column gap-2">
{% for series in case.prefetched_series %} {% for series in case.visible_prefetched_series %}
<div class="border rounded p-2 bg-body-tertiary"> <div class="border rounded p-2 bg-body-tertiary">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2"> <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
<div class="small"> <div class="small">
@@ -375,6 +375,11 @@
$('#open-viewer-local').click(function() { $('#open-viewer-local').click(function() {
$("#open-viewer-local").removeClass("flash-button"); $("#open-viewer-local").removeClass("flash-button");
viewerConnected = false; viewerConnected = false;
try {
localStorage.removeItem('rad_local_viewer_popout_state');
} catch (e) {
console.warn('Unable to clear previous viewer state', e);
}
const opened = openSecondaryWindow("{% url 'atlas:collection_viewer_local_progressive' %}"); const opened = openSecondaryWindow("{% url 'atlas:collection_viewer_local_progressive' %}");
if (!opened) { if (!opened) {
showUserMessage('Unable to open viewer window. Please allow pop-ups for this site and try again.', 'danger'); showUserMessage('Unable to open viewer window. Please allow pop-ups for this site and try again.', 'danger');
@@ -16,6 +16,14 @@
</div> </div>
<p class="card-text small text-muted mb-2">Relation: <span class="fw-medium">{{ relation }}</span></p> <p class="card-text small text-muted mb-2">Relation: <span class="fw-medium">{{ relation }}</span></p>
<p class="card-text small text-muted mb-2">
Study date:
{% if case.study_date %}
<span class="fw-medium">{{ case.study_date }}</span>
{% else %}
<span class="fw-medium">Unknown</span>
{% endif %}
</p>
<div class="mb-3 small">{{case.get_series_blocks|safe}}</div> <div class="mb-3 small">{{case.get_series_blocks|safe}}</div>
@@ -37,6 +45,12 @@
<div class="col-12"> <div class="col-12">
<input class="form-control form-control-sm" type="text" name="relation" value="{{relation}}" placeholder="Relation to case" /> <input class="form-control form-control-sm" type="text" name="relation" value="{{relation}}" placeholder="Relation to case" />
</div> </div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="use_study_date_relation" value="1" id="prior_use_date_{{case.id}}" />
<label class="form-check-label small" for="prior_use_date_{{case.id}}">Use study dates to generate relation text</label>
</div>
</div>
<div class="col-8"> <div class="col-8">
<select class="form-select form-select-sm" name="prior_visibility" id="prior_visibility_{{case.id}}"> <select class="form-select form-select-sm" name="prior_visibility" id="prior_visibility_{{case.id}}">
<option value="AL" {% if visibility == 'AL' %}selected{% endif %}>Always</option> <option value="AL" {% if visibility == 'AL' %}selected{% endif %}>Always</option>
@@ -175,10 +175,9 @@
} }
} }
var savedPayload = readPersistedViewerPayload(); // Intentionally do not auto-load persisted payload on startup.
if (savedPayload) { // The local viva launcher should open to an empty state and wait
applyViewerPayload(savedPayload); // for an explicit "open" message from the controller page.
}
window.addEventListener('storage', function (event) { window.addEventListener('storage', function (event) {
if (event.key !== POPOUT_STATE_KEY || !event.newValue) { if (event.key !== POPOUT_STATE_KEY || !event.newValue) {
+166 -57
View File
@@ -5798,8 +5798,15 @@ def collection_viva_local(request, pk):
for casedetail in casedetails: for casedetail in casedetails:
case = casedetail.case case = casedetail.case
prefetched_series = getattr(case, "prefetched_series", []) prefetched_series = getattr(case, "prefetched_series", [])
visible_prefetched_series = []
images_nested = [] images_nested = []
for series in prefetched_series: for series in prefetched_series:
if (
casedetail.get_series_visibility_for(series, review_mode=False)
== CaseDetail.SeriesVisibility.HIDDEN
):
continue
visible_prefetched_series.append(series)
prefetched_images = getattr(series, "prefetched_images", []) prefetched_images = getattr(series, "prefetched_images", [])
images_nested.append([ images_nested.append([
img.image.url img.image.url
@@ -5816,6 +5823,7 @@ def collection_viva_local(request, pk):
"report": case.report, "report": case.report,
} }
) )
case.visible_prefetched_series = visible_prefetched_series
case.series_images_nested_json = json.dumps(images_nested) case.series_images_nested_json = json.dumps(images_nested)
return render( return render(
@@ -6949,6 +6957,23 @@ def collection_case_priors(request, exam_id, case_number):
except Exception: except Exception:
raise Http404("Case not found in collection") raise Http404("Case not found in collection")
def _render_prior_card_response(prior_case, added, relation, visibility, error=None):
html = render_to_string(
"atlas/partials/_prior_card.html",
{
"case": prior_case,
"added": added,
"relation": relation,
"visibility": visibility,
"casedetail": casedetail,
"collection": collection,
"case_number": case_number,
"error": error,
},
request=request,
)
return HttpResponse(html)
if request.htmx: if request.htmx:
# Ensure we can render the updated single-card partial and return it so HTMX # Ensure we can render the updated single-card partial and return it so HTMX
# can swap the card on the client side. # can swap the card on the client side.
@@ -6962,72 +6987,79 @@ def collection_case_priors(request, exam_id, case_number):
# Already removed; continue and render the not-added card # Already removed; continue and render the not-added card
pass pass
prior_case = Case.objects.get(pk=prior_pk) prior_case = Case.objects.filter(pk=prior_pk).first()
if prior_case is None:
return HttpResponse("Prior case not found", status=404)
added = False added = False
relation = "" relation = ""
visibility = "AL" visibility = "AL"
html = render_to_string( return _render_prior_card_response(
"atlas/partials/_prior_card.html", prior_case,
{ added=added,
"case": prior_case, relation=relation,
"added": added, visibility=visibility,
"relation": relation,
"visibility": visibility,
"casedetail": casedetail,
"collection": collection,
},
request=request,
) )
return HttpResponse(html)
elif "prior_case_id" in request.POST: elif "prior_case_id" in request.POST:
if not request.POST.get("relation"): prior_case = Case.objects.filter(pk=request.POST.get("prior_case_id")).first()
if prior_case is None:
return HttpResponse("Prior case not found", status=404)
visibility = request.POST.get("prior_visibility", "AL")
if visibility not in {
CasePrior.PriorVisibility.ALWAYS,
CasePrior.PriorVisibility.REVIEW,
CasePrior.PriorVisibility.NONE,
}:
visibility = CasePrior.PriorVisibility.ALWAYS
relation = (request.POST.get("relation") or "").strip()
use_study_date_relation = request.POST.get("use_study_date_relation") == "1"
if use_study_date_relation:
current_date = casedetail.case.study_date
prior_date = prior_case.study_date
if current_date and prior_date:
relative = _format_relative_date_offset((prior_date - current_date).days)
relation = f"Study date {relative['text']} relative to the index case"
else:
return _render_prior_card_response(
prior_case,
added=False,
relation=relation,
visibility=visibility,
error="Study-date relation requires both this case and the prior case to have study dates.",
)
if not relation:
# Return the card partial with an inline error so HTMX will replace # Return the card partial with an inline error so HTMX will replace
# the card and display the validation message rather than navigating # the card and display the validation message rather than navigating
# to a new page or showing a plain text response. # to a new page or showing a plain text response.
prior_case = Case.objects.get(pk=request.POST.get("prior_case_id")) return _render_prior_card_response(
added = False prior_case,
relation = request.POST.get("relation", "") added=False,
visibility = request.POST.get("prior_visibility", "AL") relation=relation,
html = render_to_string( visibility=visibility,
"atlas/partials/_prior_card.html", error="Enter relation text, or use study-date relation.",
{
"case": prior_case,
"added": added,
"relation": relation,
"visibility": visibility,
"casedetail": casedetail,
"collection": collection,
"error": "You need to enter text to describe the relationship between the cases",
},
request=request,
) )
return HttpResponse(html)
prior_case = Case.objects.get(pk=request.POST["prior_case_id"])
p, created = CasePrior.objects.get_or_create( p, created = CasePrior.objects.get_or_create(
casedetail=casedetail, prior_case=prior_case casedetail=casedetail, prior_case=prior_case
) )
p.relation_text = request.POST.get("relation", "") p.relation_text = relation
p.prior_visibility = request.POST.get("prior_visibility", "AL") p.prior_visibility = visibility
p.save() p.save()
added = True added = True
relation = p.relation_text relation = p.relation_text
visibility = p.prior_visibility visibility = p.prior_visibility
html = render_to_string( return _render_prior_card_response(
"atlas/partials/_prior_card.html", prior_case,
{ added=added,
"case": prior_case, relation=relation,
"added": added, visibility=visibility,
"relation": relation,
"visibility": visibility,
"casedetail": casedetail,
"collection": collection,
},
request=request,
) )
return HttpResponse(html)
else: else:
return HttpResponse("False", status=400) return HttpResponse("False", status=400)
@@ -7212,10 +7244,35 @@ def collection_case_details(request, exam_id, case_number):
except Exception: except Exception:
raise Http404("Case not found in collection") raise Http404("Case not found in collection")
ordered_series = list(casedetail.case.get_ordered_series())
visibility_map = casedetail.get_series_visibility_map()
visibility_choices = [
(CaseDetail.SeriesVisibility.ALWAYS, "Show all the time"),
(CaseDetail.SeriesVisibility.REVIEW, "Show on review"),
(CaseDetail.SeriesVisibility.HIDDEN, "Do not show"),
]
if request.method == "POST": if request.method == "POST":
form = CaseDetailForm(request.POST, instance=casedetail) form = CaseDetailForm(request.POST, instance=casedetail)
if form.is_valid(): if form.is_valid():
form.save() casedetail = form.save(commit=False)
series_visibility_config = {}
for series in ordered_series:
field_name = f"series_visibility_{series.pk}"
visibility_value = request.POST.get(
field_name, CaseDetail.SeriesVisibility.ALWAYS
)
if visibility_value not in {
CaseDetail.SeriesVisibility.ALWAYS,
CaseDetail.SeriesVisibility.REVIEW,
CaseDetail.SeriesVisibility.HIDDEN,
}:
visibility_value = CaseDetail.SeriesVisibility.ALWAYS
series_visibility_config[str(series.pk)] = visibility_value
casedetail.series_visibility_config = series_visibility_config
casedetail.save()
if request.htmx: if request.htmx:
return HttpResponse("Saved") return HttpResponse("Saved")
return redirect(request.path) return redirect(request.path)
@@ -7225,6 +7282,17 @@ def collection_case_details(request, exam_id, case_number):
else: else:
form = CaseDetailForm(instance=casedetail) form = CaseDetailForm(instance=casedetail)
series_visibility_rows = []
for series in ordered_series:
series_visibility_rows.append(
{
"series": series,
"value": visibility_map.get(
str(series.pk), CaseDetail.SeriesVisibility.ALWAYS
),
}
)
case_number, case_count = collection.get_index_of_case( case_number, case_count = collection.get_index_of_case(
casedetail.case, case_count=True casedetail.case, case_count=True
) )
@@ -7238,6 +7306,8 @@ def collection_case_details(request, exam_id, case_number):
{ {
"casedetail": casedetail, "casedetail": casedetail,
"form": form, "form": form,
"visibility_choices": visibility_choices,
"series_visibility_rows": series_visibility_rows,
"collection": collection, "collection": collection,
"case": casedetail.case, "case": casedetail.case,
"previous": previous, "previous": previous,
@@ -7294,9 +7364,10 @@ def collection_mark_question(request, pk, case_number):
previous = case_number > 0 previous = case_number > 0
next = case_number < (case_count - 1) next = case_number < (case_count - 1)
casedetail = CaseDetail.objects.filter(case=case, collection=collection) casedetail_qs = CaseDetail.objects.filter(case=case, collection=collection)
casedetail = casedetail_qs.first()
answers = CidReportAnswer.objects.filter(question__in=casedetail) answers = CidReportAnswer.objects.filter(question__in=casedetail_qs)
answer_forms = [] answer_forms = []
if request.method == "POST": if request.method == "POST":
@@ -7339,7 +7410,7 @@ def collection_mark_question(request, pk, case_number):
form = CidReportAnswerMarkForm(instance=answer, prefix=answer.pk) form = CidReportAnswerMarkForm(instance=answer, prefix=answer.pk)
answer_forms.append((answer, form)) answer_forms.append((answer, form))
series_list = case.series.all().prefetch_related("images", "examination", "plane") series_list = casedetail.get_visible_case_series(review_mode=True) if casedetail else []
return render( return render(
request, request,
@@ -8212,8 +8283,6 @@ def collection_case_view_take(
if collection.collection_type in ("REP", "QUE"): if collection.collection_type in ("REP", "QUE"):
form = ReportAnswerForm(instance=answer, casedetail=casedetail) form = ReportAnswerForm(instance=answer, casedetail=casedetail)
series_list = case.series.all().prefetch_related("images", "examination", "plane")
previous = case_number > 0 previous = case_number > 0
next = case_number < (case_count - 1) next = case_number < (case_count - 1)
@@ -8249,6 +8318,8 @@ def collection_case_view_take(
resources = case.caseresource_set.filter(pre_review=True) resources = case.caseresource_set.filter(pre_review=True)
series_list = casedetail.get_visible_case_series(review_mode=question_completed)
prior_cases = casedetail.caseprior_set.all() prior_cases = casedetail.caseprior_set.all()
series_to_load = [] series_to_load = []
@@ -8293,6 +8364,9 @@ def collection_case_view_take(
"case": case, "case": case,
"casedetail": casedetail, "casedetail": casedetail,
"series_list": series_list, "series_list": series_list,
"named_stacks_json": casedetail.get_case_named_stacks(
review_mode=question_completed
),
"series_to_load": series_to_load, "series_to_load": series_to_load,
"has_priors": has_priors, "has_priors": has_priors,
"prior_count": prior_count, "prior_count": prior_count,
@@ -8437,12 +8511,47 @@ def collection_case_dicom_json(request, exam_id, case_number, review=False):
except Exception: except Exception:
raise Http404("Case not found in collection") raise Http404("Case not found in collection")
if review: main_series = casedetail.get_visible_case_series(review_mode=review)
priors = casedetail.caseprior_set.exclude(prior_visibility="NO")
else:
priors = casedetail.caseprior_set.exclude(prior_visibility__in=["NO", "RE"])
return JsonResponse(casedetail.case.get_case_dicom_json(priors=priors)) studies_json = {
"studies": [
{
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178",
"StudyTime": "",
"PatientName": casedetail.case.title,
"PatientID": "LIDC-IDRI-0001",
"AccessionNumber": "",
"PatientAge": "",
"PatientSex": "",
"series": [series.get_series_dicom_json() for series in main_series],
}
]
}
prior_qs = casedetail.caseprior_set.all().select_related("prior_case")
for prior in prior_qs:
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
continue
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review:
continue
studies_json["studies"].append(
{
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630177",
"StudyTime": "",
"PatientName": casedetail.case.title,
"PatientID": "LIDC-IDRI-0001",
"AccessionNumber": "",
"PatientAge": "",
"PatientSex": "",
"series": [
series.get_series_dicom_json()
for series in prior.prior_case.get_ordered_series()
],
"StudyDescription": f"Prior: {prior.relation_text}",
}
)
return JsonResponse(studies_json)
def case_dicom_json(request, pk): def case_dicom_json(request, pk):