internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: comic/grand-challenge.org

Issue:
CSV exports return primary keys for choice answers rather than values



Discussion:

Resolving patch:
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
@@ -1147,7 +1147,7 @@ def csv_values(self):
         """Values that are included in this ``Answer``'s csv export."""
         return self.question.csv_values + [
             self.created.isoformat(),
-            self.answer,
+            self.answer_text,
             "; ".join(self.images.values_list("name", flat=True)),
             self.creator.username,
         ]
@@ -1213,6 +1213,23 @@ def validate(  # noqa: C901
                 f"{type(answer)} found."
             )
 
+    @property
+    def answer_text(self):
+        if self.question.answer_type == Question.ANSWER_TYPE_CHOICE:
+            return (
+                self.question.options.filter(pk=self.answer)
+                .values_list("title", flat=True)
+                .first()
+                or ""
+            )
+        if self.question.answer_type == Question.ANSWER_TYPE_MULTIPLE_CHOICE:
+            return ", ".join(
+                self.question.options.filter(pk__in=self.answer).values_list(
+                    "title", flat=True
+                )
+            )
+        return self.answer
+
     def calculate_score(self, ground_truth):
         """Calculate the score for this ``Answer`` based on ``ground_truth``."""
         self.score = self.question.calculate_score(self.answer, ground_truth)


Test patch:
Repository: comic/grand-challenge.org

Issue:
Reader study progress bar text hard to read
Too small, wrong colour.


Discussion:

Resolving patch:
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
@@ -1111,9 +1111,13 @@ def clean(self):
                 "The image port must (only) be set for annotation questions."
             )
 
-        if self.answer_type == self.ANSWER_TYPE_BOOL and self.required:
+        if (
+            self.answer_type
+            in [self.ANSWER_TYPE_BOOL, self.ANSWER_TYPE_HEADING]
+            and self.required
+        ):
             raise ValidationError(
-                "Bool answer types should not have Required checked "
+                "Bool or Heading answer types cannot not be Required "
                 "(otherwise the user will need to tick a box for each image!)"
             )
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Zip case uploads fail if the zip file has no containing folder
```
Traceback (most recent call last):
  File "/opt/poetry/.venv/lib/python3.8/site-packages/celery/app/trace.py", line 385, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/opt/poetry/.venv/lib/python3.8/site-packages/celery/app/trace.py", line 650, in __protected_call__
    return self.run(*args, **kwargs)
  File "/opt/poetry/.venv/lib/python3.8/site-packages/sentry_sdk/integrations/celery.py", line 161, in _inner
    reraise(*exc_info)
  File "/opt/poetry/.venv/lib/python3.8/site-packages/sentry_sdk/_compat.py", line 57, in reraise
    raise value
  File "/opt/poetry/.venv/lib/python3.8/site-packages/sentry_sdk/integrations/celery.py", line 156, in _inner
    return f(*args, **kwargs)
  File "/app/grandchallenge/cases/tasks.py", line 382, in build_images
    _populate_tmp_dir(tmp_dir, upload_session)
  File "/app/grandchallenge/cases/tasks.py", line 58, in _populate_tmp_dir
    extract_files(tmp_dir)
  File "/app/grandchallenge/cases/tasks.py", line 326, in extract_files
    index = check_compressed_and_extract(file_path, source_path, index)
  File "/app/grandchallenge/cases/tasks.py", line 305, in check_compressed_and_extract
    index, new_files = extract_and_flatten(
  File "/app/grandchallenge/cases/tasks.py", line 276, in extract_and_flatten
    return max(indices.values()), new_files
ValueError: max() arg is an empty sequence
```


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/cases/tasks.py b/app/grandchallenge/cases/tasks.py
--- a/app/grandchallenge/cases/tasks.py
+++ b/app/grandchallenge/cases/tasks.py
@@ -273,7 +273,7 @@ def extract_and_flatten(file, path, index, prefix="", is_tar=False):
                 "path": path / filename,
             }
         )
-    return max(indices.values()), new_files
+    return max(list(indices.values()) + [index]), new_files
 
 
 def check_compressed_and_extract(file_path, target_path, index, prefix=None):


Test patch:
Repository: comic/grand-challenge.org

Issue:
Series index from DICOM conversion should take into account the study ID
At the moment the series index in the imported DICOM is based on the number of series in the upload. This number should take into account the StudyInstanceUID too, eg,

![Screenshot_20200403_122159](https://user-images.githubusercontent.com/12661555/78350691-e6c5d580-75a5-11ea-93b8-2dfba142b4c0.png)

```
a163366d-1.2.840.113654.2.70.1.320332802846616902194438716021939437477-23
a163366d-1.2.840.113654.2.70.1.320332802846616902194438716021939437477-16
```

should be 

```
a163366d-1.2.840.113654.2.70.1.320332802846616902194438716021939437477-0
a163366d-1.2.840.113654.2.70.1.320332802846616902194438716021939437477-1
```


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/cases/image_builders/dicom.py b/app/grandchallenge/cases/image_builders/dicom.py
--- a/app/grandchallenge/cases/image_builders/dicom.py
+++ b/app/grandchallenge/cases/image_builders/dicom.py
@@ -68,13 +68,13 @@ def _get_headers_by_study(path):
                 ds = pydicom.filereader.read_partial(
                     f, stop_when=pixel_data_reached
                 )
-                index_key = f"{ds.Rows}x{ds.Columns}"
-                key = f"{ds.StudyInstanceUID}-{index_key}"
+                dims = f"{ds.Rows}x{ds.Columns}"
+                key = f"{ds.StudyInstanceUID}-{dims}"
                 studies[key] = studies.get(key, {})
-                index = indices.get(index_key)
+                index = indices.get(ds.StudyInstanceUID)
                 if index is None:
                     index = max(list(indices.values()) + [-1]) + 1
-                    indices[index_key] = index
+                    indices[ds.StudyInstanceUID] = index
                 headers = studies[key].get("headers", [])
                 headers.append({"file": file, "data": ds})
                 studies[key]["index"] = index


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add a check for browser on reader study session detail
Don't use internet explorer or an old version of chrome.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/workstations/views.py b/app/grandchallenge/workstations/views.py
--- a/app/grandchallenge/workstations/views.py
+++ b/app/grandchallenge/workstations/views.py
@@ -31,6 +31,7 @@
 from rest_framework.status import HTTP_400_BAD_REQUEST
 from rest_framework.viewsets import ReadOnlyModelViewSet
 from rest_framework_guardian.filters import ObjectPermissionsFilter
+from ua_parser.user_agent_parser import ParseUserAgent
 
 from grandchallenge.core.permissions.rest_framework import (
     DjangoObjectOnlyPermissions,
@@ -322,6 +323,45 @@ class SessionDetail(
     )
     raise_exception = True
 
+    def _get_unsupported_browser_message(self):
+        user_agent = ParseUserAgent(
+            self.request.META.get("HTTP_USER_AGENT", "")
+        )
+
+        unsupported_browser = user_agent["family"].lower() not in [
+            "firefox",
+            "chrome",
+        ]
+
+        unsupported_chrome_version = (
+            user_agent["family"].lower() == "chrome"
+            and int(user_agent["major"]) < 79
+        )
+
+        if unsupported_browser:
+            unsupported_browser_message = (
+                "Unfortunately your browser is not supported. "
+                "Please try again with the latest version of Firefox or Chrome."
+            )
+        elif unsupported_chrome_version:
+            unsupported_browser_message = (
+                "Unfortunately your version of Chrome is not supported. "
+                "Please update to the latest version and try again."
+            )
+        else:
+            unsupported_browser_message = None
+
+        return unsupported_browser_message
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context.update(
+            {
+                "unsupported_browser_message": self._get_unsupported_browser_message()
+            }
+        )
+        return context
+
 
 def session_proxy(request, *, pk, path, **_):
     """Return an internal redirect to the session instance if authorised."""


Test patch:
Repository: comic/grand-challenge.org

Issue:
Include slice thickness in mha generated from DICOM
The calcium scoring algorithm is one of the very few algorithms that use both the slice spacing and the slice thickness. The slice spacing is not a standard header field in mha/mhd files, but the algorithm checks anyway if there is a "SliceThickness" header field. It would be nice if the DICOM importer would add this information to the mha header when available in the DICOM header.


Discussion:
I also noticed that these kind of custom headers are removed during the import of mha files. So if I upload an mha file with a SliceThickness header field, it is not actually available to the algorithm.
Yes, any headers that are not explicitly maintained are scrubbed. The kept headers can be updated [here (metaimage import)](https://github.com/comic/grand-challenge.org/blob/master/app/grandchallenge/cases/image_builders/metaio_utils.py#L58) and [here (DICOM import)](https://github.com/comic/grand-challenge.org/blob/master/app/grandchallenge/cases/image_builders/dicom.py#L27).
Thanks, I will try to prepare a PR.

Resolving patch:
diff --git a/app/grandchallenge/cases/image_builders/dicom.py b/app/grandchallenge/cases/image_builders/dicom.py
--- a/app/grandchallenge/cases/image_builders/dicom.py
+++ b/app/grandchallenge/cases/image_builders/dicom.py
@@ -35,6 +35,7 @@
     "StudyInstanceUID",
     "SeriesInstanceUID",
     "StudyDescription",
+    "SliceThickness",
 )
 
 
diff --git a/app/grandchallenge/cases/image_builders/metaio_utils.py b/app/grandchallenge/cases/image_builders/metaio_utils.py
--- a/app/grandchallenge/cases/image_builders/metaio_utils.py
+++ b/app/grandchallenge/cases/image_builders/metaio_utils.py
@@ -65,6 +65,7 @@
     "StudyInstanceUID": LENGTH_LIMIT_MATCH_REGEXP,
     "SeriesInstanceUID": LENGTH_LIMIT_MATCH_REGEXP,
     "StudyDescription": LENGTH_LIMIT_MATCH_REGEXP,
+    "SliceThickness": FLOAT_MATCH_REGEXP,
     "Exposures": FLOAT_LIST_MATCH_REGEXP,
     "ContentTimes": CONTENT_TIMES_LIST_MATCH_REGEXP,
     "t0": FLOAT_MATCH_REGEXP,


Test patch:
diff --git a/app/tests/cases_tests/resources/image10x11x12x13-extra-stuff.mhd b/app/tests/cases_tests/resources/image10x11x12x13-extra-stuff.mhd
--- a/app/tests/cases_tests/resources/image10x11x12x13-extra-stuff.mhd
+++ b/app/tests/cases_tests/resources/image10x11x12x13-extra-stuff.mhd
@@ -16,6 +16,7 @@ PatientName = Patient 1234
 StudyDate = 20020304
 StudyInstanceUID = 1.2.3.4
 SeriesInstanceUID = 1.2.3.4
+SliceThickness = 0.8
 ContentTimes = 000000.000 111111.111 121212.333 121213 121214 121215 121216 121217 121218 121219 121220 121221 235959.999
 Exposures = 1 2 3e-5 4 5.2 6 7 8 9 10 11 12 13.0
 t0 = 12.5
diff --git a/app/tests/cases_tests/resources/image3x4-extra-stuff.mhd b/app/tests/cases_tests/resources/image3x4-extra-stuff.mhd
--- a/app/tests/cases_tests/resources/image3x4-extra-stuff.mhd
+++ b/app/tests/cases_tests/resources/image3x4-extra-stuff.mhd
@@ -26,4 +26,5 @@ PatientBirthDate = 20200101
 PatientAge = 001Y
 PatientSex = M
 StudyDescription = super awesome study
+SliceThickness = 2
 ElementDataFile = image3x4.zraw
Repository: comic/grand-challenge.org

Issue:
Large user container images can cause timeouts when uploading them to docker
If a user uploads a large container for an algorithm (several GBs) the first algorithm run will fail with a read timeout in `self._client.images.load(f)`. The user will get a report of "an unexpected error occurred", but subsequent algorithm runs will be ok.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/container_exec/backends/docker.py b/app/grandchallenge/container_exec/backends/docker.py
--- a/app/grandchallenge/container_exec/backends/docker.py
+++ b/app/grandchallenge/container_exec/backends/docker.py
@@ -6,6 +6,8 @@
 from json import JSONDecodeError
 from pathlib import Path
 from random import randint
+from shutil import copyfileobj
+from tempfile import SpooledTemporaryFile
 from time import sleep
 from typing import Tuple
 
@@ -116,8 +118,19 @@ def _pull_images(self):
             *[img.id for img in self._client.images.list()],
             None,
         ]:
-            with self._exec_image.open("rb") as f:
-                self._client.images.load(f)
+            # This can take a long time so increase the default timeout #1330
+            old_timeout = self._client.api.timeout
+            self._client.api.timeout = 600  # 10 minutes
+            max_size = 10 * 1024 * 1024 * 1024
+
+            with SpooledTemporaryFile(
+                max_size=max_size
+            ) as fdst, self._exec_image.open("rb") as fsrc:
+                copyfileobj(fsrc=fsrc, fdst=fdst)
+                fdst.seek(0)
+                self._client.images.load(fdst)
+
+            self._client.api.timeout = old_timeout
 
 
 class Executor(DockerConnection):
@@ -204,7 +217,8 @@ def _execute_container(self) -> str:
                 remove=True,
                 labels=self._labels,
                 environment={
-                    "NVIDIA_VISIBLE_DEVICES": settings.CONTAINER_EXEC_NVIDIA_VISIBLE_DEVICES
+                    "NVIDIA_VISIBLE_DEVICES": settings.CONTAINER_EXEC_NVIDIA_VISIBLE_DEVICES,
+                    "OPENBLAS_NUM_THREADS": 1,
                 },
                 stdout=True,
                 stderr=True,


Test patch:
Repository: comic/grand-challenge.org

Issue:
Import error DICOM file
**Describe the bug**
When uploading a DICOM file in which the Window Center and Window Width attributes are set to floating values (e.g. 2047.0), the file importer raises an error (see screenshot). The issue can be solved by converting the floating values to integers and writing a new DICOM file. 

**To Reproduce**
Steps to reproduce the behavior:
1. Go to 'Reader Studies'.
2. Select a reader study. 
3. Go to 'Cases'.
4. Click 'Add Cases'.
5. Click 'Choose Files'. 
6. Select a DICOM image with Window Center/Width set to a floating value. 
7. Click 'Submit'.
8. An error will be raised.

**Screenshots**
<img width="828" alt="import_error" src="https://user-images.githubusercontent.com/47858231/83017756-817de600-a024-11ea-9094-d7e4d60cb01f.PNG">



Discussion:
Floats are now already fixed on master, however, Ajay ran into a similar problem with dicom files where the values look like this: `[00400, 00400]`
The fix is not deployed yet, will do it this evening.
The issue in my comment is not fixed yet though, I'm working on that now :)
Because the window center ad width can be either a single value as well as a list of values, I propose to save it in a `CharField` in the database and copy it to the mha headers as is. 
Oh wow, ok. What does `[00400, 00400]` actually mean? How will we handle the data migration?
I'm trying to figure that out now. It seems to differ somewhat between modalities. I think I need to look into this issue some more. Since we do not actually use the values currently, I think the best thing to do for now is to set the value to None if it cannot be cast to a float. That way the importer will be functional again. I'll then create a separate issue to look into the actual issue.
Okay, I would like to add that I used a CR image (x-ray of the hand). 

Resolving patch:
diff --git a/app/grandchallenge/cases/image_builders/utils.py b/app/grandchallenge/cases/image_builders/utils.py
--- a/app/grandchallenge/cases/image_builders/utils.py
+++ b/app/grandchallenge/cases/image_builders/utils.py
@@ -41,17 +41,16 @@ def convert_itk_to_internal(
         else:
             timepoints = None
         depth = simple_itk_image.GetDepth()
-        metadata_keys = simple_itk_image.GetMetaDataKeys()
-        window_center = (
-            float(simple_itk_image.GetMetaData("WindowCenter"))
-            if "WindowCenter" in metadata_keys
-            else None
-        )
-        window_width = (
-            float(simple_itk_image.GetMetaData("WindowWidth"))
-            if "WindowWidth" in metadata_keys
-            else None
-        )
+
+        try:
+            window_center = float(simple_itk_image.GetMetaData("WindowCenter"))
+        except (RuntimeError, ValueError):
+            window_center = None
+        try:
+            window_width = float(simple_itk_image.GetMetaData("WindowWidth"))
+        except (RuntimeError, ValueError):
+            window_width = None
+
         db_image = Image(
             pk=pk,
             name=name,


Test patch:
Repository: comic/grand-challenge.org

Issue:
Tests are taking a very long time to run
Since e949030ed5fb65f62d5cb7849c1e3538bc7137fa test time has increased by 50% (minio storage refactor) - see what's going on.


Discussion:

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -136,10 +136,9 @@ def strtobool(val) -> bool:
 IMAGE_FILES_SUBDIRECTORY = "images"
 EVALUATION_FILES_SUBDIRECTORY = "evaluation"
 
-AWS_AUTO_CREATE_BUCKET = strtobool(
-    os.environ.get("AWS_AUTO_CREATE_BUCKET", "False")
-)
-AWS_FILE_OVERWRITE = False
+AWS_S3_FILE_OVERWRITE = False
+# Note: deprecated in django storages 2.0
+AWS_BUCKET_ACL = "private"
 AWS_DEFAULT_ACL = "private"
 AWS_S3_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME", None)
 
@@ -572,16 +571,17 @@ def strtobool(val) -> bool:
 )
 WORKSTATION_SENTRY_DSN = os.environ.get("WORKSTATION_SENTRY_DSN", "")
 
-sentry_sdk.init(
-    dsn=SENTRY_DSN,
-    integrations=[
-        DjangoIntegration(),
-        CeleryIntegration(),
-        RedisIntegration(),
-    ],
-    release=COMMIT_ID,
-)
-ignore_logger("django.security.DisallowedHost")
+if SENTRY_DSN:
+    sentry_sdk.init(
+        dsn=SENTRY_DSN,
+        integrations=[
+            DjangoIntegration(),
+            CeleryIntegration(),
+            RedisIntegration(),
+        ],
+        release=COMMIT_ID,
+    )
+    ignore_logger("django.security.DisallowedHost")
 
 REST_FRAMEWORK = {
     "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAdminUser",),


Test patch:
diff --git a/app/tests/components_tests/test_tasks.py b/app/tests/components_tests/test_tasks.py
--- a/app/tests/components_tests/test_tasks.py
+++ b/app/tests/components_tests/test_tasks.py
@@ -1,6 +1,7 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
 
 import pytest
+from django.utils import timezone
 
 from grandchallenge.algorithms.models import Job as AlgorithmJob
 from grandchallenge.components.tasks import mark_long_running_jobs_failed
@@ -18,7 +19,7 @@ def test_mark_long_running_jobs_failed():
     # Long running jobs should be marked as failed
     j2 = EvaluationJobFactory()
     j2.update_status(status=EvaluationJob.STARTED)
-    j2.started_at = datetime.now() - timedelta(days=1)
+    j2.started_at = timezone.now() - timedelta(days=1)
     j2.save()
 
     # A job that has not been started should not be marked as failed, even if
Repository: comic/grand-challenge.org

Issue:
Helptext for supported file formats is not up-to-date



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/cases/forms.py b/app/grandchallenge/cases/forms.py
--- a/app/grandchallenge/cases/forms.py
+++ b/app/grandchallenge/cases/forms.py
@@ -22,7 +22,10 @@ class UploadRawImagesForm(forms.ModelForm):
             "The total size of all files uploaded in a single session "
             "cannot exceed 10 GB.<br>"
             "The following file formats are supported: "
-            ".mha, .mhd, .raw, .zraw, .dcm, .tiff, .png, .jpeg and .jpg."
+            ".mha, .mhd, .raw, .zraw, .dcm, .tiff, .png, .jpeg and .jpg.<br>"
+            "The following file formats can be uploaded and will be converted to "
+            "tif: Aperio(.svs), Hamamatsu(.vms, .vmu, .ndpi), Leica(.scn), MIRAX"
+            "(.mrxs) and Ventana(.bif)."
         ),
     )
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add example code for overlay segment configuration for workstation



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/workstation_configs/forms.py b/app/grandchallenge/workstation_configs/forms.py
--- a/app/grandchallenge/workstation_configs/forms.py
+++ b/app/grandchallenge/workstation_configs/forms.py
@@ -36,3 +36,14 @@ class Meta:
                 schema=OVERLAY_SEGMENTS_SCHEMA
             ),
         }
+        help_texts = {
+            "overlay_segments": (
+                "If an categorical overlay is shown, it is possible to show toggles "
+                "to change the visibility of the different overlay categories. To do "
+                "so, configure the categories that should be displayed. Data from the"
+                " algorithm's output.json can be added as an extra label to each "
+                "toggle using jinja templating. "
+                'For example: [{ "voxel_value": 0, "name": "Level 0", "visible": '
+                'false, "metric_template": "{{metrics.volumes[0]}} mm³"},]'
+            ),
+        }


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add leaderboard history
Each time the ranking for the leaderboard is generated save the resulting ranking dictionary so that we know what the leaderboard looked like at a certain date/time. Would be helpful if someone says in their paper "We were first on the leaderboard (at the time of writing)" and we can then independently verify this. 

Maybe this would require moving out the leaderboard into its own app, deprecating the results list view for a leaderboard detail view.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/evaluation/views.py b/app/grandchallenge/evaluation/views.py
--- a/app/grandchallenge/evaluation/views.py
+++ b/app/grandchallenge/evaluation/views.py
@@ -1,6 +1,7 @@
 from datetime import datetime, timedelta
 from typing import Dict
 
+from dateutil.relativedelta import relativedelta
 from django.contrib.messages.views import SuccessMessageMixin
 from django.contrib.postgres.aggregates import ArrayAgg
 from django.core.exceptions import ObjectDoesNotExist
@@ -367,6 +368,7 @@ def get_row_context(self, job, *args, **kwargs):
 
     def get_unfiltered_queryset(self):
         queryset = super().get_queryset()
+        queryset = self.filter_by_date(queryset=queryset)
         queryset = (
             queryset.select_related(
                 "submission__creator__user_profile", "submission__challenge"
@@ -386,6 +388,16 @@ def get_unfiltered_queryset(self):
         )
         return queryset
 
+    def filter_by_date(self, queryset):
+        if "leaderboardDate" in self.request.GET:
+            year, month, day = self.request.GET["leaderboardDate"].split("-")
+            before = datetime(
+                year=int(year), month=int(month), day=int(day),
+            ) + relativedelta(days=1)
+            return queryset.filter(submission__created__lt=before)
+        else:
+            return queryset
+
 
 class EvaluationUpdate(
     UserIsChallengeAdminMixin, SuccessMessageMixin, UpdateView


Test patch: