Ejemplo n.º 1
0
    def __call__(self, img):

      if img.opts.psf_vary_do:
        mylog = mylogger.logging.getLogger("PyBDSM."+img.log+"Psf_Vary")
        mylogger.userinfo(mylog, '\nEstimating PSF variations')
        opts = img.opts
        dir = img.basedir + '/misc/'
        plot = False # debug figures
        image = img.ch0_arr

        try:
            from astropy.io import fits as pyfits
            old_pyfits = False
        except ImportError, err:
            from distutils.version import StrictVersion
            import pyfits
            if StrictVersion(pyfits.__version__) < StrictVersion('2.2'):
                old_pyfits = True
            else:
                old_pyfits = False

        if old_pyfits:
            mylog.warning('PyFITS version is too old: psf_vary module skipped')
            return

        over = 2
        generators = opts.psf_generators; nsig = opts.psf_nsig; kappa2 = opts.psf_kappa2
        snrtop = opts.psf_snrtop; snrbot = opts.psf_snrbot; snrcutstack = opts.psf_snrcutstack
        gencode = opts.psf_gencode; primarygen = opts.psf_primarygen; itess_method = opts.psf_itess_method
        tess_sc = opts.psf_tess_sc; tess_fuzzy= opts.psf_tess_fuzzy
        bright_snr_cut = opts.psf_high_snr
        s_only = opts.psf_stype_only
        if opts.psf_snrcut < 5.0:
            mylogger.userinfo(mylog, "Value of psf_snrcut too low; increasing to 5")
            snrcut = 5.0
        else:
            snrcut = opts.psf_snrcut
        img.psf_snrcut = snrcut
        if opts.psf_high_snr != None:
            if opts.psf_high_snr < 10.0:
                mylogger.userinfo(mylog, "Value of psf_high_snr too low; increasing to 10")
                high_snrcut = 10.0
            else:
                high_snrcut = opts.psf_high_snr
        else:
            high_snrcut = opts.psf_high_snr
        img.psf_high_snr = high_snrcut

        wtfns=['unity', 'roundness', 'log10', 'sqrtlog10']
        if 0 <= itess_method < 4: tess_method=wtfns[itess_method]
        else: tess_method='unity'

        ### now put all relevant gaussian parameters into a list
        ngaus = img.ngaus
        nsrc = img.nsrc
        num = N.zeros(nsrc, dtype=N.int32)
        peak = N.zeros(nsrc)
        xc = N.zeros(nsrc)
        yc = N.zeros(nsrc)
        bmaj = N.zeros(nsrc)
        bmin = N.zeros(nsrc)
        bpa = N.zeros(nsrc)
        code = N.array(['']*nsrc);
        rms = N.zeros(nsrc)
        src_id_list = []
        for i, src in enumerate(img.sources):
            src_max = 0.0
            for gmax in src.gaussians:
                # Take only brightest Gaussian per source
                if gmax.peak_flux > src_max:
                    src_max = gmax.peak_flux
                    g = gmax
            num[i] = i
            peak[i] = g.peak_flux
            xc[i] = g.centre_pix[0]
            yc[i] = g.centre_pix[1]
            bmaj[i] = g.size_pix[0]
            bmin[i] = g.size_pix[1]
            bpa[i] = g.size_pix[2]
            code[i] = img.sources[g.source_id].code
            rms[i] = img.islands[g.island_id].rms
        gauls = (num, peak, xc, yc, bmaj, bmin, bpa, code, rms)
        tr_gauls = self.trans_gaul(gauls)

        # takes gaussians with code=S and snr > snrcut.
        if s_only:
            tr = [n for n in tr_gauls if n[1]/n[8]>snrcut and n[7] == 'S']
        else:
            tr = [n for n in tr_gauls if n[1]/n[8]>snrcut]
        g_gauls = self.trans_gaul(tr)

        # computes statistics of fitted sizes. Same as psfvary_fullstat.f in fBDSM.
        bmaj_a, bmaj_r, bmaj_ca, bmaj_cr, ni = _cbdsm.bstat(bmaj, None, nsig)
        bmin_a, bmin_r, bmin_ca, bmin_cr, ni = _cbdsm.bstat(bmin, None, nsig)
        bpa_a, bpa_r, bpa_ca, bpa_cr, ni = _cbdsm.bstat(bpa, None, nsig)

        # get subset of sources deemed to be unresolved. Same as size_ksclip_wenss.f in fBDSM.
        flag_unresolved = self.get_unresolved(g_gauls, img.beam, nsig, kappa2, over, img.psf_high_snr, plot)

        # see how much the SNR-weighted sizes of unresolved sources differ from the synthesized beam.
        wtsize_beam_snr = self.av_psf(g_gauls, img.beam, flag_unresolved)

        # filter out resolved sources
        tr_gaul = self.trans_gaul(g_gauls)
        tr = [n for i, n in enumerate(tr_gaul) if flag_unresolved[i]]
        g_gauls = self.trans_gaul(tr)
        mylogger.userinfo(mylog, 'Number of unresolved sources', str(len(g_gauls[0])))

        # get a list of voronoi generators. vorogenS has values (and not None) if generators='field'.
        vorogenP, vorogenS = self.get_voronoi_generators(g_gauls, generators, gencode, snrcut, snrtop, snrbot, snrcutstack)
        mylogger.userinfo(mylog, 'Number of generators for PSF variation', str(len(vorogenP[0])))
        if len(vorogenP[0]) < 3:
            mylog.warning('Insufficient number of generators')
            return

        mylogger.userinfo(mylog, 'Tesselating image')
        # group generators into tiles
        tile_prop = self.edit_vorogenlist(vorogenP, frac=0.9)

        # tesselate the image
        volrank, vorowts = self.tesselate(vorogenP, vorogenS, tile_prop, tess_method, tess_sc, tess_fuzzy, \
                  generators, gencode, image.shape)
        if opts.output_all:
            func.write_image_to_file(img.use_io, img.imagename + '.volrank.fits', volrank, img, dir)

        tile_list, tile_coord, tile_snr = tile_prop
        ntile = len(tile_list)
        bar = statusbar.StatusBar('Determining PSF variation ............... : ', 0, ntile)
        mylogger.userinfo(mylog, 'Number of tiles for PSF variation', str(ntile))

        # For each tile, calculate the weighted averaged psf image. Also for all the sources in the image.
        cdelt = list(img.wcs_obj.acdelt[0:2])
        factor=3.
        psfimages, psfcoords, totpsfimage, psfratio, psfratio_aper = self.psf_in_tile(image, img.beam, g_gauls, \
                   cdelt, factor, snrcutstack, volrank, tile_prop, plot, img)
        npsf = len(psfimages)

        if opts.psf_use_shap:
            # use totpsfimage to get beta, centre and nmax for shapelet decomposition. Use nmax=5 or 6
            mask=N.zeros(totpsfimage.shape, dtype=bool)
            (m1, m2, m3)=func.moment(totpsfimage, mask)
            betainit=sqrt(m3[0]*m3[1])*2.0  * 1.4
            tshape = totpsfimage.shape
            cen = N.array(N.unravel_index(N.argmax(totpsfimage), tshape))+[1,1]
            cen = tuple(cen)
            nmax = 12
            basis = 'cartesian'
            betarange = [0.5,sqrt(betainit*max(tshape))]
            beta, error  = sh.shape_varybeta(totpsfimage, mask, basis, betainit, cen, nmax, betarange, plot)
            if error == 1: print '  Unable to find minimum in beta'

            # decompose all the psf images using the beta from above
            nmax=12; psf_cf=[]
            for i in range(npsf):
                psfim = psfimages[i]
                cf = sh.decompose_shapelets(psfim, mask, basis, beta, cen, nmax, mode='')
                psf_cf.append(cf)
                if img.opts.quiet == False:
                    bar.increment()
            bar.stop()

            # transpose the psf image list
            xt, yt = N.transpose(tile_coord)
            tr_psf_cf = N.transpose(N.array(psf_cf))

            # interpolate the coefficients across the image. Ok, interpolate in scipy for
            # irregular grids is crap. doesnt even pass through some of the points.
            # for now, fit polynomial.
            compress = 100.0
            x, y = N.transpose(psfcoords)
            if len(x) < 3:
                mylog.warning('Insufficient number of tiles to do interpolation of PSF variation')
                return

            psf_coeff_interp, xgrid, ygrid = self.interp_shapcoefs(nmax, tr_psf_cf, psfcoords, image.shape, \
                     compress, plot)

            psfshape = psfimages[0].shape
            skip = 5
            aa = self.create_psf_grid(psf_coeff_interp, image.shape, xgrid, ygrid, skip, nmax, psfshape, \
                 basis, beta, cen, totpsfimage, plot)
            img.psf_images = aa
        else:
            if ntile < 4:
                mylog.warning('Insufficient number of tiles to do interpolation of PSF variation')
                return
            else:
                # Fit stacked PSFs with Gaussians and measure aperture fluxes
                bm_pix = N.array([img.pixel_beam()[0]*fwsig, img.pixel_beam()[1]*fwsig, img.pixel_beam()[2]])
                psf_maj = N.zeros(npsf)
                psf_min = N.zeros(npsf)
                psf_pa = N.zeros(npsf)
                if img.opts.quiet == False:
                    bar.start()
                for i in range(ntile):
                    psfim = psfimages[i]
                    mask = N.zeros(psfim.shape, dtype=bool)
                    x_ax, y_ax = N.indices(psfim.shape)
                    maxv = N.max(psfim)
                    p_ini = [maxv, (psfim.shape[0]-1)/2.0*1.1, (psfim.shape[1]-1)/2.0*1.1, bm_pix[0]/fwsig*1.3,
                             bm_pix[1]/fwsig*1.1, bm_pix[2]*2]
                    para, ierr = func.fit_gaus2d(psfim, p_ini, x_ax, y_ax, mask)
                    ### first extent is major
                    if para[3] < para[4]:
                        para[3:5] = para[4:2:-1]
                        para[5] += 90
                    ### clip position angle
                    para[5] = divmod(para[5], 180)[1]

                    psf_maj[i] = para[3]
                    psf_min[i] = para[4]
                    posang = para[5]
                    while posang >= 180.0:
                        posang -= 180.0
                    psf_pa[i] = posang

                    if img.opts.quiet == False:
                        bar.increment()
                bar.stop()

                # Interpolate Gaussian parameters
                if img.aperture == None:
                    psf_maps = [psf_maj, psf_min, psf_pa, psfratio]
                else:
                    psf_maps = [psf_maj, psf_min, psf_pa, psfratio, psfratio_aper]
                nimgs = len(psf_maps)
                bar = statusbar.StatusBar('Interpolating PSF images ................ : ', 0, nimgs)
                if img.opts.quiet == False:
                    bar.start()
                map_list = mp.parallel_map(func.eval_func_tuple,
                    itertools.izip(itertools.repeat(self.interp_prop),
                    psf_maps, itertools.repeat(psfcoords),
                    itertools.repeat(image.shape)), numcores=opts.ncores,
                    bar=bar)
                if img.aperture == None:
                    psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int = map_list
                else:
                    psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int = map_list

                # Smooth if desired
                if img.opts.psf_smooth != None:
                    sm_scale = img.opts.psf_smooth / img.pix2beam([1.0, 1.0, 0.0])[0] / 3600.0 # pixels
                    if img.opts.aperture == None:
                        psf_maps = [psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int]
                    else:
                        psf_maps = [psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int]
                    nimgs = len(psf_maps)
                    bar = statusbar.StatusBar('Smoothing PSF images .................... : ', 0, nimgs)
                    if img.opts.quiet == False:
                        bar.start()
                    map_list = mp.parallel_map(func.eval_func_tuple,
                        itertools.izip(itertools.repeat(self.blur_image),
                        psf_maps, itertools.repeat(sm_scale)), numcores=opts.ncores,
                        bar=bar)
                    if img.aperture == None:
                        psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int = map_list
                    else:
                        psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int = map_list

                # Make sure all smoothed, interpolated images are ndarrays
                psf_maj_int = N.array(psf_maj_int)
                psf_min_int = N.array(psf_min_int)
                psf_pa_int = N.array(psf_pa_int)
                psf_ratio_int = N.array(psf_ratio_int)
                if img.aperture == None:
                    psf_ratio_aper_int = N.zeros(psf_maj_int.shape, dtype=N.float32)
                else:
                    psf_ratio_aper_int = N.array(psf_ratio_aper_int, dtype=N.float32)

                # Blank with NaNs if needed
                mask = img.mask_arr
                if isinstance(mask, N.ndarray):
                    pix_masked = N.where(mask == True)
                    psf_maj_int[pix_masked] = N.nan
                    psf_min_int[pix_masked] = N.nan
                    psf_pa_int[pix_masked] = N.nan
                    psf_ratio_int[pix_masked] = N.nan
                    psf_ratio_aper_int[pix_masked] = N.nan

                # Store interpolated images. The major and minor axis images are
                # the sigma in units of arcsec, the PA image in units of degrees east of
                # north, the ratio images in units of 1/beam.
                img.psf_vary_maj_arr = psf_maj_int * img.pix2beam([1.0, 1.0, 0.0])[0] * 3600.0 # sigma in arcsec
                img.psf_vary_min_arr = psf_min_int * img.pix2beam([1.0, 1.0, 0.0])[0] * 3600.0 # sigma in arcsec
                img.psf_vary_pa_arr = psf_pa_int
                img.psf_vary_ratio_arr = psf_ratio_int # in 1/beam
                img.psf_vary_ratio_aper_arr = psf_ratio_aper_int # in 1/beam

                if opts.output_all:
                    func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_maj.fits', img.psf_vary_maj_arr*fwsig, img, dir)
                    func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_min.fits', img.psf_vary_min_arr*fwsig, img, dir)
                    func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_pa.fits', img.psf_vary_pa_arr, img, dir)
                    func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_ratio.fits', img.psf_vary_ratio_arr, img, dir)
                    func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_ratio_aper.fits', img.psf_vary_ratio_aper_arr, img, dir)

                # Loop through source and Gaussian lists and deconvolve the sizes using appropriate beam
                bar2 = statusbar.StatusBar('Correcting deconvolved source sizes ..... : ', 0, img.nsrc)
                if img.opts.quiet == False:
                    bar2.start()
                for src in img.sources:
                    src_pos = img.sky2pix(src.posn_sky_centroid)
                    src_pos_int = (int(src_pos[0]), int(src_pos[1]))
                    gaus_c = img.gaus2pix(src.size_sky, src.posn_sky_centroid)
                    gaus_bm = [psf_maj_int[src_pos_int]*fwsig, psf_min_int[src_pos_int]*fwsig, psf_pa_int[src_pos_int]*fwsig]
                    gaus_dc, err = func.deconv2(gaus_bm, gaus_c)
                    src.deconv_size_sky = img.pix2gaus(gaus_dc, src_pos)
                    src.deconv_size_skyE = [0.0, 0.0, 0.0]
                    for g in src.gaussians:
                        gaus_c = img.gaus2pix(g.size_sky, src.posn_sky_centroid)
                        gaus_dc, err = func.deconv2(gaus_bm, gaus_c)
                        g.deconv_size_sky = img.pix2gaus(gaus_dc, g.centre_pix)
                        g.deconv_size_skyE = [0.0, 0.0, 0.0]
                        if img.opts.quiet == False:
                            bar2.spin()
                    if img.opts.quiet == False:
                        bar2.increment()
                bar2.stop()
        img.completed_Ops.append('psf_vary')
Ejemplo n.º 2
0
class Op_gaul2srl(Op):
    """
    Slightly modified from fortran.
    """
    def __call__(self, img):
        #  for each island, get the gaussians into a list and then send them to process
        #  src_index is source number, starting from 0
        mylog = mylogger.logging.getLogger("PyBDSM." + img.log + "Gaul2Srl")
        mylogger.userinfo(mylog, 'Grouping Gaussians into sources')
        img.aperture = img.opts.aperture
        if img.aperture is not None and img.aperture <= 0.0:
            mylog.warn('Specified aperture is <= 0. Skipping aperture fluxes.')
            img.aperture = None

        src_index = -1
        dsrc_index = 0
        sources = []
        dsources = []
        no_gaus_islands = []
        for iisl, isl in enumerate(img.islands):
            isl_sources = []
            isl_dsources = []
            g_list = []
            for g in isl.gaul:
                if g.flag == 0:
                    g_list.append(g)

            if len(g_list) > 0:
                if len(g_list) == 1:
                    src_index, source = self.process_single_gaussian(img,
                                                                     g_list,
                                                                     src_index,
                                                                     code='S')
                    sources.append(source)
                    isl_sources.append(source)
                else:
                    src_index, source = self.process_CM(
                        img, g_list, isl, src_index)
                    sources.extend(source)
                    isl_sources.extend(source)
            else:
                if not img.waveletimage:
                    dg = isl.dgaul[0]
                    no_gaus_islands.append(
                        (isl.island_id, dg.centre_pix[0], dg.centre_pix[1]))
                    # Put in the dummy Source as the source and use negative IDs
                    g_list = isl.dgaul
                    dsrc_index, dsource = self.process_single_gaussian(
                        img, g_list, dsrc_index, code='S')
                    dsources.append(dsource)
                    isl_dsources.append(dsource)

            isl.sources = isl_sources
            isl.dsources = isl_dsources
        img.sources = sources
        img.dsources = dsources
        img.nsrc = src_index + 1
        mylogger.userinfo(mylog, "Number of sources formed from Gaussians",
                          str(img.nsrc))
        if not img.waveletimage and not img._pi and len(
                no_gaus_islands) > 0 and not img.opts.quiet:
            message = 'All Gaussians were flagged for the following island'
            if len(no_gaus_islands) == 1:
                message += ':\n'
            else:
                message += 's:\n'
            for isl_id in no_gaus_islands:
                message += '    Island #%i (x=%i, y=%i)\n' % isl_id
            if len(no_gaus_islands) == 1:
                message += 'Please check this island. If it is a valid island and\n'
            else:
                message += 'Please check these islands. If they are valid islands and\n'
            if img.opts.atrous_do:
                message += 'should be fit, try adjusting the flagging options (use\n'\
                           'show_fit with "ch0_flagged=True" to see the flagged Gaussians).'
            else:
                message += 'should be fit, try adjusting the flagging options (use\n'\
                           'show_fit with "ch0_flagged=True" to see the flagged Gaussians)\n'\
                           'or enabling the wavelet module (with "atrous_do=True").'
            message += '\nTo include empty islands in output source catalogs, set\n'\
                        'incl_empty=True in the write_catalog task.'
            mylog.warning(message)

        img.completed_Ops.append('gaul2srl')

#################################################################################################

    def process_single_gaussian(self, img, g_list, src_index, code):
        """ Process single gaussian into a source, for both S and C type sources. g is just one
            Gaussian object (not a list)."""

        g = g_list[0]

        total_flux = [g.total_flux, g.total_fluxE]
        peak_flux_centroid = peak_flux_max = [g.peak_flux, g.peak_fluxE]
        posn_sky_centroid = posn_sky_max = [g.centre_sky, g.centre_skyE]
        size_sky = [g.size_sky, g.size_skyE]
        size_sky_uncorr = [g.size_sky_uncorr, g.size_skyE]
        deconv_size_sky = [g.deconv_size_sky, g.deconv_size_skyE]
        deconv_size_sky_uncorr = [g.deconv_size_sky_uncorr, g.deconv_size_skyE]
        bbox = img.islands[g.island_id].bbox
        ngaus = 1
        island_id = g.island_id
        if g.gaus_num < 0:
            gaussians = []
        else:
            gaussians = list([g])
        aper_flux = func.ch0_aperture_flux(img, g.centre_pix, img.aperture)

        source_prop = list([code, total_flux, peak_flux_centroid, peak_flux_max, aper_flux, posn_sky_centroid, \
             posn_sky_max, size_sky, size_sky_uncorr, deconv_size_sky, deconv_size_sky_uncorr, bbox, ngaus, island_id, gaussians])
        source = Source(img, source_prop)

        if g.gaussian_idx == -1:
            src_index -= 1
        else:
            src_index += 1
        g.source_id = src_index
        g.code = code
        source.source_id = src_index

        return src_index, source

##################################################################################################

    def process_CM(self, img, g_list, isl, src_index):
        """
        Bundle errors with the quantities.
        ngau = number of gaussians in island
        src_id = the source index array for every gaussian in island
        nsrc = final number of distinct sources in the island
        """

        ngau = len(g_list)  # same as cisl in callgaul2srl.f
        nsrc = ngau  # same as islct; initially make each gaussian as a source
        src_id = N.arange(nsrc)  # same as islnum in callgaul2srl.f

        boxx, boxy = isl.bbox
        subn = boxx.stop - boxx.start
        subm = boxy.stop - boxy.start
        delc = [boxx.start, boxy.start]
        subim = self.make_subim(subn, subm, g_list, delc)

        index = [(i, j) for i in range(ngau) for j in range(ngau) if j > i]
        for pair in index:
            same_island = self.in_same_island(pair, img, g_list, isl, subim,
                                              subn, subm, delc)
            if same_island:
                nsrc -= 1
                mmax, mmin = max(src_id[pair[0]],
                                 src_id[pair[1]]), min(src_id[pair[0]],
                                                       src_id[pair[1]])
                arr = N.where(src_id == mmax)[0]
                src_id[arr] = mmin
                # now reorder src_id so that it is contiguous
        for i in range(ngau):
            ind1 = N.where(src_id == i)[0]
            if len(ind1) == 0:
                arr = N.where(src_id > i)[0]
                if len(arr) > 0:
                    decr = N.min(src_id[arr]) - i
                    for j in arr:
                        src_id[j] -= decr
        nsrc = N.max(src_id) + 1
        # now do whats in sub_calc_para_source

        source_list = []
        for isrc in range(nsrc):
            posn = N.where(src_id == isrc)[0]
            g_sublist = []
            for i in posn:
                g_sublist.append(g_list[i])
            ngau_insrc = len(posn)
            # Do source type C
            if ngau_insrc == 1:
                src_index, source = self.process_single_gaussian(img,
                                                                 g_sublist,
                                                                 src_index,
                                                                 code='C')
            else:
                # make mask and subim. Invalid mask value is -1 since 0 is valid srcid
                mask = self.make_mask(isl, subn, subm, 1, isrc, g_sublist,
                                      delc)
                src_index, source = self.process_Multiple(img, g_sublist, mask, src_index, isrc, subim, \
                                    isl, delc, subn, subm)
            source_list.append(source)

        return src_index, source_list

##################################################################################################

    def in_same_island(self, pair, img, g_list, isl, subim, subn, subm, delc):
        """ Whether two gaussians belong to the same source or not. """
        import functions as func

        def same_island_min(pair, g_list, subim, delc, tol=0.5):
            """ If the minimum of the reconstructed fluxes along the line joining the peak positions
                is greater than thresh_isl times the rms_clip, they belong to different islands. """

            g1 = g_list[pair[0]]
            g2 = g_list[pair[1]]
            pix1 = N.array(g1.centre_pix)
            pix2 = N.array(g2.centre_pix)

            x1, y1 = N.floor(pix1) - delc
            x2, y2 = N.floor(pix2) - delc
            pix1 = N.array(
                N.unravel_index(N.argmax(subim[x1:x1 + 2, y1:y1 + 2]),
                                (2, 2))) + [x1, y1]
            pix2 = N.array(
                N.unravel_index(N.argmax(subim[x2:x2 + 2, y2:y2 + 2]),
                                (2, 2))) + [x2, y2]
            if pix1[1] >= subn: pix1[1] = pix1[1] - 1
            if pix2[1] >= subm: pix2[1] = pix2[1] - 1

            maxline = int(round(N.max(N.abs(pix1 - pix2) + 1)))
            flux1 = g1.peak_flux
            flux2 = g2.peak_flux
            # get pix values of the line
            pixdif = pix2 - pix1
            same_island_min = False
            same_island_cont = False
            if maxline == 1:
                same_island_min = True
                same_island_cont = True
            else:
                if abs(pixdif[0]) > abs(pixdif[1]):
                    xline = N.round(min(pix1[0], pix2[0]) + N.arange(maxline))
                    yline = N.round((pix1[1]-pix2[1])/(pix1[0]-pix2[0])* \
                           (min(pix1[0],pix2[0])+N.arange(maxline)-pix1[0])+pix1[1])
                else:
                    yline = N.round(min(pix1[1], pix2[1]) + N.arange(maxline))
                    xline = N.round((pix1[0]-pix2[0])/(pix1[1]-pix2[1])* \
                           (min(pix1[1],pix2[1])+N.arange(maxline)-pix1[1])+pix1[0])
                rpixval = N.zeros(maxline, dtype=N.float32)
                xbig = N.where(xline >= N.size(subim, 0))
                xline[xbig] = N.size(subim, 0) - 1
                ybig = N.where(yline >= N.size(subim, 1))
                yline[ybig] = N.size(subim, 1) - 1
                for i in range(maxline):
                    pixval = subim[xline[i], yline[i]]
                    rpixval[i] = pixval
                min_pixval = N.min(rpixval)
                minind_p = N.argmin(rpixval)
                maxind_p = N.argmax(rpixval)

                if minind_p in (0, maxline - 1) and maxind_p in (0,
                                                                 maxline - 1):
                    same_island_cont = True
                if min_pixval >= min(flux1, flux2):
                    same_island_min = True
                elif abs(min_pixval - min(flux1, flux2)
                         ) <= tol * isl.rms * img.opts.thresh_isl:
                    same_island_min = True

            return same_island_min, same_island_cont

        def same_island_dist(pair, g_list, tol=0.5):
            """ If the centres are seperated by a distance less than half the sum of their
                fwhms along the PA of the line joining them, they belong to the same island. """
            from math import sqrt

            g1 = g_list[pair[0]]
            g2 = g_list[pair[1]]
            pix1 = N.array(g1.centre_pix)
            pix2 = N.array(g2.centre_pix)
            gsize1 = g1.size_pix
            gsize2 = g2.size_pix

            fwhm1 = func.gdist_pa(pix1, pix2, gsize1)
            fwhm2 = func.gdist_pa(pix1, pix2, gsize2)
            dx = pix2[0] - pix1[0]
            dy = pix2[1] - pix1[1]
            dist = sqrt(dy * dy + dx * dx)

            if dist <= tol * (fwhm1 + fwhm2):
                same_island = True
            else:
                same_island = False

            return same_island

        if img.opts.group_by_isl:
            same_isl1_min = True
            same_isl1_cont = True
            same_isl2 = True
        else:
            if img.opts.group_method == 'curvature':
                subim = -1.0 * func.make_curvature_map(subim)
            tol = img.opts.group_tol
            same_isl1_min, same_isl1_cont = same_island_min(
                pair, g_list, subim, delc, tol)
            same_isl2 = same_island_dist(pair, g_list, tol / 2.0)

        g1 = g_list[pair[0]]

        same_island = (same_isl1_min and same_isl2) or same_isl1_cont

        return same_island

##################################################################################################

    def process_Multiple(self, img, g_sublist, mask, src_index, isrc, subim,
                         isl, delc, subn, subm):
        """ Same as gaul_to_source.f. isrc is same as k in the fortran version. """
        from math import pi, sqrt
        from const import fwsig
        from scipy import ndimage
        import functions as func

        mylog = mylogger.logging.getLogger("PyBDSM." + img.log + "Gaul2Srl  ")
        dum = img.beam[0] * img.beam[1]
        cdeltsq = img.wcs_obj.acdelt[0] * img.wcs_obj.acdelt[1]
        bmar_p = 2.0 * pi * dum / (cdeltsq * fwsig * fwsig)

        # try
        subim_src = self.make_subim(subn, subm, g_sublist, delc)
        mompara = func.momanalmask_gaus(subim_src, mask, isrc, bmar_p, True)
        # initial peak posn and value
        maxv = N.max(subim_src)
        maxx, maxy = N.unravel_index(N.argmax(subim_src), subim_src.shape)
        # fit gaussian around this posn
        blc = N.zeros(2)
        trc = N.zeros(2)
        n, m = subim_src.shape[0:2]
        bm_pix = N.array([
            img.pixel_beam()[0] * fwsig,
            img.pixel_beam()[1] * fwsig,
            img.pixel_beam()[2]
        ])
        ssubimsize = max(N.int(N.round(N.max(bm_pix[0:2]) * 2)) + 1, 5)
        blc[0] = max(0, maxx - (ssubimsize - 1) / 2)
        blc[1] = max(0, maxy - (ssubimsize - 1) / 2)
        trc[0] = min(n, maxx + (ssubimsize - 1) / 2)
        trc[1] = min(m, maxy + (ssubimsize - 1) / 2)
        s_imsize = trc - blc + 1

        p_ini = [maxv, (s_imsize[0]-1)/2.0*1.1, (s_imsize[1]-1)/2.0*1.1, bm_pix[0]/fwsig*1.3, \
                 bm_pix[1]/fwsig*1.1, bm_pix[2]*2]
        data = subim_src[blc[0]:blc[0] + s_imsize[0],
                         blc[1]:blc[1] + s_imsize[1]]
        smask = mask[blc[0]:blc[0] + s_imsize[0], blc[1]:blc[1] + s_imsize[1]]
        rmask = N.where(smask == isrc, False, True)
        x_ax, y_ax = N.indices(data.shape)

        if N.sum(~rmask) >= 6:
            para, ierr = func.fit_gaus2d(data, p_ini, x_ax, y_ax, rmask)
            if (0.0<para[1]<s_imsize[0]) and (0.0<para[2]<s_imsize[1]) and \
              para[3]<s_imsize[0] and para[4]<s_imsize[1]:
                maxpeak = para[0]
            else:
                maxpeak = maxv
            posn = para[1:3] - (0.5 * N.sum(s_imsize) - 1) / 2.0 + N.array(
                [maxx, maxy]) - 1 + delc
        else:
            maxpeak = maxv
            posn = N.unravel_index(N.argmax(data * ~rmask),
                                   data.shape) + N.array(delc) + blc

        # calculate peak by bilinear interpolation around centroid
        # First check that moment analysis gave a valid position. If not, use
        # posn from gaussian fit instead.
        if N.isnan(mompara[1]):
            mompara[1] = posn[0] - delc[0]
        x1 = N.int(N.floor(mompara[1]))
        if N.isnan(mompara[2]):
            mompara[2] = posn[1] - delc[1]
        y1 = N.int(N.floor(mompara[2]))
        xind = slice(x1, x1 + 2, 1)
        yind = slice(y1, y1 + 2, 1)
        if img.opts.flag_smallsrc and (
                N.sum(mask[xind, yind] == N.ones((2, 2)) * isrc) != 4):
            mylog.debug('Island = ' + str(isl.island_id))
            mylog.debug('Mask = ' + repr(mask[xind, yind]) +
                        'xind, yind, x1, y1 = ' + repr(xind) + ' ' +
                        repr(yind) + ' ' + repr(x1) + ' ' + repr(y1))
        t = (mompara[1] - x1) / (x1 + 1 - x1)  # in case u change it later
        u = (mompara[2] - y1) / (y1 + 1 - y1)
        s_peak=(1.0-t)*(1.0-u)*subim_src[x1,y1]+t*(1.0-u)*subim_src[x1+1,y1]+ \
               t*u*subim_src[x1+1,y1+1]+(1.0-t)*u*subim_src[x1,y1+1]
        if (not img.opts.flag_smallsrc) and (
                N.sum(mask[xind, yind] == N.ones((2, 2)) * isrc) != 4):
            mylog.debug('Speak ' + repr(s_peak) + 'Mompara = ' + repr(mompara))
            mylog.debug('x1, y1 : ' + repr(x1) + ', ' + repr(y1))
            # import pylab as pl
            # pl.imshow(N.transpose(subim_src), origin='lower', interpolation='nearest')
            # pl.suptitle('Image of bad M source '+str(isl.island_id))
            # convert pixels to coords
        try:
            sra, sdec = img.pix2sky(
                [mompara[1] + delc[0], mompara[2] + delc[1]])
            mra, mdec = img.pix2sky(posn)
        except RuntimeError, err:
            # Invalid pixel wcs coordinate
            sra, sdec = 0.0, 0.0
            mra, mdec = 0.0, 0.0

        # "deconvolve" the sizes
        gaus_c = [mompara[3], mompara[4], mompara[5]]
        gaus_bm = [bm_pix[0], bm_pix[1], bm_pix[2]]
        gaus_dc, err = func.deconv2(gaus_bm, gaus_c)
        deconv_size_sky = img.pix2gaus(
            gaus_dc, [mompara[1] + delc[0], mompara[2] + delc[1]])
        deconv_size_sky_uncorr = img.pix2gaus(
            gaus_dc, [mompara[1] + delc[0], mompara[2] + delc[1]],
            use_wcs=False)

        # update all objects etc
        tot = 0.0
        totE_sq = 0.0
        for g in g_sublist:
            tot += g.total_flux
            totE_sq += g.total_fluxE**2
        totE = sqrt(totE_sq)
        size_pix = [mompara[3], mompara[4], mompara[5]]
        size_sky = img.pix2gaus(size_pix,
                                [mompara[1] + delc[0], mompara[2] + delc[1]])
        size_sky_uncorr = img.pix2gaus(
            size_pix, [mompara[1] + delc[0], mompara[2] + delc[1]],
            use_wcs=False)

        # Estimate uncertainties in source size and position due to
        # errors in the constituent Gaussians using a Monte Carlo technique.
        # Sum with Condon (1997) errors in quadrature.
        plist = mompara.tolist() + [tot]
        plist[0] = s_peak
        plist[3] /= fwsig
        plist[4] /= fwsig
        errors = func.get_errors(img, plist, isl.rms)

        if img.opts.do_mc_errors:
            nMC = 20
            mompara0_MC = N.zeros(nMC, dtype=N.float32)
            mompara1_MC = N.zeros(nMC, dtype=N.float32)
            mompara2_MC = N.zeros(nMC, dtype=N.float32)
            mompara3_MC = N.zeros(nMC, dtype=N.float32)
            mompara4_MC = N.zeros(nMC, dtype=N.float32)
            mompara5_MC = N.zeros(nMC, dtype=N.float32)
            for i in range(nMC):
                # Reconstruct source from component Gaussians. Draw the Gaussian
                # parameters from random distributions given by their errors.
                subim_src_MC = self.make_subim(subn,
                                               subm,
                                               g_sublist,
                                               delc,
                                               mc=True)

                try:
                    mompara_MC = func.momanalmask_gaus(subim_src_MC, mask,
                                                       isrc, bmar_p, True)
                    mompara0_MC[i] = mompara_MC[0]
                    mompara1_MC[i] = mompara_MC[1]
                    mompara2_MC[i] = mompara_MC[2]
                    mompara3_MC[i] = mompara_MC[3]
                    mompara4_MC[i] = mompara_MC[4]
                    mompara5_MC[i] = mompara_MC[5]
                except:
                    mompara0_MC[i] = mompara[0]
                    mompara1_MC[i] = mompara[1]
                    mompara2_MC[i] = mompara[2]
                    mompara3_MC[i] = mompara[3]
                    mompara4_MC[i] = mompara[4]
                    mompara5_MC[i] = mompara[5]
            mompara0E = N.std(mompara0_MC)
            mompara1E = N.std(mompara1_MC)
            if mompara1E > 2.0 * mompara[1]:
                mompara1E = 2.0 * mompara[1]  # Don't let errors get too large
            mompara2E = N.std(mompara2_MC)
            if mompara2E > 2.0 * mompara[2]:
                mompara2E = 2.0 * mompara[2]  # Don't let errors get too large
            mompara3E = N.std(mompara3_MC)
            if mompara3E > 2.0 * mompara[3]:
                mompara3E = 2.0 * mompara[3]  # Don't let errors get too large
            mompara4E = N.std(mompara4_MC)
            if mompara4E > 2.0 * mompara[4]:
                mompara4E = 2.0 * mompara[4]  # Don't let errors get too large
            mompara5E = N.std(mompara5_MC)
            if mompara5E > 2.0 * mompara[5]:
                mompara5E = 2.0 * mompara[5]  # Don't let errors get too large
        else:
            mompara1E = 0.0
            mompara2E = 0.0
            mompara3E = 0.0
            mompara4E = 0.0
            mompara5E = 0.0

        # Now add MC errors in quadrature with Condon (1997) errors
        size_skyE = [
            sqrt(mompara3E**2 + errors[3]**2) * sqrt(cdeltsq),
            sqrt(mompara4E**2 + errors[4]**2) * sqrt(cdeltsq),
            sqrt(mompara5E**2 + errors[5]**2)
        ]
        sraE, sdecE = (sqrt(mompara1E**2 + errors[1]**2) * sqrt(cdeltsq),
                       sqrt(mompara2E**2 + errors[2]**2) * sqrt(cdeltsq))
        deconv_size_skyE = size_skyE  # set deconvolved errors to non-deconvolved ones

        # Find aperture flux
        if img.opts.aperture_posn == 'centroid':
            aper_pos = [mompara[1] + delc[0], mompara[2] + delc[1]]
        else:
            aper_pos = posn
        aper_flux, aper_fluxE = func.ch0_aperture_flux(img, aper_pos,
                                                       img.aperture)

        isl_id = isl.island_id
        source_prop = list([
            'M', [tot, totE], [s_peak, isl.rms], [maxpeak, isl.rms],
            [aper_flux, aper_fluxE], [[sra, sdec], [sraE, sdecE]],
            [[mra, mdec], [sraE, sdecE]], [size_sky, size_skyE],
            [size_sky_uncorr, size_skyE], [deconv_size_sky, deconv_size_skyE],
            [deconv_size_sky_uncorr, deconv_size_skyE], isl.bbox,
            len(g_sublist), isl_id, g_sublist
        ])
        source = Source(img, source_prop)

        src_index += 1
        for g in g_sublist:
            g.source_id = src_index
            g.code = 'M'
        source.source_id = src_index

        return src_index, source
Ejemplo n.º 3
0
    def __init__(self, img, gaussian, isl_idx, g_idx, flag=0):
        """Initialize Gaussian object from fitting data

        Parameters:
        img: PyBDSM image object
        gaussian: 6-tuple of fitted numbers
        isl_idx: island serial number
        g_idx: gaussian serial number
        flag: flagging (if any)
        """
        import functions as func
        from const import fwsig
        import numpy as N

        use_wcs = True
        self.gaussian_idx = g_idx
        self.gaus_num = 0 # stored later
        self.island_id = isl_idx
        self.jlevel = img.j
        self.flag = flag
        self.parameters = gaussian

        p = gaussian
        self.peak_flux = p[0]
        self.centre_pix = p[1:3]
        size = p[3:6]
        if func.approx_equal(size[0], img.pixel_beam()[0]*1.1) and \
                func.approx_equal(size[1], img.pixel_beam()[1]) and \
                func.approx_equal(size[2], img.pixel_beam()[2]+90.0) or \
                img.opts.fix_to_beam:
            # Check whether fitted Gaussian is just the distorted pixel beam
            # given as an initial guess or if size was fixed to the beam. If so,
            # reset the size to the undistorted beam.
            # Note: these are sigma sizes, not FWHM sizes.
            size = img.pixel_beam()
            size = (size[0], size[1], size[2]+90.0) # adjust angle so that corrected_size() works correctly
        size = func.corrected_size(size)  # gives fwhm and P.A.
        self.size_pix = size # FWHM in pixels and P.A. CCW from +y axis

        # Use img.orig_beam for flux calculation and deconvolution on wavelet
        # images, as img.beam has been altered to match the wavelet scale.
        # Note: these are all FWHM sizes.
        if img.waveletimage:
            bm_pix = N.array(img.beam2pix(img.orig_beam))
        else:
            bm_pix = N.array(img.beam2pix(img.beam))

        # Calculate fluxes, sky sizes, etc. All sizes are FWHM.
        tot = p[0]*size[0]*size[1]/(bm_pix[0]*bm_pix[1])
        if flag == 0:
            # These are good Gaussians
            errors = func.get_errors(img, p+[tot], img.islands[isl_idx].rms)
            self.centre_sky = img.pix2sky(p[1:3])
            self.centre_skyE = img.pix2coord(errors[1:3], self.centre_pix, use_wcs=use_wcs)
            self.size_sky = img.pix2gaus(size, self.centre_pix, use_wcs=use_wcs) # FWHM in degrees and P.A. east from north
            self.size_sky_uncorr = img.pix2gaus(size, self.centre_pix, use_wcs=False) # FWHM in degrees and P.A. east from +y axis
            self.size_skyE = img.pix2gaus(errors[3:6], self.centre_pix, use_wcs=use_wcs)
            self.size_skyE_uncorr = img.pix2gaus(errors[3:6], self.centre_pix, use_wcs=False)
            gaus_dc, err = func.deconv2(bm_pix, size)
            self.deconv_size_sky = img.pix2gaus(gaus_dc, self.centre_pix, use_wcs=use_wcs)
            self.deconv_size_sky_uncorr = img.pix2gaus(gaus_dc, self.centre_pix, use_wcs=False)
            self.deconv_size_skyE  = img.pix2gaus(errors[3:6], self.centre_pix, use_wcs=use_wcs)
            self.deconv_size_skyE_uncorr = img.pix2gaus(errors[3:6], self.centre_pix, use_wcs=False)
        else:
            # These are flagged Gaussians, so don't calculate sky values or errors
            errors = [0]*7
            self.centre_sky = [0., 0.]
            self.centre_skyE = [0., 0.]
            self.size_sky = [0., 0., 0.]
            self.size_sky_uncorr = [0., 0., 0.]
            self.size_skyE = [0., 0.]
            self.size_skyE_uncorr = [0., 0., 0.]
            self.deconv_size_sky = [0., 0., 0.]
            self.deconv_size_sky_uncorr = [0., 0., 0.]
            self.deconv_size_skyE  = [0., 0., 0.]
            self.deconv_size_skyE_uncorr = [0., 0., 0.]
        self.total_flux = tot
        self.total_fluxE = errors[6]
        self.peak_fluxE = errors[0]
        self.total_fluxE = errors[6]
        self.centre_pixE = errors[1:3]
        self.size_pixE = errors[3:6]
        self.rms = img.islands[isl_idx].rms
        self.mean = img.islands[isl_idx].mean
        self.total_flux_isl = img.islands[isl_idx].total_flux
        self.total_flux_islE = img.islands[isl_idx].total_fluxE
Ejemplo n.º 4
0
    def __call__(self, img):

      if img.opts.psf_vary_do:
        mylog = mylogger.logging.getLogger("PyBDSM."+img.log+"Psf_Vary")
        mylogger.userinfo(mylog, '\nEstimating PSF variations')
        opts = img.opts
        dir = img.basedir + '/misc/'
        plot = False # debug figures
        image = img.ch0_arr

        try:
            from astropy.io import fits as pyfits
            old_pyfits = False
        except ImportError, err:
            from distutils.version import StrictVersion
            import pyfits
            if StrictVersion(pyfits.__version__) < StrictVersion('2.2'):
                old_pyfits = True
            else:
                old_pyfits = False

        if old_pyfits:
            mylog.warning('PyFITS version is too old: psf_vary module skipped')
            return

        if opts.psf_fwhm is not None:
            # User has specified a constant PSF to use, so skip PSF fitting/etc.
            psf_maj = opts.psf_fwhm[0] # FWHM in deg
            psf_min = opts.psf_fwhm[1] # FWHM in deg
            psf_pa = opts.psf_fwhm[2] # PA in deg
            mylogger.userinfo(mylog, 'Using constant PSF (major, minor, pos angle)',
                  '(%.5e, %.5e, %s) degrees' % (psf_maj, psf_maj,
                                            round(psf_pa, 1)))
        else:
            # Use did not specify a constant PSF to use, so estimate it
            over = 2
            generators = opts.psf_generators; nsig = opts.psf_nsig; kappa2 = opts.psf_kappa2
            snrtop = opts.psf_snrtop; snrbot = opts.psf_snrbot; snrcutstack = opts.psf_snrcutstack
            gencode = opts.psf_gencode; primarygen = opts.psf_primarygen; itess_method = opts.psf_itess_method
            tess_sc = opts.psf_tess_sc; tess_fuzzy= opts.psf_tess_fuzzy
            bright_snr_cut = opts.psf_high_snr
            s_only = opts.psf_stype_only
            if opts.psf_snrcut < 5.0:
                mylogger.userinfo(mylog, "Value of psf_snrcut too low; increasing to 5")
                snrcut = 5.0
            else:
                snrcut = opts.psf_snrcut
            img.psf_snrcut = snrcut
            if opts.psf_high_snr is not None:
                if opts.psf_high_snr < 10.0:
                    mylogger.userinfo(mylog, "Value of psf_high_snr too low; increasing to 10")
                    high_snrcut = 10.0
                else:
                    high_snrcut = opts.psf_high_snr
            else:
                high_snrcut = opts.psf_high_snr
            img.psf_high_snr = high_snrcut

            wtfns=['unity', 'roundness', 'log10', 'sqrtlog10']
            if 0 <= itess_method < 4: tess_method=wtfns[itess_method]
            else: tess_method='unity'

            ### now put all relevant gaussian parameters into a list
            ngaus = img.ngaus
            nsrc = img.nsrc
            num = N.zeros(nsrc, dtype=N.int32)
            peak = N.zeros(nsrc)
            xc = N.zeros(nsrc)
            yc = N.zeros(nsrc)
            bmaj = N.zeros(nsrc)
            bmin = N.zeros(nsrc)
            bpa = N.zeros(nsrc)
            code = N.array(['']*nsrc);
            rms = N.zeros(nsrc)
            src_id_list = []
            for i, src in enumerate(img.sources):
                src_max = 0.0
                for gmax in src.gaussians:
                    # Take only brightest Gaussian per source
                    if gmax.peak_flux > src_max:
                        src_max = gmax.peak_flux
                        g = gmax
                num[i] = i
                peak[i] = g.peak_flux
                xc[i] = g.centre_pix[0]
                yc[i] = g.centre_pix[1]
                bmaj[i] = g.size_pix[0]
                bmin[i] = g.size_pix[1]
                bpa[i] = g.size_pix[2]
                code[i] = img.sources[g.source_id].code
                rms[i] = img.islands[g.island_id].rms
            gauls = (num, peak, xc, yc, bmaj, bmin, bpa, code, rms)
            tr_gauls = self.trans_gaul(gauls)

            # takes gaussians with code=S and snr > snrcut.
            if s_only:
                tr = [n for n in tr_gauls if n[1]/n[8]>snrcut and n[7] == 'S']
            else:
                tr = [n for n in tr_gauls if n[1]/n[8]>snrcut]
            g_gauls = self.trans_gaul(tr)

            # computes statistics of fitted sizes. Same as psfvary_fullstat.f in fBDSM.
            bmaj_a, bmaj_r, bmaj_ca, bmaj_cr, ni = _cbdsm.bstat(bmaj, None, nsig)
            bmin_a, bmin_r, bmin_ca, bmin_cr, ni = _cbdsm.bstat(bmin, None, nsig)
            bpa_a, bpa_r, bpa_ca, bpa_cr, ni = _cbdsm.bstat(bpa, None, nsig)

            # get subset of sources deemed to be unresolved. Same as size_ksclip_wenss.f in fBDSM.
            flag_unresolved = self.get_unresolved(g_gauls, img.beam, nsig, kappa2, over, img.psf_high_snr, plot)
            if len(flag_unresolved) == 0:
                mylog.warning('Insufficient number of sources to determine PSF variation.\nTry changing the PSF options or specify a (constant) PSF with the "psf_fwhm" option')
                return

            # see how much the SNR-weighted sizes of unresolved sources differ from the synthesized beam.
            wtsize_beam_snr = self.av_psf(g_gauls, img.beam, flag_unresolved)

            # filter out resolved sources
            tr_gaul = self.trans_gaul(g_gauls)
            tr = [n for i, n in enumerate(tr_gaul) if flag_unresolved[i]]
            g_gauls = self.trans_gaul(tr)
            mylogger.userinfo(mylog, 'Number of unresolved sources', str(len(g_gauls[0])))

            # get a list of voronoi generators. vorogenS has values (and not None) if generators='field'.
            vorogenP, vorogenS = self.get_voronoi_generators(g_gauls, generators, gencode, snrcut, snrtop, snrbot, snrcutstack)
            mylogger.userinfo(mylog, 'Number of generators for PSF variation', str(len(vorogenP[0])))
            if len(vorogenP[0]) < 3:
                mylog.warning('Insufficient number of generators')
                return

            mylogger.userinfo(mylog, 'Tesselating image')
            # group generators into tiles
            tile_prop = self.edit_vorogenlist(vorogenP, frac=0.9)

            # tesselate the image
            volrank, vorowts = self.tesselate(vorogenP, vorogenS, tile_prop, tess_method, tess_sc, tess_fuzzy, \
                      generators, gencode, image.shape)
            if opts.output_all:
                func.write_image_to_file(img.use_io, img.imagename + '.volrank.fits', volrank, img, dir)

            tile_list, tile_coord, tile_snr = tile_prop
            ntile = len(tile_list)
            bar = statusbar.StatusBar('Determining PSF variation ............... : ', 0, ntile)
            mylogger.userinfo(mylog, 'Number of tiles for PSF variation', str(ntile))

            # For each tile, calculate the weighted averaged psf image. Also for all the sources in the image.
            cdelt = list(img.wcs_obj.acdelt[0:2])
            factor=3.
            psfimages, psfcoords, totpsfimage, psfratio, psfratio_aper = self.psf_in_tile(image, img.beam, g_gauls, \
                       cdelt, factor, snrcutstack, volrank, tile_prop, plot, img)
            npsf = len(psfimages)

        if opts.psf_use_shap:
            if opts.psf_fwhm is None:
                # use totpsfimage to get beta, centre and nmax for shapelet decomposition. Use nmax=5 or 6
                mask=N.zeros(totpsfimage.shape, dtype=bool)
                (m1, m2, m3)=func.moment(totpsfimage, mask)
                betainit=sqrt(m3[0]*m3[1])*2.0  * 1.4
                tshape = totpsfimage.shape
                cen = N.array(N.unravel_index(N.argmax(totpsfimage), tshape))+[1,1]
                cen = tuple(cen)
                nmax = 12
                basis = 'cartesian'
                betarange = [0.5,sqrt(betainit*max(tshape))]
                beta, error  = sh.shape_varybeta(totpsfimage, mask, basis, betainit, cen, nmax, betarange, plot)
                if error == 1: print '  Unable to find minimum in beta'

                # decompose all the psf images using the beta from above
                nmax=12; psf_cf=[]
                for i in range(npsf):
                    psfim = psfimages[i]
                    cf = sh.decompose_shapelets(psfim, mask, basis, beta, cen, nmax, mode='')
                    psf_cf.append(cf)
                    if img.opts.quiet == False:
                        bar.increment()
                bar.stop()

                # transpose the psf image list
                xt, yt = N.transpose(tile_coord)
                tr_psf_cf = N.transpose(N.array(psf_cf))

                # interpolate the coefficients across the image. Ok, interpolate in scipy for
                # irregular grids is crap. doesnt even pass through some of the points.
                # for now, fit polynomial.
                compress = 100.0
                x, y = N.transpose(psfcoords)
                if len(x) < 3:
                    mylog.warning('Insufficient number of tiles to do interpolation of PSF variation')
                    return

                psf_coeff_interp, xgrid, ygrid = self.interp_shapcoefs(nmax, tr_psf_cf, psfcoords, image.shape, \
                         compress, plot)

                psfshape = psfimages[0].shape
                skip = 5
                aa = self.create_psf_grid(psf_coeff_interp, image.shape, xgrid, ygrid, skip, nmax, psfshape, \
                     basis, beta, cen, totpsfimage, plot)
                img.psf_images = aa
        else:
            if opts.psf_fwhm is None:
                if ntile < 4:
                    mylog.warning('Insufficient number of tiles to do interpolation of PSF variation')
                    return
                else:
                    # Fit stacked PSFs with Gaussians and measure aperture fluxes
                    bm_pix = N.array([img.pixel_beam()[0]*fwsig, img.pixel_beam()[1]*fwsig, img.pixel_beam()[2]])
                    psf_maj = N.zeros(npsf)
                    psf_min = N.zeros(npsf)
                    psf_pa = N.zeros(npsf)
                    if img.opts.quiet == False:
                        bar.start()
                    for i in range(ntile):
                        psfim = psfimages[i]
                        mask = N.zeros(psfim.shape, dtype=bool)
                        x_ax, y_ax = N.indices(psfim.shape)
                        maxv = N.max(psfim)
                        p_ini = [maxv, (psfim.shape[0]-1)/2.0*1.1, (psfim.shape[1]-1)/2.0*1.1, bm_pix[0]/fwsig*1.3,
                                 bm_pix[1]/fwsig*1.1, bm_pix[2]*2]
                        para, ierr = func.fit_gaus2d(psfim, p_ini, x_ax, y_ax, mask)
                        ### first extent is major
                        if para[3] < para[4]:
                            para[3:5] = para[4:2:-1]
                            para[5] += 90
                        ### clip position angle
                        para[5] = divmod(para[5], 180)[1]

                        psf_maj[i] = para[3]
                        psf_min[i] = para[4]
                        posang = para[5]
                        while posang >= 180.0:
                            posang -= 180.0
                        psf_pa[i] = posang

                        if img.opts.quiet == False:
                            bar.increment()
                    bar.stop()

                    # Interpolate Gaussian parameters
                    if img.aperture is None:
                        psf_maps = [psf_maj, psf_min, psf_pa, psfratio]
                    else:
                        psf_maps = [psf_maj, psf_min, psf_pa, psfratio, psfratio_aper]
                    nimgs = len(psf_maps)
                    bar = statusbar.StatusBar('Interpolating PSF images ................ : ', 0, nimgs)
                    if img.opts.quiet == False:
                        bar.start()
                    map_list = mp.parallel_map(func.eval_func_tuple,
                        itertools.izip(itertools.repeat(self.interp_prop),
                        psf_maps, itertools.repeat(psfcoords),
                        itertools.repeat(image.shape)), numcores=opts.ncores,
                        bar=bar)
                    if img.aperture is None:
                        psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int = map_list
                    else:
                        psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int = map_list

                    # Smooth if desired
                    if img.opts.psf_smooth is not None:
                        sm_scale = img.opts.psf_smooth / img.pix2beam([1.0, 1.0, 0.0])[0] / 3600.0 # pixels
                        if img.opts.aperture is None:
                            psf_maps = [psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int]
                        else:
                            psf_maps = [psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int]
                        nimgs = len(psf_maps)
                        bar = statusbar.StatusBar('Smoothing PSF images .................... : ', 0, nimgs)
                        if img.opts.quiet == False:
                            bar.start()
                        map_list = mp.parallel_map(func.eval_func_tuple,
                            itertools.izip(itertools.repeat(self.blur_image),
                            psf_maps, itertools.repeat(sm_scale)), numcores=opts.ncores,
                            bar=bar)
                        if img.aperture is None:
                            psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int = map_list
                        else:
                            psf_maj_int, psf_min_int, psf_pa_int, psf_ratio_int, psf_ratio_aper_int = map_list

                    # Make sure all smoothed, interpolated images are ndarrays
                    psf_maj_int = N.array(psf_maj_int)
                    psf_min_int = N.array(psf_min_int)
                    psf_pa_int = N.array(psf_pa_int)
                    psf_ratio_int = N.array(psf_ratio_int)
                    if img.aperture is None:
                        psf_ratio_aper_int = N.zeros(psf_maj_int.shape, dtype=N.float32)
                    else:
                        psf_ratio_aper_int = N.array(psf_ratio_aper_int, dtype=N.float32)

                    # Blank with NaNs if needed
                    mask = img.mask_arr
                    if isinstance(mask, N.ndarray):
                        pix_masked = N.where(mask == True)
                        psf_maj_int[pix_masked] = N.nan
                        psf_min_int[pix_masked] = N.nan
                        psf_pa_int[pix_masked] = N.nan
                        psf_ratio_int[pix_masked] = N.nan
                        psf_ratio_aper_int[pix_masked] = N.nan

                    # Store interpolated images. The major and minor axis images are
                    # the sigma in units of arcsec, the PA image in units of degrees east of
                    # north, the ratio images in units of 1/beam.
                    img.psf_vary_maj_arr = psf_maj_int * img.pix2beam([1.0, 1.0, 0.0])[0] * 3600.0 # sigma in arcsec
                    img.psf_vary_min_arr = psf_min_int * img.pix2beam([1.0, 1.0, 0.0])[0] * 3600.0 # sigma in arcsec
                    img.psf_vary_pa_arr = psf_pa_int
                    img.psf_vary_ratio_arr = psf_ratio_int # in 1/beam
                    img.psf_vary_ratio_aper_arr = psf_ratio_aper_int # in 1/beam

                    if opts.output_all:
                        func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_maj.fits', img.psf_vary_maj_arr*fwsig, img, dir)
                        func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_min.fits', img.psf_vary_min_arr*fwsig, img, dir)
                        func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_pa.fits', img.psf_vary_pa_arr, img, dir)
                        func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_ratio.fits', img.psf_vary_ratio_arr, img, dir)
                        func.write_image_to_file(img.use_io, img.imagename + '.psf_vary_ratio_aper.fits', img.psf_vary_ratio_aper_arr, img, dir)

            # Loop through source and Gaussian lists and deconvolve the sizes using appropriate beam
            bar2 = statusbar.StatusBar('Correcting deconvolved source sizes ..... : ', 0, img.nsrc)
            if img.opts.quiet == False:
                bar2.start()
            for src in img.sources:
                src_pos = img.sky2pix(src.posn_sky_centroid)
                src_pos_int = (int(src_pos[0]), int(src_pos[1]))
                gaus_c = img.gaus2pix(src.size_sky, src.posn_sky_centroid)
                if opts.psf_fwhm is None:
                    gaus_bm = [psf_maj_int[src_pos_int]*fwsig, psf_min_int[src_pos_int]*fwsig, psf_pa_int[src_pos_int]]
                else:
                    # Use user-specified constant PSF instead
                    gaus_bm = img.beam2pix(opts.psf_fwhm)
                gaus_dc, err = func.deconv2(gaus_bm, gaus_c)
                src.deconv_size_sky = img.pix2gaus(gaus_dc, src_pos)
                src.deconv_size_skyE = [0.0, 0.0, 0.0]
                for g in src.gaussians:
                    gaus_c = img.gaus2pix(g.size_sky, src.posn_sky_centroid)
                    gaus_dc, err = func.deconv2(gaus_bm, gaus_c)
                    g.deconv_size_sky = img.pix2gaus(gaus_dc, g.centre_pix)
                    g.deconv_size_skyE = [0.0, 0.0, 0.0]
                    if img.opts.quiet == False:
                        bar2.spin()
                if img.opts.quiet == False:
                    bar2.increment()
            bar2.stop()
        img.completed_Ops.append('psf_vary')