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 ()
예제 #5
0
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)