Repository: comic/grand-challenge.org

Issue:
Clean up complex functions
In https://github.com/comic/grand-challenge.org/pull/970/commits/951a5e659b4d462935cb4d808c0b6f1d476e94c2 we added function complexity checks and I suppressed some warnings for existing complex functions. We should clean some of these up. In particular:

/app/grandchallenge/cases/image_builders/metaio_mhd_mha.py:23:1: C901 'image_builder_mhd' is too complex (22)
/app/grandchallenge/retina_api/views.py:351:5: C901 'DataView.get' is too complex (35)
/app/grandchallenge/retina_api/views.py:584:5: C901 'DataView.put' is too complex (31)



Discussion:

Resolving patch:
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
@@ -1,6 +1,6 @@
 import zlib
 from pathlib import Path
-from typing import Any, List, Mapping, Union
+from typing import Any, Dict, List, Mapping, Tuple, Union
 
 import SimpleITK
 import SimpleITK._SimpleITK as _SimpleITK
@@ -83,54 +83,91 @@ def parse_mh_header(filename: Path) -> Mapping[str, Union[str, None]]:
             except UnicodeDecodeError:
                 raise ValueError("Header contains invalid UTF-8")
             else:
-                # Clean line endings
-                line = line.rstrip("\n\r")
-                if line.strip():
-                    if "=" in line:
-                        key, value = line.split("=", 1)
-                        result[key.strip()] = value.strip()
-                    else:
-                        result[line.strip()] = None
+                extract_key_value_pairs(line, result)
             if "ElementDataFile" in result:
                 break  # last parsed header...
     return result
 
 
+def extract_key_value_pairs(line: str, result: Dict[str, str]):
+    line = line.rstrip("\n\r")
+    if line.strip():
+        if "=" in line:
+            key, value = line.split("=", 1)
+            result[key.strip()] = value.strip()
+        else:
+            result[line.strip()] = None
+
+
 def extract_header_listing(
     property: str, headers: Mapping[str, Union[str, None]], dtype: type = float
 ) -> List[Any]:
     return [dtype(e) for e in headers[property].strip().split(" ")]
 
 
-def load_sitk_image_with_nd_support_from_headers(
-    mhd_file: Path,
-) -> SimpleITK.Image:
+def load_sitk_image_with_nd_support(mhd_file: Path,) -> SimpleITK.Image:
     headers = parse_mh_header(mhd_file)
     is_mha = headers["ElementDataFile"].strip() == "LOCAL"
-    if is_mha:
-        data_file_path = mhd_file
-    else:
-        data_file_path = (
-            mhd_file.resolve().parent / Path(headers["ElementDataFile"]).name
-        )
-    if not data_file_path.exists():
-        raise IOError("cannot find data file")
+    data_file_path = resolve_mh_data_file_path(headers, is_mha, mhd_file)
 
     shape = extract_header_listing("DimSize", headers=headers, dtype=int)
 
+    dtype, num_components = determine_mh_components_and_dtype(headers)
+
+    sitk_image = create_sitk_img_from_mh_data(
+        data_file_path, dtype, headers, is_mha, num_components, shape
+    )
+
+    sitk_image.SetDirection(
+        extract_header_listing("TransformMatrix", headers=headers)
+    )
+    sitk_image.SetSpacing(
+        extract_header_listing("ElementSpacing", headers=headers)
+    )
+    sitk_image.SetOrigin(extract_header_listing("Offset", headers=headers))
+
+    return sitk_image
+
+
+def determine_mh_components_and_dtype(
+    headers: Mapping[str, Union[str, None]]
+) -> Tuple[int, int]:
     num_components = 1
     if "ElementNumberOfChannels" in headers:
         num_components = int(headers["ElementNumberOfChannels"])
         if "_ARRAY" not in headers["ElementType"] and num_components > 1:
             headers["ElementType"] = headers["ElementType"] + "_ARRAY"
-
     dtype = METAIO_IMAGE_TYPES[headers["ElementType"]]
     if dtype is None:
         error_msg = (
             f"MetaIO datatype: {headers['ElementType']} is not supported"
         )
         raise NotImplementedError(error_msg)
+    return dtype, num_components
+
+
+def resolve_mh_data_file_path(
+    headers: Mapping[str, Union[str, None]], is_mha: bool, mhd_file: Path
+) -> Path:
+    if is_mha:
+        data_file_path = mhd_file
+    else:
+        data_file_path = (
+            mhd_file.resolve().parent / Path(headers["ElementDataFile"]).name
+        )
+    if not data_file_path.exists():
+        raise IOError("cannot find data file")
+    return data_file_path
 
+
+def create_sitk_img_from_mh_data(
+    data_file_path: Path,
+    dtype: int,
+    headers: Mapping[str, Union[str, None]],
+    is_mha: bool,
+    num_components: int,
+    shape,
+) -> SimpleITK.Image:
     is_compressed = headers["CompressedData"] == "True"
     with open(str(data_file_path), "rb") as f:
         if is_mha:
@@ -141,17 +178,8 @@ def load_sitk_image_with_nd_support_from_headers(
             s = f.read()
         else:
             s = zlib.decompress(f.read())
-
     sitk_image = SimpleITK.Image(shape, dtype, num_components)
     _SimpleITK._SetImageFromArray(s, sitk_image)
-    sitk_image.SetDirection(
-        extract_header_listing("TransformMatrix", headers=headers)
-    )
-    sitk_image.SetSpacing(
-        extract_header_listing("ElementSpacing", headers=headers)
-    )
-    sitk_image.SetOrigin(extract_header_listing("Offset", headers=headers))
-
     return sitk_image
 
 
@@ -161,9 +189,7 @@ def load_sitk_image(mhd_file: Path) -> SimpleITK.Image:
     if ndims < 4:
         sitk_image = SimpleITK.ReadImage(str(mhd_file))
     elif ndims == 4:
-        sitk_image = load_sitk_image_with_nd_support_from_headers(
-            mhd_file=mhd_file
-        )
+        sitk_image = load_sitk_image_with_nd_support(mhd_file=mhd_file)
     else:
         error_msg = (
             "SimpleITK images with more than 4 dimensions are not supported"


Test patch:
diff --git a/app/tests/cases_tests/test_mhd.py b/app/tests/cases_tests/test_mhd.py
--- a/app/tests/cases_tests/test_mhd.py
+++ b/app/tests/cases_tests/test_mhd.py
@@ -7,7 +7,7 @@
 
 from grandchallenge.cases.image_builders.metaio_utils import (
     load_sitk_image,
-    load_sitk_image_with_nd_support_from_headers,
+    load_sitk_image_with_nd_support,
     parse_mh_header,
 )
 from tests.cases_tests import RESOURCE_PATH
@@ -189,7 +189,7 @@ def test_4d_mh_loader_with_more_than_4_dimensions_fails(tmpdir):
 )
 def test_4dloader_reproduces_normal_sitk_loader_results(image: Path):
     img_ref = SimpleITK.ReadImage(str(image))
-    img = load_sitk_image_with_nd_support_from_headers(mhd_file=image)
+    img = load_sitk_image_with_nd_support(mhd_file=image)
     assert_sitk_img_equivalence(img, img_ref)
 
 
diff --git a/app/tests/cases_tests/test_models.py b/app/tests/cases_tests/test_models.py
--- a/app/tests/cases_tests/test_models.py
+++ b/app/tests/cases_tests/test_models.py
@@ -98,7 +98,6 @@ def test_file_too_large_throws_error(self, tmpdir):
         image.files.add(too_large_imagefile)
 
         # Try to open and catch expected exception
-        exec_info = None
         with pytest.raises(IOError) as exec_info:
             image.get_sitk_image()
         assert "File exceeds maximum file size." in str(