-
-
+
+
+
-
-
+
+ {% if analyze_requested %}
+ {% if split_options %}
+
+
Only tags that can produce more than one output series are shown.
+ {% else %}
+
+ No common split tags were found that can divide this series into multiple groups.
+
+ {% endif %}
+ {% else %}
+
Run analysis to detect valid split tags and estimated output series count.
+ {% endif %}
+
+
+
diff --git a/atlas/views.py b/atlas/views.py
index de22d5b5..4e43c27e 100755
--- a/atlas/views.py
+++ b/atlas/views.py
@@ -10,6 +10,7 @@ from django.db import transaction
from django.shortcuts import render, get_object_or_404, redirect
from django import forms
from django.utils import timezone
+from django.utils.html import escape
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_http_methods
from django.template.loader import render_to_string
@@ -3637,6 +3638,43 @@ SERIES_SPLIT_TAGS = [
]
+def _normalise_dicom_split_value(value):
+ if isinstance(value, pydicom.multival.MultiValue):
+ return ",".join(map(str, value))
+ return str(value)
+
+
+def _analyse_series_split_options(series):
+ tags = [tag_value for tag_value, _ in SERIES_SPLIT_TAGS]
+ tag_value_sets = {tag: set() for tag in tags}
+ tag_missing = {tag: False for tag in tags}
+
+ for image in series.images.iterator():
+ ds = image.get_dicom_data()
+ for tag in tags:
+ if tag in ds:
+ tag_value_sets[tag].add(_normalise_dicom_split_value(ds[tag].value))
+ else:
+ tag_missing[tag] = True
+
+ split_options = []
+ for tag_value, tag_label in SERIES_SPLIT_TAGS:
+ if tag_missing[tag_value]:
+ continue
+ split_count = len(tag_value_sets[tag_value])
+ if split_count > 1:
+ split_options.append(
+ {
+ "tag_value": tag_value,
+ "tag_label": tag_label,
+ "split_count": split_count,
+ }
+ )
+
+ split_options.sort(key=lambda item: item["split_count"], reverse=True)
+ return split_options
+
+
@login_required
@user_is_author_or_atlas_series_checker
def series_split_by_tag(request, pk):
@@ -3645,20 +3683,27 @@ def series_split_by_tag(request, pk):
Scope: Atlas series authors and series checkers only (via decorator).
Functionality:
- - GET: renders a partial form with a dropdown of common DICOM tags.
+ - GET: renders a partial form and, on demand, analyzes available DICOM tags
+ to list only tags that can actually split the series.
- POST: performs the split. The original series retains the images matching
the first unique tag value; new series are cloned for each subsequent value.
- On success, redirects to the series detail page.
+ On success, returns an HTMX success fragment with number of series created.
On error (tag not found or non-DICOM images), returns an HTMX-swappable
error message fragment.
"""
series = get_object_or_404(Series, pk=pk)
if request.method == "GET":
+ analyze_requested = request.GET.get("analyze") == "1"
+ split_options = _analyse_series_split_options(series) if analyze_requested else []
return render(
request,
"atlas/partials/_series_split_by_tag.html",
- {"series": series, "split_tags": SERIES_SPLIT_TAGS},
+ {
+ "series": series,
+ "split_options": split_options,
+ "analyze_requested": analyze_requested,
+ },
)
dicom_tag = request.POST.get("dicom_tag", "").strip()
@@ -3679,18 +3724,14 @@ def series_split_by_tag(request, pk):
ds = image.get_dicom_data()
if dicom_tag not in ds:
return HttpResponse(
- f'
Tag {dicom_tag} not found in image {image}.
'
+ f'
Tag {escape(str(dicom_tag))} not found in image {escape(str(image))}.
'
)
- val = ds[dicom_tag].value
- if isinstance(val, pydicom.multival.MultiValue):
- key = ",".join(map(str, val))
- else:
- key = str(val)
+ key = _normalise_dicom_split_value(ds[dicom_tag].value)
image_map[key].append(image)
if len(image_map) < 2:
return HttpResponse(
- f'
Tag {dicom_tag} has only one unique value — no split performed.
'
+ f'
Tag {escape(str(dicom_tag))} has only one unique value, so no split was performed.
'
)
case = list(series.case.all())
@@ -3715,7 +3756,9 @@ def series_split_by_tag(request, pk):
dicom_tag,
new_series_pks,
)
- return redirect("atlas:series_detail", pk=new_series_pks[0])
+ return HttpResponse(
+ f'
Split complete: {len(new_series_pks)} series created from tag {escape(str(dicom_tag))}.
'
+ )
# Vendored in from BaseZipView