def test_high_level_wrapper(wcsobj, request): if request.node.callspec.params['wcsobj'] in ('gwcs_4d_identity_units', 'gwcs_stokes_lookup'): pytest.importorskip("astropy", minversion="4.0dev0") # Remove the bounding box because the type test is a little broken with the # bounding box. del wcsobj._pipeline[0][1].bounding_box hlvl = HighLevelWCSWrapper(wcsobj) pixel_input = [3] * wcsobj.pixel_n_dim # If the model expects units we have to pass in units if wcsobj.forward_transform.uses_quantity: pixel_input *= u.pix wc1 = hlvl.pixel_to_world(*pixel_input) wc2 = wcsobj(*pixel_input, with_units=True) assert type(wc1) is type(wc2) if isinstance(wc1, (list, tuple)): for w1, w2 in zip(wc1, wc2): _compare_frame_output(w1, w2) else: _compare_frame_output(wc1, wc2)
def test_spectral_cube(spectral_cube_3d_fitswcs): wcs = ReorderedLowLevelWCS(spectral_cube_3d_fitswcs, pixel_order=[1, 2, 0], world_order=[2, 0, 1]) assert wcs.pixel_n_dim == 3 assert wcs.world_n_dim == 3 assert tuple(wcs.world_axis_physical_types) == ('em.freq', 'pos.eq.ra', 'pos.eq.dec') assert tuple(wcs.world_axis_units) == ('Hz', 'deg', 'deg') assert tuple(wcs.pixel_axis_names) == ('', '', '') assert tuple(wcs.world_axis_names) == ('Frequency', 'Right Ascension', 'Declination') assert_equal(wcs.axis_correlation_matrix, np.array([[0, 1, 0], [1, 0, 1], [1, 0, 1]])) assert wcs.pixel_shape == (7, 3, 6) assert wcs.array_shape == (6, 3, 7) assert wcs.pixel_bounds == ((1, 7), (1, 2.5), (-1, 5)) pixel_scalar = (1.3, 2.3, 4.3) world_scalar = (-1.91e10, 5.4, -9.4) assert_allclose(wcs.pixel_to_world_values(*pixel_scalar), world_scalar) assert_allclose(wcs.array_index_to_world_values(*pixel_scalar[::-1]), world_scalar) assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar) assert_allclose(wcs.world_to_array_index_values(*world_scalar), [4, 2, 1]) pixel_array = (np.array([1.3, 1.4]), np.array([2.3, 2.4]), np.array([4.3, 4.4])) world_array = (np.array([-1.91e10, -1.88e10]), np.array([5.4, 5.2]), np.array([-9.4, -9.2])) assert_allclose(wcs.pixel_to_world_values(*pixel_array), world_array) assert_allclose(wcs.array_index_to_world_values(*pixel_array[::-1]), world_array) assert_allclose(wcs.world_to_pixel_values(*world_array), pixel_array) assert_allclose(wcs.world_to_array_index_values(*world_array), [[4, 4], [2, 2], [1, 1]]) wcs_hl = HighLevelWCSWrapper(wcs) spectral, celestial = wcs_hl.pixel_to_world(*pixel_scalar) assert isinstance(spectral, Quantity) assert_quantity_allclose(spectral, world_scalar[0] * u.Hz) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_scalar[1] * u.deg) assert_quantity_allclose(celestial.dec, world_scalar[2] * u.deg) spectral, celestial = wcs_hl.pixel_to_world(*pixel_array) assert isinstance(spectral, Quantity) assert_quantity_allclose(spectral, world_array[0] * u.Hz) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_array[1] * u.deg) assert_quantity_allclose(celestial.dec, world_array[2] * u.deg) assert str(wcs) == EXPECTED_SPECTRAL_CUBE_REPR assert EXPECTED_SPECTRAL_CUBE_REPR in repr(wcs)
def test_2d(celestial_wcs): # Upsample along the first pixel dimension and downsample along the second # pixel dimension. wcs = ResampledLowLevelWCS(celestial_wcs, [0.4, 3]) # The following shouldn't change compared to the original WCS assert wcs.pixel_n_dim == 2 assert wcs.world_n_dim == 2 assert tuple(wcs.world_axis_physical_types) == ('pos.eq.ra', 'pos.eq.dec') assert tuple(wcs.world_axis_units) == ('deg', 'deg') assert tuple(wcs.pixel_axis_names) == ('', '') assert tuple(wcs.world_axis_names) == ('Right Ascension', 'Declination') assert_equal(wcs.axis_correlation_matrix, np.ones((2, 2))) # Shapes and bounds should be floating-point if needed assert_allclose(wcs.pixel_shape, (15, 7 / 3)) assert_allclose(wcs.array_shape, (7 / 3, 15)) assert_allclose(wcs.pixel_bounds, ((-2.5, 12.5), (1 / 3, 7 / 3))) pixel_scalar = (2.3, 4.3) world_scalar = (12.16, 13.8) assert_allclose(wcs.pixel_to_world_values(*pixel_scalar), world_scalar) assert_allclose(wcs.array_index_to_world_values(*pixel_scalar[::-1]), world_scalar) assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar) assert_allclose(wcs.world_to_array_index_values(*world_scalar), [4, 2]) pixel_array = (np.array([2.3, 2.4]), np.array([4.3, 4.4])) world_array = (np.array([12.16, 12.08]), np.array([13.8, 14.4])) assert_allclose(wcs.pixel_to_world_values(*pixel_array), world_array) assert_allclose(wcs.array_index_to_world_values(*pixel_array[::-1]), world_array) assert_allclose(wcs.world_to_pixel_values(*world_array), pixel_array) assert_allclose(wcs.world_to_array_index_values(*world_array), [[4, 4], [2, 2]]) wcs_hl = HighLevelWCSWrapper(wcs) celestial = wcs_hl.pixel_to_world(*pixel_scalar) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_scalar[0] * u.deg) assert_quantity_allclose(celestial.dec, world_scalar[1] * u.deg) celestial = wcs_hl.pixel_to_world(*pixel_array) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_array[0] * u.deg) assert_quantity_allclose(celestial.dec, world_array[1] * u.deg) assert str(wcs) == EXPECTED_2D_REPR assert EXPECTED_2D_REPR in repr(wcs)
def test_wcs_type_transform_regression(): wcs = WCS(TARGET_HEADER) sliced_wcs = SlicedLowLevelWCS(wcs, np.s_[1:-1, 1:-1]) ax = plt.subplot(1, 1, 1, projection=wcs) ax.get_transform(sliced_wcs) high_wcs = HighLevelWCSWrapper(sliced_wcs) ax.get_transform(sliced_wcs)
def _slice_wcs(self, item): if self.wcs is None: return None try: llwcs = SlicedLowLevelWCS(self.wcs.low_level_wcs, item) return HighLevelWCSWrapper(llwcs) except Exception as err: self._handle_wcs_slicing_error(err, item)
def combined_wcs(self): """ A `~astropy.wcs.wcsapi.BaseHighLevelWCS` object which combines ``.wcs`` with ``.extra_coords``. """ if not self.extra_coords.wcs: return self.wcs mapping = list(range(self.wcs.pixel_n_dim)) + list(self.extra_coords.mapping) return HighLevelWCSWrapper( CompoundLowLevelWCS(self.wcs.low_level_wcs, self._extra_coords.wcs, mapping=mapping) )
def wcs(self, wcs): if self._wcs is not None and wcs is not None: raise ValueError( "You can only set the wcs attribute with a WCS if no WCS is present." ) if wcs is None or isinstance(wcs, BaseHighLevelWCS): self._wcs = wcs elif isinstance(wcs, BaseLowLevelWCS): self._wcs = HighLevelWCSWrapper(wcs) else: raise TypeError( "The wcs argument must implement either the high or" " low level WCS API.")
def test_nddata_init_data_nddata_subclass(): uncert = StdDevUncertainty(3) # There might be some incompatible subclasses of NDData around. bnd = BadNDDataSubclass(False, True, 3, 2, 'gollum', 100) # Before changing the NDData init this would not have raised an error but # would have lead to a compromised nddata instance with pytest.raises(TypeError): NDData(bnd) # but if it has no actual incompatible attributes it passes bnd_good = BadNDDataSubclass(np.array([1, 2]), uncert, 3, HighLevelWCSWrapper(WCS(naxis=1)), {'enemy': 'black knight'}, u.km) nd = NDData(bnd_good) assert nd.unit == bnd_good.unit assert nd.meta == bnd_good.meta assert nd.uncertainty == bnd_good.uncertainty assert nd.mask == bnd_good.mask assert nd.wcs is bnd_good.wcs assert nd.data is bnd_good.data
def _stokes_slice(self, stokes_ix, normalize=False): """Return a 3D NDCube (wavelength, coord1, coord2) for a given Stokes parameter""" # Construct the WCS object for the smaller 3D cube. # This function should only called if the d_sh = self.data.shape wcs_slice = [0] * self.wcs.pixel_n_dim wcs_slice[0] = stokes_ix wcs_slice[1] = slice(0, d_sh[1]) wcs_slice[2] = slice(0, d_sh[2]) wcs_slice[3] = slice(0, d_sh[3]) #print(wcs_slice) wcs_slice = SlicedLowLevelWCS(self.wcs.low_level_wcs, wcs_slice) #newcube = StokesParamCube(self.data[stokes_ix,:,:,:], HighLevelWCSWrapper(wcs_slice), self._stokes_axis[stokes_ix]) #cube_meta = {'stokes': self._stokes_axis[stokes_ix]} cube_meta = self.meta.copy() cube_meta['stokes'] = self._stokes_axis[stokes_ix] newcube = StokesParamCube(self.data[stokes_ix, :, :, :], HighLevelWCSWrapper(wcs_slice), meta=cube_meta) # Normalize the spectra if wanted. if stokes_ix != 0: if self.normalize is True: # Normalize by I I = self._stokes_slice(0) newcube = StokesParamCube(newcube.data / I.data, newcube.wcs, self._stokes_axis[stokes_ix], meta=newcube.meta) elif self.normalize: # normalize by non-zero float # TODO: sanity check input newcube = StokesParamCube(newcube.data / self.normalize, newcube.wcs, self._stokes_axis[stokes_ix], meta=newcube.meta) #newcube.meta = {'stokes': self._stokes_axis[stokes_ix]} return newcube
def test_stokes_wrapper(gwcs_stokes_lookup): pytest.importorskip("astropy", minversion="4.0dev0") hlvl = HighLevelWCSWrapper(gwcs_stokes_lookup) pixel_input = [0, 1, 2, 3] out = hlvl.pixel_to_world(pixel_input * u.pix) assert list(out) == ['I', 'Q', 'U', 'V'] pixel_input = [ [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], ] out = hlvl.pixel_to_world(pixel_input * u.pix) expected = np.array([['I', 'Q', 'U', 'V'], ['I', 'Q', 'U', 'V'], ['I', 'Q', 'U', 'V'], ['I', 'Q', 'U', 'V']], dtype=object) assert (out == expected).all() pixel_input = [-1, 4] out = hlvl.pixel_to_world(pixel_input * u.pix) assert np.isnan(out).all() pixel_input = [[-1, 4], [1, 2]] out = hlvl.pixel_to_world(pixel_input * u.pix) assert np.isnan(np.array(out[0], dtype=float)).all() assert (out[1] == np.array(['Q', 'U'], dtype=object)).all() out = hlvl.pixel_to_world(1 * u.pix) assert out == 'Q'
def get_crop_item_from_points(points, wcs, crop_by_values): """ Find slice item that crops to minimum cube in array-space containing specified world points. Parameters ---------- points : iterable of iterables Each iterable represents a point in real world space. Each element in a point gives the real world coordinate value of the point in high-level coordinate objects or quantities. (Must be consistenly high or low level within and across points.) Objects must be in the order required by wcs.world_to_array_index/world_to_array_index_values. wcs : `~astropy.wcs.wcsapi.BaseHighLevelWCS`, `~astropy.wcs.wcsapi.BaseLowLevelWCS` The WCS to use to convert the world coordinates to array indices. crop_by_values : `bool` Denotes whether cropping is done using high-level objects or "values", i.e. low-level objects. Returns ------- item : `tuple` of `slice` The slice item for each axis of the cube which, when applied to the cube, will return the minimum cube in array-index-space that contains all the input world points. """ # Define a list of lists to hold the array indices of the points # where each inner list gives the index of all points for that array axis. combined_points_array_idx = [[]] * wcs.pixel_n_dim # For each point compute the corresponding array indices. for point in points: # Get the arrays axes associated with each element in point. if crop_by_values: point_inputs_array_axes = [] for i in range(wcs.world_n_dim): pix_axes = np.array( wcs_utils.world_axis_to_pixel_axes( i, wcs.axis_correlation_matrix)) point_inputs_array_axes.append( tuple( wcs_utils.convert_between_array_and_pixel_axes( pix_axes, wcs.pixel_n_dim))) point_inputs_array_axes = tuple(point_inputs_array_axes) else: point_inputs_array_axes = wcs_utils.array_indices_for_world_objects( HighLevelWCSWrapper(wcs)) # Get indices of array axes which correspond to only None inputs in point # as well as those that correspond to a coord. point_indices_with_inputs = [] array_axes_with_input = [] for i, coord in enumerate(point): if coord is not None: point_indices_with_inputs.append(i) array_axes_with_input.append(point_inputs_array_axes[i]) array_axes_with_input = set(chain.from_iterable(array_axes_with_input)) array_axes_without_input = set(range( wcs.pixel_n_dim)) - array_axes_with_input # Slice out the axes that do not correspond to a coord # from the WCS and the input point. wcs_slice = np.array([slice(None)] * wcs.pixel_n_dim) if len(array_axes_without_input): wcs_slice[np.array(list(array_axes_without_input))] = 0 sliced_wcs = SlicedLowLevelWCS(wcs, slices=tuple(wcs_slice)) sliced_point = np.array( point, dtype=object)[np.array(point_indices_with_inputs)] # Derive the array indices of the input point and place each index # in the list corresponding to its axis. if crop_by_values: point_array_indices = sliced_wcs.world_to_array_index_values( *sliced_point) # If returned value is a 0-d array, convert to a length-1 tuple. if isinstance(point_array_indices, np.ndarray) and point_array_indices.ndim == 0: point_array_indices = (point_array_indices.item(), ) else: # Convert from scalar arrays to scalars point_array_indices = tuple(a.item() for a in point_array_indices) else: point_array_indices = HighLevelWCSWrapper( sliced_wcs).world_to_array_index(*sliced_point) # If returned value is a 0-d array, convert to a length-1 tuple. if isinstance(point_array_indices, np.ndarray) and point_array_indices.ndim == 0: point_array_indices = (point_array_indices.item(), ) for axis, index in zip(array_axes_with_input, point_array_indices): combined_points_array_idx[ axis] = combined_points_array_idx[axis] + [index] # Define slice item with which to slice cube. item = [] result_is_scalar = True for axis_indices in combined_points_array_idx: if axis_indices == []: result_is_scalar = False item.append(slice(None)) else: min_idx = min(axis_indices) max_idx = max(axis_indices) + 1 if max_idx - min_idx == 1: item.append(min_idx) else: item.append(slice(min_idx, max_idx)) result_is_scalar = False # If item will result in a scalar cube, raise an error as this is not currently supported. if result_is_scalar: raise ValueError( "Input points causes cube to be cropped to a single pixel. " "This is not supported.") return tuple(item)
def as_high_level_wcs(wcs): return HighLevelWCSWrapper(SlicedLowLevelWCS(wcs, Ellipsis))
def __init__(self, data1=None, data2=None, cids1=None, cids2=None): wcs1, wcs2 = data1.coords, data2.coords forwards = backwards = None if wcs1.pixel_n_dim == wcs2.pixel_n_dim and wcs1.world_n_dim == wcs2.world_n_dim: if (wcs1.world_axis_physical_types.count(None) == 0 and wcs2.world_axis_physical_types.count(None) == 0): # The easiest way to check if the WCSes are compatible is to simply try and # see if values can be transformed for a single pixel. In future we might # find that this requires optimization performance-wise, but for now let's # not do premature optimization. pixel_cids1, pixel_cids2, forwards, backwards = get_cids_and_functions( wcs1, wcs2, data1.pixel_component_ids[::-1], data2.pixel_component_ids[::-1]) self._physical_types_1 = wcs1.world_axis_physical_types self._physical_types_2 = wcs2.world_axis_physical_types if not forwards or not backwards: # A generalized APE 14-compatible way # Handle also the extra-spatial axes such as those of the time and wavelength dimensions wcs1_celestial_physical_types = wcs2_celestial_physical_types = [] slicing_axes1 = slicing_axes2 = [] cids1 = data1.pixel_component_ids cids2 = data2.pixel_component_ids if wcs1.has_celestial and wcs2.has_celestial: wcs1_celestial_physical_types = wcs1.celestial.world_axis_physical_types wcs2_celestial_physical_types = wcs2.celestial.world_axis_physical_types cids1_celestial = [ cids1[wcs1.wcs.naxis - wcs1.wcs.lng - 1], cids1[wcs1.wcs.naxis - wcs1.wcs.lat - 1] ] cids2_celestial = [ cids2[wcs2.wcs.naxis - wcs2.wcs.lng - 1], cids2[wcs2.wcs.naxis - wcs2.wcs.lat - 1] ] if wcs1.celestial.wcs.lng > wcs1.celestial.wcs.lat: cids1_celestial = cids1_celestial[::-1] if wcs2.celestial.wcs.lng > wcs2.celestial.wcs.lat: cids2_celestial = cids2_celestial[::-1] slicing_axes1 = [ cids1_celestial[0].axis, cids1_celestial[1].axis ] slicing_axes2 = [ cids2_celestial[0].axis, cids2_celestial[1].axis ] wcs1_sliced_physical_types = wcs2_sliced_physical_types = [] if wcs1_celestial_physical_types is not None: wcs1_sliced_physical_types = wcs1_celestial_physical_types if wcs2_celestial_physical_types is not None: wcs2_sliced_physical_types = wcs2_celestial_physical_types for i, physical_type1 in enumerate(wcs1.world_axis_physical_types): for j, physical_type2 in enumerate( wcs2.world_axis_physical_types): if physical_type1 == physical_type2: if physical_type1 not in wcs1_sliced_physical_types: slicing_axes1.append(wcs1.world_n_dim - i - 1) wcs1_sliced_physical_types.append(physical_type1) if physical_type2 not in wcs2_sliced_physical_types: slicing_axes2.append(wcs2.world_n_dim - j - 1) wcs2_sliced_physical_types.append(physical_type2) slicing_axes1 = sorted(slicing_axes1, key=str, reverse=True) slicing_axes2 = sorted(slicing_axes2, key=str, reverse=True) # Generate slices for the wcs slicing slices1 = [slice(None)] * wcs1.world_n_dim slices2 = [slice(None)] * wcs2.world_n_dim for i in range(wcs1.world_n_dim): if i not in slicing_axes1: slices1[i] = 0 for j in range(wcs2.world_n_dim): if j not in slicing_axes2: slices2[j] = 0 wcs1_sliced = SlicedLowLevelWCS(wcs1, tuple(slices1)) wcs2_sliced = SlicedLowLevelWCS(wcs2, tuple(slices2)) wcs1_final = HighLevelWCSWrapper(copy.copy(wcs1_sliced)) wcs2_final = HighLevelWCSWrapper(copy.copy(wcs2_sliced)) cids1_sliced = [cids1[x] for x in slicing_axes1] cids1_sliced = sorted(cids1_sliced, key=str, reverse=True) cids2_sliced = [cids2[x] for x in slicing_axes2] cids2_sliced = sorted(cids2_sliced, key=str, reverse=True) pixel_cids1, pixel_cids2, forwards, backwards = get_cids_and_functions( wcs1_final, wcs2_final, cids1_sliced, cids2_sliced) self._physical_types_1 = wcs1_sliced_physical_types self._physical_types_2 = wcs2_sliced_physical_types if pixel_cids1 is None: raise IncompatibleWCS( "Can't create WCS link between {0} and {1}".format( data1.label, data2.label)) super(WCSLink, self).__init__(pixel_cids1, pixel_cids2, forwards=forwards, backwards=backwards) self.data1 = data1 self.data2 = data2
def test_celestial_spectral_ape14(spectral_wcs, celestial_wcs): wcs = CompoundLowLevelWCS(spectral_wcs, celestial_wcs) assert wcs.pixel_n_dim == 3 assert wcs.world_n_dim == 3 assert tuple(wcs.world_axis_physical_types) == ('em.freq', 'pos.eq.ra', 'pos.eq.dec') assert tuple(wcs.world_axis_units) == ('Hz', 'deg', 'deg') assert tuple(wcs.pixel_axis_names) == ('', '', '') assert tuple(wcs.world_axis_names) == ('Frequency', 'Right Ascension', 'Declination') assert_equal(wcs.axis_correlation_matrix, np.array([[1, 0, 0], [0, 1, 1], [0, 1, 1]])) # If any of the individual shapes are None, return None overall assert wcs.pixel_shape is None assert wcs.array_shape is None assert wcs.pixel_bounds is None # Set the shape and bounds on the spectrum and test again spectral_wcs.pixel_shape = (3, ) spectral_wcs.pixel_bounds = [(1, 2)] assert wcs.pixel_shape == (3, 6, 7) assert wcs.array_shape == (7, 6, 3) assert wcs.pixel_bounds == ((1, 2), (-1, 5), (1, 7)) pixel_scalar = (2.3, 4.3, 1.3) world_scalar = (-1.91e10, 5.4, -9.4) assert_allclose(wcs.pixel_to_world_values(*pixel_scalar), world_scalar) assert_allclose(wcs.array_index_to_world_values(*pixel_scalar[::-1]), world_scalar) assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar) assert_allclose(wcs.world_to_array_index_values(*world_scalar), [1, 4, 2]) pixel_array = (np.array([2.3, 2.4]), np.array([4.3, 4.4]), np.array([1.3, 1.4])) world_array = (np.array([-1.91e10, -1.88e10]), np.array([5.4, 5.2]), np.array([-9.4, -9.2])) assert_allclose(wcs.pixel_to_world_values(*pixel_array), world_array) assert_allclose(wcs.array_index_to_world_values(*pixel_array[::-1]), world_array) assert_allclose(wcs.world_to_pixel_values(*world_array), pixel_array) assert_allclose(wcs.world_to_array_index_values(*world_array), [[1, 1], [4, 4], [2, 2]]) wcs_hl = HighLevelWCSWrapper(wcs) spectral, celestial = wcs_hl.pixel_to_world(*pixel_scalar) assert isinstance(spectral, Quantity) assert_quantity_allclose(spectral, world_scalar[0] * u.Hz) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_scalar[1] * u.deg) assert_quantity_allclose(celestial.dec, world_scalar[2] * u.deg) spectral, celestial = wcs_hl.pixel_to_world(*pixel_array) assert isinstance(spectral, Quantity) assert_quantity_allclose(spectral, world_array[0] * u.Hz) assert isinstance(celestial, SkyCoord) assert_quantity_allclose(celestial.ra, world_array[1] * u.deg) assert_quantity_allclose(celestial.dec, world_array[2] * u.deg) assert str(wcs) == EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR assert EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR in repr(wcs)