def multiply_cube_by_value(infile, value, brightness_unit, huge_cube_workaround=True):
    """
    Multiply a cube by some value, and update the brightness unit accordingly. Includes a switch for large cubes, where
    getchunk/putchunk may fail.
    """

    if huge_cube_workaround:
        casaStuff.exportfits(imagename=infile,
                             fitsimage=infile + '.fits',
                             overwrite=True)
        hdu = pyfits.open(infile + '.fits')[0]
        hdu.data *= value

        hdu.writeto(infile + '.fits', clobber=True)
        casaStuff.importfits(fitsimage=infile + '.fits',
                             imagename=infile,
                             overwrite=True)
        os.system('rm -rf ' + infile + '.fits')
    else:
        myia = au.createCasaTool(casaStuff.iatool)
        myia.open(infile)
        vals = myia.getchunk()
        vals *= value
        myia.putchunk(vals)
        myia.close()

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(infile)
    myia.setbrightnessunit(brightness_unit)
    myia.close()

    return True
def apply_additional_mask(
        old_mask_file=None,
        new_mask_file=None,
        new_thresh=0.0,
        operation='AND'
):
    """
    Combine a mask with another mask on the same grid and some
    threshold. Can run AND/OR operations. Can be used to apply primary
    beam based masks by setting the PB file to new_mask_file and the
    pb_limit as new_thresh.
    """
    if root_mask == None:
        logger.info("Specify a cube root file name.")
        return

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(new_mask_file)
    new_mask = myia.getchunk()
    myia.close()

    myia.open(old_mask_file)
    mask = myia.getchunk()
    if operation == "AND":
        mask *= (new_mask > new_thresh)
    else:
        mask = (mask + (new_mask > new_thresh)) >= 1.0
    myia.putchunk(mask)
    myia.close()

    return
def get_mask(infile, huge_cube_workaround=True):
    """
    Get a mask from a CASA image file. Includes a switch for large cubes, where getchunk can segfault.
    """

    if huge_cube_workaround:
        os.system('rm -rf ' + infile + '.temp_deg_ordered')
        casaStuff.imtrans(imagename=infile + '.temp_deg', outfile=infile + '.temp_deg_ordered',
                          order='0132')

        casaStuff.makemask(mode='copy', inpimage=infile + '.temp_deg_ordered',
                           inpmask=infile + '.temp_deg_ordered:mask0',
                           output=infile + '.temp_mask', overwrite=True)

        casaStuff.exportfits(imagename=infile + '.temp_mask',
                             fitsimage=infile + '.temp.fits',
                             stokeslast=False, overwrite=True)
        hdu = pyfits.open(infile + '.temp.fits')[0]
        mask = hdu.data.T[:, :, 0, :]

        os.system('rm -rf ' + infile + '.temp_deg_ordered')
        os.system('rm -rf ' + infile + '.temp_mask')
        os.system('rm -rf ' + infile + '.temp.fits')

    else:
        myia = au.createCasaTool(casaStuff.iatool)
        myia.open(infile+'.temp_deg')
        mask = myia.getchunk(getmask=True)
        myia.close()

    os.system('rm -rf ' + infile + '.temp_deg')

    return mask
def copy_mask(infile, outfile, huge_cube_workaround=True):
    """
    Copy a mask from infile to outfile. Includes a switch for large cubes, where getchunk/putchunk can segfault
    """

    if huge_cube_workaround:
        os.system('rm -rf ' + outfile + '/mask0')
        os.system('cp -r ' + infile + '/mask0' + ' ' + outfile + '/mask0')
    else:
        myia = au.createCasaTool(casaStuff.iatool)
        myia.open(infile)
        mask = myia.getchunk(getmask=True)
        myia.close()

        myia = au.createCasaTool(casaStuff.iatool)
        myia.open(outfile)
        mask = myia.putregion(pixelmask=mask)
        myia.close()

    return True
def calc_residual_statistics(
    resid_name=None,
    mask_name=None,
):
    """
    """

    if os.path.isdir(resid_name) == False:
        logger.error('Error! The input file "' + resid_name +
                     '" was not found!')
        return

    if os.path.isdir(mask_name) == False:
        logger.error('Error! The input file "' + mask_name +
                     '" was not found!')
        return

    myia = au.createCasaTool(casaStuff.iatool)

    myia.open(mask_name)
    mask = myia.getchunk()
    myia.close()

    myia.open(resid_name)
    resid = myia.getchunk()
    myia.close()

    vec = resid[((mask == 1) * np.isfinite(resid))]
    del mask
    del resid

    current_noise = cmr.noise_for_cube(infile=resid_name,
                                       method='chauvmad',
                                       niter=5)

    out_dict = {
        'cubename': resid_name,
        'maskname': mask_name,
        'max': np.max(vec),
        'p99': np.percentile(vec, 99),
        'p95': np.percentile(vec, 95),
        'p90': np.percentile(vec, 90),
        'noise': current_noise,
    }

    return (out_dict)
def noise_for_cube(
    infile=None,
    maskfile=None,
    exclude_mask=True,
    method='mad',
    niter=None,
):
    """
    Get a single noise estimate for an image cube.
    """

    if infile is None:
        logger.error('No infile specified.')
        return (None)

    if not os.path.isdir(infile) and not os.path.isfile(infile):
        logger.error('infile specified but not found - ' + infile)
        return (None)

    if maskfile is not None:
        if not os.path.isdir(maskfile) and not os.path.isfile(maskfile):
            logger.error('maskfile specified but not found - ' + maskfile)
            return (None)

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(infile)
    data = myia.getchunk()
    mask = myia.getchunk(getmask=True)
    myia.close()

    if maskfile is not None:
        myia.open(maskfile)
        user_mask = myia.getchunk()
        user_mask_mask = myia.getchunk(getmask=True)
        myia.close()
        if exclude_mask:
            mask = mask * user_mask_mask * (user_mask < 0.5)
        else:
            mask = mask * user_mask_mask * (user_mask >= 0.5)

    this_noise = estimate_noise(data=data,
                                mask=mask,
                                method=method,
                                niter=niter)

    return (this_noise)
def read_cube(infile, allow_huge=True):
    """
    Read cube from CASA image file. Includes a switch for large cubes, where getchunk may fail.
    """

    if allow_huge:
        casaStuff.exportfits(imagename=infile,
                             fitsimage=infile + '.fits',
                             stokeslast=False, overwrite=True)
        hdu = pyfits.open(infile + '.fits')[0]
        cube = hdu.data.T

        # Remove intermediate fits file
        os.system('rm -rf ' + infile + '.fits')
    else:
        myia = au.createCasaTool(casaStuff.iatool)
        myia.open(infile)
        cube = myia.getchunk()
        myia.close()

    return cube
def trim_rind(
        infile=None,
        outfile=None,
        overwrite=False,
        inplace=True,
        pixels=1
        ):

    if infile is None or outfile is None:
        logger.error("Missing required input.")
        return(False)

    if os.path.isdir(infile) == False:
        logger.error("Input file not found: "+infile)
        return(False)

    if inplace == False:
        if os.path.isdir(outfile) or os.path.isfile(outfile):
            if overwrite:
                os.system('rm -rf '+outfile)
            else:
                logger.error("Output file already present: "+outfile)
                return(False)

        os.system('cp -r '+infile+' '+outfile)
        target_file = outfile
    else:
        target_file = infile

    # Figure out the extent of the image inside the cube
    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(target_file)
    mask = myia.getchunk(getmask=True)
    elt = nd.generate_binary_structure(2,1)
    if pixels > 1:
        elt = nd.iterate_structure(elt, pixels-1)
    mask = nd.binary_erosion(mask, elt[:,:,np.newaxis, np.newaxis])
    myia.putregion(pixelmask=mask)
    myia.close()
    return(True)
예제 #9
0
# CASA imports
from taskinit import *
from importfits import importfits
from imregrid import imregrid
from imsmooth import imsmooth
from imval import imval
from immoments import immoments
from immath import immath
from makemask import makemask
from imhead import imhead
from imstat import imstat
from exportfits import exportfits
import analysisUtils as aU

mycl = aU.createCasaTool(cltool)
mycs = aU.createCasaTool(cstool)
myia = aU.createCasaTool(iatool)
myrg = aU.createCasaTool(rgtool)
myqa = aU.createCasaTool(qatool)

#####################
### Parameters
#####################
dir_proj = "/Users/saito/data/proj_jwu/"
image_co10 = dir_proj + "image_co10/co10.moment0"


#####################
### Functions
#####################
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 checksource(overwrite=True, verbose=False, subdir='', splitcal_vis=''):
    """
    Images the phasecal and check source in a manually-calibrated dataset and 
    reports statistics.  Expects to find the *.split.cal measurement set and 
    the corresponding .fluxscale file for it.
    Inputs:
    overwrite: if True, overwrite any existing image from a previous execution
    splitcal_vis: defaults to *.cal, but can be specified as list of strings, 
                  or a comma-delimited string
    Outputs:
    png image plots of each calibrator, and an ASCII file for each dataset
    The name of the ASCII file, and a list of pngs are returned.
    """
    # Read the dataset(s) and get properties
    if (splitcal_vis == ''):
        vislist = glob.glob('*.cal')
    else:
        if (type(splitcal_vis) == str):
            vislist = splitcal_vis.split(',')
        else:
            vislist = splitcal_vis
    print("Checking datasets: ", vislist)
    mymsmd = au.createCasaTool(msmdtool)
    if (len(subdir) > 0):
        if (os.path.exists(subdir)):
            if (subdir[-1] != '/'):
                subdir += '/'
        else:
            os.mkdir(subdir)
            if (subdir[-1] != '/'):
                subdir += '/'
    pnglist = []
    textfiles = []
    for vis in vislist:
        mymsmd.open(vis)
        freq = mymsmd.meanfreq(0, unit='GHz')
        # Check Source
        check = mymsmd.fieldsforintent('OBSERVE_CHECK_SOURCE*', True)[0]
        checkid = mymsmd.fieldsforintent('OBSERVE_CHECK_SOURCE*', False)[0]
        checkpos = mymsmd.phasecenter(checkid)
        # Phase calibrator
        phase = mymsmd.fieldsforintent('CALIBRATE_PHASE*', True)[0]
        phaseid = mymsmd.fieldsforintent('CALIBRATE_PHASE*', False)[0]
        phasepos = mymsmd.phasecenter(phaseid)
        if ('OBSERVE_TARGET#ON_SOURCE' in mymsmd.intents()):
            nScienceFields = len(
                mymsmd.fieldsforintent('OBSERVE_TARGET*', False))
            science = mymsmd.fieldsforintent('OBSERVE_TARGET*', True)[0]
            scienceid = mymsmd.fieldsforintent('OBSERVE_TARGET*', False)[0]
        else:
            nScienceFields = 0
        mymsmd.done()

        floatcell = au.pickCellSize(vis,
                                    maxBaselinePercentile=99,
                                    verbose=verbose)
        cell = au.pickCellSize(vis,
                               maxBaselinePercentile=99,
                               cellstring=True,
                               verbose=verbose)
        #        imsize = int(au.nextValidImsize(int(5.0/floatcell))) # valid when we only had checksources for synthBeam < 0.25
        imsize = int(
            au.nextValidImsize(
                int(
                    np.max([5.0, 5.0 * au.estimateSynthesizedBeam(vis)]) /
                    floatcell)))
        print("imsize = ", imsize)
        region = 'circle[[%dpix , %dpix], 15pix ]' % (int(
            imsize / 2), int(imsize / 2))

        if False:
            # original method (for bands 3-6 only)
            cell = str(np.round(0.015 * (100 / freq), 3)) + 'arcsec'
            if freq < 116.0:
                imsize = [320, 320]
                region = 'circle[[160pix , 160pix] ,15pix ]'
            else:
                imsize = [680, 680]
                region = 'circle[[340pix , 340pix] ,15pix ]'

        ###################################
        # IMAGE
        ###################################
        weighting = 'briggs'
        robust = 0.5
        niter = 50
        threshold = '0.0mJy'
        spw = ''
        separation = au.angularSeparationOfTwoFields(vis, checkid, phaseid)
        if (nScienceFields > 0):
            separation_pcal_science = au.angularSeparationOfTwoFields(
                vis, scienceid, phaseid)
            separation_check_science = au.angularSeparationOfTwoFields(
                vis, scienceid, checkid)

        fieldtype = ['checksource', 'phasecal']
        field = [check, phase]
        for i, cal in enumerate(field):
            if (not os.path.exists(cal + '_' + vis + '.image') or overwrite):
                os.system('rm -rf ' + cal + '_' + vis + '.*')
                if verbose:
                    print(
                        "Running tclean('%s', field='%s', cell=%s, imsize=%s, ...)"
                        % (vis, cal, str(cell), str(imsize)))
                tclean(vis=vis,
                       imagename=cal + '_' + vis,
                       field=cal,
                       spw=spw,
                       specmode='mfs',
                       deconvolver='hogbom',
                       imsize=imsize,
                       cell=cell,
                       weighting=weighting,
                       robust=robust,
                       niter=niter,
                       threshold=threshold,
                       interactive=False,
                       mask=region,
                       gridder='standard')
            png = subdir + fieldtype[i] + '_' + cal + '_' + vis + '.image.png'
            pnglist.append(png)
            au.imviewField(cal + '_' + vis + '.image',
                           radius=30 * floatcell,
                           contourImage=cal + '_' + vis + '.mask',
                           levels=[1],
                           plotfile=png)

        ###################################
        # ANALYZE
        ###################################
        ###########
        # PHASE
        ###########
        imagename = phase + '_' + vis
        if verbose:
            print("Running imfit('%s', region='%s')" %
                  (imagename + '.image', region))
        # Fit the phase source to get position and flux
        imagefit = imfit(imagename=imagename + '.image', region=region)
        fitresults = au.imfitparse(imagefit)

        # Compare the Positions
        phasepos_obs = au.direction2radec(phasepos)
        if fitresults is not None:
            phasepos_fit = ','.join(fitresults.split()[:2])
            phasepos_diff = au.angularSeparationOfStrings(
                phasepos_obs, phasepos_fit, verbose=False) * 3600.

        # Compare the Flux densities
        peakIntensity = au.imagePeak(imagename + '.image')
        selffluxfile = glob.glob('*.fluxscale')[0]
        fluxscaleResult = au.fluxscaleParseLog(selffluxfile, field=phase)
        if fluxscaleResult is not None:
            selfflux = fluxscaleResult[0][0]
            phaseflux_fit = float(fitresults.split()[2])
            phaseCoherence = 100 * peakIntensity / phaseflux_fit
            phaseflux_diff = 100 * (selfflux - phaseflux_fit) / selfflux

        # Print the final results and save to file
        textfile = subdir + 'calimage_results_' + vis + '.txt'
        textfiles.append(textfile)
        f = open(textfile, 'w')
        f.write(
            '\n*************************************************************\n\n'
        )
        line = 'CHECK_SOURCE IMAGE ANALYSIS REPORT (version %s)\n' % version(
            short=True)
        writeOut(f, line)
        info = au.getFitsBeam(imagename + '.image')
        synthBeam = (info[0] * info[1])**0.5
        if fitresults is None:
            line = "Phasecal %s: imfit failed" % (phase)
        elif fluxscaleResult is not None:
            line = "Phasecal %s: Position difference = %s arcsec = %s synth.beam, Flux %% difference = %s" % (
                phase, au.roundFiguresToString(phasepos_diff, 3),
                au.roundFiguresToString(phasepos_diff / synthBeam, 3),
                au.roundFiguresToString(phaseflux_diff, 3))
            writeOut(f, line)
            line = "    coherence = peakIntensity/fittedFluxDensity = %s%%" % (
                au.roundFiguresToString(phaseCoherence, 3))
        else:
            line = "Phasecal %s: Position difference = %s arcsec = %s synth.beam" % (
                phase, au.roundFiguresToString(phasepos_diff, 3),
                au.roundFiguresToString(phasepos_diff / synthBeam, 3))
        writeOut(f, line)
        f.close()
        if fluxscaleResult is None:
            print(
                "Full checksource analysis is not supported if there is no flux calibrator"
            )
            return textfiles, pnglist

        ###########
        # CHECK
        ###########
        imagename = check + '_' + vis
        # Fit the check source to get position and flux
        if verbose:
            print("Running imfit('%s', region='%s')" %
                  (imagename + '.image', region))
        imagefit = imfit(imagename=imagename + '.image', region=region)
        fitresults = au.imfitparse(imagefit, deconvolved=True)
        info = au.getFitsBeam(imagename + '.image')
        synthMajor, synthMinor = info[0:2]
        synthBeam = (info[0] * info[1])**0.5

        # Compare the Positions
        checkpos_obs = au.direction2radec(checkpos)
        if fitresults is not None:
            checkpos_fit = ','.join(fitresults.split()[:2])
            checkpos_diff = au.angularSeparationOfStrings(
                checkpos_obs, checkpos_fit, verbose=False) * 3600.

        # Compare the Flux densities
        selffluxfile = glob.glob('*.fluxscale')[0]
        results = au.fluxscaleParseLog(selffluxfile, field=check)
        peakIntensity = au.imagePeak(imagename + '.image')
        if (results is not None and fitresults is not None):
            selfflux = results[0][0]
            checkflux_fit = float(fitresults.split()[2])

            checkflux_diff = 100 * (selfflux - checkflux_fit) / selfflux
            checkCoherence = 100 * peakIntensity / checkflux_fit
        if fitresults is not None:
            if verbose:
                print("Checksource fitresults: ", fitresults)
            deconvolvedMajor = float(fitresults.split()[5])
            deconvolvedMinor = float(fitresults.split()[7])

        # Print the final results and save to file
        f = open(textfile, 'a')
        if fitresults is None:
            line = "Checksource %s: imfit failed" % (phase)
        else:
            if (results is not None):
                line = "\nChecksource %s: Position difference = %s arcsec = %s synth.beam, Flux %% difference = %s" % (
                    check, au.roundFiguresToString(checkpos_diff, 3),
                    au.roundFiguresToString(checkpos_diff / synthBeam, 3),
                    au.roundFiguresToString(checkflux_diff, 3))
                writeOut(f, line)
                line = "    coherence = peakIntensity/fittedFluxDensity = %s%%" % (
                    au.roundFiguresToString(checkCoherence, 3))
            else:
                line = "\nChecksource %s: Position difference = %s arcsec = %s synth.beam" % (
                    check, au.roundFiguresToString(checkpos_diff, 3),
                    au.roundFiguresToString(checkpos_diff / synthBeam, 3))
            writeOut(f, line)
            line = "    beam size = %s x %s arcsec" % (au.roundFiguresToString(
                synthMajor, 3), au.roundFiguresToString(synthMinor, 3))
            writeOut(f, line)
            line = "    apparent deconvolved size = %s x %s arcsec = %s synth.beam area" % (
                au.roundFiguresToString(deconvolvedMajor, 2),
                au.roundFiguresToString(deconvolvedMinor, 2),
                au.roundFiguresToString(
                    deconvolvedMajor * deconvolvedMinor / (synthBeam**2), 2))
        writeOut(f, line)
        line = "    angular separation of phasecal to checksource = %s degree" % (
            au.roundFiguresToString(separation, 3))
        writeOut(f, line)
        if (nScienceFields > 0):
            if (nScienceFields > 1):
                modifier = 'first'
            else:
                modifier = 'only'
            line = "    angular separation of phasecal to %s science field (%d) = %s degree" % (
                modifier, scienceid,
                au.roundFiguresToString(separation_pcal_science, 3))
            writeOut(f, line)
            line = "    angular separation of checksource to %s science field (%d) = %s degree" % (
                modifier, scienceid,
                au.roundFiguresToString(separation_check_science, 3))
            writeOut(f, line)
        f.close()
    # end 'for' loop over vislist
    return textfiles, pnglist
def generate_weight_file(
    image_file=None,
    input_file=None,
    input_value=None,
    input_type='pb',
    outfile=None,
    scale_by_noise=False,
    mask_for_noise=None,
    noise_value=None,
    scale_by_factor=None,
    overwrite=False,
):
    """
    Generate a weight image for use in a linear mosaic. The weight
    image will be used in the linear mosaicking as a weight,
    multiplied by the image file and then divided out. The optimal S/N
    choice is often 1/noise^2. The program gives some options to
    calculate a weight image of this type from the data or using the
    primary beam response.

    image_file : the data cube or image associated with the
    weighting. Used for noise calculation and to supply a template
    astrometry. Not always necessary.

    input_file : an input image of some type (see below) used to form
    the basis for the weight.

    input_value : an input value of some type (see below) used to form
    the basis for the weight. This is a single value. Only one of
    input_file or input_value can be set.

    input_type : the type of input. This can be

    - 'pb' for primary beam response (weight goes at pb^2)
    - 'noise' for a noise estimate (weight goes as 1/noise^2)
    - 'weight' for a weight value
    
    outfile : the name of the output weight file to write

    scale_by_noise (default False) : if True, then scale the weight
    image by 1/noise^2 . Either the noise_value is supplied or it will
    be calculated from the image.
    
    mask_for_noise : if supplied, the "True" values in this mask image
    will be passed to the noise estimation routine and excluded from
    the noise calculation.

    noise_value : the noise in the image. If not set, this will be
    calculated from the image.

    scale_by_factor : a factor that will be applied directly to the
    weight image.
    
    overwrite (default False) : Delete existing files. You probably
    want to set this to True but it's a user decision.

    """

    # Check input

    if image_file is None and input_file is None:
        logger.error("I need either an input or an image template file.")
        return (None)

    if input_file is None and input_value is None:
        logger.error("I need either an input value or an input file.")
        return (None)

    if input_file is not None and input_value is not None:
        logger.error(
            "I need ONE OF an input value or an input file. Got both.")
        return (None)

    if outfile is None:
        logger.error("Specify output file.")
        return (None)

    if input_file is not None:
        valid_types = ['pb', 'noise', 'weight']
        if input_type not in valid_types:
            logger.error("Valid input types are :" + str(valid_types))
            return (None)

    if input_file is None and input_value is None:
        logger.error("Need either an input value or an input file.")
        return (None)

    if input_file is not None:
        if not os.path.isdir(input_file):
            logger.error("Missing input file directory - " + input_file)
            return (None)

    if image_file is not None:
        if not os.path.isdir(image_file):
            logger.error("Missing image file directory - " + image_file)
            return (None)

    # If scaling by noise is requested and no estimate is provided,
    # generate an estimate

    if scale_by_noise:

        if noise_value is None and image_file is None:
            logger.error(
                "I can only scale by the noise if I have a noise value or an image."
            )
            return (None)

        if noise_value is None:

            logger.info("Calculating noise for " + image_file)

            # Could use kwargs here to simplify parameter passing. Fine right now, too.

            noise_value = cma.noise_for_cube(
                infile=image_file,
                maskfile=mask_for_noise,
                exclude_mask=True,
                method='chauvmad',
                niter=5,
            )

            logger.info("Noise " + str(noise_value))

    # Define the template for the astrometry

    if input_file is None:
        template = image_file
    else:
        template = input_file

    logger.debug('Template for weight file is: ' + template)

    # Check the output file

    if os.path.isdir(outfile) or os.path.isfile(outfile):
        if not overwrite:
            logger.error("File exists and overwrite set to false - " + outfile)
            return (None)
        os.system('rm -rf ' + outfile)

    # Copy the template and read the data into memory

    os.system("cp -r " + template + " " + outfile)

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(outfile)
    data = myia.getchunk()

    # Case 1 : We just have an input value.

    if input_file is None and input_value is not None:

        if input_type is 'noise':
            weight_value = 1. / input_value**2
        if input_type is 'pb':
            weight_value = input_value**2
        if input_type is 'weight':
            weight_value = input_value

        weight_image = data * 0.0 + weight_value

    # Case 2 : We have an input image. Read in the data and manipulate
    # it into a weight array.

    if input_file is not None:

        if input_type is 'noise':
            weight_image = 1. / data**2
        if input_type is 'pb':
            weight_image = data**2
        if input_type is 'weight':
            weight_image = data

    # Now we have a weight image. If request, scale the data by a factor.

    if scale_by_factor is not None:

        weight_image = weight_image * scale_by_factor

    # If request, scale the data by the inverse square of the noise estimate.

    if scale_by_noise:

        weight_image = weight_image * 1. / noise_value**2

    # Put the data back into the file and close it.
    myia.putchunk(weight_image)
    myia.close()

    return (None)
def trim_cube(
        infile=None,
        outfile=None,
        overwrite=False,
        inplace=False,
        min_pixperbeam=3,
        pad=1
    ):
    """
    Trim empty space from around the edge of a cube. Also rebin the
    cube to smaller size, while ensuring a minimum number of pixels
    across the beam. Used to reduce the volume of cubes.
    """

    if infile is None or outfile is None:
        logger.error("Missing required input.")
        return(False)

    if os.path.isdir(infile) == False:
        logger.error("Input file not found: "+infile)
        return(False)

    # First, rebin if needed
    hdr = casaStuff.imhead(infile)
    if (hdr['axisunits'][0] != 'rad'):
        logger.error("ERROR: Based on CASA experience. I expected units of radians. I did not find this. Returning.")
        logger.error("Adjust code or investigate file "+infile)
        return(False)

    pixel_as = abs(hdr['incr'][0]/np.pi*180.0*3600.)

    if (hdr['restoringbeam']['major']['unit'] != 'arcsec'):
        logger.error("ERROR: Based on CASA experience. I expected units of arcseconds for the beam. I did not find this. Returning.")
        logger.error("Adjust code or investigate file "+infile)
        return(False)
    bmaj = hdr['restoringbeam']['major']['value']

    pix_per_beam = bmaj*1.0 / pixel_as*1.0

    if pix_per_beam > 6:
        casaStuff.imrebin(
            imagename=infile,
            outfile=outfile+'.temp',
            factor=[2,2,1],
            crop=True,
            dropdeg=True,
            overwrite=overwrite,
            )
    else:
        os.system('cp -r '+infile+' '+outfile+'.temp')

    # Figure out the extent of the image inside the cube

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(outfile + '.temp')
    myia.adddegaxes(outfile=outfile + '.temp_deg', stokes='I', overwrite=True)
    myia.close()

    mask = get_mask(outfile, huge_cube_workaround=True)

    this_shape = mask.shape

    mask_spec_x = np.sum(np.sum(mask*1.0,axis=2),axis=1) > 0
    xmin = np.max([0,np.min(np.where(mask_spec_x))-pad])
    xmax = np.min([np.max(np.where(mask_spec_x))+pad,mask.shape[0]-1])

    mask_spec_y = np.sum(np.sum(mask*1.0,axis=2),axis=0) > 0
    ymin = np.max([0,np.min(np.where(mask_spec_y))-pad])
    ymax = np.min([np.max(np.where(mask_spec_y))+pad,mask.shape[1]-1])

    mask_spec_z = np.sum(np.sum(mask*1.0,axis=0),axis=0) > 0
    zmin = np.max([0,np.min(np.where(mask_spec_z))-pad])
    zmax = np.min([np.max(np.where(mask_spec_z))+pad,mask.shape[2]-1])

    box_string = ''+str(xmin)+','+str(ymin)+','+str(xmax)+','+str(ymax)
    chan_string = ''+str(zmin)+'~'+str(zmax)

    logger.info("... box selection: "+box_string)
    logger.info("... channel selection: "+chan_string)

    if overwrite:
        os.system('rm -rf '+outfile)
        casaStuff.imsubimage(
            imagename=outfile+'.temp',
            outfile=outfile,
            box=box_string,
            chans=chan_string,
            )

    os.system('rm -rf '+outfile+'.temp')

    return(True)
예제 #14
0
def feather_two_cubes(
    interf_file=None,
    sd_file=None,
    out_file=None,
    do_blank=False,
    do_apodize=False,
    apod_file=None,
    apod_cutoff=-1.0,
    overwrite=False,
):
    """
    Feather together interferometric and total power data using CASA's
    default approach. Optionally, first apply some steps to homogenize
    the two data sets. Assumes that the data have been prepared e.g.,
    using the prep_sd_for_feather routine in this module.

    interf_file : the interferometric cube to feather.

    sd_file : the single dish cube to feather.

    out_file : the output file name

    do_blank (default False) : if True then blank masked and not-a-number
    values in the cubes. The idea is to make sure missing data are
    zero for the FFT (probably not an issue) and to make sure some
    regions don't appear in one map but not the other. This is
    probably not necessary, but CASA's treatment in feather is a bit
    unclear and the case of, e.g., a much more extended single dish
    map compared to the interferometer map comes up.

    do_apodize (default False) : if True, then apodize BOTH data sets
    using the provided apodization map.

    apod_file : if do_apodize is True, this file is the map used to scale
    both the interferometer and single dish data.

    apod_cutoff : the cutoff in the apodization file below which data
    are blanked.

    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(sd_file) == False):
        logger.error("Single dish file not found: " + sd_file)
        return (False)

    if (os.path.isdir(interf_file) == False):
        logger.error("Interferometric file not found: " + interf_file)
        return (False)

    if do_apodize:
        if (os.path.isdir(apod_file) == False):
            logger.error("Apodization requested, but file not found: " +
                         apod_file)
            return (False)

    # Initialize file handling

    os.system('rm -rf ' + sd_file + '.temp')
    os.system('rm -rf ' + interf_file + '.temp')
    os.system('rm -rf ' + out_file + '.temp')

    os.system('rm -rf ' + sd_file + '.temp.temp')
    os.system('rm -rf ' + interf_file + '.temp.temp')
    os.system('rm -rf ' + out_file + '.temp.temp')

    # If requested, manipulate blanked (NaN and mask) values to make
    # sure they are zeros. This should probably not be necessary, but
    # some aspects of CASA's procedures are unclear. The main thing
    # here is that the mask used is the COMBINED single dish and
    # interferometer map, so that only regions in common should
    # survive.

    if do_blank:

        current_interf_file = interf_file + '.temp'
        current_sd_file = sd_file + '.temp'

        os.system('cp -rf ' + interf_file + ' ' + current_interf_file)
        os.system('cp -rf ' + sd_file + ' ' + current_sd_file)

        myia = au.createCasaTool(casaStuff.iatool)

        myia.open(interf_file)
        interf_mask = myia.getchunk(getmask=True)
        myia.close()

        myia.open(sd_file)
        sd_mask = myia.getchunk(getmask=True)
        myia.close()

        # CASA calls unmasked values True and masked values False. The
        # region with values in both cubes is the product.

        combined_mask = sd_mask * interf_mask

        # This isn't a great solution. Just zero out the masked
        # values. It will do what we want in the FFT but the CASA mask
        # bookkeeping is being left in the dust. The workaround is
        # complicated, though, because you can't directly manipulate
        # pixel masks for some reason.

        if np.sum(combined_mask == False) > 0:
            myia.open(current_interf_file)
            interf_data = myia.getchunk()
            interf_data[combined_mask == False] = 0.0
            myia.putchunk(interf_data)
            myia.close()

            myia.open(current_sd_file)
            sd_data = myia.getchunk()
            sd_data[combined_mask == False] = 0.0
            myia.putchunk(sd_data)
            myia.close()

    else:
        current_interf_file = interf_file
        current_sd_file = sd_file

    # If apodization is requested, multiply both data sets by the same
    # taper and create a new, temporary output data set.

    if do_apodize:

        casaStuff.impbcor(imagename=current_sd_file,
                          pbimage=apod_file,
                          outfile=current_sd_file + '.temp',
                          mode='multiply')
        current_sd_file = current_sd_file + '.temp'

        casaStuff.impbcor(imagename=current_interf_file,
                          pbimage=apod_file,
                          outfile=current_interf_file + '.temp',
                          mode='multiply')
        current_interf_file = current_interf_file + '.temp'

    # Call feather, followed by an imsubimage to deal with degenerate
    # axis stuff.

    if overwrite:
        os.system('rm -rf ' + out_file)
    os.system('rm -rf ' + out_file + '.temp')
    casaStuff.feather(imagename=out_file + '.temp',
                      highres=current_interf_file,
                      lowres=current_sd_file,
                      sdfactor=1.0,
                      lowpassfiltersd=False)
    casaStuff.imsubimage(imagename=out_file + '.temp',
                         outfile=out_file,
                         dropdeg=True)
    os.system('rm -rf ' + out_file + '.temp')

    # If we apodized, now divide out the common kernel.

    if do_apodize:
        os.system('rm -rf ' + out_file + '.temp')
        os.system('mv ' + out_file + ' ' + out_file + '.temp')
        casaStuff.impbcor(imagename=out_file + '.temp',
                          pbimage=apod_file,
                          outfile=out_file,
                          mode='divide',
                          cutoff=apod_cutoff)

    # Remove temporary files

    os.system('rm -rf ' + sd_file + '.temp')
    os.system('rm -rf ' + interf_file + '.temp')
    os.system('rm -rf ' + out_file + '.temp')

    os.system('rm -rf ' + sd_file + '.temp.temp')
    os.system('rm -rf ' + interf_file + '.temp.temp')
    os.system('rm -rf ' + out_file + '.temp.temp')

    return (True)
예제 #15
0
    def editIntents(msName='', field='', scan='', newintents='', help=False,
                    append=False):
        """
        Change the observation intents for a specified field.  Adapted from
        John Lightfoot's interactive function for the ALMA pipeline.
        For further help and examples, run editIntents(help=True) or
        see http://casaguides.nrao.edu/index.php?title=EditIntents
        - T. Hunter
        """
        validIntents = ['AMPLITUDE', 'ATMOSPHERE', 'BANDPASS', 'DELAY', 'FLUX',
                        'PHASE', 'SIDEBAND_RATIO', 'TARGET', 'WVR',
                        'POL_ANGLE',
                        'POL_LEAKAGE', 'CALIBRATE_AMPLI', 'SYS_CONFIG',
                        'CALIBRATE_ATMOSPHERE', 'CALIBRATE_BANDPASS',
                        'CALIBRATE_DELAY', 'CALIBRATE_FLUX', 'CALIBRATE_PHASE',
                        'CALIBRATE_SIDEBAND_RATIO', 'OBSERVE_TARGET',
                        'CALIBRATE_WVR', 'CALIBRATE_POL_ANGLE', 'CALIBRATE_POL_LEAKAGE']

        if help:
            print("Use editIntents?? to see the call sequence.")
            return

        if msName == '':
            raise TypeError('msName must be given.')
        if field == '':
            raise TypeError('field must be given')

        mytb = au.createCasaTool(tbtool)
        mytb.open(msName + '/FIELD')
        fieldnames = mytb.getcol('NAME')
        print("Found fieldnames = {}".format(fieldnames))
        mytb.close()

        mytb.open(msName + '/STATE')
        intentcol = mytb.getcol('OBS_MODE')
        intentcol = intentcol
        mytb.close()

        mytb.open(msName, nomodify=False)
        naddedrows = 0
        if isinstance(newintents, list):
            desiredintents = ''
            for n in newintents:
                desiredintents += n
                if (n != newintents[-1]):
                    desiredintents += ','
        else:
            desiredintents = newintents
        desiredintentsList = desiredintents.split(',')

        for intent in desiredintentsList:
            if intent not in validIntents:
                print "Invalid intent = %s.  Valid intents = %s" % \
                    (intent, validIntents)
                mytb.close()
                return

        foundField = False

        if not isinstance(scan, list):
            scan = str(scan)
        for id, name in enumerate(fieldnames):
          if name == field or id == field:
            foundField = True
            print 'FIELD_ID %s has name %s' % (id, name)
            if scan == '':
                s = mytb.query('FIELD_ID==%s' % id)
                print "Got %d rows in the ms matching field=%s" \
                    % (s.nrows(), id)
            else:
                if isinstance(scan, str):
                    scans = [int(x) for x in scan.split(',')]
                elif isinstance(scan, list):
                    scans = [scan]
                else:
                    scans = scan
                print "Querying: 'FIELD_ID==%s AND SCAN_NUMBER in %s'" \
                    % (id, str(scans))
                s = mytb.query('FIELD_ID==%s AND SCAN_NUMBER in %s'
                               % (id, str(scans)))
                print "Got %d rows in the ms matching field=%s and scan in %s" % \
                    (s.nrows(), id, str(scans))
            if s.nrows() == 0:
                mytb.close()
                print "Found zero rows for this field (and/or scan). Stopping."
                return
            state_ids = s.getcol('STATE_ID')
            # original code from J. Lightfoot, can probably be replaced
            # by the np.unique() above
            states = []
            for state_id in state_ids:
                if state_id not in states:
                    states.append(state_id)
    #        print "states = ", states
            for ij in range(len(states)):
                states[ij] = intentcol[states[ij]]
            print 'current intents are:'
            for state in states:
                print state

            if not append:
                states = []
            for desiredintent in desiredintentsList:
                if desiredintent.find('TARGET') >= 0:
                    states.append('OBSERVE_TARGET#UNSPECIFIED')
                elif desiredintent.find('BANDPASS') >= 0:
                    states.append('CALIBRATE_BANDPASS#UNSPECIFIED')
                elif desiredintent.find('PHASE') >= 0:
                    states.append('CALIBRATE_PHASE#UNSPECIFIED')
                elif desiredintent.find('AMPLI') >= 0:
                    states.append('CALIBRATE_AMPLI#UNSPECIFIED')
                elif desiredintent.find('FLUX') >= 0:
                    states.append('CALIBRATE_FLUX#UNSPECIFIED')
                elif desiredintent.find('ATMOSPHERE') >= 0:
                    states.append('CALIBRATE_ATMOSPHERE#UNSPECIFIED')
                elif desiredintent.find('WVR') >= 0:
                    states.append('CALIBRATE_WVR#UNSPECIFIED')
                elif desiredintent.find('SIDEBAND_RATIO') >= 0:
                    states.append('CALIBRATE_SIDEBAND_RATIO#UNSPECIFIED')
                elif desiredintent.find('DELAY') >= 0:
                    states.append('CALIBRATE_DELAY#UNSPECIFIED')
                elif desiredintent.find('POL_ANGLE') >= 0:
                    states.append('CALIBRATE_POL_ANGLE#UNSPECIFIED')
                elif desiredintent.find('POL_LEAKAGE') >= 0:
                    states.append('CALIBRATE_POL_LEAKAGE#UNSPECIFIED')
                elif desiredintent.find('SYS_CONFIG') >= 0:
                    states.append('SYSTEM_CONFIGURATION#UNSPECIFIED')
                else:
                    print "Unrecognized intent = %s" % desiredintent
                    continue
                print 'setting %s' % (states[-1])

            if states != []:
                state = reduce(lambda x, y: '%s,%s' % (x, y), states)
                if state not in intentcol:
                    print 'adding intent to state table'
                    intentcol = list(intentcol)
                    intentcol.append(state)
                    intentcol = np.array(intentcol)
                    state_id = len(intentcol) - 1
                    naddedrows += 1
                    print 'state_id is', state_id
                    state_ids[:] = state_id
                else:
                    print 'intent already in state table'
                    state_id = np.arange(len(intentcol))[intentcol == state]
                    print 'state_id is', state_id
                    if isinstance(state_id, list) or isinstance(state_id, np.ndarray):
                        # ms can have identical combinations of INTENT, so just
                        # pick the row for the first appearance - T. Hunter
                        state_ids[:] = state_id[0]
                    else:
                        state_ids[:] = state_id
                s.putcol('STATE_ID', state_ids)
        if not foundField:
            print "Field not found"
            return
        mytb.close()

        print 'writing new STATE table'
        mytb.open(msName + '/STATE', nomodify=False)
        if naddedrows > 0:
            mytb.addrows(naddedrows)
        mytb.putcol('OBS_MODE', intentcol)
        mytb.close()
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
import matplotlib.patches as patches
from astropy.io import fits

# CASA imports
from taskinit import *
from immoments import immoments
from immath import immath
from makemask import makemask
from imhead import imhead
from imstat import imstat
from exportfits import exportfits
import analysisUtils as aU
mycl = aU.createCasaTool(cltool)
myia = aU.createCasaTool(iatool)
myrg = aU.createCasaTool(rgtool)


#####################
### Main Procedure
#####################
def createmask(dir_data, imagename, thres, outmask="mask1.image", pixelmin=5.):
    #create mask with thres
    outfile = dir_data + outmask
    os.system("rm -rf " + outfile)
    immath(imagename=dir_data + imagename,
           mode="evalexpr",
           expr="iif(IM0 >= " + str(thres) + ", 1.0, 0.0)",
           outfile=outfile)
def calculate_mosaic_extent(
    infile_list=None,
    force_ra_ctr=None,
    force_dec_ctr=None,
    force_freq_ctr=None,
):
    """
    Given a list of input files, calculate the center and extent of
    the mosaic needed to cover them all. Return the results as a
    dictionary.

    infile_list : list of input files to loop over.

    force_ra_ctr (default None) : if set then force the RA center of
    the mosaic to be this value, and the returned extent is the
    largest separation of any image corner from this value in RA.

    force_dec_ctr (default None) : as force_ra_ctr but for
    Declination.

    If the RA and Dec. centers are supplied, then they are assumed to
    be in decimal degrees.
    """

    # Check inputs

    if infile_list is None:
        logger.error("Missing required infile_list.")
        return (None)

    for this_infile in infile_list:
        if not os.path.isdir(this_infile):
            logger.error("File not found " + this_infile + "Returning.")
            return (None)

    # Initialize the list of corner RA and Dec positions.

    ra_list = []
    dec_list = []
    # TBD - right now we assume matched frequency/velocity axis
    freq_list = []

    # Loop over input files and calculate RA and Dec coordinates of
    # the corners.

    myia = au.createCasaTool(casaStuff.iatool)

    for this_infile in infile_list:

        this_hdr = casaStuff.imhead(this_infile)

        if this_hdr['axisnames'][0] != 'Right Ascension':
            logger.error("Expected axis 0 to be Right Ascension. Returning.")
            return (None)
        if this_hdr['axisunits'][0] != 'rad':
            logger.error("Expected axis units to be radians. Returning.")
            return (None)
        if this_hdr['axisnames'][1] != 'Declination':
            logger.error("Expected axis 1 to be Declination. Returning.")
            return (None)
        if this_hdr['axisunits'][1] != 'rad':
            logger.error("Expected axis units to be radians. Returning.")
            return (None)

        this_shape = this_hdr['shape']
        xlo = 0
        xhi = this_shape[0] - 1
        ylo = 0
        yhi = this_shape[1] - 1

        pixbox = str(xlo) + ',' + str(ylo) + ',' + str(xlo) + ',' + str(ylo)
        blc = casaStuff.imval(this_infile, stokes='I', box=pixbox)

        pixbox = str(xlo) + ',' + str(yhi) + ',' + str(xlo) + ',' + str(yhi)
        tlc = casaStuff.imval(this_infile, stokes='I', box=pixbox)

        pixbox = str(xhi) + ',' + str(yhi) + ',' + str(xhi) + ',' + str(yhi)
        trc = casaStuff.imval(this_infile, stokes='I', box=pixbox)

        pixbox = str(xhi) + ',' + str(ylo) + ',' + str(xhi) + ',' + str(ylo)
        brc = casaStuff.imval(this_infile, stokes='I', box=pixbox)

        ra_list.append(blc['coords'][:, 0])
        ra_list.append(tlc['coords'][:, 0])
        ra_list.append(trc['coords'][:, 0])
        ra_list.append(brc['coords'][:, 0])

        dec_list.append(blc['coords'][:, 1])
        dec_list.append(tlc['coords'][:, 1])
        dec_list.append(trc['coords'][:, 1])
        dec_list.append(brc['coords'][:, 1])

        freq_list.append(blc['coords'][:, 2])
        freq_list.append(tlc['coords'][:, 2])
        freq_list.append(trc['coords'][:, 2])
        freq_list.append(brc['coords'][:, 2])

    # Get the minimum and maximum RA and Declination.

    # TBD - this breaks straddling the meridian (RA = 0) or the poles
    # (Dec = 90). Add catch cases or at least error calls for
    # this. Meridian seems more likely to come up, so just that is
    # probably fine.

    min_ra = np.min(np.concatenate(ra_list))
    max_ra = np.max(np.concatenate(ra_list))
    min_dec = np.min(np.concatenate(dec_list))
    max_dec = np.max(np.concatenate(dec_list))

    # TBD - right now we assume matched frequency/velocity axis

    min_freq = np.min(np.concatenate(freq_list))
    max_freq = np.max(np.concatenate(freq_list))

    # If we do not force the center of the mosaic, then take it to be
    # the average of the min and max, so that the image will be a
    # square.

    if force_ra_ctr == None:
        ra_ctr = (max_ra + min_ra) * 0.5
    else:
        ra_ctr = force_ra_ctr * np.pi / 180.

    if force_dec_ctr == None:
        dec_ctr = (max_dec + min_dec) * 0.5
    else:
        dec_ctr = force_dec_ctr * np.pi / 180.

    if force_freq_ctr == None:
        freq_ctr = (max_freq + min_freq) * 0.5
    else:
        freq_ctr = force_freq_ctr

    # Now calculate the total extent of the mosaic given the center.

    delta_ra = 2.0 * np.max([np.abs(max_ra - ra_ctr), np.abs(min_ra - ra_ctr)])
    delta_ra *= np.cos(dec_ctr)
    delta_dec = 2.0 * np.max(
        [np.abs(max_dec - dec_ctr),
         np.abs(min_dec - dec_ctr)])
    delta_freq = 2.0 * np.max(
        [np.abs(max_freq - freq_ctr),
         np.abs(min_freq - freq_ctr)])

    # Put the output into a dictionary.

    output = {
        'ra_ctr': [ra_ctr * 180. / np.pi, 'degrees'],
        'dec_ctr': [dec_ctr * 180. / np.pi, 'degrees'],
        'delta_ra': [delta_ra * 180. / np.pi * 3600., 'arcsec'],
        'delta_dec': [delta_dec * 180. / np.pi * 3600., 'arcsec'],
        'freq_ctr': [freq_ctr, 'Hz'],
        'delta_freq': [delta_freq, 'Hz'],
    }
    return (output)
def offset(workingdir,
           vis='',
           plotfile='',
           imfitlog=False,
           spw='',
           verbose=False):
    """
    Takes a pipeline working directory and find all images of the checksource 
    and produces a plot showing the relative directions of the first two science 
    targets, the phase calibrator, and the checksource, and a vector
    showing the offset of the checksource from its catalog position (computed
    using the results of the CASA task imfit), and a
    text label showing the RAO and DECO offsets.
    workingdir: path to pipeline working directory
    vis: alternate location for a measurement set to consult (ignores *_target.ms)
    Looks first for *chk*iter2.image; if not found, then *chk*iter1.image
    plotfile: default = img+'_offset.png'
    imfitlog: if True, then request imfit to generate log files (*.imfit)
    spw: int or comma-delimited string, if specified, limit to this or these spws
    verbose: print more messages explaining what images it is operating on
    """
    mymsmd = au.createCasaTool(msmdtool)
    if verbose:
        print("workingdir: ", workingdir)
    imglist = sorted(glob.glob(os.path.join(workingdir, '*_chk.spw*image')))
    if len(imglist) == 0:
        print("No check source images found in this directory.")
        return
    # If iter2.image is found, then drop the iter1 version from the list
    for i in imglist:
        if i.find('iter2') > 0:
            imglist.remove(i.replace('iter2', 'iter1'))
    if verbose:
        print("Processing %d images:" % (len(imglist)))
        for i in imglist:
            print(i)
    if vis == '':
        searchpath = os.path.join(workingdir, '*.ms')
        if verbose:
            print("searchpath: ", searchpath)
        allvislist = sorted(glob.glob(searchpath))
        if verbose:
            print("all vis found: ", allvislist)
        vislist = []
        for vis in allvislist:
            if vis.find('_target') < 0:
                vislist.append(vis)
    else:
        vislist = [vis]

    raos = []
    decos = []
    totals = []
    sourcenames = []
    spws = au.parseSpw(vis, spw)
    scienceSpws = au.getScienceSpws(vis, returnString=False)
    spws = np.intersect1d(scienceSpws, spws)
    if verbose:
        print("using spws: ", spws)
    newimglist = []
    for img in imglist:  # there will be an image for each spw
        if img.find('spw') > 0 and spw != '':
            myspw = int(img.split('spw')[1].split('.')[0])
            if myspw in spws:
                sourcenames.append(au.imageSource(img))
                newimglist.append(img)
                if verbose:
                    print("Using %s" % (img))
            elif verbose:
                print("Skipping %s" % (img))
        else:
            sourcenames.append(au.imageSource(img))
            newimglist.append(img)
    sourcenames = np.unique(sourcenames)
    pngs = []
    print("vislist = ", vislist)
    imglist = newimglist
    for sourcename in sourcenames:
        for ispw, img in enumerate(
                imglist):  # there will be an image for each spw
            if 'spw' not in img:
                print("No spw in the image name: ", img)
                continue
            spw = int(img.split('spw')[1].split('.')[0])
            # find the first vis that observed this target as check source
            checkid = -1
            for vis in vislist:
                #                print "Checking ", vis
                mymsmd.open(vis)
                if spw >= mymsmd.nspw():
                    print("Guessing that spw %d is spw %d in the split ms." %
                          (spw, ispw))
                    spw = ispw
                if 'OBSERVE_CHECK_SOURCE#ON_SOURCE' in mymsmd.intents():
                    checksources = mymsmd.fieldsforintent(
                        'OBSERVE_CHECK_SOURCE*', True)
                else:
                    checksources = mymsmd.fieldsforintent(
                        'CALIBRATE_DELAY*', True)
                if sourcename in checksources:
                    check = checksources[0]
                    checkid = mymsmd.fieldsforname(sourcename)[0]
                    checkpos = mymsmd.phasecenter(checkid)
                    # Phase calibrator
                    phase = mymsmd.fieldsforintent('CALIBRATE_PHASE*', True)[0]
                    phaseid = mymsmd.fieldsforintent('CALIBRATE_PHASE*',
                                                     False)[0]
                    phasepos = mymsmd.phasecenter(phaseid)
                    if ('OBSERVE_TARGET#ON_SOURCE' in mymsmd.intents()):
                        nScienceFields = len(
                            mymsmd.fieldsforintent('OBSERVE_TARGET*', False))
                        science = mymsmd.fieldsforintent(
                            'OBSERVE_TARGET*', True)[0]
                        scienceid = mymsmd.fieldsforintent(
                            'OBSERVE_TARGET*', False)[0]
                        sciencepos = mymsmd.phasecenter(scienceid)
                        if nScienceFields > 1:
                            science2 = mymsmd.fieldsforintent(
                                'OBSERVE_TARGET*', True)[1]
                            science2id = mymsmd.fieldsforintent(
                                'OBSERVE_TARGET*', False)[1]
                            science2pos = mymsmd.phasecenter(science2id)
                    else:
                        nScienceFields = 0
                    rxBand = mymsmd.namesforspws(spw)[0].split('#')[1].split(
                        '_')[-1].lstrip('0')  # string
                    break
                else:
                    mymsmd.close()
            if checkid < 0:
                print(
                    "Could not find an ms that observed this check source: %s. Try including the vis parameter."
                    % (sourcename))
                continue
            info = au.getFitsBeam(img)
            imsize = info[5]  # size in RA direction
            region = 'circle[[%dpix , %dpix], 15pix ]' % (int(
                imsize / 2), int(imsize / 2))
            freq = mymsmd.meanfreq(spw, unit='GHz')
            if imfitlog:
                logfile = img + '.imfit'
            else:
                logfile = ''
            imagefit = imfit(imagename=img, region=region, logfile=logfile)
            fitresults = au.imfitparse(imagefit, deconvolved=True)
            synthMajor, synthMinor = info[0:2]
            synthBeam = (info[0] * info[1])**0.5
            # Compare the Positions
            checkpos_obs = au.direction2radec(checkpos)
            if fitresults is not None:
                checkpos_fit = ','.join(fitresults.split()[:2])
                print("spw %d: checksource fitted position: " % (spw),
                      checkpos_fit)
                result = au.angularSeparationOfStrings(checkpos_fit,
                                                       checkpos_obs,
                                                       True,
                                                       verbose=False)
                checkpos_diff, deltaLong, deltaLat, deltaLongCosDec, pa = result
            total = checkpos_diff * 3600.
            rao = deltaLongCosDec * 3600.
            deco = deltaLat * 3600.
            print(
                "spw %d: %s offset=%.4f arcsec, RAO=%+.4f, DECO=%+.4f, PA=%.1fdeg"
                % (spw, sourcename, total, rao, deco, pa))
            totals.append(total)
            raos.append(rao)
            decos.append(deco)
            mymsmd.close()
            if nScienceFields > 1:
                scienceDeg = np.degrees(
                    au.angularSeparationOfDirections(science2pos, sciencepos,
                                                     True))
            phaseDeg = np.degrees(
                au.angularSeparationOfDirections(phasepos, sciencepos, True))
            checkDeg = np.degrees(
                au.angularSeparationOfDirections(checkpos, sciencepos, True))
            if len(raos) == 1:
                pl.clf()
                desc = pl.subplot(111)
                if nScienceFields > 1:
                    pl.plot([0, scienceDeg[3], phaseDeg[3], checkDeg[3]],
                            [0, scienceDeg[2], phaseDeg[2], checkDeg[2]],
                            'b+',
                            ms=10,
                            mew=2)
                else:
                    pl.plot([0, phaseDeg[3], checkDeg[3]],
                            [0, phaseDeg[2], checkDeg[2]],
                            'b+',
                            ms=10,
                            mew=2)
                pl.hold(True)
                pl.axis('equal')
                yrange = np.diff(pl.ylim())[0]
                # reverse RA axis
                x0, x1 = pl.xlim()
                xoffset = 0.15 * (x1 - x0)
                # Keep a fixed scale among the spws/images
                xscale = 0.5 * xoffset / np.max(np.abs([rao, deco]))
            # draw the arrow for each spw's image
            pl.arrow(checkDeg[3],
                     checkDeg[2],
                     rao * xscale,
                     deco * xscale,
                     lw=1,
                     shape='full',
                     head_width=0.15 * xoffset,
                     head_length=0.2 * xoffset,
                     fc='b',
                     ec='b')
            if len(raos) == 1:
                pl.xlim([x1 + xoffset, x0 - xoffset])
                yoffset = yrange * 0.025
                pl.text(0, 0 + yoffset, 'science', ha='center', va='bottom')
                if nScienceFields > 1:
                    pl.text(scienceDeg[3],
                            scienceDeg[2] + yoffset,
                            'science (%.1fdeg)' % scienceDeg[0],
                            ha='center',
                            va='bottom')
                    pl.text(scienceDeg[3],
                            scienceDeg[2] - yoffset,
                            science2,
                            ha='center',
                            va='top')
                pl.text(phaseDeg[3],
                        phaseDeg[2] + yoffset,
                        'phase (%.1fdeg)' % phaseDeg[0],
                        ha='center',
                        va='bottom')
                pl.text(checkDeg[3],
                        checkDeg[2] + yoffset,
                        'check (%.1fdeg)' % checkDeg[0],
                        ha='center',
                        va='bottom')
                pl.text(0, 0 - yoffset, science, ha='center', va='top')
                pl.text(phaseDeg[3],
                        phaseDeg[2] - yoffset,
                        phase,
                        ha='center',
                        va='top')
                pl.text(checkDeg[3],
                        checkDeg[2] - yoffset,
                        check,
                        ha='center',
                        va='top')
                pl.xlabel('RA offset (deg)')
                pl.ylabel('Dec offset (deg)')
                projCode = au.projectCodeFromDataset(vis)
                if type(projCode) == str:
                    if verbose:
                        print("Did not find project code")
                    projCode = ''
                else:
                    projCode = projCode[0] + ', Band %s, ' % (rxBand)
                pl.title(projCode + os.path.basename(img).split('.spw')[0] +
                         ', spws=%s' % spws,
                         size=12)
                pl.ylim(
                    [pl.ylim()[0] - yoffset * 8,
                     pl.ylim()[1] + yoffset * 8])
                minorLocator = MultipleLocator(0.5)  # degrees
                desc.xaxis.set_minor_locator(minorLocator)
                desc.yaxis.set_minor_locator(minorLocator)
        # end 'for' loop over spws/images
        if len(raos) < 1:
            return
        pl.ylim([pl.ylim()[0] - yoffset * 7, pl.ylim()[1] + yoffset * 15])
        rao = np.median(raos)
        raostd = np.std(raos)
        deco = np.median(decos)
        decostd = np.std(decos)
        total = np.median(totals)
        totalstd = np.std(totals)
        raoBeams = rao / synthBeam
        raostdBeams = raostd / synthBeam
        decoBeams = deco / synthBeam
        decostdBeams = decostd / synthBeam
        # draw the median arrow in thick black line
        pl.arrow(checkDeg[3],
                 checkDeg[2],
                 rao * xscale,
                 deco * xscale,
                 lw=2,
                 shape='full',
                 head_width=0.12 * xoffset,
                 head_length=0.18 * xoffset,
                 ec='k',
                 fc='k')
        print(
            "median +- std: offset=%.4f+-%.4f, RAO=%.4f+-%.4f, DECO=%.4f+-%.4f"
            % (total, totalstd, rao, raostd, deco, decostd))
        #        pl.text(checkDeg[3], checkDeg[2]-0.6*xoffset, '$\Delta\\alpha$: %+.4f"+-%.4f"' % (rao,raostd), ha='center')
        #        pl.text(checkDeg[3], checkDeg[2]-0.85*xoffset, '$\Delta\\delta$: %+.4f"+-%.4f"' % (deco,decostd), ha='center')
        pl.text(0.05,
                0.95,
                '$\Delta\\alpha$: %+.4f"+-%.4f" = %+.2f+-%.2f beams' %
                (rao, raostd, raoBeams, raostdBeams),
                ha='left',
                transform=desc.transAxes)
        pl.text(0.05,
                0.91,
                '$\Delta\\delta$: %+.4f"+-%.4f" = %+.2f+-%.2f beams' %
                (deco, decostd, decoBeams, decostdBeams),
                ha='left',
                transform=desc.transAxes)
        if plotfile == '':
            png = img + '_offset.png'
        else:
            png = plotfile
        pl.savefig(png, bbox_inches='tight')
        pl.draw()
        pngs.append(png)
        print("Wrote ", png)
def mosaic_aligned_data(
    infile_list=None,
    weightfile_list=None,
    outfile=None,
    overwrite=False,
):
    """
    Combine a list of previously aligned data into a single image
    using linear mosaicking. Weight each file using a corresponding
    weight file and also create sum and integrated weight files.

    infile_list : list of input files. Required.

    weightfile_list : a list of weight files that correspond to the
    input files. Can be a dictionary or a list. If it's a list then
    matching is by order, so that the first infile goes to first
    weight file, etc. If it's a dictionary, it looks for the infile
    name as a key.

    outfile : the name of the output mosaic image. Will create
    associated files with ".sum" and ".weight" appended to this file
    name.
    
    overwrite (default False) : Delete existing files. You probably
    want to set this to True but it's a user decision.

    """

    # Check inputs

    if infile_list is None:
        logger.error("Input file list required.")
        return (None)

    if outfile is None:
        logger.error("Output file is required.")
        return (None)

    # Define some extra outputs and then check file existence

    sum_file = outfile + '.sum'
    weight_file = outfile + '.weight'
    mask_file = outfile + '.mask'
    temp_file = outfile + '.temp'

    for this_file in [outfile, sum_file, weight_file, temp_file, mask_file]:
        if os.path.isdir(this_file):
            if not overwrite:
                logger.error("Output file present and overwrite off - " +
                             this_file)
                return (None)
            os.system('rm -rf ' + this_file)

    # Check the weightfile dictionary/list and get it set.

    if weightfile_list is None:
        logger.error("Missing weightfile_list required for mosaicking.")
        return (None)

    if (type(weightfile_list) != type([])) and (type(weightfile_list) != type(
        {})):
        logger.error("Weightfile_list must be dictionary or list.")
        return (None)

    if type(weightfile_list) == type([]):
        if len(infile_list) != len(weightfile_list):
            logger.error("Mismatch in input and output list lengths.")
            return (None)
        weightfile_dict = {}
        for ii in range(len(infile_list)):
            weightfile_dict[infile_list[ii]] = weightfile_list[ii]

    if type(weightfile_list) == type({}):
        weightfile_dict = weightfile_list

    # Check file existence

    for this_infile in infile_list:
        if not os.path.isdir(this_infile):
            logger.error("Missing file - " + this_infile)
            return (None)
        this_weightfile = weightfile_dict[this_infile]
        if not os.path.isdir(this_weightfile):
            logger.error("Missing file - " + this_weightfile)
            return (None)

    # Define LEL expressions to be fed to immath. These just sum up
    # weight*image and weight. Those produce the .sum and .weight
    # output file.

    full_imlist = []

    lel_exp_sum = ''
    lel_exp_weight = ''

    first = True
    counter = 0

    for this_infile in infile_list:

        # Build out to a list that goes infile1, infile2, ... infilen,
        # weightfile1, weightfile2, ... weightfilen.

        full_imlist.append(this_infile)
        full_imlist.append(weightfile_dict[this_infile])

        # Make LEL string expressions that refer to these two images
        # and then increment the counter by 2. So IM0 is the first
        # image, IM1 the first weight, IM2 the second image, IM3 the
        # second weight, and so on.

        this_im = 'IM' + str(counter)
        this_wt = 'IM' + str(counter + 1)
        counter += 2

        # LEL expressions that refer to the weighted sum and the
        # weight for this image pair.

        this_lel_sum = '(' + this_im + '*' + this_wt + ')'
        this_lel_weight = '(' + this_wt + ')'

        # Chain these together into a full string that adds all of the
        # images together.

        if first:
            lel_exp_sum += this_lel_sum
            lel_exp_weight += this_lel_weight
            first = False
        else:
            lel_exp_sum += '+' + this_lel_sum
            lel_exp_weight += '+' + this_lel_weight

    # Feed our two LEL strings into immath to make the sum and weight
    # images.

    myia = au.createCasaTool(casaStuff.iatool)
    for thisfile in full_imlist:
        myia.open(thisfile)
        if not np.all(myia.getchunk(getmask=True)):
            myia.replacemaskedpixels(0.0)
            myia.set(pixelmask=1)
        myia.close()

    cwd = os.getcwd()
    ppdir = os.chdir(os.path.dirname(full_imlist[0]))
    local_imlist = [os.path.basename(ll) for ll in full_imlist]
    sum_file = os.path.basename(sum_file)
    weight_file = os.path.basename(weight_file)
    temp_file = os.path.basename(temp_file)
    local_outfile = os.path.basename(outfile)
    local_maskfile = os.path.basename(mask_file)

    casaStuff.immath(imagename=local_imlist,
                     mode='evalexpr',
                     expr=lel_exp_sum,
                     outfile=sum_file,
                     imagemd=local_imlist[0])

    casaStuff.immath(imagename=local_imlist,
                     mode='evalexpr',
                     expr=lel_exp_weight,
                     outfile=weight_file,
                     imagemd=local_imlist[0])

    # Just to be safe, reset the masks on the two images.

    myia = au.createCasaTool(casaStuff.iatool)
    myia.open(sum_file)
    myia.set(pixelmask=1)
    myia.close()

    myia.open(weight_file)
    myia.set(pixelmask=1)
    myia.close()

    # Now divide the sum*weight image by the weight image.

    casaStuff.immath(imagename=[sum_file, weight_file],
                     mode='evalexpr',
                     expr='iif(IM1 > 0.0, IM0/IM1, 0.0)',
                     outfile=temp_file,
                     imagemd=sum_file)

    # The mask for the final output is where we have any weight. This
    # may not be exactly what's desired in all cases, but it's not
    # clear to me what else to do except for some weight threshold
    # (does not have to be zero, though, I guess).

    casaStuff.immath(imagename=weight_file,
                     mode='evalexpr',
                     expr='iif(IM0 > 0.0, 1.0, 0.0)',
                     outfile=local_maskfile)

    # Strip out any degenerate axes and create the final output file.

    casaStuff.imsubimage(imagename=temp_file,
                         outfile=local_outfile,
                         mask='"' + local_maskfile + '"',
                         dropdeg=True)
    os.chdir(cwd)
    return (None)
예제 #20
0
    def get_mosaic_info(vis, spw, sourceid=None, intent='TARGET', pblevel=0.1):
        '''
        Return image size based on mosaic fields

        Parameters
        ----------
        vis : str
            MS Name
        spw : str or int
            If str, searches for an exact match with the names in the MS. If
            int, is the index of SPW in the MS.
        sourceid : str, optional
            The field names used will contain sourceid.
        intent : str, optional
            Use every field with the given intent (wildcards used by default).
        pblevel : float between 0 and 1
            PB level that defines the edges of the mosaic.
        '''

        mytb = au.createCasaTool(au.tbtool)

        # Check SPWs to make sure given choice is valid
        mytb.open(vis + '/SPECTRAL_WINDOW')

        spwNames = mytb.getcol('NAME')
        if isinstance(spw, str):
            match = False
            for spw_name in spwNames:
                if spw == spw_name:
                    match = True

            if not match:
                raise ValueError("The given SPW ({0}) is not in the MS SPW"
                                 " names ({1})".format(spw, spwNames))
        elif isinstance(spw, (int, np.integer)):
            try:
                spwNames[spw]
            except IndexError:
                raise IndexError("The given SPW index {0} is not in the range"
                                 " of SPWs in the MS ({1})."
                                 .format(spw, len(spwNames)))
        else:
            raise TypeError("spw must be a str or int.")

        refFreq = mytb.getcol("REF_FREQUENCY")[spw]
        lambdaMeters = au.c_mks / refFreq

        mytb.close()

        # Get field info
        mytb.open(vis + '/FIELD')

        delayDir = mytb.getcol('DELAY_DIR')
        ra = delayDir[0, :][0] * 12 / np.pi
        for i in range(len(ra)):
            if ra[i] < 0:
                ra[i] += 24
        ra *= 15
        dec = np.degrees(delayDir[1, :][0])

        # First choose fields by given sourceid
        if sourceid is not None:

            names = mytb.getcol('NAME')
            fields = mytb.getcol("SOURCE_ID")

            good_names = []
            good_fields = []
            for name, field in zip(names, fields):
                # Check if it has sourceid
                if name.find(sourceid) != -1:
                    good_names.append(name)
                    good_fields.append(field)
            names = good_names
            fields = good_fields

        # Then try choosing all fields based on the given intent.
        elif intent is not None:
            # Ensure a string
            intent = str(intent)

            mymsmd = au.createCasaTool(au.msmdtool)
            mymsmd.open(vis)
            intentsToSearch = '*' + intent + '*'
            fields = mymsmd.fieldsforintent(intentsToSearch)
            names = mymsmd.namesforfields(fields)
            mymsmd.close()
        # On or the other must be given
        else:
            raise ValueError("Either sourceid or intent must be given.")

        mytb.close()

        ra = ra[fields]
        dec = dec[fields]

        raAverageDegrees = np.mean(ra)
        decAverageDegrees = np.mean(dec)

        raRelativeArcsec = 3600 * (ra - raAverageDegrees) * \
            np.cos(np.deg2rad(decAverageDegrees))
        decRelativeArcsec = 3600 * (dec - decAverageDegrees)

        centralField = au.findNearestField(ra, dec,
                                           raAverageDegrees,
                                           decAverageDegrees)[0]

        # This next step is crucial, as it converts from the field number
        # determined from a subset list back to the full list.
        centralFieldName = names[centralField]
        centralField = fields[centralField]

        # Find which antenna have data
        mytb.open(vis)
        antennasWithData = np.sort(np.unique(mytb.getcol('ANTENNA1')))
        mytb.close()

        if antennasWithData.size == 0:
            raise Warning("No antennas with data found.")

        # Now we need the dish diameters
        mytb.open(vis + "/ANTENNA")
        # These are in m
        dish_diameters = \
            np.unique(mytb.getcol("DISH_DIAMETER")[antennasWithData])
        mytb.close()

        # Find maxradius
        maxradius = 0
        for diam in dish_diameters:
            arcsec = 0.5 * \
                au.primaryBeamArcsec(wavelength=lambdaMeters * 1000,
                                     diameter=diam, showEquation=False)
            radius = arcsec / 3600.0
            if radius > maxradius:
                maxradius = radius

        # Border about each point, down to the given pblevel
        border = 2 * maxradius * au.gaussianBeamOffset(pblevel) * 3600.

        size_ra = np.ptp(raRelativeArcsec) + 2 * border
        size_dec = np.ptp(decRelativeArcsec) + 2 * border

        mosaicInfo = {"Central_Field_ID": centralField,
                      "Central_Field_Name": centralFieldName,
                      "Center_RA": ra,
                      "Center_Dec": dec,
                      "Size_RA": size_ra,
                      "Size_Dec": size_dec}

        return mosaicInfo