def align_to_target(infile=None, outfile=None, template=None, interpolation='cubic', asvelocity=True, overwrite=False, axes=[-1]): """ Align one cube to another, creating a copy. Right now a thin wrapper to imregrid used mostly to avoid exposing CASA directly into the postprocessHandler. Might evolve in the future. """ if infile is None or template is None or outfile is None: logger.error("Missing required input.") return (False) if os.path.isdir(infile) == False and os.path.isfile(infile) == False: logger.error("Input file missing - " + infile) return (False) if os.path.isdir(template) == False and os.path.isfile(template) == False: logger.error("Template file missing - " + template) return (False) if os.path.isfile(outfile) or os.path.isdir(outfile): if overwrite: os.system('rm -rf ' + outfile) else: logger.error("Output exists and overwrite set to false - " + outfile) return (False) casaStuff.imregrid(imagename=infile, template=template, output=outfile, interpolation=interpolation, asvelocity=asvelocity, axes=axes, overwrite=True) return (True)
def common_grid_for_mosaic( infile_list=None, outfile_list=None, target_hdr=None, template_file=None, # could use **kwargs here if this gets much more complicated ra_ctr=None, dec_ctr=None, delta_ra=None, delta_dec=None, allow_big_image=False, too_big_pix=1e4, asvelocity=True, interpolation='cubic', axes=[-1], overwrite=False, ): """ Build a common astrometry for a mosaic and align all input image files to that astrometry. If the common astrometry isn't supplied as a header, the program calls other routines to create it based on the supplied parameters and stack of input images. Returns the common header. infile_list : list of input files. outfile_list : a list of output files that will get the convolved data. Can be a dictionary or a list. If it's a list then matching is by order, so that firs infile goes to first outfile, etc. If it's a dictionary, it looks for the infile name as a key. target_hdr : the CASA-format header used to align the images, needs the same format returned by a call to imregrid with template='get'. ra_ctr, dec_ctr, delta_ra, delta_dec, allow_big_image, too_big_pix : keywords passed to the header creation routine. See documentation for "build_common_header" to explain these. asvelocity, interpolation, axes : keywords passed to the CASA imregrid call. See documentation there. overwrite (default False) : Delete existing files. You probably want to set this to True but it's a user decision. """ # Error checking - mostly the subprograms do this. if infile_list is None: logger.error("Infile list missing.") return (None) for this_infile in infile_list: if os.path.isdir(this_infile) == False: logger.error("File " + this_infile + " not found. Continuing.") continue if outfile_list is None: logger.error("Outfile list missing.") return (None) # Make sure that the outfile list is a dictionary if (type(outfile_list) != type([])) and (type(outfile_list) != type({})): logger.error("outfile_list must be dictionary or list.") return (None) if type(outfile_list) == type([]): if len(infile_list) != len(outfile_list): logger.error("Mismatch in input and output list lengths.") return (None) outfile_dict = {} for ii in range(len(infile_list)): outfile_dict[infile_list[ii]] = outfile_list[ii] if type(outfile_list) == type({}): outfile_dict = outfile_list # Get the common header if one is not supplied if target_hdr is None: logger.info('Generating target header.') target_hdr = build_common_header( infile_list=infile_list, template_file=template_file, ra_ctr=ra_ctr, dec_ctr=dec_ctr, delta_ra=delta_ra, delta_dec=delta_dec, allow_big_image=allow_big_image, too_big_pix=too_big_pix, ) if target_hdr is None: logger.error('No target header, and was not able to build one.') return (None) # Align the input files to the new astrometry. This will also loop # over and align any "weight" files. logger.info('Aligning image files.') for this_infile in infile_list: this_outfile = outfile_dict[this_infile] casaStuff.imregrid(imagename=this_infile, template=target_hdr, output=this_outfile, asvelocity=asvelocity, axes=axes, interpolation=interpolation, overwrite=overwrite) return (target_hdr)
def build_common_header( infile_list=None, template_file=None, ra_ctr=None, dec_ctr=None, delta_ra=None, delta_dec=None, freq_ctr=None, delta_freq=None, allow_big_image=False, too_big_pix=1e4, ): """ Build a target header to be used as a template by imregrid when setting up linear mosaicking operations. infile_list : the list of input files. Used to generate the center, extent, and pick a template file if these things aren't supplied by the user. template_file : the name of a file to use as the template. The coordinate axes and size are manipulated but other things like the pixel size and units remain the same. If this is not supplied the first file from the input file list is selected. ra_ctr : the center of the output file in right ascension. Assumed to be in decimal degrees. If None or not supplied, then this is calculated from the image stack. dec_ctr : as ra_ctr but for declination. delta_ra : the extent of the output image in arcseconds. If this is not supplied, it is calculated from the image stack. delta_dec : as delta_ra but for declination. allow_big_image (default False) : allow very big images? If False then the program throws an error if the image appears too big. This is often the sign of a bug. too_big_pix (default 1e4) : definition of pixel scale (in one dimension) that marks an image as too big. """ # Check inputs if template_file is None: if infile_list is None: logger.error("Missing required infile_list and no template file.") return (None) template_file = infile_list[0] logger.info("Using first input file as template - " + template_file) if infile_list is not None: for this_infile in infile_list: if not os.path.isdir(this_infile): logger.error("File not found " + this_infile + " . Returning.") return (None) if template_file is not None: if os.path.isdir(template_file) == False: logger.error("The specified template file does not exist.") return (None) if infile_list is None: if template_file is None: logger.error( "Without an input file stack, I need a template file.") return (None) if (delta_ra is None) or (delta_dec is None) or (ra_ctr is None) or ( dec_ctr is None): logger.error( "Without an input file stack, I need ra_ctr, dec_ctr, delta_ra, delta_dec." ) return (None) # If the RA and Dec center and extent are not full specified, then # calculate the extent based on the image stack. if (delta_ra is None) or (delta_dec is None) or \ (ra_ctr is None) or (dec_ctr is None): logger.info( "Extent not fully specified. Calculating it from image stack.") extent_dict = calculate_mosaic_extent(infile_list=infile_list, force_ra_ctr=ra_ctr, force_dec_ctr=dec_ctr, force_freq_ctr=freq_ctr) if ra_ctr is None: ra_ctr = extent_dict['ra_ctr'][0] if dec_ctr is None: dec_ctr = extent_dict['dec_ctr'][0] if delta_ra is None: delta_ra = extent_dict['delta_ra'][0] if delta_dec is None: delta_dec = extent_dict['delta_dec'][0] # Just assume Doppler if freq_ctr is None: freq_ctr = extent_dict['freq_ctr'][0] if delta_freq is None: delta_freq = extent_dict['delta_freq'][0] # Get the header from the template file target_hdr = casaStuff.imregrid(template_file, template='get') # Get the pixel scale. This makes some assumptions. We could put a # lot of general logic here, but we are usually working in a # case where this works. if (target_hdr['csys']['direction0']['units'][0] != 'rad') or \ (target_hdr['csys']['direction0']['units'][1] != 'rad'): logger.error( "ERROR: Based on CASA experience. I expected pixel units of radians." ) logger.error( "I did not find this. Returning. Adjust code or investigate file " + infile_list[0]) return (None) # Add our target center pixel values to the header after # converting to radians. ra_ctr_in_rad = ra_ctr * np.pi / 180. dec_ctr_in_rad = dec_ctr * np.pi / 180. target_hdr['csys']['direction0']['crval'][0] = ra_ctr_in_rad target_hdr['csys']['direction0']['crval'][1] = dec_ctr_in_rad target_hdr['csys']['spectral1']['wcs']['crval'] = freq_ctr # Calculate the size of the image in pixels and set the central # pixel coordinate for the RA and Dec axis. ra_pix_in_as = np.abs(target_hdr['csys']['direction0']['cdelt'][0] * 180. / np.pi * 3600.) ra_axis_size = np.ceil(delta_ra / ra_pix_in_as) + 1 new_ra_ctr_pix = (ra_axis_size + 1) / 2.0 dec_pix_in_as = np.abs(target_hdr['csys']['direction0']['cdelt'][1] * 180. / np.pi * 3600.) dec_axis_size = np.ceil(delta_dec / dec_pix_in_as) + 1 new_dec_ctr_pix = (dec_axis_size + 1) / 2.0 freq_pix_in_hz = np.abs(target_hdr['csys']['spectral1']['wcs']['cdelt']) freq_axis_size = np.ceil(delta_freq / freq_pix_in_hz) + 1 # +1 or the 1-indexing new_freq_ctr_pix = (freq_axis_size + 1) / 2.0 # Check that the axis size isn't too big. This is likely to be a # bug. If allowbigimage is True then bypass this, otherwise exit. if not allow_big_image: if ra_axis_size > too_big_pix or \ dec_axis_size > too_big_pix: logger.error("WARNING! This is a very big image you plan to create, "+str(ra_axis_size)+ \ " x "+str(dec_axis_size)) logger.error( " To make an image this big set allowbigimage=True. Returning." ) return (None) # Enter the new values into the header and return. target_hdr['csys']['direction0']['crpix'][0] = new_ra_ctr_pix target_hdr['csys']['direction0']['crpix'][1] = new_dec_ctr_pix target_hdr['csys']['spectral1']['wcs']['crpix'] = new_freq_ctr_pix target_hdr['shap'][0] = int(ra_axis_size) target_hdr['shap'][1] = int(dec_axis_size) target_hdr['shap'][2] = int(freq_axis_size) return (target_hdr)
def import_and_align_mask( in_file=None, out_file=None, template=None, blank_to_match=False, ): """ Align a mask to a target astrometry. This includes some klugy steps (especially related to axes and interpolation) to make this work, e.g., for clean masks, most of the time. """ # Import from FITS (could make optional) os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') logger.debug('Importing mask file: "' + in_file + '"') casaStuff.importfits(fitsimage=in_file, imagename=out_file + '.temp_copy', overwrite=True) # Prepare analysis utility tool myia = au.createCasaTool(casaStuff.iatool) myim = au.createCasaTool(casaStuff.imtool) # Read mask data # myia.open(out_file+'.temp_copy') # mask = myia.getchunk(dropdeg=True) # myia.close() # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # note that here the mask is in F dimension order, not Pythonic. # print(np.max(mask), np.min(mask)) # print('**********************') # Read template image header hdr = casaStuff.imhead(template) # print('hdr', hdr) maskhdr = casaStuff.imhead(out_file + '.temp_copy') # print('maskhdr', maskhdr) # Check if 2D or 3D logger.debug('Template data axis names: ' + str(hdr['axisnames']) + ', shape: ' + str(hdr['shape'])) logger.debug('Mask data axis names: ' + str(maskhdr['axisnames']) + ', shape: ' + str(maskhdr['shape'])) is_template_2D = (np.prod(list(hdr['shape'])) == np.prod(list(hdr['shape'])[0:2])) is_mask_2D = (np.prod(list(maskhdr['shape'])) == np.prod(list(maskhdr['shape'])[0:2])) if is_template_2D and not is_mask_2D: logger.debug('Template image is 2D but mask is 3D, collapsing the mask over channel axes: ' + str( np.arange(maskhdr['ndim'] - 1, 2 - 1, -1))) # read mask array myia.open(out_file + '.temp_copy') mask = myia.getchunk(dropdeg=True) myia.close() # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # # collapse channel and higher axes # mask = np.any(mask.astype(int).astype(bool), axis=np.arange(maskhdr['ndim']-1, 2-1, -1)) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # mask = mask.astype(int) # while len(mask.shape) < len(hdr['shape']): # mask = np.expand_dims(mask, axis=len(mask.shape)) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') # os.system('rm -rf '+out_file+'.temp_collapsed'+' 2>/dev/null') ##myia.open(out_file+'.temp_copy') # newimage = myia.newimagefromarray(outfile=out_file+'.temp_collapsed', pixels=mask.astype(int), overwrite=True) # newimage.done() # myia.close() # # collapse channel and higher axes os.system('rm -rf ' + out_file + '.temp_collapsed' + ' 2>/dev/null') myia.open(out_file + '.temp_copy') collapsed = myia.collapse(outfile=out_file + '.temp_collapsed', function='max', axes=np.arange(maskhdr['ndim'] - 1, 2 - 1, -1)) collapsed.done() myia.close() # ia tools -- https://casa.nrao.edu/docs/CasaRef/image-Tool.html os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('cp -r ' + out_file + '.temp_collapsed' + ' ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('rm -rf ' + out_file + '.temp_collapsed' + ' 2>/dev/null') # Align to the template grid os.system('rm -rf ' + out_file + '.temp_aligned' + ' 2>/dev/null') casaStuff.imregrid(imagename=out_file + '.temp_copy', template=template, output=out_file + '.temp_aligned', asvelocity=True, interpolation='nearest', replicate=False, overwrite=True) # Make an EXACT copy of the template, avoids various annoying edge cases os.system('rm -rf ' + out_file + ' 2>/dev/null') myim.mask(image=template, mask=out_file) # Pull the data out of the aligned mask and place it in the output file myia.open(out_file + '.temp_aligned') mask = myia.getchunk(dropdeg=True) myia.close() # If requested, blank the mask wherever the cube is non-finite. if blank_to_match: myia.open(template) nans = np.invert(myia.getchunk(dropdeg=True, getmask=True)) myia.close() mask[nans] = 0.0 # Shove the mask into the data set if is_template_2D and not is_mask_2D: while len(mask.shape) < len(hdr['shape']): mask = np.expand_dims(mask, axis=len(mask.shape)) # print('**********************') # print('type(mask)', type(mask), 'mask.dtype', mask.dtype, 'mask.shape', mask.shape) # Note that here array shapes are in F dimension order, i.e., axis 0 is RA, axis 1 is Dec, axis 2 is Frequency, etc. # print('**********************') myia.open(out_file) data = myia.getchunk(dropdeg=False) data = mask myia.putchunk(data) myia.close() else: # Need to make sure this works for two dimensional cases, too. if (hdr['axisnames'][3] == 'Frequency') and (hdr['ndim'] == 4): myia.open(out_file) data = myia.getchunk(dropdeg=False) data[:, :, 0, :] = mask myia.putchunk(data) myia.close() elif (hdr['axisnames'][2] == 'Frequency') and (hdr['ndim'] == 4): myia.open(out_file) data = myia.getchunk(dropdeg=False) data[:, :, :, 0] = mask myia.putchunk(data) myia.close() else: logger.info("ALERT! Did not find a case.") os.system('rm -rf ' + out_file + '.temp_copy' + ' 2>/dev/null') os.system('rm -rf ' + out_file + '.temp_aligned' + ' 2>/dev/null') return ()
def prep_sd_for_feather(sdfile_in=None, sdfile_out=None, interf_file=None, do_import=True, do_dropdeg=True, do_align=True, do_checkunits=True, overwrite=False): """ Prepare single dish data for feathering. sdfile_in : the input single dish file. Can be a FITS file (with do_import) or a CASA image. sdfile_out : the output of the program. If all flags are called, this will be a CASA image on the same astrometric grid as the interferometric file with units of Jy/beam and no degenerate axes. interf_file : the interferometric file being used in the feather call. Used as a template here to astrometrically align the data. do_import (default True) : If True and the infile is a FITS file, then import the data from FITS. do_dropdeg (default True) : if True then pare degenerate axes from the signle dish file. In general this is a good idea for postprocessing, where mixing and matching degenerate axes causes many CASA routines to fail. do_align (default True) : If True then align the single dish data to the astrometric grid of the the interferometer file. do_checkunits (default True) : If True then check the units to make sure that they are in Jy/beam, which is required by feather. overwrite (default False) : Delete existing files. You probably want to set this to True but it's a user decision. """ # Check inputs if (os.path.isdir(interf_file) == False): logger.error("Interferometric file not found: " + interf_file) return (None) if (os.path.isdir(sdfile_in) == False) and (os.path.isfile(sdfile_in) == False): logger.error("Single dish file not found: " + sdfile_in) return (None) if sdfile_out is None: logger.error( "Output single dish file name not supplied via sdfile_out=") return (None) # Initialize file handling current_infile = sdfile_in current_outfile = sdfile_out tempfile_name = sdfile_out + '.temp' # Import from FITS if needed. Keep blanks as not-a-numbers. if do_import: if ((current_infile[-4:] == 'FITS') or \ (current_infile[-4:] == 'fits')) and \ os.path.isfile(current_infile): logger.info("Importing from FITS.") casaStuff.importfits(fitsimage=current_infile, imagename=current_outfile, zeroblanks=False, overwrite=overwrite) current_infile = current_outfile # Drop degenerate axes using a call to imsubimage if do_dropdeg: if current_infile == current_outfile: if os.path.isdir(tempfile_name) or os.path.isfile(tempfile_name): if overwrite: os.system('rm -rf ' + tempfile_name) else: logger.error( "Temp file needed but exists and overwrite=False - " + tempfile_name) return (None) os.system('cp -r ' + current_infile + ' ' + tempfile_name) current_infile = tempfile_name os.system('rm -rf ' + current_outfile) if os.path.isdir(current_outfile) or os.path.isfile(current_outfile): if overwrite: os.system('rm -rf ' + current_outfile) else: logger.error( "Output file needed exists and overwrite=False - " + current_outfile) return (None) casaStuff.imsubimage(imagename=current_infile, outfile=current_outfile, dropdeg=True) current_infile = current_outfile # Align the single dish data to the astrometric grid of the interferometric data if do_align: if current_infile == current_outfile: if os.path.isdir(tempfile_name) or os.path.isfile(tempfile_name): if overwrite: os.system('rm -rf ' + tempfile_name) else: logger.error( "Temp file needed but exists and overwrite=False - " + tempfile_name) return (None) os.system('cp -r ' + current_infile + ' ' + tempfile_name) current_infile = tempfile_name os.system('rm -rf ' + current_outfile) casaStuff.imregrid(imagename=current_infile, template=interf_file, output=current_outfile, asvelocity=True, axes=[-1], interpolation='cubic', overwrite=overwrite) current_infile = current_outfile # Check the units on the singledish file and convert from K to Jy/beam if needed. if do_checkunits: hdr = casaStuff.imhead(current_outfile, mode='list') unit = hdr['bunit'] if unit == 'K': logger.info("Unit is Kelvin. Converting.") ccr.convert_ktojy(infile=current_outfile, overwrite=overwrite, inplace=True) # Remove leftover temporary files. if (os.path.isdir(tempfile_name) or os.path.isfile(tempfile_name)): if overwrite: os.system('rm -rf ' + tempfile_name) return (None)