def test_fit_wcs_from_points_CRPIX_bounds(): # Test CRPIX bounds requirement wcs_str = """ WCSAXES = 2 / Number of coordinate axes CRPIX1 = 1045.0 / Pixel coordinate of reference point CRPIX2 = 1001.0 / Pixel coordinate of reference point PC1_1 = 0.00056205870415378 / Coordinate transformation matrix element PC1_2 = -0.00569181083243 / Coordinate transformation matrix element PC2_1 = 0.0056776810932466 / Coordinate transformation matrix element PC2_2 = 0.0004208048403273 / Coordinate transformation matrix element CDELT1 = 1.0 / [deg] Coordinate increment at reference point CDELT2 = 1.0 / [deg] Coordinate increment at reference point CUNIT1 = 'deg' / Units of coordinate increment and value CUNIT2 = 'deg' / Units of coordinate increment and value CTYPE1 = 'RA---TAN' / Right ascension, gnomonic projection CTYPE2 = 'DEC--TAN' / Declination, gnomonic projection CRVAL1 = 104.57797893504 / [deg] Coordinate value at reference point CRVAL2 = -74.195502593322 / [deg] Coordinate value at reference point LONPOLE = 180.0 / [deg] Native longitude of celestial pole LATPOLE = -74.195502593322 / [deg] Native latitude of celestial pole TIMESYS = 'TDB' / Time scale TIMEUNIT= 'd' / Time units DATEREF = '1858-11-17' / ISO-8601 fiducial time MJDREFI = 0.0 / [d] MJD of fiducial time, integer part MJDREFF = 0.0 / [d] MJD of fiducial time, fractional part DATE-OBS= '2019-03-27T03:30:13.832Z' / ISO-8601 time of observation MJD-OBS = 58569.145993426 / [d] MJD of observation MJD-OBS = 58569.145993426 / [d] MJD at start of observation TSTART = 1569.6467941661 / [d] Time elapsed since fiducial time at start DATE-END= '2019-03-27T04:00:13.831Z' / ISO-8601 time at end of observation MJD-END = 58569.166826748 / [d] MJD at end of observation TSTOP = 1569.6676274905 / [d] Time elapsed since fiducial time at end TELAPSE = 0.02083332443 / [d] Elapsed time (start to stop) TIMEDEL = 0.020833333333333 / [d] Time resolution TIMEPIXR= 0.5 / Reference position of timestamp in binned data RADESYS = 'ICRS' / Equatorial coordinate system """ wcs_header = fits.Header.fromstring(wcs_str, sep='\n') ffi_wcs = WCS(wcs_header) yi, xi = (1000, 1000) y, x = (10, 200) center_coord = SkyCoord(ffi_wcs.all_pix2world([[xi + x // 2, yi + y // 2]], 0), unit='deg')[0] ypix, xpix = [arr.flatten() for arr in np.mgrid[xi:xi + x, yi:yi + y]] world_pix = SkyCoord(*ffi_wcs.all_pix2world(xpix, ypix, 0), unit='deg') fit_wcs = fit_wcs_from_points((ypix, xpix), world_pix, proj_point='center') assert (fit_wcs.wcs.crpix.astype(int) == [1100, 1005]).all() assert fit_wcs.pixel_shape == (200, 10)
def recomputeXylsPixelPositions(originalXylsPath, originalWcsPath, newWcsPathOrHeader): """ Return pixel coordinates valid for `newWcsPathOrHeader` for the reference stars found in `originalXylsPath` (belonging to `originalWcsPath`). :rtype: tuple (x,y) with x and y being ndarrays """ # Step 1: compute RA,Dec of reference stars (as this is not stored in .xyls) originalWCS = WCS(readHeader(originalWcsPath)) x, y = readXy(originalXylsPath) ra, dec = originalWCS.all_pix2world(x, y, 0) # Step 2: compute pixel positions of reference stars in new WCS solution if isinstance(newWcsPathOrHeader, string_types): newWCS = WCS(readHeader(newWcsPathOrHeader)) else: newWCS = WCS(newWcsPathOrHeader) # all_world2pix raised a NoConvergenc error # As we don't use SIP, we don't need to use all_world2pix. # wcs_world2pix doesn't support any distortion correction. xNew, yNew = newWCS.wcs_world2pix(ra, dec, 0) return xNew, yNew
def pix2world(wcsHeader, width, height, startX=0, startY=0, corner=True, ascartesian=False): """ Calculate RA, Dec coordinates of a given pixel coordinate rectangle. ra, dec = pix2world(..) ra and dec are indexed as [y,x] Each array element contains the RA,Dec coords of the top left corner of the given pixel if corner==True, otherwise the coords of the pixel center. If corner==True, an additional row and column exists at the bottom and right so that it is possible to get the bottom and right corner values for those pixels. :param dictionary wcsHeader: WCS header :param width: width of rectangle :param height: height of rectangle :param startX: x coordinate of rectangle, can be negative :param startY: y coordinate of rectangle, can be negative If ascartesian=False: :rtype: tuple(ra, dec) with arrays of shape (height+1,width+1) if corner==True, else (height,width) If ascartesian=True: :rtype: array of shape (height[+1],width[+1],3) with x,y,z order """ if corner: startX -= 0.5 # top left corner instead of pixel center startY -= 0.5 x, y = np.meshgrid(np.arange(startX, startX + width + corner), np.arange(startY, startY + height + corner)) # check if TAN projection and use our fast version, otherwise fall-back to astropy if wcsHeader['CTYPE1'] == 'RA---TAN' and wcsHeader['CTYPE2'] == 'DEC--TAN' and \ wcsHeader['LATPOLE'] == 0.0: res = tan_pix2world(wcsHeader, x, y, 0, ascartesian=ascartesian) else: wcs = WCS(wcsHeader) ra, dec = wcs.all_pix2world(x, y, 0, ra_dec_order=True) if ascartesian: np.deg2rad(ra, ra) np.deg2rad(dec, dec) res = spherical_to_cartesian(None, dec, ra, astuple=False) else: res = ra, dec return res
def pix2world(wcsHeader, width, height, startX=0, startY=0, corner=True, ascartesian=False): """ Calculate RA, Dec coordinates of a given pixel coordinate rectangle. ra, dec = pix2world(..) ra and dec are indexed as [y,x] Each array element contains the RA,Dec coords of the top left corner of the given pixel if corner==True, otherwise the coords of the pixel center. If corner==True, an additional row and column exists at the bottom and right so that it is possible to get the bottom and right corner values for those pixels. :param dictionary wcsHeader: WCS header :param width: width of rectangle :param height: height of rectangle :param startX: x coordinate of rectangle, can be negative :param startY: y coordinate of rectangle, can be negative If ascartesian=False: :rtype: tuple(ra, dec) with arrays of shape (height+1,width+1) if corner==True, else (height,width) If ascartesian=True: :rtype: array of shape (height[+1],width[+1],3) with x,y,z order """ if corner: startX -= 0.5 # top left corner instead of pixel center startY -= 0.5 x, y = np.meshgrid(np.arange(startX,startX+width+corner), np.arange(startY,startY+height+corner)) # check if TAN projection and use our fast version, otherwise fall-back to astropy if wcsHeader['CTYPE1'] == 'RA---TAN' and wcsHeader['CTYPE2'] == 'DEC--TAN' and \ wcsHeader['LATPOLE'] == 0.0: res = tan_pix2world(wcsHeader, x, y, 0, ascartesian=ascartesian) else: wcs = WCS(wcsHeader) ra, dec = wcs.all_pix2world(x, y, 0, ra_dec_order=True) if ascartesian: np.deg2rad(ra, ra) np.deg2rad(dec, dec) res = spherical_to_cartesian(None, dec, ra, astuple=False) else: res = ra, dec return res
def recomputeXylsPixelPositions(originalXylsPath, originalWcsPath, newWcsPathOrHeader): """ Return pixel coordinates valid for `newWcsPathOrHeader` for the reference stars found in `originalXylsPath` (belonging to `originalWcsPath`). :rtype: tuple (x,y) with x and y being ndarrays """ # Step 1: compute RA,Dec of reference stars (as this is not stored in .xyls) originalWCS = WCS(readHeader(originalWcsPath)) x, y = readXy(originalXylsPath) ra, dec = originalWCS.all_pix2world(x, y, 0) # Step 2: compute pixel positions of reference stars in new WCS solution if isinstance(newWcsPathOrHeader, string_types): newWCS = WCS(readHeader(newWcsPathOrHeader)) else: newWCS = WCS(newWcsPathOrHeader) # all_world2pix raised a NoConvergenc error # As we don't use SIP, we don't need to use all_world2pix. # wcs_world2pix doesn't support any distortion correction. xNew, yNew = newWCS.wcs_world2pix(ra, dec, 0) return xNew,yNew
def test_fit_wcs_from_points(): header_str_linear = """ XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 50 NAXIS2 = 50 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups RADESYS = 'ICRS ' EQUINOX = 2000.0 WCSAXES = 2 CTYPE1 = 'RA---TAN' CTYPE2 = 'DEC--TAN' CRVAL1 = 250.3497414839765 CRVAL2 = 2.280925599609063 CRPIX1 = 1045.0 CRPIX2 = 1001.0 CD1_1 = -0.005564478186178 CD1_2 = -0.001042099258152 CD2_1 = 0.00118144146585 CD2_2 = -0.005590816683583 """ header_str_sip = """ XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 50 NAXIS2 = 50 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups RADESYS = 'ICRS ' EQUINOX = 2000.0 WCSAXES = 2 CTYPE1 = 'RA---TAN-SIP' CTYPE2 = 'DEC--TAN-SIP' CRVAL1 = 250.3497414839765 CRVAL2 = 2.280925599609063 CRPIX1 = 1045.0 CRPIX2 = 1001.0 CD1_1 = -0.005564478186178 CD1_2 = -0.001042099258152 CD2_1 = 0.00118144146585 CD2_2 = -0.005590816683583 A_ORDER = 2 B_ORDER = 2 A_2_0 = 2.02451189234E-05 A_0_2 = 3.317603337918E-06 A_1_1 = 1.73456334971071E-05 B_2_0 = 3.331330003472E-06 B_0_2 = 2.04247482482589E-05 B_1_1 = 1.71476710804143E-05 AP_ORDER= 2 BP_ORDER= 2 AP_1_0 = 0.000904700296389636 AP_0_1 = 0.000627660715584716 AP_2_0 = -2.023482905861E-05 AP_0_2 = -3.332285841011E-06 AP_1_1 = -1.731636633824E-05 BP_1_0 = 0.000627960882053211 BP_0_1 = 0.000911222886084808 BP_2_0 = -3.343918167224E-06 BP_0_2 = -2.041598249021E-05 BP_1_1 = -1.711876336719E-05 A_DMAX = 44.72893589844534 B_DMAX = 44.62692873032506 """ # A known header that failed before header_str_prob = """ NAXIS = 2 / number of array dimensions WCSAXES = 2 / Number of coordinate axes CRPIX1 = 1024.5 / Pixel coordinate of reference point CRPIX2 = 1024.5 / Pixel coordinate of reference point CD1_1 = -1.7445934400771E-05 / Coordinate transformation matrix element CD1_2 = -4.9826985362578E-08 / Coordinate transformation matrix element CD2_1 = -5.0068838822312E-08 / Coordinate transformation matrix element CD2_2 = 1.7530614610951E-05 / Coordinate transformation matrix element CTYPE1 = 'RA---TAN' / Right ascension, gnomonic projection CTYPE2 = 'DEC--TAN' / Declination, gnomonic projection CRVAL1 = 5.8689341666667 / [deg] Coordinate value at reference point CRVAL2 = -71.995508583333 / [deg] Coordinate value at reference point """ header_linear = fits.Header.fromstring(header_str_linear, sep='\n') header_sip = fits.Header.fromstring(header_str_sip, sep='\n') header_prob = fits.Header.fromstring(header_str_prob, sep='\n') true_wcs_linear = WCS(header_linear, relax=True) true_wcs_sip = WCS(header_sip, relax=True) true_wcs_prob = WCS(header_prob, relax=True) # Getting the pixel coordinates x, y = np.meshgrid(list(range(10)), list(range(10))) x = x.flatten() y = y.flatten() # Calculating the true sky positions world_pix_linear = true_wcs_linear.pixel_to_world(x, y) world_pix_sip = true_wcs_sip.pixel_to_world(x, y) world_pix_prob = true_wcs_prob.pixel_to_world(x, y) # Fitting the wcs, no distortion. fit_wcs_linear = fit_wcs_from_points((x, y), world_pix_linear, proj_point='center', sip_degree=None) # Fitting the wcs, with distortion. fit_wcs_sip = fit_wcs_from_points((x, y), world_pix_sip, proj_point='center', sip_degree=2) # Fitting the problematic WCS fit_wcs_prob = fit_wcs_from_points((x, y), world_pix_prob, proj_point='center', sip_degree=None) # Validate that the true sky coordinates calculated with `true_wcs_linear` # match sky coordinates calculated from the wcs fit with only linear terms world_pix_linear_new = fit_wcs_linear.pixel_to_world(x, y) dists = world_pix_linear.separation(world_pix_linear_new) assert dists.max() < 7e-5 * u.deg assert np.std(dists) < 2.5e-5 * u.deg # Validate that the true sky coordinates calculated with `true_wcs_sip` # match the sky coordinates calculated from the wcs fit with SIP of same # degree (2) world_pix_sip_new = fit_wcs_sip.pixel_to_world(x, y) dists = world_pix_sip.separation(world_pix_sip_new) assert dists.max() < 7e-6 * u.deg assert np.std(dists) < 2.5e-6 * u.deg # Validate that the true sky coordinates calculated from the problematic # WCS match world_pix_prob_new = fit_wcs_prob.pixel_to_world(x, y) dists = world_pix_prob.separation(world_pix_prob_new) assert dists.max() < 7e-6 * u.deg assert np.std(dists) < 2.5e-6 * u.deg # Test 360->0 degree crossover header_linear["CRVAL1"] = 352.3497414839765 header_sip["CRVAL1"] = 352.3497414839765 header_prob["CRVAL1"] = 352.3497414839765 true_wcs_linear = WCS(header_linear, relax=True) true_wcs_sip = WCS(header_sip, relax=True) true_wcs_prob = WCS(header_prob) # Calculating the true sky positions world_pix_linear = true_wcs_linear.pixel_to_world(x, y) world_pix_sip = true_wcs_sip.pixel_to_world(x, y) world_pix_prob = true_wcs_prob.pixel_to_world(x, y) # Fitting the wcs, no distortion. fit_wcs_linear = fit_wcs_from_points((x, y), world_pix_linear, proj_point='center', sip_degree=None) # Fitting the wcs, with distortion. fit_wcs_sip = fit_wcs_from_points((x, y), world_pix_sip, proj_point='center', sip_degree=2) # Fitting the problem WCS fit_wcs_prob = fit_wcs_from_points((x, y), world_pix_prob, proj_point='center', sip_degree=None) # Validate that the true sky coordinates calculated with `true_wcs_linear` # match sky coordinates calculated from the wcs fit with only linear terms world_pix_linear_new = fit_wcs_linear.pixel_to_world(x, y) dists = world_pix_linear.separation(world_pix_linear_new) assert dists.max() < 7e-5 * u.deg assert np.std(dists) < 2.5e-5 * u.deg # Validate fit with SIP world_pix_sip_new = fit_wcs_sip.pixel_to_world(x, y) dists = world_pix_sip.separation(world_pix_sip_new) assert dists.max() < 7e-6 * u.deg assert np.std(dists) < 2.5e-6 * u.deg # Validate the problematic WCS world_pix_prob_new = fit_wcs_prob.pixel_to_world(x, y) dists = world_pix_prob.separation(world_pix_prob_new) assert dists.max() < 7e-6 * u.deg assert np.std(dists) < 2.5e-6 * u.deg # Test CRPIX bounds requirement wcs_str = """ WCSAXES = 2 / Number of coordinate axes CRPIX1 = 1045.0 / Pixel coordinate of reference point CRPIX2 = 1001.0 / Pixel coordinate of reference point PC1_1 = 0.00056205870415378 / Coordinate transformation matrix element PC1_2 = -0.00569181083243 / Coordinate transformation matrix element PC2_1 = 0.0056776810932466 / Coordinate transformation matrix element PC2_2 = 0.0004208048403273 / Coordinate transformation matrix element CDELT1 = 1.0 / [deg] Coordinate increment at reference point CDELT2 = 1.0 / [deg] Coordinate increment at reference point CUNIT1 = 'deg' / Units of coordinate increment and value CUNIT2 = 'deg' / Units of coordinate increment and value CTYPE1 = 'RA---TAN' / Right ascension, gnomonic projection CTYPE2 = 'DEC--TAN' / Declination, gnomonic projection CRVAL1 = 104.57797893504 / [deg] Coordinate value at reference point CRVAL2 = -74.195502593322 / [deg] Coordinate value at reference point LONPOLE = 180.0 / [deg] Native longitude of celestial pole LATPOLE = -74.195502593322 / [deg] Native latitude of celestial pole TIMESYS = 'TDB' / Time scale TIMEUNIT= 'd' / Time units DATEREF = '1858-11-17' / ISO-8601 fiducial time MJDREFI = 0.0 / [d] MJD of fiducial time, integer part MJDREFF = 0.0 / [d] MJD of fiducial time, fractional part DATE-OBS= '2019-03-27T03:30:13.832Z' / ISO-8601 time of observation MJD-OBS = 58569.145993426 / [d] MJD of observation MJD-OBS = 58569.145993426 / [d] MJD at start of observation TSTART = 1569.6467941661 / [d] Time elapsed since fiducial time at start DATE-END= '2019-03-27T04:00:13.831Z' / ISO-8601 time at end of observation MJD-END = 58569.166826748 / [d] MJD at end of observation TSTOP = 1569.6676274905 / [d] Time elapsed since fiducial time at end TELAPSE = 0.02083332443 / [d] Elapsed time (start to stop) TIMEDEL = 0.020833333333333 / [d] Time resolution TIMEPIXR= 0.5 / Reference position of timestamp in binned data RADESYS = 'ICRS' / Equatorial coordinate system """ wcs_header = fits.Header.fromstring(wcs_str, sep='\n') ffi_wcs = WCS(wcs_header) yi, xi = (1000, 1000) y, x = (10, 200) center_coord = SkyCoord(ffi_wcs.all_pix2world([[xi + x // 2, yi + y // 2]], 0), unit='deg')[0] ypix, xpix = [arr.flatten() for arr in np.mgrid[xi:xi + x, yi:yi + y]] world_pix = SkyCoord(*ffi_wcs.all_pix2world(xpix, ypix, 0), unit='deg') fit_wcs = fit_wcs_from_points((ypix, xpix), world_pix, proj_point='center') assert (fit_wcs.wcs.crpix.astype(int) == [1100, 1005]).all() assert fit_wcs.pixel_shape == (200, 10)