from __future__ import absolute_import
import os
import re
import pylab as pl

from casatasks.private.casa_transition import is_CASA6
if is_CASA6:
    from casatools import ctsys, quanta, imager
    from casatasks import casalog
    from .simutil import *
    from .simutil import is_array_type

    qa = quanta()
else:
    from taskinit import *
    from simutil import *
    from casa_stack_manip import stack_frame_find
    from simutil import is_array_type

    imager = imtool

def simobserve(
    project=None, 
    skymodel=None, inbright=None, indirection=None, incell=None, 
    incenter=None, inwidth=None, # innchan=None,
    complist=None, compwidth=None, comp_nchan=1,
    setpointings=None,
    ptgfile=None, integration=None, direction=None, mapsize=None, 
    maptype=None, pointingspacing=None, caldirection=None, calflux=None, 
    # observe=None,
    obsmode=None, 
    refdate=None, hourangle=None, 
    totaltime=None, antennalist=None, 
    sdantlist=None,
    sdant=None,
    outframe=None,
    thermalnoise=None,
    user_pwv=None, t_ground=None, t_sky=None, tau0=None, seed=None,
    leakage=None,
    graphics=None,
    verbose=None, 
    overwrite=None
    ):

    # Collect a list of parameter values to save inputs
    in_params =  locals()

    try:

        #########################
        # some hardcoded variables
        pbcoeff = 1.13 ##  PB defined as pbcoeff*lambda/d
        nyquist = 0.5/pbcoeff ## Nyquist spacing = PB*nyquist

        gridratio_int = 1./pl.sqrt(3) # times lambda/d
        gridratio_tp  = 1./3

        relmargin = .5  # number of PB between edge of model and ptg centers
        scanlength = 1  # number of integrations per scan


        # RI TODO for inbright=unchanged, need to scale input image to jy/pix
        # according to actual units in the input image

        casalog.origin('simobserve')
        if verbose: casalog.filter(level="DEBUG2")

        if not is_CASA6:
            myf = stack_frame_find( )

        # create the utility object
        # this is the dir of the observation (could be "")
        util = simutil(direction)  
        if verbose: util.verbose = True
        msg = util.msg

        # it was requested to make the user interface "observe" for what 
        # is sm.observe and sm.predict.
        # interally the code is clearer if we stick with predict so
        predict = obsmode.startswith('i') or obsmode.startswith('s')
        if predict:
            uvmode = obsmode.startswith('i')
            if not uvmode: antennalist = sdantlist
        elif sdantlist != "":
            if antennalist == "":
                uvmode = False
                antennalist = sdantlist
            else:
                #uvmode = True
                #msg("Both antennalist and sdantlist are defined. sdantlist will be ignored",priority="warn")
                emsg = "Both antennalist and sdantlist are defined. Define one of them."
                raise ValueError(emsg)
        else:
            uvmode = True
        #    uvmode = (sdant < 0) #when flexible default values come available


        # put output in directory called "project"
        fileroot = project
        if not os.path.exists(fileroot):
            os.mkdir(fileroot)


        # filename parsing of cfg file here so that the project filenames 
        # can contain the cfg
        if is_CASA6:
            repodir = ctsys.resolve("alma/simmos")
        else:
            repodir = os.getenv("CASAPATH").split(' ')[0] + "/data/alma/simmos"

        # convert "alma;0.4arcsec" to an actual configuration
        # can only be done after reading skymodel, so here, we just string parse
        if str.upper(antennalist[0:4]) == "ALMA":
            foo=antennalist.replace(';','_')
        elif len(antennalist) > 0:
            foo=antennalist
        else:
            msg("The name of antenna list (antennalist/sdantlist) is not specified",priority="error")

        if foo:
            foo=foo.replace(".cfg","")
            sfoo=foo.split('/')
            if len(sfoo)>1:
                foo=sfoo[-1]
            project=project+"."+foo


        if not overwrite:
            if (predict and uvmode and os.path.exists(fileroot+"/"+
                                                      project+".ms")):
                emsg = fileroot+"/"+project+".ms exists but overwrite=F"
                raise RuntimeError(emsg)
            if (predict and (not uvmode) and os.path.exists(fileroot+"/"+
                                                            project+".sd.ms")):
                emsg = fileroot+"/"+project+".sd.ms exists but overwrite=F"
                raise RuntimeError(emsg)


        # saveinputs is not available in casatasks
        if not is_CASA6:
            saveinputs = myf['saveinputs']
            # something broken in saveinputs
            in_params['antennalist']=''+in_params['antennalist']+''
            saveinputs('simobserve',fileroot+"/"+project+".simobserve.last",
                       myparams=in_params)

        if is_array_type(skymodel):
            skymodel = skymodel[0]
        skymodel = skymodel.replace('$project',project)

        if is_array_type(complist):
            complist = complist[0]

        if((not os.path.exists(skymodel)) and (not os.path.exists(complist))):
            if len(skymodel)>0:
                msg("Your skymodel '"+skymodel+"' could not be found.",
                    priority="warn")
            if len(complist)>0:
                msg("Your complist '"+complist+"' could not be found.",
                    priority="warn")
            if len(skymodel)==0 and len(complist)==0:
                msg("At least one of skymodel or complist must be set.",
                    priority="error")
                
            else:
                msg("No sky input found."+
                    "  At least one of skymodel or complist must exist.",
                    priority="error")

        ### WORKAROUND for wrong flux in COMP TP simulations (CAS-5095)
        if (obsmode.startswith("s") and os.path.exists(complist)):
            emsg = "Single dish simulation has a flux recovery issue when using a components list as an input.\nPlease generate compskymodel image first by obsmode='' and use the image as the skymodel input.\nSorry for the inconvenience."
            raise RuntimeError(emsg)
        ### End of WORKAROUND

        grscreen = False
        grfile = False
        if graphics == "both":
            grscreen = True
            grfile = True
        if graphics == "screen":
            grscreen = True
        if graphics == "file":
            grfile = True

        ##################################################################
        # set up skymodel image

        if os.path.exists(skymodel):
            components_only = False
            # create a new skymodel called skymodel, 
            # or if it's already there, called newmodel
            default_model = project + ".skymodel"
            if skymodel == default_model:
                newmodel = fileroot + "/" + project + ".newmodel"
            else:
                newmodel = fileroot + "/" + default_model
            if os.path.exists(newmodel):
                if overwrite:
                    shutil.rmtree(newmodel)
                else:
                    emsg = (newmodel+" exists -- "+
                            "please delete it, change skymodel, or set overwrite=T")
                    raise RuntimeError(emsg)

            # modifymodel just collects info if skymodel==newmodel
            innchan = -1
            returnpars = util.modifymodel(skymodel,newmodel,
                                          inbright,indirection,incell,
                                          incenter,inwidth,innchan,
                                          flatimage=False)
            if not returnpars:
                raise RuntimeError('Failure in modifymodel. It returned: {}'.
                                   format(returnpars))

            (model_refdir,model_cell,model_size,
             model_nchan,model_specrefval,model_specrefpix,model_width,
             model_stokes) = returnpars

            modelflat = fileroot + "/" + project + ".skymodel.flat"
            if os.path.exists(modelflat) and (not predict):
                # if we're not predicting, then we want to use the previously
                # created modelflat, because it may have components added 
                msg("flat sky model "+modelflat+
                    " exists, predict not requested",priority="warn")
                msg(" working from existing model image - "+
                    "please delete it if you wish to overwrite.",
                    priority="warn")
            else:
                # create and add components into modelflat with util.flatimage()
                util.flatimage(newmodel,complist=complist,verbose=verbose)
                # we want skymodel.flat image to be called that no matter what 
                # the skymodel image is called, since it's used in analysis
                if modelflat != newmodel+".flat":
                    if os.path.exists(modelflat):
                        shutil.rmtree(modelflat)
                    shutil.move(newmodel+".flat",modelflat)

            casalog.origin('simobserve')

            # set startfeq and bandwidth in util object after modifymodel
            bandwidth = qa.mul(qa.quantity(model_nchan),
                               qa.quantity(model_width))
            util.bandwidth = bandwidth

        else:
            components_only = True
            msg("component-only simulation",priority="info")
            # calculate model parameters from the component list:

            compdirs = []
            cl.done()
            cl.open(complist)

            for i in range(cl.length()):
                compdirs.append(util.dir_m2s(cl.getrefdir(i)))

            model_refdir, coffs = util.average_direction(compdirs)
            model_specrefval = cl.getspectrum(0)['frequency']['m0']

            if util.isquantity(compwidth,halt=False):
                model_width = compwidth
                msg("compwidth set: setting model bandwidth to input",
                    priority="info")
            else:
                model_width = "2GHz"
                msg("compwidth unset: setting bandwidth to 2GHz",
                    priority="warn")

            model_nchan = comp_nchan
            # channelize component-only MS 
            # currently assuming equal width and center as frequency reference
            model_specrefpix = 0.5*(comp_nchan-1)
            msg("scaling model bandwidth by model_nchan",priority="info")
            model_width = qa.div(model_width,model_nchan)
            model_stokes = "I"

            cmax = 0.0014 # ~5 arcsec
            for i in range(coffs.shape[1]):
                xc = pl.absolute(coffs[0,i])  # offsets in deg
                yc = pl.absolute(coffs[1,i])
                if xc > cmax:
                    cmax = xc
                if yc > cmax:
                    cmax = yc

            model_size = ["%fdeg" % (3*cmax), "%fdeg" % (3*cmax)]


        # for cases either if there is a skymodel or are only components,
        # if user has not input a map size (for setpointings), use model_size
        if len(mapsize) == 0:
            mapsize = model_size
            if verbose: msg("setting map size to "+str(model_size),
                            origin='simobserve')
        else:
             if is_array_type(mapsize):
                 if len(mapsize[0]) == 0:
                     mapsize = model_size
                     if verbose: msg("setting map size to "+str(model_size),
                                     origin="simobserve")

        if components_only:
            if is_array_type(mapsize):
                map_asec = qa.convert(mapsize[0],"arcsec")['value']
            else:
                map_asec = qa.convert(mapsize,"arcsec")['value']
            if is_array_type(model_size):
                mod_asec = qa.convert(model_size[0],"arcsec")['value']
            else:
                mod_asec = qa.convert(model_size,"arcsec")['value']
            if map_asec>mod_asec:
                model_size=["%farcsec" % map_asec,"%farcsec" % map_asec]




        ##################################################################
        # read antenna file here to get Primary Beam, and estimate psfsize
        aveant = -1
        stnx = []  # for later, to know if we read an array in or not
        pb = 0. # primary beam

        # convert "alma;0.4arcsec" to an actual configuration
        if str.upper(antennalist[0:4]) == "ALMA":

            resparsed=False
            # test for cycle 1
            q = re.compile('.*CYCLE.?1.?;(.*)')
            qq = q.match(antennalist.upper())
            if qq:
                z = qq.groups()
                tail=z[0]
                tail=tail.lower()
                if util.isquantity(tail,halt=False):
                    resl = qa.convert(tail,"arcsec")['value']
                    if os.path.exists(repodir):
                        confnum = (1.044 - 6.733 * pl.log10(resl * qa.convert(model_specrefval,"GHz")['value'] / 345.))
                        confnum = max(1,min(6,confnum))
                        conf = str(int(round(confnum)))
                        antennalist = os.path.join(repodir,"alma.cycle1." + conf + ".cfg")
                        msg("converted resolution to antennalist "+antennalist)
                        resparsed=True
                    else:
                        msg("failed to find antenna configuration repository"+
                            " at "+repodir,priority="error")
            if not resparsed:
                q = re.compile('.*CYCLE.?2.?;(.*)')
                qq = q.match(antennalist.upper())
                if qq:
                    z = qq.groups()
                    tail=z[0]
                    tail=tail.lower()
                    if util.isquantity(tail,halt=False):
                        resl = qa.convert(tail,"arcsec")['value']
                        if os.path.exists(repodir):
                            confnum = 10. ** (0.91 - 0.74 * (resl * qa.convert(model_specrefval,"GHz")['value']/345.))
                            confnum = max(1,min(7,confnum))
                            conf = str(int(round(confnum)))
                            antennalist = os.path.join(repodir,"alma.cycle2." +
                                                       conf + ".cfg")
                            msg("converted resolution to antennalist "+
                                antennalist)
                            resparsed=True
                        else:
                            msg("failed to find antenna configuration repository at "+repodir,priority="error")

            if not resparsed: # assume FS
                tail = antennalist[5:]
                if util.isquantity(tail,halt=False):
                    resl = qa.convert(tail,"arcsec")['value']
                    if os.path.exists(repodir):
                        confnum = (2.867-pl.log10(resl*1000*qa.convert(model_specrefval,"GHz")['value']/672.))/0.0721
                        confnum = max(1,min(28,confnum))
                        conf = str(int(round(confnum)))
                        if len(conf) < 2: conf = '0' + conf
                        antennalist = os.path.join(repodir,"alma.out" 
                                                   + conf + ".cfg")
                        msg("converted resolution to antennalist "+antennalist)
                    else:
                        msg("failed to find antenna configuration repository at "+repodir,priority="error")


        # Search order is fileroot/ -> specified path -> repository
        if len(antennalist) > 0:
            if os.path.exists(fileroot+"/"+antennalist):
                antennalist = fileroot + "/" + antennalist
            elif not os.path.exists(antennalist) and \
                     os.path.exists(os.path.join(repodir,antennalist)):
                antennalist = os.path.join(repodir,antennalist)
            # Now make sure the antennalist exists
            if not os.path.exists(antennalist):
                emsg = "Couldn't find antennalist: %s" % antennalist
                raise RuntimeError(emsg)
        elif predict or components_only:
            # antennalist is required when predicting or components only
            util.msg("Must specify antennalist", priority="error")

        # Read antennalist
        if os.path.exists(antennalist):
            stnx, stny, stnz, stnd, padnames, antnames, telescopename, posobs = util.readantenna(antennalist)
            nant=len(stnx)
            if nant == 1:
                if predict and uvmode:
                    # observe="int" but antennalist is SD
                    util.msg("antennalist contains only 1 antenna", 
                             priority="error")
                uvmode = False
            if not uvmode: #Single-dish
                # KS TODO: what if not predicting but SD with multi-Ants
                # in antennalist (e.g., aca.tp)? In that case, PB on plots and
                # pointingspacing="??PB" will not be correct for heterogeneous list.
                if sdant > nant-1 or sdant < -nant:
                    msg("antenna index %d is out of range. setting sdant=0"%sdant,priority="warn")
                    sdant = 0
                stnx = [stnx[sdant]]
                stny = [stny[sdant]]
                stnz = [stnz[sdant]]
                stnd = pl.array(stnd[sdant])
                padnames = [padnames[sdant]]
                antnames = [antnames[sdant]]
                nant = 1

            # (set back to simdata - there must be an automatic way to do this)
            casalog.origin('simobserve')

            aveant = stnd.mean()
            # TODO use max ant = min PB instead?
            pb = pbcoeff*0.29979/qa.convert(qa.quantity(model_specrefval),'GHz')['value']/aveant*3600.*180/pl.pi # arcsec

            # PSF size
            if uvmode:
                # approx beam, to compare with model_cell:
                psfsize = util.approxBeam(antennalist,qa.convert(qa.quantity(model_specrefval),'GHz')['value'])
            else: # Single-dish
                psfsize = pb
                # check for model size
                if not components_only:
                    minsize = min(qa.convert(model_size[0],'arcsec')['value'],
                                  qa.convert(model_size[1],'arcsec')['value'])
                    if minsize < (2.5 * pb):
                        msg("skymodel should be larger than 2.5*primary beam. Your skymodel: %.3f arcsec < %.3f arcsec: 2.5*primary beam" % (minsize, 2.5*pb),priority="error")
                    del minsize
        else:
            emsg = "Can't find antennalist"
            raise RuntimeError(emsg)


        # now we have an estimate of the psf from the antenna configuration, 
        # so we can guess a model_cell for the case of component-only 
        # simulation, 
        if components_only:
            # first set based on psfsize:
            # needs high subsampling because small shifts in placement of 
            # components lead to large changes in the difference image.
            model_cell = [ qa.quantity(str(psfsize/20)+"arcsec"), 
                           qa.quantity(str(psfsize/20)+"arcsec") ]
            
            # XXX if the user has set direction should we center the compskymodel there?
            # if len(direction)>0: model_refdir = direction

        # and can create a compskymodel image (tmp) and 
        # skymodel.flat which is what is needed for analysis.

        if components_only:
            newmodel = fileroot + "/" + project + ".compskymodel"
            needmodel=True

            modimsize=int((qa.convert(model_size[0],"arcsec")['value'])/(qa.convert(model_cell[0],"arcsec")['value']))
#            modimsize=max([modimsize,32])
            newepoch,newlat,newlon = util.direction_splitter(model_refdir)

            if os.path.exists(newmodel):
                if overwrite:
                    shutil.rmtree(newmodel)
                else:
                    needmodel=False
                    ia.open(newmodel)
                    oldshape=ia.shape()
                    if len(oldshape) != 2:
                        needmodel=True
                    else:
                        if oldshape[0] != modimsize or oldshape[1]==modimsize:
                            needmodel=True
                    oldcs=ia.coordsys()
                    ia.done()
                    olddir = (oldcs.referencevalue())['numeric']
                    if ( olddir[0] != qa.convert(newlat,oldcs.units()[0])['value'] or
                         olddir[1] != qa.convert(newlon,oldcs.units()[1])['value'] or
                         newepoch != oldcs.referencecode() ):
                        needmodel=True
                    oldcs.done()
                    del oldcs, olddir
                    if needmodel:
                        emsg = newmodel+" exists and is inconsistent with required size="+str(modimsize)+" and direction. Please set overwrite=True"
                        raise RuntimeError(emsg)

            if needmodel:
                csmodel = ia.newimagefromshape(newmodel,[modimsize,modimsize,1,1])
                modelcsys = csmodel.coordsys()
                modelshape = csmodel.shape()
                cell0_rad=qa.convert(model_cell[0],'rad')['value']
                cell1_rad=qa.convert(model_cell[1],'rad')['value']
                modelcsys.setdirection(refpix=[modimsize/2,modimsize/2],
                                       refval=[qa.tos(newlat),qa.tos(newlon)],
                                       refcode=newepoch)
                modelcsys.setincrement([-cell0_rad,cell1_rad],'direction')
                modelcsys.setreferencevalue(type="spectral",value=qa.convert(model_specrefval,'Hz')['value'])
                modelcsys.setreferencepixel(type="spectral",value=model_specrefpix)
                modelcsys.setrestfrequency(model_specrefval)
                modelcsys.setincrement(type="spectral",value=compwidth)
                csmodel.setcoordsys(modelcsys.torecord())

                modelcsys.done()
                cl.open(complist)
                csmodel.setbrightnessunit("Jy/pixel")
                csmodel.modify(cl.torecord(),subtract=False)
                cl.done()
                csmodel.done()
                # as noted, compskymodel doesn't need to exist, only skymodel.flat
                # flatimage adds in components if complist!=None
                #util.flatimage(newmodel,complist=complist,verbose=verbose)
                util.flatimage(newmodel,verbose=verbose)
                modelflat = fileroot + "/" + project + ".compskymodel.flat"
#                modelflat = fileroot + "/" + project + ".skymodel.flat"
#                if modelflat != newmodel+".flat":
#                    if os.path.exists(modelflat):
#                        shutil.rmtree(modelflat)
#                    shutil.move(newmodel+".flat",modelflat)


        # and finally, with model_cell set either from an actual skymodel,
        # or from the antenna configuration in components_only case,
        # we can check for the user that the psf is likely to be sampled enough:
        cell_asec=qa.convert(model_cell[0],'arcsec')['value']
        if psfsize < cell_asec:
            emsg = "Sky model cell of "+str(cell_asec)+" asec is very large compared to highest resolution "+str(psfsize)+" asec - this will lead to blank or erroneous output. (Did you set incell?)"
            shutil.rmtree(modelflat)
            raise RuntimeError(emsg)
        if psfsize < 2*cell_asec:
            msg("Sky model cell of "+str(cell_asec)+" asec is large compared to highest resolution "+str(psfsize)+" asec. (Did you set incell?)",priority="warn")

        # set this for future minimum image size
        minimsize = 8* int(psfsize/cell_asec)



        ##################################################################
        # set up pointings
        dir = model_refdir
        dir0 = dir
        if is_array_type(direction):
            if len(direction) > 0:
                if util.isdirection(direction[0],halt=False):
                    dir = direction
                    dir0 = direction[0]
        else:
            if util.isdirection(direction,halt=False):
                dir = direction
                dir0 = dir
        util.direction = dir0

        # if the integration time is a real time quantity
        # test for weird units
        if not util.isquantity(integration):
            emsg = "integration time "+integration+" does not appear to represent a time interval (use 's','min'; not 'sec','m')"
            raise RuntimeError(emsg)

        if qa.quantity(integration)['unit'] != '':
            if not qa.compare(integration,"1s"):
                emsg = "integration time "+integration+" does not appear to represent a time interval ('s','min'; not 'sec','m')"
                raise RuntimeError(emsg)
            intsec = qa.convert(qa.quantity(integration),"s")['value']
        else:
            if len(integration)>0:
                intsec = float(integration)
                msg("interpreting integration time parameter as "+
                    str(intsec)+"s",priority="warn")
            else:
                intsec = 0
        integration="%fs" %intsec


        if setpointings:
            util.msg("calculating map pointings centered at "+
                     str(dir0),origin='simobserve')

            if len(pointingspacing) < 1:
                if uvmode:
                    # ALMA OT uses lambda/d/sqrt(3)
                    pointingspacing = "%fPB" % (gridratio_int/pbcoeff) 
                else:
                    pointingspacing = "%fPB" % (gridratio_tp/pbcoeff) 
            if str.upper(pointingspacing)=="NYQUIST":
                pointingspacing="%fPB" % nyquist
            q = re.compile('(\d+.?\d+)\s*PB')
            qq = q.match(pointingspacing.upper())
            if qq:
                z = qq.groups()
                if pb <= 0:
                    emsg = "Can't calculate pointingspacing in terms of primary beam because antennalist is not specified"
                    raise RuntimeError(emsg)
                pointingspacing = "%farcsec" % (float(z[0])*pb)
                # todo make more robust to nonconforming z[0] strings

            if verbose:
                msg("pointing spacing in mosaic = "+
                    pointingspacing,origin='simobserve')
            pointings = util.calc_pointings2(pointingspacing,
                                             mapsize,maptype=maptype, 
                                             direction=dir, beam=pb)
            nfld=len(pointings)
            etime = qa.convert(qa.mul(qa.quantity(integration),scanlength),"s")['value']
            # etime is an array of scan lengths - here they're all the same.
            etime = etime + pl.zeros(nfld)
            # totaltime might not allow all fields to be observed, or it might
            # repeat
            ptgfile = fileroot + "/" + project + ".ptg.txt"
        else:
            if is_array_type(ptgfile):
                ptgfile = ptgfile[0]
            ptgfile = ptgfile.replace('$project',project)
            # precedence to ptg file outside the project dir
            if os.path.exists(ptgfile):
                shutil.copyfile(ptgfile,fileroot+"/"+project + ".ptg.txt")
                ptgfile = fileroot + "/" + project + ".ptg.txt"
            else:
                if os.path.exists(fileroot+"/"+ptgfile):
                    ptgfile = fileroot + "/" + ptgfile
                else:
                    emsg = "Can't find pointing file "+ptgfile
                    raise RuntimeError(emsg)

            nfld, pointings, etime = util.read_pointings(ptgfile)
            if max(etime) <= 0:
                # integration is a string in input params
                etime = intsec
                # make etime into an array
                etime = etime + pl.zeros(nfld)
            # etimes determine stop/start i.e. the scan
            # if a longer etime is in the file, it'll do multiple integrations
            # per scan
            # expects that the cal is separate, and this is just one round of the mosaic
            # furthermore, the cal will use _integration_ from the inputs, and that
            # needs to be less than the min etime:
            if min(etime) < intsec:
                integration = str(min(etime))+"s"
                msg("Setting integration to "+integration+
                    " to match the shortest time in the pointing file.",
                    priority="warn")
                intsec = min(etime)


        # find imcenter - phase center
        imcenter , offsets = util.median_direction(pointings)     
        epoch, ra, dec = util.direction_splitter(imcenter)

        # model is centered at model_refdir, and has model_size;
        mepoch, mra, mdec = util.direction_splitter(model_refdir)
        # ra/mra should be in degrees, but just in case
        ra=qa.convert(ra,'deg')
        mra=qa.convert(mra,'deg')
        dec=qa.convert(dec,'deg')
        mdec=qa.convert(mdec,'deg')

        # observation near ra=0:
        if abs(mra['value'])<10 or abs(mra['value']-360.)<10 or abs(ra['value'])<10 or abs(mra['value']-360.)<10:
            nearzero=True
        else:
            nearzero=False

        # fix wraps
        if nearzero: # put break at 180
           if ra['value'] >= 180.:
               ra['value'] = ra['value'] - 360.
           if mra['value'] >= 180.:
               mra['value'] = mra['value'] - 360.
        else:
           if ra['value'] >= 360.:
               ra['value'] = ra['value'] - 360.
           if mra['value'] >= 360.:
               mra['value'] = mra['value'] - 360.

        # shift is the offset from the model to imcenter
        shift = [ (qa.convert(ra,'deg')['value'] -
                   qa.convert(mra,'deg')['value'])*pl.cos(qa.convert(dec,'rad')['value'] ),
                  (qa.convert(dec,'deg')['value'] - qa.convert(mdec,'deg')['value']) ]
        if verbose: 
            msg("pointings are shifted relative to the model by %g,%g arcsec" % (shift[0]*3600,shift[1]*3600),origin='simobserve')

        xmax = qa.convert(model_size[0],'deg')['value']*0.5
        ymax = qa.convert(model_size[1],'deg')['value']*0.5
        # add PB halfwidth (relmargin=0.5) 
        # for mosaics of small model images
        xmax=xmax+pb*relmargin/3600
        ymax=ymax+pb*relmargin/3600
        overlap = False
        # wrapang in median_direction should make offsets always small, not >360
        for i in range(offsets.shape[1]):
            xc = pl.absolute(offsets[0,i]+shift[0])  # offsets and shift are in degrees
            yc = pl.absolute(offsets[1,i]+shift[1])
            if xc < xmax and yc < ymax:
                overlap = True
                break

        if setpointings:
            if os.path.exists(ptgfile):
                if overwrite:
                    os.remove(ptgfile)
                else:
                    emsg = "pointing file "+ptgfile+" already exists and user does not want to overwrite"
                    raise RuntimeError(emsg)
            util.write_pointings(ptgfile,pointings,etime.tolist())

        msg("center = "+imcenter,origin='simobserve')
        if nfld > 1 and verbose:
            for idir in range(min(len(pointings),20)):
                msg("   "+pointings[idir],origin='simobserve')
            if nfld >= 20:
                msg("   (printing only first 20 - see pointing file for full list)")

        if not overlap:
            emsg = "No overlap between model and pointings"
            raise RuntimeError(emsg)



        ##################################################################
        # calibrator is not explicitly contained in the pointing file
        # but interleaved with etime=intergration
        util.isquantity(calflux)
        calfluxjy = qa.convert(calflux,'Jy')['value']
        # XML returns a list even for a string:
        if is_array_type(caldirection): caldirection = caldirection[0]
        if len(caldirection) < 4: caldirection = ""
        if calfluxjy > 0 and caldirection != "" and uvmode:
            docalibrator = True
            if os.path.exists(fileroot+"/"+project+'.cal.cclist'):
                shutil.rmtree(fileroot+"/"+project+'.cal.cclist')
            util.isdirection(caldirection)
            cl.done()
            cl.addcomponent(flux=calfluxjy,dir=caldirection,
                            label="phase calibrator")
            # set reference freq to center freq of model
            cl.rename(fileroot+"/"+project+'.cal.cclist')
            cl.done()
        else:
            docalibrator = False





        ##################################################################
        # create one figure for model and pointings - need antenna diam 
        # to determine primary beam
        if grfile:
            file = fileroot + "/" + project + ".skymodel.png"
        else:
            file = ""
    
        if grscreen or grfile:
            util.newfig(show=grscreen)

# remove after we fix the scaling algorithm for the images
            if components_only:
                pl.plot()
                # TODO add symbols at locations of components
                pl.plot(coffs[0,]*3600,coffs[1,]*3600,'o',c="#dddd66")
                pl.axis("equal")
            else:
                discard = util.statim(modelflat,plot=True,incell=model_cell)
            
            lims = pl.xlim(),pl.ylim()
            if pb <= 0 and verbose:
                msg("unknown primary beam size for plot",priority="warn")
            if max(max(lims)) > pb and not components_only:
                plotcolor = 'w'
            else:
                plotcolor = 'k'

            #if offsets.shape[1] > 16 or pb <= 0 or pb > pl.absolute(max(max(lims))):
            if offsets.shape[1] > 19 or pb <= 0:
                lims = pl.xlim(),pl.ylim()
                pl.plot((offsets[0]+shift[0])*3600.,
                        (offsets[1]+shift[1])*3600.,
                        plotcolor+'+',markeredgewidth=1)
                #if pb > 0 and pl.absolute(lims[0][0]) > pb:
                if pb > 0:
                    plotpb(pb,pl.gca(),lims=lims,color=plotcolor)
            else:
                from matplotlib.patches import Circle
                for i in range(offsets.shape[1]):
                    pl.gca().add_artist(Circle(
                        ((offsets[0,i]+shift[0])*3600,
                         (offsets[1,i]+shift[1])*3600),
                        radius=pb/2.,edgecolor=plotcolor,fill=False,
                        label='beam',transform=pl.gca().transData,clip_on=True))

            xlim = max(abs(pl.array(lims[0])))
            ylim = max(abs(pl.array(lims[1])))
            # show entire pb: (statim doesn't by default)
            pl.xlim([max([xlim,pb/2]),min([-xlim,-pb/2])])
            pl.ylim([min([-ylim,-pb/2]),max([ylim,pb/2])])         
            pl.xlabel("resized model sky",fontsize="x-small")
            util.endfig(show=grscreen,filename=file)








        ##################################################################
        # set up observatory, feeds, etc
        quickpsf_current = False

        if uvmode:
            msfile = fileroot + "/" + project + '.ms'
        else:
            msfile = fileroot + "/" + project + '.sd.ms'

        if predict:
            # TODO check for frequency overlap here - if zero stop
            # position overlap already checked above in pointing section

            message = "preparing empty measurement set"
            if verbose:
                msg(" ",priority="info")
                msg(message,origin="simobserve",priority="warn")
            else:
                msg(message,origin="simobserve")

            nbands = 1;
            fband  = util.bandname(qa.convert(model_specrefval, 'GHz')['value'])

            ############################################
            # predict observation

            # if someone has the old style refdate with the included, discard
            q = re.compile('(\d*/\d+/\d+)([/:\d]*)')
            qq = q.match(refdate)
            if not qq:
                emsg = "Invalid reference date "+refdate
                raise RuntimeError(emsg)
            else:
                z = qq.groups()
                refdate=z[0]
                if len(z)>1:
                    if len(z[1])>1:
                        msg("Discarding time part of refdate, '"+z[1]+
                            "', in favor of hourangle parameter = "+hourangle,
                            origin='simobserve')

            if hourangle=="transit":
                haoffset=0.0
            else:                
                haoffset="no"
                # is this a time quantity?
                if qa.isquantity(str(hourangle)+"h"): 
                    if qa.compare(str(hourangle)+"h","s"):
                        haoffset=qa.convert(qa.quantity(str(hourangle)+
                                                        "h"),'s')['value']
                if qa.isquantity(hourangle):
                    qha=qa.convert(hourangle,"s")
                    if qa.compare(qha,"s"):
                        haoffset=qa.convert(qha,'s')['value']
            if haoffset=="no":
                msg("Cannot interpret your hourangle parameter "+hourangle+
                    " as a time quantity e.g. '5h', 30min'",
                    origin="simobserve",priority="error")
            else:
                msg("You desire an hour angle of "+
                    str(haoffset/3600.)+" hours",origin="simobserve")

            refdate=refdate+"/00:00:00"
            usehourangle=True


            intsec = qa.convert(qa.quantity(integration),"s")['value']

            # totaltime as an integer for # times through the mosaic:
            if not util.isquantity(totaltime,halt=False):
                emsg = "totaltime "+totaltime+" does not appear to represent a time interval (use 's','min','h'; not 'sec','m','hr')"
                raise RuntimeError(emsg)

            if qa.quantity(totaltime)['value'] < 0.:
                # casapy crashes for negative totaltime
                emsg = "Negative totaltime is not allowed."
                raise ValueError(emsg)
            if qa.quantity(totaltime)['unit'] == '':
                # assume it means number of maps, or # repetitions.
                totalsec = sum(etime)
                if docalibrator:
                    totalsec = totalsec + intsec # cal gets one int-time
                totalsec = float(totaltime) * totalsec
                msg("Total observing time = "+str(totalsec)+"s.")
            else:
                if not qa.compare(totaltime,"1s"):
                    emsg = "totaltime "+totaltime+" does not appear to represent a time interval (use 's','min','h'; not 'sec','m','hr')"
                    raise ValueError(emsg)
                totalsec = qa.convert(qa.quantity(totaltime),'s')['value']

            if os.path.exists(msfile) and not overwrite: #redundant check?
                emsg = ("measurement set "+msfile+
                       " already exists and user does not wish to overwrite")
                raise RuntimeError(emsg)
            sm.open(msfile)

            diam = stnd;
            # WARNING: sm.setspwindow is not consistent with clean::center
            # but the "start" is the center of the first channel:
            model_start = qa.sub(model_specrefval,
                                 qa.mul(model_width,model_specrefpix))

            mounttype = 'alt-az'
            if telescopename in ['DRAO', 'WSRT']:
                mounttype = 'EQUATORIAL'
            # Should ASKAP be BIZARRE or something else?  It may be effectively equatorial.

            sm.setconfig(telescopename=telescopename, x=stnx, y=stny, z=stnz,
                         dishdiameter=diam.tolist(),
                         mount=[mounttype], antname=antnames, padname=padnames, 
                         coordsystem='global', referencelocation=posobs)
            if str.upper(telescopename).find('VLA') >= 0:
                sm.setspwindow(spwname=fband, freq=qa.tos(model_start),
                               deltafreq=qa.tos(model_width),
                               freqresolution=qa.tos(model_width),
                               nchannels=model_nchan, refcode=outframe,
                               stokes='RR LL')
                sm.setfeed(mode='perfect R L',pol=[''])
            else:
                sm.setspwindow(spwname=fband, freq=qa.tos(model_start),
                               deltafreq=qa.tos(model_width),
                               freqresolution=qa.tos(model_width), 
                               nchannels=model_nchan, refcode=outframe,
                               stokes='XX YY')
                sm.setfeed(mode='perfect X Y',pol=[''])

            if verbose: 
                msg(" spectral window set at %s" % qa.tos(model_specrefval),
                    origin='simobserve')
                sm.setlimits(shadowlimit=0.01, elevationlimit='10deg')
            if uvmode:
                sm.setauto(0.0)
            else: #Single-dish
                # auto-correlation should be unity for single dish obs.
                sm.setauto(1.0)

            mereftime = me.epoch('UTC', refdate)
            # integration is a scalar quantity, etime is a vector of seconds
            sm.settimes(integrationtime=integration, usehourangle=usehourangle, 
                        referencetime=mereftime)

            for k in range(0,nfld):
                src = project + '_%d' % k
                sm.setfield(sourcename=src, sourcedirection=pointings[k],
                            calcode="OBJ", distance='0m')
                if k == 0:
                    sourcefieldlist = src
                else:
                    sourcefieldlist = sourcefieldlist + ',' + src
            if docalibrator:
                sm.setfield(sourcename="phase calibrator", 
                            sourcedirection=caldirection,calcode='C',
                            distance='0m')

            # time required to observe all planned scanes in etime array:
            totalscansec = sum(etime)
            kfld = 0

            if totalsec < totalscansec:
                msg("Not all pointings in the mosaic will be observed - check mosaic setup and exposure time parameters!",priority="warn")
                ###
                #casalog.post("you need at least %16.12e sec but you have %16.12e sec (%f < %f = %s)" % (totalscansec, totalsec, totalsec, totalscansec, str(totalsec<totalscansec)))
                ###

            # sm.observemany
            srces = []
            starttimes = []
            stoptimes = []
            dirs = []

            if usehourangle:
                sttime = -totalsec/2.0
            else:
                sttime = 0. # leave start at the reftime
            sttime=sttime+haoffset
            scanstart=sttime

            # can before sources
            if docalibrator:
                endtime = sttime + qa.convert(integration,'s')['value']
                sm.observe(sourcename="phase calibrator", spwname=fband,
                           starttime=qa.quantity(sttime, "s"),
                           stoptime=qa.quantity(endtime, "s"),
                           state_obs_mode="CALIBRATE_PHASE.ON_SOURCE",
                           state_sig=True,project=project);
                sttime = endtime

            while (sttime-scanstart) < totalsec: # the last scan could exceed totaltime
                endtime = sttime + etime[kfld]
                src = project + '_%d' % kfld
                srces.append(src)
                starttimes.append(str(sttime)+"s")
                stoptimes.append(str(endtime)+"s")
                dirs.append(pointings[kfld])
                kfld = kfld + 1
                # advance start time - XX someday slew goes here
                sttime = endtime

                if kfld == nfld:
                    if docalibrator:
                        endtime = sttime + qa.convert(integration,'s')['value'] 

                        # need to observe cal singly to get new row in obs table
                        # so first observemany the on-source pointing(s)
                        sm.observemany(sourcenames=srces,
                                       spwname=fband,
                                       starttimes=starttimes,
                                       stoptimes=stoptimes,
                                       project=project)
                        # and clear the list
                        srces = []
                        starttimes = []
                        stoptimes = []
                        dirs = []
                        sm.observe(sourcename="phase calibrator", spwname=fband,
                                   starttime=qa.quantity(sttime, "s"),
                                   stoptime=qa.quantity(endtime, "s"),
                                   state_obs_mode="CALIBRATE_PHASE.ON_SOURCE",
                                   state_sig=True,project=project);
                    kfld = kfld + 1
                    sttime = endtime
#                 if kfld > nfld: kfld = 0
                if kfld > nfld-1: kfld = 0
            # if directions is unset, NewMSSimulator::observemany

            # looks up the direction in the field table.
            if not docalibrator:
                sm.observemany(sourcenames=srces,
                               spwname=fband,
                               starttimes=starttimes,
                               stoptimes=stoptimes,
                               project=project)

            sm.setdata(fieldid=list(range(0,nfld)))
            if uvmode or components_only: #Interferometer only
                sm.setvp(dovp=True,usedefaultvp=False)
                # only use mosaic gridding for Het arrays for now - 
                # the standard gridding with a VPSkyJones is less susceptible
                # to issues if the image is too small which can happen a lot
                # in Simulation.
                if len(pl.unique(diam))>1:
                    if nfld>1:
                        sm.setoptions(ftmachine="mosaic")
                    else:
                        msg("Heterogeneous array only supported for mosaics (nfld>1), and make sure that your image is larger than the primary beam or results may be unstable",priority="error")
                else:
                    # checks have to be manual since there's no way to 
                    # get the "diam" out of PBMath AFAIK
                    if telescopename=="ALMA":
                        if (diam[0]<10)|(diam[0]>13):
                            msg("Diameter = %f is inconsistent with telescope=ALMA in the configuration file. *12m ALMA PB will be used*. Use observatory='ACA' in the configuration file to use a 7m diameter."%diam[0],priority="warn")
                            n=len(diam)
                            diam=12.+pl.zeros(n)
                    elif telescopename=="ACA":
                        if (diam[0]<6)|(diam[0]>7.5):
                            msg("Diameter = %f is inconsistent with telescope=ALMA in the configuration file. *7m ACA PB will be used*"%diam[0],priority="warn")
                            n=len(diam)
                            diam=7.+pl.zeros(n)
                    else:
                        msg("Note: diameters in configuration file will not be used - PB for "+telescopename+" will be used",priority="info")


            msg("done setting up observations (blank visibilities)",
                origin='simobserve')
            if verbose: sm.summary()

            # do actual calculation of visibilities:

            if not uvmode: #Single-dish
                sm.setoptions(gridfunction='pb',ftmachine="sd",location=posobs)

            if not components_only:
                if docalibrator:
                    if len(complist) <=0:
                        complist=fileroot+"/"+project+'.cal.cclist'
                    else:
                        # XXX will 2 cl work?
                        complist=complist+","+fileroot+"/"+project+'.cal.cclist'

                if len(complist) > 1:
                    message = "predicting from "+newmodel+" and "+complist
                    if verbose:
                        msg(" ",priority="info")
                        msg(message,priority="warn",origin="simobserve")
                    else:
                        msg(message,origin="simobserve")
                else:
                    message = "predicting from "+newmodel
                    if verbose:
                        msg(" ",priority="info")
                        msg(message,priority="warn",origin="simobserve")
                    else:
                        msg(message,origin="simobserve")
                sm.predict(imagename=newmodel,complist=complist)
            else:   # if we're doing only components
                # XXX will 2 cl work?
                if docalibrator:
                    complist=complist+","+fileroot+"/"+project+'.cal.cclist'
                if verbose:
                    msg("predicting from "+complist,priority="warn",
                        origin="simobserve")
                else:
                    msg("predicting from "+complist,origin="simobserve")
                sm.predict(complist=complist)

            sm.done()
            msg('generation of measurement set '+msfile+' complete',
                origin="simobserve")

            # rest freqs are hardcoded to the first freq in the spw in core
            tb.open(msfile+"/SPECTRAL_WINDOW/",nomodify=False)
            restfreq=tb.getcol("REF_FREQUENCY")
            for i in range(len(restfreq)):
                restfreq[i]=qa.convert(qa.quantity(model_specrefval),'Hz')['value']
            tb.putcol("REF_FREQUENCY",restfreq)
            tb.done()
            tb.open(msfile+"/SOURCE/",nomodify=False)
            restfreq=tb.getcol("REST_FREQUENCY")
            for i in range(len(restfreq)):
                restfreq[i]=qa.convert(qa.quantity(model_specrefval),'Hz')['value']
            tb.putcol("REST_FREQUENCY",restfreq)
            tb.done()
            




            ############################################
            # create figure 
            if grfile:
                file = fileroot + "/" + project + ".observe.png"
            else:
                file = ""

            # update psfsize using uv coverage instead of maxbase above
            if os.path.exists(msfile):
                # psfsize was set from the antenna posns before, but uv is better
                tb.open(msfile)
                rawdata = tb.getcol("UVW")
                tb.done()
                # TODO make this use the 90% baseline as in aU.getBaselineStats
                maxbase = max([max(rawdata[0,]),max(rawdata[1,])])  # in m
                if maxbase > 0.:
                    psfsize = 0.3/qa.convert(qa.quantity(model_specrefval),'GHz')['value']/maxbase*3600.*180/pl.pi # lambda/b converted to arcsec
                    if not uvmode:
                        msg("An single observation is requested but CROSS-correlation in "+msfile,priority="error")
                else: #Single-dish (zero-spacing)
                    psfsize = pb
                    if uvmode:
                        msg("An interferometer observation is requested but only AUTO-correlation in "+msfile,priority="error")
                minimsize = 8* int(psfsize/cell_asec)
            else:
                msg("Couldn't find "+msfile,priority="error")

            if uvmode:
                multi = [2,2,1]
            else:
                multi = 0

            if (grscreen or grfile):
                util.newfig(multi=multi,show=grscreen)
                util.ephemeris(refdate,
                               direction=util.direction,
                               telescope=telescopename,
                               ms=msfile,
                               usehourangle=usehourangle,
                               cofa=posobs)
                casalog.origin('simobserve')
                if uvmode:
                    util.nextfig()
                    util.plotants(stnx, stny, stnz, stnd, padnames)

                    # uv coverage
                    util.nextfig()
                    klam_m = 300/qa.convert(model_specrefval,'GHz')['value']
                    pl.box()
                    pl.plot(rawdata[0,]/klam_m,rawdata[1,]/klam_m,'b,')
                    pl.plot(-rawdata[0,]/klam_m,-rawdata[1,]/klam_m,'b,')
                    ax = pl.gca()
                    ax.yaxis.LABELPAD = -4
                    pl.xlabel('u[klambda]',fontsize='x-small')
                    pl.ylabel('v[klambda]',fontsize='x-small')
                    pl.axis('equal')

                    # show dirty beam from observed uv coverage
                    util.nextfig()
                    imt = imager()
                    imt.open(msfile)
                    # TODO spectral parms
                    msg("using default model cell "+str(model_cell[0])+
                        " for PSF calculation",origin='simobserve')
                    imt.defineimage(cellx=str(model_cell[0]["value"])+
                                    str(model_cell[0]["unit"]),
                                    nx=int(max([minimsize,128])))
                    # TODO trigger im.setoptions(ftmachine="mosaic")
                    if os.path.exists(fileroot+"/"+project+".quick.psf"):
                        shutil.rmtree(fileroot+"/"+project+".quick.psf")

                    # if obs is unknown, casalog will send a warning to screen
                    # temporarily(?) suppress that
                    if not telescopename in me.obslist():
                        casalog.filter("ERROR")                        
                    imt.approximatepsf(psf=fileroot+"/"+project+".quick.psf")
                    if not telescopename in me.obslist():
                        casalog.filter() # set back to default level.

                    quickpsf_current = True
                    beam = imt.fitpsf(psf=fileroot+"/"+project+".quick.psf")
                    imt.done()
                    ia.open(fileroot+"/"+project+".quick.psf")
                    beamcs = ia.coordsys()
                    beam_array = ia.getchunk(axes=[beamcs.findcoordinate("spectral")['pixel'][0],beamcs.findcoordinate("stokes")['pixel'][0]],dropdeg=True)
                    nn = beam_array.shape
                    xextent = nn[0]*cell_asec*0.5
                    xextent = [xextent,-xextent]
                    yextent = nn[1]*cell_asec*0.5
                    yextent = [-yextent,yextent]
                    flipped_array = beam_array.transpose()
                    ttrans_array = flipped_array.tolist()
                    ttrans_array.reverse()
                    pl.imshow(ttrans_array,
                              interpolation='bilinear',
                              cmap=pl.cm.jet,
                              extent=xextent+yextent,
                              origin="lower")
                    pl.title(project+".quick.psf",fontsize="x-small")
                    b = qa.convert(beam[1],'arcsec')['value']
                    pl.xlim([-3*b,3*b])
                    pl.ylim([-3*b,3*b])
                    ax = pl.gca()
                    pl.text(0.05,0.95,"bmaj=%7.1e\nbmin=%7.1e" % (beam[1]['value'],beam[2]['value']),transform = ax.transAxes,bbox=dict(facecolor='white', alpha=0.7),size="x-small",verticalalignment="top")
                    ia.done()
                util.endfig(show=grscreen,filename=file)

            elif thermalnoise != "" or leakage > 0:
                # Not predicting this time but corrupting. Get obsmode from ms.
                if not os.path.exists(msfile):
                    msg("Couldn't find "+msfile,priority="error")
                uvmode = (not util.ismstp(msfile,halt=False))


        ######################################################################
        # noisify

        noise_any = False
        msroot = fileroot + "/" + project  # if leakage, can just copy from this project

        if thermalnoise != "":
            knowntelescopes = ["ALMASD", "ALMA", "ACA", "SMA", "EVLA", "VLA"]

            noise_any = True

            noisymsroot = msroot + ".noisy"
            if not uvmode: #Single-dish
                msroot += ".sd"
                noisymsroot += ".sd"
 
            # Cosmic background radiation temperature in K.
            t_cmb = 2.725


            # check for interferometric ms:
            if not os.path.exists(msroot+".ms"):
                msg("Couldn't find "+msroot+".ms",priority="error")

            # MS exists
            message = 'copying '+msroot+'.ms to ' + \
                      noisymsroot+'.ms and adding thermal noise'
            if verbose:
                msg(" ",priority="info")
                msg(message,origin="noise",priority="warn")
            else:
                msg(message,origin="noise")

            if os.path.exists(noisymsroot+".ms"):
                shutil.rmtree(noisymsroot+".ms")
            shutil.copytree(msfile,noisymsroot+".ms")
            if sm.name() != '':
                emsg = "table persistence error on %s" % sm.name()
                raise RuntimeError(emsg)

            # if not predicted this time, get telescopename from ms
            if not predict:
                tb.open(noisymsroot+".ms/OBSERVATION")
                n = tb.getcol("TELESCOPE_NAME")
                telescopename = n[0]
                # todo add check that entire column is the same
                tb.done()
                msg("telescopename read from "+noisymsroot+".ms: "+
                    telescopename)

            if telescopename not in knowntelescopes:
                msg("thermal noise only works properly for ALMA/ACA, (E)VLA, and SMA",origin="simobserve",priority="warn")
            eta_p, eta_s, eta_b, eta_t, eta_q, t_rx = util.noisetemp(telescope=telescopename,freq=model_specrefval)

            # antenna efficiency
            eta_a = eta_p * eta_s * eta_b * eta_t
            if verbose: 
                msg('antenna efficiency    = '+str(eta_a), origin="simobserve")
                msg('spillover efficiency  = '+str(eta_s), origin="simobserve")
                msg('correlator efficiency = '+str(eta_q), origin="simobserve")
            # sensitivity constant
            scoeff = -1  #Force setting the default value, 1./sqrt(2.0)
            if not uvmode: #Single-dish
                scoeff = 1.0
                if verbose: msg('sensitivity constant = '+str(scoeff), 
                                origin="simobserve")

            sm.openfromms(noisymsroot+".ms")    # an existing MS
            sm.setdata(fieldid=[]) # force to get all fields
            sm.setseed(seed)
            if thermalnoise == "tsys-manual":
                if verbose:
                    message = "sm.setnoise(spillefficiency="+str(eta_s)+\
                              ",correfficiency="+str(eta_q)+\
                              ",antefficiency="+str(eta_a)+\
                              ",trx="+str(t_rx)+",tau="+str(tau0)+\
                              ",tatmos="+str(t_sky)+",tground="+str(t_ground)+\
                              ",tcmb="+str(t_cmb)
                    if not uvmode: message += ",senscoeff="+str(scoeff)
                    message += ",mode='tsys-manual')"
                    msg(message);
                    msg("** this may be slow if your MS is finely sampled in time ** ",priority="warn")
                sm.setnoise(spillefficiency=eta_s,correfficiency=eta_q,
                            antefficiency=eta_a,trx=t_rx,
                            tau=tau0,tatmos=t_sky,tground=t_ground,tcmb=t_cmb,
                            mode="tsys-manual",senscoeff=scoeff)
            else:
                if verbose:
                    message = "sm.setnoise(spillefficiency="+str(eta_s)+\
                              ",correfficiency="+str(eta_q)+\
                              ",antefficiency="+str(eta_a)+\
                              ",trx="+str(t_rx)+",tground="+str(t_ground)+\
                              ",tcmb="+str(t_cmb)
                    if not uvmode: message += ",senscoeff="+str(scoeff)
                    message += ",mode='tsys-atm'"+\
                               ",pground='560mbar',altitude='5000m'"+\
                               ",waterheight='2km',relhum=20,pwv="+str(user_pwv)+"mm)"
                    msg(message);
                    msg("** this may be slow if your MS is finely sampled in time ** ",priority="warn")
                sm.setnoise(spillefficiency=eta_s,correfficiency=eta_q,
                            antefficiency=eta_a,trx=t_rx,
                            tground=t_ground,tcmb=t_cmb,pwv=str(user_pwv)+"mm",
                            mode="tsys-atm",table=noisymsroot,senscoeff=scoeff)
                # don't set table, that way it won't save to disk
                #                        mode="calculate",table=noisymsroot)

            sm.corrupt();
            sm.done();


            msroot = noisymsroot
            if verbose: msg("done corrupting with thermal noise",origin="noise")


        if leakage > 0:
            noise_any = True
            # TODO: need to handle SD name when leakage is available
            if msroot == fileroot+"/"+project:
                noisymsroot = fileroot + "/" + project + ".noisy"
            else:
                noisymsroot = fileroot + "/" + project + ".noisier"
            if not uvmode: #Single-dish
                msg("Can't corrupt SD data with polarization leakage",
                    priority="warn")
            if os.path.exists(msfile):
                msg('copying '+msfile+' to ' + 
                    noisymsroot+'.ms and adding polarization leakage',
                    origin="noise",priority="warn")
                if os.path.exists(noisymsroot+".ms"):
                    shutil.rmtree(noisymsroot+".ms")
                shutil.copytree(msfile,noisymsroot+".ms")
                if sm.name() != '':
                    emsg = "table persistence error on %s" % sm.name()
                    raise RuntimeError(emsg)

                sm.openfromms(noisymsroot+".ms")    # an existing MS
                sm.setdata(fieldid=[]) # force to get all fields
                sm.setleakage(amplitude=leakage,table=noisymsroot+".cal")
                sm.corrupt();
                sm.done();



        # cleanup - delete newmodel, newmodel.flat etc
#        if os.path.exists(modelflat):
#            shutil.rmtree(modelflat)
        if os.path.exists(modelflat+".regrid"):
            shutil.rmtree(modelflat+".regrid")
        if os.path.exists(fileroot+"/"+project+".noisy.T.cal"):
            shutil.rmtree(fileroot+"/"+project+".noisy.T.cal")
        if os.path.exists(fileroot+"/"+project+".noisy.sd.T.cal"):
            shutil.rmtree(fileroot+"/"+project+".noisy.sd.T.cal")

    finally:
        finalize_tools()

##### Helper functions to plot primary beam
def plotpb(pb,axes,lims=None,color='k'):
    # This beam is automatically scaled when you zoom in/out but
    # not anchored in plot area. We'll wait for Matplotlib 0.99
    # for that function.
    #major=major
    #minor=minor
    #rangle=rangle
    #bwidth=max(major*pl.cos(rangle),minor*pl.sin(rangle))*1.1
    #bheight=max(major*pl.sin(rangle),minor*pl.cos(rangle))*1.1
    from matplotlib.patches import Rectangle, Circle #,Ellipse
    try:
        from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox
        box = AuxTransformBox(axes.transData)
        box.set_alpha(0.7)
        circ = Circle((pb,pb),radius=pb/2.,color=color,fill=False,\
                      label='primary beam',linewidth=2.0)
        box.add_artist(circ)
        pblegend = AnchoredOffsetbox(loc=3,pad=0.2,borderpad=0.,\
                                     child=box,prop=None,frameon=False)#,frameon=True)
        pblegend.set_alpha(0.7)
        axes.add_artist(pblegend)
    except:
        casalog.post("Using old matplotlib substituting with circle")
        # work around for old matplotlib
        boxsize = pb*1.1
        if not lims: lims = axes.get_xlim(),axes.get_ylim()
        incx = 1
        incy = 1
        if axes.xaxis_inverted(): incx = -1
        if axes.yaxis_inverted(): incy = -1
        #ecx = lims[0][0] + bwidth/2.*incx
        #ecy = lims[1][0] + bheight/2.*incy
        ccx = lims[0][0] + boxsize/2.*incx
        ccy = lims[1][0] + boxsize/2.*incy
    
        #box = Rectangle((lims[0][0],lims[1][0]),incx*bwidth,incy*bheight,
        box = Rectangle((lims[0][0],lims[1][0]),incx*boxsize,incy*boxsize,
                        alpha=0.7,facecolor='w',
                        transform=axes.transData) #Axes
        #beam = Ellipse((ecx,ecy),major,minor,angle=rangle,
        beam = Circle((ccx,ccy), radius=pb/2.,
                      edgecolor='k',fill=False,
                      label='beam',transform=axes.transData)
        #props = {'pad': 3, 'edgecolor': 'k', 'linewidth':2, 'facecolor': 'w', 'alpha': 0.5}
        #pl.matplotlib.patches.bbox_artist(beam,axes.figure.canvas.get_renderer(),props=props)
        axes.add_artist(box)
        axes.add_artist(beam)

def finalize_tools():
    if ia.isopen(): ia.close()
    sm.close()
    #cl.close()   # crashes casa