Skip to content

Commit d78b3cb

Browse files
authored
Merge pull request #8052 from nden/fix-bbox-serialization
fix serialization of bounding_box with quantities
2 parents 701b5d6 + c7f5fa2 commit d78b3cb

5 files changed

Lines changed: 70 additions & 50 deletions

File tree

‎CHANGES.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,9 @@ astropy.io.misc
854854
unicode strings. Now those columns are first encoded to UTF-8 and
855855
written as byte strings. [#7024, #8017]
856856

857+
- Fixed a bug with serializing the bounding_box of models initialized
858+
with ``Quantities`` . [#8052]
859+
857860
astropy.io.fits
858861
^^^^^^^^^^^^^^^
859862

@@ -873,6 +876,9 @@ astropy.modeling
873876
- Fix behaviour of certain models with units, by making certain unit-related
874877
attributes readonly. [#7210]
875878

879+
- Fixed an issue with validating a ``bounding_box`` whose items are
880+
``Quantities``. [#8052]
881+
876882
astropy.nddata
877883
^^^^^^^^^^^^^^
878884

‎astropy/io/misc/asdf/tags/transform/basic.py‎

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ...types import AstropyAsdfType
1010

1111

12-
__all__ = ['TransformType', 'IdentityType', 'ConstantType', 'DomainType']
12+
__all__ = ['TransformType', 'IdentityType', 'ConstantType']
1313

1414

1515
class TransformType(AstropyAsdfType):
@@ -25,21 +25,11 @@ def _from_tree_base_transform_members(cls, model, node, ctx):
2525
if 'name' in node:
2626
model = model.rename(node['name'])
2727

28-
# TODO: Remove domain in a later version.
29-
if 'domain' in node:
30-
model.bounding_box = cls._domain_to_bounding_box(node['domain'])
31-
elif 'bounding_box' in node:
32-
model.bounding_box = node['bounding_box']
28+
if 'bounding_box' in node:
29+
model.bounding_box = yamlutil.tagged_tree_to_custom_tree(node['bounding_box'], ctx)
3330

3431
return model
3532

36-
@classmethod
37-
def _domain_to_bounding_box(cls, domain):
38-
bb = tuple([(item['lower'], item['upper']) for item in domain])
39-
if len(bb) == 1:
40-
bb = bb[0]
41-
return bb
42-
4333
@classmethod
4434
def from_tree_transform(cls, node, ctx):
4535
raise NotImplementedError(
@@ -70,7 +60,7 @@ def _to_tree_base_transform_members(cls, model, node, ctx):
7060
bb = list(bb)
7161
else:
7262
bb = [list(item) for item in model.bounding_box]
73-
node['bounding_box'] = bb
63+
node['bounding_box'] = yamlutil.custom_tree_to_tagged_tree(bb, ctx)
7464

7565

7666
@classmethod
@@ -129,20 +119,6 @@ def to_tree_transform(cls, data, ctx):
129119
}
130120

131121

132-
class DomainType(AstropyAsdfType):
133-
# TODO: Is this used anywhere? Can it be removed?
134-
name = "transform/domain"
135-
version = '1.0.0'
136-
137-
@classmethod
138-
def from_tree(cls, node, ctx):
139-
return node
140-
141-
@classmethod
142-
def to_tree(cls, data, ctx):
143-
return data
144-
145-
146122
class GenericModel(mappings.Mapping):
147123
def __init__(self, n_inputs, n_outputs):
148124
mapping = tuple(range(n_inputs))

‎astropy/io/misc/asdf/tags/transform/tabular.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
from asdf import yamlutil
88

9-
from astropy import modeling
9+
from ...... import modeling
10+
from ...... import units as u
1011
from .basic import TransformType
11-
12+
from ......tests.helper import assert_quantity_allclose
1213

1314
__all__ = ['TabularType']
1415

@@ -24,26 +25,25 @@ class TabularType(TransformType):
2425
def from_tree_transform(cls, node, ctx):
2526
lookup_table = node.pop("lookup_table")
2627
dim = lookup_table.ndim
27-
name = node.get('name', None)
2828
fill_value = node.pop("fill_value", None)
2929
if dim == 1:
3030
# The copy is necessary because the array is memory mapped.
3131
points = (node['points'][0][:],)
3232
model = modeling.models.Tabular1D(points=points, lookup_table=lookup_table,
3333
method=node['method'], bounds_error=node['bounds_error'],
34-
fill_value=fill_value, name=name)
34+
fill_value=fill_value)
3535
elif dim == 2:
3636
points = tuple([p[:] for p in node['points']])
3737
model = modeling.models.Tabular2D(points=points, lookup_table=lookup_table,
3838
method=node['method'], bounds_error=node['bounds_error'],
39-
fill_value=fill_value, name=name)
39+
fill_value=fill_value)
4040

4141
else:
4242
tabular_class = modeling.models.tabular_model(dim, name)
4343
points = tuple([p[:] for p in node['points']])
4444
model = tabular_class(points=points, lookup_table=lookup_table,
4545
method=node['method'], bounds_error=node['bounds_error'],
46-
fill_value=fill_value, name=name)
46+
fill_value=fill_value)
4747

4848
return model
4949

@@ -60,8 +60,15 @@ def to_tree_transform(cls, model, ctx):
6060

6161
@classmethod
6262
def assert_equal(cls, a, b):
63-
assert_array_equal(a.lookup_table, b.lookup_table)
64-
assert_array_equal(a.points, b.points)
63+
if isinstance(a.lookup_table, u.Quantity):
64+
assert_quantity_allclose(a.lookup_table, b.lookup_table)
65+
assert_quantity_allclose(a.points, b.points)
66+
for i in range(len(a.bounding_box)):
67+
assert_quantity_allclose(a.bounding_box[i], b.bounding_box[i])
68+
else:
69+
assert_array_equal(a.lookup_table, b.lookup_table)
70+
assert_array_equal(a.points, b.points)
71+
assert_array_equal(a.bounding_box, b.bounding_box)
6572
assert (a.method == b.method)
6673
if a.fill_value is None:
6774
assert b.fill_value is None

‎astropy/io/misc/asdf/tags/transform/tests/test_transform.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,20 @@ def test_linear1d_quantity(tmpdir):
139139
model = astmodels.Linear1D(1*u.nm, 1*(u.nm/u.pixel))
140140
tree = {'model': model}
141141
helpers.assert_roundtrip_tree(tree, tmpdir)
142+
143+
144+
def test_tabular_model_units(tmpdir):
145+
points = np.arange(0, 5) * u.pix
146+
values = [1., 10, 2, 45, -3] * u.nm
147+
model = astmodels.Tabular1D(points=points, lookup_table=values)
148+
tree = {'model': model}
149+
helpers.assert_roundtrip_tree(tree, tmpdir)
150+
table = np.array([[3., 0., 0.],
151+
[0., 2., 0.],
152+
[0., 0., 0.]]) * u.nm
153+
points = ([1, 2, 3], [1, 2, 3]) * u.pix
154+
model2 = astmodels.Tabular2D(points, lookup_table=table,
155+
bounds_error=False, fill_value=None,
156+
method='nearest')
157+
tree = {'model': model2}
158+
helpers.assert_roundtrip_tree(tree, tmpdir)

‎astropy/modeling/utils.py‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -473,26 +473,40 @@ def validate(cls, model, bounding_box):
473473
nd = model.n_inputs
474474

475475
if nd == 1:
476-
if (not isiterable(bounding_box)
477-
or np.shape(bounding_box) not in ((2,), (1, 2))):
478-
raise ValueError(
479-
"Bounding box for {0} model must be a sequence of length "
480-
"2 consisting of a lower and upper bound, or a 1-tuple "
481-
"containing such a sequence as its sole element.".format(
482-
model.name))
476+
MESSAGE = "Bounding box for {0} model must be a sequence of length "
477+
"2 consisting of a lower and upper bound, or a 1-tuple "
478+
"containing such a sequence as its sole element.".format(model.name)
479+
480+
try:
481+
valid_shape = np.shape(bounding_box) in ((2,), (1, 2))
482+
except TypeError:
483+
# np.shape does not work with lists of Quantities
484+
valid_shape = np.shape([b.to_value() for b in bounding_box]) in ((2,), (1, 2))
485+
except ValueError:
486+
raise ValueError(MESSAGE)
487+
488+
if not isiterable(bounding_box) or not valid_shape:
489+
raise ValueError(MESSAGE)
483490

484491
if len(bounding_box) == 1:
485492
return cls((tuple(bounding_box[0]),))
486493
else:
487494
return cls(tuple(bounding_box))
488495
else:
489-
if (not isiterable(bounding_box)
490-
or np.shape(bounding_box) != (nd, 2)):
491-
raise ValueError(
492-
"Bounding box for {0} model must be a sequence of length "
493-
"{1} (the number of model inputs) consisting of pairs of "
494-
"lower and upper bounds for those inputs on which to "
495-
"evaluate the model.".format(model.name, nd))
496+
MESSAGE = "Bounding box for {0} model must be a sequence of length "
497+
"{1} (the number of model inputs) consisting of pairs of "
498+
"lower and upper bounds for those inputs on which to "
499+
"evaluate the model.".format(model.name, nd)
500+
501+
try:
502+
valid_shape = all([len(i) == 2 for i in bounding_box])
503+
except TypeError:
504+
valid_shape = False
505+
if len(bounding_box) != nd:
506+
valid_shape = False
507+
508+
if not isiterable(bounding_box) or not valid_shape:
509+
raise ValueError(MESSAGE)
496510

497511
return cls(tuple(bounds) for bounds in bounding_box)
498512

0 commit comments

Comments
 (0)