def peakstats(image, freq, sigma, nsigma, minchan, maxgap, psample, peakfit = False): """ Go through a cube and find peaks in the spectral dimension It will gather a table of <peak>,<freq>,<sigma> which can be optionally used for plotting """ if psample < 0: return cutoff = nsigma * sigma madata = casautil.getdata(image) data = madata.data shape = data.shape logging.debug("peakstats: shape=%s cutoff=%g" % (str(shape),cutoff)) #print "DATA SHAPE:",shape #print "cutoff=",cutoff nx = shape[0] ny = shape[1] nz = shape[2] chan = np.arange(nz) # prepare the segment finder # we now have an array data[nx,ny,nz] sum = 0.0 pval = [] mval = [] wval = [] for x in range(0,nx,psample): for y in range(0,ny,psample): s0 = data[x,y,:] spec = ma.masked_invalid(s0) sum += spec.sum() # using abs=True is a bit counter intuitive, but a patch to deal with the confusion in # ADMITSegmentFinder w.r.t abs usage asf = ADMITSegmentFinder(pmin=nsigma, minchan=minchan, maxgap=maxgap, freq=freq, spec=spec, abs=True) #asf = ADMITSegmentFinder(pmin=nsigma, minchan=minchan, maxgap=maxgap, freq=freq, spec=spec, abs=False) f = asf.line_segments(spec, nsigma*sigma) for s in f: if False: for i in range(s[0],s[1]+1): print "# ",x,y,i,spec[i] ## area preserving and peak are correlated, 18% difference ## fitgauss1Dm was about 5" ## with fitgauss1D was about 30", and still bad fits par = utils.fitgauss1Dm(chan[s[0]:s[1]+1], spec[s[0]:s[1]+1], True) # peak from max #par = utils.fitgauss1Dm(chan[s[0]:s[1]+1], spec[s[0]:s[1]+1], False) # peak from area preserving if peakfit: (par,cov) = utils.fitgauss1D (chan[s[0]:s[1]+1], spec[s[0]:s[1]+1],par) #print "FIND: ",x,y,s,cutoff,0.0,0.0,par[0],par[1],par[2],s[1]-s[0]+1 pval.append(par[0]) mval.append(par[1]) wval.append(par[2]) #print "SUM:",sum return (np.array(pval),np.array(mval),np.array(wval))
def run(self): """ The run method creates the BDP. Parameters ---------- None Returns ------- None """ dt = utils.Dtime("ContinuumSub") # tagging time self._summary = {} # an ADMIT summary will be created here contsub = self.getkey("contsub") pad = self.getkey("pad") fitorder = self.getkey("fitorder") # x.im -> x.cim + x.lim # b1 = input spw BDP # b1a = optional input {Segment,Line}List # b1b = optional input Cont Map (now deprecated) # b2 = output line cube # b3 = output cont map b1 = self._bdp_in[0] f1 = b1.getimagefile(bt.CASA) b1a = self._bdp_in[1] # b1b = self._bdp_in[2] b1b = None # do not allow continuum maps to be input f2 = self.mkext(f1,'lim') f3 = self.mkext(f1,'cim') f3a = self.mkext(f1,'cim3d') # temporary cube name, map is needed b2 = SpwCube_BDP(f2) b3 = Image_BDP(f3) self.addoutput(b2) self.addoutput(b3) taskinit.ia.open(self.dir(f1)) s = taskinit.ia.summary() nchan = s['shape'][2] # ingest has guarenteed this to the spectral axis if b1a != None: # if a LineList was given, use that if len(b1a.table) > 0: # this section of code actually works for len(ch0)==0 as well # ch0 = b1a.table.getFullColumnByName("startchan") ch1 = b1a.table.getFullColumnByName("endchan") if pad != 0: # can widen or narrow the segments if pad > 0: logging.info("pad=%d to widen the segments" % pad) else: logging.info("pad=%d to narrow the segments" % pad) ch0 = np.where(ch0-pad < 0, 0, ch0-pad) ch1 = np.where(ch1+pad >= nchan, nchan-1, ch1+pad) s = Segments(ch0,ch1,nchan=nchan) ch = s.getchannels(True) # take the complement of lines as the continuum else: ch = range(nchan) # no lines? take everything as continuum (probably bad) logging.warning("All channels taken as continuum. Are you sure?") elif len(contsub) > 0: # else if contsub[] was supplied manually s = Segments(contsub,nchan=nchan) ch = s.getchannels() else: raise Exception,"No contsub= or input LineList given" if len(ch) > 0: taskinit.ia.open(self.dir(f1)) taskinit.ia.continuumsub(outline=self.dir(f2),outcont=self.dir(f3a),channels=ch,fitorder=fitorder) taskinit.ia.close() dt.tag("continuumsub") casa.immoments(self.dir(f3a),-1,outfile=self.dir(f3)) # mean of the continuum cube (f3a) utils.remove(self.dir(f3a)) # is the continuum map (f3) dt.tag("immoments") if b1b != None: # this option is now deprecated (see above, by setting b1b = None), no user option allowed # there is likely a mis-match in the beam, given how they are produced. So it's safer to # remove this here, and force the flow to smooth manually print "Adding back in a continuum map" f1b = b1b.getimagefile(bt.CASA) f1c = self.mkext(f1,'sum') # @todo notice we are not checking for conforming mapsize and WCS # and let CASA fail out if we've been bad. casa.immath([self.dir(f3),self.dir(f1b)],'evalexpr',self.dir(f1c),'IM0+IM1') utils.rename(self.dir(f1c),self.dir(f3)) dt.tag("immath") else: raise Exception,"No channels left to determine continuum. pad=%d too large?" % pad # regression rdata = casautil.getdata(self.dir(f3)).data logging.regression("CSUB: %f %f" % (rdata.min(),rdata.max())) # 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=f3,figname=f3,colorwedge=True) figname = implot.getFigure(figno=implot.figno,relative=True) thumbname = implot.getThumbnail(figno=implot.figno,relative=True) b2.setkey("image", Image(images={bt.CASA:f2})) b3.setkey("image", Image(images={bt.CASA:f3, bt.PNG : figname})) dt.tag("implot") if len(ch) > 0: taskargs = "pad=%d fitorder=%d contsub=%s" % (pad,fitorder,str(contsub)) imcaption = "Continuum map" self._summary["continuumsub"] = SummaryEntry([figname,thumbname,imcaption],"ContinuumSub_AT",self.id(True),taskargs) dt.tag("done") dt.end()
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 += ' <span style="background-color:white"> ' + basename.split( '/')[0] + ' </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()
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()
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()
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()
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()
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 += ' <span style="background-color:white"> ' + basename.split('/')[0] + ' </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()
def peakstats(image, freq, sigma, nsigma, minchan, maxgap, psample, peakfit=False): """ Go through a cube and find peaks in the spectral dimension It will gather a table of <peak>,<freq>,<sigma> which can be optionally used for plotting """ if psample < 0: return cutoff = nsigma * sigma madata = casautil.getdata(image) data = madata.data shape = data.shape logging.debug("peakstats: shape=%s cutoff=%g" % (str(shape), cutoff)) #print "DATA SHAPE:",shape #print "cutoff=",cutoff nx = shape[0] ny = shape[1] nz = shape[2] chan = np.arange(nz) # prepare the segment finder # we now have an array data[nx,ny,nz] sum = 0.0 pval = [] mval = [] wval = [] for x in range(0, nx, psample): for y in range(0, ny, psample): s0 = data[x, y, :] spec = ma.masked_invalid(s0) sum += spec.sum() # using abs=True is a bit counter intuitive, but a patch to deal with the confusion in # ADMITSegmentFinder w.r.t abs usage asf = ADMITSegmentFinder(pmin=nsigma, minchan=minchan, maxgap=maxgap, freq=freq, spec=spec, abs=True) #asf = ADMITSegmentFinder(pmin=nsigma, minchan=minchan, maxgap=maxgap, freq=freq, spec=spec, abs=False) f = asf.line_segments(spec, nsigma * sigma) for s in f: if False: for i in range(s[0], s[1] + 1): print "# ", x, y, i, spec[i] ## area preserving and peak are correlated, 18% difference ## fitgauss1Dm was about 5" ## with fitgauss1D was about 30", and still bad fits par = utils.fitgauss1Dm(chan[s[0]:s[1] + 1], spec[s[0]:s[1] + 1], True) # peak from max #par = utils.fitgauss1Dm(chan[s[0]:s[1]+1], spec[s[0]:s[1]+1], False) # peak from area preserving if peakfit: (par, cov) = utils.fitgauss1D(chan[s[0]:s[1] + 1], spec[s[0]:s[1] + 1], par) #print "FIND: ",x,y,s,cutoff,0.0,0.0,par[0],par[1],par[2],s[1]-s[0]+1 pval.append(par[0]) mval.append(par[1]) wval.append(par[2]) #print "SUM:",sum return (np.array(pval), np.array(mval), np.array(wval))