Skip to content

Commit

Permalink
Merge pull request #475 from aleju/deterministic_list
Browse files Browse the repository at this point in the history
Add DeterministicList parameter
  • Loading branch information
aleju committed Nov 23, 2019
2 parents 326315e + c021658 commit 51f0ec6
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
5 changes: 5 additions & 0 deletions changelogs/master/added/20191101_deterministic_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Added DeterministicList #475

* Added `imgaug.parameters.DeterministicList`. Upon a request to generate
samples of shape `S`, this parameter will create a new array of shape `S`
and fill it by cycling over its list of values repeatedly.
62 changes: 62 additions & 0 deletions imgaug/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,68 @@ def __str__(self):
return "Deterministic(%s)" % (str(self.value),)


# TODO replace two-value parameters used in tests with this
class DeterministicList(StochasticParameter):
"""Parameter that repeats elements from a list in the given order.
E.g. of samples of shape ``(A, B, C)`` are requested, this parameter will
return the first ``A*B*C`` elements, reshaped to ``(A, B, C)`` from the
provided list. If the list contains less than ``A*B*C`` elements, it
will (by default) be tiled until it is long enough (i.e. the sampling
will start again at the first element, if necessary multiple times).
Parameters
----------
values : ndarray or iterable of number
An iterable of values to sample from in the order within the iterable.
"""

def __init__(self, values):
super(DeterministicList, self).__init__()

assert ia.is_iterable(values), (
"Expected to get an iterable as input, got type %s." % (
type(values).__name__,))
assert len(values) > 0, ("Expected to get at least one value, got "
"zero.")

if ia.is_np_array(values):
# this would not be able to handle e.g. [[1, 2], [3]] and output
# dtype object due to the non-regular shape, hence we have the
# else block
self.values = values.flatten()
else:
self.values = np.array(list(ia.flatten(values)))
kind = self.values.dtype.kind

# limit to 32bit instead of 64bit for efficiency
if kind == "i":
self.values = self.values.astype(np.int32)
elif kind == "f":
self.values = self.values.astype(np.float32)

def _draw_samples(self, size, random_state):
nb_requested = int(np.prod(size))
values = self.values
if nb_requested > self.values.size:
# we don't use itertools.cycle() here, as that would require
# running through a loop potentially many times (as `size` can
# be very large), which would be slow
multiplier = int(np.ceil(nb_requested / values.size))
values = np.tile(values, (multiplier,))
return values[:nb_requested].reshape(size)

def __repr__(self):
return self.__str__()

def __str__(self):
if self.values.dtype.kind == "f":
values = ["%.4f" % (value,) for value in self.values]
return "DeterministicList([%s])" % (", ".join(values),)
return "DeterministicList(%s)" % (str(self.values.tolist()),)


class Choice(StochasticParameter):
"""Parameter that samples value from a list of allowed values.
Expand Down
134 changes: 134 additions & 0 deletions test/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,140 @@ def test_argument_has_invalid_type(self):
in str(context.exception))


class TestDeterministicList(unittest.TestCase):
def setUp(self):
reseed()

def test___init___with_array(self):
values = np.arange(1*2*3).reshape((1, 2, 3))
param = iap.DeterministicList(values)
assert np.array_equal(param.values, values.flatten())

def test___init___with_list_int(self):
values = [[1, 2], [3, 4]]
param = iap.DeterministicList(values)
assert np.array_equal(param.values, [1, 2, 3, 4])
assert param.values.dtype.name == "int32"

def test___init___with_list_float(self):
values = [[1.1, 2.2], [3.3, 4.4]]
param = iap.DeterministicList(values)
assert np.allclose(param.values, [1.1, 2.2, 3.3, 4.4])
assert param.values.dtype.name == "float32"

def test_samples_same_values_for_same_seeds(self):
values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
param = iap.DeterministicList(values)

rs1 = iarandom.RNG(123456)
rs2 = iarandom.RNG(123456)

samples1 = param.draw_samples(10, random_state=rs1)
samples2 = param.draw_samples(10, random_state=rs2)

assert np.array_equal(samples1, samples2)

def test_draw_sample_int(self):
values = [10, 20, 30, 40, 50]
param = iap.DeterministicList(values)

sample1 = param.draw_sample()
sample2 = param.draw_sample()

assert sample1.shape == tuple()
assert sample1 == sample2

def test_draw_sample_float(self):
values = [10.1, 20.2, 30.3, 40.4, 50.5]
param = iap.DeterministicList(values)

sample1 = param.draw_sample()
sample2 = param.draw_sample()

assert sample1.shape == tuple()
assert np.isclose(
sample1, sample2, rtol=0, atol=_eps(sample1))

def test_draw_samples_int(self):
values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
shapes = [3, (2, 3), (2, 3, 1)]
expecteds = [
[10, 20, 30],
[[10, 20, 30], [40, 50, 60]],
[[[10], [20], [30]], [[40], [50], [60]]]
]
param = iap.DeterministicList(values)
for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)

shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))

assert samples.shape == shape_expected
assert np.array_equal(samples, expected)

def test_draw_samples_float(self):
values = [10.1, 20.2, 30.3, 40.4, 50.5, 60.6, 70.7, 80.8, 90.9, 100.10]
shapes = [3, (2, 3), (2, 3, 1)]
expecteds = [
[10.1, 20.2, 30.3],
[[10.1, 20.2, 30.3], [40.4, 50.5, 60.6]],
[[[10.1], [20.2], [30.3]], [[40.4], [50.5], [60.6]]]
]
param = iap.DeterministicList(values)
for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)

shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))

assert samples.shape == shape_expected
assert np.allclose(samples, expected, rtol=0, atol=1e-5)

def test_draw_samples_cycles_when_shape_too_large(self):
values = [10, 20, 30]
param = iap.DeterministicList(values)

shapes = [(6,), (7,), (8,), (9,), (3, 3)]
expecteds = [
[10, 20, 30, 10, 20, 30],
[10, 20, 30, 10, 20, 30, 10],
[10, 20, 30, 10, 20, 30, 10, 20],
[10, 20, 30, 10, 20, 30, 10, 20, 30],
[[10, 20, 30],
[10, 20, 30],
[10, 20, 30]]
]

for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)

assert np.array_equal(samples, expected)

def test___str___and___repr___float(self):
param = iap.DeterministicList([10.1, 20.2, 30.3])
assert (
param.__str__()
== param.__repr__()
== "DeterministicList([10.1000, 20.2000, 30.3000])"
)

def test___str___and___repr___intlike(self):
param = iap.DeterministicList([10, 20, 30])
assert (
param.__str__()
== param.__repr__()
== "DeterministicList([10, 20, 30])"
)


class TestFromLowerResolution(unittest.TestCase):
def setUp(self):
reseed()
Expand Down

0 comments on commit 51f0ec6

Please sign in to comment.