Skip to content

Commit cc5d3cc

Browse files
committed
Add CIVForObjectMixin.create_civ_for_dicom_image
1 parent 356517a commit cc5d3cc

3 files changed

Lines changed: 135 additions & 15 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.25 on 2025-10-09 18:35
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("cases", "0020_remove_dicomimageset_image_frame_ids_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="dicomimagesetupload",
15+
name="task_on_success",
16+
field=models.JSONField(
17+
default=None,
18+
editable=False,
19+
help_text="Serialized task that is run on job success",
20+
null=True,
21+
),
22+
),
23+
]

app/grandchallenge/cases/models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from actstream.actions import follow
1212
from actstream.models import Follow
1313
from botocore.exceptions import ClientError
14+
from celery import signature
1415
from django.conf import settings
1516
from django.contrib.contenttypes.models import ContentType
1617
from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation
@@ -1063,6 +1064,12 @@ class DICOMImageSetUpload(UUIDModel):
10631064
name = models.CharField(
10641065
max_length=255, help_text="The name for the resulting Image instance"
10651066
)
1067+
task_on_success = models.JSONField(
1068+
default=None,
1069+
null=True,
1070+
editable=False,
1071+
help_text="Serialized task that is run on job success",
1072+
)
10661073

10671074
class Meta:
10681075
verbose_name = "DICOM image set upload"
@@ -1287,6 +1294,7 @@ def handle_event(self, *, event):
12871294
else:
12881295
self.status = self.DICOMImageSetUploadStatusChoices.COMPLETED
12891296
self.save()
1297+
self.execute_task_on_success()
12901298
finally:
12911299
self.delete_input_files()
12921300

@@ -1374,3 +1382,7 @@ def convert_image_set_to_internal(self, *, image_set_id):
13741382
image = Image(dicom_image_set=dicom_image_set, name=self.name)
13751383
image.full_clean()
13761384
image.save()
1385+
1386+
def execute_task_on_success(self):
1387+
if self.task_on_success:
1388+
on_commit(signature(self.task_on_success).apply_async)

app/grandchallenge/components/models.py

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636
from django_extensions.db.fields import AutoSlugField
3737
from panimg.models import MAXIMUM_SEGMENTS_LENGTH
3838

39-
from grandchallenge.cases.models import Image, ImageFile, RawImageUploadSession
39+
from grandchallenge.cases.models import (
40+
DICOMImageSetUpload,
41+
Image,
42+
ImageFile,
43+
RawImageUploadSession,
44+
)
4045
from grandchallenge.charts.specs import components_line
4146
from grandchallenge.components.backends.exceptions import (
4247
CINotAllowedException,
@@ -2241,8 +2246,8 @@ def file_civ(self):
22412246
return self._file_civ
22422247

22432248
@property
2244-
def image_name(self):
2245-
return self._image_name
2249+
def dicom_upload_with_name(self):
2250+
return self._dicom_upload_with_name
22462251

22472252
def __init__(self, *, interface_slug, value):
22482253
self._interface_slug = interface_slug
@@ -2253,7 +2258,7 @@ def __init__(self, *, interface_slug, value):
22532258
self._user_upload = None
22542259
self._user_upload_queryset = None
22552260
self._file_civ = None
2256-
self._image_name = None
2261+
self._dicom_upload_with_name = None
22572262

22582263
ci = ComponentInterface.objects.get(slug=interface_slug)
22592264

@@ -2288,8 +2293,7 @@ def _init_dicom_civ_data(self):
22882293
from grandchallenge.cases.widgets import DICOMUploadWithName
22892294

22902295
if isinstance(self._initial_value, DICOMUploadWithName):
2291-
self._user_upload_queryset = self._initial_value.user_uploads
2292-
self._image_name = self._initial_value.name
2296+
self._dicom_upload_with_name = self._initial_value
22932297
else:
22942298
ValidationError(
22952299
f"Unknown data type {type(self._initial_value)} for interface {self._interface_slug}"
@@ -2441,15 +2445,25 @@ def create_civ(self, *, civ_data, user=None, linked_task=None):
24412445
linked_task=linked_task,
24422446
)
24432447
elif ci.super_kind == ci.SuperKind.IMAGE:
2444-
return self.create_civ_for_image(
2445-
ci=ci,
2446-
current_civ=current_civ,
2447-
user=user,
2448-
image=civ_data.image,
2449-
upload_session=civ_data.upload_session,
2450-
user_upload_queryset=civ_data.user_upload_queryset,
2451-
linked_task=linked_task,
2452-
)
2448+
if ci.is_dicom_image_kind:
2449+
return self.create_civ_for_dicom_image(
2450+
ci=ci,
2451+
current_civ=current_civ,
2452+
user=user,
2453+
image=civ_data.image,
2454+
dicom_upload_with_name=civ_data.dicom_upload_with_name,
2455+
linked_task=linked_task,
2456+
)
2457+
else:
2458+
return self.create_civ_for_image(
2459+
ci=ci,
2460+
current_civ=current_civ,
2461+
user=user,
2462+
image=civ_data.image,
2463+
upload_session=civ_data.upload_session,
2464+
user_upload_queryset=civ_data.user_upload_queryset,
2465+
linked_task=linked_task,
2466+
)
24532467
elif ci.super_kind == ci.SuperKind.FILE:
24542468
return self.create_civ_for_file(
24552469
ci=ci,
@@ -2567,6 +2581,77 @@ def create_civ_for_image( # noqa: C901
25672581
),
25682582
)
25692583

2584+
def create_civ_for_dicom_image(
2585+
self,
2586+
ci,
2587+
current_civ,
2588+
user=None,
2589+
image=None,
2590+
dicom_upload_with_name=None,
2591+
linked_task=None,
2592+
):
2593+
current_image = current_civ.image if current_civ else None
2594+
2595+
if image and current_image != image:
2596+
civ, created = ComponentInterfaceValue.objects.get_first_or_create(
2597+
interface=ci, image=image
2598+
)
2599+
2600+
if created:
2601+
try:
2602+
civ.full_clean()
2603+
except ValidationError as e:
2604+
civ.delete()
2605+
error_handler = self.get_error_handler()
2606+
error_handler.handle_error(
2607+
interface=ci,
2608+
error_message=format_validation_error_message(error=e),
2609+
user=user,
2610+
)
2611+
return
2612+
2613+
self.remove_civ(civ=current_civ)
2614+
self.add_civ(civ=civ)
2615+
2616+
if linked_task is not None:
2617+
on_commit(signature(linked_task).apply_async)
2618+
2619+
elif dicom_upload_with_name:
2620+
from grandchallenge.cases.tasks import (
2621+
import_dicom_to_health_imaging,
2622+
)
2623+
from grandchallenge.components.tasks import (
2624+
add_dicom_image_set_to_object,
2625+
)
2626+
2627+
if not user:
2628+
raise RuntimeError(
2629+
f"You need to provide a user along with the user upload "
2630+
f"queryset for interface {ci}"
2631+
)
2632+
upload = DICOMImageSetUpload(
2633+
creator=user, name=dicom_upload_with_name.name
2634+
)
2635+
upload.task_on_success = add_dicom_image_set_to_object.signature(
2636+
kwargs={
2637+
"app_label": self._meta.app_label,
2638+
"model_name": self._meta.model_name,
2639+
"object_pk": self.pk,
2640+
"interface_pk": str(ci.pk),
2641+
"dicom_image_set_upload_pk": upload.pk,
2642+
"linked_task": linked_task,
2643+
},
2644+
)
2645+
upload.full_clean()
2646+
upload.save()
2647+
upload.user_uploads.set(dicom_upload_with_name.user_uploads)
2648+
2649+
on_commit(
2650+
import_dicom_to_health_imaging.signature(
2651+
kwargs={"dicom_imageset_upload_pk": upload.pk}
2652+
).apply_async
2653+
)
2654+
25702655
def create_civ_for_file(
25712656
self,
25722657
*,

0 commit comments

Comments
 (0)