Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DeterministicList parameter #475

Merged
merged 1 commit into from
Nov 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelogs/master/added/20191101_deterministic_list.md
@@ -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
Expand Up @@ -661,6 +661,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
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