def flatten(filename, channel=0, freqaxis=0): """ Flatten a fits file so that it becomes a 2D image. Return new header and data """ f = pyfits.open(filename) naxis = f[0].header['NAXIS'] if naxis < 2: raise RadioError('Can\'t make map from this') if naxis == 2: pass #return f[0].header,f[0].data w = pywcs(f[0].header) wn = pywcs(naxis=2) wn.wcs.crpix[0] = w.wcs.crpix[0] wn.wcs.crpix[1] = w.wcs.crpix[1] wn.wcs.cdelt = w.wcs.cdelt[0:2] wn.wcs.crval = w.wcs.crval[0:2] wn.wcs.ctype[0] = w.wcs.ctype[0] wn.wcs.ctype[1] = w.wcs.ctype[1] header = wn.to_header() header["NAXIS"] = 2 header["NAXIS1"] = f[0].header['NAXIS1'] header["NAXIS2"] = f[0].header['NAXIS2'] copy = ('EQUINOX', 'EPOCH') for k in copy: r = f[0].header.get(k) if r: header[k] = r dataslice = [] for i in range(naxis, 0, -1): if i <= 2: dataslice.append(np.s_[:], ) elif i == freqaxis: dataslice.append(channel) else: dataslice.append(0) # add freq header["FREQ"] = find_freq(f[0].header) # add beam if present try: header["BMAJ"] = f[0].header['BMAJ'] header["BMIN"] = f[0].header['BMIN'] header["BPA"] = f[0].header['BPA'] except: pass # slice=(0,)*(naxis-2)+(np.s_[:],)*2 return header, f[0].data[tuple(dataslice)]
def blank(self, vertices_file=None): """ Blank pixels (NaN) outside of polygon region """ # Construct polygon if vertices_file is None: vertices_file = self.vertices_file vertices = misc.read_vertices(vertices_file) w = pywcs(self.header) RAind = w.axis_type_names.index('RA') Decind = w.axis_type_names.index('DEC') RAverts = vertices[0] Decverts = vertices[1] verts = [] for RAvert, Decvert in zip(RAverts, Decverts): ra_dec = np.array([[0.0, 0.0, 0.0, 0.0]]) ra_dec[0][RAind] = RAvert ra_dec[0][Decind] = Decvert verts.append((w.wcs_world2pix(ra_dec, 0)[0][RAind], w.wcs_world2pix(ra_dec, 0)[0][Decind])) poly = Polygon(verts) poly_padded = poly.buffer(2) verts = [(xi, yi) for xi, yi in zip(poly_padded.exterior.coords.xy[0].tolist(), poly_padded.exterior.coords.xy[1].tolist())] # Blank pixels (= NaN) outside of the polygon self.img_data = misc.rasterize(verts, self.img_data, blank_value=np.nan)
def wcs_from_pv_header(hdr): ''' This is mostly for dealing with the Ellerbroek data ''' # Create a WCS object mywcs = pywcs(hdr, fix=False) # Make sure the WCS info is in [spatial, spectral] # order if mywcs.wcs.cdelt[0] == 0.2: pass elif mywcs.wcs.cdelt[1] == 0.2: mywcs = mywcs.swapaxes(0, 1) #mywcs.pixel_shape = mywcs.pixel_shape[::-1] # Is this velocity or wavelength? if mywcs.wcs.crval[1] < 1: ctype2 = 'VELO' cunit2 = u.Unit('km/s').to_string('fits') else: ctype2 = 'AWAV' cunit2 = u.Unit('nm').to_string('fits') ctype1 = 'OFFSET' cunit1 = u.Unit('arcsec').to_string('fits') mywcs.wcs.crpix = np.array([1., 1.]) mywcs.wcs.ctype = [ctype1, ctype2] #mywcs.wcs.cunit = [cunit1, cunit2] return mywcs
def flatten(filename, channel=0, freqaxis=0): """ Flatten a fits file so that it becomes a 2D image. Return new header and data """ f = pyfits.open(filename) naxis=f[0].header['NAXIS'] if naxis<2: raise RadioError('Can\'t make map from this') if naxis==2: return f[0].header,f[0].data w = pywcs(f[0].header) wn = pywcs(naxis=2) wn.wcs.crpix[0]=w.wcs.crpix[0] wn.wcs.crpix[1]=w.wcs.crpix[1] wn.wcs.cdelt=w.wcs.cdelt[0:2] wn.wcs.crval=w.wcs.crval[0:2] wn.wcs.ctype[0]=w.wcs.ctype[0] wn.wcs.ctype[1]=w.wcs.ctype[1] header = wn.to_header() header["NAXIS"]=2 header["NAXIS1"]=f[0].header['NAXIS1'] header["NAXIS2"]=f[0].header['NAXIS2'] copy=('EQUINOX','EPOCH') for k in copy: r=f[0].header.get(k) if r: header[k]=r dataslice=[] for i in range(naxis,0,-1): if i<=2: dataslice.append(np.s_[:],) elif i==freqaxis: dataslice.append(channel) else: dataslice.append(0) # add freq header["FREQ"] = find_freq(f[0].header) # slice=(0,)*(naxis-2)+(np.s_[:],)*2 return header, f[0].data[dataslice]
def flatten(self): """ Flatten a fits file so that it becomes a 2D image. Return new header and data """ f = pyfits.open(self.imagefile) naxis = f[0].header['NAXIS'] if naxis < 2: raise RuntimeError('Can\'t make map from this') if naxis == 2: self.img_hdr = f[0].header self.img_data = f[0].data else: w = pywcs(f[0].header) wn = pywcs(naxis=2) wn.wcs.crpix[0] = w.wcs.crpix[0] wn.wcs.crpix[1] = w.wcs.crpix[1] wn.wcs.cdelt = w.wcs.cdelt[0:2] wn.wcs.crval = w.wcs.crval[0:2] wn.wcs.ctype[0] = w.wcs.ctype[0] wn.wcs.ctype[1] = w.wcs.ctype[1] header = wn.to_header() header["NAXIS"] = 2 header["NAXIS1"] = f[0].header['NAXIS1'] header["NAXIS2"] = f[0].header['NAXIS2'] header["FREQ"] = self.freq header['RESTFREQ'] = self.freq header['BMAJ'] = self.beam[0] header['BMIN'] = self.beam[1] header['BPA'] = self.beam[2] copy = ('EQUINOX', 'EPOCH') for k in copy: r = f[0].header.get(k) if r: header[k] = r dataslice = [] for i in range(naxis, 0, -1): if i <= 2: dataslice.append(np.s_[:], ) else: dataslice.append(0) self.img_hdr = header self.img_data = f[0].data[tuple(dataslice)]
def flatten(filename, channel=0, freqaxis=0): """ Flatten a fits file so that it becomes a 2D image. Return new header and data """ f = pyfits.open(filename) naxis = f[0].header['NAXIS'] if naxis < 2: raise RadioError('Can\'t make map from this') if naxis == 2: return f[0].header, f[0].data w = pywcs(f[0].header) wn = pywcs(naxis=2) wn.wcs.crpix[0] = w.wcs.crpix[0] wn.wcs.crpix[1] = w.wcs.crpix[1] wn.wcs.cdelt = w.wcs.cdelt[0:2] wn.wcs.crval = w.wcs.crval[0:2] wn.wcs.ctype[0] = w.wcs.ctype[0] wn.wcs.ctype[1] = w.wcs.ctype[1] header = wn.to_header() header["NAXIS"] = 2 header["NAXIS1"] = f[0].header['NAXIS1'] # Test header["NAXIS2"] = f[0].header['NAXIS2'] # Test copy = ('EQUINOX', 'EPOCH') for k in copy: r = f[0].header.get(k) if r: header[k] = r slice = [] for i in range(naxis, 0, -1): if i <= 2: slice.append(np.s_[:], ) elif i == freqaxis: slice.append(channel) else: slice.append(0) # slice=(0,)*(naxis-2)+(np.s_[:],)*2 return header, f[0].data[slice]
def __init__(self, hdr=None, crpix=[1., 1.], crval=[1., 1.], cdelt=[1., 1.], unit1=u.arcsec, unit2=u.dimensionless_unscaled, ctype=['LINEAR', 'AWAV'], shape=None): unit1 = u.Unit(unit1).to_string('fits') unit2 = u.Unit(unit2) self.shape = shape #if mode is not None: # print(self.unit) if (hdr is not None): h = hdr.copy() #print(h) n = h['NAXIS'] self.shape = h['NAXIS%d' % n] if n == 3: self.wcs = wcs_from_cube_header(h) elif n == 2: self.wcs = wcs_from_pv_header(h) print(self.wcs.wcs.cunit) if self.wcs.wcs.cunit[0] != '': self.spatial_unit = self.wcs.wcs.cunit[0] elif self.wcs.wcs.ctype[0] == 'OFFSET': self.spatial_unit = u.Unit('arcsec') #print(self.wcs.wcs.cunit[1]) if self.wcs.wcs.cunit[1] == 'm': self.spectral_unit = u.Unit('angstrom') elif (self.wcs.wcs.cunit[1] == u.Unit('km/s')) or (self.wcs.wcs.ctype[1] == 'VELO'): self.spectral_unit = u.Unit('km/s') elif hdr is None: #if data is not None: self.spatial_unit = u.Unit(unit1) self.spectral_unit = u.Unit(unit2) self.wcs = pywcs(naxis=2) #self.shape = # set the reference pixel self.wcs.wcs.crval = np.array([crval[0], crval[1]]) self.wcs.wcs.ctype = np.array([ctype[0], ctype[1]]) self.wcs.wcs.cdelt = np.array([cdelt[0], cdelt[1]]) self.wcs.wcs.crpix = np.array([crpix[0], crpix[1]]) #self.wcs.wcs.set() self.shape = self.wcs.pixel_shape
if args.shift: d.calc_shift(tgss_catalog) # prepare header for final gridding if args.header is None: logging.warning('Calculate output headers...') mra = np.mean( np.array([d.get_wcs().wcs.crval[0] for d in directions]) ) mdec = np.mean( np.array([d.get_wcs().wcs.crval[1] for d in directions]) ) logging.info('Will make mosaic at %f %f' % (mra,mdec)) # we make a reference WCS and use it to find the extent in pixels # needed for the combined image rwcs = pywcs(naxis=2) rwcs.wcs.ctype = directions[0].get_wcs().wcs.ctype rwcs.wcs.cdelt = directions[0].get_wcs().wcs.cdelt rwcs.wcs.crval = [mra,mdec] rwcs.wcs.crpix = [1,1] xmin=0 xmax=0 ymin=0 ymax=0 for d in directions: w = d.get_wcs() ys, xs = np.where(d.img_data) axmin = xs.min() aymin = ys.min() axmax = xs.max()
def get_wcs(self): return pywcs(self.img_hdr)
def regrid_common(self, size=None, radec=None, pixscale=None, pix_per_bmin=None, square=False): """ Move all images to a common grid Parameters ---------- size: float or array-like of size 2, optional. Default = None Size of the new grid in degree. If not a list of size two, is assumed to be square. If not provided, automatically determines the largest size that fits all images. radec: list of size 2, optional. [ra, dec] in degree. If not specified, default to center of first image. pixscale: float, optional. Default = use from first image Size of a square pixel in arcseconds pix_per_bmin: int, optional. How many pixels should there be per BMIN of 1st image? square: bool, optional. Default = True If False, do not force square image. """ rwcs = pywcs(naxis=2) rwcs.wcs.ctype = self.images[0].get_wcs().wcs.ctype if pixscale and pix_per_bmin: logging.error('Cannot specify both pixscale and pix_per_bmin!') sys.exit(1) elif pixscale: cdelt = pixscale / 3600. elif pix_per_bmin: cdelt = self.images[0].img_hdr['BMIN'] / pix_per_bmin print(self.images[0].img_hdr['BMIN'], cdelt) else: cdelt = np.mean(np.abs(self.images[0].get_wcs().wcs.cdelt) ) # could also use 1/5 of minor axes (deg) logging.info('Pixel scale: %f"' % (cdelt * 3600.)) rwcs.wcs.cdelt = [-cdelt, cdelt] if radec: mra, mdec = radec else: mra = self.images[0].img_hdr['CRVAL1'] mdec = self.images[0].img_hdr['CRVAL2'] rwcs.wcs.crval = [mra, mdec] # Calculate sizes of all images to find smallest size that fits all images sizes = np.empty((len(self.images), 2)) for i, image in enumerate(self.images): sizes[i] = np.array(image.img_data.shape) * image.degperpixel if size: size = np.array([size]) if np.any(np.min(sizes, axis=1) < size): logging.warning( f'Requested size {size} is larger than smallest image size {np.min(sizes, axis=1)} in at least one dimension. This will result in NaN values in the regridded images.' ) else: size = np.min(sizes, axis=0) if square: size = np.min(size) xsize = int(np.rint(np.array([size[0]]) / cdelt)) ysize = int(np.rint(np.array([size[-1]]) / cdelt)) if xsize % 2 != 0: xsize += 1 if ysize % 2 != 0: ysize += 1 rwcs.wcs.crpix = [xsize / 2, ysize / 2] regrid_hdr = rwcs.to_header() regrid_hdr['NAXIS'] = 2 regrid_hdr['NAXIS1'] = xsize regrid_hdr['NAXIS2'] = ysize logging.info( f'Image size: {size} deg ({xsize:.0f},{ysize:.0f} pixels))') for image in self.images: this_regrid_hdr = regrid_hdr.copy() this_regrid_hdr['BMAJ'], this_regrid_hdr['BMIN'], this_regrid_hdr[ 'BPA'] = image.get_beam() image.regrid(this_regrid_hdr)
dra = ref_cat['RA'][idx_matched_ref] - image.cat['RA'][idx_matched_img] dra[ dra>180 ] -= 360 dra[ dra<-180 ] += 360 ddec = ref_cat['DEC'][idx_matched_ref] - image.cat['DEC'][idx_matched_img] flux = ref_cat['Peak_flux'][idx_matched_ref] image.apply_shift(np.average(dra, weights=flux), np.average(ddec, weights=flux)) # clean up #for image in all_images: # os.system(rm ...) ###################################################### # Generate regrid headers rwcs = pywcs(naxis=2) rwcs.wcs.ctype = all_images[0].get_wcs().wcs.ctype cdelt = target_beam[1]/5. # 1/5 of minor axes (deg) logging.info('Pixel scale: %f"' % (cdelt*3600.)) rwcs.wcs.cdelt = [-cdelt, cdelt] if args.radec is not None: mra = radec[0]*np.pi/180 mdec = radec[1]*np.pi/180 else: mra = all_images[0].img_hdr['CRVAL1'] mdec = all_images[0].img_hdr['CRVAL2'] rwcs.wcs.crval = [mra,mdec] # if size is not give is taken from the mask if args.size is None: if args.region is not None:
def wcs_from_cube_header(hdr): ''' Install coordinates for the data. This will have one spatial axis and one spectral axis ''' # Don't convert units if 'CUNIT3' in hdr: unit = u.Unit(hdr.pop('CUNIT3')) try: naxis = hdr['NAXIS'] except KeyError: naxis = hdr['WCSAXES'] # generate a WCS object mywcs = pywcs(hdr, fix=False) # Figure out the spatial limits # get the full extent of the data nx, ny = mywcs.pixel_shape[:2] #print(nx, ny) # ideally, crpix should be our centers # TODO: add option to pass a center xc, yc = mywcs.wcs.crpix[:2] # make new WCS object with Spatial and Wavelength axes # `wcs.sub()` sets naxis1 to None, so make sure it has # the same pixel shape as the parent data new_wcs = mywcs.sub([0, WCSSUB_SPECTRAL]) new_wcs.pixel_shape = mywcs.pixel_shape[1:] # get cdelt from CD cd = mywcs.wcs.cd dy = np.sqrt(cd[0, 1]**2 + cd[1, 1]**2) # get dy in arcseconds dy = np.round((dy * u.deg).to(u.arcsec).value, 1) cdelt1 = dy cdelt2 = cd[2, 2] # if the WCS header is taken from a cube or a 2D spatial image, # get rid of the CD parameter so astropy won't ignore a cdelt value if new_wcs.wcs.has_cd(): del new_wcs.wcs.cd # set the WCS info new_wcs.wcs.crpix[0] = yc new_wcs.wcs.cdelt[0] = cdelt1 new_wcs.wcs.cdelt[1] = cdelt2 new_wcs.wcs.crval[0] = 0. # this is arcsec, so we want the center at 0 new_wcs.wcs.ctype[0] = 'OFFSET' new_wcs.wcs.cunit[0] = u.Unit('arcsec') #.to_string('fits') #new_wcs.wcs.cunit[1] = u.Unit(unit)#.to_string('fits') #new_wcs.wcs.set() try: new_wcs.wcs.pc[1, 0] = new_wcs.wcs.pc[0, 1] = 0 except AttributeError: pass return new_wcs
def main(input_image_list, vertices_file_list, output_image, skip=False, padding=1.1): """ Make a mosaic template image Parameters ---------- input_image_list : list List of filenames of input images to mosaic vertices_file_list : list List of filenames of input vertices files output_image : str Filename of output image skip : bool If True, skip all processing padding : float Fraction with which to increase the final mosaic size """ input_image_list = misc.string2list(input_image_list) vertices_file_list = misc.string2list(vertices_file_list) skip = misc.string2bool(skip) if skip: return # Set up images used in mosaic directions = [] for image_file, vertices_file in zip(input_image_list, vertices_file_list): d = FITSImage(image_file) d.vertices_file = vertices_file d.blank() directions.append(d) # Prepare header for final gridding mra = np.mean(np.array([d.get_wcs().wcs.crval[0] for d in directions])) mdec = np.mean(np.array([d.get_wcs().wcs.crval[1] for d in directions])) # Make a reference WCS and use it to find the extent in pixels # needed for the combined image rwcs = pywcs(naxis=2) rwcs.wcs.ctype = directions[0].get_wcs().wcs.ctype rwcs.wcs.cdelt = directions[0].get_wcs().wcs.cdelt rwcs.wcs.crval = [mra, mdec] rwcs.wcs.crpix = [1, 1] xmin, xmax, ymin, ymax = 0, 0, 0, 0 for d in directions: w = d.get_wcs() ys, xs = np.where(d.img_data) axmin, aymin, axmax, aymax = xs.min(), ys.min(), xs.max(), ys.max() del xs, ys for x, y in ((axmin, aymin), (axmax, aymin), (axmin, aymax), (axmax, aymax)): ra, dec = [float(f) for f in w.wcs_pix2world(x, y, 0)] nx, ny = [float(f) for f in rwcs.wcs_world2pix(ra, dec, 0)] xmin, xmax, ymin, ymax = min(nx, xmin), max(nx, xmax), min( ny, ymin), max(ny, ymax) xsize = int(xmax - xmin) ysize = int(ymax - ymin) xmax += int(xsize * (padding - 1.0) / 2.0) xmin -= int(xsize * (padding - 1.0) / 2.0) ymax += int(ysize * (padding - 1.0) / 2.0) ymin -= int(ysize * (padding - 1.0) / 2.0) xsize = int(xmax - xmin) ysize = int(ymax - ymin) rwcs.wcs.crpix = [-int(xmin) + 1, -int(ymin) + 1] regrid_hdr = rwcs.to_header() regrid_hdr['NAXIS'] = 2 regrid_hdr['NAXIS1'] = xsize regrid_hdr['NAXIS2'] = ysize for ch in ('BMAJ', 'BMIN', 'BPA', 'FREQ', 'RESTFREQ', 'EQUINOX'): regrid_hdr[ch] = directions[0].img_hdr[ch] regrid_hdr['ORIGIN'] = 'Raptor' regrid_hdr['UNITS'] = 'Jy/beam' regrid_hdr['TELESCOP'] = 'LOFAR' isum = np.zeros([ysize, xsize]) hdu = pyfits.PrimaryHDU(header=regrid_hdr, data=isum) hdu.writeto(output_image, overwrite=True)
def main(input_image, template_image, vertices_file, output_image, skip=False): """ Regrid a FITS image Parameters ---------- input_image : str Filename of input FITS image to regrid template_image : str Filename of mosaic template FITS image vertices_file : str Filename of file with vertices output_image : str Filename of output FITS image skip : bool If True, skip all processing """ skip = misc.string2bool(skip) if skip: if os.path.exists(output_image): os.remove(output_image) shutil.copyfile(input_image, output_image) return # Read template header and data regrid_hdr = pyfits.open(template_image)[0].header isum = pyfits.open(template_image)[0].data isum[:] = np.nan shape_out = isum.shape wcs_out = pywcs(regrid_hdr) # Read input image and blank outside its polygon d = FITSImage(input_image) d.vertices_file = vertices_file d.blank() wcs_in = d.get_wcs() # Define the subarray of the output image that fully encloses the reprojected input # image ny, nx = d.img_data.shape xc = np.array([-0.5, nx - 0.5, nx - 0.5, -0.5]) yc = np.array([-0.5, -0.5, ny - 0.5, ny - 0.5]) xc_out, yc_out = wcs_out.world_to_pixel(wcs_in.pixel_to_world(xc, yc)) imin = max(0, int(np.floor(xc_out.min() + 0.5))) imax = min(shape_out[1], int(np.ceil(xc_out.max() + 0.5))) jmin = max(0, int(np.floor(yc_out.min() + 0.5))) jmax = min(shape_out[0], int(np.ceil(yc_out.max() + 0.5))) # Set up output projection wcs_out_indiv = wcs_out.deepcopy() wcs_out_indiv.wcs.crpix[0] -= imin wcs_out_indiv.wcs.crpix[1] -= jmin shape_out_indiv = (jmax - jmin, imax - imin) # Reproject, place into output image, and write out final FITS file ind = slice(jmin, jmax), slice(imin, imax) isum[ind] = reproject_interp((d.img_data, wcs_in), output_projection=wcs_out_indiv, shape_out=shape_out_indiv, return_footprint=False) d.img_data = isum d.img_hdr = regrid_hdr d.write(output_image)