Skip to content

Commit a64deb9

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

3 files changed

Lines changed: 133 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: 98 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,23 @@ 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+
linked_task=linked_task,
2454+
)
2455+
else:
2456+
return self.create_civ_for_image(
2457+
ci=ci,
2458+
current_civ=current_civ,
2459+
user=user,
2460+
image=civ_data.image,
2461+
upload_session=civ_data.upload_session,
2462+
user_upload_queryset=civ_data.user_upload_queryset,
2463+
linked_task=linked_task,
2464+
)
24532465
elif ci.super_kind == ci.SuperKind.FILE:
24542466
return self.create_civ_for_file(
24552467
ci=ci,
@@ -2567,6 +2579,77 @@ def create_civ_for_image( # noqa: C901
25672579
),
25682580
)
25692581

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

0 commit comments

Comments
 (0)