Example #1
0
def mfs_clean_spws(img, spws, chans, spwtype):
    """
    Clean each supplied spw (MFS)

    Inputs:
        img :: Imaging object
            The Imaging object
        spws, chans :: lists
            spectral windows and channels to image
        spwtype :: string
            'line' or 'cont', to determine which clean params to use

    Returns: Nothing
    """
    for spw, chan in zip(spws, chans):
        # If not interactive, Lightly clean to get threshold
        imagename = "{0}.spw{1}.{2}.mfs".format(img.imfield, spw, img.stokes)
        if img.uvtaper:
            imagename = imagename + ".uvtaper"
        imagename = os.path.join(img.outdir, imagename)
        cleanspw = spw
        if chan:
            cleanspw = "{0}:{1}".format(spw, chan)
        if not img.interactive:
            # Save model if necessary
            savemodel = "none"
            if img.savemodel == "light":
                savemodel = "modelcolumn"
            img.logger.info("Lightly cleaning spw {0} (MFS)...".format(spw))
            casa.tclean(
                vis=img.vis,
                imagename=imagename,
                phasecenter=img.cp["phasecenter"],
                field=img.field,
                spw=cleanspw,
                specmode="mfs",
                gridder=img.cp["gridder"],
                wprojplanes=img.cp["wprojplanes"],
                threshold="0mJy",
                niter=img.cp["lightniter"] * len(img.stokes),
                usemask="auto-multithresh",
                pbmask=img.cp[spwtype + "pbmask"],
                sidelobethreshold=img.cp[spwtype + "sidelobethreshold"],
                noisethreshold=img.cp[spwtype + "noisethreshold"],
                lownoisethreshold=img.cp[spwtype + "lownoisethreshold"],
                negativethreshold=img.cp[spwtype + "negativethreshold"],
                smoothfactor=img.cp[spwtype + "smoothfactor"],
                minbeamfrac=img.cp[spwtype + "minbeamfrac"],
                cutthreshold=img.cp[spwtype + "cutthreshold"],
                growiterations=img.cp[spwtype + "growiterations"],
                deconvolver="multiscale",
                scales=img.cp["scales"],
                gain=img.cp["gain"],
                cyclefactor=img.cp["cyclefactor"],
                imsize=img.cp["imsize"],
                pblimit=-1.0,
                cell=img.cp["cell"],
                weighting=img.cp["weighting"],
                robust=img.cp["robust"],
                uvtaper=img.outertaper,
                uvrange=img.uvrange,
                stokes=img.stokes,
                savemodel=savemodel,
                pbcor=False,
                restart=True,
                calcres=False,
                calcpsf=False,
                parallel=img.parallel,
            )
            img.logger.info("Done.")

            # Get RMS of residuals
            dat = casa.imstat(
                imagename="{0}.residual".format(imagename),
                axes=[0, 1],
                mask="'{0}.mask' == 0".format(imagename),
            )
            img.logger.info("Max un-masked RMS: {0:.2f} mJy/beam".format(
                1000.0 * np.max(dat["rms"])))
            img.logger.info(
                "Max un-masked MAD*1.4826: {0:.2f} mJy/beam".format(
                    1000.0 * 1.4826 * np.max(dat["medabsdevmed"])))
            img.logger.info(
                "Using max(MAD) x 1.4826 x {0} as threshold".format(
                    img.cp["nrms"]))
            threshold = "{0:.2f}mJy".format(img.cp["nrms"] * 1000.0 * 1.4826 *
                                            np.max(dat["medabsdevmed"]))
        else:
            threshold = "0.0mJy"

        # Clean to threshold
        # Save model if necessary
        savemodel = "none"
        if img.savemodel == "clean":
            savemodel = "modelcolumn"
        img.logger.info("Cleaning spw {0} (MFS) to threshold: {1}...".format(
            spw, threshold))
        casa.tclean(
            vis=img.vis,
            imagename=imagename,
            field=img.field,
            phasecenter=img.cp["phasecenter"],
            spw=cleanspw,
            gridder=img.cp["gridder"],
            wprojplanes=img.cp["wprojplanes"],
            specmode="mfs",
            threshold=threshold,
            niter=img.cp["maxniter"] * len(img.stokes),
            usemask="auto-multithresh",
            pbmask=img.cp[spwtype + "pbmask"],
            sidelobethreshold=img.cp[spwtype + "sidelobethreshold"],
            noisethreshold=img.cp[spwtype + "noisethreshold"],
            lownoisethreshold=img.cp[spwtype + "lownoisethreshold"],
            negativethreshold=img.cp[spwtype + "negativethreshold"],
            smoothfactor=img.cp[spwtype + "smoothfactor"],
            minbeamfrac=img.cp[spwtype + "minbeamfrac"],
            cutthreshold=img.cp[spwtype + "cutthreshold"],
            growiterations=img.cp[spwtype + "growiterations"],
            deconvolver="multiscale",
            scales=img.cp["scales"],
            gain=img.cp["gain"],
            cyclefactor=img.cp["cyclefactor"],
            imsize=img.cp["imsize"],
            pblimit=-1.0,
            cell=img.cp["cell"],
            weighting=img.cp["weighting"],
            robust=img.cp["robust"],
            uvtaper=img.outertaper,
            uvrange=img.uvrange,
            pbcor=False,
            stokes=img.stokes,
            savemodel=savemodel,
            interactive=img.interactive,
            restart=True,
            calcres=False,
            calcpsf=False,
            parallel=img.parallel,
        )
        img.logger.info("Done.")

        # Primary beam correction
        img.logger.info("Performing primary beam correction...")
        casa.impbcor(
            imagename="{0}.image".format(imagename),
            pbimage="{0}.pb.image".format(imagename),
            outfile="{0}.pbcor.image".format(imagename),
            overwrite=True,
        )
        img.logger.info("Done.")

        # Export to fits
        img.logger.info("Exporting fits file...")
        casa.exportfits(
            imagename="{0}.image".format(imagename),
            fitsimage="{0}.clean.image.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.mask".format(imagename),
            fitsimage="{0}.clean.mask.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.residual".format(imagename),
            fitsimage="{0}.clean.residual.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.pbcor.image".format(imagename),
            fitsimage="{0}.clean.pbcor.image.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        img.logger.info("Done.")
Example #2
0
def mfs_dirty_spws(img, spws, chans):
    """
    Dirty image each supplied spw (MFS)

    Inputs:
        img :: Imaging object
            The Imaging object
        spws, chans :: lists
            spectral windows and channels to image

    Returns: Nothing
    """
    for spw, chan in zip(spws, chans):
        imagename = "{0}.spw{1}.{2}.mfs".format(img.imfield, spw, img.stokes)
        if img.uvtaper:
            imagename = imagename + ".uvtaper"
        imagename = os.path.join(img.outdir, imagename)
        img.logger.info(
            "Generating dirty image of spw {0} (MFS)...".format(spw))
        cleanspw = spw
        if chan:
            cleanspw = "{0}:{1}".format(spw, chan)
        casa.tclean(
            vis=img.vis,
            imagename=imagename,
            phasecenter=img.cp["phasecenter"],
            field=img.field,
            spw=cleanspw,
            specmode="mfs",
            gridder=img.cp["gridder"],
            wprojplanes=img.cp["wprojplanes"],
            threshold="0mJy",
            niter=0,
            deconvolver="multiscale",
            scales=img.cp["scales"],
            gain=img.cp["gain"],
            cyclefactor=img.cp["cyclefactor"],
            imsize=img.cp["imsize"],
            pblimit=-1.0,
            cell=img.cp["cell"],
            weighting=img.cp["weighting"],
            robust=img.cp["robust"],
            uvtaper=img.outertaper,
            uvrange=img.uvrange,
            stokes=img.stokes,
            pbcor=False,
            parallel=img.parallel,
        )
        img.logger.info("Done.")

        # Generate primary beam image
        img.logger.info(
            "Generating primary beam image of spw {0} (MFS)...".format(spw))
        makePB(
            vis=img.vis,
            field=img.field,
            spw=spw,
            uvrange=img.uvrange,
            stokes=img.stokes,
            imtemplate="{0}.image".format(imagename),
            outimage="{0}.pb.image".format(imagename),
            pblimit=img.cp["pblimit"],
        )
        img.logger.info("Done.")

        # Primary beam correction
        img.logger.info("Performing primary beam correction...")
        casa.impbcor(
            imagename="{0}.image".format(imagename),
            pbimage="{0}.pb.image".format(imagename),
            outfile="{0}.pbcor.image".format(imagename),
            overwrite=True,
        )
        img.logger.info("Done.")

        # Export to fits
        img.logger.info("Exporting fits file...")
        casa.exportfits(
            imagename="{0}.pb.image".format(imagename),
            fitsimage="{0}.pb.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.image".format(imagename),
            fitsimage="{0}.dirty.image.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.residual".format(imagename),
            fitsimage="{0}.dirty.residual.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.pbcor.image".format(imagename),
            fitsimage="{0}.dirty.pbcor.image.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        img.logger.info("Done.")
Example #3
0
def channel_dirty_spws(img, spws, spwtype):
    """
    Dirty image each supplied spectral window (channel cube)

    Inputs:
        img :: Imaging object
            The Imaging object
        spws :: string
            comma-separated string of spws to image
        spwtype :: string
            'line' or 'cont', to determine which clean params to use

    Returns: Nothing
    """
    # Set channel parameters
    if spwtype == "cont":
        restfreqs = [None for spw in spws]
        start = None
        width = img.cp["contwidth"]
        nchans = [None for spw in spws]
        outframe = img.cp["contoutframe"]
        veltype = None
        interpolation = None
    elif spwtype == "line":
        spw_inds = [
            img.config.get("Spectral Windows", "Line").split(",").index(spw)
            for spw in spws
        ]
        restfreqs = [
            img.config.get("Clean", "restfreqs").split(",")[spw_ind]
            for spw_ind in spw_inds
        ]
        outframe = img.cp["lineoutframe"]
        veltype = img.cp["veltype"]
        interpolation = img.cp["interpolation"]

        # Determine velocity-gridding parameter
        start, width, nchans = grid_parameters(img, spws)
    else:
        img.logger.critical("Error: spwtype {0} not supported".format(spwtype))
        raise ValueError("Invalid spwtype")

    # Loop over spws
    for spw, restfreq, nchan in zip(spws, restfreqs, nchans):
        imagename = "{0}.spw{1}.{2}.channel".format(img.imfield, spw, img.stokes)
        if img.uvtaper:
            imagename = imagename + ".uvtaper"
        imagename = os.path.join(img.outdir, imagename)
        img.logger.info(
            "Dirty imaging spw {0} (restfreq: {1})...".format(spw, restfreq)
        )
        casa.tclean(
            vis=img.vis,
            imagename=imagename,
            phasecenter=img.cp["phasecenter"],
            field=img.field,
            spw=spw,
            specmode="cube",
            gridder=img.cp["gridder"],
            wprojplanes=img.cp["wprojplanes"],
            threshold="0mJy",
            niter=0,
            deconvolver="multiscale",
            scales=img.cp["scales"],
            gain=img.cp["gain"],
            cyclefactor=img.cp["cyclefactor"],
            imsize=img.cp["imsize"],
            pblimit=-1.0,
            cell=img.cp["cell"],
            weighting=img.cp["weighting"],
            robust=img.cp["robust"],
            restfreq=restfreq,
            start=start,
            width=width,
            nchan=nchan,
            outframe=outframe,
            veltype=veltype,
            interpolation=interpolation,
            uvtaper=img.outertaper,
            uvrange=img.uvrange,
            stokes=img.stokes,
            pbcor=False,
            parallel=img.parallel,
        )
        img.logger.info("Done.")

        # Generate primary beam image
        img.logger.info(
            "Generating primary beam image of spw {0} (channel)...".format(spw)
        )
        makePB(
            vis=img.vis,
            field=img.field,
            spw=spw,
            uvrange=img.uvrange,
            stokes=img.stokes,
            imtemplate="{0}.image".format(imagename),
            outimage="{0}.pb.image".format(imagename),
            pblimit=img.cp["pblimit"],
        )

        # Primary beam correction
        pbimage = "{0}.pb.image".format(imagename)
        img.logger.info("Performing primary beam correction...")
        if not os.path.exists(pbimage):
            raise ValueError(
                "Could not find {0}. Did you dirty MFS first?".format(pbimage)
            )
        casa.impbcor(
            imagename="{0}.image".format(imagename),
            pbimage=pbimage,
            outfile="{0}.pbcor.image".format(imagename),
            overwrite=True,
        )
        img.logger.info("Done.")

        # Export to fits
        img.logger.info("Exporting fits file...")
        velocity = spwtype == "line"
        casa.exportfits(
            imagename="{0}.pb.image".format(imagename),
            fitsimage="{0}.pb.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.image".format(imagename),
            fitsimage="{0}.dirty.image.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.residual".format(imagename),
            fitsimage="{0}.dirty.residual.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.pbcor.image".format(imagename),
            fitsimage="{0}.dirty.pbcor.image.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        img.logger.info("Done.")
Example #4
0
def channel_clean_spws(img, spws, spwtype):
    """
    Clean all supplied spws by channel using clean mask from MFS
    images.

    Inputs:
        img :: Imaging object
            The imaging object
        spws :: string
            comma-separated string of spws to image
        spwtype :: string
            'line' or 'cont', to determine which clean params to use

    Returns: Nothing
    """
    #
    # Set channel parameters
    #
    if spwtype == "cont":
        restfreqs = [None for spw in spws]
        start = None
        width = img.cp["contwidth"]
        nchans = [None for spw in spws]
        outframe = img.cp["contoutframe"]
        veltype = None
        interpolation = None
    elif spwtype == "line":
        spw_inds = [
            img.config.get("Spectral Windows", "Line").split(",").index(spw)
            for spw in spws
        ]
        restfreqs = [
            img.config.get("Clean", "restfreqs").split(",")[spw_ind]
            for spw_ind in spw_inds
        ]
        outframe = img.cp["lineoutframe"]
        veltype = img.cp["veltype"]
        interpolation = img.cp["interpolation"]

        # Determine velocity-gridding parameter
        start, width, nchans = grid_parameters(img, spws)
    else:
        img.logger.critical("Error: spwtype {0} not supported".format(spwtype))
        raise ValueError("Invalid spwtype")

    # Loop over spws
    for spw, restfreq, nchan in zip(spws, restfreqs, nchans):
        # Get niters
        if nchan is None:
            # get number of channels from dirty image
            imagename = "{0}.spw{1}.{2}.channel".format(img.imfield, spw, img.stokes)
            if img.uvtaper:
                imagename = imagename + ".uvtaper"
            imagename = os.path.join(img.outdir, imagename)
            imagename = imagename + ".dirty.image.fits"
            if not os.path.exists(imagename):
                raise ValueError(
                    "Must create dirty channel cube first: {0}".format(imagename)
                )
            dirty_hdr = fits.getheader(imagename)
            lightniter = img.cp["lightniter"] * dirty_hdr["NAXIS3"] * len(img.stokes)
            niter = img.cp["maxniter"] * dirty_hdr["NAXIS3"] * len(img.stokes)
        else:
            lightniter = img.cp["lightniter"] * nchan * len(img.stokes)
            niter = img.cp["maxniter"] * nchan * len(img.stokes)

        # If not interactive, Lightly clean spw
        imagename = "{0}.spw{1}.{2}.channel".format(img.imfield, spw, img.stokes)
        if img.uvtaper:
            imagename = imagename + ".uvtaper"
            mask = "{0}.spw{1}.{2}.mfs.uvtaper.mask".format(
                img.imfield, spw, img.stokes
            )
        else:
            mask = "{0}.spw{1}.{2}.mfs.mask".format(img.imfield, spw, img.stokes)
        imagename = os.path.join(img.outdir, imagename)
        mask = os.path.join(img.outdir, mask)
        if not os.path.isdir(mask):
            img.logger.critical("Error: {0} does not exist".format(mask))
            raise ValueError("{0} does not exist".format(mask))
        if not img.interactive:
            img.logger.info(
                "Lightly cleaning spw {0} (restfreq: {1})...".format(spw, restfreq)
            )
            img.logger.info("Using mask: {0}".format(mask))
            casa.tclean(
                vis=img.vis,
                imagename=imagename,
                phasecenter=img.cp["phasecenter"],
                field=img.field,
                spw=spw,
                gridder=img.cp["gridder"],
                wprojplanes=img.cp["wprojplanes"],
                specmode="cube",
                threshold="0mJy",
                niter=lightniter,
                mask=mask,
                deconvolver="multiscale",
                scales=img.cp["scales"],
                gain=img.cp["gain"],
                cyclefactor=img.cp["cyclefactor"],
                imsize=img.cp["imsize"],
                pblimit=-1.0,
                cell=img.cp["cell"],
                weighting=img.cp["weighting"],
                robust=img.cp["robust"],
                restfreq=restfreq,
                start=start,
                width=width,
                nchan=nchan,
                outframe=outframe,
                veltype=veltype,
                interpolation=interpolation,
                uvtaper=img.outertaper,
                uvrange=img.uvrange,
                stokes=img.stokes,
                pbcor=False,
                restart=True,
                calcres=False,
                calcpsf=False,
                parallel=img.parallel,
            )

            # This generates a channel mask, so next clean can't
            # have mfs mask
            mask = ""

            # Get RMS of residuals
            dat = casa.imstat(
                imagename="{0}.residual".format(imagename),
                axes=[0, 1],
                mask="'{0}.mask' == 0".format(imagename),
            )
            img.logger.info(
                "Max un-masked RMS: {0:.2f} mJy/beam".format(
                    1000.0 * np.max(dat["rms"])
                )
            )
            img.logger.info(
                "Max un-masked MAD*1.4826: {0:.2f} mJy/beam".format(
                    1000.0 * 1.4826 * np.max(dat["medabsdevmed"])
                )
            )
            img.logger.info(
                "Using max(MAD) x 1.4826 x {0} as threshold".format(img.cp["nrms"])
            )
            threshold = "{0:.2f}mJy".format(
                img.cp["nrms"] * 1000.0 * 1.4826 * np.max(dat["medabsdevmed"])
            )
        else:
            threshold = "0.0mJy"

        # Deep clean to threshold
        img.logger.info(
            "Cleaning spw {0} (restfreq: {1}) to threshold: {2}...".format(
                spw, restfreq, threshold
            )
        )
        if mask:
            img.logger.info("Using mask: {0}".format(mask))
        casa.tclean(
            vis=img.vis,
            imagename=imagename,
            phasecenter=img.cp["phasecenter"],
            field=img.field,
            spw=spw,
            specmode="cube",
            gridder=img.cp["gridder"],
            wprojplanes=img.cp["wprojplanes"],
            threshold=threshold,
            niter=niter,
            mask=mask,
            deconvolver="multiscale",
            scales=img.cp["scales"],
            gain=img.cp["gain"],
            cyclefactor=img.cp["cyclefactor"],
            imsize=img.cp["imsize"],
            pblimit=-1.0,
            cell=img.cp["cell"],
            weighting=img.cp["weighting"],
            robust=img.cp["robust"],
            restfreq=restfreq,
            start=start,
            width=width,
            nchan=nchan,
            outframe=outframe,
            veltype=veltype,
            interpolation=interpolation,
            uvtaper=img.outertaper,
            uvrange=img.uvrange,
            pbcor=False,
            stokes=img.stokes,
            interactive=img.interactive,
            restart=True,
            calcres=False,
            calcpsf=False,
            parallel=img.parallel,
        )
        img.logger.info("Done.")

        # Primary beam correction
        pbimage = "{0}.pb.image".format(imagename)
        img.logger.info("Performing primary beam correction...")
        if not os.path.exists(pbimage):
            raise ValueError(
                "Could not find {0}. Did you dirty MFS first?".format(pbimage)
            )
        casa.impbcor(
            imagename="{0}.image".format(imagename),
            pbimage=pbimage,
            outfile="{0}.pbcor.image".format(imagename),
            overwrite=True,
        )
        img.logger.info("Done.")

        # Export to fits
        img.logger.info("Exporting fits file...")
        velocity = spwtype == "line"
        casa.exportfits(
            imagename="{0}.image".format(imagename),
            fitsimage="{0}.clean.image.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.mask".format(imagename),
            fitsimage="{0}.clean.mask.fits".format(imagename),
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.residual".format(imagename),
            fitsimage="{0}.clean.residual.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        casa.exportfits(
            imagename="{0}.pbcor.image".format(imagename),
            fitsimage="{0}.clean.pbcor.image.fits".format(imagename),
            velocity=velocity,
            overwrite=True,
            history=False,
        )
        img.logger.info("Done.")
Example #5
0
def smooth_all(field, spws='', stokes='', imagetype='clean',
               mosaic=False, overwrite=False):
    """
    Smooth all line and continuum images/cubes to largest 
    beam size of any individual image/cube.

    Inputs: 
      field :: string
        The field to analyze
      spws :: string
        comma separated string of spws to smooth
      stokes :: string
        The Stokes parameters in the image
      imagetype :: string
        What images to process. For example,
        'dirty', 'clean', 'dirty.uvtaper', or 'clean.uvtaper'
      mosaic :: boolean
        if True, these are mosaic images (.linmos.fits)
      overwrite :: boolean
        if True, overwrite existing images

    Returns: Nothing
    """
    myspws = ['spw{0}'.format(spw) if spw != 'cont' else spw
              for spw in spws.split(',')]
    #
    # Find beam major axes, minor axes, and position angles for all
    # available images
    #
    print("Finding largest synthesized beam")
    bmajs = []
    bmins = []
    # images
    imname = '{0}.{1}.{2}.mfs.{3}.image.fits'
    if mosaic:
        imname = '{0}.{1}.{2}.mfs.{3}.image.linmos.fits'
    images = [imname.format(field, spw, stokes, imagetype)
              for spw in myspws
              if os.path.exists(imname.format(field,spw,stokes,imagetype))]
    # residual images
    resimages = [image.replace('.image.', '.residual.') for image in images]
    # cubes
    imname = '{0}.{1}.{2}.channel.{3}.image.fits'
    if mosaic:
        imname = '{0}.{1}.{2}.channel.{3}.image.linmos.fits'
    cubes = [imname.format(field, spw, stokes, imagetype)
             for spw in myspws
             if os.path.exists(imname.format(field,spw,stokes,imagetype))]
    # residual cubes
    rescubes = [image.replace('.image.', '.residual.') for image in cubes]
    for imagename, resimagename in zip(images+cubes, resimages+rescubes):
        with fits.open(imagename) as imhdulist:
            bunit = imhdulist[0].header['BUNIT']
            if len(imhdulist) > 1:
                bmaj = imhdulist[1].data['BMAJ'][0] # arcsec
                bmin = imhdulist[1].data['BMIN'][0] # arcsec
                bpa = imhdulist[1].data['BPA'][0]
            else:
                bmaj = imhdulist[0].header['BMAJ'] * 3600. # arcsec
                bmin = imhdulist[0].header['BMIN'] * 3600. # arcsec
                bpa = imhdulist[0].header['BPA']
            # check that residual images have beams and units
            with fits.open(resimagename, 'update') as reshdulist:
                if 'BMIN' not in reshdulist[0].header:
                    reshdulist[0].header['BMIN'] = bmin/3600. # deg
                    reshdulist[0].header['BMAJ'] = bmaj/3600. # deg
                    reshdulist[0].header['BPA'] = bpa
                if not reshdulist[0].header['BUNIT']:
                    reshdulist[0].header['BUNIT'] = bunit
            # check that residual images have units
            # append
            bmajs.append(bmaj)
            bmins.append(bmin)
    #
    # Smooth available images to maximum (circular) beam size
    # + pixel diagonal size (otherwise imsmooth will complain)
    #
    cell_size = abs(casa.imhead(imagename)['incr'][0]) * 206265.
    bmaj_target = np.max(bmajs)+1.42*cell_size
    bmin_target = np.max(bmajs)+1.42*cell_size
    bpa_target = 0.
    print("Smoothing all images to")
    print("Major axis: {0:.2f} arcsec".format(bmaj_target))
    print("Minor axis: {0:.2f} arcsec".format(bmin_target))
    print("Position angle: {0:.2f} degs".format(bpa_target))
    bmaj_target = {'unit':'arcsec','value':bmaj_target}
    bmin_target = {'unit':'arcsec','value':bmin_target}
    bpa_target = {'unit':'deg','value':bpa_target}
    for imagename, resimagename in zip(images+cubes, resimages+rescubes):
        # export velocity axis if this is a cube
        velocity = 'channel' in imagename
        # smooth image
        outfile = imagename.replace('.image.fits','.imsmooth.image')
        if mosaic:
            outfile = imagename.replace('.image.linmos.fits', '.imsmooth.image.linmos')
        casa.imsmooth(imagename=imagename,kernel='gauss',
                      targetres=True,major=bmaj_target,minor=bmin_target,
                      pa=bpa_target,outfile=outfile,overwrite=overwrite)
        casa.exportfits(imagename=outfile,fitsimage='{0}.fits'.format(outfile),
                        velocity=velocity,overwrite=True,history=False)
        # primary beam correct
        if not mosaic:
            smoimagename = outfile
            pbimage = imagename.replace('.{0}.image.fits'.format(imagetype), '.pb.fits')
            outfile = imagename.replace('.image.fits', '.pbcor.imsmooth.image')
            casa.impbcor(imagename=smoimagename, pbimage=pbimage,
                        outfile=outfile, overwrite=True)
            casa.exportfits(imagename=outfile,fitsimage='{0}.fits'.format(outfile),
                            velocity=velocity,overwrite=True,history=False)
        # check that residual image has beam size, if not add it
        with fits.open(resimagename, 'update') as hdulist:
            hdu = hdulist[0]
            if 'BMIN' not in hdu.header:
                hdu.header['BMIN'] = bmin_target['value']/3600.
                hdu.header['BMAJ'] = bmaj_target['value']/3600.
                hdu.header['BPA'] = bpa_target['value']
        # smooth residual image
        outfile = resimagename.replace('.residual.fits','.imsmooth.residual')
        if mosaic:
            outfile = resimagename.replace('.residual.linmos.fits', '.imsmooth.residual.linmos')
        casa.imsmooth(imagename=resimagename,kernel='gauss',
                      targetres=True,major=bmaj_target,minor=bmin_target,
                      pa=bpa_target,outfile=outfile,overwrite=overwrite)
        casa.exportfits(imagename=outfile,fitsimage='{0}.fits'.format(outfile),
                        velocity=velocity,overwrite=True,history=False)
    print("Done!")