Skip to content

Commit 82bd4ba

Browse files
committed
NF+BF+TST - try offset for all int scaling
Try subtracting offset for int output (as well as uint output) to see if it will put values within output range. Add int_abs when testing for mn / mx sign flip (corner case) Refactor (u)int to (u)int scaling into own method
1 parent 7d48ef1 commit 82bd4ba

File tree

2 files changed

+34
-38
lines changed

2 files changed

+34
-38
lines changed

nibabel/arraywriters.py

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def __init__(self, array, out_dtype=None)
2323

2424
import numpy as np
2525

26-
from .casting import shared_range, int_to_float, as_int
26+
from .casting import shared_range, int_to_float, as_int, int_abs
2727
from .volumeutils import finite_range, array_to_file
2828

2929

@@ -277,11 +277,10 @@ def to_fileobj(self, fileobj, order='F', nan2zero=True):
277277

278278
def _do_scaling(self):
279279
arr = self._array
280-
arr_dtype = arr.dtype
281280
out_dtype = self._out_dtype
282281
assert out_dtype.kind in 'iu'
283282
mn, mx = self.finite_range()
284-
if arr_dtype.kind == 'f':
283+
if arr.dtype.kind == 'f':
285284
# Float to (u)int scaling
286285
self._range_scale()
287286
return
@@ -291,10 +290,21 @@ def _do_scaling(self):
291290
if mx <= out_max and mn >= out_min: # already in range
292291
return
293292
# (u)int to (u)int scaling
293+
self._iu2iu()
294+
295+
def _iu2iu(self):
296+
# (u)int to (u)int scaling
297+
mn, mx = self.finite_range()
294298
if self._out_dtype.kind == 'u':
299+
# We're checking for a sign flip. This can only work for uint
300+
# output, because, for int output, the abs min of the type is
301+
# greater than the abs max, so the data either fit into the range in
302+
# the test above, or this test won't pass
295303
shared_min, shared_max = shared_range(self.scaler_dtype,
296304
self._out_dtype)
297-
if mx <= 0 and abs(mn) <= shared_max: # sign flip enough?
305+
# Need abs that deals with max neg ints. abs problem only arises
306+
# when all the data is set to max neg integer value
307+
if mx <= 0 and int_abs(mn) <= shared_max: # sign flip enough?
298308
# -1.0 * arr will be in scaler_dtype precision
299309
self.slope = -1.0
300310
return
@@ -406,42 +416,20 @@ def to_fileobj(self, fileobj, order='F', nan2zero=True):
406416
order=order,
407417
nan2zero=nan2zero)
408418

409-
def _do_scaling(self):
410-
""" Calculate / set scaling for floats/(u)ints to (u)ints
411-
"""
412-
arr = self._array
413-
arr_dtype = arr.dtype
414-
out_dtype = self._out_dtype
415-
assert out_dtype.kind in 'iu'
416-
mn, mx = self.finite_range()
417-
if mn == np.inf : # No valid data
418-
return
419-
if (mn, mx) == (0.0, 0.0): # Data all zero
420-
return
421-
if arr_dtype.kind == 'f':
422-
# Float to (u)int scaling
423-
self._range_scale()
424-
return
419+
def _iu2iu(self):
425420
# (u)int to (u)int
426-
info = np.iinfo(out_dtype)
427-
out_max, out_min = info.max, info.min
428-
if mx <= out_max and mn >= out_min: # already in range
421+
mn, mx = self.finite_range()
422+
shared_min, shared_max = shared_range(self.scaler_dtype,
423+
self._out_dtype)
424+
# range may be greater than the largest integer for this type.
425+
# as_int needed to work round numpy 1.4.1 int casting bug
426+
type_range = as_int(shared_max) - as_int(shared_min)
427+
mn2mx = as_int(mx) - as_int(mn)
428+
if mn2mx <= type_range: # offset enough?
429+
self.inter = mn - shared_min
429430
return
430-
# (u)int to (u)int scaling
431-
if self._out_dtype.kind == 'u':
432-
shared_min, shared_max = shared_range(self.scaler_dtype,
433-
self._out_dtype)
434-
# range may be greater than the largest integer for this type.
435-
# as_int needed to work round numpy 1.4.1 int casting bug
436-
mn2mx = as_int(mx) - as_int(mn)
437-
if mn2mx <= shared_max: # offset enough?
438-
self.inter = mn
439-
return
440-
if mx <= 0 and abs(mn) <= shared_max: # sign flip enough?
441-
# -1.0 * arr will be in scaler_dtype precision
442-
self.slope = -1.0
443-
return
444-
self._range_scale()
431+
# Try slope options (sign flip) and then range scaling
432+
super(SlopeInterArrayWriter, self)._iu2iu()
445433

446434
def _range_scale(self):
447435
""" Calculate scaling, intercept based on data range and output type """

nibabel/tests/test_arraywriters.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ def test_no_offset_scale():
312312
assert_equal(aw.slope, 2)
313313
aw = SAW(np.array([-128 * 2.0, 127], dtype=np.float32), np.int8)
314314
assert_equal(aw.slope, 2)
315+
# Test that nasty abs behavior does not upset us
316+
n = -2**15
317+
aw = SAW(np.array([n, n], dtype=np.int16), np.uint8)
318+
assert_array_almost_equal(aw.slope, n / 255.0, 5)
315319

316320

317321
def test_with_offset_scale():
@@ -325,6 +329,10 @@ def test_with_offset_scale():
325329
assert_equal((aw.slope, aw.inter), (1, -1)) # offset only
326330
aw = SIAW(np.array([-1, 255], dtype=np.int16), np.uint8)
327331
assert_not_equal((aw.slope, aw.inter), (1, -1)) # Too big for offset only
332+
aw = SIAW(np.array([-256, -2], dtype=np.int16), np.uint8)
333+
assert_equal((aw.slope, aw.inter), (1, -256)) # offset only
334+
aw = SIAW(np.array([-256, -2], dtype=np.int16), np.int8)
335+
assert_equal((aw.slope, aw.inter), (1, -128)) # offset only
328336

329337

330338
def test_io_scaling():

0 commit comments

Comments
 (0)