def __init__(self, *args, **kwargs): self.verts = None self.weights = None if len(args) == 0: return None assert len( args) == 6, "args = im, im_header, locx, locy, ap_type, npix" im, im_header, locx, locy, ap_type, npix = args if ap_type == 'circular': radius = np.sqrt(npix / np.pi) _ap = circular_aperture(im, locx, locy, radius) elif ap_type == 'region': radius = np.sqrt(npix / np.pi) _ap = region_aperture(im, locx, locy, npix) else: assert False, "ap_type must be circular or aperture" verts = _ap.verts verts = pd.DataFrame(verts, columns=['x', 'y']) # Convert x, y to sky coordinates wcs = astropy.wcs.find_all_wcs(im_header, keysel=['binary'])[0] verts['ra'], verts['dec'] = wcs.all_pix2world(verts.x, verts.y, 0) self.verts = verts self.weights = _ap.weights self.npix = npix self.ap_type = ap_type self.name = "{}-{:.1f}".format(ap_type, npix)
def get_solid_angle(hdu): wcs = astropy.wcs.WCS(hdu.header) nx = hdu.header['NAXIS1'] ny = hdu.header['NAXIS2'] xx = numpy.arange(-0.5, nx, 1) xy = numpy.arange(0, ny, 1) yx = numpy.arange(0, nx, 1) yy = numpy.arange(-0.5, ny, 1) xX, xY = numpy.meshgrid(xx, xy) yX, yY = numpy.meshgrid(yx, yy) xXw, xYw = wcs.all_pix2world(xX, xY, 0) yXw, yYw = wcs.all_pix2world(yX, yY, 0) dXw = xXw[:, 1:] - xXw[:, :-1] dYw = yYw[1:] - yYw[:-1] A = abs(dXw * dYw) * deg * deg return A
def get_solid_angle(hdu): wcs = astropy.wcs.WCS(hdu.header) nx = hdu.header['NAXIS1'] ny = hdu.header['NAXIS2'] xx = numpy.arange(-0.5, nx, 1) xy = numpy.arange(0, ny, 1) yx = numpy.arange(0, nx, 1) yy = numpy.arange(-0.5, ny, 1) xX, xY = numpy.meshgrid(xx, xy) yX, yY = numpy.meshgrid(yx, yy) xXw, xYw = wcs.all_pix2world(xX, xY, 0) yXw, yYw = wcs.all_pix2world(yX, yY, 0) dXw = xXw[:,1:] - xXw[:,:-1] dYw = yYw[1:] - yYw[:-1] A = abs(dXw * dYw) * deg * deg return A
def pix2world(self, x, y): """ Transform pixel coordinates to world coordinates. Return a two-element tuple with the right ascension and declination to which the specified x- and y-coordinates correspond in the FITS image. Raises NoWCSInformationError if the header of the FITS image does not contain an astrometric solution -- i.e., if the astropy.wcs.WCS class is unable to recognize it as such. This is something that should very rarely happen, and almost positively caused by non-standard systems or FITS keywords. """ coords = (x, y) wcs = self._get_wcs() pixcrd = numpy.array([coords]) ra, dec = wcs.all_pix2world(pixcrd, 1)[0] # We could use astropy.wcs.WCS.has_celestial for this, but as of today # [Tue Jan 20 2015] it is only available in the development version of # Astropy. Therefore, do a simple (but in theory enough) check: if the # header does not contain an astrometric solution, WCS.all_pix2world() # will not be able to transform the pixel coordinates, and therefore # will return the same coordinates as the FITSImage.center attribute. if (ra, dec) == coords: msg = ("{0}: the header of the FITS image does not seem to " "contain WCS information. You may want to make sure that " "the image has been solved astrometrically, for example " "with the 'astrometry' LEMON command.".format(self.path)) raise NoWCSInformationError(msg) return ra, dec
def __init__(self, fname, fill_value=0.0): with fits.open(fname) as hdul: rawdata = hdul[0].data wcs = astropy.wcs.WCS(hdul[0].header) # WL is in Angstroms -> to microns all_wl = wcs.all_pix2world(range(hdul[0].data.size), 0) self._interp = ii.interp1d(all_wl[0] / 1e4, numpy.abs(rawdata), bounds_error=False, fill_value=fill_value)
def pc_gaia_cat(exp, mag_thresh=None, edge_pad_pix=0, nmp=None, max_n_stars=3000, pm_corr=False): print('Reading Gaia DR2 catalogs...') xgrid, ygrid = xy_subsamp_grid() wcs = exp.wcs # last arg is 0 rather than 1 because that's what agrees with IDL ra, dec = wcs.all_pix2world(xgrid, ygrid, 0) cat = read_gaia_cat(ra, dec, nmp=nmp) if mag_thresh is not None: keep = (cat['PHOT_G_MEAN_MAG'] <= mag_thresh) assert(np.sum(keep) > 0) cat = cat[keep] if pm_corr: gaia_pm_corr(cat, exp.header['MJD-OBS']) x_gaia_guess, y_gaia_guess = wcs.all_world2pix(cat['RA'], cat['DEC'], 0) par = common.pc_params() keep = (x_gaia_guess > edge_pad_pix) & (y_gaia_guess > edge_pad_pix) & (x_gaia_guess < (par['nx'] - 1 - edge_pad_pix)) & (y_gaia_guess < (par['ny'] - 1 - edge_pad_pix)) assert(np.sum(keep) > 0) cat = cat[keep] x_gaia_guess = x_gaia_guess[keep] y_gaia_guess = y_gaia_guess[keep] cat = Table(cat) cat['x_gaia_guess'] = x_gaia_guess cat['y_gaia_guess'] = y_gaia_guess if len(cat) > max_n_stars: # retain brightest max_n_stars # it'd be better to do this cut based on # 'G_PRIME', the color-corrected pointing # camera Gaia-based mag # in the future can evaluate trying to spread the # selected max_n_stars evenly across quadrants # (could imagine a pathological case with e.g., a # globular cluster in the FOV) print('Restricting to the brightest ' + str(max_n_stars) + \ ' of ' + str(len(cat)) + ' stars') sind = np.argsort(cat['PHOT_G_MEAN_MAG']) cat = cat[sind[0:max_n_stars]] return cat
def indices_to_velocity( indices: np.ndarray, base_wavelength: u.Quantity, wcs: astropy.wcs.WCS, wscale: int ): wavelengths, _, _ = wcs.all_pix2world(wscale * indices, 0, 0, 0) wavelengths = wavelengths << u.AA velocities = astropy.constants.c * (wavelengths - base_wavelength) / base_wavelength velocities = velocities.to(u.km / u.s) return velocities
def corner_catalog_1ext(telra, teldec, extname): # LL -> UL -> UR -> LR xpix, ypix = util.ci_corner_pixel_coords() wcs = ci_wcs.nominal_tan_wcs(telra, teldec, extname) ra, dec = wcs.all_pix2world(xpix, ypix, 0) name = [(extname + '_' + corner) for corner in ['LL', 'UL', 'UR', 'LR']] tab = Table([ra, dec, name], names=('ra', 'dec', 'name')) return tab
def test_qphot_run_proper_motions(self): # Do photometry on Barnard's Star, the star with the largest-known # proper motion relative to the Solar System. Its coordinates are # J2000, but the DSS image dates back from 1993: qphot.run() must # correct the right ascension and declination before doing photometry, # adjusting for the change in its position over those seven years. barnard_path = "./test/test_data/fits/Barnard's_Star.fits" barnard = astromatic.Coordinates(269.452075, 4.693391, -0.79858, 10.32812) nbits = get_nbits() if nbits == 64: expected_output = ( # x y mag sum flux stdev qphot.QPhotResult(440.947, 382.595, 17.245, 8563646, 5311413, 1039.844)) else: assert nbits == 32 expected_output = ( # <<>> qphot.QPhotResult(440.947, 382.595, 17.245, 8563646, 5312029, 1039.844)) path = fix_DSS_image(barnard_path) with test.test_fitsimage.FITSImage(path) as img: # Fix incorrect date in FITS header: '1993-07-26T04:87:00'. # Subtract sixty minutes and add one hour: 4h87m == 5h27m keyword = 'DATE-OBS' assert img.read_keyword(keyword) == '1993-07-26T04:87:00' img.update_keyword(keyword, '1993-07-26T05:27:00') # The proper-motion corrected coordinates year = img.year(exp_keyword='EXPOSURE') expected_coordinates = barnard.get_exact_coordinates(year) result = qphot.run(img, [barnard], **self.QPHOT_KWARGS)[0] self.assertEqual(result, expected_output) # Transform the pixel coordinates that IRAF's qphot outputs for # each measured object to celestial coordinates. This allows us to # make sure that photometry has been effectively done on the right, # proper-motion corrected coordinates. wcs = astropy.wcs.WCS(img._header) ra, dec = wcs.all_pix2world(result.x, result.y, 1) f = self.assertAlmostEqual f(ra, expected_coordinates.ra, delta=1e-3) # delta = 0.24 arcsec f(dec, expected_coordinates.dec, delta=1e-3) # delta = 3.6 arcsec
def test_qphot_run_proper_motions(self): # Do photometry on Barnard's Star, the star with the largest-known # proper motion relative to the Solar System. Its coordinates are # J2000, but the DSS image dates back from 1993: qphot.run() must # correct the right ascension and declination before doing photometry, # adjusting for the change in its position over those seven years. barnard_path = "./test/test_data/fits/Barnard's_Star.fits" barnard = astromatic.Coordinates(269.452075, 4.693391, -0.79858, 10.32812) nbits = methods.get_nbits() if nbits == 64: expected_output = ( # x y mag sum flux stdev qphot.QPhotResult(440.947, 382.595, 17.245, 8563646, 5311413, 1039.844)) else: assert nbits == 32 expected_output = ( # <<>> qphot.QPhotResult(440.947, 382.595, 17.245, 8563646, 5312029, 1039.844)) path = fix_DSS_image(barnard_path) with test.test_fitsimage.FITSImage(path) as img: # Fix incorrect date in FITS header: '1993-07-26T04:87:00'. # Subtract sixty minutes and add one hour: 4h87m == 5h27m keyword = 'DATE-OBS' assert img.read_keyword(keyword) == '1993-07-26T04:87:00' img.update_keyword(keyword, '1993-07-26T05:27:00') # The proper-motion corrected coordinates year = img.year(exp_keyword = 'EXPOSURE') expected_coordinates = barnard.get_exact_coordinates(year) result = qphot.run(img, [barnard], **self.QPHOT_KWARGS)[0] self.assertEqual(result, expected_output) # Transform the pixel coordinates that IRAF's qphot outputs for # each measured object to celestial coordinates. This allows us to # make sure that photometry has been effectively done on the right, # proper-motion corrected coordinates. wcs = astropy.wcs.WCS(img._header) ra, dec = wcs.all_pix2world(result.x, result.y, 1) f = self.assertAlmostEqual f(ra, expected_coordinates.ra, delta = 1e-3) # delta = 0.24 arcsec f(dec, expected_coordinates.dec, delta = 1e-3) # delta = 3.6 arcsec
def pix2world(pix, header, axis): wcs = astropy.wcs.WCS(header) cunit = astropy.units.Unit(header.get('CUNIT%d' % (axis))) pix = numpy.array(pix) if pix.ndim == 0: plen = 1 else: plen = len(pix) pass p = numpy.zeros([plen, wcs.naxis]) p[:, axis - 1] = pix world = wcs.all_pix2world(p, 0)[:, axis - 1] * cunit if pix.ndim == 0: return world[0] return world
def convertImgCoords(coords, image, to_pix=None, to_radec=None): ''' Transform the WCS info in an image Convert image pixel coordinates to RA/Dec based on PNG image metadata or vice_versa Parameters: coords (tuple): The input coordindates to transform image (str): The full path to the image to_pix (bool): Set to convert to pixel coordinates to_radec (bool): Set to convert to RA/Dec coordinates Returns: newcoords (tuple): Tuple of either (x, y) pixel coordinates or (RA, Dec) coordinates ''' try: wcs = getWCSFromPng(image) except Exception as e: raise MarvinError('Cannot get wcs info from image {0}: {1}'.format( image, e)) if to_radec: try: newcoords = wcs.all_pix2world([coords], 1)[0] except AttributeError as e: raise MarvinError( 'Cannot convert coords to RA/Dec. No wcs! {0}'.format(e)) if to_pix: try: newcoords = wcs.all_world2pix([coords], 1)[0] except AttributeError as e: raise MarvinError( 'Cannot convert coords to image pixels. No wcs! {0}'.format( e)) return newcoords
def pix2world(pix, header, axis): wcs = astropy.wcs.WCS(header) cunit = astropy.units.Unit(header.get('CUNIT%d'%(axis))) pix = numpy.array(pix) if pix.ndim == 0: plen = 1 else: plen = len(pix) pass p = numpy.zeros([plen, wcs.naxis]) p[:,axis-1] = pix world = wcs.all_pix2world(p, 0)[:,axis-1] * cunit if pix.ndim == 0: return world[0] return world
def center_wcs(self): """ Return the world coordinates of the central pixel of the image. Transform the pixel coordinates of the center of the image to world coordinates. Return a two-element tuple with the right ascension and declination. Most of the time the celestial coordinates of the field center can be found in the FITS header, but (a) these keywords are non-standard and (b) it would not be the first time that we come across incorrect (or, at least, not as accurate as we would expect) coordinates. Instead of blindly trusting the FITS header, compute these values ourselves -- provided, of course, that our images are calibrated astrometrically. Raises NoWCSInformationError if the header of the FITS image does not contain an astrometric solution -- i.e., if the astropy.wcs.WCS class is unable to recognize it as such. This is something that should very rarely happen, and almost positively caused by non-standard systems or FITS keywords. """ center = tuple(self.center) wcs = self._get_wcs() pixcrd = numpy.array([center]) ra, dec = wcs.all_pix2world(pixcrd, 1)[0] # We could use astropy.wcs.WCS.has_celestial for this, but as of today # [Tue Jan 20 2015] it is only available in the development version of # Astropy. Therefore, do a simple (but in theory enough) check: if the # header does not contain an astrometric solution, WCS.all_pix2world() # will not be able to transform the pixel coordinates, and therefore # will return the same coordinates as the FITSImage.center attribute. if (ra, dec) == center: msg = ("{0}: the header of the FITS image does not seem to " "contain WCS information. You may want to make sure that " "the image has been solved astrometrically, for example " "with the 'astrometry' LEMON command.".format(self.path)) raise NoWCSInformationError(msg) return ra, dec
def get_coordinate_grid_gal(hdu, frame='fk5'): """Returns world coordinate grid of an image [Image has to be in J2000 at the moment] Gives both RA/DEC and GALACTIC Input: hdu = Input fits hdu in J2000 coordinates frame = Input reference frame; default is fk5 Output: (coordinate_grid_l, coordinate_grid_b) = coordinates of each array index in GALACTIC (coordinate_grid_ra, coordinate_grid_dec) = coordinates of each array index in RA/DEC """ wcs = astropy.wcs.WCS(hdu) shape = hdu.shape index_grid_x = np.empty(shape) index_grid_y = np.empty(shape) coordinate_grid_ra = np.empty(shape) coordinate_grid_dec = np.empty(shape) coordinate_grid_l = np.empty(shape) coordinate_grid_b = np.empty(shape) for row in range(shape[1]): for col in range(shape[0]): index_grid_x[col, row] = row index_grid_y[col, row] = col coordinate_grid_radec = wcs.all_pix2world(index_grid_x, index_grid_y, 0) coordinate_grid_ra, coordinate_grid_dec = coordinate_grid_radec lb = coordinates.SkyCoord(coordinate_grid_ra, coordinate_grid_dec, frame=frame, unit=(au.deg, au.deg)).galactic coordinate_grid_l = lb.l.deg coordinate_grid_b = lb.b.deg return (coordinate_grid_l, coordinate_grid_b), (coordinate_grid_ra, coordinate_grid_dec)
def update_flux_limits(header, pixlims, wcs=None, ref=1): """Update keywords used for flux limits""" pixlim_coms = ["Start of valid flux calibration", "End of valid flux calibration", 'Start of region with at least one fiber', 'End of region with at least one fiber', 'Start of region with all fibers', 'End of region with all fibers', ] if ref not in [0, 1]: raise ValueError("ref must be 0 or 1") off = (ref + 1) % 2 if wcs is None: wcs = astropy.wcs.WCS(header) for key, com in zip(PIXLIM_KEYS, pixlim_coms): header[key] = (pixlims[key] + off, com) r1 = numpy.array([pixlims[key] for key in PIXLIM_KEYS]) r2 = numpy.zeros_like(r1) lm = numpy.array([r1, r2]) # Values are 0-based wavelen_ = wcs.all_pix2world(lm.T, ref) if wcs.wcs.cunit[0] == u.dimensionless_unscaled: # CUNIT is empty, assume Angstroms wavelen = wavelen_[:, 0] * u.AA else: wavelen = wavelen_[:, 0] * wcs.wcs.cunit[0] for idx, (key, com) in enumerate(zip(WAVLIM_KEYS, pixlim_coms)): header[key] = (wavelen[idx].to(u.AA).value, com) return header
def xy2rd(self, wcs, pixx, pixy): """ Transform input pixel positions into sky positions in the WCS provided. """ return wcs.all_pix2world(pixx, pixy, 1)
def calcNewEdges(wcs, shape): """ This method will compute sky coordinates for all the pixels around the edge of an image AFTER applying the geometry model. Parameters ---------- wcs : obj HSTWCS object for image shape : tuple numpy shape tuple for size of image Returns ------- border : arr array which contains the new positions for all pixels around the border of the edges in alpha,dec """ naxis1 = shape[1] naxis2 = shape[0] # build up arrays for pixel positions for the edges # These arrays need to be: array([(x,y),(x1,y1),...]) numpix = naxis1*2 + naxis2*2 border = np.zeros(shape=(numpix,2),dtype=np.float64) # Now determine the appropriate values for this array # We also need to account for any subarray offsets xmin = 1. xmax = naxis1 ymin = 1. ymax = naxis2 # Build range of pixel values for each side # Add 1 to make them consistent with pixel numbering in IRAF # Also include the LTV offsets to represent position in full chip # since the model works relative to full chip positions. xside = np.arange(naxis1) + xmin yside = np.arange(naxis2) + ymin #Now apply them to the array to generate the appropriate tuples #bottom _range0 = 0 _range1 = naxis1 border[_range0:_range1,0] = xside border[_range0:_range1,1] = ymin #top _range0 = _range1 _range1 = _range0 + naxis1 border[_range0:_range1,0] = xside border[_range0:_range1,1] = ymax #left _range0 = _range1 _range1 = _range0 + naxis2 border[_range0:_range1,0] = xmin border[_range0:_range1,1] = yside #right _range0 = _range1 _range1 = _range0 + naxis2 border[_range0:_range1,0] = xmax border[_range0:_range1,1] = yside edges = wcs.all_pix2world(border[:,0],border[:,1],1) return edges
def make_radio_combination_signature(radio_annotation, wcs, atlas_positions, subject, pix_offset): """Generates a unique signature for a radio annotation. radio_annotation: 'radio' dictionary from a classification. wcs: World coordinate system associated with the ATLAS image. atlas_positions: [[RA, DEC]] NumPy array. subject: RGZ subject dict. pix_offset: (x, y) pixel position of this radio subject on the ATLAS image. -> Something immutable """ from . import rgz_data as data # TODO(MatthewJA): This only works on ATLAS. Generalise. # My choice of immutable object will be stringified crowdastro ATLAS # indices. zooniverse_id = subject['zooniverse_id'] subject_fits = data.get_radio_fits(subject) subject_wcs = astropy.wcs.WCS(subject_fits.header) atlas_ids = [] x_offset, y_offset = pix_offset for c in radio_annotation.values(): # Note that the x scale is not the same as the IR scale, but the scale # factor is included in the annotation, so I have multiplied this out # here for consistency. scale_width = c.get('scale_width', '') scale_height = c.get('scale_height', '') if scale_width: scale_width = float(scale_width) else: # Sometimes, there's no scale, so I've included a default scale. scale_width = config['surveys']['atlas']['scale_width'] if scale_height: scale_height = float(scale_height) else: scale_height = config['surveys']['atlas']['scale_height'] # These numbers are in terms of the PNG images, so I need to multiply by # the click-to-fits ratio. scale_width *= config['surveys']['atlas']['click_to_fits_x'] scale_height *= config['surveys']['atlas']['click_to_fits_y'] subject_bbox = [ [ float(c['xmin']) * scale_width, float(c['xmax']) * scale_width, ], [ float(c['ymin']) * scale_height, float(c['ymax']) * scale_height, ], ] # ...and by the mosaic ratio. There's probably double-up here, but this # makes more sense. scale_width *= config['surveys']['atlas']['mosaic_scale_x'] scale_height *= config['surveys']['atlas']['mosaic_scale_y'] # Get the bounding box of the radio source in pixels. # Format: [xs, ys] bbox = [ [ float(c['xmin']) * scale_width, float(c['xmax']) * scale_width, ], [ float(c['ymin']) * scale_height, float(c['ymax']) * scale_height, ], ] assert bbox[0][0] < bbox[0][1] assert bbox[1][0] < bbox[1][1] # Convert the bounding box into RA/DEC. bbox = wcs.all_pix2world(bbox[0] + x_offset, bbox[1] + y_offset, FITS_CONVENTION) subject_bbox = subject_wcs.all_pix2world(subject_bbox[0], subject_bbox[1], FITS_CONVENTION) # TODO(MatthewJA): Remove (or disable) this sanity check. # The bbox is backwards along the x-axis for some reason. bbox[0] = bbox[0][::-1] assert bbox[0][0] < bbox[0][1] assert bbox[1][0] < bbox[1][1] bbox = numpy.array(bbox) # What is this radio source called? Check if we have an object in the # bounding box. We'll cache these results because there is a lot of # overlap. cache_key = tuple(tuple(b) for b in bbox) if cache_key in bbox_cache_: index = bbox_cache_[cache_key] else: x_gt_min = atlas_positions[:, 0] >= bbox[0, 0] x_lt_max = atlas_positions[:, 0] <= bbox[0, 1] y_gt_min = atlas_positions[:, 1] >= bbox[1, 0] y_lt_max = atlas_positions[:, 1] <= bbox[1, 1] within = numpy.all([x_gt_min, x_lt_max, y_gt_min, y_lt_max], axis=0) indices = numpy.where(within)[0] if len(indices) == 0: logging.debug('Skipping radio source not in catalogue for ' '%s', zooniverse_id) continue else: if len(indices) > 1: logging.debug('Found multiple (%d) ATLAS matches ' 'for %s', len(indices), zooniverse_id) index = indices[0] bbox_cache_[cache_key] = index atlas_ids.append(str(index)) atlas_ids.sort() if not atlas_ids: raise CatalogueError('No catalogued radio sources.') return ';'.join(atlas_ids)
(check_sources, check_dead, check_sky, check_source_sky) = check_hdulists pyfits.HDUList(check_sources).writeto("check_sources.fits", overwrite=True) pyfits.HDUList(check_dead).writeto("check_dead.fits", overwrite=True) pyfits.HDUList(check_sky).writeto("check_sky.fits", overwrite=True) pyfits.HDUList(check_source_sky).writeto("check_source_sky.fits", overwrite=True) # src_data.info() # convert polygon center coordinates from native pixels to Ra/Dec #src_data.info() #print(src_data['center_x'].astype(numpy.float).to_numpy()) _ra, _dec = wcs.all_pix2world( src_data['center_x'].astype(numpy.float).to_numpy(), src_data['center_y'].astype(numpy.float).to_numpy(), 1) src_data['center_ra'] = _ra src_data['center_dec'] = _dec #print(radec) # apply flux calibrations calib_factor = 1.0 if (name in calibration_factors): calib_factor = calibration_factors[name] named_logger.info("Apply calibration factor: %g" % (calib_factor)) sky_error = (src_data['src_area'] * src_data['sky_var']).astype( numpy.float).to_numpy() src_error = gain * src_data['src_flux'].astype(numpy.float).to_numpy() flx_error = numpy.fabs(src_error) + sky_error * gain**2
def detect_sources(image, cat_name=None): """ A generator of (ra, dec) tuples (PROOF OF CONCEPT) """ log.debug("Reading FITS file") with astropy.io.fits.open(image) as hdulist: data = hdulist[0].data header = hdulist[0].header with warnings.catch_warnings(): warnings.simplefilter("ignore") log.info("Loading WCS data") wcs = astropy.wcs.WCS(header) try: log.info("Estimating background") background = sextractor.Background(data) except ValueError: # Fix for error "Input array with dtype '>f4' has non-native # byte order. Only native byte order arrays are supported". log.debug("Converting data to native byte order") data = data.byteswap(True).newbyteorder() background = sextractor.Background(data) log.info("Subtracting background") background.subfrom(data) # in-place # Global "average" RMS of background log.info("Detecting sources") rms_ntimes = 1.5 while True: try: threshold = rms_ntimes * background.globalrms objects = sextractor.extract(data, threshold) except Exception as e: # Fix for error "internal pixel buffer full: The limit of 300000 # active object pixels over the detection threshold was reached. # Check that the image is background subtracted and the detection # threshold is not too low. detection threshold". If a different # exception is raised, just re-raise it. if "threshold is not too low" in str(e): rms_ntimes *= 1.25 log.debug("Internal pixel buffer full") log.debug("Retrying with threshold {0:.2} times RMS of background".format(rms_ntimes)) else: raise else: break pixel_coords = numpy.empty([0,2]) print "len(objects) = ", len(objects) print "Image = ", image sys.exit() # https://github.com/kbarbary/sep/blob/master/sep.pyx#L555 log.info("Transforming pixel to celestial coordinates") for index in range(len(objects)): x, y = objects[index]['x'], objects[index]['y'] pixel_coords = numpy.row_stack(pixel_coords, (x, y)) print pixel_coords.shape # If cat_name is present, save the coordinates to a file if cat_name: fd = open(cat_name, 'w') for ra, dec in list(wcs.all_pix2world(pixel_coords[:,0], pixel_coords[:,1], 0)): fd.write( "{0} {1} \n ".format(ra, dec) ) fd.close() return list(wcs.all_pix2world(pixel_coords[:,0], pixel_coords[:,1], 0))
basedir, uncompressed=args.uncompressed) else: print('No bright star catalog, not marking bright stars.') res = process(im, sqivar, flag, psf, refit_psf=args.refit_psf, verbose=args.verbose, nx=4, ny=4, derivcentroids=True) outfn = args.outfn[0] x = (res[0])['x'] y = (res[0])['y'] wcs = wcs.WCS(hdr) ra, dec = wcs.all_pix2world(y, x, 0) import numpy.lib.recfunctions as rfn cat = rfn.append_fields(res[0], ['ra', 'dec'], [ra, dec]) fits.writeto(outfn, cat) fits.append(outfn, res[1]) fits.append(outfn, res[2]) psfext = numpy.array([tpsf(0, 0, stampsz=19) for tpsf in res[3]]) fits.append(outfn, psfext)
def make_radio_combination_signature(radio_annotation, wcs, atlas_positions, subject, pix_offset): """Generates a unique signature for a radio annotation. radio_annotation: 'radio' dictionary from a classification. wcs: World coordinate system associated with the ATLAS image. atlas_positions: [[RA, DEC]] NumPy array. subject: RGZ subject dict. pix_offset: (x, y) pixel position of this radio subject on the ATLAS image. -> Something immutable """ from . import rgz_data as data # TODO(MatthewJA): This only works on ATLAS. Generalise. # My choice of immutable object will be stringified crowdastro ATLAS # indices. zooniverse_id = subject['zooniverse_id'] subject_fits = data.get_radio_fits(subject) subject_wcs = astropy.wcs.WCS(subject_fits.header) atlas_ids = [] x_offset, y_offset = pix_offset for c in radio_annotation.values(): # Note that the x scale is not the same as the IR scale, but the scale # factor is included in the annotation, so I have multiplied this out # here for consistency. scale_width = c.get('scale_width', '') scale_height = c.get('scale_height', '') if scale_width: scale_width = float(scale_width) else: # Sometimes, there's no scale, so I've included a default scale. scale_width = config['surveys']['atlas']['scale_width'] if scale_height: scale_height = float(scale_height) else: scale_height = config['surveys']['atlas']['scale_height'] # These numbers are in terms of the PNG images, so I need to multiply by # the click-to-fits ratio. scale_width *= config['surveys']['atlas']['click_to_fits_x'] scale_height *= config['surveys']['atlas']['click_to_fits_y'] subject_bbox = [ [ float(c['xmin']) * scale_width, float(c['xmax']) * scale_width, ], [ float(c['ymin']) * scale_height, float(c['ymax']) * scale_height, ], ] # ...and by the mosaic ratio. There's probably double-up here, but this # makes more sense. scale_width *= config['surveys']['atlas']['mosaic_scale_x'] scale_height *= config['surveys']['atlas']['mosaic_scale_y'] # Get the bounding box of the radio source in pixels. # Format: [xs, ys] bbox = [ [ float(c['xmin']) * scale_width, float(c['xmax']) * scale_width, ], [ float(c['ymin']) * scale_height, float(c['ymax']) * scale_height, ], ] assert bbox[0][0] < bbox[0][1] assert bbox[1][0] < bbox[1][1] # Convert the bounding box into RA/DEC. bbox = wcs.all_pix2world(bbox[0] + x_offset, bbox[1] + y_offset, FITS_CONVENTION) subject_bbox = subject_wcs.all_pix2world(subject_bbox[0], subject_bbox[1], FITS_CONVENTION) # TODO(MatthewJA): Remove (or disable) this sanity check. # The bbox is backwards along the x-axis for some reason. bbox[0] = bbox[0][::-1] assert bbox[0][0] < bbox[0][1] assert bbox[1][0] < bbox[1][1] bbox = numpy.array(bbox) # What is this radio source called? Check if we have an object in the # bounding box. We'll cache these results because there is a lot of # overlap. cache_key = tuple(tuple(b) for b in bbox) if cache_key in bbox_cache_: index = bbox_cache_[cache_key] else: x_gt_min = atlas_positions[:, 0] >= bbox[0, 0] x_lt_max = atlas_positions[:, 0] <= bbox[0, 1] y_gt_min = atlas_positions[:, 1] >= bbox[1, 0] y_lt_max = atlas_positions[:, 1] <= bbox[1, 1] within = numpy.all([x_gt_min, x_lt_max, y_gt_min, y_lt_max], axis=0) indices = numpy.where(within)[0] if len(indices) == 0: logging.debug( 'Skipping radio source not in catalogue for ' '%s', zooniverse_id) continue else: if len(indices) > 1: logging.debug( 'Found multiple (%d) ATLAS matches ' 'for %s', len(indices), zooniverse_id) index = indices[0] bbox_cache_[cache_key] = index atlas_ids.append(str(index)) atlas_ids.sort() if not atlas_ids: raise CatalogueError('No catalogued radio sources.') return ';'.join(atlas_ids)
(_median - 3 * _sigma)) use_for_fit[bad] = False area_ellipse = numpy.pi * a * b # break fit_results.append([a, b, theta, x0, y0, area, area_ellipse]) df = pandas.DataFrame(data=fit_results, columns=[ 'A', 'B', 'theta', 'x0', 'y0', 'area_contours', "area_ellipse" ]) wcs = astropy.wcs.WCS(header) radec = wcs.all_pix2world(df[['x0', 'y0']].to_numpy(), 0) print(radec) df.loc[:, ['ra', 'dec']] = radec df.loc[:, 'filename'] = fn df.loc[:, ['a_arcsec', 'b_arcsec']] = df[['A', 'B' ]].to_numpy() * pixelscale df.loc[:, 'area_error'] = (df['area_ellipse'] - df['area_contours']) / df['area_contours'] df.loc[:, 'good_fit'] = numpy.fabs(df['area_error']) < 0.05 df.info() if (master_df is None): master_df = df else: master_df = master_df.append(df, ignore_index=False)
# now write the region file again, this time using WCS instead of pixel information hdr = img_hdu[0].header try: pixelscale = numpy.hypot(hdr['CD1_1'], hdr['CD1_2']) * 3600. # in arcsec/pixel except: try: pixelscale = hdr['CDELT1'] * 3600 except: pixelscale = 1. pass # convert all x/y into ra/dec wcs = astropy.wcs.WCS(hdr) (ra0, dec0) = wcs.all_pix2world(df['x0'], df['y0'], 0) # print(ra_dec) df['ra0'] = ra0 #_dec[:,0] df['dec0'] = dec0 #ra_dec[:,1] ds9_reg_fn = output_basename + "_ellipses_wcs.reg" with open(ds9_reg_fn, "w") as ds9_reg: print("""\ # Region file format: DS9 version 4.1 global color=green dashlist=8 3 width=1 font="helvetica 10 normal roman" select=1 highlite=1 dash=0 fixed=0 edit=1 move=1 delete=1 include=1 source=1 fk5""", file=ds9_reg) for index, e in df.iterrows(): # print(e) print('ellipse(%f,%f,%f",%f",%f) # %s' % (e['ra0'], e['dec0'], e['sma'] * pixelscale, (1. - e['ellipticity']) * e['sma'] * pixelscale, e['pa'],