Skip to content

Commit ab96044

Browse files
committed
ENH: Cover edge cases like non-scaling images, bad slices
1 parent ace3e4e commit ab96044

File tree

1 file changed

+37
-13
lines changed

1 file changed

+37
-13
lines changed

nibabel/cmdline/roi.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
import os
13
import argparse
24
import nibabel as nb
35

@@ -6,11 +8,13 @@ def lossless_slice(img, slicers):
68
if not nb.imageclasses.spatial_axes_first(img):
79
raise ValueError("Cannot slice an image that is not known to have spatial axes first")
810

9-
roi_img = img.__class__(
10-
img.dataobj._get_unscaled(slicers),
11-
affine=img.slicer.slice_affine(slicers),
12-
header=img.header)
13-
roi_img.header.set_slope_inter(img.dataobj.slope, img.dataobj.inter)
11+
scaling = hasattr(img.dataobj, 'slope')
12+
13+
data = img.dataobj._get_unscaled(slicers) if scaling else img.dataobj[slicers]
14+
roi_img = img.__class__(data, affine=img.slicer.slice_affine(slicers), header=img.header)
15+
16+
if scaling:
17+
roi_img.header.set_slope_inter(img.dataobj.slope, img.dataobj.inter)
1418
return roi_img
1519

1620

@@ -20,7 +24,7 @@ def parse_slice(crop, allow_step=True):
2024
start, stop, *extra = [int(val) if val else None for val in crop.split(":")]
2125
if len(extra) > 1:
2226
raise ValueError(f"Cannot parse specification: {crop}")
23-
if extra and not allow_step:
27+
if not allow_step and extra and extra[0] not in (1, None):
2428
raise ValueError(f"Step entry not permitted: {crop}")
2529

2630
step = extra[0] if extra else None
@@ -30,9 +34,19 @@ def parse_slice(crop, allow_step=True):
3034
return slice(start, stop, step)
3135

3236

33-
def main():
34-
parser = argparse.ArgumentParser(description="Crop images to a region of interest",
35-
epilog="If a start or stop value is omitted, the start or end of the axis is assumed.")
37+
def sanitize(args):
38+
# Argparse likes to treat "-1:..." as a flag
39+
return [f' {arg}' if arg[0] == '-' and ":" in arg else arg
40+
for arg in args]
41+
42+
43+
def main(args=None):
44+
if args is None:
45+
args = sys.argv[1:]
46+
parser = argparse.ArgumentParser(
47+
description="Crop images to a region of interest",
48+
epilog="If a start or stop value is omitted, the start or end of the axis is assumed.")
49+
parser.add_argument('--version', action='version', version=nb.__version__)
3650
parser.add_argument("-i", metavar="I1:I2[:-1]",
3751
help="Start/stop [flip] along first axis (0-indexed)")
3852
parser.add_argument("-j", metavar="J1:J2[:-1]",
@@ -43,7 +57,7 @@ def main():
4357
parser.add_argument("in_file", help="Image file to crop")
4458
parser.add_argument("out_file", help="Output file name")
4559

46-
opts = parser.parse_args()
60+
opts = parser.parse_args(args=sanitize(args))
4761

4862
try:
4963
islice = parse_slice(opts.i)
@@ -54,10 +68,20 @@ def main():
5468
print(f"Could not parse input arguments. Reason follows.\n{err}")
5569
return 1
5670

57-
img = nb.load(opts.in_file)
71+
kwargs = {}
72+
if os.path.realpath(opts.in_file) == os.path.realpath(opts.out_file):
73+
kwargs['mmap'] = False
74+
img = nb.load(opts.in_file, **kwargs)
75+
76+
slicers = (islice, jslice, kslice, tslice)[:img.ndim]
77+
expected_shape = nb.fileslice.predict_shape(slicers, img.shape)
78+
if any(dim == 0 for dim in expected_shape):
79+
print(f"Cannot take zero-length slices. Predicted shape {expected_shape}.")
80+
return 1
81+
5882
try:
59-
sliced_img = lossless_slice(img, (islice, jslice, kslice, tslice)[:img.ndim])
60-
except:
83+
sliced_img = lossless_slice(img, slicers)
84+
except Exception:
6185
print("Could not slice image. Full traceback follows.")
6286
raise
6387
nb.save(sliced_img, opts.out_file)

0 commit comments

Comments
 (0)