Source code for ndcube.wcs.wrappers.resampled_wcs

import numpy as np
from astropy.wcs.wcsapi.wrappers.base import BaseWCSWrapper

__all__ = ['ResampledLowLevelWCS']


[docs] class ResampledLowLevelWCS(BaseWCSWrapper): """ A wrapper for a low-level WCS object that has down- or up-sampled pixel axes. Parameters ---------- wcs : `~astropy.wcs.wcsapi.BaseLowLevelWCS` The original WCS for which to reorder axes factor : `int` or `float` or iterable of the same The factor by which to increase the pixel size for each pixel axis. If a scalar, the same factor is used for all axes. offset: `int` or `float` or iterable of the same The location on the underlying pixel grid which corresponds to zero on the top level pixel grid. If a scalar, the grid will be shifted by the same amount in all dimensions. """ def __init__(self, wcs, factor, offset=0): self._wcs = wcs if np.isscalar(factor): factor = [factor] * self.pixel_n_dim self._factor = np.array(factor) if len(self._factor) != self.pixel_n_dim: raise ValueError(f"Length of factor must equal number of dimensions {self.pixel_n_dim}.") if np.isscalar(offset): offset = [offset] * self.pixel_n_dim self._offset = np.array(offset) if len(self._offset) != self.pixel_n_dim: raise ValueError(f"Length of offset must equal number of dimensions {self.pixel_n_dim}.") def _top_to_underlying_pixels(self, top_pixels): # Convert user-facing pixel indices to the pixel grid of underlying WCS. factor = self._pad_dims(self._factor, top_pixels.ndim) offset = self._pad_dims(self._offset, top_pixels.ndim) return top_pixels * factor + offset def _underlying_to_top_pixels(self, underlying_pixels): # Convert pixel indices of underlying pixel grid to user-facing grid. factor = self._pad_dims(self._factor, underlying_pixels.ndim) offset = self._pad_dims(self._offset, underlying_pixels.ndim) return (underlying_pixels - offset) / factor def _pad_dims(self, arr, ndim): # Pad array with trailing degenerate dimensions. # This make scaling with pixel arrays easier. shape = np.ones(ndim, dtype=int) shape[0] = len(arr) return arr.reshape(tuple(shape))
[docs] def pixel_to_world_values(self, *pixel_arrays): underlying_pixel_arrays = self._top_to_underlying_pixels(np.asarray(pixel_arrays)) return self._wcs.pixel_to_world_values(*underlying_pixel_arrays)
[docs] def world_to_pixel_values(self, *world_arrays): underlying_pixel_arrays = self._wcs.world_to_pixel_values(*world_arrays) top_pixel_arrays = self._underlying_to_top_pixels(np.asarray(underlying_pixel_arrays)) return tuple(array for array in top_pixel_arrays)
@property def pixel_shape(self): # Return pixel shape of resampled grid. # Where shape is an integer, return an int type as its required for some uses. if self._wcs.pixel_shape is None: return self._wcs.pixel_shape underlying_shape = np.asarray(self._wcs.pixel_shape) int_elements = np.isclose(np.mod(underlying_shape, self._factor), 0, atol=np.finfo(float).resolution) pixel_shape = underlying_shape / self._factor return tuple(int(np.rint(i)) if is_int else i for i, is_int in zip(pixel_shape, int_elements)) @property def pixel_bounds(self): if self._wcs.pixel_bounds is None: return self._wcs.pixel_bounds top_level_bounds = self._underlying_to_top_pixels(np.asarray(self._wcs.pixel_bounds)) return [tuple(bounds) for bounds in top_level_bounds]