diff --git a/atlas/api.py b/atlas/api.py index 8782f485..08215f04 100644 --- a/atlas/api.py +++ b/atlas/api.py @@ -220,6 +220,7 @@ def import_dicoms_helper(request, case_id: int | None = None, dicoms=None): for d in dicoms.iterator(): # Use iterator() to avoid loading all at once tags = d.basic_dicom_tags series_uid = tags["SeriesInstanceUID"] + study_uid = tags.get("StudyInstanceUID") if series_uid not in series_map: # Get or create Series and related objects as needed @@ -236,6 +237,7 @@ def import_dicoms_helper(request, case_id: int | None = None, dicoms=None): defaults={ "modality": modality, "description": tags.get("SeriesDescription", ""), + "study_instance_uid": study_uid, } ) if created and tags.get("StudyDescription"): @@ -250,6 +252,10 @@ def import_dicoms_helper(request, case_id: int | None = None, dicoms=None): except IntegrityError: series = Series.objects.get(series_instance_uid=series_uid) + if study_uid and not series.study_instance_uid: + series.study_instance_uid = study_uid + series.save(update_fields=["study_instance_uid"]) + # Add author and case if needed # Attach author to the series series.author.add(request.user) diff --git a/atlas/migrations/0101_series_study_instance_uid.py b/atlas/migrations/0101_series_study_instance_uid.py new file mode 100644 index 00000000..62ee5ea5 --- /dev/null +++ b/atlas/migrations/0101_series_study_instance_uid.py @@ -0,0 +1,65 @@ +# Generated by Django 5.2.7 on 2026-05-11 08:56 + +from django.db import migrations, models +import pydicom + + +def backfill_series_study_instance_uid(apps, schema_editor): + Series = apps.get_model('atlas', 'Series') + SeriesImage = apps.get_model('atlas', 'SeriesImage') + + series_qs = Series.objects.filter(study_instance_uid__isnull=True) + + for series in series_qs.iterator(): + image_obj = ( + SeriesImage.objects.filter(series_id=series.pk, removed=False) + .exclude(image='') + .order_by('pk') + .first() + ) + if image_obj is None: + image_obj = ( + SeriesImage.objects.filter(series_id=series.pk) + .exclude(image='') + .order_by('pk') + .first() + ) + + if image_obj is None or not image_obj.image: + continue + + try: + with image_obj.image.open('rb') as fp: + ds = pydicom.dcmread(fp, stop_before_pixels=True, force=True) + value = ds.get('StudyInstanceUID') + if value is None: + continue + if hasattr(value, 'value'): + value = value.value + study_uid = str(value).strip() + if not study_uid: + continue + + Series.objects.filter(pk=series.pk).update(study_instance_uid=study_uid) + except Exception: + continue + + +def noop_reverse(apps, schema_editor): + return + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0100_casecollection_show_results_sharing_information'), + ] + + operations = [ + migrations.AddField( + model_name='series', + name='study_instance_uid', + field=models.CharField(blank=True, db_index=True, max_length=255, null=True), + ), + migrations.RunPython(backfill_series_study_instance_uid, noop_reverse), + ] diff --git a/atlas/models.py b/atlas/models.py index 1bb8b3fb..3367accd 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -1114,6 +1114,7 @@ class Series(SeriesBase): ) series_instance_uid = models.CharField(max_length=255, blank=True, null=True) + study_instance_uid = models.CharField(max_length=255, blank=True, null=True, db_index=True) # findings = models.TextField(null=True, blank=True, help_text="Findings on the series / stack") def __str__(self) -> str: diff --git a/atlas/templates/atlas/partials/_upload_import_result.html b/atlas/templates/atlas/partials/_upload_import_result.html new file mode 100644 index 00000000..8893a0c1 --- /dev/null +++ b/atlas/templates/atlas/partials/_upload_import_result.html @@ -0,0 +1,26 @@ +{% if imported_count %} +
Admin-only tool for searching uncategorised uploads and imported Atlas images by image_blake3_hash.
Admin-only tool for searching pending uploads and imported Atlas data by hash, series UID, or study UID.