Repository: comic/grand-challenge.org

Issue:
Deprecate POLYGON_IMAGE and MULTIPLE_POLYGONS_IMAGE question types
Use the mask question types as alternatives.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/algorithms/tasks.py b/app/grandchallenge/algorithms/tasks.py
--- a/app/grandchallenge/algorithms/tasks.py
+++ b/app/grandchallenge/algorithms/tasks.py
@@ -84,7 +84,7 @@ def on_job_creation_error(self, task_id, *args, **kwargs):
         error_message += str(res)
 
     job.update_status(
-        status=job.FAILURE, error_message=error_message,
+        status=job.CANCELLED, error_message=error_message,
     )
 
     on_commit(linked_task.apply_async)
@@ -104,7 +104,7 @@ def execute_algorithm_job_for_inputs(*, job_pk):
     missing_inputs = list(civ for civ in job.inputs.all() if not civ.has_value)
     if missing_inputs:
         job.update_status(
-            status=job.FAILURE,
+            status=job.CANCELLED,
             error_message=(
                 f"Job can't be started, input is missing for "
                 f"{oxford_comma([c.interface.title for c in missing_inputs])}"
diff --git a/app/grandchallenge/reader_studies/forms.py b/app/grandchallenge/reader_studies/forms.py
--- a/app/grandchallenge/reader_studies/forms.py
+++ b/app/grandchallenge/reader_studies/forms.py
@@ -38,8 +38,10 @@
 from grandchallenge.groups.forms import UserGroupForm
 from grandchallenge.reader_studies.models import (
     Answer,
+    AnswerType,
     CASE_TEXT_SCHEMA,
     CategoricalOption,
+    DEPRECATED_ANSWER_TYPES,
     HANGING_LIST_SCHEMA,
     Question,
     ReaderStudy,
@@ -235,6 +237,13 @@ def clean(self, *args, **kwargs):
 class QuestionForm(SaveFormInitMixin, ModelForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
+
+        self.fields["answer_type"].choices = [
+            a
+            for a in AnswerType.choices
+            if a[0] not in DEPRECATED_ANSWER_TYPES
+        ]
+
         self.helper = FormHelper()
         self.helper.form_tag = True
         self.helper.layout = Layout(
diff --git a/app/grandchallenge/reader_studies/models.py b/app/grandchallenge/reader_studies/models.py
--- a/app/grandchallenge/reader_studies/models.py
+++ b/app/grandchallenge/reader_studies/models.py
@@ -558,6 +558,10 @@ def is_valid(self):
             and len(self.non_unique_study_image_names) == 0
         )
 
+    @property
+    def has_deprecated_questions(self):
+        return any(q.is_deprecated for q in self.questions.all())
+
     @property
     def hanging_list_images(self):
         """
@@ -894,31 +898,39 @@ def delete_reader_study_groups_hook(*_, instance: ReaderStudy, using, **__):
         pass
 
 
+class AnswerType(models.TextChoices):
+    # WARNING: Do not change the display text, these are used in the front end
+    SINGLE_LINE_TEXT = "STXT", "Single line text"
+    MULTI_LINE_TEXT = "MTXT", "Multi line text"
+    BOOL = "BOOL", "Bool"
+    NUMBER = "NUMB", "Number"
+    HEADING = "HEAD", "Heading"
+    BOUNDING_BOX_2D = "2DBB", "2D bounding box"
+    MULTIPLE_2D_BOUNDING_BOXES = "M2DB", "Multiple 2D bounding boxes"
+    DISTANCE_MEASUREMENT = "DIST", "Distance measurement"
+    MULTIPLE_DISTANCE_MEASUREMENTS = (
+        "MDIS",
+        "Multiple distance measurements",
+    )
+    POINT = "POIN", "Point"
+    MULTIPLE_POINTS = "MPOI", "Multiple points"
+    POLYGON = "POLY", "Polygon"
+    POLYGON_IMAGE = "PIMG", "Polygon (saved as mask)"
+    MULTIPLE_POLYGONS = "MPOL", "Multiple polygons"
+    MULTIPLE_POLYGONS_IMAGE = "MPIM", "Multiple polygons (saved as mask)"
+    CHOICE = "CHOI", "Choice"
+    MULTIPLE_CHOICE = "MCHO", "Multiple choice"
+    MULTIPLE_CHOICE_DROPDOWN = "MCHD", "Multiple choice dropdown"
+    MASK = "MASK", "Mask"
+
+
+DEPRECATED_ANSWER_TYPES = frozenset(
+    [AnswerType.POLYGON_IMAGE, AnswerType.MULTIPLE_POLYGONS_IMAGE]
+)
+
+
 class Question(UUIDModel):
-    class AnswerType(models.TextChoices):
-        # WARNING: Do not change the display text, these are used in the front end
-        SINGLE_LINE_TEXT = "STXT", "Single line text"
-        MULTI_LINE_TEXT = "MTXT", "Multi line text"
-        BOOL = "BOOL", "Bool"
-        NUMBER = "NUMB", "Number"
-        HEADING = "HEAD", "Heading"
-        BOUNDING_BOX_2D = "2DBB", "2D bounding box"
-        MULTIPLE_2D_BOUNDING_BOXES = "M2DB", "Multiple 2D bounding boxes"
-        DISTANCE_MEASUREMENT = "DIST", "Distance measurement"
-        MULTIPLE_DISTANCE_MEASUREMENTS = (
-            "MDIS",
-            "Multiple distance measurements",
-        )
-        POINT = "POIN", "Point"
-        MULTIPLE_POINTS = "MPOI", "Multiple points"
-        POLYGON = "POLY", "Polygon"
-        POLYGON_IMAGE = "PIMG", "Polygon (saved as mask)"
-        MULTIPLE_POLYGONS = "MPOL", "Multiple polygons"
-        MULTIPLE_POLYGONS_IMAGE = "MPIM", "Multiple polygons (saved as mask)"
-        CHOICE = "CHOI", "Choice"
-        MULTIPLE_CHOICE = "MCHO", "Multiple choice"
-        MULTIPLE_CHOICE_DROPDOWN = "MCHD", "Multiple choice dropdown"
-        MASK = "MASK", "Mask"
+    AnswerType = AnswerType
 
     # What is the orientation of the question form when presented on the
     # front end?
@@ -1005,6 +1017,10 @@ def is_fully_editable(self):
         """``True`` if no ``Answer`` has been given for this ``Question``."""
         return self.answer_set.count() == 0
 
+    @property
+    def is_deprecated(self):
+        return self.answer_type in DEPRECATED_ANSWER_TYPES
+
     @property
     def read_only_fields(self):
         """


Test patch:
diff --git a/app/tests/algorithms_tests/test_tasks.py b/app/tests/algorithms_tests/test_tasks.py
--- a/app/tests/algorithms_tests/test_tasks.py
+++ b/app/tests/algorithms_tests/test_tasks.py
@@ -522,7 +522,7 @@ def test_execute_algorithm_job_for_inputs(
     execute_algorithm_job_for_inputs(job_pk=job.pk)
 
     job.refresh_from_db()
-    assert job.status == Job.FAILURE
+    assert job.status == Job.CANCELLED
     assert "Job can't be started, input is missing for " in job.error_message