diff --git a/atlas/templates/atlas/partials/_series_split_by_tag.html b/atlas/templates/atlas/partials/_series_split_by_tag.html index 19b3f719..3dc3bb5e 100644 --- a/atlas/templates/atlas/partials/_series_split_by_tag.html +++ b/atlas/templates/atlas/partials/_series_split_by_tag.html @@ -1,17 +1,41 @@ {% load django_htmx %} -
- {% csrf_token %} -
- - +
+
+
- -
+ + {% if analyze_requested %} + {% if split_options %} +
+ {% csrf_token %} +
+ + +
+
+

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