Repository: comic/grand-challenge.org

Issue:
Users cannot map answers to images
With the introduction of Display Sets for reader studies there have been several users who have asked how to link answers to images:

- https://github.com/DIAGNijmegen/rse-gcapi/issues/129
- https://teams.microsoft.com/l/message/19:6daf914d105b412989baaffa5b066dfa@thread.tacv2/1650973157371?tenantId=b208fe69-471e-48c4-8d87-025e9b9a157f&groupId=97a88c45-447f-4147-9e80-5e7c013f7501&parentMessageId=1650973157371&teamName=DIAG&channelName=Grand%20Challenge&createdTime=1650973157371
- https://teams.microsoft.com/l/message/19:6daf914d105b412989baaffa5b066dfa@thread.tacv2/1651756065894?tenantId=b208fe69-471e-48c4-8d87-025e9b9a157f&groupId=97a88c45-447f-4147-9e80-5e7c013f7501&parentMessageId=1651756065894&teamName=DIAG&channelName=Grand%20Challenge&createdTime=1651756065894

To solve this:

- Display sets and Images CSV files need to be added to the Reader Study export tab
- Documentation needs to be added to https://grand-challenge.org/documentation/reader-studies/


Discussion:
Do we really need to also link the images csv? Since the answers are mapped to display sets won't that be enough? Or do we need to include the images csv to include the original source file?
The display set CSV only contains links to images. The images are needed so that the user can map those links to the image name.
This will need updating https://github.com/comic/grand-challenge.org/blob/2e676876e4b8ca815eeb5fe96bf820530057dd19/app/grandchallenge/cases/filters.py#L41

Obviously there is a test missing there too as it doesn't work right now!

Resolving patch:
diff --git a/app/grandchallenge/cases/filters.py b/app/grandchallenge/cases/filters.py
--- a/app/grandchallenge/cases/filters.py
+++ b/app/grandchallenge/cases/filters.py
@@ -38,7 +38,7 @@ class ImageFilterSet(FilterSet):
         widget=Select2MultipleWidget,
         label="Reader Study",
         help_text="Filter images that belong to a reader study",
-        field_name="readerstudies__pk",
+        field_name="componentinterfacevalue__display_sets__reader_study__pk",
         to_field_name="pk",
     )
     patient_id__isempty = EmptyStringFilter(field_name="patient_id")
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -270,7 +270,18 @@ def _reader_study_export_context(self):
                 ).count(),
                 limit,
             ),
-            "image_offsets": range(0, self.object.display_sets.count(), limit),
+            "display_set_offsets": range(
+                0, self.object.display_sets.count(), limit
+            ),
+            "image_offsets": range(
+                0,
+                Image.objects.filter(
+                    componentinterfacevalue__display_sets__reader_study=self.object
+                )
+                .distinct()
+                .count(),
+                limit,
+            ),
         }
 
 


Test patch:
diff --git a/app/tests/cases_tests/test_api.py b/app/tests/cases_tests/test_api.py
--- a/app/tests/cases_tests/test_api.py
+++ b/app/tests/cases_tests/test_api.py
@@ -178,7 +178,7 @@ def test_upload_session_post_permissions(client, is_active, expected_response):
 
 
 @pytest.mark.django_db
-def test_filter_images_api_view(client):
+def test_filter_origin_images_api_view(client):
     alg = AlgorithmFactory()
     user = UserFactory()
     alg.add_editor(user=user)
@@ -212,6 +212,46 @@ def test_filter_images_api_view(client):
     assert response.json()["results"][0]["pk"] == str(im.pk)
 
 
+@pytest.mark.django_db
+def test_filter_reader_study_images_api_view(client):
+    rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory()
+    user = UserFactory()
+    rs1.add_editor(user)
+    rs2.add_editor(user)
+
+    ds1 = DisplaySetFactory(reader_study=rs1)
+    ds2 = DisplaySetFactory(reader_study=rs2)
+
+    im1, im2 = ImageFactory(), ImageFactory()
+    civ = ComponentInterfaceValueFactory(image=im1)
+    with capture_on_commit_callbacks(execute=True):
+        ds1.values.add(civ)
+
+    civ = ComponentInterfaceValueFactory(image=im2)
+    with capture_on_commit_callbacks(execute=True):
+        ds2.values.add(civ)
+
+    response = get_view_for_user(
+        client=client,
+        user=user,
+        viewname="api:image-list",
+        content_type="application/json",
+    )
+    assert response.status_code == 200
+    assert response.json()["count"] == 2
+
+    response = get_view_for_user(
+        client=client,
+        user=user,
+        viewname="api:image-list",
+        data={"reader_study": str(rs1.pk)},
+        content_type="application/json",
+    )
+    assert response.status_code == 200
+    assert response.json()["count"] == 1
+    assert response.json()["results"][0]["pk"] == str(im1.pk)
+
+
 @pytest.mark.django_db
 @pytest.mark.parametrize(
     "obj,factory",