We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
local variable 'file_origin' referenced before assignment
https://github.com/comic/grand-challenge.org/commit/e92de073dbf28a80d84075b9dc1c6d39d8cd5777#diff-06cfbdc71403fdb7de1ab89e3d50d139R214

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/grandchallenge/cases/image_builders/dicom.py`
Content:
```
1 import tempfile
2 from collections import namedtuple
3 from pathlib import Path
4 
5 import SimpleITK
6 import numpy as np
7 import pydicom
8 
9 from grandchallenge.cases.image_builders import ImageBuilderResult
10 from grandchallenge.cases.image_builders.utils import convert_itk_to_internal
11 
12 NUMPY_IMAGE_TYPES = {
13     "character": SimpleITK.sitkUInt8,
14     "uint8": SimpleITK.sitkUInt8,
15     "uint16": SimpleITK.sitkUInt16,
16     "uint32": SimpleITK.sitkUInt32,
17     "uint64": SimpleITK.sitkUInt64,
18     "int8": SimpleITK.sitkInt8,
19     "int16": SimpleITK.sitkInt16,
20     "int32": SimpleITK.sitkInt32,
21     "int64": SimpleITK.sitkInt64,
22     "float32": SimpleITK.sitkFloat32,
23     "float64": SimpleITK.sitkFloat64,
24 }
25 
26 
27 def pixel_data_reached(tag, vr, length):
28     return pydicom.datadict.keyword_for_tag(tag) == "PixelData"
29 
30 
31 def _get_headers_by_study(path):
32     """
33     Gets all headers from dicom files found in path.
34 
35     Parameters
36     ----------
37     path
38         Path to a directory that contains all images that were uploaded during
39         an upload session.
40 
41     Returns
42     -------
43     A dictionary of sorted headers for all dicom image files found within path,
44     grouped by study id.
45     """
46     studies = {}
47     errors = {}
48     for file in path.iterdir():
49         if not file.is_file():
50             continue
51         with file.open("rb") as f:
52             try:
53                 ds = pydicom.filereader.read_partial(
54                     f, stop_when=pixel_data_reached
55                 )
56                 studies[ds.StudyInstanceUID] = studies.get(
57                     ds.StudyInstanceUID, {}
58                 )
59                 headers = studies[ds.StudyInstanceUID].get("headers", [])
60                 headers.append({"file": file, "data": ds})
61                 studies[ds.StudyInstanceUID]["headers"] = headers
62             except Exception as e:
63                 errors[file.name] = str(e)
64 
65     for key in studies:
66         studies[key]["headers"].sort(
67             key=lambda x: int(x["data"].InstanceNumber)
68         )
69     return studies, errors
70 
71 
72 def _validate_dicom_files(path):
73     """
74     Gets the headers for all dicom files on path and validates them.
75 
76     Parameters
77     ----------
78     path
79         Path to a directory that contains all images that were uploaded during
80         an upload session.
81 
82     Returns
83     -------
84     A list of `dicom_dataset` named tuples per study, consisting of:
85      - Headers for all dicom image files for the study
86      - Number of time points
87      - Number of slices per time point
88 
89     Any study with an inconsistent amount of slices per time point is discarded.
90     """
91     studies, errors = _get_headers_by_study(path)
92     result = []
93     dicom_dataset = namedtuple(
94         "dicom_dataset", ["headers", "n_time", "n_slices"]
95     )
96     for key in studies:
97         headers = studies[key]["headers"]
98         if not headers:
99             continue
100         n_time = getattr(headers[-1]["data"], "TemporalPositionIndex", None)
101         # Not a 4d dicom file
102         if n_time is None:
103             result.append(
104                 dicom_dataset(
105                     headers=headers, n_time=n_time, n_slices=len(headers)
106                 )
107             )
108             continue
109         if len(headers) % n_time > 0:
110             for d in headers:
111                 errors[
112                     d["file"].name
113                 ] = "Number of slices per time point differs"
114             continue
115         n_slices = len(headers) // n_time
116         result.append(
117             dicom_dataset(headers=headers, n_time=n_time, n_slices=n_slices)
118         )
119     del studies
120     return result, errors
121 
122 
123 def _extract_direction(dicom_ds, direction):
124     try:
125         # Try to extract the direction from the file
126         sitk_ref = SimpleITK.ReadImage(str(dicom_ds.headers[0]["file"]))
127         # The direction per slice is a 3x3 matrix, so we add the time
128         # dimension ourselves
129         dims = sitk_ref.GetDimension()
130         _direction = np.reshape(sitk_ref.GetDirection(), (dims, dims))
131         direction[:dims, :dims] = _direction
132     except Exception:
133         pass
134     return direction
135 
136 
137 def _create_sitk_image(dcm_array):
138     shape = dcm_array.shape[::-1]
139     # Write the numpy array to a file, so there is no need to keep it in memory
140     # anymore. Then create a SimpleITK image from it.
141     with tempfile.NamedTemporaryFile() as temp:
142         temp.seek(0)
143         temp.write(dcm_array.tostring())
144         temp.flush()
145         temp.seek(0)
146         del dcm_array
147         img = SimpleITK.Image(shape, SimpleITK.sitkFloat32, 1)
148         SimpleITK._SimpleITK._SetImageFromArray(temp.read(), img)
149         return img
150 
151 
152 def _process_dicom_file(dicom_ds):  # noqa: C901
153     ref_file = pydicom.dcmread(str(dicom_ds.headers[0]["file"]))
154     ref_origin = tuple(
155         float(i) for i in getattr(ref_file, "ImagePositionPatient", (0, 0, 0))
156     )
157     dimensions = 4 if dicom_ds.n_time else 3
158     direction = np.eye(dimensions, dtype=np.float)
159     direction = _extract_direction(dicom_ds, direction)
160     pixel_dims = (
161         dicom_ds.n_slices,
162         int(ref_file.Rows),
163         int(ref_file.Columns),
164     )
165     if dicom_ds.n_time:
166         pixel_dims = (dicom_ds.n_time,) + pixel_dims
167     dcm_array = np.zeros(pixel_dims, dtype=np.float32)
168 
169     # Additional Meta data Contenttimes and Exposures
170     content_times = []
171     exposures = []
172 
173     origin = None
174     origin_diff = np.array((0, 0, 0), dtype=float)
175     n_diffs = 0
176     for partial in dicom_ds.headers:
177         ds = partial["data"]
178         if "ImagePositionPatient" in ds:
179             file_origin = np.array(ds.ImagePositionPatient, dtype=float)
180             if origin is not None:
181                 diff = file_origin - origin
182                 origin_diff = origin_diff + diff
183                 n_diffs += 1
184             origin = file_origin
185     avg_origin_diff = tuple(origin_diff / n_diffs)
186     try:
187         z_i = avg_origin_diff[2]
188     except IndexError:
189         z_i = 1.0
190     for index, partial in enumerate(dicom_ds.headers):
191         ds = pydicom.dcmread(str(partial["file"]))
192 
193         # Apply RescaleSlope and RescaleIntercept
194         pixel_array = float(
195             getattr(ds, "RescaleSlope", 1)
196         ) * ds.pixel_array + float(getattr(ds, "RescaleIntercept", 0))
197         if len(ds.pixel_array.shape) == dimensions:
198             dcm_array = pixel_array
199             break
200         z_index = index if z_i >= 0 else len(dicom_ds.headers) - index - 1
201         if dimensions == 4:
202             dcm_array[
203                 index // dicom_ds.n_slices, z_index % dicom_ds.n_slices, :, :
204             ] = pixel_array
205             if index % dicom_ds.n_slices == 0:
206                 content_times.append(str(ds.ContentTime))
207                 exposures.append(str(ds.Exposure))
208         else:
209             dcm_array[z_index % dicom_ds.n_slices, :, :] = pixel_array
210         del ds
211 
212     img = _create_sitk_image(dcm_array)
213 
214     sitk_origin = ref_origin if z_i > 0.0 else tuple(file_origin)
215     z_i = np.abs(z_i)
216 
217     if "PixelSpacing" in ref_file:
218         x_i, y_i = (float(x) for x in ref_file.PixelSpacing)
219     else:
220         x_i = y_i = 1.0
221 
222     sitk_spacing = (x_i, y_i, z_i)
223     if dimensions == 4:
224         sitk_spacing += (1.0,)
225         sitk_origin += (0.0,)
226 
227     sitk_direction = tuple(direction.flatten())
228     img.SetDirection(sitk_direction)
229     img.SetSpacing(sitk_spacing)
230     img.SetOrigin(sitk_origin)
231 
232     if dimensions == 4:
233         # Set Additional Meta Data
234         img.SetMetaData("ContentTimes", " ".join(content_times))
235         img.SetMetaData("Exposures", " ".join(exposures))
236 
237     # Convert the SimpleITK image to our internal representation
238     return convert_itk_to_internal(img)
239 
240 
241 def image_builder_dicom(path: Path) -> ImageBuilderResult:
242     """
243     Constructs image objects by inspecting files in a directory.
244 
245     Parameters
246     ----------
247     path
248         Path to a directory that contains all images that were uploaded during
249         an upload session.
250 
251     Returns
252     -------
253     An `ImageBuilder` object consisting of:
254      - a list of filenames for all files consumed by the image builder
255      - a list of detected images
256      - a list files associated with the detected images
257      - path->error message map describing what is wrong with a given file
258     """
259     studies, file_errors_map = _validate_dicom_files(path)
260     new_images = []
261     new_image_files = []
262     consumed_files = []
263     for dicom_ds in studies:
264         try:
265             n_image, n_image_files = _process_dicom_file(dicom_ds)
266             new_images.append(n_image)
267             new_image_files += n_image_files
268             consumed_files += [d["file"].name for d in dicom_ds.headers]
269         except Exception as e:
270             for d in dicom_ds.headers:
271                 file_errors_map[d["file"].name] = str(e)
272 
273     return ImageBuilderResult(
274         consumed_files=consumed_files,
275         file_errors_map=file_errors_map,
276         new_images=new_images,
277         new_image_files=new_image_files,
278         new_folder_upload=[],
279     )
280
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -187,6 +187,7 @@
         z_i = avg_origin_diff[2]
     except IndexError:
         z_i = 1.0
+
     for index, partial in enumerate(dicom_ds.headers):
         ds = pydicom.dcmread(str(partial["file"]))
 
@@ -211,8 +212,10 @@
 
     img = _create_sitk_image(dcm_array)
 
-    sitk_origin = ref_origin if z_i > 0.0 else tuple(file_origin)
-    z_i = np.abs(z_i)
+    if origin is None:
+        origin = (0.0, 0.0, 0.0)
+    sitk_origin = ref_origin if z_i >= 0.0 else tuple(origin)
+    z_i = np.abs(z_i) if not np.isnan(z_i) else 1.0
 
     if "PixelSpacing" in ref_file:
         x_i, y_i = (float(x) for x in ref_file.PixelSpacing)
@@ -233,7 +236,6 @@
         # Set Additional Meta Data
         img.SetMetaData("ContentTimes", " ".join(content_times))
         img.SetMetaData("Exposures", " ".join(exposures))
-
     # Convert the SimpleITK image to our internal representation
     return convert_itk_to_internal(img)

{"golden_diff": "diff --git a/app/grandchallenge/cases/image_builders/dicom.py b/app/grandchallenge/cases/image_builders/dicom.py\n--- a/app/grandchallenge/cases/image_builders/dicom.py\n+++ b/app/grandchallenge/cases/image_builders/dicom.py\n@@ -187,6 +187,7 @@\n         z_i = avg_origin_diff[2]\n     except IndexError:\n         z_i = 1.0\n+\n     for index, partial in enumerate(dicom_ds.headers):\n         ds = pydicom.dcmread(str(partial[\"file\"]))\n \n@@ -211,8 +212,10 @@\n \n     img = _create_sitk_image(dcm_array)\n \n-    sitk_origin = ref_origin if z_i > 0.0 else tuple(file_origin)\n-    z_i = np.abs(z_i)\n+    if origin is None:\n+        origin = (0.0, 0.0, 0.0)\n+    sitk_origin = ref_origin if z_i >= 0.0 else tuple(origin)\n+    z_i = np.abs(z_i) if not np.isnan(z_i) else 1.0\n \n     if \"PixelSpacing\" in ref_file:\n         x_i, y_i = (float(x) for x in ref_file.PixelSpacing)\n@@ -233,7 +236,6 @@\n         # Set Additional Meta Data\n         img.SetMetaData(\"ContentTimes\", \" \".join(content_times))\n         img.SetMetaData(\"Exposures\", \" \".join(exposures))\n-\n     # Convert the SimpleITK image to our internal representation\n     return convert_itk_to_internal(img)\n", "issue": "local variable 'file_origin' referenced before assignment\nhttps://github.com/comic/grand-challenge.org/commit/e92de073dbf28a80d84075b9dc1c6d39d8cd5777#diff-06cfbdc71403fdb7de1ab89e3d50d139R214\n", "before_files": [{"content": "import tempfile\nfrom collections import namedtuple\nfrom pathlib import Path\n\nimport SimpleITK\nimport numpy as np\nimport pydicom\n\nfrom grandchallenge.cases.image_builders import ImageBuilderResult\nfrom grandchallenge.cases.image_builders.utils import convert_itk_to_internal\n\nNUMPY_IMAGE_TYPES = {\n    \"character\": SimpleITK.sitkUInt8,\n    \"uint8\": SimpleITK.sitkUInt8,\n    \"uint16\": SimpleITK.sitkUInt16,\n    \"uint32\": SimpleITK.sitkUInt32,\n    \"uint64\": SimpleITK.sitkUInt64,\n    \"int8\": SimpleITK.sitkInt8,\n    \"int16\": SimpleITK.sitkInt16,\n    \"int32\": SimpleITK.sitkInt32,\n    \"int64\": SimpleITK.sitkInt64,\n    \"float32\": SimpleITK.sitkFloat32,\n    \"float64\": SimpleITK.sitkFloat64,\n}\n\n\ndef pixel_data_reached(tag, vr, length):\n    return pydicom.datadict.keyword_for_tag(tag) == \"PixelData\"\n\n\ndef _get_headers_by_study(path):\n    \"\"\"\n    Gets all headers from dicom files found in path.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    A dictionary of sorted headers for all dicom image files found within path,\n    grouped by study id.\n    \"\"\"\n    studies = {}\n    errors = {}\n    for file in path.iterdir():\n        if not file.is_file():\n            continue\n        with file.open(\"rb\") as f:\n            try:\n                ds = pydicom.filereader.read_partial(\n                    f, stop_when=pixel_data_reached\n                )\n                studies[ds.StudyInstanceUID] = studies.get(\n                    ds.StudyInstanceUID, {}\n                )\n                headers = studies[ds.StudyInstanceUID].get(\"headers\", [])\n                headers.append({\"file\": file, \"data\": ds})\n                studies[ds.StudyInstanceUID][\"headers\"] = headers\n            except Exception as e:\n                errors[file.name] = str(e)\n\n    for key in studies:\n        studies[key][\"headers\"].sort(\n            key=lambda x: int(x[\"data\"].InstanceNumber)\n        )\n    return studies, errors\n\n\ndef _validate_dicom_files(path):\n    \"\"\"\n    Gets the headers for all dicom files on path and validates them.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    A list of `dicom_dataset` named tuples per study, consisting of:\n     - Headers for all dicom image files for the study\n     - Number of time points\n     - Number of slices per time point\n\n    Any study with an inconsistent amount of slices per time point is discarded.\n    \"\"\"\n    studies, errors = _get_headers_by_study(path)\n    result = []\n    dicom_dataset = namedtuple(\n        \"dicom_dataset\", [\"headers\", \"n_time\", \"n_slices\"]\n    )\n    for key in studies:\n        headers = studies[key][\"headers\"]\n        if not headers:\n            continue\n        n_time = getattr(headers[-1][\"data\"], \"TemporalPositionIndex\", None)\n        # Not a 4d dicom file\n        if n_time is None:\n            result.append(\n                dicom_dataset(\n                    headers=headers, n_time=n_time, n_slices=len(headers)\n                )\n            )\n            continue\n        if len(headers) % n_time > 0:\n            for d in headers:\n                errors[\n                    d[\"file\"].name\n                ] = \"Number of slices per time point differs\"\n            continue\n        n_slices = len(headers) // n_time\n        result.append(\n            dicom_dataset(headers=headers, n_time=n_time, n_slices=n_slices)\n        )\n    del studies\n    return result, errors\n\n\ndef _extract_direction(dicom_ds, direction):\n    try:\n        # Try to extract the direction from the file\n        sitk_ref = SimpleITK.ReadImage(str(dicom_ds.headers[0][\"file\"]))\n        # The direction per slice is a 3x3 matrix, so we add the time\n        # dimension ourselves\n        dims = sitk_ref.GetDimension()\n        _direction = np.reshape(sitk_ref.GetDirection(), (dims, dims))\n        direction[:dims, :dims] = _direction\n    except Exception:\n        pass\n    return direction\n\n\ndef _create_sitk_image(dcm_array):\n    shape = dcm_array.shape[::-1]\n    # Write the numpy array to a file, so there is no need to keep it in memory\n    # anymore. Then create a SimpleITK image from it.\n    with tempfile.NamedTemporaryFile() as temp:\n        temp.seek(0)\n        temp.write(dcm_array.tostring())\n        temp.flush()\n        temp.seek(0)\n        del dcm_array\n        img = SimpleITK.Image(shape, SimpleITK.sitkFloat32, 1)\n        SimpleITK._SimpleITK._SetImageFromArray(temp.read(), img)\n        return img\n\n\ndef _process_dicom_file(dicom_ds):  # noqa: C901\n    ref_file = pydicom.dcmread(str(dicom_ds.headers[0][\"file\"]))\n    ref_origin = tuple(\n        float(i) for i in getattr(ref_file, \"ImagePositionPatient\", (0, 0, 0))\n    )\n    dimensions = 4 if dicom_ds.n_time else 3\n    direction = np.eye(dimensions, dtype=np.float)\n    direction = _extract_direction(dicom_ds, direction)\n    pixel_dims = (\n        dicom_ds.n_slices,\n        int(ref_file.Rows),\n        int(ref_file.Columns),\n    )\n    if dicom_ds.n_time:\n        pixel_dims = (dicom_ds.n_time,) + pixel_dims\n    dcm_array = np.zeros(pixel_dims, dtype=np.float32)\n\n    # Additional Meta data Contenttimes and Exposures\n    content_times = []\n    exposures = []\n\n    origin = None\n    origin_diff = np.array((0, 0, 0), dtype=float)\n    n_diffs = 0\n    for partial in dicom_ds.headers:\n        ds = partial[\"data\"]\n        if \"ImagePositionPatient\" in ds:\n            file_origin = np.array(ds.ImagePositionPatient, dtype=float)\n            if origin is not None:\n                diff = file_origin - origin\n                origin_diff = origin_diff + diff\n                n_diffs += 1\n            origin = file_origin\n    avg_origin_diff = tuple(origin_diff / n_diffs)\n    try:\n        z_i = avg_origin_diff[2]\n    except IndexError:\n        z_i = 1.0\n    for index, partial in enumerate(dicom_ds.headers):\n        ds = pydicom.dcmread(str(partial[\"file\"]))\n\n        # Apply RescaleSlope and RescaleIntercept\n        pixel_array = float(\n            getattr(ds, \"RescaleSlope\", 1)\n        ) * ds.pixel_array + float(getattr(ds, \"RescaleIntercept\", 0))\n        if len(ds.pixel_array.shape) == dimensions:\n            dcm_array = pixel_array\n            break\n        z_index = index if z_i >= 0 else len(dicom_ds.headers) - index - 1\n        if dimensions == 4:\n            dcm_array[\n                index // dicom_ds.n_slices, z_index % dicom_ds.n_slices, :, :\n            ] = pixel_array\n            if index % dicom_ds.n_slices == 0:\n                content_times.append(str(ds.ContentTime))\n                exposures.append(str(ds.Exposure))\n        else:\n            dcm_array[z_index % dicom_ds.n_slices, :, :] = pixel_array\n        del ds\n\n    img = _create_sitk_image(dcm_array)\n\n    sitk_origin = ref_origin if z_i > 0.0 else tuple(file_origin)\n    z_i = np.abs(z_i)\n\n    if \"PixelSpacing\" in ref_file:\n        x_i, y_i = (float(x) for x in ref_file.PixelSpacing)\n    else:\n        x_i = y_i = 1.0\n\n    sitk_spacing = (x_i, y_i, z_i)\n    if dimensions == 4:\n        sitk_spacing += (1.0,)\n        sitk_origin += (0.0,)\n\n    sitk_direction = tuple(direction.flatten())\n    img.SetDirection(sitk_direction)\n    img.SetSpacing(sitk_spacing)\n    img.SetOrigin(sitk_origin)\n\n    if dimensions == 4:\n        # Set Additional Meta Data\n        img.SetMetaData(\"ContentTimes\", \" \".join(content_times))\n        img.SetMetaData(\"Exposures\", \" \".join(exposures))\n\n    # Convert the SimpleITK image to our internal representation\n    return convert_itk_to_internal(img)\n\n\ndef image_builder_dicom(path: Path) -> ImageBuilderResult:\n    \"\"\"\n    Constructs image objects by inspecting files in a directory.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    An `ImageBuilder` object consisting of:\n     - a list of filenames for all files consumed by the image builder\n     - a list of detected images\n     - a list files associated with the detected images\n     - path->error message map describing what is wrong with a given file\n    \"\"\"\n    studies, file_errors_map = _validate_dicom_files(path)\n    new_images = []\n    new_image_files = []\n    consumed_files = []\n    for dicom_ds in studies:\n        try:\n            n_image, n_image_files = _process_dicom_file(dicom_ds)\n            new_images.append(n_image)\n            new_image_files += n_image_files\n            consumed_files += [d[\"file\"].name for d in dicom_ds.headers]\n        except Exception as e:\n            for d in dicom_ds.headers:\n                file_errors_map[d[\"file\"].name] = str(e)\n\n    return ImageBuilderResult(\n        consumed_files=consumed_files,\n        file_errors_map=file_errors_map,\n        new_images=new_images,\n        new_image_files=new_image_files,\n        new_folder_upload=[],\n    )\n", "path": "app/grandchallenge/cases/image_builders/dicom.py"}], "after_files": [{"content": "import tempfile\nfrom collections import namedtuple\nfrom pathlib import Path\n\nimport SimpleITK\nimport numpy as np\nimport pydicom\n\nfrom grandchallenge.cases.image_builders import ImageBuilderResult\nfrom grandchallenge.cases.image_builders.utils import convert_itk_to_internal\n\nNUMPY_IMAGE_TYPES = {\n    \"character\": SimpleITK.sitkUInt8,\n    \"uint8\": SimpleITK.sitkUInt8,\n    \"uint16\": SimpleITK.sitkUInt16,\n    \"uint32\": SimpleITK.sitkUInt32,\n    \"uint64\": SimpleITK.sitkUInt64,\n    \"int8\": SimpleITK.sitkInt8,\n    \"int16\": SimpleITK.sitkInt16,\n    \"int32\": SimpleITK.sitkInt32,\n    \"int64\": SimpleITK.sitkInt64,\n    \"float32\": SimpleITK.sitkFloat32,\n    \"float64\": SimpleITK.sitkFloat64,\n}\n\n\ndef pixel_data_reached(tag, vr, length):\n    return pydicom.datadict.keyword_for_tag(tag) == \"PixelData\"\n\n\ndef _get_headers_by_study(path):\n    \"\"\"\n    Gets all headers from dicom files found in path.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    A dictionary of sorted headers for all dicom image files found within path,\n    grouped by study id.\n    \"\"\"\n    studies = {}\n    errors = {}\n    for file in path.iterdir():\n        if not file.is_file():\n            continue\n        with file.open(\"rb\") as f:\n            try:\n                ds = pydicom.filereader.read_partial(\n                    f, stop_when=pixel_data_reached\n                )\n                studies[ds.StudyInstanceUID] = studies.get(\n                    ds.StudyInstanceUID, {}\n                )\n                headers = studies[ds.StudyInstanceUID].get(\"headers\", [])\n                headers.append({\"file\": file, \"data\": ds})\n                studies[ds.StudyInstanceUID][\"headers\"] = headers\n            except Exception as e:\n                errors[file.name] = str(e)\n\n    for key in studies:\n        studies[key][\"headers\"].sort(\n            key=lambda x: int(x[\"data\"].InstanceNumber)\n        )\n    return studies, errors\n\n\ndef _validate_dicom_files(path):\n    \"\"\"\n    Gets the headers for all dicom files on path and validates them.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    A list of `dicom_dataset` named tuples per study, consisting of:\n     - Headers for all dicom image files for the study\n     - Number of time points\n     - Number of slices per time point\n\n    Any study with an inconsistent amount of slices per time point is discarded.\n    \"\"\"\n    studies, errors = _get_headers_by_study(path)\n    result = []\n    dicom_dataset = namedtuple(\n        \"dicom_dataset\", [\"headers\", \"n_time\", \"n_slices\"]\n    )\n    for key in studies:\n        headers = studies[key][\"headers\"]\n        if not headers:\n            continue\n        n_time = getattr(headers[-1][\"data\"], \"TemporalPositionIndex\", None)\n        # Not a 4d dicom file\n        if n_time is None:\n            result.append(\n                dicom_dataset(\n                    headers=headers, n_time=n_time, n_slices=len(headers)\n                )\n            )\n            continue\n        if len(headers) % n_time > 0:\n            for d in headers:\n                errors[\n                    d[\"file\"].name\n                ] = \"Number of slices per time point differs\"\n            continue\n        n_slices = len(headers) // n_time\n        result.append(\n            dicom_dataset(headers=headers, n_time=n_time, n_slices=n_slices)\n        )\n    del studies\n    return result, errors\n\n\ndef _extract_direction(dicom_ds, direction):\n    try:\n        # Try to extract the direction from the file\n        sitk_ref = SimpleITK.ReadImage(str(dicom_ds.headers[0][\"file\"]))\n        # The direction per slice is a 3x3 matrix, so we add the time\n        # dimension ourselves\n        dims = sitk_ref.GetDimension()\n        _direction = np.reshape(sitk_ref.GetDirection(), (dims, dims))\n        direction[:dims, :dims] = _direction\n    except Exception:\n        pass\n    return direction\n\n\ndef _create_sitk_image(dcm_array):\n    shape = dcm_array.shape[::-1]\n    # Write the numpy array to a file, so there is no need to keep it in memory\n    # anymore. Then create a SimpleITK image from it.\n    with tempfile.NamedTemporaryFile() as temp:\n        temp.seek(0)\n        temp.write(dcm_array.tostring())\n        temp.flush()\n        temp.seek(0)\n        del dcm_array\n        img = SimpleITK.Image(shape, SimpleITK.sitkFloat32, 1)\n        SimpleITK._SimpleITK._SetImageFromArray(temp.read(), img)\n        return img\n\n\ndef _process_dicom_file(dicom_ds):  # noqa: C901\n    ref_file = pydicom.dcmread(str(dicom_ds.headers[0][\"file\"]))\n    ref_origin = tuple(\n        float(i) for i in getattr(ref_file, \"ImagePositionPatient\", (0, 0, 0))\n    )\n    dimensions = 4 if dicom_ds.n_time else 3\n    direction = np.eye(dimensions, dtype=np.float)\n    direction = _extract_direction(dicom_ds, direction)\n    pixel_dims = (\n        dicom_ds.n_slices,\n        int(ref_file.Rows),\n        int(ref_file.Columns),\n    )\n    if dicom_ds.n_time:\n        pixel_dims = (dicom_ds.n_time,) + pixel_dims\n    dcm_array = np.zeros(pixel_dims, dtype=np.float32)\n\n    # Additional Meta data Contenttimes and Exposures\n    content_times = []\n    exposures = []\n\n    origin = None\n    origin_diff = np.array((0, 0, 0), dtype=float)\n    n_diffs = 0\n    for partial in dicom_ds.headers:\n        ds = partial[\"data\"]\n        if \"ImagePositionPatient\" in ds:\n            file_origin = np.array(ds.ImagePositionPatient, dtype=float)\n            if origin is not None:\n                diff = file_origin - origin\n                origin_diff = origin_diff + diff\n                n_diffs += 1\n            origin = file_origin\n    avg_origin_diff = tuple(origin_diff / n_diffs)\n    try:\n        z_i = avg_origin_diff[2]\n    except IndexError:\n        z_i = 1.0\n\n    for index, partial in enumerate(dicom_ds.headers):\n        ds = pydicom.dcmread(str(partial[\"file\"]))\n\n        # Apply RescaleSlope and RescaleIntercept\n        pixel_array = float(\n            getattr(ds, \"RescaleSlope\", 1)\n        ) * ds.pixel_array + float(getattr(ds, \"RescaleIntercept\", 0))\n        if len(ds.pixel_array.shape) == dimensions:\n            dcm_array = pixel_array\n            break\n        z_index = index if z_i >= 0 else len(dicom_ds.headers) - index - 1\n        if dimensions == 4:\n            dcm_array[\n                index // dicom_ds.n_slices, z_index % dicom_ds.n_slices, :, :\n            ] = pixel_array\n            if index % dicom_ds.n_slices == 0:\n                content_times.append(str(ds.ContentTime))\n                exposures.append(str(ds.Exposure))\n        else:\n            dcm_array[z_index % dicom_ds.n_slices, :, :] = pixel_array\n        del ds\n\n    img = _create_sitk_image(dcm_array)\n\n    if origin is None:\n        origin = (0.0, 0.0, 0.0)\n    sitk_origin = ref_origin if z_i >= 0.0 else tuple(origin)\n    z_i = np.abs(z_i) if not np.isnan(z_i) else 1.0\n\n    if \"PixelSpacing\" in ref_file:\n        x_i, y_i = (float(x) for x in ref_file.PixelSpacing)\n    else:\n        x_i = y_i = 1.0\n\n    sitk_spacing = (x_i, y_i, z_i)\n    if dimensions == 4:\n        sitk_spacing += (1.0,)\n        sitk_origin += (0.0,)\n\n    sitk_direction = tuple(direction.flatten())\n    img.SetDirection(sitk_direction)\n    img.SetSpacing(sitk_spacing)\n    img.SetOrigin(sitk_origin)\n\n    if dimensions == 4:\n        # Set Additional Meta Data\n        img.SetMetaData(\"ContentTimes\", \" \".join(content_times))\n        img.SetMetaData(\"Exposures\", \" \".join(exposures))\n    # Convert the SimpleITK image to our internal representation\n    return convert_itk_to_internal(img)\n\n\ndef image_builder_dicom(path: Path) -> ImageBuilderResult:\n    \"\"\"\n    Constructs image objects by inspecting files in a directory.\n\n    Parameters\n    ----------\n    path\n        Path to a directory that contains all images that were uploaded during\n        an upload session.\n\n    Returns\n    -------\n    An `ImageBuilder` object consisting of:\n     - a list of filenames for all files consumed by the image builder\n     - a list of detected images\n     - a list files associated with the detected images\n     - path->error message map describing what is wrong with a given file\n    \"\"\"\n    studies, file_errors_map = _validate_dicom_files(path)\n    new_images = []\n    new_image_files = []\n    consumed_files = []\n    for dicom_ds in studies:\n        try:\n            n_image, n_image_files = _process_dicom_file(dicom_ds)\n            new_images.append(n_image)\n            new_image_files += n_image_files\n            consumed_files += [d[\"file\"].name for d in dicom_ds.headers]\n        except Exception as e:\n            for d in dicom_ds.headers:\n                file_errors_map[d[\"file\"].name] = str(e)\n\n    return ImageBuilderResult(\n        consumed_files=consumed_files,\n        file_errors_map=file_errors_map,\n        new_images=new_images,\n        new_image_files=new_image_files,\n        new_folder_upload=[],\n    )\n", "path": "app/grandchallenge/cases/image_builders/dicom.py"}]}