Example #1
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """
        self._summary = {}
        pvslicesummary = []
        sumslicetype = 'slice'
        sliceargs = []
        dt = utils.Dtime("PVSlice")
        # import here, otherwise sphinx cannot parse
        from impv     import impv
        from imsmooth import imsmooth

        pvslice = self.getkey('slice')       # x_s,y_s,x_e,y_e (start and end of line)
        pvslit  = self.getkey('slit')        # x_c,y_c,len,pa  (center, length and PA of line)

        # BDP's used :

        #   b10 = input BDP
        #   b11 = input BDP (moment)
        #   b12 = input BDP (new style cubestats w/ maxpos)
        #   b2 = output BDP

        b10 = self._bdp_in[0]                 # input SpwCube
        fin = b10.getimagefile(bt.CASA)       # input name

        b11 = self._bdp_in[1]                 # 
        b12 = self._bdp_in[2]

        clip  = self.getkey('clip')           # clipping to data for Moment-of-Inertia
        gamma = self.getkey('gamma')          # gamma factor to data for Moment-of-Inertia

        if b11 != None and len(pvslice) == 0 and len(pvslit) == 0:
            # if a map (e.g. cubesum ) given, and no slice/slit, get a best pvslice from that
            (pvslice,clip) = map_to_slit(self.dir(b11.getimagefile(bt.CASA)),clip=clip,gamma=gamma)
        elif b12 != None and len(pvslice) == 0 and len(pvslit) == 0:
            # PPP doesn't seem to work too well yet
            logging.debug("testing new slice computation from a PPP")
            max     = b12.table.getColumnByName("max")
            maxposx = b12.table.getColumnByName("maxposx")
            maxposy = b12.table.getColumnByName("maxposy")
            if maxposx == None:
              raise Exception,"PPP was not enabled in your CubeStats"
            (pvslice,clip) = tab_to_slit([maxposx,maxposy,max],clip=clip,gamma=gamma)
        sliceargs = deepcopy(pvslice)
        if len(sliceargs)==0:
            logging.warning("no slice for plot yet")
        # ugh, this puts single quotes around the numbers
        formattedslice = str(["%.2f" % a for a in sliceargs])
        taskargs = "slice="+formattedslice
        dt.tag("slice")

        pvname = self.mkext(fin,'pv')        # output image name
        b2 = PVSlice_BDP(pvname)
        self.addoutput(b2)

        width   = self.getkey('width')       # @todo also:  "4arcsec"  (can't work since it's a single keyword)

        if len(pvslice) == 4:
            start = pvslice[:2]   # @todo also allow:   ["14h20m20.5s","-30d45m25.4s"]
            end   = pvslice[2:]
            impv(self.dir(fin), self.dir(pvname),"coords",start=start,end=end,width=width,overwrite=True)
        elif len(pvslit) == 4:
            sumslicetype = 'slit'
            sliceargs = deepcopy(pvslit)
            formattedslice = str(["%.2f" % a for a in sliceargs])
            taskargs = "slit="+formattedslice
            # length="40arcsec" same as {"value": 40, "unit": "arcsec"})
            center = pvslit[:2]   # @todo also:   ["14h20m20.5s","-30d45m25.4s"].
            length = pvslit[2]    # @todo also:   "40arcsec", {"value": 40, "unit": "arcsec"})
            if type(pvslit[3]) is float or type(pvslit[3]) is int:
                pa = "%gdeg" % pvslit[3]
            else:
                pa = pvslit[3]
            impv(self.dir(fin), self.dir(pvname),"length",center=center,length=length,pa=pa,width=width,overwrite=True)
        else:
            raise Exception,"no valid input  slit= or slice= or bad Moment_BDP input"
        sliceargs.append(width)
        taskargs = taskargs + " width=%d" % width
        dt.tag("impv")

        smooth = self.getkey('pvsmooth')
        if len(smooth) > 0:
            if len(smooth) == 1:
                smooth.append(smooth[0])
            major = '%dpix' % smooth[0]
            minor = '%dpix' % smooth[1]
            logging.info("imsmooth PV slice: %s %s" % (major,minor))
            imsmooth(self.dir(pvname), outfile=self.dir(pvname)+'.smooth',kernel='boxcar',major=major,minor=minor)
            dt.tag("imsmooth")
            # utils.rename(self.dir(pvname)+'.smooth',self.dir(pvname))
            # @todo  we will keep the smooth PVslice for inspection, no further flow work

        # get some statistics
        data = casautil.getdata_raw(self.dir(pvname))
        rpix = stats.robust(data.flatten())
        r_mean = rpix.mean()
        r_std  = rpix.std()
        r_max = rpix.max()
        logging.info("PV stats: mean/std/max %f %f %f" % (r_mean, r_std, r_max))
        logging.regression("PVSLICE: %f %f %f" % (r_mean, r_std, r_max))

        myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())

        # hack to get a slice on a mom0map 
        # @todo   if pmode is not png, can viewer handle this?
        figname   = pvname + ".png"
        slicename = self.dir(figname)
        overlay   = "pvoverlay"
        if b11 != None:
            f11 = b11.getimagefile(bt.CASA)
            taskinit.tb.open(self.dir(f11))
            data = taskinit.tb.getcol('map')
            nx = data.shape[0]
            ny = data.shape[1]
            taskinit.tb.close()
            d1 = np.flipud(np.rot90 (data.reshape((nx,ny))))
            if len(pvslice) == 4:
              segm = [[pvslice[0],pvslice[2],pvslice[1],pvslice[3]]]
              pa = np.arctan2(pvslice[2]-pvslice[0],pvslice[1]-pvslice[3])*180.0/np.pi
              title = "PV Slice location : slice PA=%.1f" % pa
            elif len(pvslit) == 4:
              # can only do this now if using pixel coordinates
              xcen = pvslit[0]
              ycen = ny-pvslit[1]-1
              slen = pvslit[2]
              pard = pvslit[3]*np.pi/180.0
              cosp = np.cos(pard)
              sinp = np.sin(pard)
              halflen = 0.5*slen
              segm = [[xcen-halflen*sinp,xcen+halflen*sinp,ycen-halflen*cosp,ycen+halflen*cosp]]
              pa   = pvslit[3]
              title = "PV Slice location : slit PA=%g" % pa
            else:
              # bogus, some error in pvslice
              logging.warning("bogus segm since pvslice=%s" % str(pvslice))
              segm = [[10,20,10,20]]
              pa   = -999.999
              title = "PV Slice location - bad PA"
            logging.info("MAP1 segm %s %s" % (str(segm),str(pvslice)))
            if d1.max() < clip:
              logging.warning("datamax=%g,  clip=%g" % (d1.max(), clip))
              title = title + ' (no signal over %g?)' % clip
              myplot.map1(d1,title,overlay,segments=segm,thumbnail=True)
            else:
              myplot.map1(d1,title,overlay,segments=segm,range=[clip],thumbnail=True)
            dt.tag("plot")
            overlayname = myplot.getFigure(figno=myplot.figno,relative=True)
            overlaythumbname = myplot.getThumbnail(figno=myplot.figno,relative=True)
            Qover = True
        else:
            Qover = False

        implot = ImPlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())
        implot.plotter(rasterfile=pvname, figname=pvname, colorwedge=True)
        thumbname = implot.getThumbnail(figno=implot.figno,relative=True)
        figname   = implot.getFigure(figno=implot.figno,relative=True)
        if False:
            # debug:
            #
            # @todo    tmp1 is ok, tmp2 is not displaying the whole thing
            # old style:   viewer() seems to plot full image, but imview() wants square pixels?
            casa.viewer(infile=self.dir(pvname), outfile=self.dir('tmp1.pv.png'), gui=False, outformat="png")
            casa.imview(raster={'file':self.dir(pvname),  'colorwedge' : True, 'scaling':-1},
                    axes={'y':'Declination'},
                    out=self.dir('tmp2.pv.png'))
            #
            # -> this one works, axes= should be correct
            # imview(raster={'file':'x.pv',  'colorwedge' : True, 'scaling':-1},axes={'y':'Frequency'})
            #
            # @TODO big fixme, we're going to reuse 'tmp1.pv.png' because implot give a broken view
            figname = 'tmp1.pv.png'
                    
        # @todo   technically we don't know what map it was overlay'd on.... CubeSum/Moment0
        overlaycaption = "Location of position-velocity slice overlaid on a CubeSum map"
        pvcaption = "Position-velocity diagram through emission centroid"
        pvimage = Image(images={bt.CASA : pvname, bt.PNG : figname},thumbnail=thumbname,thumbnailtype=bt.PNG, description=pvcaption)
        b2.setkey("image",pvimage)
        b2.setkey("mean",float(r_mean))
        b2.setkey("sigma",float(r_std))
        if Qover:
          thispvsummary = [sumslicetype,sliceargs,figname,thumbname,pvcaption,overlayname,overlaythumbname,overlaycaption,pvname,fin]
        else:
          thispvsummary = [sumslicetype,sliceargs,figname,thumbname,pvcaption,pvname,fin]
        
        # Yes, this is a nested list.  Against the day when PVSLICE can
        # compute multiple slices per map.
        pvslicesummary.append(thispvsummary)
        self._summary["pvslices"] = SummaryEntry(pvslicesummary,"PVSlice_AT",self.id(True),taskargs)

        dt.tag("done")
        dt.end()
Example #2
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """

        self._summary = {}
        dt = utils.Dtime("CubeStats")

        #maxvrms = 2.0      # maximum variation in rms allowed (hardcoded for now)
        #maxvrms = -1.0     # turn maximum variation in rms allowed off
        maxvrms = self.getkey("maxvrms")

        psample = -1
        psample = self.getkey("psample")        

        # BDP's used :
        #   b1 = input BDP
        #   b2 = output BDP

        b1 = self._bdp_in[0]
        fin = b1.getimagefile(bt.CASA)

        bdp_name = self.mkext(fin,'cst')
        b2 = CubeStats_BDP(bdp_name)
        self.addoutput(b2)

        # PeakPointPlot 
        use_ppp = self.getkey("ppp")

        # peakstats: not enabled for mortal users yet
        # peakstats = (psample=1, numsigma=4, minchan=3, maxgap=2, peakfit=False)
        pnumsigma = 4
        minchan   = 3
        maxgap    = 2
        peakfit   = False             # True will enable a true gaussian fit
        
        # numsigma:  adding all signal > numsigma ; not user enabled;   for peaksum.
        numsigma = -1.0
        numsigma = 3.0

        # grab the new robust statistics. If this is used, 'rms' will be the RMS,
        # else we will use RMS = 1.4826*MAD (MAD does a decent job on outliers as well)
        # and was the only method available before CASA 4.4 when robust was implemented
        robust = self.getkey("robust")
        rargs = casautil.parse_robust(robust)
        nrargs = len(rargs)

        if nrargs == 0:
           sumrargs = "medabsdevmed"      # for the summary, indicate the default robust
        else:
           sumrargs = str(rargs)

        self._summary["rmsmethd"] = SummaryEntry([sumrargs,fin],"CubeStats_AT",self.id(True))
        #@todo think about using this instead of putting 'fin' in all the SummaryEntry
        #self._summary["casaimage"] = SummaryEntry(fin,"CubeStats_AT",self.id(True))

        # extra CASA call to get the freq's in GHz, as these are not in imstat1{}
        # @todo what if the coordinates are not in FREQ ?
        # Note: CAS-7648 bug on 3D cubes
        if False:
            # csys method
            ia.open(self.dir(fin))
            csys = ia.coordsys() 
            spec_axis = csys.findaxisbyname("spectral") 
            # ieck, we need a valid position, or else it will come back and "Exception: All selected pixels are masked"
            #freqs = ia.getprofile(spec_axis, region=rg.box([0,0],[0,0]))['coords']/1e9
            #freqs = ia.getprofile(spec_axis)['coords']/1e9
            freqs = ia.getprofile(spec_axis,unit="GHz")['coords']
            dt.tag("getprofile")
        else:
            # old imval method 
            #imval0 = casa.imval(self.dir(fin),box='0,0,0,0')     # this fails on 3D
            imval0 = casa.imval(self.dir(fin))
            freqs = imval0['coords'].transpose()[2]/1e9
            dt.tag("imval")
        nchan = len(freqs)
        chans = np.arange(nchan)

        # call CASA to get what we want
        # imstat0 is the whole cube, imstat1 the plane based statistics
        # warning: certain robust stats (**rargs) on the whole cube are going to be very slow
        dt.tag("start")
        imstat0 = casa.imstat(self.dir(fin),           logfile=self.dir('imstat0.logfile'),append=False,**rargs)
        dt.tag("imstat0")
        imstat1 = casa.imstat(self.dir(fin),axes=[0,1],logfile=self.dir('imstat1.logfile'),append=False,**rargs)
        dt.tag("imstat1")
        # imm = casa.immoments(self.dir(fin),axis='spec', moments=8, outfile=self.dir('ppp.im'))
        if nrargs > 0:
            # need to get the peaks without rubust
            imstat10 = casa.imstat(self.dir(fin),           logfile=self.dir('imstat0.logfile'),append=True)
            dt.tag("imstat10")
            imstat11 = casa.imstat(self.dir(fin),axes=[0,1],logfile=self.dir('imstat1.logfile'),append=True)
            dt.tag("imstat11")

        # grab the relevant plane-based things from imstat1
        if nrargs == 0:
            mean    = imstat1["mean"]
            sigma   = imstat1["medabsdevmed"]*1.4826     # see also: astropy.stats.median_absolute_deviation()
            peakval = imstat1["max"]
            minval  = imstat1["min"]
        else:
            mean    = imstat1["mean"]
            sigma   = imstat1["rms"]
            peakval = imstat11["max"]
            minval  = imstat11["min"]

        if True:
            # work around a bug in imstat(axes=[0,1]) for last channel [CAS-7697]
            for i in range(len(sigma)):
                if sigma[i] == 0.0:
                    minval[i] = peakval[i] = 0.0

        # too many variations in the RMS ?
        sigma_pos = sigma[np.where(sigma>0)]
        smin = sigma_pos.min()
        smax = sigma_pos.max()
        logging.info("sigma varies from %f to %f; %d/%d channels ok" % (smin,smax,len(sigma_pos),len(sigma)))
        if maxvrms > 0:
            if smax/smin > maxvrms:
                cliprms = smin * maxvrms
                logging.warning("sigma varies too much, going to clip to %g (%g > %g)" % (cliprms, smax/smin, maxvrms))
                sigma = np.where(sigma < cliprms, sigma, cliprms)

        # @todo   (and check again) for foobar.fits all sigma's became 0 when robust was selected
        #         was this with mask=True/False?

        # PeakPointPlot (can be expensive, hence the option)
        if use_ppp:
            logging.info("Computing MaxPos for PeakPointPlot")
            xpos    = np.zeros(nchan)
            ypos    = np.zeros(nchan)
            peaksum = np.zeros(nchan)

            ia.open(self.dir(fin))
            for i in range(nchan):
                if sigma[i] > 0.0:
                    plane = ia.getchunk(blc=[0,0,i,-1],trc=[-1,-1,i,-1],dropdeg=True)
                    v = ma.masked_invalid(plane)
                    v_abs = np.absolute(v)
                    max = np.unravel_index(v_abs.argmax(), v_abs.shape)
                    xpos[i] = max[0]
                    ypos[i] = max[1]
                    if numsigma > 0.0:
                        peaksum[i] = ma.masked_less(v,numsigma * sigma[i]).sum()
            peaksum = np.nan_to_num(peaksum)    # put 0's where nan's are found
            ia.close()
            dt.tag("ppp")

        nzeros = len(np.where(sigma<=0.0))
        if nzeros > 0:
            zeroch = np.where(sigma<=0.0)
            logging.warning("There are %d fully masked channels (%s)" % (nzeros,str(zeroch)))

        # construct the admit Table for CubeStats_BDP
        # note data needs to be a tuple, later to be column_stack'd
        if use_ppp:
            labels = ["channel" ,"frequency" ,"mean"    ,"sigma"   ,"max"     ,"maxposx" ,"maxposy" ,"min",     "peaksum"]
            units  = ["number"  ,"GHz"       ,"Jy/beam" ,"Jy/beam" ,"Jy/beam" ,"number"  ,"number"  ,"Jy/beam", "Jy"]
            data   = (chans     ,freqs       ,mean      ,sigma     ,peakval   ,xpos      ,ypos      ,minval,    peaksum)

        else:
            labels = ["channel" ,"frequency" ,"mean"    ,"sigma"   ,"max"     ,"min"]
            units  = ["number"  ,"GHz"       ,"Jy/beam" ,"Jy/beam" ,"Jy/beam" ,"Jy/beam"]
            data   = (chans     ,freqs       ,mean      ,sigma     ,peakval   ,minval)

        table = Table(columns=labels,units=units,data=np.column_stack(data))
        b2.setkey("table",table)

        # get the full cube statistics, it depends if robust was pre-selected
        if nrargs == 0:
            mean0  = imstat0["mean"][0]
            sigma0 = imstat0["medabsdevmed"][0]*1.4826
            peak0  = imstat0["max"][0]
            b2.setkey("mean" , float(mean0))
            b2.setkey("sigma", float(sigma0))
            b2.setkey("minval",float(imstat0["min"][0]))
            b2.setkey("maxval",float(imstat0["max"][0]))
            b2.setkey("minpos",imstat0["minpos"][:3].tolist())     #? [] or array(..dtype=int32) ??
            b2.setkey("maxpos",imstat0["maxpos"][:3].tolist())     #? [] or array(..dtype=int32) ??
            logging.info("CubeMax: %f @ %s" % (imstat0["max"][0],str(imstat0["maxpos"])))
            logging.info("CubeMin: %f @ %s" % (imstat0["min"][0],str(imstat0["minpos"])))
            logging.info("CubeRMS: %f" % sigma0)
        else:
            mean0  = imstat0["mean"][0]
            sigma0 = imstat0["rms"][0]
            peak0  = imstat10["max"][0]
            b2.setkey("mean" , float(mean0))
            b2.setkey("sigma", float(sigma0))
            b2.setkey("minval",float(imstat10["min"][0]))
            b2.setkey("maxval",float(imstat10["max"][0]))
            b2.setkey("minpos",imstat10["minpos"][:3].tolist())     #? [] or array(..dtype=int32) ??
            b2.setkey("maxpos",imstat10["maxpos"][:3].tolist())     #? [] or array(..dtype=int32) ??
            logging.info("CubeMax: %f @ %s" % (imstat10["max"][0],str(imstat10["maxpos"])))
            logging.info("CubeMin: %f @ %s" % (imstat10["min"][0],str(imstat10["minpos"])))
            logging.info("CubeRMS: %f" % sigma0)
        b2.setkey("robust",robust)
        rms_ratio = imstat0["rms"][0]/sigma0
        logging.info("RMS Sanity check %f" % rms_ratio)
        if rms_ratio > 1.5:
            logging.warning("RMS sanity check = %f.  Either bad sidelobes, lotsa signal, or both" % rms_ratio)
        logging.regression("CST: %f %f" % (sigma0, rms_ratio))

        # plots: no plots need to be made when nchan=1 for continuum
        # however we could make a histogram, overlaying the "best" gauss so 
        # signal deviations are clear?

        logging.info('mean,rms,S/N=%f %f %f' % (mean0,sigma0,peak0/sigma0))

        if nchan == 1:
            # for a continuum/1-channel we only need to stuff some numbers into the _summary
            self._summary["chanrms"] = SummaryEntry([float(sigma0), fin], "CubeStats_AT", self.id(True))
            self._summary["dynrange"] = SummaryEntry([float(peak0)/float(sigma0), fin], "CubeStats_AT", self.id(True))
            self._summary["datamean"] = SummaryEntry([float(mean0), fin], "CubeStats_AT", self.id(True))
        else:
            y1 = np.log10(ma.masked_invalid(peakval))
            y2 = np.log10(ma.masked_invalid(sigma))
            y3 = y1-y2
            y4 = np.log10(ma.masked_invalid(-minval))
            y5 = y1-y4
            y = [y1,y2,y3,y4]
            title = 'CubeStats: ' + bdp_name+'_0'
            xlab  = 'Channel'
            ylab  = 'log(Peak,Noise,Peak/Noise)'
            labels = ['log(peak)','log(rms noise)','log(peak/noise)','log(|minval|)']
            myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())
            segp = [[chans[0],chans[nchan-1],math.log10(sigma0),math.log10(sigma0)]]
            myplot.plotter(chans,y,title,bdp_name+"_0",xlab=xlab,ylab=ylab,segments=segp,labels=labels,thumbnail=True)
            imfile = myplot.getFigure(figno=myplot.figno,relative=True)
            thumbfile = myplot.getThumbnail(figno=myplot.figno,relative=True)

            image0 = Image(images={bt.PNG:imfile},thumbnail=thumbfile,thumbnailtype=bt.PNG,description="CubeStats_0")
            b2.addimage(image0,"im0")

            if use_ppp:
                # new trial for Lee
                title = 'PeakSum: (numsigma=%.1f)' % (numsigma)
                ylab = 'Jy*N_ppb'
                myplot.plotter(chans,[peaksum],title,bdp_name+"_00",xlab=xlab,ylab=ylab,thumbnail=False)

            if True:
                # hack ascii table
                y30 = np.where(sigma > 0, np.log10(peakval/sigma), 0.0)
                table2 = Table(columns=["freq","log(P/N)"],data=np.column_stack((freqs,y30)))
                table2.exportTable(self.dir("testCubeStats.tab"))
                del table2

            # the "box" for the "spectrum" is all pixels.  Don't know how to 
            # get this except via shape.
            ia.open(self.dir(fin))
            s = ia.summary()
            ia.close()
            if 'shape' in s:
                specbox = (0,0,s['shape'][0],s['shape'][1])
            else:
                specbox = ()

            caption = "Emission characteristics as a function of channel, as derived by CubeStats_AT "
            caption += "(cyan: global rms,"
            caption += " green: noise per channel,"
            caption += " blue: peak value per channel,"
            caption += " red: peak/noise per channel)."
            self._summary["spectra"] = SummaryEntry([0, 0, str(specbox), 'Channel', imfile, thumbfile , caption, fin], "CubeStats_AT", self.id(True))
            self._summary["chanrms"] = SummaryEntry([float(sigma0), fin], "CubeStats_AT", self.id(True))

            # @todo Will imstat["max"][0] always be equal to s['datamax']?  If not, why not?
            if 'datamax' in s:
                self._summary["dynrange"] = SummaryEntry([float(s['datamax']/sigma0), fin], "CubeStats_AT", self.id(True))
            else:
                self._summary["dynrange"] = SummaryEntry([float(imstat0["max"][0]/sigma0), fin], "CubeStats_AT", self.id(True))
            self._summary["datamean"] = SummaryEntry([imstat0["mean"][0], fin], "CubeStats_AT", self.id(True))

            title = bdp_name + "_1"
            xlab =  'log(Peak,Noise,P/N)'
            myplot.histogram([y1,y2,y3],title,bdp_name+"_1",xlab=xlab,thumbnail=True)

            imfile = myplot.getFigure(figno=myplot.figno,relative=True)
            thumbfile = myplot.getThumbnail(figno=myplot.figno,relative=True)
            image1 = Image(images={bt.PNG:imfile},thumbnail=thumbfile,thumbnailtype=bt.PNG,description="CubeStats_1")
            b2.addimage(image1,"im1")

            # note that the 'y2' can have been clipped, which can throw off stats.robust()
            # @todo  should set a mask for those.

            title = bdp_name + "_2"
            xlab = 'log(Noise))'
            n = len(y2)
            ry2 = stats.robust(y2)
            y2_mean = ry2.mean()
            y2_std  = ry2.std()
            if n>9: logging.debug("NORMALTEST2: %s" % str(scipy.stats.normaltest(ry2)))
            myplot.hisplot(y2,title,bdp_name+"_2",xlab=xlab,gauss=[y2_mean,y2_std],thumbnail=True)

            title = bdp_name + "_3"
            xlab = 'log(diff[Noise])'
            n = len(y2)
            # dy2 = y2[0:-2] - y2[1:-1]
            dy2 = ma.masked_equal(y2[0:-2] - y2[1:-1],0.0).compressed()
            rdy2 = stats.robust(dy2)
            dy2_mean = rdy2.mean()
            dy2_std  = rdy2.std()
            if n>9: logging.debug("NORMALTEST3: %s" % str(scipy.stats.normaltest(rdy2)))
            myplot.hisplot(dy2,title,bdp_name+"_3",xlab=xlab,gauss=[dy2_mean,dy2_std],thumbnail=True)


            title = bdp_name + "_4"
            xlab = 'log(Signal/Noise))'
            n = len(y3)
            ry3 = stats.robust(y3)
            y3_mean = ry3.mean()
            y3_std  = ry3.std()
            if n>9: logging.debug("NORMALTEST4: %s" % str(scipy.stats.normaltest(ry3)))
            myplot.hisplot(y3,title,bdp_name+"_4",xlab=xlab,gauss=[y3_mean,y3_std],thumbnail=True)

            title = bdp_name + "_5"
            xlab = 'log(diff[Signal/Noise)])'
            n = len(y3)
            dy3 = y3[0:-2] - y3[1:-1]
            rdy3 = stats.robust(dy3)
            dy3_mean = rdy3.mean()
            dy3_std  = rdy3.std()
            if n>9: logging.debug("NORMALTEST5: %s" % str(scipy.stats.normaltest(rdy3)))
            myplot.hisplot(dy3,title,bdp_name+"_5",xlab=xlab,gauss=[dy3_mean,dy3_std],thumbnail=True)


            title = bdp_name + "_6"
            xlab = 'log(Peak+Min)'
            n = len(y1)
            ry5 = stats.robust(y5)
            y5_mean = ry5.mean()
            y5_std  = ry5.std()
            if n>9: logging.debug("NORMALTEST6: %s" % str(scipy.stats.normaltest(ry5)))
            myplot.hisplot(y5,title,bdp_name+"_6",xlab=xlab,gauss=[y5_mean,y5_std],thumbnail=True)

            logging.debug("LogPeak: m,s= %f %f min/max %f %f" % (y1.mean(),y1.std(),y1.min(),y1.max()))
            logging.debug("LogNoise: m,s= %f %f %f %f min/max %f %f" % (y2.mean(),y2.std(),y2_mean,y2_std,y2.min(),y2.max()))
            logging.debug("LogDeltaNoise: RMS/sqrt(2)= %f %f " % (dy2.std()/math.sqrt(2),dy2_std/math.sqrt(2)))
            logging.debug("LogDeltaP/N:   RMS/sqrt(2)= %f %f" % (dy3.std()/math.sqrt(2),dy3_std/math.sqrt(2)))
            logging.debug("LogPeak+Min: robust m,s= %f %f" % (y5_mean,y5_std))

            # compute two ratios that should both be near 1.0 if noise is 'normal'
            ratio  = y2.std()/(dy2.std()/math.sqrt(2))
            ratio2 = y2_std/(dy2_std/math.sqrt(2))
            logging.info("RMS BAD VARIATION RATIO: %f %f" % (ratio,ratio2))

        # making PPP plot
        if nchan > 1 and use_ppp:
            smax = 10
            gamma = 0.75

            z0 = peakval/peakval.max()
            # point sizes
            s = np.pi * ( smax * (z0**gamma) )**2
            cmds = ["grid", "axis equal"]
            title = "Peak Points per channel"
            pppimage = bdp_name + '_ppp'
            myplot.scatter(xpos,ypos,title=title,figname=pppimage,size=s,color=chans,cmds=cmds,thumbnail=True)
            pppimage     = myplot.getFigure(figno=myplot.figno,relative=True)
            pppthumbnail = myplot.getThumbnail(figno=myplot.figno,relative=True)
            caption = "Peak point plot: Locations of per-channel peaks in the image cube " + fin
            self._summary["peakpnt"] = SummaryEntry([pppimage, pppthumbnail, caption, fin], "CubeStats_AT", self.id(True))
        dt.tag("plotting")

        # making PeakStats plot
        if nchan > 1 and psample > 0:
            logging.info("Computing peakstats")
            # grab peak,mean and width values for all peaks
            (pval,mval,wval) = peakstats(self.dir(fin),freqs,sigma0,pnumsigma,minchan,maxgap,psample,peakfit)
            title = "PeakStats: cutoff = %g" % (sigma0*pnumsigma)
            xlab = 'Peak value'
            ylab = 'FWHM (channels)'
            pppimage = bdp_name + '_peakstats'
            cval = mval
            myplot.scatter(pval,wval,title=title,xlab=xlab,ylab=ylab,color=cval,figname=pppimage,thumbnail=False)
            dt.tag("peakstats")
            

        # myplot.final()    # pjt debug 
        # all done!
        dt.tag("done")

        taskargs = "robust=" + sumrargs 
        if use_ppp: 
            taskargs = taskargs + " ppp=True"
        else: 
            taskargs = taskargs + " ppp=False"
        for v in self._summary:
            self._summary[v].setTaskArgs(taskargs)

        dt.tag("summary")
        dt.end()
Example #3
0
class PVCorr_AT(AT):
    """PV correllation in a PVSlice map.

       PVCorr_AT computes a cross-correlation of a feature in a PVSlice with the 
       whole PVSlice, looking for repeated patterns to detect spectral lines. Much
       like the output from CubeStats_AT and CubeSpectrum_AT, this table can then be
       given to LineID_AT to attempt a line identification.

       See also :ref:`PVCorr-AT-Design` for the design document.

       **Keywords**

          **numsigma**: float 
            Minimum intensity, in terms of sigma, above which a selected portion
            of the spectrum will be used for cross-correlation.
            Default: 3.0.

          **range**: integer list
            If given, it has to be a list with 2 channel numbers, the first and last channel
            (0-based channels) of the range which to use for the cross-correlation.
            Default: [].

          **nchan**: integer
            The number of channels (in case range= was not used) that defines the line.
            The line is centers on the strongest point in the input PV-map.
            If 0 is given, it will watershed down from the strongest line.
            Default: 0.

       **Input BDPs**

          **PVSlice_BDP**: count: 1
            Input PV Slice. As created with e.g. PVSlice_AT.

          **CubeStats_BDP**: count: 1
            Input cube statistics from which the RMS is taken.

       **Output BDPs**

          **PVCorr_BDP**: count: 1
            Output table.
       

    """
    def __init__(self, **keyval):
        keys = {
            "numsigma": 3.0,  # N-sigma
            "range": [],  # optional channel range
            "nchan":
            0,  # number of channels around the channel where the peak is
        }
        AT.__init__(self, keys, keyval)
        self._version = "1.0.1"
        self.set_bdp_in([
            (Image_BDP, 1, bt.REQUIRED),
            # @todo optional 2nd PVSlice can be used to draw the template from
            (CubeStats_BDP, 1, bt.REQUIRED)
        ])
        self.set_bdp_out([(PVCorr_BDP, 1)])

    def summary(self):
        """Returns the summary dictionary from the AT, for merging
           into the ADMIT Summary object.

           PVCorr_AT adds the following to ADMIT summary:

           .. table::
              :class: borderless

              +----------+----------+-----------------------------------+
              |   Key    | type     |    Description                    |
              +==========+==========+===================================+
              | pvcorr   | list     |   correlation diagram             |
              +----------+----------+-----------------------------------+
           
           Parameters
           ----------
           None

           Returns
           -------
           dict
               Dictionary of SummaryEntry
        """
        if hasattr(self, "_summary"):
            return self._summary
        else:
            return {}

    def run(self):
        dt = utils.Dtime("PVCorr")
        self._summary = {}

        numsigma = self.getkey("numsigma")
        mode = 1  # PV corr mode (1,2,3)
        normalize = True
        # normalize = False

        b1 = self._bdp_in[0]  # PVSlice_BDP
        fin = b1.getimagefile(bt.CASA)  # CASA image
        data = casautil.getdata_raw(
            self.dir(fin))  # grab the data as a numpy array
        self.myplot = APlot(ptype=self._plot_type,
                            pmode=self._plot_mode,
                            abspath=self.dir())
        #print 'DATA[0,0]:',data[0,0]
        #print 'pv shape: ',data.shape
        npos = data.shape[0]
        nvel = data.shape[1]
        dt.tag("getdata")

        b2 = self._bdp_in[1]  # CubeStats_BDP
        sigma = b2.sigma  # global sigma in the cube
        cutoff = numsigma * sigma
        freq = b2.table.getColumnByName("frequency")

        chans = self.getkey("range")  # range of channels, if used
        if len(chans) > 0:
            if len(chans) != 2:
                logging.fatal("range=%s" % chans)
                raise Exception, "range= needs two values, left and right (inclusive) channel"
            ch0 = chans[0]
            ch1 = chans[1]
        else:
            nchan = self.getkey("nchan")
            imstat0 = casa.imstat(self.dir(fin))  # @todo   can use data[] now
            xmaxpos = imstat0['maxpos'][0]
            ymaxpos = imstat0['maxpos'][1]
            logging.info("MAXPOS-VEL %s %g" %
                         (str(imstat0['maxpos']), imstat0['max'][0]))
            if nchan > 0:
                # expand around it, later ch0,ch1 will be checked for running off the edge
                ch0 = ymaxpos - nchan / 2
                ch1 = ymaxpos + nchan / 2
            else:
                # watershed down to find ch0 and ch1 ?
                # this doesn't work well in crowded areas
                ch0 = ymaxpos
                ch1 = ymaxpos
                spmax = data.max(axis=0)
                k = spmax.argmax()
                n = len(spmax)
                logging.debug('spmax %s %d %g' %
                              (str(spmax.shape), k, spmax[k]))
                # find lower cutoff
                for i in range(n):
                    ch0 = ymaxpos - i
                    if ch0 < 0: break
                    if spmax[ch0] < cutoff: break
                ch0 = ch0 + 1
                # find higher cutoff
                for i in range(n):
                    ch1 = ymaxpos + i
                    if ch1 == n: break
                    if spmax[ch1] < cutoff: break
                ch1 = ch1 - 1
            dt.tag("imstat")

        bdp_name = self.mkext(fin, "pvc")  # output PVCorr_BDP
        b3 = PVCorr_BDP(bdp_name)
        self.addoutput(b3)

        if ch0 < 0 or ch1 >= nvel:
            # this probably only happens to small cubes (problematic for PVCorr)
            # or when the strongest line is really close to the edge of the band
            # (which is probably ok)
            if ch0 < 0 and ch1 >= nvel:
                logging.warning("Serious issues with the size of this cube")
            if ch0 < 0:
                logging.warning("Resetting ch0 edge to 0")
                ch0 = 0
            if ch1 >= nvel:
                ch1 = nvel - 1
                logging.warning("Resetting ch1 edge to the maximum")

        if ch0 > ch1:
            logging.warning("Sanity swapping ch0,1 due to likely noisy data")
            ch0, ch1 = ch1, ch0

        if mode == 1:
            out, rms = mode1(data, ch0, ch1, cutoff, normalize)
            corr = out
        elif mode == 2:
            out, rms = mode2(data, ch0, ch1, cutoff)  # slower 2D version
            corr = out[
                npos /
                2, :]  # center cut, but could also try feature detection
        elif mode == 3:
            out, rms = self.mode3(data, ch0, ch1,
                                  cutoff)  # Doug's faster 2D version
            # get the peak of each column
            corr = np.amax(out, axis=0)
        # print "PVCORR SHAPE ",corr.shape," mode", mode
        if len(corr) > 0:
            # print "SHAPE out:",out.shape,corr.shape,npos/2
            ch = range(len(corr))
            if len(corr) != len(freq):
                logging.fatal("ch (%d) and freq (%d) do not have same size" %
                              (len(corr), len(freq)))
                raise Exception, "ch and freq do not have same dimension"
            dt.tag("mode")
            labels = ["channel", "frequency", "pvcorr"]
            units = ["number", "GHz", "N/A"]
            data = (ch, freq, corr)
            table = Table(columns=labels,
                          units=units,
                          data=np.column_stack(data))
        else:
            # still construct a table, but with no rows
            labels = ["channel", "frequency", "pvcorr"]
            units = ["number", "GHz", "N/A"]
            table = Table(columns=labels, units=units)
        b3.setkey("table", table)
        b3.setkey("sigma", float(rms))
        dt.tag("table")
        if len(corr) > 0:
            table.exportTable(self.dir("testPVCorr.tab"),
                              cols=['frequency', 'pvcorr'])
            test_single(ch, freq, corr)

            logging.regression("PVC: %f %f" % (corr.min(), corr.max()))

            title = 'PVCorr mode=%d [%d,%d] %g' % (mode, ch0, ch1, cutoff)
            x = ch
            xlab = 'Channel'
            y = [corr]
            ylab = 'PV Correlation'
            p1 = "%s_%d" % (bdp_name, 0)
            segp = []
            segp.append([0, len(ch), 0.0, 0.0])
            segp.append([0, len(ch), 3.0 * rms, 3.0 * rms])
            # @todo:   in principle we know with given noise and  size of box, what the sigma in pvcorr should be
            self.myplot.plotter(x,
                                y,
                                title,
                                figname=p1,
                                xlab=xlab,
                                ylab=ylab,
                                segments=segp,
                                thumbnail=True)

            #out1 = np.rot90 (data.reshape((nvel,npos)) )
            if mode > 1:
                self.myplot.map1(data=out,
                                 title="testing PVCorr_AT:  mode%d" % mode,
                                 figname='testPVCorr',
                                 thumbnail=True)

            taskargs = "numsigma=%.1f range=[%d,%d]" % (numsigma, ch0, ch1)
            caption = "Position-velocity correlation plot"
            thumbname = self.myplot.getThumbnail(figno=self.myplot.figno,
                                                 relative=True)
            figname = self.myplot.getFigure(figno=self.myplot.figno,
                                            relative=True)
            image = Image(images={bt.PNG: figname},
                          thumbnail=thumbname,
                          thumbnailtype=bt.PNG,
                          description=caption)
            b3.image.addimage(image, "pvcorr")

            self._summary["pvcorr"] = SummaryEntry(
                [figname, thumbname, caption, fin], "PVCorr_AT", self.id(True),
                taskargs)
        else:
            self._summary["pvcorr"] = None
            logging.warning("No summary")
            logging.regression("PVC: -1")

        dt.tag("done")
        dt.end()

    def mode3(self, data, v0, v1, dmin=0.0):
        """ v0..v1 (both inclusive) are channel selections
            threshold on dmin
            @todo the frequency axis is not properly calibrated here
            @todo a full 2D is slow, we only need the 1D version
        """
        print "PVCorr mode3: v0,1=", v0, v1
        smin = data.min()
        #s = data[v0:v1+1,:]
        s = data[:, v0:v1 + 1]
        if dmin == 0.0:
            logging.warning("Using all data in crosscorr")
            f = s
        else:
            f = np.where(s > dmin, s, 0)
        # find out where the zeros are
        temp = np.amax(f, axis=1)
        nz = np.nonzero(temp)
        # trim the kernel in the y direction, removing rows that are all 0.0
        f = f[nz[0][0]:nz[0][-1], :]
        f0 = np.where(s > smin, 1, 0)
        f1 = np.where(s > dmin, 1, 0)
        fmax = f.max()
        print "PVCorr mode3:", f1.sum(), '/', f0.sum(), 'min/max', smin, fmax
        out = scipy.signal.correlate2d(data, f, mode='same')
        self.myplot.map1(data=f,
                         title="PVCorr 2D Kernel",
                         figname='PVCorrKernel',
                         thumbnail=True)

        print 'PVCorr min/max:', out.min(), out.max()
        n1, m1, s1, n2, m2, s2 = stats.mystats(out.flatten())
        print "PVCorr stats", n1, m1, s1, n2, m2, s2
        rms_est = s2 / np.sqrt(f1.sum())
        return out, rms_est
Example #4
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """

        self._summary = {}
        dt = utils.Dtime("CubeSpectrum")
        seed = self.getkey("seed")
        if seed <= 0:
            np.random.seed()
        else:
            np.random.seed(seed)
        #print "RANDOM.GET_STATE:",np.random.get_state()
        contin = self.getkey("contin")
        rms = 1.0  # not a user parameter, we do all spectra in S/N space
        f0 = self.getkey("freq")  # central frequency in band
        df = self.getkey("delta") / 1000.0  # channel width (in GHz)
        nspectra = self.getkey("nspectra")
        taskargs = " contin=%f freq=%f delta=%f nspectra=%f " % (contin, f0,
                                                                 df, nspectra)
        spec = range(nspectra)
        dt.tag("start")
        if self.getkey("file") != "":
            print "READING spectrum from", self.getkey("file")
            (freq, spec[0]) = getspec(self.getkey("file"))
            nchan = len(freq)
            print "Spectrum %d chans from %f to %f: min/max = %f %f" % (
                nchan, freq.min(), freq.max(), spec[0].min(), spec[0].max())
            # @todo nspectra>1 not tested
            for i in range(1, nspectra):
                spec[i] = deepcopy(spec[0])
            dt.tag("getspec")
        else:
            nchan = self.getkey("nchan")
            freq = np.arange(nchan, dtype=np.float64)
            center = int(nchan / 2)
            for i in range(nchan):
                freq[i] = f0 + (float((i - center)) * df)
            for i in range(nspectra):
                spec[i] = np.zeros(nchan)
        chans = np.arange(nchan)
        taskargs += " nchan = %d" % nchan
        for i in range(nspectra):
            if seed >= 0:
                spec[i] += np.random.normal(contin, rms, nchan)
#            print "MEAN/STD",spec[i].mean(),spec[i].std()
        lines = self.getkey("lines")
        sls = SpectralLineSearch(False)
        for item in self.getkey("transitions"):
            kw = {
                "include_only_nrao": True,
                "line_strengths": ["ls1", "ls2"],
                "energy_levels": ["el2", "el4"],
                "fel": True,
                "species": item[0]
            }
            results = sls.search(item[1][0], item[1][1], "off", **kw)
            # look at line strengths
            if len(results) > 0:
                mx = 0.0
                indx = -1
                for i in range(len(results)):
                    if results[i].getkey("linestrength") > mx:
                        indx = i
                        mx = results[i].getkey("linestrength")
                for res in results:
                    if mx > 0.0:
                        lines.append([
                            item[2] * res.getkey("linestrength") / mx,
                            res.getkey("frequency") +
                            utils.veltofreq(item[4], res.getkey("frequency")),
                            item[3]
                        ])
                    else:
                        lines.append([
                            item[2],
                            res.getkey("frequency") +
                            utils.veltofreq(item[4], res.getkey("frequency")),
                            item[3]
                        ])
        for item in lines:
            for i in range(nspectra):
                spec[i] += utils.gaussian1D(freq, item[0], item[1],
                                            utils.veltofreq(item[2], item[1]))

        if self.getkey("hanning"):
            for i in range(nspectra):
                filter = Filter1D.Filter1D(spec[i], "hanning", **{"width": 3})
                spec[i] = filter.run()
            dt.tag("hanning")
        center = int(nchan / 2)
        dt.tag("open")
        bdp_name = self.mkext("Genspec", "csp")
        b2 = CubeSpectrum_BDP(bdp_name)
        self.addoutput(b2)
        images = {}  # png's accumulated
        for i in range(nspectra):
            sd = []
            caption = "Generated Spectrum %d" % i
            # construct the Table for CubeSpectrum_BDP
            # @todo note data needs to be a tuple, later to be column_stack'd
            labels = ["channel", "frequency", "flux"]
            units = ["number", "GHz", ""]
            data = (chans, freq, spec[i])

            # plane 0 : we are allowing a multiplane table, so the first plane is special
            if i == 0:
                table = Table(columns=labels,
                              units=units,
                              data=np.column_stack(data),
                              planes=["0"])
            else:
                table.addPlane(np.column_stack(data), "%d" % i)
            # example plot , one per position for now
            x = chans
            xlab = 'Channel'
            y = [spec[i]]
            sd.append(xlab)

            myplot = APlot(ptype=self._plot_type,
                           pmode=self._plot_mode,
                           abspath=self.dir())
            ylab = 'Flux'
            p1 = "%s_%d" % (bdp_name, i)
            myplot.plotter(x, y, "", p1, xlab=xlab, ylab=ylab, thumbnail=True)
            # Why not use p1 as the key?
            ii = images["pos%d" % i] = myplot.getFigure(figno=myplot.figno,
                                                        relative=True)
            thumbname = myplot.getThumbnail(figno=myplot.figno, relative=True)

            image = Image(images=images, description="CubeSpectrum")
            sd.extend([ii, thumbname, caption])
            self.spec_description.append(sd)

        self._summary["spectra"] = SummaryEntry(self.spec_description,
                                                "GenerateSpectrum_AT",
                                                self.id(True), taskargs)

        dt.tag("table")
        b2.setkey("image", image)
        b2.setkey("table", table)
        b2.setkey("sigma", rms)
        b2.setkey("mean", contin)

        dt.tag("done")
        dt.end()
Example #5
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """

        self._summary = {}
        dt = utils.Dtime("CubeSpectrum")

        # our BDP's
        # b1  = input BDP
        # b1s = optional input CubeSpectrum
        # b1m = optional input Moment
        # b1p = optional input SourceList for positions
        # b2  = output BDP

        b1 = self._bdp_in[0]  # check input SpwCube (or LineCube)
        fin = b1.getimagefile(bt.CASA)
        if self._bdp_in[0]._type == bt.LINECUBE_BDP:
            use_vel = True
        else:
            use_vel = False

        sources = self.getkey("sources")
        pos = [
        ]  # blank it first, then try and grab it from the optional bdp_in's
        cmean = 0.0
        csigma = 0.0
        smax = []  # accumulate max in each spectrum for regression
        self.spec_description = []  # for summary()

        if self._bdp_in[1] != None:  # check if CubeStats_BDP
            #print "BDP[1] type: ",self._bdp_in[1]._type
            if self._bdp_in[1]._type != bt.CUBESTATS_BDP:
                raise Exception, "bdp_in[1] not a CubeStats_BDP, should never happen"
            # a table (cubestats)
            b1s = self._bdp_in[1]
            pos.append(b1s.maxpos[0])
            pos.append(b1s.maxpos[1])
            logging.info('CubeStats::maxpos,val=%s,%f' %
                         (str(b1s.maxpos), b1s.maxval))
            cmean = b1s.mean
            csigma = b1s.sigma
            dt.tag("CubeStats-pos")

        if self._bdp_in[
                2] != None:  # check if Moment_BDP (probably from CubeSum)
            #print "BDP[2] type: ",self._bdp_in[2]._type
            if self._bdp_in[2]._type != bt.MOMENT_BDP:
                raise Exception, "bdp_in[2] not a Moment_BDP, should never happen"
            b1m = self._bdp_in[2]
            fim = b1m.getimagefile(bt.CASA)
            pos1, maxval = self.maxpos_im(
                self.dir(fim))  # compute maxpos, since it is not in bdp (yet)
            logging.info('CubeSum::maxpos,val=%s,%f' % (str(pos1), maxval))
            pos.append(pos1[0])
            pos.append(pos1[1])
            dt.tag("Moment-pos")

        if self._bdp_in[3] != None:  # check if SourceList
            #print "BDP[3] type: ",self._bdp_in[3]._type
            # a table (SourceList)
            b1p = self._bdp_in[3]
            ra = b1p.table.getFullColumnByName("RA")
            dec = b1p.table.getFullColumnByName("DEC")
            peak = b1p.table.getFullColumnByName("Peak")
            if sources == []:
                # use the whole SourceList
                for (r, d, p) in zip(ra, dec, peak):
                    rdc = convert_sexa(r, d)
                    pos.append(rdc[0])
                    pos.append(rdc[1])
                    logging.info('SourceList::maxpos,val=%s,%f' %
                                 (str(rdc), p))
            else:
                # select specific ones from the source list
                for ipos in sources:
                    if ipos < len(ra):
                        radec = convert_sexa(ra[ipos], dec[ipos])
                        pos.append(radec[0])
                        pos.append(radec[1])
                        logging.info('SourceList::maxpos,val=%s,%f' %
                                     (str(radec), peak[ipos]))
                    else:
                        logging.warning('Skipping illegal source number %d' %
                                        ipos)

            dt.tag("SourceList-pos")

        # if pos[] still blank, use the AT keyword.
        if len(pos) == 0:
            pos = self.getkey("pos")

        # if still none, try the map center
        if len(pos) == 0:
            # @todo  this could result in a masked pixel and cause further havoc
            # @todo  could also take the reference pixel, but that could be outside image
            taskinit.ia.open(self.dir(fin))
            s = taskinit.ia.summary()
            pos = [int(s['shape'][0]) / 2, int(s['shape'][1]) / 2]
            logging.warning(
                "No input positions supplied, map center choosen: %s" %
                str(pos))
            dt.tag("map-center")

        # exhausted all sources where pos[] can be set; if still zero, bail out
        if len(pos) == 0:
            raise Exception, "No positions found from input BDP's or pos="

        # convert this regular list to a list of tuples with duplicates removed
        # sadly the order is lost.
        pos = list(set(zip(pos[0::2], pos[1::2])))
        npos = len(pos)

        dt.tag("open")

        bdp_name = self.mkext(fin, "csp")
        b2 = CubeSpectrum_BDP(bdp_name)
        self.addoutput(b2)

        imval = range(npos)  # spectra, one for each pos (placeholder)
        planes = range(npos)  # labels for the tables (placeholder)
        images = {}  # png's accumulated

        for i in range(npos):  # loop over pos, they can have mixed types now
            sd = []
            caption = "Spectrum"
            xpos = pos[i][0]
            ypos = pos[i][1]
            if type(xpos) != type(ypos):
                print "POS:", xpos, ypos
                raise Exception, "position pair not of the same type"
            if type(xpos) == int:
                # for integers, boxes are allowed, even multiple
                box = '%d,%d,%d,%d' % (xpos, ypos, xpos, ypos)
                # convention for summary is (box)
                cbox = '(%d,%d,%d,%d)' % (xpos, ypos, xpos, ypos)
                # use extend here, not append, we want individual values in a list
                sd.extend([xpos, ypos, cbox])
                caption = "Average Spectrum at %s" % cbox
                if False:
                    # this will fail on 3D cubes (see CAS-7648)
                    imval[i] = casa.imval(self.dir(fin), box=box)
                else:
                    # work around that CAS-7648 bug
                    # another approach is the ia.getprofile(), see CubeStats, this will
                    # also integrate over regions, imval will not (!!!)
                    region = 'centerbox[[%dpix,%dpix],[1pix,1pix]]' % (xpos,
                                                                       ypos)
                    caption = "Average Spectrum at %s" % region
                    imval[i] = casa.imval(self.dir(fin), region=region)
            elif type(xpos) == str:
                # this is tricky, to stay under 1 pixel , or you get a 2x2 back.
                region = 'centerbox[[%s,%s],[1pix,1pix]]' % (xpos, ypos)
                caption = "Average Spectrum at %s" % region
                sd.extend([xpos, ypos, region])
                imval[i] = casa.imval(self.dir(fin), region=region)
            else:
                print "Data type: ", type(xpos)
                raise Exception, "Data type for region not handled"
            dt.tag("imval")

            flux = imval[i]['data']
            if len(flux.shape
                   ) > 1:  # rare case if we step on a boundary between cells?
                logging.warning(
                    "source %d has spectrum shape %s: averaging the spectra" %
                    (i, repr(flux.shape)))
                flux = np.average(flux, axis=0)
            logging.debug('minmax: %f %f %d' %
                          (flux.min(), flux.max(), len(flux)))
            smax.append(flux.max())
            if i == 0:  # for first point record few extra things
                if len(imval[i]['coords'].shape) == 2:  # normal case: 1 pixel
                    freqs = imval[i]['coords'].transpose(
                    )[2] / 1e9  # convert to GHz  @todo: input units ok?
                elif len(imval[i]['coords'].shape
                         ) == 3:  # rare case if > 1 point in imval()
                    freqs = imval[i]['coords'][0].transpose(
                    )[2] / 1e9  # convert to GHz  @todo: input units ok?
                else:
                    logging.fatal(
                        "bad shape %s in freq return from imval - SHOULD NEVER HAPPEN"
                        % imval[i]['coords'].shape)
                chans = np.arange(len(freqs))  # channels 0..nchans-1
                unit = imval[i]['unit']
                restfreq = casa.imhead(
                    self.dir(fin), mode="get",
                    hdkey="restfreq")['value'] / 1e9  # in GHz
                dt.tag("imhead")
                vel = (
                    1 - freqs / restfreq
                ) * utils.c  #  @todo : use a function (and what about relativistic?)

            # construct the Table for CubeSpectrum_BDP
            # @todo note data needs to be a tuple, later to be column_stack'd
            labels = ["channel", "frequency", "flux"]
            units = ["number", "GHz", unit]
            data = (chans, freqs, flux)

            if i == 0:
                # plane 0 : we are allowing a multiplane table, so the first plane is special
                table = Table(columns=labels,
                              units=units,
                              data=np.column_stack(data),
                              planes=["0"])
            else:
                # planes 1,2,3.... are stacked onto the previous one
                table.addPlane(np.column_stack(data), "%d" % i)

            # example plot , one per position for now
            if use_vel:
                x = vel
                xlab = 'VLSR (km/s)'
            else:
                x = chans
                xlab = 'Channel'
            y = [flux]
            sd.append(xlab)
            if type(xpos) == int:
                # grab the RA/DEC... kludgy
                h = casa.imstat(self.dir(fin), box=box)
                ra = h['blcf'].split(',')[0]
                dec = h['blcf'].split(',')[1]
                title = '%s %d @ %d,%d = %s,%s' % (bdp_name, i, xpos, ypos, ra,
                                                   dec)
            else:
                title = '%s %d @ %s,%s' % (
                    bdp_name, i, xpos, ypos
                )  # or use box, once we allow non-points

            myplot = APlot(ptype=self._plot_type,
                           pmode=self._plot_mode,
                           abspath=self.dir())
            ylab = 'Flux (%s)' % unit
            p1 = "%s_%d" % (bdp_name, i)
            myplot.plotter(x,
                           y,
                           title,
                           p1,
                           xlab=xlab,
                           ylab=ylab,
                           thumbnail=True)
            # Why not use p1 as the key?
            ii = images["pos%d" % i] = myplot.getFigure(figno=myplot.figno,
                                                        relative=True)
            thumbname = myplot.getThumbnail(figno=myplot.figno, relative=True)
            sd.extend([ii, thumbname, caption, fin])
            self.spec_description.append(sd)

        logging.regression("CSP: %s" % str(smax))

        image = Image(images=images, description="CubeSpectrum")
        b2.setkey("image", image)
        b2.setkey("table", table)
        b2.setkey("sigma", csigma)  # TODO: not always available
        b2.setkey("mean", cmean)  # TODO: not always available

        if True:
            #       @todo     only first plane due to limitation in exportTable()
            islash = bdp_name.find('/')
            if islash < 0:
                tabname = self.dir("testCubeSpectrum.tab")
            else:
                tabname = self.dir(bdp_name[:islash] + "/testCubeSpectrum.tab")
            table.exportTable(tabname, cols=["frequency", "flux"])
        dt.tag("done")
        # For a single spectrum this is
        # SummaryEntry([[data for spec1]], "CubeSpectrum_AT",taskid)
        # For multiple spectra this is
        # SummaryEntry([[data for spec1],[data for spec2],...], "CubeSpectrum_AT",taskid)
        self._summary["spectra"] = SummaryEntry(self.spec_description,
                                                "CubeSpectrum_AT",
                                                self.id(True))
        taskargs = "pos=" + str(pos)
        taskargs += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span style="background-color:white">&nbsp;' + fin.split(
            '/')[0] + '&nbsp;</span>'
        for v in self._summary:
            self._summary[v].setTaskArgs(taskargs)
        dt.tag("summary")
        dt.end()
Example #6
0
    def run(self):
        """ The run method creates the BDP

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        dt = utils.Dtime("CubeSum")              # tagging time
        self._summary = {}                       # an ADMIT summary will be created here
 
        numsigma = self.getkey("numsigma")       # get the input keys
        sigma = self.getkey("sigma")
        use_lines = self.getkey("linesum")
        pad = self.getkey("pad") 

        b1  = self._bdp_in[0]                    # spw image cube
        b1a = self._bdp_in[1]                    # cubestats (optional)
        b1b = self._bdp_in[2]                    # linelist  (optional)

        f1 =  b1.getimagefile(bt.CASA)
        taskinit.ia.open(self.dir(f1))
        s = taskinit.ia.summary()
        nchan = s['shape'][2]

        if b1b != None:
            ch0 = b1b.table.getFullColumnByName("startchan")
            ch1 = b1b.table.getFullColumnByName("endchan")
            s = Segments(ch0,ch1,nchan=nchan)
            # @todo something isn't merging here as i would have expected,
            #       e.g. test0.fits [(16, 32), (16, 30), (16, 29)]
            if pad > 0:
                for (c0,c1) in s.getsegmentsastuples():
                    s.append([c0-pad,c0])
                    s.append([c1,c1+pad])
            s.merge()
            s.recalcmask()
            # print "PJT segments:",s.getsegmentsastuples()
            ns = len(s.getsegmentsastuples())
            chans = s.chans(not use_lines)
            if use_lines:
                msum = s.getmask()
            else:
                msum = 1 - s.getmask()
            logging.info("Read %d segments" % ns)
            # print "chans",chans
            # print "msum",msum

        #  from a deprecated keyword, but kept here to pre-smooth the spectrum before clipping
        #  examples are:  ['boxcar',3]    ['gaussian',7]    ['hanning',5] 
        smooth= []
                
        sig_const = False                        # figure out if sigma is taken as constant in the cube
        if b1a == None:                          # if no 2nd BDP was given, sigma needs to be specified 
            if sigma <= 0.0:
                raise Exception,"Neither user-supplied sigma nor CubeStats_BDP input given. One is required."
            else:
                sig_const = True                 # and is constant
        else:
            if sigma > 0:
                sigma = b1a.get("sigma")
                sig_const = True

        if sig_const:
            logging.info("Using constant sigma = %f" % sigma)
        else:
            logging.info("Using varying sigma per plane")

        infile = b1.getimagefile(bt.CASA)          # ADMIT filename of the image (cube)
        bdp_name = self.mkext(infile,'csm')        # morph to the new output name with replaced extension 'csm'
        image_out = self.dir(bdp_name)             # absolute filename
        
        args = {"imagename" : self.dir(infile)}    # assemble arguments for immoments()
        args["moments"] = 0                        # only need moments=0 (or [0] is ok as well)
        args["outfile"] = image_out                # note full pathname

        dt.tag("start")

        if sig_const:
            args["excludepix"] = [-numsigma*sigma, numsigma*sigma]        # single global sigma
            if b1b != None:
                # print "PJT: ",chans
                args["chans"] = chans
        else:
            # @todo    in this section bad channels can cause a fully masked cubesum = bad
            # cubestats input
            sigma_array = b1a.table.getColumnByName("sigma")              # channel dependent sigma
            sigma_pos = sigma_array[np.where(sigma_array>0)]
            smin = sigma_pos.min()
            smax = sigma_pos.max()
            logging.info("sigma varies from %f to %f" % (smin,smax))
            maxval = b1a.get("maxval")                                    # max in cube
            nzeros = len(np.where(sigma_array<=0.0)[0])                   # check bad channels
            if nzeros > 0:
                logging.warning("There are %d NaN channels " % nzeros)
                # raise Exception,"need to recode CubeSum or use constant sigma" 
            dt.tag("grab_sig")

            if len(smooth) > 0:
                # see also LineID and others
                filter = Filter1D.Filter1D(sigma_array,smooth[0],**Filter1D.Filter1D.convertargs(smooth))
                sigma_array = filter.run()
                dt.tag("smooth_sig")
            # create a CASA image copy for making the mirror sigma cube to mask against
            file = self.dir(infile)
            mask = file+"_mask"
            taskinit.ia.fromimage(infile=file, outfile=mask)
            nx = taskinit.ia.shape()[0]
            ny = taskinit.ia.shape()[1]
            nchan = taskinit.ia.shape()[2]
            taskinit.ia.fromshape(shape=[nx,ny,1])
            plane = taskinit.ia.getchunk([0,0,0],[-1,-1,0])     # convenience plane for masking operation
            dt.tag("mask_sig")

            taskinit.ia.open(mask) 
            dt.tag("open_mask")
              
            count = 0
            for i in range(nchan):
                if sigma_array[i] > 0:
                    if b1b != None:
                        if msum[i]:
                            taskinit.ia.putchunk(plane*0+sigma_array[i],blc=[0,0,i,-1])
                            count = count + 1
                        else:
                            taskinit.ia.putchunk(plane*0+maxval,blc=[0,0,i,-1])                            
                    else:
                        taskinit.ia.putchunk(plane*0+sigma_array[i],blc=[0,0,i,-1])
                        count = count + 1
                else:
                    taskinit.ia.putchunk(plane*0+maxval,blc=[0,0,i,-1])
            taskinit.ia.close()
            logging.info("%d/%d channels used for CubeSum" % (count,nchan))
            dt.tag("close_mask")

            names = [file, mask]
            tmp = file + '.tmp'
            if numsigma == 0.0:
                # hopefully this will also make use of the mask
                exp = "IM0[IM1<%f]" % (0.99*maxval)
            else:
                exp = "IM0[abs(IM0/IM1)>%f]" % (numsigma)
            # print "PJT: exp",exp
            casa.immath(mode='evalexpr', imagename=names, expr=exp, outfile=tmp) 
            args["imagename"] = tmp
            dt.tag("immath")

        casa.immoments(**args) 
        dt.tag("immoments")

        if sig_const is False:  
            # get rid of temporary files
            utils.remove(tmp)
            utils.remove(mask)

        # get the flux
        taskinit.ia.open(image_out)
        st = taskinit.ia.statistics()
        taskinit.ia.close()
        dt.tag("statistics")
        # report that flux, but there's no way to get the units from casa it seems
        # ia.summary()['unit'] is usually 'Jy/beam.km/s' for ALMA
        # imstat() does seem to know it.
        if st.has_key('flux'):
            rdata = [st['flux'][0],st['sum'][0]]
            logging.info("Total flux: %f (sum=%f)" % (st['flux'],st['sum']))
        else:
            rdata = [st['sum'][0]]
            logging.info("Sum: %f (beam parameters missing)" % (st['sum']))
        logging.regression("CSM: %s" % str(rdata))
            
        # Create two output images for html and their thumbnails, too
        implot = ImPlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())
        implot.plotter(rasterfile=bdp_name,figname=bdp_name,colorwedge=True)
        figname   = implot.getFigure(figno=implot.figno,relative=True)
        thumbname = implot.getThumbnail(figno=implot.figno,relative=True)
       
        dt.tag("implot")

        thumbtype = bt.PNG            # really should be correlated with self._plot_type!!

        # 2. Create a histogram of the map data
        # get the data for a histogram
        data = casautil.getdata(image_out,zeromask=True).compressed()
        dt.tag("getdata")

        # get the label for the x axis
        bunit = casa.imhead(imagename=image_out, mode="get", hdkey="bunit")

        # Make the histogram plot
        # Since we give abspath in the constructor, figname should be relative
        myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())
        auxname = bdp_name + "_histo"
        auxtype = bt.PNG  # really should be correlated with self._plot_type!!
        myplot.histogram(columns = data,
                         figname = auxname,
                         xlab    = bunit,
                         ylab    = "Count",
                         title   = "Histogram of CubeSum: %s" % (bdp_name),
                         thumbnail=True)
        auxname = myplot.getFigure(figno=myplot.figno,relative=True)
        auxthumb = myplot.getThumbnail(figno=myplot.figno,relative=True)

        images = {bt.CASA : bdp_name, bt.PNG : figname}
        casaimage = Image(images    = images,
                                auxiliary = auxname,
                                auxtype   = auxtype,
                                thumbnail = thumbname,
                                thumbnailtype = thumbtype)

        if hasattr(b1,"line"):                      # SpwCube doesn't have Line
            line = deepcopy(getattr(b1,"line"))
            if type(line) != type(Line):
                line = Line(name="Undetermined")
        else:
            line = Line(name="Undetermined")    # fake a Line if there wasn't one

        self.addoutput(Moment_BDP(xmlFile=bdp_name,moment=0,image=deepcopy(casaimage),line=line))
        imcaption = "Integral (moment 0) of all emission in image cube"
        auxcaption = "Histogram of cube sum for image cube"
        taskargs = "numsigma=%.1f sigma=%g smooth=%s" % (numsigma, sigma, str(smooth))
        self._summary["cubesum"] = SummaryEntry([figname,thumbname,imcaption,auxname,auxthumb,auxcaption,bdp_name,infile],"CubeSum_AT",self.id(True),taskargs)
        
        dt.tag("done")
        dt.end()
Example #7
0
    def run(self):
        """ The run method, locates lines, attempts to identify them, and
            creates the BDP

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        if not self.boxcar:
            logging.info("Boxcar smoothing turned off.")
        self._summary = {}
        self.freq = []
        self.chan = []
        dt = utils.Dtime("LineSegment")  # timer for debugging
        spec_description = []
        taskargs = self._taskargs()
        statbdp = None  # for the CubeStats BDP
        specbdp = None  # for the CubeSpectrum BDP
        specs = []  # to hold the input CubeSpectrum based spectra
        statspec = []  # to hold the input CubeStats based spectrum
        statseg = []  # to hold the detected segments from statspec
        specseg = []  # to hold the detected segments from specs
        #statcutoff = []           # cutoff for statspec line finding
        #speccutoff = []             # cutoff for specs line finding
        infile = ""
        if self.getkey("minchan") < 1:
            raise Exception("minchan must eb a positive value.")
        elif self.getkey("minchan") == 1 and self.getkey("iterate"):
            logging.info(
                "iterate=True is not allowed for minchan=1, setting iterate to False"
            )
            self.setkey("iterate", False)

        vlsr = 0.0
        # get the input bdp
        if self._bdp_in[0] is not None:
            specbdp = self._bdp_in[0]
            infile = specbdp.xmlFile
        if self._bdp_in[1] is not None:
            statbdp = self._bdp_in[1]
            infile = statbdp.xmlFile
        # still need to do this check since all are optional inputs
        if specbdp == statbdp is None:
            raise Exception("No input BDP's found.")
        imbase = self.mkext(infile, 'lseg')

        # grab any optional references overplotted on the "ll" plots

        # instantiate a plotter for all plots made herein
        self._plot_type = admit.util.PlotControl.SVG
        myplot = APlot(ptype=self._plot_type,
                       pmode=self._plot_mode,
                       abspath=self.dir())
        dt.tag("start")

        ############################################################################
        #  Smoothing and continuum (baseline) subtraction of input spectra         #
        ############################################################################

        # get and smooth all input spectra
        basicsegment = {
            "method": self.getkey("segment"),
            "minchan": self.getkey("minchan"),
            "maxgap": self.getkey("maxgap"),
            "numsigma": self.getkey("numsigma"),
            "iterate": self.getkey("iterate"),
            "nomean": True
        }

        segargsforcont = {
            "name": "Line_Segment.%i.asap" % self.id(True),
            "pmin": self.getkey("numsigma"),
            "minchan": self.getkey("minchan"),
            "maxgap": self.getkey("maxgap")
        }

        if specbdp is not None:
            # get the spectrum
            specs = specutil.getspectrum(specbdp, vlsr, self.getkey("smooth"),
                                         self.getkey("recalcnoise"),
                                         basicsegment)
            # remove the continuum, if requested
            if self.getkey("csub")[1] is not None:
                logging.info(
                    "Attempting Continuum Subtraction for Input Spectra")
                order = self.getkey("csub")[1]
                specutil.contsub(self.id(True),
                                 specs,
                                 self.getkey("segment"),
                                 segargsforcont,
                                 algorithm="PolyFit",
                                 **{"deg": order})
            else:
                for spec in specs:
                    spec.set_contin(np.zeros(len(spec)))

            for spec in specs:
                self.freq, self.chan = specutil.mergefreq(
                    self.freq, self.chan, spec.freq(False), spec.chans(False))

        # get any input cubestats
        if statbdp is not None:
            statspec = specutil.getspectrum(statbdp, vlsr,
                                            self.getkey("smooth"),
                                            self.getkey("recalcnoise"),
                                            basicsegment)
            # remove the continuum
            if self.getkey("csub")[0] is not None:
                logging.info(
                    "Attempting Continuum Subtraction for Input CubeStats Spectra"
                )
                order = self.getkey("csub")[0]
                specutil.contsub(self.id(True),
                                 statspec,
                                 self.getkey("segment"),
                                 segargsforcont,
                                 algorithm="PolyFit",
                                 **{"deg": order})

            # The 'min' spectrum is inverted for segment finding.
            # Doesn't this mean it will also be plotted upside down?
            if len(statspec) > 0: statspec[1].invert()

            for spec in statspec:
                self.freq, self.chan = specutil.mergefreq(
                    self.freq, self.chan, spec.freq(False), spec.chans(False))

        dt.tag("getspectrum")

        if isinstance(self.freq, np.ndarray):
            self.freq = self.freq.tolist()
        if isinstance(self.chan, np.ndarray):
            self.chan = self.chan.tolist()

        # search for segments of spectral line emission

        #NB: this is repetitive with basicsegment above.
        method = self.getkey("segment")
        minchan = self.getkey("minchan")
        maxgap = self.getkey("maxgap")
        numsigma = self.getkey("numsigma")
        iterate = self.getkey("iterate")

        if specbdp is not None:
            logging.info("Detecting segments in CubeSpectrum based data")
            values = specutil.findsegments(specs, method, minchan, maxgap,
                                           numsigma, iterate)
            for i, t in enumerate(values):
                specseg.append(t[0])
                specs[i].set_noise(t[2])

        if statbdp is not None:
            logging.info("Detecting segments in CubeStats based data")
            values = specutil.findsegments(statspec, method, minchan, maxgap,
                                           numsigma, iterate)
            for i, t in enumerate(values):
                statseg.append(t[0])
                # print ("MWP LINESEGMENT %d Setting noise=%f minchan=%d",(i,t[2],minchan))
                statspec[i].set_noise(t[2])
                #statcutoff.append(t[1])

        dt.tag("segment finder")
        lsbdp = LineSegment_BDP(imbase)

        finalsegs = utils.mergesegments([statseg, specseg], len(self.freq))
        lines = specutil.linedatafromsegments(self.freq, self.chan, finalsegs,
                                              specs, statspec)
        llist = []
        for l in lines:
            lsbdp.addRow(l)
            llist.append(l)

        rdata = []

        # create the output
        label = ["Peak/Noise", "Minimum/Noise"]
        caption = [
            "Potential lines overlaid on peak intensity plot from CubeStats_BDP.",
            "Potential lines overlaid on minimum intensity plot from CubeStats_BDP."
        ]

        xlabel = "Frequency (GHz)"
        for i, spec in enumerate(statspec):
            freqs = []
            for ch in statseg[i]:
                frq = [
                    min(spec.freq()[ch[0]],
                        spec.freq()[ch[1]]),
                    max(spec.freq()[ch[0]],
                        spec.freq()[ch[1]])
                ]
                freqs.append(frq)
                rdata.append(frq)
                #print("Stats segment, peak, ratio, fwhm ",lname,peak,ratio,fwhm)
            mult = 1.
            if i == 1:
                mult = -1.
#            print("MWP statspec plot cutoff[%d] = %f, contin=%f" % (i, (statspec[i].contin() + mult*(statspec[i].noise() * self.getkey("numsigma")))[0], statspec[i].contin()[0] ) )
            myplot.segplotter(
                spec.freq(),
                spec.spec(csub=False),
                title="Detected Line Segments",
                xlab=xlabel,
                ylab=label[i],
                figname=imbase + "_statspec%i" % i,
                segments=freqs,
                cutoff=(spec.contin() + mult *
                        (spec.noise() * self.getkey("numsigma"))),
                continuum=spec.contin(),
                thumbnail=True)
            imname = myplot.getFigure(figno=myplot.figno, relative=True)
            thumbnailname = myplot.getThumbnail(figno=myplot.figno,
                                                relative=True)
            image = Image(images={bt.SVG: imname},
                          thumbnail=thumbnailname,
                          thumbnailtype=bt.PNG,
                          description=caption[i])
            lsbdp.image.addimage(image, "statspec%i" % i)
            spec_description.append([
                lsbdp.ra, lsbdp.dec, "", xlabel, imname, thumbnailname,
                caption[i], infile
            ])

        for i in range(len(specs)):
            freqs = []
            for ch in specseg[i]:
                frq = [
                    min(specs[i].freq()[ch[0]], specs[i].freq()[ch[1]]),
                    max(specs[i].freq()[ch[0]], specs[i].freq()[ch[1]])
                ]
                freqs.append(frq)
                rdata.append(frq)
            myplot.segplotter(specs[i].freq(),
                              specs[i].spec(csub=False),
                              title="Detected Line Segments",
                              xlab=xlabel,
                              ylab="Intensity",
                              figname=imbase + "_spec%03d" % i,
                              segments=freqs,
                              cutoff=specs[i].contin() +
                              (specs[i].noise() * self.getkey("numsigma")),
                              continuum=specs[i].contin(),
                              thumbnail=True)
            imname = myplot.getFigure(figno=myplot.figno, relative=True)
            thumbnailname = myplot.getThumbnail(figno=myplot.figno,
                                                relative=True)
            caption = "Detected line segments from input spectrum #%i." % (i)
            image = Image(images={bt.SVG: imname},
                          thumbnail=thumbnailname,
                          thumbnailtype=bt.PNG,
                          description=caption)
            lsbdp.image.addimage(image, "spec%03d" % i)
            spec_description.append([
                lsbdp.ra, lsbdp.dec, "", xlabel, imname, thumbnailname,
                caption, infile
            ])

        caption = "Merged segments overlaid on CubeStats spectrum"

        myplot.summaryspec(statspec, specs, None, imbase + "_summary", llist)
        imname = myplot.getFigure(figno=myplot.figno, relative=True)
        thumbnailname = myplot.getThumbnail(figno=myplot.figno, relative=True)
        caption = "Identified segments overlaid on Signal/Noise plot of all spectra."

        image = Image(images={bt.SVG: imname},
                      thumbnail=thumbnailname,
                      thumbnailtype=bt.PNG,
                      description=caption)

        lsbdp.image.addimage(image, "summary")
        spec_description.append([
            lsbdp.ra, lsbdp.dec, "", "Signal/Noise", imname, thumbnailname,
            caption, infile
        ])

        self._summary["segments"] = SummaryEntry(lsbdp.table.serialize(),
                                                 "LineSegment_AT",
                                                 self.id(True), taskargs)
        self._summary["spectra"] = [
            SummaryEntry(spec_description, "LineSegment_AT", self.id(True),
                         taskargs)
        ]

        self.addoutput(lsbdp)
        logging.regression("LINESEG: %s" % str(rdata))
        dt.tag("done")
        dt.end()
Example #8
0
    def run(self):
        """ The run method, calculates the moments and creates the BDP(s)

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        self._summary = {}
        momentsummary = []
        dt = utils.Dtime("Moment")

        # variable to track if we are using a single cutoff for all moment maps
        allsame = False
        moments = self.getkey("moments")
        numsigma = self.getkey("numsigma")
        mom0clip = self.getkey("mom0clip")
        # determine if there is only 1 cutoff or if there is a cutoff for each moment
        if len(moments) != len(numsigma):
            if len(numsigma) != 1:
                raise Exception("Length of numsigma and moment lists do not match. They must be the same length or the length of the cutoff list must be 1.")
            allsame = True
        # default moment file extensions, this is information copied from casa.immoments()
        momentFileExtensions = {-1: ".average",
                                 0: ".integrated",
                                 1: ".weighted_coord",
                                 2: ".weighted_dispersion_coord",
                                 3: ".median",
                                 4: "",
                                 5: ".standard_deviation",
                                 6: ".rms",
                                 7: ".abs_mean_dev",
                                 8: ".maximum",
                                 9: ".maximum_coord",
                                10: ".minimum",
                                11: ".minimum_coord",
                                }

        logging.debug("MOMENT: %s %s %s" %  (str(moments), str(numsigma), str(allsame)))

        # get the input casa image from bdp[0]
        # also get the channels the line actually covers (if any)
        bdpin = self._bdp_in[0]
        infile = bdpin.getimagefile(bt.CASA)
        chans = self.getkey("chans")
        # the basename of the moments, we will append _0, _1, etc.
        basename = self.mkext(infile, "mom")
        fluxname = self.mkext(infile, "flux")
        # beamarea = nppb(self.dir(infile))
        beamarea = 1.0  # until we have it from the MOM0 map

        sigma0 = self.getkey("sigma")
        sigma  = sigma0

        ia = taskinit.iatool()

        dt.tag("open")

        # if no CubseStats BDP was given and no sigma was specified, find a 
        # noise level via casa.imstat()
        if self._bdp_in[1] is None and sigma <= 0.0:
            raise Exception("A sigma or a CubeStats_BDP must be input to calculate the cutoff")
        elif self._bdp_in[1] is not None:
            sigma = self._bdp_in[1].get("sigma")

        # immoments is a bit peculiar. If you give one moment, it will use 
        # exactly the outfile you picked for multiple moments, it will pick
        # extensions such as .integrated [0], .weighted_coord [1] etc.
        # we loop over the moments and will use the numeric extension instead. 
        # Might be laborious loop for big input cubes
        #
        # arguments for immoments
        args = {"imagename" : self.dir(infile),
                "moments"   : moments,
                "outfile"   : self.dir(basename)}

        # set the channels if given
        if chans != "":
            args["chans"] = chans
        # error check the mom0clip input
        if mom0clip > 0.0 and not 0 in moments:
            logging.warning("mom0clip given, but no moment0 map was requested. One will be generated anyway.")
            # add moment0 to the list of computed moments, but it has to be first
            moments.insert(0,0)
            if not allsame:
                numsigma.insert(0, 2.0*sigma)

        if allsame:
            # this is only executed now if len(moments) > 1 and len(cutoff)==1
            args["excludepix"] = [-numsigma[0] * sigma, numsigma[0] * sigma]
            casa.immoments(**args)
            dt.tag("immoments-all")
        else:
            # this is execute if len(moments)==len(cutoff) , even when len=1
            for i in range(len(moments)):
                args["excludepix"] = [-numsigma[i] * sigma, numsigma[i] * sigma]
                args["moments"] = moments[i]
                args["outfile"] = self.dir(basename + momentFileExtensions[moments[i]])
                casa.immoments(**args)
                dt.tag("immoments-%d" % moments[i])

        taskargs = "moments=%s numsigma=%s" % (str(moments), str(numsigma)) 
        if sigma0 > 0:
            taskargs = taskargs + " sigma=%.2f" % sigma0
        if mom0clip > 0:
            taskargs = taskargs + " mom0clip=%g" % mom0clip
        if chans == "": 
            taskargs = taskargs + " chans=all"
        else:
            taskargs = taskargs + " chans=%s" % str(chans)
        taskargs += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span style="background-color:white">&nbsp;' + basename.split('/')[0] + '&nbsp;</span>'

        # generate the mask to be applied to all but moment 0
        if mom0clip > 0.0:
            # get the statistics from mom0 map
            # this is usually a very biased map, so unclear if mom0sigma is all that reliable
            args = {"imagename": self.dir(infile)}
            stat = casa.imstat(imagename=self.dir(basename + momentFileExtensions[0]))
            mom0sigma = float(stat["sigma"][0])
            # generate a temporary masked file, mask will be copied to other moments
            args = {"imagename" : self.dir(basename + momentFileExtensions[0]),
                    "expr"      : 'IM0[IM0>%f]' % (mom0clip * mom0sigma),
                    "outfile"   : self.dir("mom0.masked")
                    }
            casa.immath(**args)
            # get the default mask name
            ia.open(self.dir("mom0.masked"))
            defmask = ia.maskhandler('default')
            ia.close()
            dt.tag("mom0clip")

        # loop over moments to rename them to _0, _1, _2 etc.
        # apply a mask as well for proper histogram creation
        map = {}
        myplot = APlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())
        implot = ImPlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())

        for mom in moments:
            figname = imagename = "%s_%i" % (basename, mom)
            tempname = basename + momentFileExtensions[mom]
            # rename and remove the old one if there is one
            utils.rename(self.dir(tempname), self.dir(imagename))
            # copy the moment0 mask if requested; this depends on that mom0 was done before
            if mom0clip > 0.0 and mom != 0:
                #print "PJT: output=%s:%s" % (self.dir(imagename), defmask[0])
                #print "PJT: inpmask=%s:%s" % (self.dir("mom0.masked"),defmask[0])
                makemask(mode="copy", inpimage=self.dir("mom0.masked"),
                         output="%s:%s" % (self.dir(imagename), defmask[0]),
                         overwrite=True, inpmask="%s:%s" % (self.dir("mom0.masked"),
                                                            defmask[0]))
                ia.open(self.dir(imagename))
                ia.maskhandler('set', defmask)
                ia.close()
                dt.tag("makemask")
            if mom == 0:
                beamarea = nppb(self.dir(imagename))
            implot.plotter(rasterfile=imagename,figname=figname,
                           colorwedge=True,zoom=self.getkey("zoom"))
            imagepng  = implot.getFigure(figno=implot.figno,relative=True)
            thumbname = implot.getThumbnail(figno=implot.figno,relative=True)
            images = {bt.CASA : imagename, bt.PNG  : imagepng}
            thumbtype=bt.PNG
            dt.tag("implot")

            # get the data for a histogram (ia access is about 1000-2000 faster than imval())
            map[mom] = casautil.getdata(self.dir(imagename))
            data = map[mom].compressed()
            dt.tag("getdata")

            # make the histogram plot

            # get the label for the x axis
            bunit = casa.imhead(imagename=self.dir(imagename), mode="get", hdkey="bunit")
            # object for the caption
            objectname = casa.imhead(imagename=self.dir(imagename), mode="get", hdkey="object")

            # Make the histogram plot
            # Since we give abspath in the constructor, figname should be relative
            auxname = imagename + '_histo'
            auxtype = bt.PNG
            myplot.histogram(columns = data,
                             figname = auxname,
                             xlab    = bunit,
                             ylab    = "Count",
                             title   = "Histogram of Moment %d: %s" % (mom, imagename), thumbnail=True)

            casaimage = Image(images    = images,
                                    auxiliary = auxname,
                                    auxtype   = auxtype,
                                    thumbnail = thumbname,
                                    thumbnailtype = thumbtype)
            auxname = myplot.getFigure(figno=myplot.figno,relative=True)
            auxthumb = myplot.getThumbnail(figno=myplot.figno,relative=True)

            if hasattr(self._bdp_in[0], "line"):   # SpwCube doesn't have Line
                line = deepcopy(getattr(self._bdp_in[0], "line"))
                if not isinstance(line, Line):
                    line = Line(name="Unidentified")
            else:
                # fake a Line if there wasn't one
                line = Line(name="Unidentified")
            # add the BDP to the output array
            self.addoutput(Moment_BDP(xmlFile=imagename, moment=mom,
                           image=deepcopy(casaimage), line=line))
            dt.tag("ren+mask_%d" % mom)

            imcaption = "%s Moment %d map of Source %s" % (line.name, mom, objectname)
            auxcaption = "Histogram of %s Moment %d of Source %s" % (line.name, mom, objectname)
            thismomentsummary = [line.name, mom, imagepng, thumbname, imcaption,
                                 auxname, auxthumb, auxcaption, infile]
            momentsummary.append(thismomentsummary)

        if map.has_key(0) and map.has_key(1) and map.has_key(2):
            logging.debug("MAPs present: %s" % (map.keys()))

            # m0 needs a new mask, inherited from the more restricted m1 (and m2)
            m0 = ma.masked_where(map[1].mask,map[0])
            m1 = map[1]
            m2 = map[2]
            m01 = m0*m1
            m02 = m0*m1*m1
            m22 = m0*m2*m2
            sum0 = m0.sum()
            vmean = m01.sum()/sum0
            # lacking the full 3D cube, get two estimates and take the max
            sig1  = math.sqrt(m02.sum()/sum0 - vmean*vmean)
            sig2  = m2.max()
            #vsig = max(sig1,sig2)
            vsig = sig1
            
            # consider clipping in the masked array (mom0clip)
            # @todo   i can't use info from line, so just borrow basename for now for grepping
            #         this also isn't really the flux, the points per beam is still in there
            loc = basename.rfind('/')
            sum1 = ma.masked_less(map[0],0.0).sum()   # mom0clip
            # print out:   LINE,FLUX1,FLUX0,BEAMAREA,VMEAN,VSIGMA for regression
            # the linechans parameter in bdpin is not useful to print out here, it's local to the LineCube
            s_vlsr = admit.Project.summaryData.get('vlsr')[0].getValue()[0]
            s_rest = admit.Project.summaryData.get('restfreq')[0].getValue()[0]/1e9
            s_line = line.frequency
            if loc>0:
                if basename[:loc][0:2] == 'U_':
                    # for U_ lines we'll reference the VLSR w.r.t. RESTFREQ in that band
                    if abs(vmean) > vsig:
                        vwarn = '*'
                    else:
                        vwarn = ''
                    vlsr = vmean + (1.0-s_line/s_rest)*utils.c
                    msg = "MOM0FLUX: %s %g %g %g %g %g %g" % (basename[:loc],map[0].sum(),sum0,beamarea,vmean,vlsr,vsig)
                else:
                    # for identified lines we'll assume the ID was correct and not bother with RESTFREQ
                    msg = "MOM0FLUX: %s %g %g %g %g %g %g" % (basename[:loc],map[0].sum(),sum0,beamarea,vmean,vmean,vsig)
            else:
                msg = "MOM0FLUX: %s %g %g %g %g %g %g" % ("SPW_FULL"    ,map[0].sum(),sum0,beamarea,vmean,vmean,vsig)
            logging.regression(msg)
            dt.tag("mom0flux")

            # create a histogram of flux per channel

            # grab the X coordinates for the histogram, we want them in km/s
            # restfreq should also be in summary
            restfreq = casa.imhead(self.dir(infile),mode="get",hdkey="restfreq")['value']/1e9    # in GHz
            # print "PJT  %.10f %.10f" % (restfreq,s_rest)
            imval0 = casa.imval(self.dir(infile))
            freqs = imval0['coords'].transpose()[2]/1e9
            x = (1-freqs/restfreq)*utils.c
            # 
            h = casa.imstat(self.dir(infile), axes=[0,1])
            if h.has_key('flux'):
                flux0 = h['flux']
            else:
                flux0 = h['sum']/beamarea
            flux0sum = flux0.sum() * abs(x[1]-x[0])
            # @todo   make a flux1 with fluxes derived from a good mask
            flux1 = flux0 
            # construct histogram
            title = 'Flux Spectrum (%g)' % flux0sum
            xlab = 'VLSR (km/s)'
            ylab = 'Flux (Jy)'
            myplot.plotter(x,[flux0,flux1],title=title,figname=fluxname,xlab=xlab,ylab=ylab,histo=True)
            dt.tag("flux-spectrum")
            
        self._summary["moments"] = SummaryEntry(momentsummary, "Moment_AT", 
                                                self.id(True), taskargs)
        # get rid of the temporary mask
        if mom0clip > 0.0: 
            utils.rmdir(self.dir("mom0.masked"))

        dt.tag("done")
        dt.end()
Example #9
0
    def run(self):
        """ The run method creates the BDP

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        dt = utils.Dtime("SFind2D")  # tagging time
        self._summary = {}
        # get key words that user input
        nsigma = self.getkey("numsigma")
        sigma = self.getkey("sigma")
        region = self.getkey("region")
        robust = self.getkey("robust")
        snmax = self.getkey("snmax")
        nmax = self.getkey("nmax")
        ds9 = True  # writes a "ds9.reg" file
        mpl = True  # aplot.map1() plot
        dynlog = 20.0  # above this value of dyn range finder chart is log I-scaled
        bpatch = True  # patch units to Jy/beam for ia.findsources()

        # get the input casa image from bdp[0]
        bdpin = self._bdp_in[0]
        infile = bdpin.getimagefile(bt.CASA)
        if mpl:
            data = np.flipud(np.rot90(casautil.getdata(self.dir(infile)).data))

        # check if there is a 2nd image (which will be a PB)
        for i in range(len(self._bdp_in)):
            print 'BDP', i, type(self._bdp_in[i])

        if self._bdp_in[2] != None:
            bdpin_pb = self._bdp_in[1]
            bdpin_cst = self._bdp_in[2]
            print "Need to process PB"
        else:
            bdpin_pb = None
            bdpin_cst = self._bdp_in[1]
            print "No PB given"

        # get the output bdp basename
        slbase = self.mkext(infile, 'sl')

        # make sure it's a 2D map
        if not casautil.mapdim(self.dir(infile), 2):
            raise Exception, "Input map dimension not 2: %s" % infile

        # arguments for imstat call if required
        args = {"imagename": self.dir(infile)}
        if region != "":
            args["region"] = region
        dt.tag("start")

        # The following code sets the sigma level for searching for sources using
        # the sigma and snmax keyword as appropriate
        # if no CubeStats BDP was given and no sigma was specified:
        # find a noise level via casa.imstat()
        # if a CubeStat_BDP is given get it from there.
        if bdpin_cst == None:
            # get statistics from input image with imstat because no CubeStat_BDP
            stat = casa.imstat(**args)
            dmin = float(
                stat["min"]
                [0])  # these would be wrong if robust were used already
            dmax = float(stat["max"][0])
            args.update(casautil.parse_robust(
                robust))  # only now add robust keywords for the sigma
            stat = casa.imstat(**args)
            if sigma <= 0.0:
                sigma = float(stat["sigma"][0])
            dt.tag("imstat")
        else:
            # get statistics from CubeStat_BDP
            sigma = bdpin_cst.get("sigma")
            dmin = bdpin_cst.get("minval")
            dmax = bdpin_cst.get("maxval")

        self.setkey("sigma", sigma)
        # calculate cutoff based either on RMS or dynamic range limitation
        drange = dmax / (nsigma * sigma)
        if snmax < 0.0:
            snmax = drange
        if drange > snmax:
            cutoff = 1.0 / snmax
        else:
            cutoff = 1.0 / drange
        logging.info("sigma, dmin, dmax, snmax, cutoff %g %g %g %g %g" %
                     (sigma, dmin, dmax, snmax, cutoff))
        # define arguments for call to findsources
        args2 = {"cutoff": cutoff}
        args2["nmax"] = nmax
        if region != "":
            args2["region"] = region
        #args2["mask"] = ""
        args2["point"] = False
        args2["width"] = 5
        args2["negfind"] = False
        # set-up for SourceList_BDP
        slbdp = SourceList_BDP(slbase)

        # connect to casa image and call casa ia.findsources tool
        ia = taskinit.iatool()
        ia.open(self.dir(infile))

        # findsources() cannot deal with  'Jy/beam.km/s' ???
        # so for the duration of findsources() we patch it
        bunit = ia.brightnessunit()
        if bpatch and bunit != 'Jy/beam':
            logging.warning(
                "Temporarely patching your %s units to Jy/beam for ia.findsources()"
                % bunit)
            ia.setbrightnessunit('Jy/beam')
        else:
            bpatch = False
        atab = ia.findsources(**args2)
        if bpatch:
            ia.setbrightnessunit(bunit)

        taskargs = "nsigma=%4.1f sigma=%g region=%s robust=%s snmax=%5.1f nmax=%d" % (
            nsigma, sigma, str(region), str(robust), snmax, nmax)
        dt.tag("findsources")
        nsources = atab["nelements"]
        xtab = []
        ytab = []
        logscale = False
        sumflux = 0.0
        if nsources > 0:
            # @TODO: Why are Xpix, YPix not stored in the table?
            #        -> PJT: I left them out since they are connected to an image which may not be available here
            #                but we should store the frequency of the observation here for later bandmerging
            logging.debug("%s" % str(atab['component0']['shape']))
            logging.info(
                "Right Ascen.  Declination   X(pix)   Y(pix)      Peak       Flux    Major   Minor    PA    SNR"
            )
            funits = atab['component0']['flux']['unit']
            if atab['component0']['shape'].has_key('majoraxis'):
                sunits = atab['component0']['shape']['majoraxis']['unit']
                aunits = atab['component0']['shape']['positionangle']['unit']
            else:
                sunits = "n/a"
                aunits = "n/a"
            punits = ia.summary()['unit']
            logging.info(
                "                                               %s       %s    %s   %s   %s"
                % (punits, funits, sunits, sunits, aunits))
            #
            # @todo future improvement is to look at image coordinates and control output appropriately
            #
            if ds9:
                # @todo variable name
                regname = self.mkext(infile, 'ds9.reg')
                fp9 = open(self.dir(regname), "w!")
            sn0 = -1.0
            for i in range(nsources):
                c = "component%d" % i
                name = "%d" % (i + 1)
                r = atab[c]['shape']['direction']['m0']['value']
                d = atab[c]['shape']['direction']['m1']['value']
                pixel = ia.topixel([r, d])
                xpos = pixel['numeric'][0]
                ypos = pixel['numeric'][1]
                rd = ia.toworld([xpos, ypos], 's')
                ra = rd['string'][0][:12]
                dec = rd['string'][1][:12]
                flux = atab[c]['flux']['value'][0]
                sumflux = sumflux + flux
                if atab[c]['shape'].has_key('majoraxis'):
                    smajor = atab[c]['shape']['majoraxis']['value']
                    sminor = atab[c]['shape']['minoraxis']['value']
                    sangle = atab[c]['shape']['positionangle']['value']
                else:
                    smajor = 0.0
                    sminor = 0.0
                    sangle = 0.0
                peakstr = ia.pixelvalue([xpos, ypos, 0, 0])
                if len(peakstr) == 0:
                    logging.warning("Problem with source %d @ %d,%d" %
                                    (i, xpos, ypos))
                    continue
                peakf = peakstr['value']['value']
                snr = peakf / sigma
                if snr > dynlog:
                    logscale = True
                if snr > sn0:
                    sn0 = snr
                logging.info(
                    "%s %s %8.2f %8.2f %10.3g %10.3g %7.3f %7.3f %6.1f %6.1f" %
                    (ra, dec, xpos, ypos, peakf, flux, smajor, sminor, sangle,
                     snr))

                xtab.append(xpos)
                ytab.append(ypos)
                slbdp.addRow(
                    [name, ra, dec, flux, peakf, smajor, sminor, sangle])
                if ds9:
                    ras = ra
                    des = dec.replace('.', ':', 2)
                    msg = 'ellipse(%s,%s,%g",%g",%g) # text={%s}' % (
                        ras, des, smajor, sminor, sangle + 90.0, i + 1)
                    fp9.write("%s\n" % msg)
            if ds9:
                fp9.close()
                logging.info("Wrote ds9.reg")
            dt.tag("table")
        logging.regression("CONTFLUX: %d %g" % (nsources, sumflux))

        summary = ia.summary()
        beammaj = summary['restoringbeam']['major']['value']
        beammin = summary['restoringbeam']['minor']['value']
        beamunit = summary['restoringbeam']['minor']['unit']
        beamang = summary['restoringbeam']['positionangle']['value']
        angunit = summary['restoringbeam']['positionangle']['unit']
        # @todo add to table comments?
        logging.info(" Fitted Gaussian size; NOT deconvolved source size.")
        logging.info(
            " Restoring Beam: Major axis: %10.3g %s , Minor axis: %10.3g %s , PA: %5.1f %s"
            % (beammaj, beamunit, beammin, beamunit, beamang, angunit))
        # form into a xml table

        # output is a table_bdp
        self.addoutput(slbdp)

        # instantiate a plotter for all plots made herein
        myplot = APlot(ptype=self._plot_type,
                       pmode=self._plot_mode,
                       abspath=self.dir())

        # make output png with circles marking sources found
        if mpl:
            circles = []
            nx = data.shape[1]  # data[] array was already flipud(rot90)'d
            ny = data.shape[0]  #
            for (x, y) in zip(xtab, ytab):
                circles.append([x, y, 1])
            # @todo variable name
            if logscale:
                logging.warning("LogScaling applied")
                data = data / sigma
                data = np.where(data < 0, -np.log10(1 - data),
                                +np.log10(1 + data))
            if nsources == 0:
                title = "SFind2D: 0 sources above S/N=%.1f" % (nsigma)
            elif nsources == 1:
                title = "SFind2D: 1 source (%.1f < S/N < %.1f)" % (nsigma, sn0)
            else:
                title = "SFind2D: %d sources (%.1f < S/N < %.1f)" % (
                    nsources, nsigma, sn0)
            myplot.map1(data,
                        title,
                        slbase,
                        thumbnail=True,
                        circles=circles,
                        zoom=self.getkey("zoom"))

        #---------------------------------------------------------
        # Get the figure and thumbmail names and create a caption
        #---------------------------------------------------------
        imname = myplot.getFigure(figno=myplot.figno, relative=True)
        thumbnailname = myplot.getThumbnail(figno=myplot.figno, relative=True)
        caption = "Image of input map with sources found by SFind2D overlayed in green."
        slbdp.table.description = "Table of source locations and sizes (not deconvolved)"

        #---------------------------------------------------------
        # Add finder image to the BDP
        #---------------------------------------------------------
        image = Image(images={bt.PNG: imname},
                      thumbnail=thumbnailname,
                      thumbnailtype=bt.PNG,
                      description=caption)
        slbdp.image.addimage(image, "finderimage")

        #-------------------------------------------------------------
        # Create the summary entry for the table and image
        #-------------------------------------------------------------
        self._summary["sources"] = SummaryEntry(
            [slbdp.table.serialize(),
             slbdp.image.serialize()], "SFind2D_AT", self.id(True), taskargs)

        dt.tag("done")
        dt.end()
Example #10
0
    def run(self):
        """ Main program for OverlapIntegral
        """
        dt = utils.Dtime("OverlapIntegral")
        self._summary = {}
        chans =self.getkey("chans")
        cmap = self.getkey("cmap")
        normalize = self.getkey("normalize")
        doCross = True
        doCross = False
        myplot = APlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())
        
        dt.tag("start")
 
        n = len(self._bdp_in)
        if n==0:
            raise Exception,"Need at least 1 input Image_BDP "
        logging.debug("Processing %d input maps" % n)
        data = range(n)     # array in which each element is placeholder for the data
        mdata = range(n)    # array to hold the max in each array
        summarytable = admit.util.Table()
        summarytable.columns = ["File name","Spectral Line ID"]
        summarytable.description = "Images used in Overlap Integral"
        for i in range(n):
            bdpfile = self._bdp_in[i].getimagefile(bt.CASA)
            if hasattr(self._bdp_in[i],"line"):
                line = getattr(self._bdp_in[i],"line")
                logging.info("Map %d: %s" % (i,line.uid))
                lineid = line.uid
            else:
                lineid="no line"
            data[i] = casautil.getdata(self.dir(bdpfile),chans)
            mdata[i] = data[i].max()
            logging.info("shape[%d] = %s with %d good data" % (i,data[i].shape,data[i].count()))
            if i==0:
                shape = data[i].shape
                outfile = self.mkext("testOI","oi")
            else:
                if shape != data[i].shape:
                    raise Exception,"Shapes not the same, cannot overlap them"
            # collect the file names and line identifications for the summary
            summarytable.addRow([bdpfile,lineid])
        logging.regression("OI: %s" % str(mdata))
                    
        if len(shape)>2 and shape[2] > 1:
            raise Exception,"Cannot handle 3D cubes yet"

        if doCross:
            # debug: produce all cross-corr's of the N input maps (expensive!)
            crossn(data, myplot)
            dt.tag("crossn")

        b1 = Image_BDP(outfile)
        self.addoutput(b1)
        b1.setkey("image", Image(images={bt.CASA:outfile}))

        dt.tag("open")

        useClone = True

        # to create an output dataset, clone the first input, but using the chans=ch0~ch1
        # e.g. using imsubimage(infile,outfile=,chans=
        if len(chans) > 0:
            # ia.regrid() doesn't have the chans=
            taskinit.ia.open(self.dir(self._bdp_in[0].getimagefile(bt.CASA)))
            taskinit.ia.regrid(outfile=self.dir(outfile))
            taskinit.ia.close()
        else:
            # 2D for now
            if not useClone:
                logging.info("OVERLAP out=%s" % outfile)
                taskinit.ia.fromimage(infile=self.dir(self._bdp_in[0].getimagefile(bt.CASA)),
                                      outfile=self.dir(outfile), overwrite=True)
                taskinit.ia.close()
        dt.tag("fromimage")


        if n==3:
            # RGB
            logging.info("RGB mode")
            out = rgb1(data[0],data[1],data[2], normalize)
        else:
            # simple sum
            out = data[0]
            for i in range(1,n):
                out = out + data[i]

        if useClone:
            casautil.putdata_raw(self.dir(outfile),out,clone=self.dir(self._bdp_in[0].getimagefile(bt.CASA)))
        else:
            taskinit.ia.open(self.dir(outfile))
            s1 = taskinit.ia.shape()
            s0 = [0,0,0,0]
            r1 = taskinit.rg.box(blc=s0,trc=s1)
            pixeldata = out.data
            pixelmask = ~out.mask
            taskinit.ia.putregion(pixels=pixeldata, pixelmask=pixelmask, region=r1)
            taskinit.ia.close()

        title = "OverlapIntegral"
        pdata = np.rot90(out.squeeze())
        logging.info("PDATA: %s" % str(pdata.shape))
        
        myplot.map1(pdata,title,"testOI",thumbnail=True,cmap=cmap)
        
        #-----------------------------
        # Populate summary information
        #-----------------------------
        taskargs = "chans=%s cmap=%s" % (chans, cmap)
        imname = ""
        thumbnailname = ""
        # uncomment when ready.
        imname = myplot.getFigure(figno=myplot.figno,relative=True)
        thumbnailname = myplot.getThumbnail(figno=myplot.figno,relative=True)
        #@todo fill in caption with more info - line names, etc.
        caption = "Need descriptive caption here"
        summaryinfo = [summarytable.serialize(),imname,thumbnailname,caption]
        self._summary["overlap"] = SummaryEntry(summaryinfo,
                                   "OverlapIntegral_AT",
                                   self.id(True),taskargs)
        #-----------------------------
        dt.tag("done")
        dt.end()
Example #11
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """

        self._summary = {}
        dt = utils.Dtime("CubeSpectrum")

        # our BDP's
        # b1  = input BDP
        # b1s = optional input CubeSpectrum
        # b1m = optional input Moment
        # b1p = optional input SourceList for positions
        # b2  = output BDP

        b1 = self._bdp_in[0]                                            # check input SpwCube (or LineCube)
        fin = b1.getimagefile(bt.CASA)
        if self._bdp_in[0]._type == bt.LINECUBE_BDP:
            use_vel = True
        else:
            use_vel = False

        sources = self.getkey("sources")
        pos = []                     # blank it first, then try and grab it from the optional bdp_in's
        cmean  = 0.0
        csigma = 0.0
        smax  = []                   # accumulate max in each spectrum for regression
        self.spec_description = []   # for summary() 

        if self._bdp_in[1] != None:                                      # check if CubeStats_BDP
            #print "BDP[1] type: ",self._bdp_in[1]._type
            if self._bdp_in[1]._type != bt.CUBESTATS_BDP:
                raise Exception,"bdp_in[1] not a CubeStats_BDP, should never happen"
            # a table (cubestats)
            b1s = self._bdp_in[1]
            pos.append(b1s.maxpos[0])
            pos.append(b1s.maxpos[1])
            logging.info('CubeStats::maxpos,val=%s,%f' % (str(b1s.maxpos),b1s.maxval))
            cmean  = b1s.mean
            csigma = b1s.sigma
            dt.tag("CubeStats-pos")
            
        if self._bdp_in[2] != None:                                      # check if Moment_BDP (probably from CubeSum)
            #print "BDP[2] type: ",self._bdp_in[2]._type
            if self._bdp_in[2]._type != bt.MOMENT_BDP:
                raise Exception,"bdp_in[2] not a Moment_BDP, should never happen"
            b1m = self._bdp_in[2]
            fim = b1m.getimagefile(bt.CASA)
            pos1,maxval = self.maxpos_im(self.dir(fim))     # compute maxpos, since it is not in bdp (yet)
            logging.info('CubeSum::maxpos,val=%s,%f' % (str(pos1),maxval))
            pos.append(pos1[0])
            pos.append(pos1[1])
            dt.tag("Moment-pos")

        if self._bdp_in[3] != None:                                      # check if SourceList
            #print "BDP[3] type: ",self._bdp_in[3]._type
            # a table (SourceList)
            b1p = self._bdp_in[3]
            ra   = b1p.table.getFullColumnByName("RA")
            dec  = b1p.table.getFullColumnByName("DEC")
            peak = b1p.table.getFullColumnByName("Peak")
            if sources == []:
                # use the whole SourceList
                for (r,d,p) in zip(ra,dec,peak):
                  rdc = convert_sexa(r,d)
                  pos.append(rdc[0])
                  pos.append(rdc[1])
                  logging.info('SourceList::maxpos,val=%s,%f' % (str(rdc),p))
            else:                  
                # select specific ones from the source list
                for ipos in sources:
                    if ipos < len(ra):
                        radec =  convert_sexa(ra[ipos],dec[ipos])
                        pos.append(radec[0])
                        pos.append(radec[1])
                        logging.info('SourceList::maxpos,val=%s,%f' % (str(radec),peak[ipos]))
                    else:
                        logging.warning('Skipping illegal source number %d' % ipos)

            dt.tag("SourceList-pos")

        # if pos[] still blank, use the AT keyword.
        if len(pos) == 0:
            pos = self.getkey("pos")

        # if still none, try the map center
        if len(pos) == 0:
            # @todo  this could result in a masked pixel and cause further havoc
            # @todo  could also take the reference pixel, but that could be outside image
            taskinit.ia.open(self.dir(fin))
            s = taskinit.ia.summary()
            pos = [int(s['shape'][0])/2, int(s['shape'][1])/2]
            logging.warning("No input positions supplied, map center choosen: %s" % str(pos))
            dt.tag("map-center")

        # exhausted all sources where pos[] can be set; if still zero, bail out
        if len(pos) == 0:
            raise Exception,"No positions found from input BDP's or pos="

        # convert this regular list to a list of tuples with duplicates removed
        # sadly the order is lost.
        pos = list(set(zip(pos[0::2],pos[1::2])))
        npos = len(pos)
        
        dt.tag("open")

        bdp_name = self.mkext(fin,"csp")
        b2 = CubeSpectrum_BDP(bdp_name)
        self.addoutput(b2)

        imval  = range(npos)                             # spectra, one for each pos (placeholder)
        planes = range(npos)                             # labels for the tables (placeholder)
        images = {}                                      # png's accumulated

        for i in range(npos):                            # loop over pos, they can have mixed types now
            sd = []
            caption = "Spectrum"
            xpos = pos[i][0]
            ypos = pos[i][1]
            if type(xpos) != type(ypos):
                print "POS:",xpos,ypos
                raise Exception,"position pair not of the same type"
            if type(xpos)==int:
                # for integers, boxes are allowed, even multiple
                box = '%d,%d,%d,%d' % (xpos,ypos,xpos,ypos)
                # convention for summary is (box)
                cbox = '(%d,%d,%d,%d)' % (xpos,ypos,xpos,ypos)
                # use extend here, not append, we want individual values in a list
                sd.extend([xpos,ypos,cbox])
                caption = "Average Spectrum at %s" % cbox
                if False:
                    # this will fail on 3D cubes (see CAS-7648)
                    imval[i] = casa.imval(self.dir(fin),box=box)
                else:
                    # work around that CAS-7648 bug 
                    # another approach is the ia.getprofile(), see CubeStats, this will
                    # also integrate over regions, imval will not (!!!)
                    region = 'centerbox[[%dpix,%dpix],[1pix,1pix]]' % (xpos,ypos)
                    caption = "Average Spectrum at %s" % region
                    imval[i] = casa.imval(self.dir(fin),region=region)
            elif type(xpos)==str:
                # this is tricky, to stay under 1 pixel , or you get a 2x2 back.
                region = 'centerbox[[%s,%s],[1pix,1pix]]' % (xpos,ypos)
                caption = "Average Spectrum at %s" % region
                sd.extend([xpos,ypos,region])
                imval[i] = casa.imval(self.dir(fin),region=region)
            else:
                print "Data type: ",type(xpos)
                raise Exception,"Data type for region not handled"
            dt.tag("imval")

            flux  = imval[i]['data']
            if len(flux.shape) > 1:     # rare case if we step on a boundary between cells?
                logging.warning("source %d has spectrum shape %s: averaging the spectra" % (i,repr(flux.shape)))
                flux = np.average(flux,axis=0)
            logging.debug('minmax: %f %f %d' % (flux.min(),flux.max(),len(flux)))
            smax.append(flux.max())
            if i==0:                                              # for first point record few extra things
                if len(imval[i]['coords'].shape) == 2:                   # normal case: 1 pixel
                    freqs = imval[i]['coords'].transpose()[2]/1e9        # convert to GHz  @todo: input units ok?
                elif len(imval[i]['coords'].shape) == 3:                 # rare case if > 1 point in imval()
                    freqs = imval[i]['coords'][0].transpose()[2]/1e9     # convert to GHz  @todo: input units ok?
                else:
                    logging.fatal("bad shape %s in freq return from imval - SHOULD NEVER HAPPEN" % imval[i]['coords'].shape)
                chans = np.arange(len(freqs))                     # channels 0..nchans-1
                unit  = imval[i]['unit']
                restfreq = casa.imhead(self.dir(fin),mode="get",hdkey="restfreq")['value']/1e9    # in GHz
                dt.tag("imhead")
                vel   = (1-freqs/restfreq)*utils.c                #  @todo : use a function (and what about relativistic?)

            # construct the Table for CubeSpectrum_BDP 
            # @todo note data needs to be a tuple, later to be column_stack'd
            labels = ["channel" ,"frequency" ,"flux" ]
            units  = ["number"  ,"GHz"       ,unit   ]
            data   = (chans     ,freqs       ,flux   )

            if i==0:
                # plane 0 : we are allowing a multiplane table, so the first plane is special
                table = Table(columns=labels,units=units,data=np.column_stack(data),planes=["0"])
            else:
                # planes 1,2,3.... are stacked onto the previous one
                table.addPlane(np.column_stack(data),"%d" % i)

            # example plot , one per position for now
            if use_vel:
                x = vel
                xlab = 'VLSR (km/s)'
            else:
                x = chans
                xlab  = 'Channel'
            y = [flux]
            sd.append(xlab)
            if type(xpos)==int:
                # grab the RA/DEC... kludgy
                h = casa.imstat(self.dir(fin),box=box)
                ra  = h['blcf'].split(',')[0]
                dec = h['blcf'].split(',')[1]
                title = '%s %d @ %d,%d = %s,%s' % (bdp_name,i,xpos,ypos,ra,dec)
            else:
                title = '%s %d @ %s,%s' % (bdp_name,i,xpos,ypos)       # or use box, once we allow non-points

            myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode, abspath=self.dir())
            ylab  = 'Flux (%s)' % unit
            p1 = "%s_%d" % (bdp_name,i)
            myplot.plotter(x,y,title,p1,xlab=xlab,ylab=ylab,thumbnail=True)
            # Why not use p1 as the key?
            ii = images["pos%d" % i] = myplot.getFigure(figno=myplot.figno,relative=True)
            thumbname = myplot.getThumbnail(figno=myplot.figno,relative=True)
            sd.extend([ii, thumbname, caption, fin])
            self.spec_description.append(sd)

        logging.regression("CSP: %s" % str(smax))

        image = Image(images=images, description="CubeSpectrum")
        b2.setkey("image",image)
        b2.setkey("table",table)
        b2.setkey("sigma",csigma)   # TODO: not always available
        b2.setkey("mean",cmean)     # TODO: not always available

        if True:
            #       @todo     only first plane due to limitation in exportTable()
            islash = bdp_name.find('/')
            if islash < 0:
                tabname = self.dir("testCubeSpectrum.tab")
            else:
                tabname = self.dir(bdp_name[:islash] + "/testCubeSpectrum.tab")
            table.exportTable(tabname,cols=["frequency" ,"flux"])
        dt.tag("done")
        # For a single spectrum this is
        # SummaryEntry([[data for spec1]], "CubeSpectrum_AT",taskid)
        # For multiple spectra this is
        # SummaryEntry([[data for spec1],[data for spec2],...], "CubeSpectrum_AT",taskid)
        self._summary["spectra"] = SummaryEntry(self.spec_description,"CubeSpectrum_AT",self.id(True))
        taskargs = "pos="+str(pos)
        taskargs += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span style="background-color:white">&nbsp;' + fin.split('/')[0] + '&nbsp;</span>'
        for v in self._summary:
            self._summary[v].setTaskArgs(taskargs)
        dt.tag("summary")
        dt.end()
Example #12
0
    def run(self):
        """Runs the task.

           Parameters
           ----------
           None

           Returns
           -------
           None
        """

        self._summary = {}
        dt = utils.Dtime("CubeSpectrum")
        seed = self.getkey("seed")
        if seed <= 0:
            np.random.seed()
        else:
            np.random.seed(seed)
        #print "RANDOM.GET_STATE:",np.random.get_state()
        contin = self.getkey("contin")
        rms = 1.0        # not a user parameter, we do all spectra in S/N space
        f0 = self.getkey("freq")     # central frequency in band
        df = self.getkey("delta") / 1000.0      # channel width (in GHz)
        nspectra = self.getkey("nspectra")
        taskargs = " contin=%f freq=%f delta=%f nspectra=%f " % (contin,f0,df,nspectra)
        spec = range(nspectra)
        dt.tag("start")
        if self.getkey("file") != "":
            print "READING spectrum from",self.getkey("file") 
            (freq, spec[0]) = getspec(self.getkey("file"))
            nchan = len(freq)
            print "Spectrum %d chans from %f to %f: min/max = %f %f" % (nchan, freq.min(), freq.max(), spec[0].min(), spec[0].max())
            # @todo nspectra>1 not tested
            for i in range(1,nspectra):
                spec[i] = deepcopy(spec[0])
            dt.tag("getspec")                
        else:
            nchan = self.getkey("nchan")
            freq = np.arange(nchan, dtype=np.float64)
            center = int(nchan/2)
            for i in range(nchan):
                freq[i] = f0 + (float((i - center)) * df)
            for i in range(nspectra):
                spec[i] = np.zeros(nchan)
        chans = np.arange(nchan)
        taskargs += " nchan = %d" % nchan
        for i in range(nspectra):
            if seed >= 0:
                spec[i] += np.random.normal(contin, rms, nchan)
#            print "MEAN/STD",spec[i].mean(),spec[i].std()
        lines = self.getkey("lines")
        sls = SpectralLineSearch(False)
        for item in self.getkey("transitions"):
            kw = {"include_only_nrao" : True,
                  "line_strengths": ["ls1", "ls2"],
                  "energy_levels" : ["el2", "el4"],
                  "fel" : True,
                  "species" : item[0]
                  }
            results = sls.search(item[1][0], item[1][1], "off", **kw)
            # look at line strengths
            if len(results) > 0:
                mx = 0.0
                indx = -1
                for i in range(len(results)):
                    if results[i].getkey("linestrength") > mx:
                        indx = i
                        mx = results[i].getkey("linestrength")
                for res in results:
                    if mx > 0.0:
                        lines.append([item[2] * res.getkey("linestrength") / mx, res.getkey("frequency") +
                                      utils.veltofreq(item[4], res.getkey("frequency")), item[3]])
                    else:
                        lines.append([item[2], res.getkey("frequency") + utils.veltofreq(item[4], 
                                      res.getkey("frequency")), item[3]])
        for item in lines:
            for i in range(nspectra):
                spec[i] += utils.gaussian1D(freq, item[0], item[1], utils.veltofreq(item[2], item[1]))

        if self.getkey("hanning"):
            for i in range(nspectra):
                filter = Filter1D.Filter1D(spec[i], "hanning", **{"width" : 3})
                spec[i] = filter.run()
            dt.tag("hanning")
        center = int(nchan/2)
        dt.tag("open")
        bdp_name = self.mkext("Genspec","csp")
        b2 = CubeSpectrum_BDP(bdp_name)
        self.addoutput(b2)
        images = {}                                      # png's accumulated
        for i in range(nspectra):
            sd = []
            caption = "Generated Spectrum %d" % i
            # construct the Table for CubeSpectrum_BDP 
            # @todo note data needs to be a tuple, later to be column_stack'd
            labels = ["channel" ,"frequency" ,"flux" ]
            units  = ["number"  ,"GHz"       ,""   ]
            data   = (chans     ,freq       ,spec[i]   )

            # plane 0 : we are allowing a multiplane table, so the first plane is special
            if i==0:
                table = Table(columns=labels,units=units,data=np.column_stack(data),planes=["0"])
            else:
                table.addPlane(np.column_stack(data),"%d" % i)
            # example plot , one per position for now
            x = chans
            xlab  = 'Channel'
            y = [spec[i]]
            sd.append(xlab)

            myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode, abspath=self.dir())
            ylab  = 'Flux'
            p1 = "%s_%d" % (bdp_name,i)
            myplot.plotter(x,y,"",p1,xlab=xlab,ylab=ylab,thumbnail=True)
            # Why not use p1 as the key?
            ii = images["pos%d" % i] = myplot.getFigure(figno=myplot.figno,relative=True)
            thumbname = myplot.getThumbnail(figno=myplot.figno,relative=True)

            image = Image(images=images, description="CubeSpectrum")
            sd.extend([ii, thumbname, caption])
            self.spec_description.append(sd)

        self._summary["spectra"] = SummaryEntry(self.spec_description,"GenerateSpectrum_AT",self.id(True), taskargs)
        

        dt.tag("table")
        b2.setkey("image",image)
        b2.setkey("table",table)
        b2.setkey("sigma",rms)
        b2.setkey("mean",contin)

        dt.tag("done")
        dt.end()
Example #13
0
class PVCorr_AT(AT):
    """PV correllation in a PVSlice map.

       PVCorr_AT computes a cross-correlation of a feature in a PVSlice with the 
       whole PVSlice, looking for repeated patterns to detect spectral lines. Much
       like the output from CubeStats_AT and CubeSpectrum_AT, this table can then be
       given to LineID_AT to attempt a line identification.

       See also :ref:`PVCorr-AT-Design` for the design document.

       **Keywords**

          **numsigma**: float 
            Minimum intensity, in terms of sigma, above which a selected portion
            of the spectrum will be used for cross-correlation.
            Default: 3.0.

          **range**: integer list
            If given, it has to be a list with 2 channel numbers, the first and last channel
            (0-based channels) of the range which to use for the cross-correlation.
            Default: [].

          **nchan**: integer
            The number of channels (in case range= was not used) that defines the line.
            The line is centers on the strongest point in the input PV-map.
            If 0 is given, it will watershed down from the strongest line.
            Default: 0.

       **Input BDPs**

          **PVSlice_BDP**: count: 1
            Input PV Slice. As created with e.g. PVSlice_AT.

          **CubeStats_BDP**: count: 1
            Input cube statistics from which the RMS is taken.

       **Output BDPs**

          **PVCorr_BDP**: count: 1
            Output table.
       

    """

    def __init__(self,**keyval):
        keys = {"numsigma" : 3.0,    # N-sigma
                "range"    : [],     # optional channel range
                "nchan"    : 0,      # number of channels around the channel where the peak is
               }
        AT.__init__(self,keys,keyval)
        self._version = "1.0.1"
        self.set_bdp_in([(Image_BDP,1,bt.REQUIRED),
                         # @todo optional 2nd PVSlice can be used to draw the template from
                         (CubeStats_BDP,1,bt.REQUIRED)])
        self.set_bdp_out([(PVCorr_BDP,1)])

    def summary(self):
        """Returns the summary dictionary from the AT, for merging
           into the ADMIT Summary object.

           PVCorr_AT adds the following to ADMIT summary:

           .. table::
              :class: borderless

              +----------+----------+-----------------------------------+
              |   Key    | type     |    Description                    |
              +==========+==========+===================================+
              | pvcorr   | list     |   correlation diagram             |
              +----------+----------+-----------------------------------+
           
           Parameters
           ----------
           None

           Returns
           -------
           dict
               Dictionary of SummaryEntry
        """
        if hasattr(self,"_summary"):
            return self._summary
        else:
            return {}

    def run(self):
        dt = utils.Dtime("PVCorr")
        self._summary = {}

        numsigma = self.getkey("numsigma")
        mode = 1                                            # PV corr mode (1,2,3)
        normalize = True
        # normalize = False

        b1 = self._bdp_in[0]                                # PVSlice_BDP
        fin = b1.getimagefile(bt.CASA)                      # CASA image
        data = casautil.getdata_raw(self.dir(fin))          # grab the data as a numpy array
        self.myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())
        #print 'DATA[0,0]:',data[0,0]
        #print 'pv shape: ',data.shape
        npos = data.shape[0]
        nvel = data.shape[1]
        dt.tag("getdata")

        b2 = self._bdp_in[1]                                # CubeStats_BDP
        sigma = b2.sigma                                    # global sigma in the cube
        cutoff = numsigma * sigma
        freq =  b2.table.getColumnByName("frequency")

        chans = self.getkey("range")                        # range of channels, if used
        if len(chans) > 0:
            if len(chans) != 2:
                logging.fatal("range=%s" % chans)
                raise Exception,"range= needs two values, left and right (inclusive) channel"
            ch0 = chans[0]
            ch1 = chans[1]
        else:
            nchan = self.getkey("nchan")
            imstat0 = casa.imstat(self.dir(fin))         # @todo   can use data[] now
            xmaxpos = imstat0['maxpos'][0]
            ymaxpos = imstat0['maxpos'][1]
            logging.info("MAXPOS-VEL %s %g" % (str(imstat0['maxpos']),imstat0['max'][0]))
            if nchan > 0:
                # expand around it, later ch0,ch1 will be checked for running off the edge
                ch0 = ymaxpos - nchan/2
                ch1 = ymaxpos + nchan/2
            else:
                # watershed down to find ch0 and ch1 ?
                # this doesn't work well in crowded areas
                ch0 = ymaxpos
                ch1 = ymaxpos
                spmax = data.max(axis=0)
                k = spmax.argmax()
                n = len(spmax)
                logging.debug('spmax %s %d %g' % (str(spmax.shape),k,spmax[k]))
                # find lower cutoff
                for i in range(n):
                    ch0 = ymaxpos - i
                    if ch0<0: break
                    if spmax[ch0] < cutoff: break
                ch0 = ch0 + 1
                # find higher cutoff
                for i in range(n):
                    ch1 = ymaxpos + i
                    if ch1==n: break
                    if spmax[ch1] < cutoff: break
                ch1 = ch1 - 1
            dt.tag("imstat")

        
        bdp_name = self.mkext(fin,"pvc")                    # output PVCorr_BDP
        b3 = PVCorr_BDP(bdp_name)
        self.addoutput(b3)

        if ch0<0 or ch1>=nvel:
            # this probably only happens to small cubes (problematic for PVCorr)
            # or when the strongest line is really close to the edge of the band
            # (which is probably ok)
            if ch0<0 and ch1>=nvel:
                logging.warning("Serious issues with the size of this cube")
            if ch0<0: 
                logging.warning("Resetting ch0 edge to 0")
                ch0=0
            if ch1>=nvel: 
                ch1=nvel-1
                logging.warning("Resetting ch1 edge to the maximum")

        if ch0 > ch1:
            logging.warning("Sanity swapping ch0,1 due to likely noisy data")
            ch0,ch1 = ch1,ch0

        if mode == 1:
            out,rms = mode1(data, ch0, ch1, cutoff, normalize)
            corr = out
        elif mode == 2:
            out,rms = mode2(data, ch0, ch1, cutoff)             # slower 2D version
            corr = out[npos/2,:]                                # center cut, but could also try feature detection
        elif mode == 3:
            out,rms = self.mode3(data, ch0, ch1, cutoff)        # Doug's faster 2D version
            # get the peak of each column
            corr = np.amax(out,axis=0)
        # print "PVCORR SHAPE ",corr.shape," mode", mode
        if len(corr) > 0:
            # print "SHAPE out:",out.shape,corr.shape,npos/2
            ch  = range(len(corr))
            if len(corr) != len(freq):
                logging.fatal("ch (%d) and freq (%d) do not have same size" % (len(corr),len(freq)))
                raise Exception,"ch and freq do not have same dimension"
            dt.tag("mode")
            labels = ["channel",   "frequency",  "pvcorr"]
            units  = ["number",    "GHz",        "N/A"]
            data   = (ch,          freq,         corr)
            table = Table(columns=labels,units=units,data=np.column_stack(data))
        else:
            # still construct a table, but with no rows
            labels = ["channel",   "frequency",  "pvcorr"]
            units  = ["number",    "GHz",        "N/A"]
            table = Table(columns=labels,units=units)
        b3.setkey("table",table)
        b3.setkey("sigma",float(rms))
        dt.tag("table")
        if len(corr) > 0:
            table.exportTable(self.dir("testPVCorr.tab"),cols=['frequency','pvcorr'])
            test_single(ch,freq,corr)

            logging.regression("PVC: %f %f" % (corr.min(),corr.max()))

            title = 'PVCorr mode=%d [%d,%d] %g' % (mode,ch0,ch1,cutoff)
            x = ch
            xlab = 'Channel'
            y = [corr]
            ylab = 'PV Correlation'
            p1 = "%s_%d" % (bdp_name,0)
            segp = []
            segp.append( [0,len(ch),0.0,0.0] )
            segp.append( [0,len(ch),3.0*rms, 3.0*rms] )
            # @todo:   in principle we know with given noise and  size of box, what the sigma in pvcorr should be
            self.myplot.plotter(x,y,title,figname=p1,xlab=xlab,ylab=ylab,segments=segp, thumbnail=True)

            #out1 = np.rot90 (data.reshape((nvel,npos)) )
            if mode > 1:
                self.myplot.map1(data=out,title="testing PVCorr_AT:  mode%d"%mode,figname='testPVCorr', thumbnail=True)

            taskargs = "numsigma=%.1f range=[%d,%d]" % (numsigma,ch0,ch1)
            caption = "Position-velocity correlation plot"
            thumbname = self.myplot.getThumbnail(figno=self.myplot.figno,relative=True)
            figname   = self.myplot.getFigure(figno=self.myplot.figno,relative=True)
            image = Image(images={bt.PNG: figname}, thumbnail=thumbname, thumbnailtype=bt.PNG,
                description=caption)
            b3.image.addimage(image, "pvcorr")

            self._summary["pvcorr"] = SummaryEntry([figname,thumbname,caption,fin],"PVCorr_AT",self.id(True),taskargs)
        else:
            self._summary["pvcorr"] = None
            logging.warning("No summary")
            logging.regression("PVC: -1")

        dt.tag("done")
        dt.end()

    def mode3(self, data, v0, v1, dmin=0.0):
        """ v0..v1 (both inclusive) are channel selections
            threshold on dmin
            @todo the frequency axis is not properly calibrated here
            @todo a full 2D is slow, we only need the 1D version
        """
        print "PVCorr mode3: v0,1=",v0,v1
        smin = data.min()
        #s = data[v0:v1+1,:]
        s = data[:,v0:v1+1]
        if dmin==0.0:
            logging.warning("Using all data in crosscorr")
            f = s
        else:
            f = np.where(s>dmin,s,0)
        # find out where the zeros are
        temp = np.amax(f,axis=1)
        nz = np.nonzero(temp)
        # trim the kernel in the y direction, removing rows that are all 0.0
        f = f[nz[0][0]:nz[0][-1],:]
        f0 = np.where(s>smin,1,0)
        f1 = np.where(s>dmin,1,0)
        fmax = f.max()
        print "PVCorr mode3:",f1.sum(),'/',f0.sum(),'min/max',smin,fmax
        out =  scipy.signal.correlate2d(data,f,mode='same')
        self.myplot.map1(data=f,title="PVCorr 2D Kernel",figname='PVCorrKernel', thumbnail=True)

        print 'PVCorr min/max:',out.min(),out.max()
        n1,m1,s1,n2,m2,s2 = stats.mystats(out.flatten())
        print "PVCorr stats", n1,m1,s1,n2,m2,s2
        rms_est = s2/np.sqrt(f1.sum())
        return out,rms_est
Example #14
0
    def run(self):
        """ The run method creates the BDP

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        dt = utils.Dtime("CubeSum")  # tagging time
        self._summary = {}  # an ADMIT summary will be created here

        numsigma = self.getkey("numsigma")  # get the input keys
        sigma = self.getkey("sigma")
        use_lines = self.getkey("linesum")
        pad = self.getkey("pad")

        b1 = self._bdp_in[0]  # spw image cube
        b1a = self._bdp_in[1]  # cubestats (optional)
        b1b = self._bdp_in[2]  # linelist  (optional)

        f1 = b1.getimagefile(bt.CASA)
        taskinit.ia.open(self.dir(f1))
        s = taskinit.ia.summary()
        nchan = s['shape'][2]

        if b1b != None:
            ch0 = b1b.table.getFullColumnByName("startchan")
            ch1 = b1b.table.getFullColumnByName("endchan")
            s = Segments(ch0, ch1, nchan=nchan)
            # @todo something isn't merging here as i would have expected,
            #       e.g. test0.fits [(16, 32), (16, 30), (16, 29)]
            if pad > 0:
                for (c0, c1) in s.getsegmentsastuples():
                    s.append([c0 - pad, c0])
                    s.append([c1, c1 + pad])
            s.merge()
            s.recalcmask()
            # print "PJT segments:",s.getsegmentsastuples()
            ns = len(s.getsegmentsastuples())
            chans = s.chans(not use_lines)
            if use_lines:
                msum = s.getmask()
            else:
                msum = 1 - s.getmask()
            logging.info("Read %d segments" % ns)
            # print "chans",chans
            # print "msum",msum

        #  from a deprecated keyword, but kept here to pre-smooth the spectrum before clipping
        #  examples are:  ['boxcar',3]    ['gaussian',7]    ['hanning',5]
        smooth = []

        sig_const = False  # figure out if sigma is taken as constant in the cube
        if b1a == None:  # if no 2nd BDP was given, sigma needs to be specified
            if sigma <= 0.0:
                raise Exception, "Neither user-supplied sigma nor CubeStats_BDP input given. One is required."
            else:
                sig_const = True  # and is constant
        else:
            if sigma > 0:
                sigma = b1a.get("sigma")
                sig_const = True

        if sig_const:
            logging.info("Using constant sigma = %f" % sigma)
        else:
            logging.info("Using varying sigma per plane")

        infile = b1.getimagefile(bt.CASA)  # ADMIT filename of the image (cube)
        bdp_name = self.mkext(
            infile, 'csm'
        )  # morph to the new output name with replaced extension 'csm'
        image_out = self.dir(bdp_name)  # absolute filename

        args = {
            "imagename": self.dir(infile)
        }  # assemble arguments for immoments()
        args["moments"] = 0  # only need moments=0 (or [0] is ok as well)
        args["outfile"] = image_out  # note full pathname

        dt.tag("start")

        if sig_const:
            args["excludepix"] = [-numsigma * sigma,
                                  numsigma * sigma]  # single global sigma
            if b1b != None:
                # print "PJT: ",chans
                args["chans"] = chans
        else:
            # @todo    in this section bad channels can cause a fully masked cubesum = bad
            # cubestats input
            sigma_array = b1a.table.getColumnByName(
                "sigma")  # channel dependent sigma
            sigma_pos = sigma_array[np.where(sigma_array > 0)]
            smin = sigma_pos.min()
            smax = sigma_pos.max()
            logging.info("sigma varies from %f to %f" % (smin, smax))
            maxval = b1a.get("maxval")  # max in cube
            nzeros = len(np.where(sigma_array <= 0.0)[0])  # check bad channels
            if nzeros > 0:
                logging.warning("There are %d NaN channels " % nzeros)
                # raise Exception,"need to recode CubeSum or use constant sigma"
            dt.tag("grab_sig")

            if len(smooth) > 0:
                # see also LineID and others
                filter = Filter1D.Filter1D(
                    sigma_array, smooth[0],
                    **Filter1D.Filter1D.convertargs(smooth))
                sigma_array = filter.run()
                dt.tag("smooth_sig")
            # create a CASA image copy for making the mirror sigma cube to mask against
            file = self.dir(infile)
            mask = file + "_mask"
            taskinit.ia.fromimage(infile=file, outfile=mask)
            nx = taskinit.ia.shape()[0]
            ny = taskinit.ia.shape()[1]
            nchan = taskinit.ia.shape()[2]
            taskinit.ia.fromshape(shape=[nx, ny, 1])
            plane = taskinit.ia.getchunk(
                [0, 0, 0],
                [-1, -1, 0])  # convenience plane for masking operation
            dt.tag("mask_sig")

            taskinit.ia.open(mask)
            dt.tag("open_mask")

            count = 0
            for i in range(nchan):
                if sigma_array[i] > 0:
                    if b1b != None:
                        if msum[i]:
                            taskinit.ia.putchunk(plane * 0 + sigma_array[i],
                                                 blc=[0, 0, i, -1])
                            count = count + 1
                        else:
                            taskinit.ia.putchunk(plane * 0 + maxval,
                                                 blc=[0, 0, i, -1])
                    else:
                        taskinit.ia.putchunk(plane * 0 + sigma_array[i],
                                             blc=[0, 0, i, -1])
                        count = count + 1
                else:
                    taskinit.ia.putchunk(plane * 0 + maxval, blc=[0, 0, i, -1])
            taskinit.ia.close()
            logging.info("%d/%d channels used for CubeSum" % (count, nchan))
            dt.tag("close_mask")

            names = [file, mask]
            tmp = file + '.tmp'
            if numsigma == 0.0:
                # hopefully this will also make use of the mask
                exp = "IM0[IM1<%f]" % (0.99 * maxval)
            else:
                exp = "IM0[abs(IM0/IM1)>%f]" % (numsigma)
            # print "PJT: exp",exp
            casa.immath(mode='evalexpr',
                        imagename=names,
                        expr=exp,
                        outfile=tmp)
            args["imagename"] = tmp
            dt.tag("immath")

        casa.immoments(**args)
        dt.tag("immoments")

        if sig_const is False:
            # get rid of temporary files
            utils.remove(tmp)
            utils.remove(mask)

        # get the flux
        taskinit.ia.open(image_out)
        st = taskinit.ia.statistics()
        taskinit.ia.close()
        dt.tag("statistics")
        # report that flux, but there's no way to get the units from casa it seems
        # ia.summary()['unit'] is usually 'Jy/beam.km/s' for ALMA
        # imstat() does seem to know it.
        if st.has_key('flux'):
            rdata = [st['flux'][0], st['sum'][0]]
            logging.info("Total flux: %f (sum=%f)" % (st['flux'], st['sum']))
        else:
            rdata = [st['sum'][0]]
            logging.info("Sum: %f (beam parameters missing)" % (st['sum']))
        logging.regression("CSM: %s" % str(rdata))

        # Create two output images for html and their thumbnails, too
        implot = ImPlot(ptype=self._plot_type,
                        pmode=self._plot_mode,
                        abspath=self.dir())
        implot.plotter(rasterfile=bdp_name, figname=bdp_name, colorwedge=True)
        figname = implot.getFigure(figno=implot.figno, relative=True)
        thumbname = implot.getThumbnail(figno=implot.figno, relative=True)

        dt.tag("implot")

        thumbtype = bt.PNG  # really should be correlated with self._plot_type!!

        # 2. Create a histogram of the map data
        # get the data for a histogram
        data = casautil.getdata(image_out, zeromask=True).compressed()
        dt.tag("getdata")

        # get the label for the x axis
        bunit = casa.imhead(imagename=image_out, mode="get", hdkey="bunit")

        # Make the histogram plot
        # Since we give abspath in the constructor, figname should be relative
        myplot = APlot(ptype=self._plot_type,
                       pmode=self._plot_mode,
                       abspath=self.dir())
        auxname = bdp_name + "_histo"
        auxtype = bt.PNG  # really should be correlated with self._plot_type!!
        myplot.histogram(columns=data,
                         figname=auxname,
                         xlab=bunit,
                         ylab="Count",
                         title="Histogram of CubeSum: %s" % (bdp_name),
                         thumbnail=True)
        auxname = myplot.getFigure(figno=myplot.figno, relative=True)
        auxthumb = myplot.getThumbnail(figno=myplot.figno, relative=True)

        images = {bt.CASA: bdp_name, bt.PNG: figname}
        casaimage = Image(images=images,
                          auxiliary=auxname,
                          auxtype=auxtype,
                          thumbnail=thumbname,
                          thumbnailtype=thumbtype)

        if hasattr(b1, "line"):  # SpwCube doesn't have Line
            line = deepcopy(getattr(b1, "line"))
            if type(line) != type(Line):
                line = Line(name="Undetermined")
        else:
            line = Line(name="Undetermined")  # fake a Line if there wasn't one

        self.addoutput(
            Moment_BDP(xmlFile=bdp_name,
                       moment=0,
                       image=deepcopy(casaimage),
                       line=line))
        imcaption = "Integral (moment 0) of all emission in image cube"
        auxcaption = "Histogram of cube sum for image cube"
        taskargs = "numsigma=%.1f sigma=%g smooth=%s" % (numsigma, sigma,
                                                         str(smooth))
        self._summary["cubesum"] = SummaryEntry([
            figname, thumbname, imcaption, auxname, auxthumb, auxcaption,
            bdp_name, infile
        ], "CubeSum_AT", self.id(True), taskargs)

        dt.tag("done")
        dt.end()
Example #15
0
    def run(self):
        """ The run method creates the BDP

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        dt = utils.Dtime("SFind2D")               # tagging time
        self._summary = {}
        # get key words that user input
        nsigma = self.getkey("numsigma")
        sigma  = self.getkey("sigma")
        region = self.getkey("region")
        robust = self.getkey("robust")
        snmax  = self.getkey("snmax")
        ds9 = True                                     # writes a "ds9.reg" file
        mpl = True                                     # aplot.map1() plot
        dynlog = 20.0                                  # above this value of dyn range finder chart is log I-scaled
        bpatch = True                                  # patch units to Jy/beam for ia.findsources()
        
        # get the input casa image from bdp[0]
        bdpin = self._bdp_in[0]
        infile = bdpin.getimagefile(bt.CASA)
        if mpl:
            data = np.flipud(np.rot90(casautil.getdata(self.dir(infile)).data))

        # check if there is a 2nd image (which will be a PB)
        for i in range(len(self._bdp_in)):
            print 'BDP',i,type(self._bdp_in[i])

        if self._bdp_in[2] != None:
            bdpin_pb  = self._bdp_in[1]            
            bdpin_cst = self._bdp_in[2]
            print "Need to process PB"
        else:
            bdpin_pb  = None
            bdpin_cst = self._bdp_in[1]
            print "No PB given"
            

        # get the output bdp basename
        slbase = self.mkext(infile,'sl')

        # make sure it's a 2D map
        if not casautil.mapdim(self.dir(infile),2):
            raise Exception,"Input map dimension not 2: %s" % infile

        # arguments for imstat call if required
        args = {"imagename" : self.dir(infile)}
        if region != "":
            args["region"] = region
        dt.tag("start")

        # The following code sets the sigma level for searching for sources using
        # the sigma and snmax keyword as appropriate
        # if no CubeStats BDP was given and no sigma was specified:
        # find a noise level via casa.imstat()
        # if a CubeStat_BDP is given get it from there.
        if bdpin_cst == None:
            # get statistics from input image with imstat because no CubeStat_BDP
            stat  = casa.imstat(**args)
            dmin  = float(stat["min"][0])                 # these would be wrong if robust were used already
            dmax  = float(stat["max"][0])
            args.update(casautil.parse_robust(robust))    # only now add robust keywords for the sigma
            stat  = casa.imstat(**args)            
            if sigma <= 0.0 :
                sigma = float(stat["sigma"][0])
            dt.tag("imstat")
        else:
            # get statistics from CubeStat_BDP 
            sigma = bdpin_cst.get("sigma")
            dmin  = bdpin_cst.get("minval")
            dmax  = bdpin_cst.get("maxval")

        self.setkey("sigma",sigma)
        # calculate cutoff based either on RMS or dynamic range limitation
        drange = dmax/(nsigma*sigma)
        if snmax < 0.0 :
            snmax = drange
        if drange > snmax :
            cutoff = 1.0/snmax
        else:
            cutoff = 1.0/drange
        logging.info("sigma, dmin, dmax, snmax, cutoff %g %g %g %g %g" % (sigma, dmin, dmax, snmax, cutoff))
        # define arguments for call to findsources
        args2 = {"cutoff" : cutoff}
        args2["nmax"] = 30
        if region != "" :
            args2["region"] = region
        #args2["mask"] = ""
        args2["point"] = False
        args2["width"] = 5
        args2["negfind"] = False
        # set-up for SourceList_BDP
        slbdp = SourceList_BDP(slbase)

        # connect to casa image and call casa ia.findsources tool
        taskinit.ia.open(self.dir(infile))

        # findsources() cannot deal with  'Jy/beam.km/s' ???
        # so for the duration of findsources() we patch it
        bunit = taskinit.ia.brightnessunit()
        if bpatch and bunit != 'Jy/beam':
            logging.warning("Temporarely patching your %s units to Jy/beam for ia.findsources()" % bunit) 
            taskinit.ia.setbrightnessunit('Jy/beam')
        else:
            bpatch = False
        atab = taskinit.ia.findsources(**args2)
        if bpatch:
            taskinit.ia.setbrightnessunit(bunit)
        
        taskargs = "nsigma=%4.1f sigma=%g region=%s robust=%s snmax=%5.1f" % (nsigma,sigma,str(region),str(robust),snmax)
        dt.tag("findsources")
        nsources = atab["nelements"] 
        xtab = []
        ytab = []
        logscale = False
        sumflux = 0.0
        if nsources > 0:
            # @TODO: Why are Xpix, YPix not stored in the table?
            #        -> PJT: I left them out since they are connected to an image which may not be available here
            #                but we should store the frequency of the observation here for later bandmerging
            logging.debug("%s" % str(atab['component0']['shape']))
            logging.info("Right Ascen.  Declination   X(pix)   Y(pix)      Peak       Flux    Major   Minor    PA    SNR")
            funits = atab['component0']['flux']['unit']
            if atab['component0']['shape'].has_key('majoraxis'):
                sunits = atab['component0']['shape']['majoraxis']['unit']
                aunits = atab['component0']['shape']['positionangle']['unit']
            else:
                sunits = "n/a"
                aunits = "n/a"
            punits = taskinit.ia.summary()['unit']
            logging.info("                                               %s       %s    %s   %s   %s" % (punits,funits,sunits,sunits,aunits))
            #
            # @todo future improvement is to look at image coordinates and control output appropriately
            #
            if ds9:
                # @todo variable name
                regname = self.mkext(infile,'ds9.reg')
                fp9 = open(self.dir(regname),"w!")
            for i in range(nsources):
                c = "component%d" % i
                name = "%d" % (i+1)
                r = atab[c]['shape']['direction']['m0']['value']
                d = atab[c]['shape']['direction']['m1']['value']
                pixel = taskinit.ia.topixel([r,d])
                xpos = pixel['numeric'][0]
                ypos = pixel['numeric'][1]
                rd = taskinit.ia.toworld([xpos,ypos],'s')
                ra = rd['string'][0][:12]
                dec = rd['string'][1][:12]
                flux = atab[c]['flux']['value'][0]
                sumflux = sumflux + flux
                if atab[c]['shape'].has_key('majoraxis'):
                    smajor = atab[c]['shape']['majoraxis']['value']
                    sminor = atab[c]['shape']['minoraxis']['value']
                    sangle = atab[c]['shape']['positionangle']['value']
                else:
                    smajor = 0.0
                    sminor = 0.0
                    sangle = 0.0
                peakstr = taskinit.ia.pixelvalue([xpos,ypos,0,0])
                if len(peakstr) == 0:
                    logging.warning("Problem with source %d @ %d,%d" % (i,xpos,ypos))
                    continue
                peakf = peakstr['value']['value']
                snr = peakf/sigma
                if snr > dynlog:
                    logscale = True
                logging.info("%s %s %8.2f %8.2f %10.3g %10.3g %7.3f %7.3f %6.1f %6.1f" % (ra,dec,xpos,ypos,peakf,flux,smajor,sminor,sangle,snr))
                
                xtab.append(xpos)
                ytab.append(ypos)
                slbdp.addRow([name,ra,dec,flux,peakf,smajor,sminor,sangle])
                if ds9:
                    ras = ra
                    des = dec.replace('.',':',2)
                    msg = 'ellipse(%s,%s,%g",%g",%g) # text={%s}' % (ras,des,smajor,sminor,sangle+90.0,i+1)
                    fp9.write("%s\n" % msg)
            if ds9:
                fp9.close()
                logging.info("Wrote ds9.reg")
            dt.tag("table")
        logging.regression("CONTFLUX: %d %g" % (nsources,sumflux))
        

        summary = taskinit.ia.summary()
        beammaj = summary['restoringbeam']['major']['value']
        beammin = summary['restoringbeam']['minor']['value']
        beamunit = summary['restoringbeam']['minor']['unit']
        beamang = summary['restoringbeam']['positionangle']['value']
        angunit = summary['restoringbeam']['positionangle']['unit']
        # @todo add to table comments?
        logging.info(" Fitted Gaussian size; NOT deconvolved source size.")
        logging.info(" Restoring Beam: Major axis: %10.3g %s , Minor axis: %10.3g %s , PA: %5.1f %s" % (beammaj, beamunit, beammin, beamunit, beamang, angunit))
        # form into a xml table
        
        # output is a table_bdp
        self.addoutput(slbdp)

        # instantiate a plotter for all plots made herein
        myplot = APlot(ptype=self._plot_type,pmode=self._plot_mode,abspath=self.dir())

        # make output png with circles marking sources found
        if mpl:
            circles=[]
            nx = data.shape[1]             # data[] array was already flipud(rot90)'d
            ny = data.shape[0]             # 
            for (x,y) in zip(xtab,ytab):
                circles.append([x,y,1])
            # @todo variable name
            if logscale:
                logging.warning("LogScaling applied")
                data = data/sigma
                data = np.where(data<0,-np.log10(1-data),+np.log10(1+data))
            title = "SFind2D: %d sources" % nsources
            myplot.map1(data,title,slbase,thumbnail=True,circles=circles)

        #---------------------------------------------------------
        # Get the figure and thumbmail names and create a caption
        #---------------------------------------------------------
        imname = myplot.getFigure(figno=myplot.figno,relative=True)
        thumbnailname = myplot.getThumbnail(figno=myplot.figno,relative=True)
        caption = "Image of input map with sources found by SFind2D overlayed in green."
        slbdp.table.description="Table of source locations and sizes (not deconvolved)"
 
        #---------------------------------------------------------
        # Add finder image to the BDP
        #---------------------------------------------------------
        image = Image(images={bt.PNG: imname}, 
                      thumbnail=thumbnailname, 
                      thumbnailtype=bt.PNG, description=caption)
        slbdp.image.addimage(image, "finderimage")

        #-------------------------------------------------------------
        # Create the summary entry for the table and image
        #-------------------------------------------------------------
        self._summary["sources"] = SummaryEntry([slbdp.table.serialize(),
                                                 slbdp.image.serialize()],
                                                "SFind2D_AT", 
                                                self.id(True), taskargs)
        
        dt.tag("done")
        dt.end()
Example #16
0
    def run(self):
        """ The run method, calculates the moments and creates the BDP(s)

            Parameters
            ----------
            None

            Returns
            -------
            None
        """
        self._summary = {}
        momentsummary = []
        dt = utils.Dtime("Moment")

        # variable to track if we are using a single cutoff for all moment maps
        allsame = False
        moments = self.getkey("moments")
        numsigma = self.getkey("numsigma")
        mom0clip = self.getkey("mom0clip")
        # determine if there is only 1 cutoff or if there is a cutoff for each moment
        if len(moments) != len(numsigma):
            if len(numsigma) != 1:
                raise Exception("Length of numsigma and moment lists do not match. They must be the same length or the length of the cutoff list must be 1.")
            allsame = True
        # default moment file extensions, this is information copied from casa.immoments()
        momentFileExtensions = {-1: ".average",
                                 0: ".integrated",
                                 1: ".weighted_coord",
                                 2: ".weighted_dispersion_coord",
                                 3: ".median",
                                 4: "",
                                 5: ".standard_deviation",
                                 6: ".rms",
                                 7: ".abs_mean_dev",
                                 8: ".maximum",
                                 9: ".maximum_coord",
                                10: ".minimum",
                                11: ".minimum_coord",
                                }

        logging.debug("MOMENT: %s %s %s" %  (str(moments), str(numsigma), str(allsame)))

        # get the input casa image from bdp[0]
        # also get the channels the line actually covers (if any)
        bdpin = self._bdp_in[0]
        infile = bdpin.getimagefile(bt.CASA)
        chans = self.getkey("chans")
        # the basename of the moments, we will append _0, _1, etc.
        basename = self.mkext(infile, "mom")
        fluxname = self.mkext(infile, "flux")
        # beamarea = nppb(self.dir(infile))
        beamarea = 1.0  # until we have it from the MOM0 map

        sigma0 = self.getkey("sigma")
        sigma  = sigma0

        dt.tag("open")

        # if no CubseStats BDP was given and no sigma was specified, find a 
        # noise level via casa.imstat()
        if self._bdp_in[1] is None and sigma <= 0.0:
            raise Exception("A sigma or a CubeStats_BDP must be input to calculate the cutoff")
        elif self._bdp_in[1] is not None:
            sigma = self._bdp_in[1].get("sigma")

        # immoments is a bit peculiar. If you give one moment, it will use 
        # exactly the outfile you picked for multiple moments, it will pick
        # extensions such as .integrated [0], .weighted_coord [1] etc.
        # we loop over the moments and will use the numeric extension instead. 
        # Might be laborious loop for big input cubes
        #
        # arguments for immoments
        args = {"imagename" : self.dir(infile),
                "moments"   : moments,
                "outfile"   : self.dir(basename)}

        # set the channels if given
        if chans != "":
            args["chans"] = chans
        # error check the mom0clip input
        if mom0clip > 0.0 and not 0 in moments:
            logging.warning("mom0clip given, but no moment0 map was requested. One will be generated anyway.")
            # add moment0 to the list of computed moments, but it has to be first
            moments.insert(0,0)
            if not allsame:
                numsigma.insert(0, 2.0*sigma)

        if allsame:
            # this is only executed now if len(moments) > 1 and len(cutoff)==1
            args["excludepix"] = [-numsigma[0] * sigma, numsigma[0] * sigma]
            casa.immoments(**args)
            dt.tag("immoments-all")
        else:
            # this is execute if len(moments)==len(cutoff) , even when len=1
            for i in range(len(moments)):
                args["excludepix"] = [-numsigma[i] * sigma, numsigma[i] * sigma]
                args["moments"] = moments[i]
                args["outfile"] = self.dir(basename + momentFileExtensions[moments[i]])
                casa.immoments(**args)
                dt.tag("immoments-%d" % moments[i])

        taskargs = "moments=%s numsigma=%s" % (str(moments), str(numsigma)) 
        if sigma0 > 0:
            taskargs = taskargs + " sigma=%.2f" % sigma0
        if mom0clip > 0:
            taskargs = taskargs + " mom0clip=%g" % mom0clip
        if chans == "": 
            taskargs = taskargs + " chans=all"
        else:
            taskargs = taskargs + " chans=%s" % str(chans)
        taskargs += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span style="background-color:white">&nbsp;' + basename.split('/')[0] + '&nbsp;</span>'

        # generate the mask to be applied to all but moment 0
        if mom0clip > 0.0:
            # get the statistics from mom0 map
            # this is usually a very biased map, so unclear if mom0sigma is all that reliable
            args = {"imagename": self.dir(infile)}
            stat = casa.imstat(imagename=self.dir(basename + momentFileExtensions[0]))
            mom0sigma = float(stat["sigma"][0])
            # generate a temporary masked file, mask will be copied to other moments
            args = {"imagename" : self.dir(basename + momentFileExtensions[0]),
                    "expr"      : 'IM0[IM0>%f]' % (mom0clip * mom0sigma),
                    "outfile"   : self.dir("mom0.masked")
                    }
            casa.immath(**args)
            # get the default mask name
            taskinit.ia.open(self.dir("mom0.masked"))
            defmask = taskinit.ia.maskhandler('default')
            taskinit.ia.close()
            dt.tag("mom0clip")

        # loop over moments to rename them to _0, _1, _2 etc.
        # apply a mask as well for proper histogram creation
        map = {}
        myplot = APlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())
        implot = ImPlot(pmode=self._plot_mode,ptype=self._plot_type,abspath=self.dir())

        for mom in moments:
            figname = imagename = "%s_%i" % (basename, mom)
            tempname = basename + momentFileExtensions[mom]
            # rename and remove the old one if there is one
            utils.rename(self.dir(tempname), self.dir(imagename))
            # copy the moment0 mask if requested; this depends on that mom0 was done before
            if mom0clip > 0.0 and mom != 0:
                #print "PJT: output=%s:%s" % (self.dir(imagename), defmask[0])
                #print "PJT: inpmask=%s:%s" % (self.dir("mom0.masked"),defmask[0])
                makemask(mode="copy", inpimage=self.dir("mom0.masked"),
                         output="%s:%s" % (self.dir(imagename), defmask[0]),
                         overwrite=True, inpmask="%s:%s" % (self.dir("mom0.masked"),
                                                            defmask[0]))
                taskinit.ia.open(self.dir(imagename))
                taskinit.ia.maskhandler('set', defmask)
                taskinit.ia.close()
                dt.tag("makemask")
            if mom == 0:
                beamarea = nppb(self.dir(imagename))
            implot.plotter(rasterfile=imagename,figname=figname,colorwedge=True)
            imagepng  = implot.getFigure(figno=implot.figno,relative=True)
            thumbname = implot.getThumbnail(figno=implot.figno,relative=True)
            images = {bt.CASA : imagename, bt.PNG  : imagepng}
            thumbtype=bt.PNG
            dt.tag("implot")

            # get the data for a histogram (ia access is about 1000-2000 faster than imval())
            map[mom] = casautil.getdata(self.dir(imagename))
            data = map[mom].compressed()
            dt.tag("getdata")

            # make the histogram plot

            # get the label for the x axis
            bunit = casa.imhead(imagename=self.dir(imagename), mode="get", hdkey="bunit")
            # object for the caption
            objectname = casa.imhead(imagename=self.dir(imagename), mode="get", hdkey="object")

            # Make the histogram plot
            # Since we give abspath in the constructor, figname should be relative
            auxname = imagename + '_histo'
            auxtype = bt.PNG
            myplot.histogram(columns = data,
                             figname = auxname,
                             xlab    = bunit,
                             ylab    = "Count",
                             title   = "Histogram of Moment %d: %s" % (mom, imagename), thumbnail=True)

            casaimage = Image(images    = images,
                                    auxiliary = auxname,
                                    auxtype   = auxtype,
                                    thumbnail = thumbname,
                                    thumbnailtype = thumbtype)
            auxname = myplot.getFigure(figno=myplot.figno,relative=True)
            auxthumb = myplot.getThumbnail(figno=myplot.figno,relative=True)

            if hasattr(self._bdp_in[0], "line"):   # SpwCube doesn't have Line
                line = deepcopy(getattr(self._bdp_in[0], "line"))
                if not isinstance(line, Line):
                    line = Line(name="Unidentified")
            else:
                # fake a Line if there wasn't one
                line = Line(name="Unidentified")
            # add the BDP to the output array
            self.addoutput(Moment_BDP(xmlFile=imagename, moment=mom,
                           image=deepcopy(casaimage), line=line))
            dt.tag("ren+mask_%d" % mom)

            imcaption = "%s Moment %d map of Source %s" % (line.name, mom, objectname)
            auxcaption = "Histogram of %s Moment %d of Source %s" % (line.name, mom, objectname)
            thismomentsummary = [line.name, mom, imagepng, thumbname, imcaption,
                                 auxname, auxthumb, auxcaption, infile]
            momentsummary.append(thismomentsummary)

        if map.has_key(0) and map.has_key(1) and map.has_key(2):
            logging.debug("MAPs present: %s" % (map.keys()))

            # m0 needs a new mask, inherited from the more restricted m1 (and m2)
            m0 = ma.masked_where(map[1].mask,map[0])
            m1 = map[1]
            m2 = map[2]
            m01 = m0*m1
            m02 = m0*m1*m1
            m22 = m0*m2*m2
            sum0 = m0.sum()
            vmean = m01.sum()/sum0
            # lacking the full 3D cube, get two estimates and take the max
            sig1  = math.sqrt(m02.sum()/sum0 - vmean*vmean)
            sig2  = m2.max()
            #vsig = max(sig1,sig2)
            vsig = sig1
            
            # consider clipping in the masked array (mom0clip)
            # @todo   i can't use info from line, so just borrow basename for now for grepping
            #         this also isn't really the flux, the points per beam is still in there
            loc = basename.rfind('/')
            sum1 = ma.masked_less(map[0],0.0).sum()   # mom0clip
            # print out:   LINE,FLUX1,FLUX0,BEAMAREA,VMEAN,VSIGMA for regression
            # the linechans parameter in bdpin is not useful to print out here, it's local to the LineCube
            s_vlsr = admit.Project.summaryData.get('vlsr')[0].getValue()[0]
            s_rest = admit.Project.summaryData.get('restfreq')[0].getValue()[0]/1e9
            s_line = line.frequency
            if loc>0:
                if basename[:loc][0:2] == 'U_':
                    # for U_ lines we'll reference the VLSR w.r.t. RESTFREQ in that band
                    if abs(vmean) > vsig:
                        vwarn = '*'
                    else:
                        vwarn = ''
                    vlsr = vmean + (1.0-s_line/s_rest)*utils.c
                    msg = "MOM0FLUX: %s %g %g %g %g %g %g" % (basename[:loc],map[0].sum(),sum0,beamarea,vmean,vlsr,vsig)
                else:
                    # for identified lines we'll assume the ID was correct and not bother with RESTFREQ
                    msg = "MOM0FLUX: %s %g %g %g %g %g %g" % (basename[:loc],map[0].sum(),sum0,beamarea,vmean,vmean,vsig)
            else:
                msg = "MOM0FLUX: %s %g %g %g %g %g %g" % ("SPW_FULL"    ,map[0].sum(),sum0,beamarea,vmean,vmean,vsig)
            logging.regression(msg)
            dt.tag("mom0flux")

            # create a histogram of flux per channel

            # grab the X coordinates for the histogram, we want them in km/s
            # restfreq should also be in summary
            restfreq = casa.imhead(self.dir(infile),mode="get",hdkey="restfreq")['value']/1e9    # in GHz
            # print "PJT  %.10f %.10f" % (restfreq,s_rest)
            imval0 = casa.imval(self.dir(infile))
            freqs = imval0['coords'].transpose()[2]/1e9
            x = (1-freqs/restfreq)*utils.c
            # 
            h = casa.imstat(self.dir(infile), axes=[0,1])
            if h.has_key('flux'):
                flux0 = h['flux']
            else:
                flux0 = h['sum']/beamarea
            flux0sum = flux0.sum() * abs(x[1]-x[0])
            # @todo   make a flux1 with fluxes derived from a good mask
            flux1 = flux0 
            # construct histogram
            title = 'Flux Spectrum (%g)' % flux0sum
            xlab = 'VLSR (km/s)'
            ylab = 'Flux (Jy)'
            myplot.plotter(x,[flux0,flux1],title=title,figname=fluxname,xlab=xlab,ylab=ylab,histo=True)
            dt.tag("flux-spectrum")
            
        self._summary["moments"] = SummaryEntry(momentsummary, "Moment_AT", 
                                                self.id(True), taskargs)
        # get rid of the temporary mask
        if mom0clip > 0.0: 
            utils.rmdir(self.dir("mom0.masked"))

        dt.tag("done")
        dt.end()