We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
NIFTI Division by zero error
When converting a nifti file only consisting of zeros, the to_target_datatype function divides by 0.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wkcuber/convert_nifti.py`
Content:
```
1 import time
2 import logging
3 import numpy as np
4 from argparse import ArgumentParser
5 from pathlib import Path
6 import nibabel as nib
7 
8 from wkcuber.api.Dataset import WKDataset, TiffDataset
9 from .utils import (
10     add_verbose_flag,
11     setup_logging,
12     add_scale_flag,
13     pad_or_crop_to_size_and_topleft,
14     parse_bounding_box,
15 )
16 
17 from wkcuber.utils import DEFAULT_WKW_FILE_LEN, DEFAULT_WKW_VOXELS_PER_BLOCK
18 
19 
20 def create_parser():
21     parser = ArgumentParser()
22 
23     parser.add_argument(
24         "source_path",
25         help="Path to NIFTY file or to a directory if multiple NIFTI files should be converted. "
26         "In the latter case, also see --color_file and --segmentation_file.",
27     )
28 
29     parser.add_argument(
30         "target_path", help="Output directory for the generated WKW dataset."
31     )
32 
33     parser.add_argument(
34         "--layer_name",
35         "-l",
36         help="Name of the cubed layer (color or segmentation).",
37         default="color",
38     )
39 
40     parser.add_argument(
41         "--dtype", "-d", help="Target datatype (e.g. uint8, uint16).", default="uint8"
42     )
43 
44     parser.add_argument(
45         "--color_file",
46         help="When converting folder, name of file to become color layer",
47         default=None,
48     )
49 
50     parser.add_argument(
51         "--segmentation_file",
52         help="When converting folder, name of file to become segmentation layer",
53         default=None,
54     )
55 
56     parser.add_argument(
57         "--write_tiff",
58         help="Output tiff dataset instead of wkw.",
59         default=False,
60         action="store_true",
61     )
62 
63     parser.add_argument(
64         "--use_orientation_header",
65         help="Use orientation information from header to interpret the input data (should be tried if output orientation seems to be wrong).",
66         default=False,
67         action="store_true",
68     )
69 
70     parser.add_argument(
71         "--enforce_bounding_box",
72         help="The BoundingBox to which the input data should be written. If the input data is too small, it will be padded. If it's too large, it will be cropped. The input format is x,y,z,width,height,depth.",
73         default=None,
74         type=parse_bounding_box,
75     )
76 
77     parser.add_argument(
78         "--flip_axes",
79         help="The axes at which should be flipped. Input format is a comma separated list of axis indices. For example, 1,2,3 will flip the x, y and z axes.",
80         default=None,
81     )
82 
83     add_scale_flag(parser, required=False)
84     add_verbose_flag(parser)
85 
86     return parser
87 
88 
89 def to_target_datatype(
90     data: np.ndarray, target_dtype, is_probably_binary: bool
91 ) -> np.ndarray:
92     if is_probably_binary:
93         logging.info(
94             f"Casting directly to {target_dtype}, as input seems to be binary."
95         )
96         return data.astype(np.dtype(target_dtype))
97 
98     if data.dtype == np.dtype("float32"):
99         factor = data.max()
100     elif data.dtype == np.dtype("float64"):
101         factor = data.max() / np.iinfo(target_dtype).max
102     else:
103         factor = np.iinfo(data.dtype).max / np.iinfo(target_dtype).max
104 
105     return (data / factor).astype(np.dtype(target_dtype))
106 
107 
108 def convert_nifti(
109     source_nifti_path,
110     target_path,
111     layer_name,
112     dtype,
113     scale,
114     mag=1,
115     file_len=DEFAULT_WKW_FILE_LEN,
116     bbox_to_enforce=None,
117     write_tiff=False,
118     use_orientation_header=False,
119     flip_axes=None,
120 ):
121     voxels_per_cube = file_len * DEFAULT_WKW_VOXELS_PER_BLOCK
122     ref_time = time.time()
123 
124     source_nifti = nib.load(str(source_nifti_path.resolve()))
125 
126     if use_orientation_header:
127         # Get canonical representation of data to incorporate
128         # encoded transformations. Needs to be flipped later
129         # to match the coordinate system of WKW.
130         source_nifti = nib.funcs.as_closest_canonical(source_nifti, enforce_diag=False)
131 
132     cube_data = np.array(source_nifti.get_fdata())
133 
134     is_probably_binary = np.unique(cube_data).shape[0] == 2
135     assume_segmentation_layer = (
136         False
137     )  # Since webKnossos does not support multiple segmention layers, this is hardcoded to False right now.
138 
139     max_cell_id_args = (
140         {"largest_segment_id": int(np.max(cube_data) + 1)}
141         if assume_segmentation_layer
142         else {}
143     )
144     category_type = "segmentation" if assume_segmentation_layer else "color"
145     logging.debug(f"Assuming {category_type} as layer type for {layer_name}")
146 
147     if len(source_nifti.shape) == 3:
148         cube_data = cube_data.reshape((1,) + source_nifti.shape)
149 
150     elif len(source_nifti.shape) == 4:
151         cube_data = np.transpose(cube_data, (3, 0, 1, 2))
152 
153     else:
154         logging.warning(
155             "Converting of {} failed! Too many or too less dimensions".format(
156                 source_nifti_path
157             )
158         )
159 
160         return
161 
162     if use_orientation_header:
163         # Flip y and z to transform data into wkw's coordinate system.
164         cube_data = np.flip(cube_data, (2, 3))
165 
166     if flip_axes:
167         cube_data = np.flip(cube_data, flip_axes)
168 
169     if scale is None:
170         scale = tuple(map(float, source_nifti.header["pixdim"][:3]))
171     logging.info(f"Using scale: {scale}")
172     cube_data = to_target_datatype(cube_data, dtype, is_probably_binary)
173 
174     # everything needs to be padded to
175     if bbox_to_enforce is not None:
176         target_topleft = np.array((0,) + tuple(bbox_to_enforce.topleft))
177         target_size = np.array((1,) + tuple(bbox_to_enforce.size))
178 
179         cube_data = pad_or_crop_to_size_and_topleft(
180             cube_data, target_size, target_topleft
181         )
182 
183     # Writing wkw compressed requires files of shape (voxels_per_cube, voxels_per_cube, voxels_per_cube)
184     # Pad data accordingly
185     padding_offset = voxels_per_cube - np.array(cube_data.shape[1:4]) % voxels_per_cube
186     padding_offset = (0, 0, 0)
187     cube_data = np.pad(
188         cube_data,
189         (
190             (0, 0),
191             (0, int(padding_offset[0])),
192             (0, int(padding_offset[1])),
193             (0, int(padding_offset[2])),
194         ),
195     )
196 
197     if write_tiff:
198         ds = TiffDataset.get_or_create(target_path, scale=scale or (1, 1, 1))
199         layer = ds.get_or_add_layer(
200             layer_name, category_type, np.dtype(dtype), **max_cell_id_args
201         )
202         mag = layer.get_or_add_mag("1")
203 
204         mag.write(cube_data.squeeze())
205     else:
206         ds = WKDataset.get_or_create(target_path, scale=scale or (1, 1, 1))
207         layer = ds.get_or_add_layer(
208             layer_name, category_type, np.dtype(dtype), **max_cell_id_args
209         )
210         mag = layer.get_or_add_mag("1", file_len=file_len)
211         mag.write(cube_data)
212 
213     logging.debug(
214         "Converting of {} took {:.8f}s".format(
215             source_nifti_path, time.time() - ref_time
216         )
217     )
218 
219 
220 def convert_folder_nifti(
221     source_folder_path,
222     target_path,
223     color_subpath,
224     segmentation_subpath,
225     scale,
226     use_orientation_header=False,
227     bbox_to_enforce=None,
228     write_tiff=False,
229     flip_axes=None,
230 ):
231     paths = list(source_folder_path.rglob("**/*.nii"))
232 
233     color_path = None
234     segmentation_path = None
235     if color_subpath is not None:
236         color_path = target_path / color_subpath
237         if color_path not in paths:
238             logging.warning(
239                 "Specified color file {} not in source path {}!".format(
240                     color_path, source_folder_path
241                 )
242             )
243 
244     if segmentation_subpath is not None:
245         segmentation_path = target_path / segmentation_subpath
246         if segmentation_path not in paths:
247             logging.warning(
248                 "Specified segmentation_file file {} not in source path {}!".format(
249                     segmentation_path, segmentation_path
250                 )
251             )
252 
253     logging.info("Segmentation file will also use uint8 as a datatype.")
254 
255     conversion_args = {
256         "scale": scale,
257         "write_tiff": write_tiff,
258         "bbox_to_enforce": bbox_to_enforce,
259         "use_orientation_header": use_orientation_header,
260         "flip_axes": flip_axes,
261     }
262     for path in paths:
263         if path == color_path:
264             convert_nifti(path, target_path, "color", "uint8", **conversion_args)
265         elif path == segmentation_path:
266             convert_nifti(path, target_path, "segmentation", "uint8", **conversion_args)
267         else:
268             convert_nifti(path, target_path, path.stem, "uint8", **conversion_args)
269 
270 
271 def main():
272     args = create_parser().parse_args()
273     setup_logging(args)
274 
275     source_path = Path(args.source_path)
276 
277     flip_axes = None
278     if args.flip_axes is not None:
279         flip_axes = tuple(int(x) for x in args.flip_axes.split(","))
280         for index in flip_axes:
281             assert (
282                 0 <= index <= 3
283             ), "flip_axes parameter must only contain indices between 0 and 3."
284 
285     conversion_args = {
286         "scale": args.scale,
287         "write_tiff": args.write_tiff,
288         "bbox_to_enforce": args.enforce_bounding_box,
289         "use_orientation_header": args.use_orientation_header,
290         "flip_axes": flip_axes,
291     }
292 
293     if source_path.is_dir():
294         convert_folder_nifti(
295             source_path,
296             Path(args.target_path),
297             args.color_file,
298             args.segmentation_file,
299             **conversion_args,
300         )
301     else:
302         convert_nifti(
303             source_path,
304             Path(args.target_path),
305             args.layer_name,
306             args.dtype,
307             **conversion_args,
308         )
309 
310 
311 if __name__ == "__main__":
312     main()
313
```
--- 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/wkcuber/convert_nifti.py b/wkcuber/convert_nifti.py
--- a/wkcuber/convert_nifti.py
+++ b/wkcuber/convert_nifti.py
@@ -1,21 +1,22 @@
-import time
 import logging
-import numpy as np
+import time
 from argparse import ArgumentParser
 from pathlib import Path
+
 import nibabel as nib
+import numpy as np
 
-from wkcuber.api.Dataset import WKDataset, TiffDataset
-from .utils import (
-    add_verbose_flag,
-    setup_logging,
+from wkcuber.api.Dataset import TiffDataset, WKDataset
+from wkcuber.utils import (
+    DEFAULT_WKW_FILE_LEN,
+    DEFAULT_WKW_VOXELS_PER_BLOCK,
     add_scale_flag,
+    add_verbose_flag,
     pad_or_crop_to_size_and_topleft,
     parse_bounding_box,
+    setup_logging,
 )
 
-from wkcuber.utils import DEFAULT_WKW_FILE_LEN, DEFAULT_WKW_VOXELS_PER_BLOCK
-
 
 def create_parser():
     parser = ArgumentParser()
@@ -102,6 +103,10 @@
     else:
         factor = np.iinfo(data.dtype).max / np.iinfo(target_dtype).max
 
+    if data.max() == 0:
+        logging.warning("Not rescaling data since maximum is 0")
+        factor = 1
+
     return (data / factor).astype(np.dtype(target_dtype))

{"golden_diff": "diff --git a/wkcuber/convert_nifti.py b/wkcuber/convert_nifti.py\n--- a/wkcuber/convert_nifti.py\n+++ b/wkcuber/convert_nifti.py\n@@ -1,21 +1,22 @@\n-import time\n import logging\n-import numpy as np\n+import time\n from argparse import ArgumentParser\n from pathlib import Path\n+\n import nibabel as nib\n+import numpy as np\n \n-from wkcuber.api.Dataset import WKDataset, TiffDataset\n-from .utils import (\n-    add_verbose_flag,\n-    setup_logging,\n+from wkcuber.api.Dataset import TiffDataset, WKDataset\n+from wkcuber.utils import (\n+    DEFAULT_WKW_FILE_LEN,\n+    DEFAULT_WKW_VOXELS_PER_BLOCK,\n     add_scale_flag,\n+    add_verbose_flag,\n     pad_or_crop_to_size_and_topleft,\n     parse_bounding_box,\n+    setup_logging,\n )\n \n-from wkcuber.utils import DEFAULT_WKW_FILE_LEN, DEFAULT_WKW_VOXELS_PER_BLOCK\n-\n \n def create_parser():\n     parser = ArgumentParser()\n@@ -102,6 +103,10 @@\n     else:\n         factor = np.iinfo(data.dtype).max / np.iinfo(target_dtype).max\n \n+    if data.max() == 0:\n+        logging.warning(\"Not rescaling data since maximum is 0\")\n+        factor = 1\n+\n     return (data / factor).astype(np.dtype(target_dtype))\n", "issue": "NIFTI Division by zero error\nWhen converting a nifti file only consisting of zeros, the to_target_datatype function divides by 0.\n", "before_files": [{"content": "import time\nimport logging\nimport numpy as np\nfrom argparse import ArgumentParser\nfrom pathlib import Path\nimport nibabel as nib\n\nfrom wkcuber.api.Dataset import WKDataset, TiffDataset\nfrom .utils import (\n    add_verbose_flag,\n    setup_logging,\n    add_scale_flag,\n    pad_or_crop_to_size_and_topleft,\n    parse_bounding_box,\n)\n\nfrom wkcuber.utils import DEFAULT_WKW_FILE_LEN, DEFAULT_WKW_VOXELS_PER_BLOCK\n\n\ndef create_parser():\n    parser = ArgumentParser()\n\n    parser.add_argument(\n        \"source_path\",\n        help=\"Path to NIFTY file or to a directory if multiple NIFTI files should be converted. \"\n        \"In the latter case, also see --color_file and --segmentation_file.\",\n    )\n\n    parser.add_argument(\n        \"target_path\", help=\"Output directory for the generated WKW dataset.\"\n    )\n\n    parser.add_argument(\n        \"--layer_name\",\n        \"-l\",\n        help=\"Name of the cubed layer (color or segmentation).\",\n        default=\"color\",\n    )\n\n    parser.add_argument(\n        \"--dtype\", \"-d\", help=\"Target datatype (e.g. uint8, uint16).\", default=\"uint8\"\n    )\n\n    parser.add_argument(\n        \"--color_file\",\n        help=\"When converting folder, name of file to become color layer\",\n        default=None,\n    )\n\n    parser.add_argument(\n        \"--segmentation_file\",\n        help=\"When converting folder, name of file to become segmentation layer\",\n        default=None,\n    )\n\n    parser.add_argument(\n        \"--write_tiff\",\n        help=\"Output tiff dataset instead of wkw.\",\n        default=False,\n        action=\"store_true\",\n    )\n\n    parser.add_argument(\n        \"--use_orientation_header\",\n        help=\"Use orientation information from header to interpret the input data (should be tried if output orientation seems to be wrong).\",\n        default=False,\n        action=\"store_true\",\n    )\n\n    parser.add_argument(\n        \"--enforce_bounding_box\",\n        help=\"The BoundingBox to which the input data should be written. If the input data is too small, it will be padded. If it's too large, it will be cropped. The input format is x,y,z,width,height,depth.\",\n        default=None,\n        type=parse_bounding_box,\n    )\n\n    parser.add_argument(\n        \"--flip_axes\",\n        help=\"The axes at which should be flipped. Input format is a comma separated list of axis indices. For example, 1,2,3 will flip the x, y and z axes.\",\n        default=None,\n    )\n\n    add_scale_flag(parser, required=False)\n    add_verbose_flag(parser)\n\n    return parser\n\n\ndef to_target_datatype(\n    data: np.ndarray, target_dtype, is_probably_binary: bool\n) -> np.ndarray:\n    if is_probably_binary:\n        logging.info(\n            f\"Casting directly to {target_dtype}, as input seems to be binary.\"\n        )\n        return data.astype(np.dtype(target_dtype))\n\n    if data.dtype == np.dtype(\"float32\"):\n        factor = data.max()\n    elif data.dtype == np.dtype(\"float64\"):\n        factor = data.max() / np.iinfo(target_dtype).max\n    else:\n        factor = np.iinfo(data.dtype).max / np.iinfo(target_dtype).max\n\n    return (data / factor).astype(np.dtype(target_dtype))\n\n\ndef convert_nifti(\n    source_nifti_path,\n    target_path,\n    layer_name,\n    dtype,\n    scale,\n    mag=1,\n    file_len=DEFAULT_WKW_FILE_LEN,\n    bbox_to_enforce=None,\n    write_tiff=False,\n    use_orientation_header=False,\n    flip_axes=None,\n):\n    voxels_per_cube = file_len * DEFAULT_WKW_VOXELS_PER_BLOCK\n    ref_time = time.time()\n\n    source_nifti = nib.load(str(source_nifti_path.resolve()))\n\n    if use_orientation_header:\n        # Get canonical representation of data to incorporate\n        # encoded transformations. Needs to be flipped later\n        # to match the coordinate system of WKW.\n        source_nifti = nib.funcs.as_closest_canonical(source_nifti, enforce_diag=False)\n\n    cube_data = np.array(source_nifti.get_fdata())\n\n    is_probably_binary = np.unique(cube_data).shape[0] == 2\n    assume_segmentation_layer = (\n        False\n    )  # Since webKnossos does not support multiple segmention layers, this is hardcoded to False right now.\n\n    max_cell_id_args = (\n        {\"largest_segment_id\": int(np.max(cube_data) + 1)}\n        if assume_segmentation_layer\n        else {}\n    )\n    category_type = \"segmentation\" if assume_segmentation_layer else \"color\"\n    logging.debug(f\"Assuming {category_type} as layer type for {layer_name}\")\n\n    if len(source_nifti.shape) == 3:\n        cube_data = cube_data.reshape((1,) + source_nifti.shape)\n\n    elif len(source_nifti.shape) == 4:\n        cube_data = np.transpose(cube_data, (3, 0, 1, 2))\n\n    else:\n        logging.warning(\n            \"Converting of {} failed! Too many or too less dimensions\".format(\n                source_nifti_path\n            )\n        )\n\n        return\n\n    if use_orientation_header:\n        # Flip y and z to transform data into wkw's coordinate system.\n        cube_data = np.flip(cube_data, (2, 3))\n\n    if flip_axes:\n        cube_data = np.flip(cube_data, flip_axes)\n\n    if scale is None:\n        scale = tuple(map(float, source_nifti.header[\"pixdim\"][:3]))\n    logging.info(f\"Using scale: {scale}\")\n    cube_data = to_target_datatype(cube_data, dtype, is_probably_binary)\n\n    # everything needs to be padded to\n    if bbox_to_enforce is not None:\n        target_topleft = np.array((0,) + tuple(bbox_to_enforce.topleft))\n        target_size = np.array((1,) + tuple(bbox_to_enforce.size))\n\n        cube_data = pad_or_crop_to_size_and_topleft(\n            cube_data, target_size, target_topleft\n        )\n\n    # Writing wkw compressed requires files of shape (voxels_per_cube, voxels_per_cube, voxels_per_cube)\n    # Pad data accordingly\n    padding_offset = voxels_per_cube - np.array(cube_data.shape[1:4]) % voxels_per_cube\n    padding_offset = (0, 0, 0)\n    cube_data = np.pad(\n        cube_data,\n        (\n            (0, 0),\n            (0, int(padding_offset[0])),\n            (0, int(padding_offset[1])),\n            (0, int(padding_offset[2])),\n        ),\n    )\n\n    if write_tiff:\n        ds = TiffDataset.get_or_create(target_path, scale=scale or (1, 1, 1))\n        layer = ds.get_or_add_layer(\n            layer_name, category_type, np.dtype(dtype), **max_cell_id_args\n        )\n        mag = layer.get_or_add_mag(\"1\")\n\n        mag.write(cube_data.squeeze())\n    else:\n        ds = WKDataset.get_or_create(target_path, scale=scale or (1, 1, 1))\n        layer = ds.get_or_add_layer(\n            layer_name, category_type, np.dtype(dtype), **max_cell_id_args\n        )\n        mag = layer.get_or_add_mag(\"1\", file_len=file_len)\n        mag.write(cube_data)\n\n    logging.debug(\n        \"Converting of {} took {:.8f}s\".format(\n            source_nifti_path, time.time() - ref_time\n        )\n    )\n\n\ndef convert_folder_nifti(\n    source_folder_path,\n    target_path,\n    color_subpath,\n    segmentation_subpath,\n    scale,\n    use_orientation_header=False,\n    bbox_to_enforce=None,\n    write_tiff=False,\n    flip_axes=None,\n):\n    paths = list(source_folder_path.rglob(\"**/*.nii\"))\n\n    color_path = None\n    segmentation_path = None\n    if color_subpath is not None:\n        color_path = target_path / color_subpath\n        if color_path not in paths:\n            logging.warning(\n                \"Specified color file {} not in source path {}!\".format(\n                    color_path, source_folder_path\n                )\n            )\n\n    if segmentation_subpath is not None:\n        segmentation_path = target_path / segmentation_subpath\n        if segmentation_path not in paths:\n            logging.warning(\n                \"Specified segmentation_file file {} not in source path {}!\".format(\n                    segmentation_path, segmentation_path\n                )\n            )\n\n    logging.info(\"Segmentation file will also use uint8 as a datatype.\")\n\n    conversion_args = {\n        \"scale\": scale,\n        \"write_tiff\": write_tiff,\n        \"bbox_to_enforce\": bbox_to_enforce,\n        \"use_orientation_header\": use_orientation_header,\n        \"flip_axes\": flip_axes,\n    }\n    for path in paths:\n        if path == color_path:\n            convert_nifti(path, target_path, \"color\", \"uint8\", **conversion_args)\n        elif path == segmentation_path:\n            convert_nifti(path, target_path, \"segmentation\", \"uint8\", **conversion_args)\n        else:\n            convert_nifti(path, target_path, path.stem, \"uint8\", **conversion_args)\n\n\ndef main():\n    args = create_parser().parse_args()\n    setup_logging(args)\n\n    source_path = Path(args.source_path)\n\n    flip_axes = None\n    if args.flip_axes is not None:\n        flip_axes = tuple(int(x) for x in args.flip_axes.split(\",\"))\n        for index in flip_axes:\n            assert (\n                0 <= index <= 3\n            ), \"flip_axes parameter must only contain indices between 0 and 3.\"\n\n    conversion_args = {\n        \"scale\": args.scale,\n        \"write_tiff\": args.write_tiff,\n        \"bbox_to_enforce\": args.enforce_bounding_box,\n        \"use_orientation_header\": args.use_orientation_header,\n        \"flip_axes\": flip_axes,\n    }\n\n    if source_path.is_dir():\n        convert_folder_nifti(\n            source_path,\n            Path(args.target_path),\n            args.color_file,\n            args.segmentation_file,\n            **conversion_args,\n        )\n    else:\n        convert_nifti(\n            source_path,\n            Path(args.target_path),\n            args.layer_name,\n            args.dtype,\n            **conversion_args,\n        )\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "wkcuber/convert_nifti.py"}], "after_files": [{"content": "import logging\nimport time\nfrom argparse import ArgumentParser\nfrom pathlib import Path\n\nimport nibabel as nib\nimport numpy as np\n\nfrom wkcuber.api.Dataset import TiffDataset, WKDataset\nfrom wkcuber.utils import (\n    DEFAULT_WKW_FILE_LEN,\n    DEFAULT_WKW_VOXELS_PER_BLOCK,\n    add_scale_flag,\n    add_verbose_flag,\n    pad_or_crop_to_size_and_topleft,\n    parse_bounding_box,\n    setup_logging,\n)\n\n\ndef create_parser():\n    parser = ArgumentParser()\n\n    parser.add_argument(\n        \"source_path\",\n        help=\"Path to NIFTY file or to a directory if multiple NIFTI files should be converted. \"\n        \"In the latter case, also see --color_file and --segmentation_file.\",\n    )\n\n    parser.add_argument(\n        \"target_path\", help=\"Output directory for the generated WKW dataset.\"\n    )\n\n    parser.add_argument(\n        \"--layer_name\",\n        \"-l\",\n        help=\"Name of the cubed layer (color or segmentation).\",\n        default=\"color\",\n    )\n\n    parser.add_argument(\n        \"--dtype\", \"-d\", help=\"Target datatype (e.g. uint8, uint16).\", default=\"uint8\"\n    )\n\n    parser.add_argument(\n        \"--color_file\",\n        help=\"When converting folder, name of file to become color layer\",\n        default=None,\n    )\n\n    parser.add_argument(\n        \"--segmentation_file\",\n        help=\"When converting folder, name of file to become segmentation layer\",\n        default=None,\n    )\n\n    parser.add_argument(\n        \"--write_tiff\",\n        help=\"Output tiff dataset instead of wkw.\",\n        default=False,\n        action=\"store_true\",\n    )\n\n    parser.add_argument(\n        \"--use_orientation_header\",\n        help=\"Use orientation information from header to interpret the input data (should be tried if output orientation seems to be wrong).\",\n        default=False,\n        action=\"store_true\",\n    )\n\n    parser.add_argument(\n        \"--enforce_bounding_box\",\n        help=\"The BoundingBox to which the input data should be written. If the input data is too small, it will be padded. If it's too large, it will be cropped. The input format is x,y,z,width,height,depth.\",\n        default=None,\n        type=parse_bounding_box,\n    )\n\n    parser.add_argument(\n        \"--flip_axes\",\n        help=\"The axes at which should be flipped. Input format is a comma separated list of axis indices. For example, 1,2,3 will flip the x, y and z axes.\",\n        default=None,\n    )\n\n    add_scale_flag(parser, required=False)\n    add_verbose_flag(parser)\n\n    return parser\n\n\ndef to_target_datatype(\n    data: np.ndarray, target_dtype, is_probably_binary: bool\n) -> np.ndarray:\n    if is_probably_binary:\n        logging.info(\n            f\"Casting directly to {target_dtype}, as input seems to be binary.\"\n        )\n        return data.astype(np.dtype(target_dtype))\n\n    if data.dtype == np.dtype(\"float32\"):\n        factor = data.max()\n    elif data.dtype == np.dtype(\"float64\"):\n        factor = data.max() / np.iinfo(target_dtype).max\n    else:\n        factor = np.iinfo(data.dtype).max / np.iinfo(target_dtype).max\n\n    if data.max() == 0:\n        logging.warning(\"Not rescaling data since maximum is 0\")\n        factor = 1\n\n    return (data / factor).astype(np.dtype(target_dtype))\n\n\ndef convert_nifti(\n    source_nifti_path,\n    target_path,\n    layer_name,\n    dtype,\n    scale,\n    mag=1,\n    file_len=DEFAULT_WKW_FILE_LEN,\n    bbox_to_enforce=None,\n    write_tiff=False,\n    use_orientation_header=False,\n    flip_axes=None,\n):\n    voxels_per_cube = file_len * DEFAULT_WKW_VOXELS_PER_BLOCK\n    ref_time = time.time()\n\n    source_nifti = nib.load(str(source_nifti_path.resolve()))\n\n    if use_orientation_header:\n        # Get canonical representation of data to incorporate\n        # encoded transformations. Needs to be flipped later\n        # to match the coordinate system of WKW.\n        source_nifti = nib.funcs.as_closest_canonical(source_nifti, enforce_diag=False)\n\n    cube_data = np.array(source_nifti.get_fdata())\n\n    is_probably_binary = np.unique(cube_data).shape[0] == 2\n    assume_segmentation_layer = (\n        False\n    )  # Since webKnossos does not support multiple segmention layers, this is hardcoded to False right now.\n\n    max_cell_id_args = (\n        {\"largest_segment_id\": int(np.max(cube_data) + 1)}\n        if assume_segmentation_layer\n        else {}\n    )\n    category_type = \"segmentation\" if assume_segmentation_layer else \"color\"\n    logging.debug(f\"Assuming {category_type} as layer type for {layer_name}\")\n\n    if len(source_nifti.shape) == 3:\n        cube_data = cube_data.reshape((1,) + source_nifti.shape)\n\n    elif len(source_nifti.shape) == 4:\n        cube_data = np.transpose(cube_data, (3, 0, 1, 2))\n\n    else:\n        logging.warning(\n            \"Converting of {} failed! Too many or too less dimensions\".format(\n                source_nifti_path\n            )\n        )\n\n        return\n\n    if use_orientation_header:\n        # Flip y and z to transform data into wkw's coordinate system.\n        cube_data = np.flip(cube_data, (2, 3))\n\n    if flip_axes:\n        cube_data = np.flip(cube_data, flip_axes)\n\n    if scale is None:\n        scale = tuple(map(float, source_nifti.header[\"pixdim\"][:3]))\n    logging.info(f\"Using scale: {scale}\")\n    cube_data = to_target_datatype(cube_data, dtype, is_probably_binary)\n\n    # everything needs to be padded to\n    if bbox_to_enforce is not None:\n        target_topleft = np.array((0,) + tuple(bbox_to_enforce.topleft))\n        target_size = np.array((1,) + tuple(bbox_to_enforce.size))\n\n        cube_data = pad_or_crop_to_size_and_topleft(\n            cube_data, target_size, target_topleft\n        )\n\n    # Writing wkw compressed requires files of shape (voxels_per_cube, voxels_per_cube, voxels_per_cube)\n    # Pad data accordingly\n    padding_offset = voxels_per_cube - np.array(cube_data.shape[1:4]) % voxels_per_cube\n    padding_offset = (0, 0, 0)\n    cube_data = np.pad(\n        cube_data,\n        (\n            (0, 0),\n            (0, int(padding_offset[0])),\n            (0, int(padding_offset[1])),\n            (0, int(padding_offset[2])),\n        ),\n    )\n\n    if write_tiff:\n        ds = TiffDataset.get_or_create(target_path, scale=scale or (1, 1, 1))\n        layer = ds.get_or_add_layer(\n            layer_name, category_type, np.dtype(dtype), **max_cell_id_args\n        )\n        mag = layer.get_or_add_mag(\"1\")\n\n        mag.write(cube_data.squeeze())\n    else:\n        ds = WKDataset.get_or_create(target_path, scale=scale or (1, 1, 1))\n        layer = ds.get_or_add_layer(\n            layer_name, category_type, np.dtype(dtype), **max_cell_id_args\n        )\n        mag = layer.get_or_add_mag(\"1\", file_len=file_len)\n        mag.write(cube_data)\n\n    logging.debug(\n        \"Converting of {} took {:.8f}s\".format(\n            source_nifti_path, time.time() - ref_time\n        )\n    )\n\n\ndef convert_folder_nifti(\n    source_folder_path,\n    target_path,\n    color_subpath,\n    segmentation_subpath,\n    scale,\n    use_orientation_header=False,\n    bbox_to_enforce=None,\n    write_tiff=False,\n    flip_axes=None,\n):\n    paths = list(source_folder_path.rglob(\"**/*.nii\"))\n\n    color_path = None\n    segmentation_path = None\n    if color_subpath is not None:\n        color_path = target_path / color_subpath\n        if color_path not in paths:\n            logging.warning(\n                \"Specified color file {} not in source path {}!\".format(\n                    color_path, source_folder_path\n                )\n            )\n\n    if segmentation_subpath is not None:\n        segmentation_path = target_path / segmentation_subpath\n        if segmentation_path not in paths:\n            logging.warning(\n                \"Specified segmentation_file file {} not in source path {}!\".format(\n                    segmentation_path, segmentation_path\n                )\n            )\n\n    logging.info(\"Segmentation file will also use uint8 as a datatype.\")\n\n    conversion_args = {\n        \"scale\": scale,\n        \"write_tiff\": write_tiff,\n        \"bbox_to_enforce\": bbox_to_enforce,\n        \"use_orientation_header\": use_orientation_header,\n        \"flip_axes\": flip_axes,\n    }\n    for path in paths:\n        if path == color_path:\n            convert_nifti(path, target_path, \"color\", \"uint8\", **conversion_args)\n        elif path == segmentation_path:\n            convert_nifti(path, target_path, \"segmentation\", \"uint8\", **conversion_args)\n        else:\n            convert_nifti(path, target_path, path.stem, \"uint8\", **conversion_args)\n\n\ndef main():\n    args = create_parser().parse_args()\n    setup_logging(args)\n\n    source_path = Path(args.source_path)\n\n    flip_axes = None\n    if args.flip_axes is not None:\n        flip_axes = tuple(int(x) for x in args.flip_axes.split(\",\"))\n        for index in flip_axes:\n            assert (\n                0 <= index <= 3\n            ), \"flip_axes parameter must only contain indices between 0 and 3.\"\n\n    conversion_args = {\n        \"scale\": args.scale,\n        \"write_tiff\": args.write_tiff,\n        \"bbox_to_enforce\": args.enforce_bounding_box,\n        \"use_orientation_header\": args.use_orientation_header,\n        \"flip_axes\": flip_axes,\n    }\n\n    if source_path.is_dir():\n        convert_folder_nifti(\n            source_path,\n            Path(args.target_path),\n            args.color_file,\n            args.segmentation_file,\n            **conversion_args,\n        )\n    else:\n        convert_nifti(\n            source_path,\n            Path(args.target_path),\n            args.layer_name,\n            args.dtype,\n            **conversion_args,\n        )\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "wkcuber/convert_nifti.py"}]}