From 960994b26b83f8de603453e0c0ca6ca7d4770c3b Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 23 Dec 2016 09:25:06 -0500 Subject: [PATCH 01/40] Back to development 0.8 --- CHANGES.rst | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7fa47298..10d3bbfe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,6 @@ +0.8 (Unreleased) +---------------- + 0.7 (2016-12-23) ---------------- diff --git a/setup.py b/setup.py index 299d95d2..f29bbb79 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ builtins._ASTROPY_PACKAGE_NAME_ = PACKAGENAME # VERSION should be PEP386 compatible (http://www.python.org/dev/peps/pep-0386) -VERSION = '0.7' +VERSION = '0.8dev' # Indicates if this version is a release version RELEASE = 'dev' not in VERSION From 9467616796d3afca4c4ac6e8b6f87292422917aa Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 24 Feb 2017 11:58:02 -0500 Subject: [PATCH 02/40] improve logic in range-mapper --- gwcs/selector.py | 13 ++++++++----- gwcs/tests/test_region.py | 19 ++++++++++++++----- gwcs/utils.py | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/gwcs/selector.py b/gwcs/selector.py index 655a3da6..7916eaa7 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -66,7 +66,7 @@ """ from __future__ import absolute_import, division, unicode_literals, print_function - +import warnings import numpy as np from astropy.modeling.core import Model @@ -420,7 +420,7 @@ def evaluate(self, *args): keys = args keys = keys.flatten() # Define an array for the results. - res = np.empty(keys.shape) + res = np.zeros(keys.shape) + self._no_label nan_ind = np.isnan(keys) res[nan_ind] = self._no_label value_ranges = list(self.mapper.keys()) @@ -428,17 +428,20 @@ def evaluate(self, *args): # which fall within the range it defines. for val_range in value_ranges: temp = keys.copy() - temp[nan_ind] = self._no_label + temp[nan_ind] = np.nan temp = np.where(np.logical_or(temp <= val_range[0], temp >= val_range[1]), - 0, temp) - ind = temp.nonzero() + np.nan, temp) + ind = ~np.isnan(temp) + if ind: inputs = [a[ind] for a in args] res[ind] = self.mapper[tuple(val_range)](*inputs) else: continue res.shape = shape + if len(np.nonzero(res)[0]) == 0: + warnings.warn("All data is outside the valid range - {0}.".format(self.name)) return res diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 6a5ae129..68a2955b 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -83,10 +83,10 @@ def test_create_mask_two_polygons(): def create_range_mapper(): m = [] - for i in np.arange(9) *.1: + for i in np.arange(1, 10) *.1: c0_0, c1_0, c0_1, c1_1 = np.ones((4,)) * i - m.append(models.Polynomial2D(2, c0_0=c0_0, - c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) + m.append(models.Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) + keys = np.array([[ 4.88, 5.64], [ 5.75, 6.5], [ 6.67, 7.47 ], @@ -130,12 +130,12 @@ def test_LabelMapperDict(): def test_LabelMapperRange(): sel = create_range_mapper() - assert(sel(6, 2) == 2.1) + assert(sel(6, 2) == 4.2) def test_overalpping_ranges(): """ - Overlapping ranges should raise an error. + Initializing a ``LabelMapperRange`` with overlapping ranges should raise an error. """ keys = np.array([[ 4.88, 5.75], [ 5.64, 6.5], @@ -146,3 +146,12 @@ def test_overalpping_ranges(): with pytest.raises(ValueError): lmr = selector.LabelMapperRange(('x', 'y'), rmapper, inputs_mapping=((0,))) + +def test_outside_range(): + """ + Return ``_no_label`` value when keys are outside the range. + """ + lmr = create_range_mapper() + assert lmr(1, 1) == 0 + assert lmr(5, 1) == 1.2 + diff --git a/gwcs/utils.py b/gwcs/utils.py index 316801d1..e3b2e40c 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -203,7 +203,7 @@ def read_wcs_from_header(header): except KeyError: p = re.compile('ctype[\d]*', re.IGNORECASE) ctypes = header['CTYPE*'] - keys = ctypes.keys() + keys = list(ctypes.keys()) for key in keys[::-1]: if p.split(key)[-1] != "": keys.remove(key) From 959264ba1918bd553524a44d20691b1ac6299d1f Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 24 Feb 2017 12:02:06 -0500 Subject: [PATCH 03/40] improve comparison --- gwcs/selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwcs/selector.py b/gwcs/selector.py index 7916eaa7..dc259a9a 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -434,7 +434,7 @@ def evaluate(self, *args): np.nan, temp) ind = ~np.isnan(temp) - if ind: + if ind.any(): inputs = [a[ind] for a in args] res[ind] = self.mapper[tuple(val_range)](*inputs) else: From e1c4a66388bbdf2da013584dede3f76500ed6934 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sat, 25 Feb 2017 23:14:55 -0500 Subject: [PATCH 04/40] change log [skip ci] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 10d3bbfe..c662abc7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ 0.8 (Unreleased) ---------------- +- ``LabelMapperRange`` now returns ``LabelMapperRange._no_label`` when the key is + not within any range. [#71] + 0.7 (2016-12-23) ---------------- From c654fd349a6f776d21b37179296cd870f454d9d9 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Thu, 2 Mar 2017 12:27:29 -0500 Subject: [PATCH 05/40] further changes to allow NaNs to pass through the ipeline --- gwcs/selector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gwcs/selector.py b/gwcs/selector.py index dc259a9a..8af8607f 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -302,11 +302,11 @@ def evaluate(self, *args): keys = args keys = keys.flatten() # create an empty array for the results - res = np.empty(keys.shape) + res = np.zeros(keys.shape) + self._no_label # If this is part of a combined transform, some of the inputs # may be NaNs. # Set NaNs to the ``_no_label`` value - res[np.isnan(keys)] = self._no_label + #res[np.isnan(keys)] = self._no_label mapper_keys = list(self.mapper.keys()) # Loop over the keys in mapper and compare to inputs. # Find the indices where they are within ``atol`` From 47c5df7a83294c9c118167e029c70a47560f0ec6 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Thu, 2 Mar 2017 12:58:59 -0500 Subject: [PATCH 06/40] add change log [skip ci] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c662abc7..8094b984 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ - ``LabelMapperRange`` now returns ``LabelMapperRange._no_label`` when the key is not within any range. [#71] +- ``LabelMapperDict`` now returns ``LabelMapperDict._no_label`` when the key does + not match. [#72] + 0.7 (2016-12-23) ---------------- From 1e637a5b33c2f8ad5e9d86565d868c9274035b6f Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Mon, 20 Mar 2017 00:04:55 +0000 Subject: [PATCH 07/40] Updated astropy-helpers to v1.3.1 --- astropy_helpers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astropy_helpers b/astropy_helpers index f06ca37c..70b09af7 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit f06ca37c4e389b90556815b300d9992fa3945199 +Subproject commit 70b09af7e4f6b8a4e409412a913ff2adb05c12b7 From 16d44a452890ac37729707b6ad0849cf88e60c1a Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 29 Mar 2017 14:22:56 -0400 Subject: [PATCH 08/40] deprecate domain, replace it with bounding_box --- gwcs/wcs.py | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index bda5e65c..602a7eb8 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -2,6 +2,8 @@ from __future__ import absolute_import, division, unicode_literals, print_function import functools +import warnings + import numpy as np from astropy.extern import six from astropy.modeling.core import Model @@ -11,6 +13,7 @@ from .utils import _toindex from . import utils + __all__ = ['WCS'] @@ -151,7 +154,7 @@ def set_transform(self, from_frame, to_frame, transform): @property def forward_transform(self): """ - Return the total forward transform - from input to output coordinate frame. + Return the total forward transform - from input to outpu coordinate frame. """ @@ -389,11 +392,47 @@ def pipeline(self): """Return the pipeline structure.""" return self._pipeline + @property + def bounding_box(self): + """ + Return the range of acceptable values for each input axis. + The order of the axes is `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. + """ + frames = self.available_frames + transform_0 = self.get_transform(frames[0], frames[1]) + frame_0 = getattr(self, frames[0]) + try: + # Model.bounding_mox is in numpy order + bb = transform_0.bounding_box[::-1] + except NotImplementedError: + return None + bb = np.array(bb)[np.array(frame_0.axes_order)] + return tuple(tuple(item) for item in bb) + + @bounding_box.setter + def bounding_box(self, value): + """ + Set the range of acceptable values for each input axis. + The order of the axes is `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. + + Parameters + ---------- + value : list + List of tuples with ("low", high") values for the range. + """ + frames = self.available_frames + transform_0 = self.get_transform(frames[0], frames[1]) + frame_0 = getattr(self, frames[0]) + axes_ind = np.argsort(frame_0.axes_order) + transform_0.bounding_box = np.array(value)[axes_ind][::-1] + self.set_transform(frames[0], frames[1], transform_0) + @property def domain(self): """ Return the range of acceptable values for each input axis. """ + warnings.warn('"domain" was deprecated in v0.8 and will be removed in the next version. Use "bounding_box" instead.') frames = self.available_frames transform_meta = self.get_transform(frames[0], frames[1]).meta if 'domain' in transform_meta: @@ -406,6 +445,7 @@ def domain(self, value): """ Set the range of acceptable values for each input axis. """ + warnings.warn('"domain" was deprecated in v.0.8 and will be removed in the next version. Use "bounding_box" instead.') self._validate_domain(value) frames = self.available_frames transform = self.get_transform(frames[0], frames[1]) From 97e7b6550c1eae2e4167eef9d6a843bc91c359ca Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 29 Mar 2017 14:27:53 -0400 Subject: [PATCH 09/40] fix typos --- gwcs/wcs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 602a7eb8..5ba94f5f 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -154,7 +154,7 @@ def set_transform(self, from_frame, to_frame, transform): @property def forward_transform(self): """ - Return the total forward transform - from input to outpu coordinate frame. + Return the total forward transform - from input to output coordinate frame. """ @@ -402,7 +402,7 @@ def bounding_box(self): transform_0 = self.get_transform(frames[0], frames[1]) frame_0 = getattr(self, frames[0]) try: - # Model.bounding_mox is in numpy order + # Model.bounding_box is in numpy order, need to reverse it first. bb = transform_0.bounding_box[::-1] except NotImplementedError: return None @@ -417,8 +417,8 @@ def bounding_box(self, value): Parameters ---------- - value : list - List of tuples with ("low", high") values for the range. + value : tuple + Tuple of tuples with ("low", high") values for the range. """ frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) From d31e98ea4b1c205450955cdd276257b967ab788b Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 29 Mar 2017 14:37:51 -0400 Subject: [PATCH 10/40] use input_frame --- gwcs/wcs.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 5ba94f5f..d8373e26 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -400,13 +400,12 @@ def bounding_box(self): """ frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) - frame_0 = getattr(self, frames[0]) try: # Model.bounding_box is in numpy order, need to reverse it first. bb = transform_0.bounding_box[::-1] except NotImplementedError: return None - bb = np.array(bb)[np.array(frame_0.axes_order)] + bb = np.array(bb)[np.array(self.input_frame.axes_order)] return tuple(tuple(item) for item in bb) @bounding_box.setter @@ -422,8 +421,7 @@ def bounding_box(self, value): """ frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) - frame_0 = getattr(self, frames[0]) - axes_ind = np.argsort(frame_0.axes_order) + axes_ind = np.argsort(self.input_frame.axes_order) transform_0.bounding_box = np.array(value)[axes_ind][::-1] self.set_transform(frames[0], frames[1], transform_0) From 233f1956a0757f6dec16b1812c07b2df27b9df4a Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Tue, 4 Apr 2017 13:49:24 -0400 Subject: [PATCH 11/40] more bounding_box changes --- gwcs/tests/test_wcs.py | 42 ++++++++++++------------ gwcs/utils.py | 32 +++++++++++++++++-- gwcs/wcs.py | 69 ++++++++++++++++++++++++---------------- gwcs/wcstools.py | 72 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 156 insertions(+), 59 deletions(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 65dccd63..e845e2d0 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -15,7 +15,7 @@ from ..wcstools import * from .. import coordinate_frames as cf from .. import utils -from ..utils import CoordinateFrameError +from ..utils import CoordinateFrameError, DimensionalityError m1 = models.Shift(12.4) & models.Shift(-2) @@ -192,23 +192,24 @@ def test_domain(): trans3 = models.Shift(10) & models.Scale(2) & models.Shift(-1) pipeline = [('detector', trans3), ('sky', None)] w = wcs.WCS(pipeline) - domain = [{'lower': -1, 'upper': 10, 'include_lower': True, 'include_upper': False, 'step': .1}, - {'lower': 6, 'upper': 15, 'include_lower': False, 'include_upper': True, 'step': .5}] - with pytest.raises(ValueError): - w.domain = domain + bb = ((-1, 10), (6, 15)) + with pytest.raises(DimensionalityError): + w.bounding_box = bb trans2 = models.Shift(10) & models.Scale(2) pipeline = [('detector', trans2), ('sky', None)] w = wcs.WCS(pipeline) - w.domain = domain - assert w.domain == w.forward_transform.meta['domain'] - - -def test_grid_from_domain(): - domain = [{'lower': -1, 'upper': 10, 'includes_lower': True, - 'includes_upper': False, 'step': .1}, - {'lower': 6, 'upper': 15, 'includes_lower': False, - 'includes_upper': True, 'step': .5}] - x, y = grid_from_domain(domain) + w.bounding_box = bb + assert w.bounding_box == w.forward_transform.bounding_box[::-1] + + +def test_grid_from_bounding_box(): + #domain = [{'lower': -1, 'upper': 10, 'includes_lower': True, + #'includes_upper': False, 'step': .1}, + #{'lower': 6, 'upper': 15, 'includes_lower': False, + #'includes_upper': True, 'step': .5}] + bb = ((-1, 9.9), (6.5, 15)) + #x, y = grid_from_domain(domain) + x, y = grid_from_bounding_box(bb, step=[.1, .5], center=False) assert_allclose(x[:, 0], -1) assert_allclose(x[:, -1], 9.9) assert_allclose(y[0], 6.5) @@ -244,15 +245,16 @@ def setup_class(self): tan = models.Pix2Sky_TAN(name='tangent_projection') sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky') + det = cf.Frame2D('detector') wcs_forward = wcslin | tan | n2c pipeline = [('detector', distortion), ('focal', wcs_forward), (sky_cs, None) ] - self.wcs = wcs.WCS(input_frame='detector', - output_frame=sky_cs, - forward_transform=pipeline) + self.wcs = wcs.WCS(input_frame = det, + output_frame = sky_cs, + forward_transform = pipeline) nx, ny = (5, 2) x = np.linspace(0, 1, nx) y = np.linspace(0, 1, ny) @@ -286,8 +288,8 @@ def test_backward(self): assert_allclose(px_coord[1], self.yv, atol=10**-6) def test_footprint(self): - domain = [{'lower': 1, 'upper': 4097}, {'lower': 1, 'upper': 2049}] - footprint = (self.wcs.footprint(domain)).T + bb = ((1, 4096), (1, 2048)) + footprint = (self.wcs.footprint(bb)).T fits_footprint = self.fitsw.calc_footprint(axes=(4096, 2048)) assert_allclose(footprint, fits_footprint) diff --git a/gwcs/utils.py b/gwcs/utils.py index e3b2e40c..53452d4b 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -15,6 +15,8 @@ from astropy import coordinates as coords from astropy import units as u +from astropy.utils.decorators import deprecated + # these ctype values do not include yzLN and yzLT pairs sky_pairs = {"equatorial": ["RA--", "DEC-"], @@ -40,10 +42,10 @@ def __init__(self, code): super(UnsupportedProjectionError, self).__init__(message) -class ModelDimensionalityError(Exception): +class DimensionalityError(Exception): def __init__(self, message): - super(ModelDimensionalityError, self).__init__(message) + super(DimensionalityError, self).__init__(message) class RegionError(Exception): @@ -58,6 +60,12 @@ def __init__(self, message): super(CoordinateFrameError, self).__init__(message) +def _domain_to_bounding_box(domain): + bb = tuple([(item['lower'], item['upper']) for item in domain]) + if len(bb) == 1: + bb = bb[0] + return bb + def _toindex(value): """ Convert value to an int or an int array. @@ -77,10 +85,12 @@ def _toindex(value): >>> _toindex(np.array([1.5, 2.49999])) array([2, 2]) """ - indx = np.asarray(np.floor(value + 0.5), dtype=np.int) + indx = np.asarray(np.floor(np.asarray(value) + 0.5), dtype=np.int) return indx +@deprecated("0.8", message="_domain has been deprecated in 0.8 and will be" + "removed in the next version", alternative="bounding_box") def _domain_to_bounds(domain): def _get_bounds(axis_domain): step = axis_domain.get('step', 1) @@ -95,6 +105,7 @@ def _get_bounds(axis_domain): def _get_slice(axis_domain): + """ TODO: Remove when domain is removed""" step = axis_domain.get('step', 1) x = axis_domain['lower'] if axis_domain.get('includes_lower', True) \ else axis_domain['lower'] + step @@ -103,6 +114,21 @@ def _get_slice(axis_domain): return slice(x, y, step) +def axis_domain_to_slice(axis_domain, step): + """ + Return a slice from the bounding_box for an axis. + + Parameters + ---------- + axis_domain : tuple + The range of acceptable input values for an axis, usually from bounding_box. + step : int + A step to use in the slice. + """ + x, y = axis_domain + return slice(x, y, step) + + def _get_values(units, *args): """ Return the values of SkyCoord or Quantity objects. diff --git a/gwcs/wcs.py b/gwcs/wcs.py index d8373e26..d508fdd1 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -219,8 +219,8 @@ def __call__(self, *args, **kwargs): args : float or array-like Inputs in the input coordinate system, separate inputs for each dimension. output : str - One of ["numericals", "numericals_plus"] - If "numericals_plus" - returns a `~astropy.coordinates.SkyCoord` or + One of [``numericals``, ``numericals_plus``] + If ``numericals_plus`` - returns a `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` object. """ if self.forward_transform is None: @@ -286,8 +286,8 @@ def transform(self, from_frame, to_frame, *args, **kwargs): args : float or array-like Inputs in ``from_frame``, separate inputs for each dimension. output : str - One of ["numericals", "numericals_plus"] - If "numericals_plus" - returns a `~astropy.coordinates.SkyCoord` or + One of [``numericals``, ``numericals_plus``] + If ``numericals_plus`` - returns a `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` object. """ transform = self.get_transform(from_frame, to_frame) @@ -339,7 +339,7 @@ def insert_transform(self, frame, transform, after=False): New transform to be inserted in the pipeline after : bool If True, the new transform is inserted in the pipeline - immediately after `frame`. + immediately after ``frame``. """ name, _ = self._get_frame_name(frame) frame_ind = self._get_frame_index(name) @@ -405,14 +405,20 @@ def bounding_box(self): bb = transform_0.bounding_box[::-1] except NotImplementedError: return None - bb = np.array(bb)[np.array(self.input_frame.axes_order)] + try: + axes_order = self.input_frame.axes_order + except AttributeError: + axes_order = np.arange(transform_0.n_inputs) + bb = np.array(bb)[np.array(axes_order)] return tuple(tuple(item) for item in bb) @bounding_box.setter def bounding_box(self, value): """ - Set the range of acceptable values for each input axis. + Set the range of acceptable values for each input axis. + The order of the axes is `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. + For two inputs and axes_order(0, 1) the bounding box is ((xlow, xhigh), (ylow, yhigh)). Parameters ---------- @@ -421,8 +427,15 @@ def bounding_box(self, value): """ frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) - axes_ind = np.argsort(self.input_frame.axes_order) - transform_0.bounding_box = np.array(value)[axes_ind][::-1] + try: + axes_ind = np.argsort(self.input_frame.axes_order) + except AttributeError: + # the case of a frame being a string + axes_ind = np.arange(transform_0.n_inputs) + try: + transform_0.bounding_box = np.array(value)[axes_ind][::-1] + except IndexError: + raise utils.DimensionalityError("The bounding_box does not match the number of inputs.") self.set_transform(frames[0], frames[1], transform_0) @property @@ -430,7 +443,8 @@ def domain(self): """ Return the range of acceptable values for each input axis. """ - warnings.warn('"domain" was deprecated in v0.8 and will be removed in the next version. Use "bounding_box" instead.') + warnings.warn('"domain" was deprecated in v0.8 and will be' + 'removed in the next version. Use "bounding_box" instead.') frames = self.available_frames transform_meta = self.get_transform(frames[0], frames[1]).meta if 'domain' in transform_meta: @@ -443,7 +457,8 @@ def domain(self, value): """ Set the range of acceptable values for each input axis. """ - warnings.warn('"domain" was deprecated in v.0.8 and will be removed in the next version. Use "bounding_box" instead.') + warnings.warn('"domain" was deprecated in v.0.8 and will be removed' + ' in the next version. Use "bounding_box" instead.') self._validate_domain(value) frames = self.available_frames transform = self.get_transform(frames[0], frames[1]) @@ -482,33 +497,33 @@ def __repr__(self): self.output_frame, self.input_frame, self.forward_transform) return fmt - def footprint(self, domain=None, center=True): + def footprint(self, bounding_box=None, center=False): """ Return the footprint of the observation in world coordinates. Parameters ---------- - domain : slice or tuple of floats: (start, stop, step) or (start, stop) or (stop,) - size of image + bounding_box : tuple of floats: (start, stop) + `prop: bounding_box` center : bool If `True` use the center of the pixel, otherwise use the corner. Returns ------- - coord : (4, 2) array of (*x*, *y*) coordinates. + coord : array of coordinates in the output_frame. The order is counter-clockwise starting with the bottom left corner. """ - if domain is None: - if self.domain is None: - raise TypeError("Need a valid domain to compute the footprint.") - domain = self.domain - self._validate_domain(domain) - - bounds = utils._domain_to_bounds(domain) - vertices = np.asarray([[bounds[0][0], bounds[1][0]], [bounds[0][0], bounds[1][1]], - [bounds[0][1], bounds[1][1]], [bounds[0][1], bounds[1][0]]]) - vertices = _toindex(vertices).T - if not center: - vertices += .5 + if bounding_box is None: + if self.bounding_box is None: + raise TypeError("Need a valid bounding_box to compute the footprint.") + bb = self.bounding_box + else: + bb = bounding_box + vertices = np.asarray([[bb[0][0], bb[1][0]], [bb[0][0], bb[1][1]], + [bb[0][1], bb[1][1]], [bb[0][1], bb[1][0]]]).T + print('bb', bb) + print('vert', vertices) + if center: + vertices = _toindex(vertices) result = self.__call__(*vertices) return np.asarray(result) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 2f19e48c..0cb11d2b 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -11,14 +11,18 @@ from .wcs import WCS from .coordinate_frames import * from .utils import UnsupportedTransformError, UnsupportedProjectionError -from .utils import _compute_lon_pole, _get_slice +from .utils import _compute_lon_pole, _get_slice, _toindex, axis_domain_to_slice +import warnings +from astropy.utils.decorators import deprecated +from .utils import _domain_to_bounding_box -__all__ = ['wcs_from_fiducial', 'grid_from_domain'] + +__all__ = ['wcs_from_fiducial', 'grid_from_domain', 'grid_from_bounding_box'] def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, - transform=None, name='', domain=None): + transform=None, name='', bounding_box=None, domain=None): """ Create a WCS object from a fiducial point in a coordinate frame. @@ -44,7 +48,7 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, the number of axes in the coordinate frame. name : str Name of this WCS. - domain : list of dicts + bounding_box : tuple Domain of this WCS. The format is a list of dictionaries for each axis in the input frame [{'lower': float, 'upper': float, 'includes_lower': bool, @@ -54,7 +58,11 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, if not isinstance(transform, Model): raise UnsupportedTransformError("Expected transform to be an instance" "of astropy.modeling.Model") - # transform_outputs = transform.n_outputs + if domain is not None: + warnings.warning("'domain' was deprecated in 0.8 and will be removed from next" + "version. Use 'bounding_box' instead.") + bounding_box = _domain_to_bounding_box(domain) + # transform_outputs = transform.n_outputs if isinstance(fiducial, coord.SkyCoord): coordinate_frame = CelestialFrame(reference_frame=fiducial.frame, unit=(fiducial.spherical.lon.unit, @@ -83,11 +91,11 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, forward_transform = transform | fiducial_transform else: forward_transform = fiducial_transform - if domain is not None: - if len(domain) != forward_transform.n_outputs: - raise ValueError("Expected the number of items in 'domain' to be equal to the " + if bounding_box is not None: + if len(bounding_box) != forward_transform.n_outputs: + raise ValueError("Expected the number of items in 'bounding_box' to be equal to the " "number of outputs of the forawrd transform.") - forward_transform.meta['domain'] = domain + forward_transform.bounding_box = bonding_box[::-1] return WCS(output_frame=coordinate_frame, forward_transform=forward_transform, name=name) @@ -131,6 +139,7 @@ def _frame2D_transform(fiducial, **kwargs): } +@deprecated("0.8", alternative="grid_from_bounding_box") def grid_from_domain(domain): """ Create a grid of input points from the WCS domain. @@ -171,3 +180,48 @@ def grid_from_domain(domain): """ slices = [_get_slice(d) for d in domain] return np.mgrid[slices[::-1]][::-1] + + +def grid_from_bounding_box(bounding_box, step=1, center=True): + """ + Create a grid of input points from the WCS bounding_box. + + Parameters + ---------- + bounding_box : tuple + `ref: prop: bounding_box` + step : None, tuple + A step for the grid in each dimension. + If None, step=1. + center : bool + + The bounding_box is in order of X, Y [, Z] and the output will be in the same order. + + Examples + -------- + >>> bb = bb=((-1, 2.9), (6, 7.5)) + >>> grid_from_bounding_box(bb, step=(1, .5) + [[[-1. , 0. , 1. , 2. ], + [-1. , 0. , 1. , 2. ], + [-1. , 0. , 1. , 2. ], + [-1. , 0. , 1. , 2. ]], + + [[ 6. , 6. , 6. , 6. ], + [ 6.5, 6.5, 6.5, 6.5], + [ 7. , 7. , 7. , 7. ], + [ 7.5, 7.5, 7.5, 7.5]]]) + + + Returns + ------- + x, y : ndarray + Input points. + """ + slices = [] + if center: + bb = _toindex(bounding_box) + else: + bb = bounding_box + for d, s in zip(bb, step): + slices.append(slice(d[0], d[1] + s, s)) + return np.mgrid[slices[::-1]][::-1] From b767ed1d71688a180ed5762df52400c03abef63c Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Tue, 4 Apr 2017 21:09:04 -0400 Subject: [PATCH 12/40] add change log [skip ci] --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8094b984..97c5853c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,8 @@ - ``LabelMapperDict`` now returns ``LabelMapperDict._no_label`` when the key does not match. [#72] +- Replace ``domain`` with ``bounding_box``. [#74] + 0.7 (2016-12-23) ---------------- From b7cd6307d5253843454d9c2d0d6ef0aed764ec25 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 7 Apr 2017 11:31:58 -0400 Subject: [PATCH 13/40] respect bbox when evaluating the transform --- gwcs/tests/test_wcs.py | 22 +++++++++++++++++----- gwcs/wcs.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index e845e2d0..d7a18eac 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -203,12 +203,7 @@ def test_domain(): def test_grid_from_bounding_box(): - #domain = [{'lower': -1, 'upper': 10, 'includes_lower': True, - #'includes_upper': False, 'step': .1}, - #{'lower': 6, 'upper': 15, 'includes_lower': False, - #'includes_upper': True, 'step': .5}] bb = ((-1, 9.9), (6.5, 15)) - #x, y = grid_from_domain(domain) x, y = grid_from_bounding_box(bb, step=[.1, .5], center=False) assert_allclose(x[:, 0], -1) assert_allclose(x[:, -1], 9.9) @@ -216,6 +211,23 @@ def test_grid_from_bounding_box(): assert_allclose(y[-1], 15) +def test_bounding_box_eval(): + """ + Tests evaluation with and without respecting the bounding_box. + """ + trans3 = models.Shift(10) & models.Scale(2) & models.Shift(-1) + pipeline = [('detector', trans3), ('sky', None)] + w = wcs.WCS(pipeline) + w.bounding_box = ((-1, 10), (6, 15), (4.3, 6.9)) + + # test scalar outside bbox + assert_allclose(w(1, 7, 3), [np.nan, np.nan, np.nan]) + # test scalar inside bbox + assert_allclose(w(1, 7, 5), [11, 4, 4]) + # test arrays + assert_allclose(w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 4], [np.nan, 4]]) + + class TestImaging(object): def setup_class(self): diff --git a/gwcs/wcs.py b/gwcs/wcs.py index d508fdd1..a9318c70 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -212,21 +212,49 @@ def _get_frame_name(self, frame): frame_obj = frame return name, frame_obj - def __call__(self, *args, **kwargs): + def __call__(self, *args, output="numericals", with_bounding_box=True, fill_value=np.nan): """ Executes the forward transform. args : float or array-like Inputs in the input coordinate system, separate inputs for each dimension. - output : str + output : str, optional One of [``numericals``, ``numericals_plus``] If ``numericals_plus`` - returns a `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` object. + with_bounding_box : bool, optional + If True(default) values in the result which correspond to any of the inputs being + outside the bounding_box are set to ``fill_value``. + fill_value : float, optional + Output value for inputs outside the bounding_box (default is np.nan). """ if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") result = self.forward_transform(*args) - output = kwargs.pop('output', None) + print('result', result) + + # Set values outside the ``bounding_box`` to `fill_value``. + if with_bounding_box and self.bounding_box: + bbox = self.bounding_box + inputs= args[:] + + inputs = [np.array(arg) for arg in inputs] + result = [np.array(r) for r in result] + + for ind, inp in enumerate(inputs): + # Pass an ``out`` array so that ``axis_ind`` is array for scalars as well. + axis_ind = np.zeros(inp.shape, dtype=np.bool) + axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) + for ind, _ in enumerate(result): + result[ind][axis_ind] = fill_value + if np.isscalar(args[0]): + result = tuple([np.asscalar(r) for r in result]) + else: + result = tuple(result) + + if output not in ["numericals", "numericals_plus"]: + raise ValueError("'output' should be 'numericals' or " + "'numericals_plus', not '{0}'.".format(output)) if output == 'numericals_plus': if self.output_frame.naxes == 1: result = self.output_frame.coordinates(result) From 97cfb4e08df8dc0861f03393e039389febed6724 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 7 Apr 2017 11:46:47 -0400 Subject: [PATCH 14/40] with bounding_box --- gwcs/tests/test_wcs.py | 4 ++-- gwcs/wcs.py | 45 +++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index d7a18eac..0f4debad 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -223,9 +223,9 @@ def test_bounding_box_eval(): # test scalar outside bbox assert_allclose(w(1, 7, 3), [np.nan, np.nan, np.nan]) # test scalar inside bbox - assert_allclose(w(1, 7, 5), [11, 4, 4]) + assert_allclose(w(1, 7, 5), [11, 14, 4]) # test arrays - assert_allclose(w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 4], [np.nan, 4]]) + assert_allclose(w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 14], [np.nan, 4]]) class TestImaging(object): diff --git a/gwcs/wcs.py b/gwcs/wcs.py index a9318c70..cdc9a35b 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -230,27 +230,13 @@ def __call__(self, *args, output="numericals", with_bounding_box=True, fill_valu """ if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") - result = self.forward_transform(*args) - print('result', result) + #result = self.forward_transform(*args) # Set values outside the ``bounding_box`` to `fill_value``. if with_bounding_box and self.bounding_box: - bbox = self.bounding_box - inputs= args[:] - - inputs = [np.array(arg) for arg in inputs] - result = [np.array(r) for r in result] - - for ind, inp in enumerate(inputs): - # Pass an ``out`` array so that ``axis_ind`` is array for scalars as well. - axis_ind = np.zeros(inp.shape, dtype=np.bool) - axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) - for ind, _ in enumerate(result): - result[ind][axis_ind] = fill_value - if np.isscalar(args[0]): - result = tuple([np.asscalar(r) for r in result]) - else: - result = tuple(result) + result = self._with_bounding_box(self.forward_transform, fill_value, *args) + else: + result = self.forward_transform(*args) if output not in ["numericals", "numericals_plus"]: raise ValueError("'output' should be 'numericals' or " @@ -264,6 +250,29 @@ def __call__(self, *args, output="numericals", with_bounding_box=True, fill_valu raise ValueError("Type of output unrecognized {0}".format(output)) return result + def _with_bounding_box(self, transform, fill_value, *args): + # Set values outside the ``bounding_box`` to `fill_value``. + result = transform(*args) + try: + bbox = transform.bounding_box[::-1] + except NotImplementedError: + bbox = None + + inputs = [np.array(arg) for arg in args] + result = [np.array(r) for r in result] + + for ind, inp in enumerate(inputs): + # Pass an ``out`` array so that ``axis_ind`` is array for scalars as well. + axis_ind = np.zeros(inp.shape, dtype=np.bool) + axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) + for ind, _ in enumerate(result): + result[ind][axis_ind] = fill_value + if np.isscalar(args[0]): + result = tuple([np.asscalar(r) for r in result]) + else: + result = tuple(result) + return result + def invert(self, *args, **kwargs): """ Invert coordnates. From 834886c19a1743fde2e942d08e755da75a752d72 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 7 Apr 2017 12:13:15 -0400 Subject: [PATCH 15/40] fix for bounding_box=None --- gwcs/wcs.py | 62 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index cdc9a35b..3944e4d1 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -230,11 +230,11 @@ def __call__(self, *args, output="numericals", with_bounding_box=True, fill_valu """ if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") - #result = self.forward_transform(*args) # Set values outside the ``bounding_box`` to `fill_value``. if with_bounding_box and self.bounding_box: - result = self._with_bounding_box(self.forward_transform, fill_value, *args) + result = self._with_bounding_box(self.forward_transform, fill_value, *args, + bbox=self.bounding_box) else: result = self.forward_transform(*args) @@ -250,14 +250,35 @@ def __call__(self, *args, output="numericals", with_bounding_box=True, fill_valu raise ValueError("Type of output unrecognized {0}".format(output)) return result - def _with_bounding_box(self, transform, fill_value, *args): - # Set values outside the ``bounding_box`` to `fill_value``. - result = transform(*args) - try: - bbox = transform.bounding_box[::-1] - except NotImplementedError: - bbox = None + def _with_bounding_box(self, transform, fill_value, *args, bbox=None): + """ + Evaluate the transform respecting the bounding_box. + + TODO: Move this to modeling. + Parameters + ---------- + transform : `~astropy.modeling.Model` + Transform to evaluate + fill_value : float + Fill value + args : list + Inputs + kwargs: dict, optional + pass a bbox here if necessary + bbox : a bounding_box + Sometimes the bounding_box is attached to part of + the transform. Instead of looking for it just pass it. + """ + # Set values outside the ``bounding_box`` to `fill_value``. + result = transform(*args) + if bbox is None: + try: + bbox = transform.bounding_box[::-1] + except NotImplementedError: + bbox = None + if bbox is None: + return result inputs = [np.array(arg) for arg in args] result = [np.array(r) for r in result] @@ -459,20 +480,23 @@ def bounding_box(self, value): Parameters ---------- - value : tuple + value : tuple or None Tuple of tuples with ("low", high") values for the range. """ frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) - try: - axes_ind = np.argsort(self.input_frame.axes_order) - except AttributeError: - # the case of a frame being a string - axes_ind = np.arange(transform_0.n_inputs) - try: - transform_0.bounding_box = np.array(value)[axes_ind][::-1] - except IndexError: - raise utils.DimensionalityError("The bounding_box does not match the number of inputs.") + if value is None: + transform_0.bounding_box = value + else: + try: + axes_ind = np.argsort(self.input_frame.axes_order) + except AttributeError: + # the case of a frame being a string + axes_ind = np.arange(transform_0.n_inputs) + try: + transform_0.bounding_box = np.array(value)[axes_ind][::-1] + except IndexError: + raise utils.DimensionalityError("The bounding_box does not match the number of inputs.") self.set_transform(frames[0], frames[1], transform_0) @property From b29d72fbd2e8d240735edd486b6e54c784a52c6c Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 7 Apr 2017 14:04:18 -0400 Subject: [PATCH 16/40] add bbox to transform and invert. fix python 2 errors --- gwcs/tests/test_wcs.py | 6 ++++++ gwcs/utils.py | 3 --- gwcs/wcs.py | 39 +++++++++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 0f4debad..6c25e92c 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -222,11 +222,17 @@ def test_bounding_box_eval(): # test scalar outside bbox assert_allclose(w(1, 7, 3), [np.nan, np.nan, np.nan]) + assert_allclose(w(1, 7, 3, with_bounding_box=False), [11, 14, 2]) + assert_allclose(w(1, 7, 3, fill_value=100.3), [100.3, 100.3, 100.3]) + assert_allclose(w(1, 7, 3, fill_value=np.inf), [np.inf, np.inf, np.inf]) # test scalar inside bbox assert_allclose(w(1, 7, 5), [11, 14, 4]) # test arrays assert_allclose(w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 14], [np.nan, 4]]) + # test ``transform`` method + assert_allclose(w.transform('detector', 'sky', 1, 7, 3), [np.nan, np.nan, np.nan]) + class TestImaging(object): diff --git a/gwcs/utils.py b/gwcs/utils.py index 53452d4b..42d976f5 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -141,12 +141,9 @@ def _get_values(units, *args): """ val = [] values = [] - print('args', args) for arg in args: - print('arg', arg) if isinstance(arg, coords.SkyCoord): try: - print('arg1', arg) lon = arg.data.lon lat = arg.data.lat except AttributeError: diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 3944e4d1..3acb2711 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -212,7 +212,7 @@ def _get_frame_name(self, frame): frame_obj = frame return name, frame_obj - def __call__(self, *args, output="numericals", with_bounding_box=True, fill_value=np.nan): + def __call__(self, *args, **kwargs): """ Executes the forward transform. @@ -231,6 +231,10 @@ def __call__(self, *args, output="numericals", with_bounding_box=True, fill_valu if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") + fill_value = kwargs.pop('fill_value', np.nan) + with_bounding_box = kwargs.pop('with_bounding_box', True) + output = kwargs.pop("output", "numericals") + # Set values outside the ``bounding_box`` to `fill_value``. if with_bounding_box and self.bounding_box: result = self._with_bounding_box(self.forward_transform, fill_value, *args, @@ -307,11 +311,23 @@ def invert(self, *args, **kwargs): coordinates to be inverted kwargs : dict keyword arguments to be passed to the iterative invert method. + with_bounding_box : bool, optional + If True(default) values in the result which correspond to any of the inputs being + outside the bounding_box are set to ``fill_value``. + fill_value : float, optional + Output value for inputs outside the bounding_box (default is np.nan). """ if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) + + fill_value = kwargs.pop('fill_value', np.nan) + with_bounding_box = kwargs.pop('with_bounding_box', True) + try: - result = self.backward_transform(*args) + if with_bounding_box: + result = self._with_bounding_box(self.backward_transform, fill_value, *args) + else: + result = self.backward_transform(*args) except (NotImplementedError, KeyError): result = self._invert(*args, **kwargs) output = kwargs.pop('output', None) @@ -339,20 +355,29 @@ def transform(self, from_frame, to_frame, *args, **kwargs): Initial coordinate frame. to_frame : str, or instance of `~gwcs.cordinate_frames.CoordinateFrame` Coordinate frame into which to transform. - args : float - input coordinates to transform args : float or array-like Inputs in ``from_frame``, separate inputs for each dimension. output : str One of [``numericals``, ``numericals_plus``] If ``numericals_plus`` - returns a `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` object. + with_bounding_box : bool, optional + If True(default) values in the result which correspond to any of the inputs being + outside the bounding_box are set to ``fill_value``. + fill_value : float, optional + Output value for inputs outside the bounding_box (default is np.nan). """ + fill_value = kwargs.pop('fill_value', np.nan) + with_bounding_box = kwargs.pop('with_bounding_box', True) + transform = self.get_transform(from_frame, to_frame) if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) - result = transform(*args) + if with_bounding_box: + result = self._with_bounding_box(transform, fill_value, *args) + else: + result = transform(*args) output = kwargs.pop("output", None) if output == "numericals_plus": to_frame_name, to_frame_obj = self._get_frame_name(to_frame) @@ -582,9 +607,7 @@ def footprint(self, bounding_box=None, center=False): bb = bounding_box vertices = np.asarray([[bb[0][0], bb[1][0]], [bb[0][0], bb[1][1]], [bb[0][1], bb[1][1]], [bb[0][1], bb[1][0]]]).T - print('bb', bb) - print('vert', vertices) if center: vertices = _toindex(vertices) - result = self.__call__(*vertices) + result = self.__call__(*vertices, **{'with_bounding_box': False}) return np.asarray(result) From b80ea1e7e85042e1b75222d516e44f90086879a4 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 7 Apr 2017 14:35:34 -0400 Subject: [PATCH 17/40] more python 2 problems fixed --- gwcs/wcs.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 3acb2711..68dc8918 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -231,17 +231,20 @@ def __call__(self, *args, **kwargs): if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") - fill_value = kwargs.pop('fill_value', np.nan) - with_bounding_box = kwargs.pop('with_bounding_box', True) - output = kwargs.pop("output", "numericals") + if 'with_bounding_box' not in kwargs: + kwargs['with_bounding_box'] = True # Set values outside the ``bounding_box`` to `fill_value``. - if with_bounding_box and self.bounding_box: - result = self._with_bounding_box(self.forward_transform, fill_value, *args, - bbox=self.bounding_box) + if kwargs['with_bounding_box'] and self.bounding_box: + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan + kwargs['transform'] = self.forward_transform + kwargs['bbox'] = self.bounding_box + result = self._with_bounding_box(*args, **kwargs) else: result = self.forward_transform(*args) + output = kwargs.pop("output", "numericals") if output not in ["numericals", "numericals_plus"]: raise ValueError("'output' should be 'numericals' or " "'numericals_plus', not '{0}'.".format(output)) @@ -254,7 +257,7 @@ def __call__(self, *args, **kwargs): raise ValueError("Type of output unrecognized {0}".format(output)) return result - def _with_bounding_box(self, transform, fill_value, *args, bbox=None): + def _with_bounding_box(self, *args, **kwargs): """ Evaluate the transform respecting the bounding_box. @@ -274,6 +277,9 @@ def _with_bounding_box(self, transform, fill_value, *args, bbox=None): Sometimes the bounding_box is attached to part of the transform. Instead of looking for it just pass it. """ + transform = kwargs.pop('transform', None) + bbox = kwargs.pop('bbox', None) + # Set values outside the ``bounding_box`` to `fill_value``. result = transform(*args) if bbox is None: @@ -291,7 +297,7 @@ def _with_bounding_box(self, transform, fill_value, *args, bbox=None): axis_ind = np.zeros(inp.shape, dtype=np.bool) axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) for ind, _ in enumerate(result): - result[ind][axis_ind] = fill_value + result[ind][axis_ind] = kwargs['fill_value'] if np.isscalar(args[0]): result = tuple([np.asscalar(r) for r in result]) else: @@ -320,12 +326,14 @@ def invert(self, *args, **kwargs): if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) - fill_value = kwargs.pop('fill_value', np.nan) with_bounding_box = kwargs.pop('with_bounding_box', True) try: if with_bounding_box: - result = self._with_bounding_box(self.backward_transform, fill_value, *args) + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan + kwargs['transform'] = self.backward_transform + result = self._with_bounding_box(*args, **kwargs) else: result = self.backward_transform(*args) except (NotImplementedError, KeyError): @@ -367,15 +375,16 @@ def transform(self, from_frame, to_frame, *args, **kwargs): fill_value : float, optional Output value for inputs outside the bounding_box (default is np.nan). """ - fill_value = kwargs.pop('fill_value', np.nan) - with_bounding_box = kwargs.pop('with_bounding_box', True) - transform = self.get_transform(from_frame, to_frame) if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) + with_bounding_box = kwargs.pop('with_bounding_box', True) if with_bounding_box: - result = self._with_bounding_box(transform, fill_value, *args) + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan + kwargs['transform'] = transform + result = self._with_bounding_box(*args, **kwargs) else: result = transform(*args) output = kwargs.pop("output", None) From e6abd8a92100f65607620baf46895bbceff3c13c Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sat, 22 Apr 2017 17:56:42 -0400 Subject: [PATCH 18/40] improve bbox evaluation --- gwcs/wcs.py | 62 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 68dc8918..db65f7bf 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -279,25 +279,63 @@ def _with_bounding_box(self, *args, **kwargs): """ transform = kwargs.pop('transform', None) bbox = kwargs.pop('bbox', None) + fill_value = kwargs.pop('fill_value', np.nan) # Set values outside the ``bounding_box`` to `fill_value``. - result = transform(*args) + if bbox is None: try: - bbox = transform.bounding_box[::-1] + # Get the bbox from the transform + bbox = transform.bounding_box except NotImplementedError: bbox = None + if transform.n_inputs > 1 and bbox is not None: + # The bbox on a transform is in python order + bbox = bbox[::-1] if bbox is None: - return result - inputs = [np.array(arg) for arg in args] - result = [np.array(r) for r in result] - - for ind, inp in enumerate(inputs): + return transform(*args) + + args = [np.array(arg) for arg in args] + if len(args) == 1: + bbox = [bbox] + + # indices where input is outside the bbox + # have a value of 1 in ``nan_ind`` + nan_ind = np.zeros(args[0].shape) + for ind, inp in enumerate(args): # Pass an ``out`` array so that ``axis_ind`` is array for scalars as well. axis_ind = np.zeros(inp.shape, dtype=np.bool) axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) - for ind, _ in enumerate(result): - result[ind][axis_ind] = kwargs['fill_value'] + nan_ind[axis_ind] = 1 + + # get an array with indices of valid inputs + valid_ind = np.logical_not(nan_ind).nonzero() + # inputs holds only inputs within the bbox + inputs = [] + for arg in args: + if not arg.shape: + # shape is () + if valid_ind[0] != 0: + return tuple([np.nan for a in args]) + else: + inputs.append(arg) + else: + inputs.append(arg[valid_ind]) + valid_result = transform(*inputs) + if transform.n_outputs == 1: + valid_result = [valid_result] + # combine the valid results with the ``fill_value`` values + # outside the bbox + result = [np.zeros(args[0].shape) + kwargs['fill_value'] for i in range(len(valid_result))] + for ind, r in enumerate(valid_result): + if not result[ind].shape: + # the sahpe is () + result[ind] = r + else: + result[ind][valid_ind] = r + # format output + if transform.n_outputs == 1: + return result[0] if np.isscalar(args[0]): result = tuple([np.asscalar(r) for r in result]) else: @@ -494,14 +532,16 @@ def bounding_box(self): transform_0 = self.get_transform(frames[0], frames[1]) try: # Model.bounding_box is in numpy order, need to reverse it first. - bb = transform_0.bounding_box[::-1] + bb = transform_0.bounding_box#[::-1] except NotImplementedError: return None + if transform_0.n_inputs == 1: + return bb try: axes_order = self.input_frame.axes_order except AttributeError: axes_order = np.arange(transform_0.n_inputs) - bb = np.array(bb)[np.array(axes_order)] + bb = np.array(bb[::-1])[np.array(axes_order)] return tuple(tuple(item) for item in bb) @bounding_box.setter From 618d170830a4a1eaa217c93542fc3e343fc2125b Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sat, 22 Apr 2017 18:17:00 -0400 Subject: [PATCH 19/40] fix tests --- gwcs/tests/test_wcs.py | 11 +++++++++++ gwcs/wcs.py | 10 ++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 6c25e92c..3dfdde24 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -233,6 +233,17 @@ def test_bounding_box_eval(): # test ``transform`` method assert_allclose(w.transform('detector', 'sky', 1, 7, 3), [np.nan, np.nan, np.nan]) + +def test_number_of_inputs(): + points = np.arange(5) + values = np.array([1.5, 3.4, 6.7, 7, 32]) + t = models.Tabular1D(points, values) + pipe = [('detector', t), ('world', None)] + w = wcs.WCS(pipe) + assert_allclose(w(1), 3.4) + assert_allclose(w([1, 2]), [3.4, 6.7]) + assert np.isscalar(w(1)) + class TestImaging(object): diff --git a/gwcs/wcs.py b/gwcs/wcs.py index db65f7bf..8c8a8d0b 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -281,8 +281,6 @@ def _with_bounding_box(self, *args, **kwargs): bbox = kwargs.pop('bbox', None) fill_value = kwargs.pop('fill_value', np.nan) - # Set values outside the ``bounding_box`` to `fill_value``. - if bbox is None: try: # Get the bbox from the transform @@ -307,7 +305,7 @@ def _with_bounding_box(self, *args, **kwargs): axis_ind = np.zeros(inp.shape, dtype=np.bool) axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) nan_ind[axis_ind] = 1 - + # get an array with indices of valid inputs valid_ind = np.logical_not(nan_ind).nonzero() # inputs holds only inputs within the bbox @@ -315,8 +313,8 @@ def _with_bounding_box(self, *args, **kwargs): for arg in args: if not arg.shape: # shape is () - if valid_ind[0] != 0: - return tuple([np.nan for a in args]) + if nan_ind: + return tuple([fill_value for a in args]) else: inputs.append(arg) else: @@ -326,7 +324,7 @@ def _with_bounding_box(self, *args, **kwargs): valid_result = [valid_result] # combine the valid results with the ``fill_value`` values # outside the bbox - result = [np.zeros(args[0].shape) + kwargs['fill_value'] for i in range(len(valid_result))] + result = [np.zeros(args[0].shape) + fill_value for i in range(len(valid_result))] for ind, r in enumerate(valid_result): if not result[ind].shape: # the sahpe is () From b1b91fc119cbe0446711c10fd5172acbd0fed304 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sat, 22 Apr 2017 18:18:35 -0400 Subject: [PATCH 20/40] fix test --- gwcs/tests/test_wcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 3dfdde24..6448ce73 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -234,7 +234,7 @@ def test_bounding_box_eval(): assert_allclose(w.transform('detector', 'sky', 1, 7, 3), [np.nan, np.nan, np.nan]) -def test_number_of_inputs(): +def test_format_output(): points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) From 83567c6ec0966fc7a0ada122717fceac918122fc Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sat, 22 Apr 2017 20:17:51 -0400 Subject: [PATCH 21/40] add scipy as test dependency --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d7410d3f..40b7d43a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ env: - NUMPY_VERSION=1.9 - ASTROPY_VERSION=development - MAIN_CMD='python setup.py' - - CONDA_DEPENDENCIES='' + - CONDA_DEPENDENCIES='scipy' - PIP_DEPENDENCIES='git+https://github.com/spacetelescope/pyasdf.git#egg=pyasdf' matrix: - SETUP_CMD='egg_info' From e0b2de7106a8ad53d8aa08c39efe6a950fda06c3 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Tue, 18 Apr 2017 16:46:46 -0400 Subject: [PATCH 22/40] label mapper function --- gwcs/selector.py | 65 +++++++++++++++++++++++++++++++++++++-- gwcs/tags/selectortags.py | 20 +++++++++--- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/gwcs/selector.py b/gwcs/selector.py index 8af8607f..98b7ac17 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -69,12 +69,14 @@ import warnings import numpy as np from astropy.modeling.core import Model +from astropy.modeling import models as astmodels from . import region from .utils import RegionError, _toindex -__all__ = ['LabelMapperArray', 'LabelMapperDict', 'LabelMapperRange', 'RegionsSelector'] +__all__ = ['LabelMapperArray', 'LabelMapperDict', 'LabelMapperRange', 'RegionsSelector', + 'LabelMapper'] def get_unique_regions(regions): @@ -306,7 +308,6 @@ def evaluate(self, *args): # If this is part of a combined transform, some of the inputs # may be NaNs. # Set NaNs to the ``_no_label`` value - #res[np.isnan(keys)] = self._no_label mapper_keys = list(self.mapper.keys()) # Loop over the keys in mapper and compare to inputs. # Find the indices where they are within ``atol`` @@ -568,3 +569,63 @@ def outputs(self): @property def selector(self): return self._selector + + +class LabelMapper(_LabelMapper): + """ + Maps inputs to regions. Returns the region labels corresponding to the inputs. + + Labels are strings or numbers which uniquely identify a location. + For example, labels may represent slices of an IFU or names of spherical polygons. + + Parameters + ---------- + mapper : `~astropy.modeling.core.Model` + A function which returns a region. + no_label : str or int + "" or 0 + A return value for a location which has no corresponding label. + inputs_mapping : `~astropy.modeling.mappings.Mapping` or tuple + An optional Mapping model to be prepended to the LabelMapper + with the purpose to filter the inputs or change their order. + If tuple, a `~astropy.modeling.mappings.Mapping` model will be created from it. + name : str + The name of this transform. + """ + + outputs = ('label',) + + def __init__(self, inputs, mapper, no_label=np.nan, inputs_mapping=None, name=None, **kwargs): + self._no_label = no_label + self.inputs = inputs + self.outputs = tuple(['x{0}'.format(ind) for ind in list(range(mapper.n_outputs))]) + if isinstance(inputs_mapping, tuple): + inputs_mapping = astmodels.Mapping(inputs_mapping) + elif inputs_mapping is not None and not isinstance(inputs_mapping, astmodels.Mapping): + raise TypeError("inputs-mapping must be an instance of astropy.modeling.Mapping.") + + self._inputs_mapping = inputs_mapping + self._mapper = mapper + super(_LabelMapper, self).__init__(name=name, **kwargs) + + @property + def mapper(self): + return self._mapper + + @property + def inputs_mapping(self): + return self._inputs_mapping + + @property + def no_label(self): + return self._no_label + + def evaluate(self, *args): + if self.inputs_mapping is not None: + args = self.inputs_mapping(*args) + if self.n_outputs == 1: + args = [args] + res = self.mapper(*args) + if np.isscalar(res): + res = np.array([res]) + return np.array(res) diff --git a/gwcs/tags/selectortags.py b/gwcs/tags/selectortags.py index a6336bc5..f9541c16 100644 --- a/gwcs/tags/selectortags.py +++ b/gwcs/tags/selectortags.py @@ -7,6 +7,7 @@ import numpy as np from numpy.testing import assert_array_equal from astropy.modeling import models +from astropy.modeling.core import Model from astropy.utils.misc import isiterable from asdf import yamlutil @@ -21,7 +22,7 @@ class LabelMapperType(TransformType): name = "transform/label_mapper" - types = [LabelMapperArray, LabelMapperDict, LabelMapperRange] + types = [LabelMapperArray, LabelMapperDict, LabelMapperRange, LabelMapper] @classmethod def from_tree_transform(cls, node, ctx): @@ -31,12 +32,16 @@ def from_tree_transform(cls, node, ctx): "of astropy.modeling.models.Mapping.") mapper = node['mapper'] atol = node.get('atol', 10**-8) + no_label = node.get('no_label', np.nan) if isinstance(mapper, NDArrayType): if mapper.ndim != 2: raise NotImplementedError( "GWCS currently only supports 2x2 masks ") return LabelMapperArray(mapper, inputs_mapping) + elif isinstance(mapper, Model): + inputs = node.get('inputs') + return LabelMapper(inputs, mapper, inputs_mapping=inputs_mapping, no_label=no_label) else: inputs = node.get('inputs', None) if inputs is not None: @@ -54,9 +59,16 @@ def from_tree_transform(cls, node, ctx): @classmethod def to_tree_transform(cls, model, ctx): node = OrderedDict() + node['no_label'] = model.no_label + if model.inputs_mapping is not None: + node['inputs_mapping'] = model.inputs_mapping + if isinstance(model, LabelMapperArray): node['mapper'] = model.mapper - if isinstance(model, (LabelMapperDict, LabelMapperRange)): + elif isinstance(model, LabelMapper): + node['mapper'] = model.mapper + node['inputs'] = list(model.inputs) + elif isinstance(model, (LabelMapperDict, LabelMapperRange)): if hasattr(model, 'atol'): node['atol'] = model.atol mapper = OrderedDict() @@ -71,8 +83,8 @@ def to_tree_transform(cls, model, ctx): mapper['models'] = transforms node['mapper'] = mapper node['inputs'] = list(model.inputs) - if model.inputs_mapping is not None: - node['inputs_mapping'] = model.inputs_mapping + else: + raise TypeError("Unrecognized type of LabelMapper - {0}".format(model)) return yamlutil.custom_tree_to_tagged_tree(node, ctx) From aa5f56026e4e41fba59aea05f773099f81f25474 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sun, 23 Apr 2017 11:53:46 -0400 Subject: [PATCH 23/40] Turn an error into a warning. refactor grid_from_bounding_box --- gwcs/selector.py | 2 +- gwcs/wcstools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gwcs/selector.py b/gwcs/selector.py index 98b7ac17..b0f60525 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -525,7 +525,7 @@ def evaluate(self, *args): rids = self.label_mapper(*args).flatten() # Raise an error if all pixels are outside regions if (rids == self.label_mapper.no_label).all(): - raise RegionError("The input positions are not inside any region.") + warnings.warn("The input positions are not inside any region.") # Create output arrays and set any pixels not within regions to # "undefined_transform_value" diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 0cb11d2b..947763d7 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -219,7 +219,7 @@ def grid_from_bounding_box(bounding_box, step=1, center=True): """ slices = [] if center: - bb = _toindex(bounding_box) + bb = tuple([(np.floor(b[0] + 0.5), np.ceil(b[1] - .5)) for b in bounding_box]) else: bb = bounding_box for d, s in zip(bb, step): From 38585dde8ecbbed69d10f7c69c18c678e4158d0d Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 28 Apr 2017 16:10:49 -0400 Subject: [PATCH 24/40] log entry [skip ci] --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 97c5853c..415a6aa3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,10 @@ - Replace ``domain`` with ``bounding_box``. [#74] +- Added a ``LabelMapper`` model where ``mapper`` is an instance of + `~astropy.modeling.core.Model`. [#78] + + 0.7 (2016-12-23) ---------------- From c832a202dfac108e7276e9b5e81ef1b0dd87098d Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Sun, 14 May 2017 08:45:07 -0400 Subject: [PATCH 25/40] update tavis --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 40b7d43a..19d42922 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,13 +15,14 @@ addons: python: - 2.7 - 3.5 + - 3.6 env: global: # The following versions are the 'default' for tests, unless # overidden underneath. They are defined here in order to save having # to repeat them for all configurations. - - NUMPY_VERSION=1.9 + - NUMPY_VERSION=1.11 - ASTROPY_VERSION=development - MAIN_CMD='python setup.py' - CONDA_DEPENDENCIES='scipy' @@ -48,11 +49,11 @@ matrix: # Numpy - python: 2.7 - env: NUMPY_VERSION=1.8 SETUP_CMD="test" + env: NUMPY_VERSION=1.12 SETUP_CMD="test" - python: 2.7 env: NUMPY_VERSION=1.10 SETUP_CMD="test" - - python: 3.5 - env: NUMPY_VERSION=1.10 SETUP_CMD="test" + - python: 3.6 + env: NUMPY_VERSION=1.12 SETUP_CMD="test" # Do a PEP8 test with pycodestyle - python: 3.5 From 60727199626e61b56077ce94e08869d4aeb5c308 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 31 May 2017 17:17:05 -0400 Subject: [PATCH 26/40] Move evaluation with bounding_box to astropy.modeling. --- CHANGES.rst | 5 ++ gwcs/selector.py | 7 ++- gwcs/wcs.py | 153 ++++++++++++----------------------------------- 3 files changed, 49 insertions(+), 116 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 415a6aa3..b0fc90b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,11 @@ - Added a ``LabelMapper`` model where ``mapper`` is an instance of `~astropy.modeling.core.Model`. [#78] +- Evaluating a WCS with bounding box was moved to ``astropy.modeling``. [#86] + +- RegionsSelector now handles the case when a label does not have a corresponding + transform and returns RegionsSelector.undefined_transform_value. [#86] + 0.7 (2016-12-23) ---------------- diff --git a/gwcs/selector.py b/gwcs/selector.py index b0f60525..b68d19c8 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -541,7 +541,12 @@ def evaluate(self, *args): for rid in uniq: ind = (rids == rid) inputs = [a[ind] for a in args] - result = self._selector[rid](*inputs) + if rid in self._selector: + result = self._selector[rid](*inputs) + else: + # If there's no transform for a label, return np.nan + result = [np.empty(inputs[0].shape) + + self._undefined_transform_value for i in range(self.n_outputs)] for j in range(self.n_outputs): outputs[j][ind] = result[j] return outputs diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 8c8a8d0b..e0ff7e61 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -231,20 +231,24 @@ def __call__(self, *args, **kwargs): if self.forward_transform is None: raise NotImplementedError("WCS.forward_transform is not implemented.") + output = kwargs.pop("output", "numericals") if 'with_bounding_box' not in kwargs: kwargs['with_bounding_box'] = True + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan + + transform = self.forward_transform + if self.bounding_box is not None: + # Currently compound models do not attempt to combine individual model + # bounding boxes. Get the forward transform and assign the ounding_box to it + # before evaluating it. The order Model.bounding_box is reversed. + axes_ind = self._get_axes_indices() + if transform.n_inputs > 1: + transform.bounding_box = np.array(self.bounding_box)[axes_ind][::-1] + else: + transform.bounding_box = self.bounding_box + result = transform(*args, **kwargs) - # Set values outside the ``bounding_box`` to `fill_value``. - if kwargs['with_bounding_box'] and self.bounding_box: - if 'fill_value' not in kwargs: - kwargs['fill_value'] = np.nan - kwargs['transform'] = self.forward_transform - kwargs['bbox'] = self.bounding_box - result = self._with_bounding_box(*args, **kwargs) - else: - result = self.forward_transform(*args) - - output = kwargs.pop("output", "numericals") if output not in ["numericals", "numericals_plus"]: raise ValueError("'output' should be 'numericals' or " "'numericals_plus', not '{0}'.".format(output)) @@ -257,88 +261,6 @@ def __call__(self, *args, **kwargs): raise ValueError("Type of output unrecognized {0}".format(output)) return result - def _with_bounding_box(self, *args, **kwargs): - """ - Evaluate the transform respecting the bounding_box. - - TODO: Move this to modeling. - - Parameters - ---------- - transform : `~astropy.modeling.Model` - Transform to evaluate - fill_value : float - Fill value - args : list - Inputs - kwargs: dict, optional - pass a bbox here if necessary - bbox : a bounding_box - Sometimes the bounding_box is attached to part of - the transform. Instead of looking for it just pass it. - """ - transform = kwargs.pop('transform', None) - bbox = kwargs.pop('bbox', None) - fill_value = kwargs.pop('fill_value', np.nan) - - if bbox is None: - try: - # Get the bbox from the transform - bbox = transform.bounding_box - except NotImplementedError: - bbox = None - if transform.n_inputs > 1 and bbox is not None: - # The bbox on a transform is in python order - bbox = bbox[::-1] - if bbox is None: - return transform(*args) - - args = [np.array(arg) for arg in args] - if len(args) == 1: - bbox = [bbox] - - # indices where input is outside the bbox - # have a value of 1 in ``nan_ind`` - nan_ind = np.zeros(args[0].shape) - for ind, inp in enumerate(args): - # Pass an ``out`` array so that ``axis_ind`` is array for scalars as well. - axis_ind = np.zeros(inp.shape, dtype=np.bool) - axis_ind = np.logical_or(inp < bbox[ind][0], inp > bbox[ind][1], out=axis_ind) - nan_ind[axis_ind] = 1 - - # get an array with indices of valid inputs - valid_ind = np.logical_not(nan_ind).nonzero() - # inputs holds only inputs within the bbox - inputs = [] - for arg in args: - if not arg.shape: - # shape is () - if nan_ind: - return tuple([fill_value for a in args]) - else: - inputs.append(arg) - else: - inputs.append(arg[valid_ind]) - valid_result = transform(*inputs) - if transform.n_outputs == 1: - valid_result = [valid_result] - # combine the valid results with the ``fill_value`` values - # outside the bbox - result = [np.zeros(args[0].shape) + fill_value for i in range(len(valid_result))] - for ind, r in enumerate(valid_result): - if not result[ind].shape: - # the sahpe is () - result[ind] = r - else: - result[ind][valid_ind] = r - # format output - if transform.n_outputs == 1: - return result[0] - if np.isscalar(args[0]): - result = tuple([np.asscalar(r) for r in result]) - else: - result = tuple(result) - return result def invert(self, *args, **kwargs): """ @@ -362,19 +284,17 @@ def invert(self, *args, **kwargs): if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) - with_bounding_box = kwargs.pop('with_bounding_box', True) + output = kwargs.pop('output', None) + if 'with_bounding_box' not in kwargs: + kwargs['with_bounding_box'] = True + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan try: - if with_bounding_box: - if 'fill_value' not in kwargs: - kwargs['fill_value'] = np.nan - kwargs['transform'] = self.backward_transform - result = self._with_bounding_box(*args, **kwargs) - else: - result = self.backward_transform(*args) + result = self.backward_transform(*args, **kwargs) except (NotImplementedError, KeyError): result = self._invert(*args, **kwargs) - output = kwargs.pop('output', None) + if output == 'numericals_plus': if self.input_frame.naxes == 1: return self.input_frame.coordinates(result) @@ -415,15 +335,14 @@ def transform(self, from_frame, to_frame, *args, **kwargs): if not utils.isnumerical(args[0]): args = utils._get_values(self.unit, *args) - with_bounding_box = kwargs.pop('with_bounding_box', True) - if with_bounding_box: - if 'fill_value' not in kwargs: - kwargs['fill_value'] = np.nan - kwargs['transform'] = transform - result = self._with_bounding_box(*args, **kwargs) - else: - result = transform(*args) output = kwargs.pop("output", None) + if 'with_bounding_box' not in kwargs: + kwargs['with_bounding_box'] = True + if 'fill_value' not in kwargs: + kwargs['fill_value'] = np.nan + + result = transform(*args, **kwargs) + if output == "numericals_plus": to_frame_name, to_frame_obj = self._get_frame_name(to_frame) if to_frame_obj is not None: @@ -560,17 +479,21 @@ def bounding_box(self, value): if value is None: transform_0.bounding_box = value else: - try: - axes_ind = np.argsort(self.input_frame.axes_order) - except AttributeError: - # the case of a frame being a string - axes_ind = np.arange(transform_0.n_inputs) + axes_ind = self._get_axes_indices() try: transform_0.bounding_box = np.array(value)[axes_ind][::-1] except IndexError: raise utils.DimensionalityError("The bounding_box does not match the number of inputs.") self.set_transform(frames[0], frames[1], transform_0) + def _get_axes_indices(self): + try: + axes_ind = np.argsort(self.input_frame.axes_order) + except AttributeError: + # the case of a frame being a string + axes_ind = np.arange(self.forward_transform.n_inputs) + return axes_ind + @property def domain(self): """ From 5d12b26b9a598671c82d9bb3bfa10f96452e8bd8 Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Wed, 7 Jun 2017 17:24:57 +0100 Subject: [PATCH 27/40] Use the generic ci-helpers script, it will figure out OS [skip appveyor] --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 19d42922..5a8a0cb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ matrix: install: - git clone git://github.com/astropy/ci-helpers.git - - source ci-helpers/travis/setup_conda_$TRAVIS_OS_NAME.sh + - source ci-helpers/travis/setup_conda.sh script: - $MAIN_CMD $SETUP_CMD From 63482aac77ce9bb170f003f6f76055379b324141 Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Wed, 7 Jun 2017 17:28:05 +0100 Subject: [PATCH 28/40] Using system pytest rather than astropy bundled --- .travis.yml | 2 ++ gwcs/tags/tests/test_selector.py | 2 +- gwcs/tests/test_coordinate_systems.py | 2 +- gwcs/tests/test_region.py | 2 +- gwcs/tests/test_separable.py | 2 +- gwcs/tests/test_utils.py | 2 +- gwcs/tests/test_wcs.py | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19d42922..93965a4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,8 @@ env: - MAIN_CMD='python setup.py' - CONDA_DEPENDENCIES='scipy' - PIP_DEPENDENCIES='git+https://github.com/spacetelescope/pyasdf.git#egg=pyasdf' + - ASTROPY_USE_SYSTEM_PYTEST=1 + matrix: - SETUP_CMD='egg_info' - SETUP_CMD='test' diff --git a/gwcs/tags/tests/test_selector.py b/gwcs/tags/tests/test_selector.py index 0fdc77a7..76be9d6a 100644 --- a/gwcs/tags/tests/test_selector.py +++ b/gwcs/tags/tests/test_selector.py @@ -6,7 +6,7 @@ import numpy as np from astropy.modeling.models import Mapping, Shift, Scale, Polynomial2D -from astropy.tests.helper import pytest +import pytest from ... import selector, extension from asdf.tests import helpers from ...tests.test_region import create_range_mapper, create_scalar_mapper diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index aa3b4e7a..fe6ba25e 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -5,7 +5,7 @@ from numpy.testing import assert_allclose from astropy import units as u from astropy import coordinates as coord -from astropy.tests.helper import pytest +import pytest from .. import coordinate_frames as cf diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 68a2955b..39177e5a 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -7,7 +7,7 @@ import numpy as np from numpy.testing import utils from astropy.modeling import models -from astropy.tests.helper import pytest +import pytest from .. import region, selector diff --git a/gwcs/tests/test_separable.py b/gwcs/tests/test_separable.py index 870e800d..5f177393 100644 --- a/gwcs/tests/test_separable.py +++ b/gwcs/tests/test_separable.py @@ -6,7 +6,7 @@ from __future__ import absolute_import, division, unicode_literals, print_function from astropy.modeling import models from astropy.modeling.models import Mapping -from astropy.tests.helper import pytest +import pytest import numpy as np from numpy.testing import utils diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index 6367178a..7d2cae4c 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -4,7 +4,7 @@ from astropy.io import fits from astropy import wcs as fitswcs from astropy.utils.data import get_pkg_data_filename -from astropy.tests.helper import pytest +import pytest from numpy.testing.utils import assert_allclose from .. import utils as gwutils diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 6448ce73..0e1d0780 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -7,7 +7,7 @@ from astropy import coordinates as coord from astropy.io import fits from astropy import units as u -from astropy.tests.helper import pytest +import pytest from astropy.utils.data import get_pkg_data_filename from astropy import wcs as astwcs From 904ff7c642aa74ea6e242f9c7fb958a6802cbc17 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 21 Jun 2017 10:59:31 -0400 Subject: [PATCH 29/40] keep in sync with astropy --- gwcs/tests/test_utils.py | 17 +++++++++++++++++ gwcs/utils.py | 27 +++++++++++++++++++-------- setup.cfg | 2 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index 7d2cae4c..a2b8acc9 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -3,7 +3,11 @@ from astropy.io import fits from astropy import wcs as fitswcs +from astropy import units as u +from astropy import coordinates as coord +from astropy.modeling import models from astropy.utils.data import get_pkg_data_filename +from astropy.tests.helper import assert_quantity_allclose import pytest from numpy.testing.utils import assert_allclose @@ -27,3 +31,16 @@ def test_fits_transform(): gw1 = gwutils.make_fitswcs_transform(hdr) w1 = fitswcs.WCS(hdr) assert_allclose(gw1(1, 2), w1.wcs_pix2world(1, 2, 1), atol=10**-8) + + +def test_lon_pole(): + tan = models.Pix2Sky_TAN() + car = models.Pix2Sky_CAR() + sky_positive_lat = coord.SkyCoord(3 * u.deg, 1 * u.deg) + sky_negative_lat = coord.SkyCoord(3 * u.deg, -1 * u.deg) + assert_quantity_allclose(gwutils._compute_lon_pole(sky_positive_lat, tan), 180 * u.deg) + assert_quantity_allclose(gwutils._compute_lon_pole(sky_negative_lat, tan), 180 * u.deg) + assert_quantity_allclose(gwutils._compute_lon_pole(sky_positive_lat, car), 0 * u.deg) + assert_quantity_allclose(gwutils._compute_lon_pole(sky_negative_lat, car), 180 * u.deg) + assert_quantity_allclose(gwutils._compute_lon_pole((0, 34 * u.rad), tan), 180 * u.deg) + assert_allclose(gwutils._compute_lon_pole((1, -34), tan), 180) diff --git a/gwcs/utils.py b/gwcs/utils.py index 42d976f5..7ce4f878 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -169,30 +169,41 @@ def _compute_lon_pole(skycoord, projection): Parameters ---------- - skycoord : `astropy.coordinates.SkyCoord` + skycoord : `astropy.coordinates.SkyCoord`, or + sequence of floats or `~astropy.units.Quantity` of length 2 The fiducial point of the native coordinate system. + If tuple, it's length is 2 projection : `astropy.modeling.projections.Projection` A Projection instance. Returns ------- - lon_pole : float - Longitude in the units of skycoord.spherical + lon_pole : float or `~astropy/units.Quantity` + Native longitude of the celestial pole [deg]. TODO: Implement all projections - Currently this only supports Zenithal projection. + Currently this only supports Zenithal and Cylindrical. """ if isinstance(skycoord, coords.SkyCoord): lat = skycoord.spherical.lat + unit = u.deg else: - lat = skycoord[1] - if isinstance(projection, projections.Zenithal): - if lat < 0: - lon_pole = 180 + lon, lat = skycoord + if isinstance(lat, u.Quantity): + unit = u.deg else: + unit = None + if isinstance(projection, projections.Zenithal): + lon_pole = 180 + elif isinstance(projection, projections.Cylindrical): + if lat >= 0: lon_pole = 0 + else: + lon_pole = 180 else: raise UnsupportedProjectionError("Projection {0} is not supported.".format(projection)) + if unit is not None: + lon_pole = lon_pole * unit return lon_pole diff --git a/setup.cfg b/setup.cfg index 58aa5f69..bbf8b248 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ all_files = 1 upload-dir = docs/_build/html show-response = 1 -[pytest] +[tool:pytest] minversion = 2.2 norecursedirs = build docs/_build From 68e5e2ade5546af042ddf494e2f6e9ec5724bbe0 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 21 Jun 2017 14:58:39 -0400 Subject: [PATCH 30/40] fix typo [skip ci] --- gwcs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwcs/utils.py b/gwcs/utils.py index 7ce4f878..90c84b50 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -172,7 +172,7 @@ def _compute_lon_pole(skycoord, projection): skycoord : `astropy.coordinates.SkyCoord`, or sequence of floats or `~astropy.units.Quantity` of length 2 The fiducial point of the native coordinate system. - If tuple, it's length is 2 + If tuple, its length is 2 projection : `astropy.modeling.projections.Projection` A Projection instance. From 455921b52e7ab754c372e43fe8e1054194318f4d Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Sat, 8 Jul 2017 00:05:57 +0100 Subject: [PATCH 31/40] Updated astropy-helpers to v2.0 --- ah_bootstrap.py | 57 ++----- astropy_helpers | 2 +- ez_setup.py | 436 ++++++++++++++++++++++++++---------------------- 3 files changed, 249 insertions(+), 246 deletions(-) diff --git a/ah_bootstrap.py b/ah_bootstrap.py index 0dc50071..786b8b14 100644 --- a/ah_bootstrap.py +++ b/ah_bootstrap.py @@ -91,13 +91,24 @@ use_setuptools() +# typing as a dependency for 1.6.1+ Sphinx causes issues when imported after +# initializing submodule with ah_boostrap.py +# See discussion and references in +# https://github.com/astropy/astropy-helpers/issues/302 + +try: + import typing # noqa +except ImportError: + pass + + # Note: The following import is required as a workaround to # https://github.com/astropy/astropy-helpers/issues/89; if we don't import this # module now, it will get cleaned up after `run_setup` is called, but that will # later cause the TemporaryDirectory class defined in it to stop working when # used later on by setuptools try: - import setuptools.py31compat + import setuptools.py31compat # noqa except ImportError: pass @@ -702,7 +713,7 @@ def _update_submodule(self, submodule, status): if self.offline: cmd.append('--no-fetch') elif status == 'U': - raise _AHBoostrapSystemExit( + raise _AHBootstrapSystemExit( 'Error: Submodule {0} contains unresolved merge conflicts. ' 'Please complete or abandon any changes in the submodule so that ' 'it is in a usable state, then try again.'.format(submodule)) @@ -763,7 +774,7 @@ def run_cmd(cmd): msg = 'Command not found: `{0}`'.format(' '.join(cmd)) raise _CommandNotFound(msg, cmd) else: - raise _AHBoostrapSystemExit( + raise _AHBootstrapSystemExit( 'An unexpected error occurred when running the ' '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) @@ -878,46 +889,6 @@ def __init__(self, *args): super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) -if sys.version_info[:2] < (2, 7): - # In Python 2.6 the distutils log does not log warnings, errors, etc. to - # stderr so we have to wrap it to ensure consistency at least in this - # module - import distutils - - class log(object): - def __getattr__(self, attr): - return getattr(distutils.log, attr) - - def warn(self, msg, *args): - self._log_to_stderr(distutils.log.WARN, msg, *args) - - def error(self, msg): - self._log_to_stderr(distutils.log.ERROR, msg, *args) - - def fatal(self, msg): - self._log_to_stderr(distutils.log.FATAL, msg, *args) - - def log(self, level, msg, *args): - if level in (distutils.log.WARN, distutils.log.ERROR, - distutils.log.FATAL): - self._log_to_stderr(level, msg, *args) - else: - distutils.log.log(level, msg, *args) - - def _log_to_stderr(self, level, msg, *args): - # This is the only truly 'public' way to get the current threshold - # of the log - current_threshold = distutils.log.set_threshold(distutils.log.WARN) - distutils.log.set_threshold(current_threshold) - if level >= current_threshold: - if args: - msg = msg % args - sys.stderr.write('%s\n' % msg) - sys.stderr.flush() - - log = log() - - BOOTSTRAPPER = _Bootstrapper.main() diff --git a/astropy_helpers b/astropy_helpers index 70b09af7..ed2e7897 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit 70b09af7e4f6b8a4e409412a913ff2adb05c12b7 +Subproject commit ed2e7897862ae9e979b336df670d7a5541cdd8f0 diff --git a/ez_setup.py b/ez_setup.py index 9dc2c872..800c31ef 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -1,66 +1,62 @@ -#!python -"""Bootstrap setuptools installation +#!/usr/bin/env python -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: +""" +Setuptools bootstrapping installer. - from ez_setup import use_setuptools - use_setuptools() +Maintained at https://github.com/pypa/setuptools/tree/bootstrap. -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. +Run this script to install or upgrade setuptools. -This file can also be run as a script to install or upgrade setuptools. +This method is DEPRECATED. Check https://github.com/pypa/setuptools/issues/581 for more details. """ + import os import shutil import sys import tempfile -import tarfile +import zipfile import optparse import subprocess import platform +import textwrap +import contextlib from distutils import log +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + try: from site import USER_SITE except ImportError: USER_SITE = None -DEFAULT_VERSION = "1.4.2" -DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" +# 33.1.1 is the last version that supports setuptools self upgrade/installation. +DEFAULT_VERSION = "33.1.1" +DEFAULT_URL = "https://pypi.io/packages/source/s/setuptools/" +DEFAULT_SAVE_DIR = os.curdir +DEFAULT_DEPRECATION_MESSAGE = "ez_setup.py is deprecated and when using it setuptools will be pinned to {0} since it's the last version that supports setuptools self upgrade/installation, check https://github.com/pypa/setuptools/issues/581 for more info; use pip to install setuptools" + +MEANINGFUL_INVALID_ZIP_ERR_MSG = 'Maybe {0} is corrupted, delete it and try again.' + +log.warn(DEFAULT_DEPRECATION_MESSAGE.format(DEFAULT_VERSION)) + def _python_cmd(*args): + """ + Execute a command. + + Return True if the command succeeded. + """ args = (sys.executable,) + args return subprocess.call(args) == 0 -def _check_call_py24(cmd, *args, **kwargs): - res = subprocess.call(cmd, *args, **kwargs) - class CalledProcessError(Exception): - pass - if not res == 0: - msg = "Command '%s' return non-zero exit status %d" % (cmd, res) - raise CalledProcessError(msg) -vars(subprocess).setdefault('check_call', _check_call_py24) - -def _install(tarball, install_args=()): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) +def _install(archive_filename, install_args=()): + """Install Setuptools.""" + with archive_context(archive_filename): # installing log.warn('Installing Setuptools') if not _python_cmd('setup.py', 'install', *install_args): @@ -68,93 +64,167 @@ def _install(tarball, install_args=()): log.warn('See the error message above.') # exitcode will be 2 return 2 - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) -def _build_egg(egg, tarball, to_dir): - # extracting the tarball +def _build_egg(egg, archive_filename, to_dir): + """Build Setuptools egg.""" + with archive_context(archive_filename): + # building an egg + log.warn('Building a Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +class ContextualZipFile(zipfile.ZipFile): + + """Supplement ZipFile class to support context manager for Python 2.6.""" + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def __new__(cls, *args, **kwargs): + """Construct a ZipFile or ContextualZipFile as appropriate.""" + if hasattr(zipfile.ZipFile, '__exit__'): + return zipfile.ZipFile(*args, **kwargs) + return super(ContextualZipFile, cls).__new__(cls) + + +@contextlib.contextmanager +def archive_context(filename): + """ + Unzip filename to a temporary directory, set to the cwd. + + The unzipped target is cleaned up after. + """ tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() + try: + with ContextualZipFile(filename) as archive: + archive.extractall() + except zipfile.BadZipfile as err: + if not err.args: + err.args = ('', ) + err.args = err.args + ( + MEANINGFUL_INVALID_ZIP_ERR_MSG.format(filename), + ) + raise # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) - - # building an egg - log.warn('Building a Setuptools egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + yield finally: os.chdir(old_wd) shutil.rmtree(tmpdir) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) + """Download Setuptools.""" + py_desig = 'py{sys.version_info[0]}.{sys.version_info[1]}'.format(sys=sys) + tp = 'setuptools-{version}-{py_desig}.egg' + egg = os.path.join(to_dir, tp.format(**locals())) if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - _build_egg(egg, tarball, to_dir) + archive = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, archive, to_dir) sys.path.insert(0, egg) # Remove previously-imported pkg_resources if present (see # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). if 'pkg_resources' in sys.modules: - del sys.modules['pkg_resources'] + _unload_pkg_resources() import setuptools setuptools.bootstrap_install_from = egg -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15): - # making sure we use the absolute path +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=DEFAULT_SAVE_DIR, download_delay=15): + """ + Ensure that a setuptools version is installed. + + Return None. Raise SystemExit if the requested version + or later cannot be installed. + """ to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules + + # prior to importing, capture the module state for + # representative modules. + rep_modules = 'pkg_resources', 'setuptools' + imported = set(sys.modules).intersection(rep_modules) + try: import pkg_resources - except ImportError: - return _do_download(version, download_base, to_dir, download_delay) - try: pkg_resources.require("setuptools>=" + version) + # a suitable version is already installed return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of setuptools (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U setuptools'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) + except ImportError: + # pkg_resources not available; setuptools is not installed; download + pass except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) + # no version of setuptools was found; allow download + pass + except pkg_resources.VersionConflict as VC_err: + if imported: + _conflict_bail(VC_err, version) + + # otherwise, unload pkg_resources to allow the downloaded version to + # take precedence. + del pkg_resources + _unload_pkg_resources() + + return _do_download(version, download_base, to_dir, download_delay) + + +def _conflict_bail(VC_err, version): + """ + Setuptools was imported prior to invocation, so it is + unsafe to unload it. Bail out. + """ + conflict_tmpl = textwrap.dedent(""" + The required version of setuptools (>={version}) is not available, + and can't be installed while this script is running. Please + install a more recent version first, using + 'easy_install -U setuptools'. + + (Currently using {VC_err.args[0]!r}) + """) + msg = conflict_tmpl.format(**locals()) + sys.stderr.write(msg) + sys.exit(2) + + +def _unload_pkg_resources(): + sys.meta_path = [ + importer + for importer in sys.meta_path + if importer.__class__.__module__ != 'pkg_resources.extern' + ] + del_modules = [ + name for name in sys.modules + if name.startswith('pkg_resources') + ] + for mod_name in del_modules: + del sys.modules[mod_name] + def _clean_check(cmd, target): """ - Run the command to download target. If the command fails, clean up before - re-raising the error. + Run the command to download target. + + If the command fails, clean up before re-raising the error. """ try: subprocess.check_call(cmd) @@ -163,115 +233,110 @@ def _clean_check(cmd, target): os.unlink(target) raise + def download_file_powershell(url, target): """ - Download the file at url to target using Powershell (which will validate - trust). Raise an exception if the command cannot complete. + Download the file at url to target using Powershell. + + Powershell will validate trust. + Raise an exception if the command cannot complete. """ target = os.path.abspath(target) + ps_cmd = ( + "[System.Net.WebRequest]::DefaultWebProxy.Credentials = " + "[System.Net.CredentialCache]::DefaultCredentials; " + '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")' + % locals() + ) cmd = [ 'powershell', '-Command', - "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), + ps_cmd, ] _clean_check(cmd, target) + def has_powershell(): + """Determine if Powershell is available.""" if platform.system() != 'Windows': return False cmd = ['powershell', '-Command', 'echo test'] - devnull = open(os.path.devnull, 'wb') - try: + with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except Exception: return False - finally: - devnull.close() return True - download_file_powershell.viable = has_powershell + def download_file_curl(url, target): - cmd = ['curl', url, '--silent', '--output', target] + cmd = ['curl', url, '--location', '--silent', '--output', target] _clean_check(cmd, target) + def has_curl(): cmd = ['curl', '--version'] - devnull = open(os.path.devnull, 'wb') - try: + with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except Exception: return False - finally: - devnull.close() return True - download_file_curl.viable = has_curl + def download_file_wget(url, target): cmd = ['wget', url, '--quiet', '--output-document', target] _clean_check(cmd, target) + def has_wget(): cmd = ['wget', '--version'] - devnull = open(os.path.devnull, 'wb') - try: + with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except Exception: return False - finally: - devnull.close() return True - download_file_wget.viable = has_wget + def download_file_insecure(url, target): - """ - Use Python to download the file, even though it cannot authenticate the - connection. - """ - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - src = dst = None + """Use Python to download the file, without connection authentication.""" + src = urlopen(url) try: - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. + # Read all the data in one block. data = src.read() - dst = open(target, "wb") - dst.write(data) finally: - if src: - src.close() - if dst: - dst.close() + src.close() + # Write all the data in one block to avoid creating a partial file. + with open(target, "wb") as dst: + dst.write(data) download_file_insecure.viable = lambda: True + def get_best_downloader(): - downloaders = [ + downloaders = ( download_file_powershell, download_file_curl, download_file_wget, download_file_insecure, - ] + ) + viable_downloaders = (dl for dl in downloaders if dl.viable()) + return next(viable_downloaders, None) - for dl in downloaders: - if dl.viable(): - return dl -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15, - downloader_factory=get_best_downloader): - """Download setuptools from a specified location and return its filename +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=DEFAULT_SAVE_DIR, delay=15, + downloader_factory=get_best_downloader): + """ + Download setuptools from a specified location and return its filename. `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end + as an sdist for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. @@ -281,9 +346,9 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, """ # making sure we use the absolute path to_dir = os.path.abspath(to_dir) - tgz_name = "setuptools-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) + zip_name = "setuptools-%s.zip" % version + url = download_base + zip_name + saveto = os.path.join(to_dir, zip_name) if not os.path.exists(saveto): # Avoid repeated downloads log.warn("Downloading %s", url) downloader = downloader_factory() @@ -291,73 +356,21 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, return os.path.realpath(saveto) -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - def _build_install_args(options): """ - Build the arguments to 'python setup.py install' on the setuptools package + Build the arguments to 'python setup.py install' on the setuptools package. + + Returns list of command line arguments. """ - install_args = [] - if options.user_install: - if sys.version_info < (2, 6): - log.warn("--user requires Python 2.6 or later") - raise SystemExit(1) - install_args.append('--user') - return install_args + return ['--user'] if options.user_install else [] + def _parse_args(): - """ - Parse the command line for options - """ + """Parse the command line for options.""" parser = optparse.OptionParser() parser.add_option( '--user', dest='user_install', action='store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') + help='install in user site package') parser.add_option( '--download-base', dest='download_base', metavar="URL", default=DEFAULT_URL, @@ -367,16 +380,35 @@ def _parse_args(): const=lambda: download_file_insecure, default=get_best_downloader, help='Use internal, non-validating downloader' ) + parser.add_option( + '--version', help="Specify which version to download", + default=DEFAULT_VERSION, + ) + parser.add_option( + '--to-dir', + help="Directory to save (and re-use) package", + default=DEFAULT_SAVE_DIR, + ) options, args = parser.parse_args() # positional arguments are ignored return options -def main(version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" + +def _download_args(options): + """Return args for download_setuptools function from cmdline args.""" + return dict( + version=options.version, + download_base=options.download_base, + downloader_factory=options.downloader_factory, + to_dir=options.to_dir, + ) + + +def main(): + """Install or upgrade setuptools and EasyInstall.""" options = _parse_args() - tarball = download_setuptools(download_base=options.download_base, - downloader_factory=options.downloader_factory) - return _install(tarball, _build_install_args(options)) + archive = download_setuptools(**_download_args(options)) + return _install(archive, _build_install_args(options)) if __name__ == '__main__': sys.exit(main()) From 0640cd5eed60d758fa2d17f14338448769793309 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Thu, 13 Jul 2017 18:12:54 -0400 Subject: [PATCH 32/40] deal with unknown ctype values --- gwcs/tests/test_utils.py | 42 +++++++++++++++++++++- gwcs/utils.py | 76 ++++++++++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 23 deletions(-) diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index a2b8acc9..c88598f9 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -1,6 +1,6 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from __future__ import absolute_import, division, unicode_literals, print_function - +import numpy as np from astropy.io import fits from astropy import wcs as fitswcs from astropy import units as u @@ -44,3 +44,43 @@ def test_lon_pole(): assert_quantity_allclose(gwutils._compute_lon_pole(sky_negative_lat, car), 180 * u.deg) assert_quantity_allclose(gwutils._compute_lon_pole((0, 34 * u.rad), tan), 180 * u.deg) assert_allclose(gwutils._compute_lon_pole((1, -34), tan), 180) + + +def test_unknown_ctype(): + wcsinfo = {'CDELT': np.array([ 3.61111098e-05, 3.61111098e-05, 2.49999994e-03]), + 'CRPIX': np.array([ 17., 16., 1.]), + 'CRVAL': np.array([ 4.49999564e+01, 1.72786731e-04, 4.84631542e+00]), + 'CTYPE': np.array(['MRSAL1A', 'MRSBE1A', 'WAVE']), + 'CUNIT': np.array([u.Unit("deg"), u.Unit("deg"), u.Unit("um")], dtype=object), + 'PC': np.array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]), + 'WCSAXES': 3, + 'has_cd': False + } + transform = gwutils.make_fitswcs_transform(wcsinfo) + x = np.linspace(-5, 7, 10) + y = np.linspace(-5, 7, 10) + expected = (np.array([-0.00079444, -0.0007463 , -0.00069815, -0.00065 , -0.00060185, + -0.0005537 , -0.00050556, -0.00045741, -0.00040926, -0.00036111] + ), + np.array([-0.00075833, -0.00071019, -0.00066204, -0.00061389, -0.00056574, + -0.00051759, -0.00046944, -0.0004213 , -0.00037315, -0.000325 ] + ) + ) + a, b = transform(x, y) + assert_allclose(a, expected[0], atol=10**-8) + assert_allclose(b, expected[1], atol=10**-8) + + +def test_get_axes(): + wcsinfo = {'CTYPE': np.array(['MRSAL1A', 'MRSBE1A', 'WAVE'])} + cel, spec, other = gwutils.get_axes(wcsinfo) + assert not cel + assert spec == [2] + assert other == [0, 1] + wcsinfo = {'CTYPE': np.array(['RA---TAN', 'WAVE', 'DEC--TAN'])} + cel, spec, other = gwutils.get_axes(wcsinfo) + assert cel == [0, 2] + assert spec == [1] + assert not other \ No newline at end of file diff --git a/gwcs/utils.py b/gwcs/utils.py index 90c84b50..8b98b651 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -209,10 +209,13 @@ def _compute_lon_pole(skycoord, projection): def get_projcode(wcs_info): # CTYPE here is only the imaging CTYPE keywords - sky_axes, _ = get_axes(wcs_info) + sky_axes, _, _ = get_axes(wcs_info) + if not sky_axes: + return None projcode = wcs_info['CTYPE'][sky_axes[0]][5:8].upper() if projcode not in projections.projcodes: raise UnsupportedProjectionError('Projection code %s, not recognized' % projcode) + #projcode = None return projcode @@ -288,7 +291,6 @@ def read_wcs_from_header(header): wcs_info['CRVAL'] = crval wcs_info['CDELT'] = cdelt wcs_info['PC'] = pc - return wcs_info @@ -303,7 +305,7 @@ def get_axes(header): Returns ------- - sky_inmap, spectral_inmap : tuples + sky_inmap, spectral_inmap, unknown : lists indices in the input representing sky and spectral cordinates. """ @@ -314,14 +316,29 @@ def get_axes(header): else: raise TypeError("Expected a FITS Header or a dict.") - ctype = [ax[:4] for ax in wcs_info['CTYPE']] + ctype = [ax[:4].upper() for ax in wcs_info['CTYPE']] sky_inmap = [] spec_inmap = [] + unknown = [] + skysystems = np.array(list(sky_pairs.values())).flatten() for ax in ctype: - if ax.upper() in specsystems: - spec_inmap.append(ctype.index(ax)) + ind = ctype.index(ax) + if ax in specsystems: + spec_inmap.append(ind) + elif ax in skysystems: + sky_inmap.append(ind) else: - sky_inmap.append(ctype.index(ax)) + unknown.append(ind) + + if sky_inmap: + _is_skysys_consistent(ctype, sky_inmap) + + return sky_inmap, spec_inmap, unknown + + +def _is_skysys_consistent(ctype, sky_inmap): + """ Determine if the sky axes in CTYPE mathch to form a standard celestial system.""" + for item in sky_pairs.values(): if ctype[sky_inmap[0]] == item[0]: if ctype[sky_inmap[1]] != item[1]: @@ -334,7 +351,6 @@ def get_axes(header): "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype)) sky_inmap = sky_inmap[::-1] break - return sky_inmap, spec_inmap specsystems = ["WAVE", "FREQ", "ENER", "WAVEN", "AWAV", @@ -366,9 +382,13 @@ def make_fitswcs_transform(header): wcs_info = header else: raise TypeError("Expected a FITS Header or a dict.") + transforms = [] wcs_linear = fitswcs_linear(wcs_info) + transforms.append(wcs_linear) wcs_nonlinear = fitswcs_nonlinear(wcs_info) - return functools.reduce(core._model_oper('|'), [wcs_linear, wcs_nonlinear]) + if wcs_nonlinear is not None: + transforms.append(wcs_nonlinear) + return functools.reduce(core._model_oper('|'), transforms) def fitswcs_linear(header): @@ -390,10 +410,12 @@ def fitswcs_linear(header): pc = wcs_info['PC'] # get the part of the PC matrix corresponding to the imaging axes - sky_axes = None + sky_axes, spec_axes, unknown = get_axes(wcs_info) if pc.shape != (2, 2): - sky_axes, _ = get_axes(wcs_info) - i, j = sky_axes + if sky_axes: + i, j = sky_axes + elif unknown and len(unknown) == 2: + i, j = unknown sky_pc = np.zeros((2, 2)) sky_pc[0, 0] = pc[i, i] sky_pc[0, 1] = pc[i, j] @@ -401,12 +423,15 @@ def fitswcs_linear(header): sky_pc[1, 1] = pc[j, j] pc = sky_pc.copy() - if sky_axes is not None: + sky_axes.extend(unknown) + if sky_axes: crpix = [] cdelt = [] for i in sky_axes: crpix.append(wcs_info['CRPIX'][i]) cdelt.append(wcs_info['CDELT'][i]) + #crpix = wcs_info['CRPIX'][sky_axes] + #cdelt = wcs_info['CDELT'][sky_axes] else: cdelt = wcs_info['CDELT'] crpix = wcs_info['CRPIX'] @@ -452,17 +477,24 @@ def fitswcs_nonlinear(header): else: raise TypeError("Expected a FITS Header or a dict.") + transforms = [] projcode = get_projcode(wcs_info) - projection = create_projection_transform(projcode).rename(projcode) - + if projcode is not None: + projection = create_projection_transform(projcode).rename(projcode) + transforms.append(projection) # Create the sky rotation transform - sky_axes, _ = get_axes(wcs_info) - phip, lonp = [wcs_info['CRVAL'][i] for i in sky_axes] - # TODO: write "def compute_lonpole(projcode, l)" - # Set a defaul tvalue for now - thetap = 180 - n2c = astmodels.RotateNative2Celestial(phip, lonp, thetap, name="crval") - return projection | n2c + sky_axes, _, _ = get_axes(wcs_info) + if sky_axes: + phip, lonp = [wcs_info['CRVAL'][i] for i in sky_axes] + # TODO: write "def compute_lonpole(projcode, l)" + # Set a defaul tvalue for now + thetap = 180 + n2c = astmodels.RotateNative2Celestial(phip, lonp, thetap, name="crval") + transforms.append(n2c) + if transforms: + return functools.reduce(core._model_oper('|'), transforms) + else: + return None def create_projection_transform(projcode): From 570605697eef619cc5c62518714495edd975aa29 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 19 Jul 2017 14:24:50 -0400 Subject: [PATCH 33/40] fix a bug in get_axes with 'unknown' axes types --- CHANGES.rst | 2 ++ gwcs/tests/test_utils.py | 2 +- gwcs/utils.py | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b0fc90b6..f6abad91 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,8 @@ - RegionsSelector now handles the case when a label does not have a corresponding transform and returns RegionsSelector.undefined_transform_value. [#86] +- GWCS now deals with axes types which are neither celestial nor spectral as "unknown" + and creates a transform equivalent to the FITS linear transform. [#92] 0.7 (2016-12-23) ---------------- diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index c88598f9..eaf0c9c4 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -83,4 +83,4 @@ def test_get_axes(): cel, spec, other = gwutils.get_axes(wcsinfo) assert cel == [0, 2] assert spec == [1] - assert not other \ No newline at end of file + assert not other diff --git a/gwcs/utils.py b/gwcs/utils.py index 8b98b651..68d67b95 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -19,7 +19,7 @@ # these ctype values do not include yzLN and yzLT pairs -sky_pairs = {"equatorial": ["RA--", "DEC-"], +sky_pairs = {"equatorial": ["RA", "DEC"], "ecliptic": ["ELON", "ELAT"], "galactic": ["GLON", "GLAT"], "helioecliptic": ["HLON", "HLAT"], @@ -316,7 +316,9 @@ def get_axes(header): else: raise TypeError("Expected a FITS Header or a dict.") - ctype = [ax[:4].upper() for ax in wcs_info['CTYPE']] + # Split each CTYPE value at "-" and take the first part. + # This should represent the coordinate system. + ctype = [ax.split('-')[0].upper() for ax in wcs_info['CTYPE']] sky_inmap = [] spec_inmap = [] unknown = [] From 6974f8bd6a8d19d09c0f28b458d9b12b2796ed49 Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Sun, 30 Jul 2017 00:52:35 +0100 Subject: [PATCH 34/40] Updated astropy-helpers to v2.0.1 --- astropy_helpers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astropy_helpers b/astropy_helpers index ed2e7897..14ca346b 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit ed2e7897862ae9e979b336df670d7a5541cdd8f0 +Subproject commit 14ca346b0da3e92e65bdc398cc8f726cbd7be57d From 332981e668afb57bdb58ed1e2bfdd46d14c8fe36 Mon Sep 17 00:00:00 2001 From: Bernie Simon Date: Mon, 11 Sep 2017 13:29:44 -0400 Subject: [PATCH 35/40] Change version of six library Astropy is dropping support of Python 2. To insualate ourselves from this change we are changing the six library to not point to the astropy version. --- gwcs/coordinate_frames.py | 2 +- gwcs/region.py | 2 +- gwcs/wcs.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index f156f629..63b3036d 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -8,7 +8,7 @@ from astropy import units as u from astropy import utils as astutil from astropy import coordinates as coord -from astropy.extern import six +import six __all__ = ['Frame2D', 'CelestialFrame', 'SpectralFrame', 'CompositeFrame', diff --git a/gwcs/region.py b/gwcs/region.py index 1ccad4df..eb84f414 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -3,7 +3,7 @@ import abc from collections import OrderedDict import numpy as np -from astropy.extern import six +import six @six.add_metaclass(abc.ABCMeta) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index e0ff7e61..daae0166 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -5,7 +5,7 @@ import warnings import numpy as np -from astropy.extern import six +import six from astropy.modeling.core import Model from . import coordinate_frames From 774255de5252ce586e3f6e489051ba8beebfb7a7 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Wed, 13 Sep 2017 17:46:48 -0400 Subject: [PATCH 36/40] fix asdf package name, include n 1.13 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a9e17678..b7b15fb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ env: - ASTROPY_VERSION=development - MAIN_CMD='python setup.py' - CONDA_DEPENDENCIES='scipy' - - PIP_DEPENDENCIES='git+https://github.com/spacetelescope/pyasdf.git#egg=pyasdf' + - PIP_DEPENDENCIES='git+https://github.com/spacetelescope/asdf.git#egg=asdf' - ASTROPY_USE_SYSTEM_PYTEST=1 matrix: @@ -56,6 +56,8 @@ matrix: env: NUMPY_VERSION=1.10 SETUP_CMD="test" - python: 3.6 env: NUMPY_VERSION=1.12 SETUP_CMD="test" + - python: 3.6 + env: NUMPY_VERSION=1.13 SETUP_CMD="test" # Do a PEP8 test with pycodestyle - python: 3.5 From efb84100967a53f3636ac3e0a0d5ace595aec718 Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Fri, 22 Sep 2017 15:57:40 -0400 Subject: [PATCH 37/40] remove testing on python 2 --- .travis.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7b15fb6..ded47acf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ addons: - dvipng python: - - 2.7 - 3.5 - 3.6 @@ -40,20 +39,16 @@ matrix: include: - # Do a coverage test in Python 2. - - python: 2.7 + # Do a coverage test. + - python: 3.5 env: SETUP_CMD='test --coverage' # Check for sphinx doc build warnings - we do this first because it # may run for a long time - - python: 2.7 + - python: 3.6 env: SETUP_CMD='build_sphinx -w' # Numpy - - python: 2.7 - env: NUMPY_VERSION=1.12 SETUP_CMD="test" - - python: 2.7 - env: NUMPY_VERSION=1.10 SETUP_CMD="test" - python: 3.6 env: NUMPY_VERSION=1.12 SETUP_CMD="test" - python: 3.6 From c17d41a66b9f58bc358814d5362892854a70838c Mon Sep 17 00:00:00 2001 From: Brigitta Sipocz Date: Sat, 14 Oct 2017 00:09:18 +0100 Subject: [PATCH 38/40] Updated astropy-helpers to v2.0.2 --- astropy_helpers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astropy_helpers b/astropy_helpers index 14ca346b..d23a53f4 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit 14ca346b0da3e92e65bdc398cc8f726cbd7be57d +Subproject commit d23a53f46dd1c3703e5eee63dca3f53bd18a4e8b From edc3e8fbd75af82c6b0a3c0c9f93b3bd758b6f7e Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 18 Oct 2017 16:58:33 -0400 Subject: [PATCH 39/40] grid_from_bounding_box handles scalar step args --- gwcs/wcstools.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 947763d7..ac8275d6 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -190,17 +190,17 @@ def grid_from_bounding_box(bounding_box, step=1, center=True): ---------- bounding_box : tuple `ref: prop: bounding_box` - step : None, tuple - A step for the grid in each dimension. - If None, step=1. + step : scalar or tuple + Step size for grid in each dimension. Scalar applies to all dimensions. center : bool - The bounding_box is in order of X, Y [, Z] and the output will be in the same order. + The bounding_box is in order of X, Y [, Z] and the output will be in the + same order. Examples -------- - >>> bb = bb=((-1, 2.9), (6, 7.5)) - >>> grid_from_bounding_box(bb, step=(1, .5) + >>> bb = ((-1, 2.9), (6, 7.5)) + >>> grid_from_bounding_box(bb, step=(1, .5)) [[[-1. , 0. , 1. , 2. ], [-1. , 0. , 1. , 2. ], [-1. , 0. , 1. , 2. ], @@ -214,14 +214,23 @@ def grid_from_bounding_box(bounding_box, step=1, center=True): Returns ------- - x, y : ndarray - Input points. + x, y [, z]: ndarray + Grid of points. """ - slices = [] if center: bb = tuple([(np.floor(b[0] + 0.5), np.ceil(b[1] - .5)) for b in bounding_box]) else: bb = bounding_box + + step = np.atleast_1d(step) + if len(bb) > 1 and len(step) == 1: + step = np.repeat(step, len(bb)) + + if len(step) != len(bb): + raise ValueError('`step` must be a scalar, or tuple with length ' + 'matching `bounding_box`') + + slices = [] for d, s in zip(bb, step): slices.append(slice(d[0], d[1] + s, s)) return np.mgrid[slices[::-1]][::-1] From f31004387bd3b9ada8216e859d5fd91226bdf35d Mon Sep 17 00:00:00 2001 From: Nadia Dencheva Date: Thu, 2 Nov 2017 10:43:18 -0400 Subject: [PATCH 40/40] prepare for 0.8 release --- CHANGES.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f6abad91..e5ec884d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -0.8 (Unreleased) +0.8.0 (2017-11-02) ---------------- - ``LabelMapperRange`` now returns ``LabelMapperRange._no_label`` when the key is diff --git a/setup.py b/setup.py index f29bbb79..cc9eb8c8 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ builtins._ASTROPY_PACKAGE_NAME_ = PACKAGENAME # VERSION should be PEP386 compatible (http://www.python.org/dev/peps/pep-0386) -VERSION = '0.8dev' +VERSION = '0.8.0' # Indicates if this version is a release version RELEASE = 'dev' not in VERSION