def insert_skycomponent(im: Image, sc: Skycomponent, insert_method=''): """ Insert a Skycomponent into an image :param params: :param im: :param sc: SkyComponent or list of SkyComponents :returns: image """ assert type(im) == Image nchan, npol, ny, nx = im.data.shape if not isinstance(sc, collections.Iterable): sc = [sc] for comp in sc: assert comp.shape == 'Point', "Cannot handle shape %s" % comp.shape assert_same_chan_pol(im, comp) if insert_method == "Lanczos": pixloc = skycoord_to_pixel(comp.direction, im.wcs, 0, 'wcs') _L2D(im.data, pixloc[1], pixloc[0], comp.flux) else: pixloc = numpy.round( skycoord_to_pixel(comp.direction, im.wcs, 1, 'wcs')).astype('int') x, y = pixloc[0], pixloc[1] if x >= 0 and x < nx and y >= 0 and y < ny: im.data[:, :, y, x] += comp.flux return im
def _pixel_scale_angle_at_skycoord(skycoord, wcs, offset=1.0 * u.arcsec): """ Calculate the pixel scale and WCS rotation angle at the position of a SkyCoord coordinate. Parameters ---------- skycoord : `~astropy.coordinates.SkyCoord` The SkyCoord coordinate. wcs : WCS object A world coordinate system (WCS) transformation that supports the `astropy shared interface for WCS <https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_ (e.g. `astropy.wcs.WCS`, `gwcs.wcs.WCS`). offset : `~astropy.units.Quantity` A small angular offset to use to compute the pixel scale and position angle. Returns ------- scale : `~astropy.units.Quantity` The pixel scale in arcsec/pixel. angle : `~astropy.units.Quantity` The angle (in degrees) measured counterclockwise from the positive x axis to the "North" axis of the celestial coordinate system. Notes ----- If distortions are present in the image, the x and y pixel scales likely differ. This function computes a single pixel scale along the North/South axis. """ try: x, y = wcs.world_to_pixel(skycoord) # We take a point directly North (i.e. latitude offset) the input # sky coordinate and convert it to pixel coordinates, then we use the # pixel deltas between the input and offset sky coordinate to # calculate the pixel scale and angle. skycoord_offset = skycoord.directional_offset_by(0.0, offset) x_offset, y_offset = wcs.world_to_pixel(skycoord_offset) except AttributeError: # for Astropy < 3.1 WCS support x, y = skycoord_to_pixel(skycoord, wcs) coord = skycoord.represent_as('unitspherical') coord_new = UnitSphericalRepresentation(coord.lon, coord.lat + offset) coord_offset = skycoord.realize_frame(coord_new) x_offset, y_offset = skycoord_to_pixel(coord_offset, wcs) dx = x_offset - x dy = y_offset - y scale = offset.to(u.arcsec) / (np.hypot(dx, dy) * u.pixel) angle = (np.arctan2(dy, dx) * u.radian).to(u.deg) return scale, angle
def test_skycoord_to_pixel_swapped(): # Regression test for a bug that caused skycoord_to_pixel and # pixel_to_skycoord to not work correctly if the axes were swapped in the # WCS. # Import astropy.coordinates here to avoid circular imports from astropy.coordinates import SkyCoord header = get_pkg_data_contents('maps/1904-66_TAN.hdr', encoding='binary') wcs = WCS(header) wcs_swapped = wcs.sub([WCSSUB_LATITUDE, WCSSUB_LONGITUDE]) ref = SkyCoord(0.1 * u.deg, -89. * u.deg, frame='icrs') xp1, yp1 = skycoord_to_pixel(ref, wcs) xp2, yp2 = skycoord_to_pixel(ref, wcs_swapped) assert_allclose(xp1, xp2) assert_allclose(yp1, yp2) # WCS is in FK5 so we need to transform back to ICRS new1 = pixel_to_skycoord(xp1, yp1, wcs).transform_to('icrs') new2 = pixel_to_skycoord(xp1, yp1, wcs_swapped).transform_to('icrs') assert_allclose(new1.ra.degree, new2.ra.degree) assert_allclose(new1.dec.degree, new2.dec.degree)
def show_components(im, comps, npixels=128, fig=None, vmax=None, vmin=None): """ Show components against an image :param im: :param comps: :param npixels: :param fig: :return: """ import matplotlib.pyplot as plt if vmax is None: vmax = numpy.max(im.data[0, 0, ...]) if vmin is None: vmin = numpy.min(im.data[0, 0, ...]) if not fig: fig = plt.figure() plt.clf() for isc, sc in enumerate(comps): newim = copy_image(im) plt.subplot(111, projection=newim.wcs.sub([1, 2])) centre = numpy.round(skycoord_to_pixel(sc.direction, newim.wcs, 1, 'wcs')).astype('int') newim.data = newim.data[:, :, (centre[1] - npixels // 2):(centre[1] + npixels // 2), (centre[0] - npixels // 2):(centre[0] + npixels // 2)] newim.wcs.wcs.crpix[0] -= centre[0] - npixels // 2 newim.wcs.wcs.crpix[1] -= centre[1] - npixels // 2 plt.imshow(newim.data[0, 0, ...], origin='lower', cmap='Greys', vmax=vmax, vmin=vmin) x, y = skycoord_to_pixel(sc.direction, newim.wcs, 0, 'wcs') plt.plot(x, y, marker='+', color='red') plt.title('Name = %s, flux = %s' % (sc.name, sc.flux)) plt.show()
def test_skycoord_to_pixel_swapped(): # Regression test for a bug that caused skycoord_to_pixel and # pixel_to_skycoord to not work correctly if the axes were swapped in the # WCS. # Import astropy.coordinates here to avoid circular imports from astropy.coordinates import SkyCoord header = get_pkg_data_contents('data/maps/1904-66_TAN.hdr', encoding='binary') wcs = WCS(header) wcs_swapped = wcs.sub([WCSSUB_LATITUDE, WCSSUB_LONGITUDE]) ref = SkyCoord(0.1 * u.deg, -89. * u.deg, frame='icrs') xp1, yp1 = skycoord_to_pixel(ref, wcs) xp2, yp2 = skycoord_to_pixel(ref, wcs_swapped) assert_allclose(xp1, xp2) assert_allclose(yp1, yp2) # WCS is in FK5 so we need to transform back to ICRS new1 = pixel_to_skycoord(xp1, yp1, wcs).transform_to('icrs') new2 = pixel_to_skycoord(xp1, yp1, wcs_swapped).transform_to('icrs') assert_allclose(new1.ra.degree, new2.ra.degree) assert_allclose(new1.dec.degree, new2.dec.degree)
def crop_to_training_area(image_path, out_path, freq, pad_factor=1.0): """ For a given SDC1 image, write a new FITS file containing only the training area. Training area defined by RA/Dec, which doesn't map perfectly to pixel values. Args: image_path (`str`): Path to input image out_path (`str`): Path to write sub-image to freq (`int`): [560, 1400, 9200] SDC1 image frequency (different training areas) pad_factor (`float`, optional): Area scaling factor to include edges """ hdu = fits.open(image_path)[0] wcs = WCS(hdu.header) # Lookup training limits for given frequency ra_max = TRAIN_LIM[freq]["ra_max"] ra_min = TRAIN_LIM[freq]["ra_min"] dec_max = TRAIN_LIM[freq]["dec_max"] dec_min = TRAIN_LIM[freq]["dec_min"] # Centre of training area pixel coordinate: train_centre = SkyCoord( ra=(ra_max + ra_min) / 2, dec=(dec_max + dec_min) / 2, frame="fk5", unit="deg", ) # Opposing corners of training area: train_min = SkyCoord( ra=ra_min, dec=dec_min, frame="fk5", unit="deg", ) train_max = SkyCoord( ra=ra_max, dec=dec_max, frame="fk5", unit="deg", ) # Training area approx width pixel_width = (abs( skycoord_to_pixel(train_max, wcs)[0] - skycoord_to_pixel(train_min, wcs)[0]) * pad_factor) # Training area approx height pixel_height = (abs( skycoord_to_pixel(train_max, wcs)[1] - skycoord_to_pixel(train_min, wcs)[1]) * pad_factor) save_subimage( image_path, out_path, skycoord_to_pixel(train_centre, wcs), (pixel_height, pixel_width), )
def skycoord_to_pixel_scale_angle(skycoord, wcs, small_offset=1 * u.arcsec): """ Convert a set of SkyCoord coordinates into pixel coordinates, pixel scales, and position angles. Parameters ---------- skycoord : `~astropy.coordinates.SkyCoord` Sky coordinates wcs : `~astropy.wcs.WCS` The WCS transformation to use small_offset : `~astropy.units.Quantity` A small offset to use to compute the angle Returns ------- pixcoord : `~regions.PixCoord` Pixel coordinates scale : float The pixel scale at each location, in degrees/pixel angle : `~astropy.units.Quantity` The position angle of the celestial coordinate system in pixel space. """ # Convert to pixel coordinates x, y = skycoord_to_pixel(skycoord, wcs, mode=skycoord_to_pixel_mode) pixcoord = PixCoord(x=x, y=y) # We take a point directly 'above' (in latitude) the position requested # and convert it to pixel coordinates, then we use that to figure out the # scale and position angle of the coordinate system at the location of # the points. # Find the coordinates as a representation object r_old = skycoord.represent_as('unitspherical') # Add a a small perturbation in the latitude direction (since longitude # is more difficult because it is not directly an angle). dlat = small_offset r_new = UnitSphericalRepresentation(r_old.lon, r_old.lat + dlat) coords_offset = skycoord.realize_frame(r_new) # Find pixel coordinates of offset coordinates x_offset, y_offset = skycoord_to_pixel(coords_offset, wcs, mode=skycoord_to_pixel_mode) # Find vector dx = x_offset - x dy = y_offset - y # Find the length of the vector scale = np.hypot(dx, dy) / dlat.to('degree').value # Find the position angle angle = np.arctan2(dy, dx) * u.radian return pixcoord, scale, angle
def skycoord_to_pixel_scale_angle(coords, wcs): """ Convert a set of SkyCoord coordinates into pixel coordinates, pixel scales, and position angles. Parameters ---------- coords : `~astropy.coordinates.SkyCoord` The coordinates to convert wcs : `~astropy.wcs.WCS` The WCS transformation to use Returns ------- x, y : `~numpy.ndarray` The x and y pixel coordinates corresponding to the input coordinates scale : `~astropy.units.Quantity` The pixel scale at each location, in degrees/pixel angle : `~astropy.units.Quantity` The position angle of the celestial coordinate system in pixel space. """ # Convert to pixel coordinates x, y = skycoord_to_pixel(coords, wcs, mode=skycoord_to_pixel_mode) # We take a point directly 'above' (in latitude) the position requested # and convert it to pixel coordinates, then we use that to figure out the # scale and position angle of the coordinate system at the location of # the points. # Find the coordinates as a representation object r_old = coords.represent_as('unitspherical') # Add a a small perturbation in the latitude direction (since longitude # is more difficult because it is not directly an angle). dlat = 1 * u.arcsec r_new = UnitSphericalRepresentation(r_old.lon, r_old.lat + dlat) coords_offset = coords.realize_frame(r_new) # Find pixel coordinates of offset coordinates x_offset, y_offset = skycoord_to_pixel(coords_offset, wcs, mode=skycoord_to_pixel_mode) # Find vector dx = x_offset - x dy = y_offset - y # Find the length of the vector scale = np.hypot(dx, dy) * u.pixel / dlat # Find the position angle angle = np.arctan2(dy, dx) * u.radian return x, y, scale, angle
def pixel_scale_angle_at_skycoord(skycoord, wcs, offset=1. * u.arcsec): """ Calculate the pixel scale and WCS rotation angle at the position of a SkyCoord coordinate. Parameters ---------- skycoord : `~astropy.coordinates.SkyCoord` The SkyCoord coordinate. wcs : `~astropy.wcs.WCS` The world coordinate system (WCS) transformation to use. offset : `~astropy.units.Quantity` A small angular offset to use to compute the pixel scale and position angle. Returns ------- scale : `~astropy.units.Quantity` The pixel scale in arcsec/pixel. angle : `~astropy.units.Quantity` The angle (in degrees) measured counterclockwise from the positive x axis to the "North" axis of the celestial coordinate system. Notes ----- If distortions are present in the image, the x and y pixel scales likely differ. This function computes a single pixel scale along the North/South axis. """ # We take a point directly "above" (in latitude) the input position # and convert it to pixel coordinates, then we use the pixel deltas # between the input and offset point to calculate the pixel scale and # angle. # Find the coordinates as a representation object coord = skycoord.represent_as('unitspherical') # Add a a small perturbation in the latitude direction (since longitude # is more difficult because it is not directly an angle) coord_new = UnitSphericalRepresentation(coord.lon, coord.lat + offset) coord_offset = skycoord.realize_frame(coord_new) # Find pixel coordinates of offset coordinates and pixel deltas x_offset, y_offset = skycoord_to_pixel(coord_offset, wcs, mode='all') x, y = skycoord_to_pixel(skycoord, wcs, mode='all') dx = x_offset - x dy = y_offset - y scale = offset.to(u.arcsec) / (np.hypot(dx, dy) * u.pixel) angle = (np.arctan2(dy, dx) * u.radian).to(u.deg) return scale, angle
def __wcs_skycoord_to_pixels(self) -> Tuple[np.ndarray, np.ndarray]: """Use the wcs.utils skycoord_to_pixel to derive the pixel positions Returns: Tuple[np.ndarray, np.ndarray] -- Pixels coordinate postions of nearby sources """ pix = skycoord_to_pixel(self.coords["sky"], self.wcs) * u.pixel center_pix = skycoord_to_pixel(self.center_coord, self.wcs) * u.pixel pix = [p - c for p, c in zip(pix, center_pix)] return pix
def to_pixel(self, wcs, mode='all'): """ Convert the aperture to a `CircularAperture` instance in pixel coordinates. Parameters ---------- wcs : `~astropy.wcs.WCS` The WCS transformation to use. mode : {'all', 'wcs'}, optional Whether to do the transformation including distortions (``'all'``; default) or only including only the core WCS transformation (``'wcs'``). Returns ------- aperture : `CircularAperture` object A `CircularAperture` object. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=mode) if self.r.unit.physical_type == 'angle': central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) r = (scale * self.r).to(u.pixel).value else: # pixels r = self.r.value pixel_positions = np.array([x, y]).transpose() return CircularAperture(pixel_positions, r)
def show_image(im: Image, fig=None, title: str = '', pol=0, chan=0, cm='rainbow', components=None): """ Show an Image with coordinates using matplotlib :param im: :param fig: :param title: :param pol: Polarisation :param chan: Channel :param components: Optional components :return: """ import matplotlib.pyplot as plt assert isinstance(im, Image), im if not fig: fig = plt.figure() plt.clf() fig.add_subplot(111, projection=im.wcs.sub(['longitude', 'latitude'])) if len(im.data.shape) == 4: plt.imshow(numpy.real(im.data[chan, pol, :, :]), origin='lower', cmap=cm) elif len(im.data.shape) == 2: plt.imshow(numpy.real(im.data[:, :]), origin='lower', cmap=cm) plt.xlabel('RA---SIN') plt.ylabel('DEC--SIN') plt.title(title) plt.colorbar() if components is not None: for sc in components: x, y = skycoord_to_pixel(sc.direction, im.wcs, 1, 'wcs') plt.plot(x, y, marker='+') return fig
def _world_to_pixel(skycoord, wcs): """ Calculate the sky coordinates at the input pixel positions. Parameters ---------- skycoord : `~astropy.coordinates.SkyCoord` The sky coordinate(s). wcs : WCS object or `None` A world coordinate system (WCS) transformation that supports the `astropy shared interface for WCS <https://docs.astropy.org/en/stable/wcs/wcsapi.html>`_ (e.g. `astropy.wcs.WCS`, `gwcs.wcs.WCS`). Returns ------- xpos, ypos : float or array-like The x and y pixel position(s) at the input sky coordinate(s). """ if wcs is None: return None try: return wcs.world_to_pixel(skycoord) except AttributeError: if isinstance(wcs, WCS): # for Astropy < 3.1 WCS support return skycoord_to_pixel(skycoord, wcs, origin=0) else: raise ValueError('Input wcs does not support the shared WCS ' 'interface.')
def fit_skycomponent(im: Image, sc: SkyCoord, params={}): """ Find flux at a given direction, return SkyComponent :param im: :type Image: :param sc: :type SkyCoord: :returns: SkyComponent """ log_parameters(params) log.debug( "find_flux_at_direction: Extracting flux at world coordinates %s" % str(sc)) w = im.wcs.sub(['longitude', 'latitude']) pixloc = skycoord_to_pixel(sc, im.wcs, 0, 'wcs') log.debug( "find_flux_at_direction: Extracting flux at pixel coordinates %d %d" % (pixloc[0], pixloc[1])) flux = im.data[:, :, int(pixloc[1] + 0.5), int(pixloc[0] + 0.5)] log.debug("find_flux_at_direction: Flux is %s" % flux) # We also need the frequency values w = im.wcs.sub(['spectral']) frequency = w.wcs_pix2world(range(im.data.shape[0]), 0) return create_skycomponent(direction=sc, flux=flux, frequency=frequency, shape='point')
def test_auto_rotate_systematic(self, angle): # This is a test to make sure for a number of angles that the corners # of the image are inside the final WCS but the next pixels outwards are # not. We test the full 360 range of angles. angle = np.radians(angle) pc = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) self.wcs.wcs.pc = pc wcs, shape = find_optimal_celestial_wcs([(self.array, self.wcs)], auto_rotate=True) ny, nx = self.array.shape xp = np.array([0, 0, nx - 1, nx - 1, -1, -1, nx, nx]) yp = np.array([0, ny - 1, ny - 1, 0, -1, ny, ny, -1]) c = pixel_to_skycoord(xp, yp, self.wcs, origin=0) xp_final, yp_final = skycoord_to_pixel(c, wcs, origin=0) ny_final, nx_final = shape inside = ((xp_final >= -0.5) & (xp_final <= nx_final - 0.5) & (yp_final >= -0.5) & (yp_final <= ny_final - 0.5)) assert_equal(inside, [1, 1, 1, 1, 0, 0, 0, 0])
def __init__(self, nddata, position, shape): if isinstance(position, SkyCoord): if nddata.wcs is None: raise ValueError('nddata must contain WCS if the input ' 'position is a SkyCoord') x, y = skycoord_to_pixel(position, nddata.wcs, mode='all') position = (y, x) data = np.asanyarray(nddata.data) print(data.shape, shape, position) slices_large, slices_small = overlap_slices(data.shape, shape, position) self.slices_large = slices_large self.slices_small = slices_small data = nddata.data[slices_large] mask = None uncertainty = None if nddata.mask is not None: mask = nddata.mask[slices_large] if nddata.uncertainty is not None: uncertainty = nddata.uncertainty[slices_large] self.nddata = NDData(data, mask=mask, uncertainty=uncertainty)
def generate_pv_line_coordinates(angle, box, wcs, n_points): """ This function generates the PV pixel and sky position given the its PA in degrees. """ angle_pvline = np.pi / 180 * (angle + 90) def y_pv(xp, m, x0, y0): return m * (xp - x0) + y0 vla4b_sky = SkyCoord(*mf.default_params['vla4b_deg'], unit='deg') vla4b_pixel = skycoord_to_pixel(vla4b_sky, wcs) # aframe = vla4b_sky.skyoffset_frame() # vla4b_offset = vla4b_sky.transform_to(aframe) x_first, y_first = box[1] x_last, y_last = box[0] xs_pixel = np.array([x for x in np.linspace(x_first, x_last, n_points)]) ys_pixel = np.array([ y_pv( x, np.tan(angle_pvline), vla4b_pixel[0], vla4b_pixel[1], ) for x in xs_pixel ]) xys_sky_PV = np.array( [pixel_to_skycoord(x, y, wcs) for x, y in zip(xs_pixel, ys_pixel)]) return xys_sky_PV
def make_proposal_boxes(wcs: WCS, catalogue: Table): """ Create Faster RCNN proposal boxes for all sources in the image The sky_coords seems to be swapped x and y on the boxes, so should be swapped here too :param wcs: WCS of the Radio data, so catalog data can be translated correctly :param catalogue: Catalogue to query :return: A Numpy array that holds the information in the correct location """ ra_array = np.array(catalogue["ra"], dtype=float) dec_array = np.array(catalogue["dec"], dtype=float) sky_coords = SkyCoord(ra_array, dec_array, unit="deg") # Now have the objects, need to convert those RA and Decs to pixel coordinates proposals = [] coords = skycoord_to_pixel(sky_coords, wcs, 0) for index, x in enumerate(coords[0]): try: proposals.append( make_bounding_box( ra_array[index], dec_array[index], wcs=wcs, class_name="Proposal Box", ) ) except Exception as e: print(f"Failed Proposal: {e}") return proposals
def getSourceVals(RA, DEC, w, hdu, allfreq): c = SkyCoord(RA, DEC, frame='fk5', unit='deg') x, y = wcs.skycoord_to_pixel(c, w) X = np.rint(x) Y = np.rint(y) #hdu=pf.open(cube, memmap=True, mode='denywrite') if Y < 0 or X < 0: #Y > hdu[0].data.shape[1] or X > hdu[0].data.shape[2] or #print "Source not in image!" col = [] return col, allfreq col = hdu[0].data[:, 0, int(Y), int(X)] #hdu.close() #col = cube[:,int(Y),int(X)] flag = np.zeros(col.size) flag[np.isfinite(col)] = 1 flag[np.abs(col) > 10] = 0 flag[col == 0] = 0 #pdb.set_trace() col = np.where(is_outlier(col), np.nan, col) col = np.where(np.isfinite(col), col, np.nan) col = np.where(col == 0, np.nan, col) col = np.where(np.abs(col) > 1E15, np.nan, col) #col=col[flag==1] #allfreq=allfreq[flag==1] return col, allfreq
def voronoi_decomposition(im, comps): """Construct a Voronoi decomposition of a set of components The array return contains the index into the Voronoi structure :param im: :param comps: :return: Voronoi structure, vertex image """ def voronoi_vertex(vy, vx, vertex_y, vertex_x): """ Return the nearest Voronoi vertex :param vy: :param vx: :param vertex_y: :param vertex_x: :return: """ return numpy.argmin(numpy.hypot(vy - vertex_y, vx - vertex_x)) directions = SkyCoord([u.rad * c.direction.ra.rad for c in comps], [u.rad * c.direction.dec.rad for c in comps]) x, y = skycoord_to_pixel(directions, im.wcs, 0, 'wcs') points = [(x[i], y[i]) for i, _ in enumerate(x)] vor = Voronoi(points) nchan, npol, ny, nx = im.shape vertex_image = numpy.zeros([ny, nx]).astype('int') for j in range(ny): for i in range(nx): vertex_image[j, i] = voronoi_vertex(j, i, vor.points[:, 1], vor.points[:, 0]) return vor, vertex_image
def test_skycoord_to_pixel(mode): # Import astropy.coordinates here to avoid circular imports from astropy.coordinates import SkyCoord header = get_pkg_data_contents('maps/1904-66_TAN.hdr', encoding='binary') wcs = WCS(header) ref = SkyCoord(0.1 * u.deg, -89. * u.deg, frame='icrs') xp, yp = skycoord_to_pixel(ref, wcs, mode=mode) # WCS is in FK5 so we need to transform back to ICRS new = pixel_to_skycoord(xp, yp, wcs, mode=mode).transform_to('icrs') assert_allclose(new.ra.degree, ref.ra.degree) assert_allclose(new.dec.degree, ref.dec.degree) # Make sure you can specify a different class using ``cls`` keyword class SkyCoord2(SkyCoord): pass new2 = pixel_to_skycoord(xp, yp, wcs, mode=mode, cls=SkyCoord2).transform_to('icrs') assert new2.__class__ is SkyCoord2 assert_allclose(new2.ra.degree, ref.ra.degree) assert_allclose(new2.dec.degree, ref.dec.degree)
def to_pixel(self, wcs): """ Return a EllipticalAnnulus instance in pixel coordinates. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=skycoord_to_pixel_mode) central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) if self.a_in.unit.physical_type == 'angle': a_in = (scale * self.a_in).to(u.pixel).value a_out = (scale * self.a_out).to(u.pixel).value b_out = (scale * self.b_out).to(u.pixel).value else: a_in = self.a_in.value a_out = self.a_out.value b_out = self.b_out.value theta = (angle + self.theta).to(u.radian).value pixel_positions = np.array([x, y]).transpose() return EllipticalAnnulus(pixel_positions, a_in, a_out, b_out, theta)
def extract_gaussian_parameters_from_component_catalogue( pandas_cat, wcs, arcsec_per_pixel=1.5, PA_offset_degree=90, maj_min_in_arcsec=True, peak_flux_is_in_mJy=True, ): # Create skycoords for the center locations of all gaussians c = SkyCoord(pandas_cat.RA, pandas_cat.DEC, unit="deg") # transform ra, decs to pixel coordinates if maj_min_in_arcsec: deg2arcsec = 1 else: deg2arcsec = 3600 if peak_flux_is_in_mJy: mJy2Jy = 1000 else: mJy2Jy = 1 pixel_locs = skycoord_to_pixel(c, wcs, origin=0, mode="all") gaussians = [ models.Gaussian2D( row.Peak_flux / mJy2Jy, x, y, FWHM_to_sigma_for_gaussian(row.Maj * deg2arcsec / arcsec_per_pixel), FWHM_to_sigma_for_gaussian(row.Min * deg2arcsec / arcsec_per_pixel), theta=np.deg2rad(row.PA + PA_offset_degree), ) for ((irow, row), x, y) in zip(pandas_cat.iterrows(), pixel_locs[0], pixel_locs[1]) ] return gaussians
def test_skycoord_to_pixel(mode): # Import astropy.coordinates here to avoid circular imports from astropy.coordinates import SkyCoord header = get_pkg_data_contents('data/maps/1904-66_TAN.hdr', encoding='binary') wcs = WCS(header) ref = SkyCoord(0.1 * u.deg, -89. * u.deg, frame='icrs') xp, yp = skycoord_to_pixel(ref, wcs, mode=mode) # WCS is in FK5 so we need to transform back to ICRS new = pixel_to_skycoord(xp, yp, wcs, mode=mode).transform_to('icrs') assert_allclose(new.ra.degree, ref.ra.degree) assert_allclose(new.dec.degree, ref.dec.degree) # Make sure you can specify a different class using ``cls`` keyword class SkyCoord2(SkyCoord): pass new2 = pixel_to_skycoord(xp, yp, wcs, mode=mode, cls=SkyCoord2).transform_to('icrs') assert new2.__class__ is SkyCoord2 assert_allclose(new2.ra.degree, ref.ra.degree) assert_allclose(new2.dec.degree, ref.dec.degree)
def mask_galaxy(image, wcs, Ra, Dec, name, radius): """Masks galaxy at Ra, Dec within a radius given in arcminutes Creates a circular mask centered at a given Ra, Dec. The radius is given in arcmins. The wcs object is used to convert these inputs to pixel locations. A pixel scale is also determined. If the object name is suppled, SESAME is used to find object center. If no active internet connection is available, center location must be manually entered, in degrees. If no center coordinates are supplied, (0, 0) is the default center. Args: image(array, required): Image data wcs: World Coordinte System object name(str, optional): Name of galaxy or object Ra(str): Right Ascention Dec(str): Declination Radius(float, required): Radius to be masked, in arcminutes Returns: masked_img(array): Image which has been masked mask(boolean array): Mask of the given object""" # Radius must be given in arcminutes # Dimentions of the image dim = (image.shape) y, x = dim[0], dim[1] # Finds the center of an object by inputting its name into SESAME # This requires an active internet connection # a, b are the coordinates of the center given in pixels try: center = SkyCoord.from_name(name) except Exception: print("No active internet connection. Manually enter Ra, Dec.") Ra = Ra Dec = Dec center = SkyCoord(Ra, Dec, unit="deg") c_pix = skycoord_to_pixel(center, wcs) a, b = c_pix[0], c_pix[1] print(center) radius = radius * u.arcmin # Finds pixel scale using WSC object. The default units can be found by # unit = header['CUNIT1'], they are degrees by convention # degrees are converted to arcmins and radius in computed in pixels scale = proj_plane_pixel_scales(wcs) pix_scale = scale[0] * u.deg.to(u.arcmin) print('Image Scale: ' + str(pix_scale) + ' arcmin/pix') rad_pix = (radius / pix_scale).value # Indexes each pixel and checks if its is >= radius from center Y, X = np.ogrid[:y, :x] dist_from_center = np.sqrt((X - a)**2 + (Y - b)**2) mask = dist_from_center <= rad_pix return mask
def main(): files = np.loadtxt("filenames_M8_wcs.txt", dtype='str') for infile in files: print infile ccmapfile = open('Lagoon_2MASS_xy_radec.dat', 'w') newfile = infile.replace('wcs', 'wcs2') copyfile(infile, newfile) coords = np.loadtxt('Lagoon_2MASS_radec_amended.dat', dtype=str) ra = coords[:, 0] ra = np.array([float(i) for i in ra]) dec = coords[:, 1] dec = np.array([float(i) for i in dec]) # open image: instr = fits.open(infile, mode='readonly', memmap=True) pixels = instr[0].data[:] crval1p = instr[0].header['CRVAL1P'] crval2p = instr[0].header['CRVAL2P'] instr.close() for i in np.arange(len(ra)): skyposition = SkyCoord(ra[i], dec[i], unit=('deg', 'deg'), frame='icrs') wcs = WCS(infile) pixelpos = skycoord_to_pixel(skyposition, wcs=wcs) columns = str(int(round(pixelpos[0]))) rows = str(int(round(pixelpos[1]))) ## Guess at flux: fluxguess = str(pixels[int(rows), int(columns)]) result = kepprf_AMC.kepprf_AMC(infile,'1',columns,rows,fluxguess,border=1,background='yes',focus='no', \ prfdir='/Users/acody/Data/Kepler',xtol=0.0001,ftol=0.01,verbose=False,logfile='kepprf.log') newx = result[1] - crval1p + 1 newy = result[2] - crval2p + 1 print >> ccmapfile, newx, newy, ra[i], dec[i] ccmapfile.close() # Call ccmap to compute new wcs iraf.ccmap("Lagoon_2MASS_xy_radec.dat", "Lagoon_coordfit.db", images=newfile, lngunit="degrees", latunit="degrees", update="yes", verbose="no", interactive="no") os.remove('Lagoon_2MASS_xy_radec.dat') os.remove('Lagoon_coordfit.db')
def MakeTiffCut(tiledir, outdir, positions, xs, ys, df, maketiff, makepngs): logger = logging.getLogger(__name__) os.makedirs(outdir, exist_ok=True) imgname = glob.glob(tiledir + '*.tiff') try: im = Image.open(imgname[0]) #except IOError as e: except IndexError as e: print('No TIFF file found for tile ' + df['TILENAME'][0] + '. Will not create true-color cutout.') logger.error('MakeTiffCut - No TIFF file found for tile ' + df['TILENAME'][0] + '. Will not create true-color cutout.') return # try opening I band FITS (fallback on G, R bands) hdul = None for _i in ['i','g','r','z','Y']: tilename = glob.glob(tiledir+'*_{}.fits.fz'.format(_i)) try: hdul = fits.open(tilename[0]) except IOError as e: hdul = None logger.warning('MakeTiffCut - Could not find master FITS file: ' + tilename) continue else: break if not hdul: print('Cannot find a master fits file for this tile.') logger.error('MakeTiffCut - Cannot find a master fits file for this tile.') return w = WCS(hdul['SCI'].header) pixelscale = utils.proj_plane_pixel_scales(w) dx = int(0.5 * xs * ARCMIN_TO_DEG / pixelscale[0]) # pixelscale is in degrees (CUNIT) dy = int(0.5 * ys * ARCMIN_TO_DEG / pixelscale[1]) pixcoords = utils.skycoord_to_pixel(positions, w, origin=0, mode='wcs') for i in range(len(positions)): if 'COADD_OBJECT_ID' in df: filenm = outdir + str(df['COADD_OBJECT_ID'][i]) else: filenm = outdir + 'x{0}y{1}'.format(df['RA'][i], df['DEC'][i]) #filenm = outdir + 'DESJ' + _DecConverter(df['RA'][0], df['DEC'][0]) left = max(0, pixcoords[0][i] - dx) upper = max(0, im.size[1] - pixcoords[1][i] - dy) right = min(pixcoords[0][i] + dx, 10000) lower = min(im.size[1] - pixcoords[1][i] + dy, 10000) newimg = im.crop((left, upper, right, lower)) if newimg.size != (2*dx, 2*dy): logger.info('MakeTiffCut - {} is smaller than user requested. This is likely because the object/coordinate was in close proximity to the edge of a tile.'.format(filenm.split('/')[-1])) if maketiff: newimg.save(filenm+'.tiff', format='TIFF') if makepngs: newimg.save(filenm+'.png', format='PNG') logger.info('MakeTiffCut - Tile {} complete.'.format(df['TILENAME'][0]))
def insert_skycomponent_para(im: image_for_para, sc: Union[Skycomponent, List[Skycomponent]], insert_method='', bandwidth=1.0, support=8) -> image_for_para: ''' :param im: 被插入的image :param sc: 插入的skycomponent,可以有多个skycomponent :param insert_method: 插入方法,四种分别为: Lanczos Sinc PSWF 和 缺省方法 :param bandwidth: :param support: :return: 新的image ''' if type(im) == tuple: im = im[1] assert type(im) == image_for_para support = int(support / bandwidth) ny, nx = im.shape if not isinstance(sc, collections.Iterable): sc = [sc] for comp in sc: assert comp.shape == 'Point', "Cannot handle shape %s" % comp.shape pixloc = skycoord_to_pixel(comp.direction, im.wcs, 1, 'wcs') if insert_method == "Lanczos": insert_array_para(im.data, pixloc[0], pixloc[1], comp.flux[im.channel, im.polarisation], bandwidth, support, insert_function=insert_function_L) elif insert_method == "Sinc": insert_array_para(im.data, pixloc[0], pixloc[1], comp.flux[im.channel, im.polarisation], bandwidth, support, insert_function=insert_function_sinc) elif insert_method == "PSWF": insert_array_para(im.data, pixloc[0], pixloc[1], comp.flux[im.channel, im.polarisation], bandwidth, support, insert_function=insert_function_pswf) else: y, x = numpy.round(pixloc[1]).astype('int'), numpy.round( pixloc[0]).astype('int') if x >= 0 and x < nx and y >= 0 and y < ny: im.data[y, x] += comp.flux[im.channel, im.polarisation] return im
def to_pixel(self, wcs, mode='local', tolerance=None): if mode != 'local': raise NotImplementedError if tolerance is not None: raise NotImplementedError x, y = skycoord_to_pixel(self.vertices, wcs) vertices_pix = PixCoord(x, y) return PolygonPixelRegion(vertices_pix)
def skytopix(self, sky): """ Given a skycoord (or list of skycoords) returns the pixel locations """ hdu = self.sci hdr = hdu.header wcs, frame = WCS(hdr), hdr['RADESYS'].lower() pixel = skycoord_to_pixel(sky, wcs) return pixel
def lm(self, ra, dec): coord = SkyCoord(ra=ra * u.rad, dec=dec * u.rad) coord_pixels = utils.skycoord_to_pixel(coords=coord, wcs=self.wcs, origin=0, mode='all') if np.isnan(np.sum(coord_pixels)): l, m = -0.0, 0.0 else: l, m = coord_pixels[self.ra_axis], coord_pixels[self.dec_axis] l = (l - self._l0) * -self.xscale m = (m - self._m0) * self.yscale return l, m
def show_image(im: Image, fig=None, title: str = '', pol=0, chan=0, cm='Greys', components=None, vmin=None, vmax=None, vscale=1.0): """ Show an Image with coordinates using matplotlib, optionally with components :param im: Image :param fig: Matplotlib figure :param title: String for title of plot :param pol: Polarisation to show (index) :param chan: Channel to show (index) :param components: Optional components to be overlaid :param vmin: Clip to this minimum :param vmax: Clip to this maximum :param vscale: scale max, min by this amount :return: """ import matplotlib.pyplot as plt assert isinstance(im, Image), im fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection=im.wcs.sub([1, 2])) if len(im.data.shape) == 4: data_array = numpy.real(im.data[chan, pol, :, :]) else: data_array = numpy.real(im.data) if vmax is None: vmax = vscale * numpy.max(data_array) if vmin is None: vmin = vscale * numpy.min(data_array) cm = ax.imshow(data_array, origin='lower', cmap=cm, vmax=vmax, vmin=vmin) ax.set_xlabel(im.wcs.wcs.ctype[0]) ax.set_ylabel(im.wcs.wcs.ctype[1]) ax.set_title(title) fig.colorbar(cm, orientation='vertical', shrink=0.7) if components is not None: for sc in components: x, y = skycoord_to_pixel(sc.direction, im.wcs, 0, 'wcs') ax.plot(x, y, marker='+', color='red') return fig
def show_skymodel(sms, psf_width=1.75, cm='Greys', vmax=None, vmin=None): """ Show a list of SkyModels :param sms: List of SkyModels :param psf_width: Width of PSF in pixels :param cm: matplotlib colormap :param vmax: Maximum in image display :param vmin: Minimum in image display :return: """ sp = 1 for ism, sm in enumerate(sms): plt.clf() plt.subplot(121, projection=sms[ism].image.wcs.sub([1, 2])) sp += 1 smodel = copy_image(sms[ism].image) smodel = insert_skycomponent(smodel, sms[ism].components) smodel = smooth_image(smodel, psf_width) if vmax is None: vmax = numpy.max(smodel.data[0, 0, ...]) if vmin is None: vmin = numpy.min(smodel.data[0, 0, ...]) plt.imshow(smodel.data[0, 0, ...], origin='lower', cmap=cm, vmax=vmax, vmin=vmin) plt.xlabel(sms[ism].image.wcs.wcs.ctype[0]) plt.ylabel(sms[ism].image.wcs.wcs.ctype[1]) plt.title('SkyModel%d' % ism) components = sms[ism].components if components is not None: for sc in components: x, y = skycoord_to_pixel(sc.direction, sms[ism].image.wcs, 0, 'wcs') plt.plot(x, y, marker='+', color='red') gaintable = sms[ism].gaintable if gaintable is not None: plt.subplot(122) sp += 1 phase = numpy.angle(sm.gaintable.gain[:, :, 0, 0, 0]) phase -= phase[:, 0][:, numpy.newaxis] plt.imshow(phase, origin='lower') plt.xlabel('Dish/Station') plt.ylabel('Integration') plt.show()
def plot_exclusion_mask(self, size=None, **kwargs): """Plot exclusion mask for this observation The plot will be centered at the pointing position Parameters ---------- size : `~astropy.coordinates.Angle` Edge length of the plot """ size = Angle('5 deg') if size is None else size ax = self.meta.exclusion.plot(**kwargs) self._set_ax_limits(ax, size) point = skycoord_to_pixel(self.meta.pointing, ax.wcs) ax.scatter(point[0], point[1], s=250, marker="+", color='black') return ax
def skycoord_to_pixel(coords, wcs, unit='deg'): """Transform sky coordinates (ra, dec) to pixel coordinates (x, y) given a wcs. :param coords: Coordinates. Multiple formats accepted: - [ra, dec] - [[ra1, ra2], [dec1, dec2]] - or a SkyCoord object :param wcs: an astropy.wcs.WCS object :return: A list of (x, y) coordinates in pixel units """ if not isinstance(coords, SkyCoord): coords = SkyCoord(coords[0], coords[1], unit=unit) return utils.skycoord_to_pixel(coords, wcs)
def _to_pixel_params(self, wcs, mode='all'): """ Convert the sky aperture parameters to those for a pixel aperture. Parameters ---------- wcs : `~astropy.wcs.WCS` The world coordinate system (WCS) transformation to use. mode : {'all', 'wcs'}, optional Whether to do the transformation including distortions (``'all'``; default) or only including only the core WCS transformation (``'wcs'``). Returns ------- pixel_params : dict A dictionary of parameters for an equivalent pixel aperture. """ pixel_params = {} x, y = skycoord_to_pixel(self.positions, wcs, mode=mode) pixel_params['positions'] = np.array([x, y]).transpose() # The aperture object must have a single value for each shape # parameter so we must use a single pixel scale for all positions. # Here, we define the scale at the WCS CRVAL position. crval = SkyCoord([wcs.wcs.crval], frame=wcs_to_celestial_frame(wcs), unit=wcs.wcs.cunit) scale, angle = pixel_scale_angle_at_skycoord(crval, wcs) params = self._params[:] theta_key = 'theta' if theta_key in self._params: pixel_params[theta_key] = (self.theta + angle).to(u.radian).value params.remove(theta_key) param_vals = [getattr(self, param) for param in params] if param_vals[0].unit.physical_type == 'angle': for param, param_val in zip(params, param_vals): pixel_params[param] = (param_val / scale).to(u.pixel).value else: # pixels for param, param_val in zip(params, param_vals): pixel_params[param] = param_val.value return pixel_params
def test_skycoord_to_pixel_distortions(mode): # Import astropy.coordinates here to avoid circular imports from astropy.coordinates import SkyCoord header = get_pkg_data_filename('data/sip.fits') wcs = WCS(header) ref = SkyCoord(202.50 * u.deg, 47.19 * u.deg, frame='icrs') xp, yp = skycoord_to_pixel(ref, wcs, mode=mode) # WCS is in FK5 so we need to transform back to ICRS new = pixel_to_skycoord(xp, yp, wcs, mode=mode).transform_to('icrs') assert_allclose(new.ra.degree, ref.ra.degree) assert_allclose(new.dec.degree, ref.dec.degree)
def from_moc(depth_ipix_d, wcs): # Create a new MOC that do not contain the HEALPix # cells that are backfacing the projection depths = [int(depth_str) for depth_str in depth_ipix_d.keys()] min_depth = min(depths) max_depth = max(depths) ipixels = np.asarray(depth_ipix_d[str(min_depth)]) # Split the cells located at the border of the projection # until at least the depth 7 max_split_depth = max(7, max_depth) ipix_d = {} for depth in range(min_depth, max_split_depth + 1): hp = HEALPix(nside=(1 << depth), order='nested', frame=ICRS()) ipix_boundaries = hp.boundaries_skycoord(ipixels, step=1) # Projection on the given WCS xp, yp = skycoord_to_pixel(coords=ipix_boundaries, wcs=wcs) _, _, frontface_id = backface_culling(xp, yp) # Get the pixels which are backfacing the projection backfacing_ipix = ipixels[~frontface_id] frontface_ipix = ipixels[frontface_id] depth_str = str(depth) ipix_d.update({depth_str: frontface_ipix}) next_depth = str(depth + 1) ipixels = [] if next_depth in depth_ipix_d: ipixels = depth_ipix_d[next_depth] for bf_ipix in backfacing_ipix: child_bf_ipix = bf_ipix << 2 ipixels.extend([child_bf_ipix, child_bf_ipix + 1, child_bf_ipix + 2, child_bf_ipix + 3]) ipixels = np.asarray(ipixels) return ipix_d
def wcs_skycoord_to_pixel(self, coords): """ Convert a set of SkyCoord coordinates into pixels. Calls `~astropy.wcs.utils.skycoord_to_pixel`, passing ``coords`` to it. Parameters ---------- coords : `~astropy.coordinates.SkyCoord` The coordinates to convert. Returns ------- xp, yp : `~numpy.ndarray` The pixel coordinates. """ return skycoord_to_pixel(coords=coords, wcs=self.wcs, origin=_DEFAULT_WCS_ORIGIN, mode=_DEFAULT_WCS_MODE)
def to_pixel(self, wcs, mode='all'): """ Convert the aperture to a `RectangularAnnulus` instance in pixel coordinates. Parameters ---------- wcs : `~astropy.wcs.WCS` The WCS transformation to use. mode : {'all', 'wcs'}, optional Whether to do the transformation including distortions (``'all'``; default) or only including only the core WCS transformation (``'wcs'``). Returns ------- aperture : `RectangularAnnulus` object A `RectangularAnnulus` object. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=mode) central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) if self.w_in.unit.physical_type == 'angle': w_in = (scale * self.w_in).to(u.pixel).value w_out = (scale * self.w_out).to(u.pixel).value h_out = (scale * self.h_out).to(u.pixel).value else: # pixels w_in = self.w_in.value w_out = self.w_out.value h_out = self.h_out.value theta = (angle + self.theta).to(u.radian).value pixel_positions = np.array([x, y]).transpose() return RectangularAnnulus(pixel_positions, w_in, w_out, h_out, theta)
def to_pixel(self, wcs): """ Convert the aperture to a `CircularAperture` instance in pixel coordinates. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=skycoord_to_pixel_mode) if self.r.unit.physical_type == 'angle': central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) r = (scale * self.r).to(u.pixel).value else: r = self.r.value # pixels pixel_positions = np.array([x, y]).transpose() return CircularAperture(pixel_positions, r)
def to_pixel(self, wcs): """ Return a CircularAnnulus instance in pixel coordinates. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=skycoord_to_pixel_mode) if self.r_in.unit.physical_type == 'angle': central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) r_in = (scale * self.r_in).to(u.pixel).value r_out = (scale * self.r_out).to(u.pixel).value else: # pixel r_in = self.r_in.value r_out = self.r_out.value pixel_positions = np.array([x, y]).transpose() return CircularAnnulus(pixel_positions, r_in, r_out)
def linear_offset_coordinates(wcs, center): ''' return a locally linear offset coordinate system does the simplest thing possible and assumes no projection distortions ''' assert isinstance(center, coords.SkyCoord), \ '`center` must by of type `SkyCoord`' assert center.isscalar, '`center` must have length 1' # Convert center to pixel coordinates xp, yp = skycoord_to_pixel(center, wcs) # Set up new WCS new_wcs = WCS(naxis=2) new_wcs.wcs.crpix = xp + 1, yp + 1 new_wcs.wcs.crval = 0., 0. new_wcs.wcs.cdelt = proj_plane_pixel_scales(wcs) new_wcs.wcs.ctype = 'XOFFSET', 'YOFFSET' new_wcs.wcs.cunit = 'deg', 'deg' return new_wcs
def gcentroid_wcs(im, wcs, guess, **kwargs): """Gaussian centroid given sky coordinates. Parameters ---------- im : ndarray 2D image for centroiding. wcs : astropy WCS World coordinate system object. guess : SkyCoord or tuple of Quantity Location on which to centroid. **kwargs Any ``gcentroid`` keyword arguments. Returns ------- cyx : ndarray Pixel coordinates of the computed center. The lower-left corner of a pixel is -0.5, -0.5. c : SkyCoord World coordinates of the computed center. """ if isinstance(guess, coords.SkyCoord): g = guess else: g = coords.SkyCoord(*guess) gx, gy = np.array(skycoord_to_pixel(g, wcs)) cyx = gcentroid(im, yx=(gy, gx), **kwargs) c = pixel_to_skycoord(cyx[1], cyx[0], wcs) return cyx, c
def to_pixel(self, wcs): """ Return a `~gammapy.regions.PixCircleRegion`. Parameters ---------- wcs : `~astropy.wcs.WCS` WCS object """ x, y = skycoord_to_pixel(self.pos, wcs, mode='wcs', origin=1) pix_radius = self.radius.deg / np.abs(wcs.wcs.cdelt[0]) # TODO understand what is going on here # from photutils.utils.wcs_helpers import skycoord_to_pixel_scale_angle # central_pos = SkyCoord([wcs.wcs.crval], frame=self.pos.name, unit=wcs.wcs.cunit) # xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) # val = (scale * self.radius).to(u.pixel).value # pix_radius = np.round(val[0],4) # pix_position = np.array([x, y]).transpose() return PixCircleRegion((x, y), pix_radius)
def compute_healpix_vertices(depth, ipix, wcs): path_vertices = np.array([]) codes = np.array([]) depth = int(depth) step = 1 if depth < 3: step = 2 hp = HEALPix(order="nested", nside=(1 << depth), frame=ICRS()) ipix_boundaries = hp.boundaries_skycoord(ipix, step=step) # Projection on the given WCS xp, yp = skycoord_to_pixel(ipix_boundaries, wcs=wcs) c1 = np.vstack((xp[:, 0], yp[:, 0])).T c2 = np.vstack((xp[:, 1], yp[:, 1])).T c3 = np.vstack((xp[:, 2], yp[:, 2])).T c4 = np.vstack((xp[:, 3], yp[:, 3])).T if depth < 3: c5 = np.vstack((xp[:, 4], yp[:, 4])).T c6 = np.vstack((xp[:, 5], yp[:, 5])).T c7 = np.vstack((xp[:, 6], yp[:, 6])).T c8 = np.vstack((xp[:, 7], yp[:, 7])).T cells = np.hstack((c1, c2, c3, c4, c5, c6, c7, c8, np.zeros((c1.shape[0], 2)))) path_vertices = cells.reshape((9*c1.shape[0], 2)) single_code = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) else: cells = np.hstack((c1, c2, c3, c4, np.zeros((c1.shape[0], 2)))) path_vertices = cells.reshape((5*c1.shape[0], 2)) single_code = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) codes = np.tile(single_code, c1.shape[0]) return path_vertices, codes
def to_pixel(self, wcs): """ Return a EllipticalAperture instance in pixel coordinates. """ x, y = skycoord_to_pixel(self.positions, wcs, mode=skycoord_to_pixel_mode) central_pos = SkyCoord([wcs.wcs.crval], frame=self.positions.name, unit=wcs.wcs.cunit) xc, yc, scale, angle = skycoord_to_pixel_scale_angle(central_pos, wcs) if self.a.unit.physical_type == 'angle': a = (scale * self.a).to(u.pixel).value b = (scale * self.b).to(u.pixel).value else: # pixel a = self.a.value b = self.b.value theta = (angle + self.theta).to(u.radian).value pixel_positions = np.array([x, y]).transpose() return EllipticalAperture(pixel_positions, a, b, theta)
def to_pixel(self, wcs): center_x, center_y = skycoord_to_pixel(self.center, wcs=wcs) center = PixCoord(center_x, center_y) return TextPixelRegion(center, self.text, self.meta, self.visual)
def test_fill_acceptance_image(): # create empty image # odd number of pixels needed for having the center in its own pixel n_pix_x = 101 n_pix_y = 101 bin_size = Angle(0.1, 'deg') image = make_empty_image(n_pix_x, n_pix_y, bin_size.degree, xref=0, yref=0, fill=0, proj='CAR', coordsys='GAL', xrefpix=None, yrefpix=None, dtype='float32') # define center coordinate of the image in wolrd and pixel coordinates lon = image.header['CRVAL1'] lat = image.header['CRVAL2'] center = SkyCoord(lon, lat, unit='deg', frame='galactic') # initialize WCS to the header of the image w = WCS(image.header) x_center_pix, y_center_pix = skycoord_to_pixel(center, w, origin=0) # define pixel sizes # x_pix_size = Angle(abs(image.header['CDELT1']), 'deg') # y_pix_size = Angle(abs(image.header['CDELT2']), 'deg') # define radial acceptance and offset angles # using bin_size for the offset step makes the test comparison easier offset = Angle(np.arange(0., 30., bin_size.degree), 'deg') sigma = Angle(1.0, 'deg') amplitude = 1. mean = 0. stddev = sigma.radian gaus_model = models.Gaussian1D(amplitude, mean, stddev) acceptance = gaus_model(offset.radian) # fill acceptance in the image image = fill_acceptance_image(image.header, center, offset, acceptance) # test: check points at the offsets where the acceptance is defined # along the x axis # define grids of pixel coorinates xpix_coord_grid, ypix_coord_grid = coordinates(image, world=False) # calculate pixel offset from center (in world coordinates) coord = pixel_to_skycoord(xpix_coord_grid, ypix_coord_grid, w, origin=0) pix_off = coord.separation(center) # x axis defined in the array positions [y_center_pix,:] # only interested in semi axis, so [y_center_pix, x_center_pix:] ix_min = int(x_center_pix) iy = int(y_center_pix) pix_off_x_axis = pix_off[iy, ix_min:] image.data_x_axis = image.data[iy, ix_min:] # cut offset and acceptance arrays to match image size # this is only valid if the offset step matches the pixel size n = pix_off_x_axis.size acceptance_cut = acceptance[0:n] # check acceptance of the image: np.testing.assert_almost_equal(image.data_x_axis, acceptance_cut, decimal=4)
def nddata_cutout2d(nddata, position, size, mode='trim', fill_value=np.nan): """ Create a 2D cutout of a `~astropy.nddata.NDData` object. Specifically, cutouts will made for the ``nddata.data`` and ``nddata.mask`` (if present) arrays. If ``nddata.wcs`` exists, then it will also be updated. Note that cutouts will not be made for ``nddata.uncertainty`` (if present) because they are general objects and not arrays. Parameters ---------- nddata : `~astropy.nddata.NDData` The 2D `~astropy.nddata.NDData` from which the cutout is taken. position : tuple or `~astropy.coordinates.SkyCoord` The position of the cutout array's center with respect to the ``nddata.data`` array. The position can be specified either as a ``(x, y)`` tuple of pixel coordinates or a `~astropy.coordinates.SkyCoord`, in which case ``nddata.wcs`` must exist. size : int, array-like, `~astropy.units.Quantity` The size of the cutout array along each axis. If ``size`` is a scalar number or a scalar `~astropy.units.Quantity`, then a square cutout of ``size`` will be created. If ``size`` has two elements, they should be in ``(ny, nx)`` order. Scalar numbers in ``size`` are assumed to be in units of pixels. ``size`` can also be a `~astropy.units.Quantity` object or contain `~astropy.units.Quantity` objects. Such `~astropy.units.Quantity` objects must be in pixel or angular units. For all cases, ``size`` will be converted to an integer number of pixels, rounding the the nearest integer. See the ``mode`` keyword for additional details on the final cutout size. mode : {'trim', 'partial', 'strict'}, optional The mode used for creating the cutout data array. For the ``'partial'`` and ``'trim'`` modes, a partial overlap of the cutout array and the input ``nddata.data`` array is sufficient. For the ``'strict'`` mode, the cutout array has to be fully contained within the ``nddata.data`` array, otherwise an `~astropy.nddata.utils.PartialOverlapError` is raised. In all modes, non-overlapping arrays will raise a `~astropy.nddata.utils.NoOverlapError`. In ``'partial'`` mode, positions in the cutout array that do not overlap with the ``nddata.data`` array will be filled with ``fill_value``. In ``'trim'`` mode only the overlapping elements are returned, thus the resulting cutout array may be smaller than the requested ``size``. fill_value : number, optional If ``mode='partial'``, the value to fill pixels in the cutout array that do not overlap with the input ``nddata.data``. ``fill_value`` must have the same ``dtype`` as the input ``nddata.data`` array. Returns ------- result : `~astropy.nddata.NDData` A `~astropy.nddata.NDData` object with cutouts for the data and mask, if input. Examples -------- >>> from astropy.nddata import NDData >>> import astropy.units as u >>> from astroimtools import nddata_cutout2d >>> data = np.random.random((500, 500)) >>> unit = u.electron / u.s >>> mask = (data > 0.7) >>> meta = {'exptime': 1234 * u.s} >>> nddata = NDData(data, mask=mask, unit=unit, meta=meta) >>> cutout = nddata_cutout2d(nddata, (100, 100), (10, 10)) >>> cutout.data.shape (10, 10) >>> cutout.mask.shape (10, 10) >>> cutout.unit Unit("electron / s") """ from astropy.nddata.utils import Cutout2D if not isinstance(nddata, NDData): raise ValueError('nddata input must be an NDData object') if isinstance(position, SkyCoord): if nddata.wcs is None: raise ValueError('nddata must contain WCS if the input ' 'position is a SkyCoord') position = skycoord_to_pixel(position, nddata.wcs, mode='all') data_cutout = Cutout2D(np.asanyarray(nddata.data), position, size, wcs=nddata.wcs, mode=mode, fill_value=fill_value) # need to create a new NDData instead of copying/replacing nddata_out = NDData(data_cutout.data, unit=nddata.unit, uncertainty=nddata.uncertainty, meta=nddata.meta) if nddata.wcs is not None: nddata_out.wcs = data_cutout.wcs if nddata.mask is not None: mask_cutout = Cutout2D(np.asanyarray(nddata.mask), position, size, mode=mode, fill_value=fill_value) nddata_out.mask = mask_cutout.data return nddata_out
# Todo:voir pour la normalisation normalement il le fait tout seul mais pas sur... psf_image_SgrA = fill_acceptance_image(header, on.center(), psf_file_SgrA["theta"].to("deg"), psf_file_SgrA["psf_value"].data, psf_file_SgrA["theta"].to("deg")[-1]) source_center_SgrA = SkyCoord(359.9442, -0.0462, unit='deg', frame="galactic") # source_center_SgrA = SkyCoord.from_name("SgrA*") source_center_G0p9 = SkyCoord(0.868, 0.075, unit='deg', frame="galactic") psf_image_G0p9 = fill_acceptance_image(header, on.center(), psf_file_G0p9["theta"].to("deg"), psf_file_G0p9["psf_value"].data, psf_file_G0p9["theta"].to("deg")[-1]) psf_image_SgrA.writeto("psf_image_SgrA_" + str(E1) + "_" + str(E2) + ".fits", clobber=True) psf_image_G0p9.writeto("psf_image_G0p9_" + str(E1) + "_" + str(E2) + ".fits", clobber=True) load_psf("psf_SgrA", "psf_image_SgrA_" + str(E1) + "_" + str(E2) + ".fits") load_psf("psf_G0p9", "psf_image_G0p9_" + str(E1) + "_" + str(E2) + ".fits") # modele gauss pour sgrA centre sur SgrA mygaus_SgrA = normgauss2dint("SgrA") mygaus_SgrA.xpos, mygaus_SgrA.ypos = skycoord_to_pixel(source_center_SgrA, on.wcs) mygaus_SgrA.xpos.val += 0.5 mygaus_SgrA.ypos.val += 0.5 # Modele marge gaussienne a multiplie avec CS centre sur SgrA large_gaus = Gauss2D("Gauss_to_CS") large_gaus.xpos, large_gaus.ypos = skycoord_to_pixel(source_center_SgrA, on.wcs) large_gaus.fwhm = 100 central_gauss = Gauss2D("central_gauss") central_gauss.xpos, central_gauss.ypos = skycoord_to_pixel(source_center_SgrA, on.wcs) central_gauss.fwhm = 100 # modele gauss pour G0p9 centre sur G0p9 mygaus_G0p9 = normgauss2dint("G0p9") mygaus_G0p9.xpos, mygaus_G0p9.ypos = skycoord_to_pixel(source_center_G0p9, on.wcs) mygaus_G0p9.xpos.val += 0.5 mygaus_G0p9.ypos.val += 0.5
def to_pixel(self, wcs): center_x, center_y = skycoord_to_pixel(self.center, wcs=wcs) center = PixCoord(center_x, center_y) return PointPixelRegion(center)
def __init__(self, data, position, size, wcs=None, mode='trim', fill_value=np.nan, copy=False): """ Parameters ---------- data : `~numpy.ndarray` The 2D data array from which to extract the cutout array. position : tuple or `~astropy.coordinates.SkyCoord` The position of the cutout array's center with respect to the ``data`` array. The position can be specified either as a ``(x, y)`` tuple of pixel coordinates or a `~astropy.coordinates.SkyCoord`, in which case ``wcs`` is a required input. size : int, array-like, `~astropy.units.Quantity` The size of the cutout array along each axis. If ``size`` is a scalar number or a scalar `~astropy.units.Quantity`, then a square cutout of ``size`` will be created. If ``size`` has two elements, they should be in ``(ny, nx)`` order. Scalar numbers in ``size`` are assumed to be in units of pixels. ``size`` can also be a `~astropy.units.Quantity` object or contain `~astropy.units.Quantity` objects. Such `~astropy.units.Quantity` objects must be in pixel or angular units. For all cases, ``size`` will be converted to an integer number of pixels, rounding the the nearest integer. See the ``mode`` keyword for additional details on the final cutout size. .. note:: If ``size`` is in angular units, the cutout size is converted to pixels using the pixel scales along each axis of the image at the ``CRPIX`` location. Projection and other non-linear distortions are not taken into account. wcs : `~astropy.wcs.WCS`, optional A WCS object associated with the input ``data`` array. If ``wcs`` is not `None`, then the returned cutout object will contain a copy of the updated WCS for the cutout data array. mode : {'trim', 'partial', 'strict'}, optional The mode used for creating the cutout data array. For the ``'partial'`` and ``'trim'`` modes, a partial overlap of the cutout array and the input ``data`` array is sufficient. For the ``'strict'`` mode, the cutout array has to be fully contained within the ``data`` array, otherwise an `~astropy.nddata.utils.PartialOverlapError` is raised. In all modes, non-overlapping arrays will raise a `~astropy.nddata.utils.NoOverlapError`. In ``'partial'`` mode, positions in the cutout array that do not overlap with the ``data`` array will be filled with ``fill_value``. In ``'trim'`` mode only the overlapping elements are returned, thus the resulting cutout array may be smaller than the requested ``shape``. fill_value : number, optional If ``mode='partial'``, the value to fill pixels in the cutout array that do not overlap with the input ``data``. ``fill_value`` must have the same ``dtype`` as the input ``data`` array. copy : bool, optional If `False` (default), then the cutout data will be a view into the original ``data`` array. If `True`, then the cutout data will hold a copy of the original ``data`` array. Returns ------- result : `~astropy.nddata.utils.Cutout2D` A cutout object containing the 2D cutout data array and the updated WCS, if ``wcs`` is input. Examples -------- >>> import numpy as np >>> from astropy.nddata.utils import Cutout2D >>> from astropy import units as u >>> data = np.arange(20.).reshape(5, 4) >>> cutout1 = Cutout2D(data, (2, 2), (3, 3)) >>> print(cutout1.data) [[ 5. 6. 7.] [ 9. 10. 11.] [ 13. 14. 15.]] >>> print(cutout1.center_original) (2.0, 2.0) >>> print(cutout1.center_cutout) (1.0, 1.0) >>> print(cutout1.origin_original) (1, 1) >>> cutout2 = Cutout2D(data, (2, 2), 3) >>> print(cutout2.data) [[ 5. 6. 7.] [ 9. 10. 11.] [ 13. 14. 15.]] >>> size = u.Quantity([3, 3], u.pixel) >>> cutout3 = Cutout2D(data, (0, 0), size) >>> print(cutout3.data) [[ 0. 1.] [ 4. 5.]] >>> cutout4 = Cutout2D(data, (0, 0), (3 * u.pixel, 3)) >>> print(cutout4.data) [[ 0. 1.] [ 4. 5.]] >>> cutout5 = Cutout2D(data, (0, 0), (3, 3), mode='partial') >>> print(cutout5.data) [[ nan nan nan] [ nan 0. 1.] [ nan 4. 5.]] """ if isinstance(position, SkyCoord): if wcs is None: raise ValueError('wcs must be input if position is a ' 'SkyCoord') position = skycoord_to_pixel(position, wcs, mode='all') # (x, y) if np.isscalar(size): size = np.repeat(size, 2) # special handling for a scalar Quantity if isinstance(size, u.Quantity): size = np.atleast_1d(size) if len(size) == 1: size = np.repeat(size, 2) if len(size) > 2: raise ValueError('size must have at most two elements') shape = np.zeros(2).astype(int) pixel_scales = None # ``size`` can have a mixture of int and Quantity (and even units), # so evaluate each axis separately for axis, side in enumerate(size): if not isinstance(side, u.Quantity): shape[axis] = np.int(np.round(size[axis])) # pixels else: if side.unit == u.pixel: shape[axis] = np.int(np.round(side.value)) elif side.unit.physical_type == 'angle': if wcs is None: raise ValueError('wcs must be input if any element ' 'of size has angular units') if pixel_scales is None: pixel_scales = u.Quantity( proj_plane_pixel_scales(wcs), wcs.wcs.cunit[axis]) shape[axis] = np.int(np.round( (side / pixel_scales[axis]).decompose())) else: raise ValueError('shape can contain Quantities with only ' 'pixel or angular units') data = np.asanyarray(data) # reverse position because extract_array and overlap_slices # use (y, x), but keep the input position pos_yx = position[::-1] cutout_data, input_position_cutout = extract_array( data, tuple(shape), pos_yx, mode=mode, fill_value=fill_value, return_position=True) if copy: cutout_data = np.copy(cutout_data) self.data = cutout_data self.input_position_cutout = input_position_cutout[::-1] # (x, y) slices_original, slices_cutout = overlap_slices( data.shape, shape, pos_yx, mode=mode) self.slices_original = slices_original self.slices_cutout = slices_cutout self.shape = self.data.shape self.input_position_original = position self.shape_input = shape ((self.xmin_original, self.xmax_original), (self.ymin_original, self.ymax_original)) = self.bbox_original ((self.xmin_cutout, self.xmax_cutout), (self.ymin_cutout, self.ymax_cutout)) = self.bbox_cutout # the true origin pixel of the cutout array, including any # filled cutout values self._origin_original_true = ( self.origin_original[0] - self.slices_cutout[1].start, self.origin_original[1] - self.slices_cutout[0].start) if wcs is not None: self.wcs = deepcopy(wcs) self.wcs.wcs.crpix -= self._origin_original_true else: self.wcs = None
def listpixels(data, position, shape, subarray_indices=False, wcs=None): """ Return a `~astropy.table.Table` listing the ``(y, x)`` positions and ``data`` values for a subarray. Given a position of the center of the subarray, with respect to the large array, the array indices and values are returned. This function takes care of the correct behavior at the boundaries, where the small array is appropriately trimmed. Parameters ---------- data : array-like The input data. position : tuple (int) or `~astropy.coordinates.SkyCoord` The position of the subarray center with respect to the data array. The position can be specified either as an integer ``(y, x)`` tuple of pixel coordinates or a `~astropy.coordinates.SkyCoord`, in which case ``wcs`` is a required input. shape : tuple (int) The integer shape (``(ny, nx)``) of the subarray. subarray_indices : bool, optional If `True` then the returned positions are relative to the small subarray. If `False` (default) then the returned positions are relative to the ``data`` array. wcs : `~astropy.wcs.WCS`, optional The WCS transformation to use if ``position`` is a `~astropy.coordinates.SkyCoord`. Returns ------- table : `~astropy.table.Table` A table containing the ``x`` and ``y`` positions and data values. Notes ----- This function is decorated with `~astropy.nddata.support_nddata` and thus supports `~astropy.nddata.NDData` objects as input. Examples -------- >>> import numpy as np >>> from astroimtools import listpixels >>> np.random.seed(12345) >>> data = np.random.random((25, 25)) >>> tbl = listpixels(data, (8, 11), (3, 3)) >>> for col in tbl.colnames: ... tbl[col].info.format = '%.8g' # for consistent table output >>> tbl.pprint(max_lines=-1) x y value --- --- ----------- 10 7 0.75857204 11 7 0.069529666 12 7 0.70547344 10 8 0.8406625 11 8 0.46931469 12 8 0.56264343 10 9 0.034131584 11 9 0.23049655 12 9 0.22835371 """ if isinstance(position, SkyCoord): if wcs is None: raise ValueError('wcs must be input if positions is a SkyCoord') x, y = skycoord_to_pixel(position, wcs, mode='all') position = (y, x) data = np.asanyarray(data) slices_large, slices_small = overlap_slices(data.shape, shape, position) slices = slices_large yy, xx = np.mgrid[slices] values = data[yy, xx] if subarray_indices: slices = slices_small yy, xx = np.mgrid[slices] tbl = Table() tbl['x'] = xx.ravel() tbl['y'] = yy.ravel() tbl['value'] = values.ravel() return tbl
def __init__(self, data, position, size, wcs=None, mode='trim', fill_value=np.nan, copy=False): if isinstance(position, SkyCoord): if wcs is None: raise ValueError('wcs must be input if position is a ' 'SkyCoord') position = skycoord_to_pixel(position, wcs, mode='all') # (x, y) if np.isscalar(size): size = np.repeat(size, 2) # special handling for a scalar Quantity if isinstance(size, u.Quantity): size = np.atleast_1d(size) if len(size) == 1: size = np.repeat(size, 2) if len(size) > 2: raise ValueError('size must have at most two elements') shape = np.zeros(2).astype(int) pixel_scales = None # ``size`` can have a mixture of int and Quantity (and even units), # so evaluate each axis separately for axis, side in enumerate(size): if not isinstance(side, u.Quantity): shape[axis] = int(np.round(size[axis])) # pixels else: if side.unit == u.pixel: shape[axis] = int(np.round(side.value)) elif side.unit.physical_type == 'angle': if wcs is None: raise ValueError('wcs must be input if any element ' 'of size has angular units') if pixel_scales is None: pixel_scales = u.Quantity( proj_plane_pixel_scales(wcs), wcs.wcs.cunit[axis]) shape[axis] = int(np.round( (side / pixel_scales[axis]).decompose())) else: raise ValueError('shape can contain Quantities with only ' 'pixel or angular units') data = np.asanyarray(data) # reverse position because extract_array and overlap_slices # use (y, x), but keep the input position pos_yx = position[::-1] cutout_data, input_position_cutout = extract_array( data, tuple(shape), pos_yx, mode=mode, fill_value=fill_value, return_position=True) if copy: cutout_data = np.copy(cutout_data) self.data = cutout_data self.input_position_cutout = input_position_cutout[::-1] # (x, y) slices_original, slices_cutout = overlap_slices( data.shape, shape, pos_yx, mode=mode) self.slices_original = slices_original self.slices_cutout = slices_cutout self.shape = self.data.shape self.input_position_original = position self.shape_input = shape ((self.ymin_original, self.ymax_original), (self.xmin_original, self.xmax_original)) = self.bbox_original ((self.ymin_cutout, self.ymax_cutout), (self.xmin_cutout, self.xmax_cutout)) = self.bbox_cutout # the true origin pixel of the cutout array, including any # filled cutout values self._origin_original_true = ( self.origin_original[0] - self.slices_cutout[1].start, self.origin_original[1] - self.slices_cutout[0].start) if wcs is not None: self.wcs = deepcopy(wcs) self.wcs.wcs.crpix -= self._origin_original_true self.wcs.array_shape = self.data.shape if wcs.sip is not None: self.wcs.sip = Sip(wcs.sip.a, wcs.sip.b, wcs.sip.ap, wcs.sip.bp, wcs.sip.crpix - self._origin_original_true) else: self.wcs = None