def getdata1(imgname): """Return all good data from a CASA image as a compressed 1D numpy array. The tb.open() access method, although faster, didn't seem to carry the mask (or at least in an easy way). ma.masked_invalid(data) still returned all pixels ok. NOTE: since this routine grabs all data in a single numpy array, this routine should only be used for 2D images or small 3D cubes. Parameters ---------- imagename : str The (absolute) CASA image filename Returns ------- array data in a 1D numpy array """ ia = taskinit.iatool() ia.open(imgname) d = ia.getchunk(blc=[0,0,0,0],trc=[-1,-1,0,0],getmask=False) m = ia.getchunk(blc=[0,0,0,0],trc=[-1,-1,0,0],getmask=True) ia.close() # note CASA and MA have their mask logic reversed # casa: true means a good point # ma: true means a masked/bad point dm = ma.masked_array(d,mask=~m) return dm.compressed()
def estimate_noise(imagename, linefree_range, noise_function=np.nanstd): ''' Calculate the noise in a cube in line-free channels. Should be run on *non-*pb corrected images so the spatial noise is flat! Estimate will also skip empty channels that do not have any data. ''' if imagename.endswith(".image"): try: # CASA 6 import casatools ia = casatools.image() except ImportError: try: from taskinit import iatool iatool = iatool() except ImportError: raise ImportError("Could not import CASA (casac).") ia.open(imagename) data = ia.getchunk().squeeze() ia.close() ia.done() elif imagename.lower().endswith(".fits"): cube = SpectralCube.read(imagename) data = cube.filled_data[:].value del cube else: raise ValueError("Unsure what kind of file this is.") noise_data = np.empty((1,)) for chan_range in linefree_range: for chan in range(chan_range[0], chan_range[1]): if chan >= data.shape[-1]: continue # Skip empty channels if np.isnan(data[..., chan]).all(): continue if (data[..., chan] == 0.).all(): continue valid_mask = np.logical_and(data[..., chan] != 0., np.isfinite(data[..., chan])) noise_data = np.append(noise_data, data[..., chan][valid_mask]) return noise_function(noise_data)
def map_to_slit(fname, clip=0.0, gamma=1.0): """take all values from a map over clip, compute best slit for PV Slice """ ia = taskinit.iatool() ia.open(fname) imshape = ia.shape() pix = ia.getchunk().squeeze() # this should now be a numpy pix[ix][iy] map pixmax = pix.max() pixrms = pix.std() if False: pix1 = pix.flatten() rpix = stats.robust(pix1) logging.debug("stats: mean: %g %g" % (pix1.mean(), rpix.mean())) logging.debug("stats: rms: %g %g" % (pix1.std(), rpix.std())) logging.debug("stats: max: %g %g" % (pix1.max(), rpix.max())) logging.debug('shape: %s %s %s' % (str(pix.shape), str(pix1.shape), str(imshape))) ia.close() nx = pix.shape[0] ny = pix.shape[1] x = np.arange(pix.shape[0]).reshape((nx, 1)) y = np.arange(pix.shape[1]).reshape((1, ny)) if clip > 0.0: nmax = nx * ny clip = clip * pixrms logging.debug("Using initial clip=%g for rms=%g" % (clip, pixrms)) m = ma.masked_less(pix, clip) while m.count() == 0: clip = 0.5 * clip logging.debug("no masking...trying lower clip=%g" % clip) m = ma.masked_less(pix, clip) else: logging.debug("Clip=%g now found %d/%d points" % (clip, m.count(), nmax)) else: #@ todo sigma-clipping with iterations? see also astropy.stats.sigma_clip() rpix = stats.robust(pix.flatten()) r_mean = rpix.mean() r_std = rpix.std() logging.info("ROBUST MAP mean/std: %f %f" % (r_mean, r_std)) m = ma.masked_less(pix, -clip * r_std) logging.debug("Found > clip=%g : %g" % (clip, m.count())) if m.count() == 0: logging.warning("Returning a dummy slit, no points above clip %g" % clip) edge = 3.0 #slit = [edge,0.5*ny,nx-1.0-edge,0.5*ny] # @todo file a bug, this failed # RuntimeError: (/var/rpmbuild/BUILD/casa-test/casa-test-4.5.7/code/imageanalysis/ImageAnalysis/PVGenerator.cc : 334) Failed AlwaysAssert abs( (endPixRot[0] - startPixRot[0]) - sqrt(xdiff*xdiff + ydiff*ydiff) ) < 1e-6 slit = [edge, 0.5 * ny - 0.1, nx - 1.0 - edge, 0.5 * ny + 0.1] else: slit = convert_to_slit(m, x, y, nx, ny, gamma) return (slit, clip)
def getdata(imgname, chans=[], zeromask=False): """Return all good data from a CASA image as a masked numpy array. The tb.open() access method, although faster, didn't seem to carry the mask (or at least in an easy way). ma.masked_invalid(data) still returned all pixels ok. NOTE: since this routine grabs all data in a single numpy array, this routine should only be used for 2D images or small 3D cubes, things with little impact on memory Parameters ---------- imagename : str The (absolute) CASA image filename Returns ------- array data in a masked numpy array """ ia = taskinit.iatool() ia.open(imgname) if len(chans) == 0: d = ia.getchunk(blc=[0,0,0,0],trc=[-1,-1,-1,0],getmask=False).squeeze() m = ia.getchunk(blc=[0,0,0,0],trc=[-1,-1,-1,0],getmask=True).squeeze() else: d = ia.getchunk(blc=[0,0,chans[0],0],trc=[-1,-1,chans[1],0],getmask=False).squeeze() m = ia.getchunk(blc=[0,0,chans[0],0],trc=[-1,-1,chans[1],0],getmask=True).squeeze() ia.close() # note CASA and MA have their mask logic reversed # casa: true means a good point # ma: true means a masked/bad point if zeromask: shape = d.shape ndim = len(d.shape) if ndim == 2: (x,y) = ma.where(~m) d[x,y] = 0.0 elif ndim == 3: (x,y,z) = ma.where(~m) d[x,y,z] = 0.0 else: raise Exception,"getdata: cannot handle data of dimension %d" % ndim dm = ma.masked_array(d,mask=~m) return dm
def fits_to_template(fitsfilename): """ Extract a CASA "template" header from a FITS file """ if not isinstance(fitsfilename,str): raise ValueError("FITS file name must be a filename string") ia = iatool() ia.fromfits(infile=fitsfilename, outfile=td, overwrite=True) td = tempfile.mkdtemp() ia.open(td) csys = ia.coordsys() shape = ia.shape() ia.close() outheader = {'csys':csys.torecord(), 'shap':shape} return outheader
def putdata_raw(imgname, data, clone=None): """Store (overwrite) data in an existing CASA image. See getdata_raw(imgname) for the reverse operation. Parameters ---------- imagename : str The (absolute) CASA image filename. It should exist already, unless **clone** was given. data : 2D numpy array or a list of 2D numpy arrays The data... clone : str, optional An optional filename from which to clone the image for output. It needs to be an absolute filename. """ ia = taskinit.iatool() if clone != None: ia.fromimage(infile=clone,outfile=imgname,overwrite=True) ia.close() # @todo this seems circumvent to have to borrow the odd dimensions (nx,ny,1,1,1) shape was seen if type(data) == type([]): # @todo since this needs to extend the axes, the single plane clone and replace data doesn't work here raise Exception,"Not Implemented Yet" bigim = ia.imageconcat(outfile=imgname, infiles=infiles, axis=2, relax=T, tempclose=F, overwrite=T) bigim.close() else: tb = taskinit.tbtool() tb.open(imgname,nomodify=False) d = tb.getcol('map') pdata = ma.getdata(data).reshape(d.shape) tb.putcol('map',pdata) tb.flush() tb.close() return
def mapdim(imgname, dim=None): """Return the dimensionality of the map, or if dim is given. Returns True if the dimensionality matches this value, or False if not. Warning: opens and closes the map via ia.open() Parameters ---------- imagename : str (a filename) dim : integer (or None) """ ia = taskinit.iatool() ia.open(imgname) s = ia.summary() shape = s['shape'] ia.close() # rdim = -1 for d in range(len(shape)): if shape[d] == 0 or shape[d] == 1: rdim = d break if rdim < 0: rdim = len(shape) #print "MAPDIM",imgname,shape,rdim # if dim == None: return rdim if dim == rdim: return True else: return False
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()
def ksc_sim_gauss(projname='sim_7m_array_1GHz_gaus', antennalist='ksc-7m.cfg', dishdiam=7, imagename=None, indirection='J2000 14h26m46.0s -14d31m22.0s', incell='1arcsec', frequency='1.0GHz', inwidth='1MHz', radec_offset=[100., 100.], flux=50., majoraxis='40arcsec', minoraxis='27arcsec', positionangle='45.0deg', imsize=[512, 512]): """ Simulate observation of an input Gaussian model :param projname: project name for simobserve :param antennalist: cfg file of the array configuration :param dishdiam: diameter of each dish, in meters :param imagename: name (and path) for output clean image, psf, etc. :param indirection: phase center of the observation :param incell: pixel scale of the model/simulated image :param frequency: central frequency :param inwidth: frequency bandwidth :param radec_offset: offset of the Gaussian source from the phasecenter, in arcsec :param flux: total flux of the Gaussian source, in solar flux unit (sfu) :param majoraxis: FWHM size of the Gaussian source along the major axis :param minoraxis: FWHM size of the Gaussian source along the minor axis :param positionangle: position angle of the Gaussian source :param imsize: size of the model/simulated image, in pixels (x and y) :return: """ # set voltage patterns and primary beams for KSC 7 m. This is a placeholder for now (but required for PB correction) vp = vptool() if len(vp.getvp(telescope='KSC').keys()) == 0: vprec = vp.setpbairy(telescope='KSC', dishdiam='{0:.1f}m'.format(dishdiam), blockagediam='0.75m', maxrad='1.784deg', reffreq='1.0GHz', dopb=True) # make a Gaussian source cl = cltool() ia = iatool() cl.addcomponent(dir=indirection, flux=flux * 1e4, fluxunit='Jy', freq=frequency, shape="Gaussian", majoraxis=majoraxis, minoraxis=minoraxis, positionangle=positionangle) ia.fromshape("Gaussian.im", imsize + [1, 1], overwrite=True) cs = ia.coordsys() cs.setunits(['rad', 'rad', '', 'Hz']) cell_rad = qa.convert(qa.quantity(incell), "rad")['value'] cs.setincrement([-cell_rad, cell_rad], 'direction') ra_ref = qa.toangle(indirection.split(' ')[1]) dec_ref = qa.toangle(indirection.split(' ')[2]) ra = ra_ref['value'] - radec_offset[0] / 3600. dec = dec_ref['value'] - radec_offset[1] / 3600. cs.setreferencevalue([ qa.convert('{0:.4f}deg'.format(ra), 'rad')['value'], qa.convert('{0:.4f}deg'.format(dec), 'rad')['value'] ], type="direction") cs.setreferencevalue("1.0GHz", 'spectral') cs.setincrement('10MHz', 'spectral') ia.setcoordsys(cs.torecord()) ia.setbrightnessunit("Jy/pixel") ia.modify(cl.torecord(), subtract=False) ia.close() simobserve(project=projname, skymodel='Gaussian.im', indirection=indirection, incell=incell, incenter=frequency, inwidth=inwidth, hourangle='transit', refdate='2014/11/01', totaltime='120s', antennalist=antennalist, obsmode='int', overwrite=True) if not imagename: imagename = projname + '/tst' tclean(vis=projname + '/' + projname + '.' + antennalist.split('.')[0] + '.ms', imagename=imagename, imsize=imsize, cell=incell, phasecenter=indirection, niter=200, interactive=False) viewer(projname + '/' + projname + '.' + antennalist.split('.')[0] + '.skymodel') viewer(imagename + '.psf') viewer(imagename + '.image')
def load_casa_image(filename, skipdata=False, skipvalid=False, skipcs=False, **kwargs): """ Load a cube (into memory?) from a CASA image. By default it will transpose the cube into a 'python' order and drop degenerate axes. These options can be suppressed. The object holds the coordsys object from the image in memory. """ try: import casatools ia = casatools.image() except ImportError: try: from taskinit import iatool ia = iatool() except ImportError: raise ImportError("Could not import CASA (casac) and therefore cannot read CASA .image files") # use the ia tool to get the file contents ia.open(filename) # read in the data if not skipdata: # CASA data are apparently transposed. data = ia.getchunk().transpose() # CASA stores validity of data as a mask if not skipvalid: valid = ia.getchunk(getmask=True).transpose() # transpose is dealt with within the cube object # read in coordinate system object casa_cs = ia.coordsys() wcs = wcs_casa2astropy(casa_cs) unit = ia.brightnessunit() beam_ = ia.restoringbeam() if 'major' in beam_: beam = Beam(major=u.Quantity(beam_['major']['value'], unit=beam_['major']['unit']), minor=u.Quantity(beam_['minor']['value'], unit=beam_['minor']['unit']), pa=u.Quantity(beam_['positionangle']['value'], unit=beam_['positionangle']['unit']), ) elif 'beams' in beam_: bdict = beam_['beams'] if beam_['nStokes'] > 1: raise NotImplementedError() nbeams = len(bdict) assert nbeams == beam_['nChannels'] stokesidx = '*0' majors = [u.Quantity(bdict['*{0}'.format(ii)][stokesidx]['major']['value'], bdict['*{0}'.format(ii)][stokesidx]['major']['unit']) for ii in range(nbeams)] minors = [u.Quantity(bdict['*{0}'.format(ii)][stokesidx]['minor']['value'], bdict['*{0}'.format(ii)][stokesidx]['minor']['unit']) for ii in range(nbeams)] pas = [u.Quantity(bdict['*{0}'.format(ii)][stokesidx]['positionangle']['value'], bdict['*{0}'.format(ii)][stokesidx]['positionangle']['unit']) for ii in range(nbeams)] beams = Beams(major=u.Quantity(majors), minor=u.Quantity(minors), pa=u.Quantity(pas)) else: warnings.warn("No beam information found in CASA image.", BeamWarning) # don't need this yet # stokes = get_casa_axis(temp_cs, wanttype="Stokes", skipdeg=False,) # if stokes == None: # order = np.arange(self.data.ndim) # else: # order = [] # for ax in np.arange(self.data.ndim+1): # if ax == stokes: # continue # order.append(ax) # self.casa_cs = ia.coordsys(order) # This should work, but coordsys.reorder() has a bug # on the error checking. JIRA filed. Until then the # axes will be reversed from the original. # if transpose == True: # new_order = np.arange(self.data.ndim) # new_order = new_order[-1*np.arange(self.data.ndim)-1] # print new_order # self.casa_cs.reorder(new_order) # close the ia tool ia.close() meta = {'filename': filename, 'BUNIT': unit} if wcs.naxis == 3: mask = BooleanArrayMask(np.logical_not(valid), wcs) if 'beam' in locals(): cube = SpectralCube(data, wcs, mask, meta=meta, beam=beam) elif 'beams' in locals(): cube = VaryingResolutionSpectralCube(data, wcs, mask, meta=meta, beams=beams) else: cube = SpectralCube(data, wcs, mask, meta=meta) # we've already loaded the cube into memory because of CASA # limitations, so there's no reason to disallow operations cube.allow_huge_operations = True elif wcs.naxis == 4: data, wcs = cube_utils._split_stokes(data, wcs) mask = {} for component in data: data_, wcs_slice = cube_utils._orient(data[component], wcs) mask[component] = LazyMask(np.isfinite, data=data[component], wcs=wcs_slice) if 'beam' in locals(): data[component] = SpectralCube(data_, wcs_slice, mask[component], meta=meta, beam=beam) elif 'beams' in locals(): data[component] = VaryingResolutionSpectralCube(data_, wcs_slice, mask[component], meta=meta, beams=beams) else: data[component] = SpectralCube(data_, wcs_slice, mask[component], meta=meta) data[component].allow_huge_operations = True cube = StokesSpectralCube(stokes_data=data) return cube
def imfit_iter(imgfiles, doreg, tims, msinfofile, ephem, box, region, chans, stokes, mask, includepix, excludepix, residual, model, estimates, logfile, append, newestimates, complist, overwrite, dooff, offset, fixoffset, stretch, rms, noisefwhm, summary, imidx): from taskinit import iatool, rg import pdb try: from astropy.io import fits as pyfits except: try: import pyfits except ImportError: raise ImportError( 'Neither astropy nor pyfits exists in this CASA installation') img = imgfiles[imidx] if doreg: # check if ephemfile and msinfofile exist if not ephem: print("ephemeris info does not exist!") return if not tims: print("timestamp of the image does not exist!") return try: tim = tims[imidx] helio = vla_prep.ephem_to_helio(msinfo=msinfofile, ephem=ephem, reftime=tim) fitsfile = [img.replace('.image', '.fits')] vla_prep.imreg(imagefile=[img], fitsfile=fitsfile, helio=helio, toTb=False, scl100=True) img = img.replace('.image', '.fits') except: print 'Failure in vla_prep. Skipping this image file: ' + img myia = iatool() try: if (not myia.open(img)): raise Exception, "Cannot create image analysis tool using " + img print('Processing image: ' + img) hdr = pyfits.getheader(img) pols = DButil.polsfromfitsheader(hdr) ndx, ndy, nchans, npols = myia.shape() results = {} for itpp in pols: results[itpp] = {} for pp, itpp in enumerate(pols): r = rg.box(blc=[0, 0, 0, pp], trc=[ndx, ndy, nchans, pp]) myiapol = myia.subimage(region=r, dropdeg=True) results[itpp] = myiapol.fitcomponents(box=box, region=region, chans=chans, stokes=stokes, mask=mask, includepix=includepix, excludepix=excludepix, residual=residual, model=model, estimates=estimates, logfile=logfile, append=append, newestimates=newestimates, complist=complist, overwrite=overwrite, dooff=dooff, offset=offset, fixoffset=fixoffset, stretch=stretch, rms=rms, noisefwhm=noisefwhm, summary=summary) # update timestamp timstr = hdr['date-obs'] return [True, timstr, img, results] except Exception, instance: casalog.post(str('*** Error in imfit ***') + str(instance)) # raise instance return [False, timstr, img, {}]
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) ia = taskinit.iatool() f1 = b1.getimagefile(bt.CASA) ia.open(self.dir(f1)) s = 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" ia.fromimage(infile=file, outfile=mask) nx = ia.shape()[0] ny = ia.shape()[1] nchan = ia.shape()[2] ia.fromshape(shape=[nx, ny, 1]) plane = ia.getchunk( [0, 0, 0], [-1, -1, 0]) # convenience plane for masking operation dt.tag("mask_sig") 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]: ia.putchunk(plane * 0 + sigma_array[i], blc=[0, 0, i, -1]) count = count + 1 else: ia.putchunk(plane * 0 + maxval, blc=[0, 0, i, -1]) else: ia.putchunk(plane * 0 + sigma_array[i], blc=[0, 0, i, -1]) count = count + 1 else: ia.putchunk(plane * 0 + maxval, blc=[0, 0, i, -1]) 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") if nchan == 1: logging.info("Assumed continuum - see issue 34") args[ "moments"] = -1 # force continuum @todo should we force this via a keyword? 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 ia.open(image_out) st = ia.statistics() 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, zoom=self.getkey("zoom")) 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) ia.done() dt.tag("done") dt.end()
def run(self): """ The run method creates the BDP Parameters ---------- None Returns ------- None """ self._summary = {} dt = utils.Dtime("Smooth") dt.tag("start") # get the input keys bmaj = self.getkey("bmaj") bmin = self.getkey("bmin") bpa = self.getkey("bpa") velres = self.getkey("velres") # take care of potential issues in the unit strings # @todo if not provided? bmaj['unit'] = bmaj['unit'].lower() bmin['unit'] = bmin['unit'].lower() velres['unit'] = velres['unit'].lower() taskargs = "bmaj=%s bmin=%s bpa=%s velres=%s" % (bmaj, bmin, bpa, velres) ia = taskinit.iatool() qa = taskinit.qatool() bdpnames = [] for ibdp in self._bdp_in: istem = ibdp.getimagefile(bt.CASA) image_in = ibdp.baseDir() + istem bdp_name = self.mkext(istem, 'sim') image_out = self.dir(bdp_name) ia.open(image_in) h = casa.imhead(image_in, mode='list') pix_scale = np.abs(h['cdelt1'] * 206265.0) # pix scale in asec @todo QA ? CC = 299792458.0 # speed of light @todo somewhere else [utils.c , but in km/s] rest_freq = h['crval3'] # frequency pixel scale in km/s vel_scale = np.abs(CC * h['cdelt3'] / rest_freq / 1000.0) # unit conversion to arcsec (spatial) or km/s # (velocity) or some flavor of Hz. if (bmaj['unit'] == 'pixel'): bmaj = bmaj['value'] * pix_scale else: bmaj = bmaj['value'] if (bmin['unit'] == 'pixel'): bmin = bmin['value'] * pix_scale else: bmin = bmin['value'] hertz_input = False if velres['unit'] == 'pixel': velres['value'] = velres['value'] * vel_scale velres['unit'] = 'km/s' elif velres['unit'] == 'm/s': velres['value'] = velres['value'] / 1000.0 velres['unit'] = 'km/s' elif velres['unit'][-2:] == 'hz': hertz_input = True elif velres['unit'] == 'km/s': pass else: logging.error("Unknown units in velres=%s" % velres['unit']) rdata = bmaj # we smooth in velocity first. if smoothing in velocity # the cube apparently must be closed afterwards and # then reopened if spatial smoothing is to be done. if velres['value'] > 0: # handle the different units allowed. CASA doesn't # like lowercase for hz units... if not hertz_input: freq_res = str( velres['value'] * 1000.0 / CC * rest_freq) + 'Hz' else: freq_res = str(velres['value']) # try to convert velres to km/s for debug purposes velres['value'] = velres['value'] / rest_freq * CC / 1000.0 if (velres['unit'] == 'khz'): velres['value'] = velres['value'] * 1000.0 velres['unit'] = 'kHz' elif (velres['unit'] == 'mhz'): velres['value'] = velres['value'] * 1E6 velres['unit'] = 'MHz' elif (velres['unit'] == 'ghz'): velres['value'] = velres['value'] * 1E9 velres['unit'] = 'GHz' freq_res = freq_res + velres['unit'] # NB: there is apparently a bug in CASA. only smoothing along the frequency # axis does not work. sepconvolve gives a unit error (says axis unit is radian rather # than Hz). MUST smooth in 2+ dimensions if you want this to work. if (velres['value'] < vel_scale): raise Exception, "Desired velocity resolution %g less than pixel scale %g" % ( velres['value'], vel_scale) image_tmp = self.dir('tmp.smooth') im2=ia.sepconvolve(outfile=image_tmp,axes=[0,1,2], types=["boxcar","boxcar","gauss"],\ widths=['1pix','1pix',freq_res], overwrite=True) im2.done() logging.debug("sepconvolve to %s" % image_out) # for some reason, doing this in memory does not seem to work, so outfile must be specified. logging.info( "Smoothing cube to a velocity resolution of %s km/s" % str(velres['value'])) logging.info("Smoothing cube to a frequency resolution of %s" % freq_res) ia.close() ia.open(image_tmp) dt.tag("sepconvolve") else: image_tmp = image_out # now do the spatial smoothing convolve_to_min_beam = True # default is to convolve to a min enclosing beam if bmaj > 0 and bmin > 0: # form qa objects out of these so that casa can understand bmaj = qa.quantity(bmaj, 'arcsec') bmin = qa.quantity(bmin, 'arcsec') bpa = qa.quantity(bpa, 'deg') target_res = {} target_res['major'] = bmaj target_res['minor'] = bmin target_res['positionangle'] = bpa # throw an exception if cannot be convolved try: # for whatever reason, if you give convolve2d a beam parameter, # it complains ... im2=ia.convolve2d(outfile=image_out,major = bmaj,\ minor = bmin, pa = bpa,\ targetres=True,overwrite=True) im2.done() logging.info( "Smoothing cube to a resolution of %s by %s at a PA of %s" % (str(bmaj['value']), str( bmin['value']), str(bpa['value']))) convolve_to_min_beam = False achieved_res = target_res except: # @todo remind what you need ? logging.error("Warning: Could not convolve to requested resolution of "\ +str(bmaj['value']) + " by " + str(bmin['value']) + \ " at a PA of "+ str(bpa['value'])) raise Exception, "Could not convolve to beam given!" dt.tag("convolve2d-1") if convolve_to_min_beam: restoring_beams = ia.restoringbeam() commonbeam = ia.commonbeam() # for whatever reason, setrestoringbeam does not use the same set of hashes... commonbeam['positionangle'] = commonbeam['pa'] del commonbeam['pa'] # if there's one beam, apparently the beams keyword does not exist if 'beams' in restoring_beams: print "Smoothing cube to a resolution of "+ \ str(commonbeam['major']['value']) +" by "+ \ str(commonbeam['minor']['value'])+" at a PA of "\ +str(commonbeam['pa']['value']) target_res = commonbeam im2=ia.convolve2d(outfile=image_out,major=commonbeam['major'],\ minor=commonbeam['minor'],\ pa=commonbeam['positionangle'],\ targetres=True,overwrite=True) im2.done() achieved_res = commonbeam dt.tag("convolve2d-2") else: print "One beam for all planes. Smoothing to common beam redundant." achieved_res = commonbeam if velres['value'] < 0: ia.fromimage(outfile=image_out, infile=image_in) # not really doing anything # else, we've already done what we needed to ia.setrestoringbeam(beam=achieved_res) rdata = achieved_res['major']['value'] # else do no smoothing and just close the image ia.close() dt.tag("close") b1 = SpwCube_BDP(bdp_name) self.addoutput(b1) # need to update for multiple images. b1.setkey("image", Image(images={bt.CASA: bdp_name})) bdpnames = bdpnames.append(bdp_name) # and clean up the temp image before the next image if velres['value'] > 0: utils.remove(image_tmp) # thes are task arguments not summary entries. _bmaj = qa.convert(achieved_res['major'], 'rad')['value'] _bmin = qa.convert(achieved_res['minor'], 'rad')['value'] _bpa = qa.convert(achieved_res['positionangle'], 'deg')['value'] vres = "%.2f %s" % (velres['value'], velres['unit']) logging.regression("SMOOTH: %f %f" % (rdata, velres['value'])) self._summary["smooth"] = SummaryEntry( [bdp_name, convolve_to_min_beam, _bmaj, _bmin, _bpa, vres], "Smooth_AT", self.id(True), taskargs) dt.tag("done") dt.end()
def load_casa_image(filename, skipdata=False, skipvalid=False, skipcs=False, target_cls=None, **kwargs): """ Load a cube (into memory?) from a CASA image. By default it will transpose the cube into a 'python' order and drop degenerate axes. These options can be suppressed. The object holds the coordsys object from the image in memory. """ from .core import StringWrapper if isinstance(filename, StringWrapper): filename = filename.value try: import casatools iatool = casatools.image except ImportError: try: from taskinit import iatool except ImportError: raise ImportError( "Could not import CASA (casac) and therefore cannot read CASA .image files" ) ia = iatool() # use the ia tool to get the file contents try: ia.open(filename, cache=False) except AssertionError as ex: if 'must be of cReqPath type' in str(ex): raise IOError("File {0} not found. Error was: {1}".format( filename, str(ex))) else: raise ex # read in the data if not skipdata: arrdata = ArraylikeCasaData(filename) # CASA data are apparently transposed. data = dask.array.from_array(arrdata, chunks=arrdata.chunksize, name=filename) # CASA stores validity of data as a mask if not skipvalid: boolarr = ArraylikeCasaData(filename, ia_kwargs={'getmask': True}) valid = dask.array.from_array(boolarr, chunks=boolarr.chunksize, name=filename + ".mask") # transpose is dealt with within the cube object # read in coordinate system object casa_cs = ia.coordsys() unit = ia.brightnessunit() beam_ = ia.restoringbeam() ia.done() ia.close() wcs = wcs_casa2astropy(ia, casa_cs) del casa_cs del ia if 'major' in beam_: beam = Beam( major=u.Quantity(beam_['major']['value'], unit=beam_['major']['unit']), minor=u.Quantity(beam_['minor']['value'], unit=beam_['minor']['unit']), pa=u.Quantity(beam_['positionangle']['value'], unit=beam_['positionangle']['unit']), ) elif 'beams' in beam_: bdict = beam_['beams'] if beam_['nStokes'] > 1: raise NotImplementedError() nbeams = len(bdict) assert nbeams == beam_['nChannels'] stokesidx = '*0' majors = [ u.Quantity(bdict['*{0}'.format(ii)][stokesidx]['major']['value'], bdict['*{0}'.format(ii)][stokesidx]['major']['unit']) for ii in range(nbeams) ] minors = [ u.Quantity(bdict['*{0}'.format(ii)][stokesidx]['minor']['value'], bdict['*{0}'.format(ii)][stokesidx]['minor']['unit']) for ii in range(nbeams) ] pas = [ u.Quantity( bdict['*{0}'.format(ii)][stokesidx]['positionangle']['value'], bdict['*{0}'.format(ii)][stokesidx]['positionangle']['unit']) for ii in range(nbeams) ] beams = Beams(major=u.Quantity(majors), minor=u.Quantity(minors), pa=u.Quantity(pas)) else: warnings.warn("No beam information found in CASA image.", BeamWarning) # don't need this yet # stokes = get_casa_axis(temp_cs, wanttype="Stokes", skipdeg=False,) # if stokes == None: # order = np.arange(self.data.ndim) # else: # order = [] # for ax in np.arange(self.data.ndim+1): # if ax == stokes: # continue # order.append(ax) # self.casa_cs = ia.coordsys(order) # This should work, but coordsys.reorder() has a bug # on the error checking. JIRA filed. Until then the # axes will be reversed from the original. # if transpose == True: # new_order = np.arange(self.data.ndim) # new_order = new_order[-1*np.arange(self.data.ndim)-1] # print new_order # self.casa_cs.reorder(new_order) meta = {'filename': filename, 'BUNIT': unit} if wcs.naxis == 3: data, wcs_slice = cube_utils._orient(data, wcs) valid, _ = cube_utils._orient(valid, wcs) mask = BooleanArrayMask(valid, wcs_slice) if 'beam' in locals(): cube = SpectralCube(data, wcs_slice, mask, meta=meta, beam=beam) elif 'beams' in locals(): cube = VaryingResolutionSpectralCube(data, wcs_slice, mask, meta=meta, beams=beams) else: cube = SpectralCube(data, wcs_slice, mask, meta=meta) # with #592, this is no longer true # we've already loaded the cube into memory because of CASA # limitations, so there's no reason to disallow operations # cube.allow_huge_operations = True assert cube.mask.shape == cube.shape elif wcs.naxis == 4: valid, _ = cube_utils._split_stokes(valid, wcs) data, wcs = cube_utils._split_stokes(data, wcs) mask = {} for component in data: data_, wcs_slice = cube_utils._orient(data[component], wcs) valid_, _ = cube_utils._orient(valid[component], wcs) mask[component] = BooleanArrayMask(valid_, wcs_slice) if 'beam' in locals(): data[component] = SpectralCube(data_, wcs_slice, mask[component], meta=meta, beam=beam) elif 'beams' in locals(): data[component] = VaryingResolutionSpectralCube( data_, wcs_slice, mask[component], meta=meta, beams=beams) else: data[component] = SpectralCube(data_, wcs_slice, mask[component], meta=meta) data[component].allow_huge_operations = True cube = StokesSpectralCube(stokes_data=data) assert cube.I.mask.shape == cube.shape assert wcs_utils.check_equality(cube.I.mask._wcs, cube.wcs) else: raise ValueError("CASA image has {0} dimensions, and therefore " "is not readable by spectral-cube.".format(wcs.naxis)) from .core import normalize_cube_stokes return normalize_cube_stokes(cube, target_cls=target_cls)
def getbeam(imagefile=None, beamfile=None): ia = iatool() if not imagefile: raise ValueError, 'Please specify input images' bmaj = [] bmin = [] bpa = [] beamunit = [] bpaunit = [] chans = [] nimg = len(imagefile) for n in range(nimg): img = imagefile[n] if not os.path.exists(img): raise ValueError, 'The input image does not exist!' ia.open(img) sum = ia.summary() bmaj_ = [] bmin_ = [] bpa_ = [] if sum.has_key('perplanebeams'): # beam vary with frequency nbeams = sum['perplanebeams']['nChannels'] beams = sum['perplanebeams']['beams'] chans_ = [key[1:] for key in beams.keys()] chans_.sort(key=float) for chan in chans_: bmaj0 = beams['*' + chan]['*0']['major']['value'] bmaj_.append(bmaj0) bmin0 = beams['*' + chan]['*0']['minor']['value'] bmin_.append(bmin0) bpa0 = beams['*' + chan]['*0']['positionangle']['value'] bpa_.append(bpa0) beamunit_ = beams['*' + chans_[0]]['*0']['major']['unit'] bpaunit_ = beams['*' + chans_[0]]['*0']['positionangle']['unit'] if sum.has_key('restoringbeam'): # only one beam bmaj_.append(sum['restoringbeam']['major']['value']) bmin_.append(sum['restoringbeam']['minor']['value']) bpa_.append(sum['restoringbeam']['positionangle']['value']) beamunit_ = sum['restoringbeam']['major']['unit'] bpaunit_ = sum['restoringbeam']['positionangle']['unit'] nbeams = 1 chans_ = [0] bmaj.append(bmaj_) bmin.append(bmin_) bpa.append(bpa_) beamunit.append(beamunit_) bpaunit.append(bpaunit_) chans.append(chans_) if beamfile: # write beams to ascii file print 'Writing beam info to ascii file...' f = open(beamfile, 'w') f.write('CHANNEL No., BMAJ (' + beamunit[0] + '), BMIN (' + beamunit[0] + '), BPA (' + bpaunit[0] + ')') f.write("\n") for n in range(nimg): f.write('----For image: ' + imagefile[n] + '----') f.write('\n') chans_ = chans[n] for i in range(len(chans_)): f.write( str(chans_[i]) + ', ' + str(bmaj[n][i]) + ', ' + str(bmin[n][i]) + ', ' + str(bpa[n][i])) f.write("\n") f.close() return bmaj, bmin, bpa, beamunit, bpaunit
def run(self): """ The run method creates the BDP Parameters ---------- None Returns ------- None """ self._summary = {} dt = utils.Dtime("Regrid") dt.tag("start") do_spatial_regrid = self.getkey("do_spatial_regrid") pix_scale = self.getkey("pix_scale") do_freq_regrid = self.getkey("do_freq_regrid") chan_width = self.getkey("chan_width") pix_size = [] chan_size = [] im_size = [] pix_wc_x = [] pix_wc_y = [] pix_wc_nu = [] src_dec = [] RADPERARCSEC = 4.848137E-6 ia = taskinit.iatool() for ibdp in self._bdp_in: # Convert input CASA images to numpy arrays. istem = ibdp.getimagefile(bt.CASA) ifile = ibdp.baseDir() + istem h = casa.imhead(ifile, mode='list') pix_size.append(np.abs( h['cdelt1'])) # pix scale in asec @todo QA ? chan_size.append(np.abs(h['cdelt3'])) # grab the pixels pix_x = h['shape'][0] pix_y = h['shape'][1] pix_nu = h['shape'][2] ia.open(ifile) mycs = ia.coordsys(axes=[0, 1, 2]) # getting all four corners handles the case of images where # x-y axis not aligned with RA-dec for xpix in [0, pix_x]: for ypix in [0, pix_y]: x = mycs.toworld([xpix, ypix])['numeric'][0] y = mycs.toworld([xpix, ypix])['numeric'][1] pix_wc_x.append(x) pix_wc_y.append(y) nu = mycs.toworld([pix_x, pix_y, 0])['numeric'][2] pix_wc_nu.append(nu) nu = mycs.toworld([pix_x, pix_y, pix_nu])['numeric'][2] pix_wc_nu.append(nu) ia.close() min_ra = np.min(pix_wc_x) max_ra = np.max(pix_wc_x) min_dec = np.min(pix_wc_y) max_dec = np.max(pix_wc_y) mean_ra = 0.5 * (min_ra + max_ra) mean_dec = 0.5 * (min_dec + max_dec) if (pix_scale < 0): pix_scale = np.min(pix_size) else: pix_scale = pix_scale * RADPERARCSEC npix_ra = int((max_ra - min_ra) / pix_scale * np.cos(mean_dec)) npix_dec = (max_dec - min_dec) / pix_scale npix_dec = (int(npix_dec) if npix_dec == int(npix_dec) else int(npix_dec) + 1) min_nu = np.min(pix_wc_nu) max_nu = np.max(pix_wc_nu) mean_nu = 0.5 * (min_nu + max_nu) if (chan_width < 0): chan_width = np.min(chan_size) npix_nu = int((max_nu - min_nu) / chan_width) + 1 # now regrid everything innames = [] outnames = [] incdelt = [] outcdelt = [] #========================================================= #@todo - check if bdp_ins refer to same input file. # If so, the current code will fail because the output # file name is fixed to $INPUT_regrid. A valid use case # is "regrid the same input file different ways" -- which # is not supported in the current code but should be #========================================================= for ibdp in self._bdp_in: istem = ibdp.getimagefile(bt.CASA) ifile = ibdp.baseDir() + istem ostem = "%s_regrid/" % (istem) ofile = self.baseDir() + ostem # save the input/output file names innames.append(istem) outnames.append(ostem) header = casa.imregrid(imagename=ifile, template='get') # save the input cdelt1,2,3 for summary table incdelt.append( (header['csys']['direction0']['cdelt'][0] / RADPERARCSEC, header['csys']['direction0']['cdelt'][1] / RADPERARCSEC, utils.freqtovel(mean_nu, header['csys']['spectral2']['wcs']['cdelt']))) if (do_spatial_regrid): header['csys']['direction0']['cdelt'] = [ -1 * pix_scale, pix_scale ] header['csys']['direction0']['crval'] = [mean_ra, mean_dec] header['shap'][0] = npix_ra header['shap'][1] = npix_dec header['csys']['direction0']['crpix'] = [ npix_ra / 2, npix_dec / 2 ] if (do_freq_regrid): header['csys']['spectral2']['wcs']['crval'] = min_nu header['shap'][2] = npix_nu chan_size = np.abs(header['csys']['spectral2']['wcs']['cdelt']) header['csys']['spectral2']['wcs']['cdelt'] = chan_width flux_correction = chan_width / chan_size casa.imregrid(imagename=ifile, output=ofile, template=header) # save the output cdelt1,2,3 for summary table newhead = casa.imregrid(imagename=ofile, template='get') outcdelt.append( (newhead['csys']['direction0']['cdelt'][0] / RADPERARCSEC, newhead['csys']['direction0']['cdelt'][1] / RADPERARCSEC, utils.freqtovel( mean_nu, newhead['csys']['spectral2']['wcs']['cdelt']))) if (do_freq_regrid): ia.open(ofile) print flux_correction ia.calc(pixels=ofile.replace(r"/", r"\/") + '*' + str(flux_correction)) ia.done() obdp = admit.SpwCube_BDP(ostem) self.addoutput(obdp) # make a table for summary atable = admit.util.Table() atable.columns = [ "Input Image", "cdelt1", "cdelt2", "cdelt3", "Regridded Image", "cdelt1", "cdelt2", "cdelt3" ] atable.units = [ "", "arcsec", "arcsec", "km/s", "", "arcsec", "arcsec", "km/s" ] for i in range(len(innames)): atable.addRow([ innames[i], incdelt[i][0], incdelt[i][1], incdelt[i][2], outnames[i], outcdelt[i][0], outcdelt[i][1], outcdelt[i][2] ]) #keys = "pixsize=%.4g naxis1=%d naxis2=%d mean_ra=%0.4f mean_dec=%0.4f reffreq=%g chan_width=%g" % (pix_scale/(4.848137E-6), npix_ra, npix_dec, mean_ra,mean_dec,min_nu,chan_width) taskargs = "pix_scale = " + str( self.getkey("pix_scale")) + " chan_width = " + str( self.getkey("chan_width")) self._summary["regrid"] = SummaryEntry(atable.serialize(), "Regrid_AT", self.id(True), taskargs) dt.tag("done") dt.end()
def load_casa_image(filename, skipdata=False, skipvalid=False, skipcs=False, **kwargs): """ Load a cube (into memory?) from a CASA image. By default it will transpose the cube into a 'python' order and drop degenerate axes. These options can be suppressed. The object holds the coordsys object from the image in memory. """ try: from taskinit import iatool ia = iatool() except ImportError: raise ImportError("Could not import CASA (casac) and therefore cannot read CASA .image files") # use the ia tool to get the file contents ia.open(filename) # read in the data if not skipdata: data = ia.getchunk().reshape(ia.shape()) # CASA stores validity of data as a mask if not skipvalid: valid = ia.getchunk(getmask=True).reshape(ia.shape()) # transpose is dealt with within the cube object # read in coordinate system object casa_cs = ia.coordsys() wcs = wcs_casa2astropy(casa_cs) unit = ia.brightnessunit() # don't need this yet # stokes = get_casa_axis(temp_cs, wanttype="Stokes", skipdeg=False,) # if stokes == None: # order = np.arange(self.data.ndim) # else: # order = [] # for ax in np.arange(self.data.ndim+1): # if ax == stokes: # continue # order.append(ax) # self.casa_cs = ia.coordsys(order) # This should work, but coordsys.reorder() has a bug # on the error checking. JIRA filed. Until then the # axes will be reversed from the original. # if transpose == True: # new_order = np.arange(self.data.ndim) # new_order = new_order[-1*np.arange(self.data.ndim)-1] # print new_order # self.casa_cs.reorder(new_order) # close the ia tool ia.close() meta = {'filename': filename, 'BUNIT': unit} if wcs.naxis == 3: mask = BooleanArrayMask(np.logical_not(valid), wcs) cube = SpectralCube(data, wcs, mask, meta=meta) # we've already loaded the cube into memory because of CASA # limitations, so there's no reason to disallow operations cube.allow_huge_operations = True elif wcs.naxis == 4: data, wcs = cube_utils._split_stokes(data.T, wcs) mask = {} for component in data: data_, wcs_slice = cube_utils._orient(data[component], wcs) mask[component] = LazyMask(np.isfinite, data=data[component], wcs=wcs_slice) data[component] = SpectralCube(data_, wcs_slice, mask[component], meta=meta) data[component].allow_huge_operations = True cube = StokesSpectralCube(stokes_data=data) return cube
def make_mask_3d(imagename, thresh, fl=False, useimage=False, pixelmin=0, major=0, minor=0, pixelsize=0, line=False, overwrite_old=True, closing_diameter=6, pbimage=None, myresidual=None, myimage=None, extension='.fullmask', spectral_closing=3): """ Makes a mask on any image you want it to. Parameters ---------- imagename : {casa image without file extention} Name of image you want to mask, without the file extention. thresh : {float} Masking thershold in whatever units are using fl : {bool} If you want to combine the mask with a previous iteration of clean (True), if not (i.e. you are using the dirty image) then False. useimage : {bool} If you want to use the dirty image or the residual for the masking (I usually use the residual - so set to False) pixelmin : {float} Min number of pixels within a masked region to be taken into the final mask, i.e. if your beam size is 1arcsec and pixel size is 0.2 arcsec, then three beams would be pixelmin = 75 major : {float} beam major axis, in arsec minor : {float} beam minor axis, in arsec pixelsize : {float} length of one side of pixel, in arcsec line : {bool} if the image is a line or continuum Returns ------- mask : {ndarray} The final mask (hopefully) as the ".fullmask" image """ import os from tasks import immath import numpy as np from scipy import ndimage from taskinit import iatool ia = iatool() mymask = imagename + '.mask' if myimage is None: myimage = imagename + '.image' maskim_nopb = imagename + '{}.nopb'.format(extension) maskim = imagename + extension threshmask = imagename + '.threshmask' if myresidual is None: myresidual = imagename + '.residual' if pbimage is None: pbimage = imagename + '.pb' if overwrite_old: os.system('rm -rf ' + maskim) os.system('rm -rf ' + maskim_nopb) os.system('rm -rf ' + threshmask) if useimage: print 'Using Image' immath(imagename=[myimage], outfile=threshmask, expr='iif(IM0 > ' + str(thresh) + ',1.0,0.0)') else: immath(imagename=[myresidual], outfile=threshmask, expr='iif(IM0 > ' + str(thresh) + ',1.0,0.0)') if fl: print 'Combining with previous mask..' immath(outfile=maskim_nopb, expr='iif(("' + threshmask + '" + "' + mymask + '") > 0.1,1.0,0.0)') else: print 'Making fresh new mask from image/residual' os.system('cp -r ' + threshmask + ' ' + maskim_nopb) immath(imagename=[pbimage, maskim_nopb], outfile=maskim, expr='iif(IM0 > 0.0, IM1, 0.0)') print "Using pixelmin=", pixelmin beamarea = (major * minor * np.pi / (4. * np.log(2.))) / (pixelsize**2) print 'Beam area', beamarea ia.open(maskim) mask = ia.getchunk() diam = closing_diameter # Change for large beam dilation structure = np.ones((diam, diam)) dist = ((np.indices((diam, diam)) - (diam - 1) / 2.)**2).sum(axis=0)**0.5 # circularize the closing element structure[dist > diam / 2.] = 0 if line: for k in range(mask.shape[3]): mask_temp = mask[:, :, 0, k] mask_temp = ndimage.binary_closing(mask_temp, structure=structure) labeled, j = ndimage.label(mask_temp) myhistogram = ndimage.measurements.histogram(labeled, 0, j + 1, j + 1) object_slices = ndimage.find_objects(labeled) threshold = pixelmin for i in range(j): if myhistogram[i + 1] < threshold: mask_temp[object_slices[i]] = 0.0 mask[:, :, 0, k] = mask_temp # add an additional closing run, this time with a 3d (4d?) st. element structure_3d = np.ones((diam, diam, 1, spectral_closing)) dist = ((np.indices((diam, diam)) - (diam - 1) / 2.)**2).sum(axis=0)**0.5 # circularize the closing element dist_3d = np.repeat(dist[:, :, None, None], spectral_closing, axis=3) structure_3d[dist_3d > diam / 2.] = 0 mask_closed = ndimage.binary_closing(mask, structure=structure_3d) else: raise RuntimeError("3D closing operation can only operate on cubes.") ia.putchunk(mask_closed.astype(int)) ia.done() print 'Mask created.' return maskim
def imfit_iter(imgfiles, doreg, tims, msinfofile, ephem, box, region, chans, stokes, mask, includepix, excludepix, residual, model, estimates, logfile, append, newestimates, complist, overwrite, dooff, offset, fixoffset, stretch, rms, noisefwhm, summary, imidx): from taskinit import iatool import pdb try: from astropy.io import fits except: try: import pyfits as fits except ImportError: raise ImportError( 'Neither astropy nor pyfits exists in this CASA installation') img = imgfiles[imidx] if doreg: # check if ephemfile and msinfofile exist if not ephem: print("ephemeris info does not exist!") return if not tims: print("timestamp of the image does not exist!") return try: tim = tims[imidx] helio = vla_prep.ephem_to_helio(msinfo=msinfofile, ephem=ephem, reftime=tim) fitsfile = [img.replace('.image', '.fits')] vla_prep.imreg(imagefile=[img], fitsfile=fitsfile, helio=helio, toTb=False, scl100=True) img = img.replace('.image', '.fits') except: print 'Failure in vla_prep. Skipping this image file: ' + img myia = iatool() try: if (not myia.open(img)): raise Exception, "Cannot create image analysis tool using " + img print('Processing image: ' + img) result_dict = myia.fitcomponents(box=box, region=region, chans=chans, stokes=stokes, mask=mask, includepix=includepix, excludepix=excludepix, residual=residual, model=model, estimates=estimates, logfile=logfile, append=append, newestimates=newestimates, complist=complist, overwrite=overwrite, dooff=dooff, offset=offset, fixoffset=fixoffset, stretch=stretch, rms=rms, noisefwhm=noisefwhm, summary=summary) # update timestamp hdr = fits.getheader(img) result_dict['timestamp'] = hdr['date-obs'] print('I am here after fitcomponents') return result_dict except Exception, instance: casalog.post(str('*** Error in imfit ***') + str(instance), 'SEVERE') raise instance
def run(self): # self._summary = {} # prepare to make a summary here dt = utils.Dtime("Ingest") # timer for debugging do_cbeam = True # enforce a common beam # pb = self.getkey('pb') do_pb = len(pb) > 0 use_pb = self.getkey("usepb") # create_mask = self.getkey('mask') # create a new mask ? box = self.getkey("box") # corners in Z, XY or XYZ edge = self.getkey("edge") # number of edge channels to remove restfreq = self.getkey("restfreq") # < 0 means not activated ckms = utils.c # 299792.458 km/s # smooth= could become deprecated, and/or include a decimation option to make it useful # again, Smooth_AT() does this also , at the cost of an extra cube to store smooth = self.getkey("smooth") # # vlsr = self.getkey( "vlsr") # see also LineID, where this could be given again # first place a fits file in the admit project directory (symlink) # this is a bit involved, depending on if an absolute or relative path was # give to Ingest_AT(file=) fitsfile = self.getkey('file') if fitsfile[0] != os.sep: fitsfile = os.path.abspath(os.getcwd() + os.sep + fitsfile) logging.debug('FILE=%s' % fitsfile) if fitsfile[0] != os.sep: raise Exception, "Bad file=%s, expected absolute name", fitsfile # now determine if it could have been a CASA (or MIRIAD) image already # which we'll assume if it's a directory; this is natively supported by CASA # but there are tools where if you pass it a FITS or MIRIAD # MIRIAD is not recommended for serious work, especially big files, since there # is a performance penalty due to tiling. file_is_casa = casautil.iscasa(fitsfile) loc = fitsfile.rfind(os.sep) # find the '/' ffile0 = fitsfile[loc + 1:] # basename.fits basename = self.getkey( 'basename') # (new) basename allowed (allow no dots?) if len(basename) == 0: basename = ffile0[:ffile0.rfind('.')] # basename logging.info("basename=%s" % basename) target = self.dir(ffile0) if not os.path.exists(target): cmd = 'ln -s "%s" "%s"' % (fitsfile, target) logging.debug("CMD: %s" % cmd) os.system(cmd) readonly = False if file_is_casa: logging.debug("Assuming input %s is a CASA (or MIRIAD) image" % ffile0) bdpfile = self.mkext(basename, "im") if bdpfile == ffile0: logging.warning( "No selections allowed on CASA image, since no alias was given" ) readonly = True b1 = SpwCube_BDP(bdpfile) self.addoutput(b1) b1.setkey("image", Image(images={bt.CASA: bdpfile})) # @todo b2 and PB? else: # construct the output name and construct the BDP based on the CASA image name # this also takes care of the behind the scenes alias= substitution bdpfile = self.mkext(basename, "im") if bdpfile == basename: raise Exception, "basename and bdpfile are the same, Ingest_AT needs a fix for this" b1 = SpwCube_BDP(bdpfile) self.addoutput(b1) if do_pb: print "doing the PB" bdpfile2 = self.mkext(basename, "pb") b2 = Image_BDP(bdpfile2) self.addoutput(b2) # @todo we should also set readonly=True if no box, no mask etc. and still an alias # that way it will speed up and not make a copy of the image ? # fni and fno are full (abspath) filenames, ready for CASA # fni is the same as fitsfile fni = self.dir(ffile0) fno = self.dir(bdpfile) if do_pb: fno2 = self.dir(bdpfile2) dt.tag("start") ia = taskinit.iatool() rg = taskinit.rgtool() if file_is_casa: ia.open(fni) else: if do_pb and use_pb: # @todo this needs a fix for the path for pb, only works if abs path is given # impbcor(im.fits,pb.fits,out.im,overwrite=True,mode='m') if False: # this may seem like a nice shortcut, to have the fits->casa conversion be done # internally in impbcor, but it's a terrible performance for big cubes. (tiling?) # we keep this code here, perhaps at some future time (mpi?) this performs better # @todo fno2 impbcor(fni, pb, fno, overwrite=True, mode='m') dt.tag("impbcor-1") else: # the better way is to convert FITS->CASA and then call impbcor() # the CPU savings are big, but I/O overhead can still be substantial _pbcor = utils.tmp_file('_pbcor_', '.') _pb = utils.tmp_file('_pb_', '.') ia.fromfits(_pbcor, fni, overwrite=True) ia.fromfits(_pb, pb, overwrite=True) dt.tag("impbcor-1f") if False: impbcor(_pbcor, _pb, fno, overwrite=True, mode='m') # @todo fno2 utils.remove(_pbcor) utils.remove(_pb) dt.tag("impbcor-2") else: # immath appears to be even faster (2x in CPU) # https://bugs.nrao.edu/browse/CAS-8299 # @todo this needs to be confirmed that impbcor is now good to go (r36078) casa.immath([_pbcor, _pb], 'evalexpr', fno, 'IM0*IM1') dt.tag("immath") if True: # use the mean of all channels... faster may be to use the middle plane # barf; edge channels can be with fewer subfields in a mosaic ia.open(_pb) s = ia.summary() #ia1=taskinit.ia.moments(moments=[-1],drop=True,outfile=fno2) # fails for cont maps ia1 = ia.collapse( outfile=fno2, function='mean', axes=2) # @todo ensure 2=freq axis ia1.done() ia.close() dt.tag("moments") utils.remove(_pbcor) utils.remove(_pb) dt.tag("impbcor-3") elif do_pb and not use_pb: # cheat case: PB was given, but not meant to be used # not implemented yet print "cheat case dummy PB not implemented yet" else: # no PB given if False: # re-running this was more consistently faster in wall clock time # note that zeroblanks=True will still keep the mask logging.debug("casa::ia.fromfits(%s) -> %s" % (fni, bdpfile)) ia.fromfits(fno, fni, overwrite=True) #taskinit.ia.fromfits(fno,fni,overwrite=True,zeroblanks=True) dt.tag("fromfits") else: # not working to extend 3D yet, but this would solve the impv() 3D problem logging.debug("casa::importfits(%s) -> %s" % (fni, bdpfile)) #casa.importfits(fni,fno,defaultaxes=True,defaultaxesvalues=[None,None,None,'I']) # possible bug: zeroblanks=True has no effect? casa.importfits(fni, fno, zeroblanks=True, overwrite=True) dt.tag("importfits") ia.close() ia.open(fno) if len(smooth) > 0: # smooth here, but Smooth_AT is another option # here we only allow pixel smoothing # spatial: gauss # spectral: boxcar/hanning (check for flux conservation) # is the boxcar wrong, not centered, but edged? # @todo CASA BUG: this will loose the object name (and maybe more?) from header, so VLSR lookup fails fnos = fno + '.smooth' ia.convolve2d(outfile=fnos, overwrite=True, pa='0deg', major='%gpix' % smooth[0], minor='%gpix' % smooth[1], type='gaussian') ia.close() srcname = casa.imhead(fno, mode="get", hdkey="object") # work around CASA bug #@todo use safer ia.rename() here. # https://casa.nrao.edu/docs/CasaRef/image.rename.html utils.rename(fnos, fno) casa.imhead(fno, mode="put", hdkey="object", hdvalue=srcname) # work around CASA bug dt.tag("convolve2d") if len(smooth) > 2 and smooth[2] > 0: if smooth[2] == 1: # @todo only 1 channel option specsmooth(fno, fnos, axis=2, function='hanning', dmethod="") else: # @todo may have the wrong center specsmooth(fno, fnos, axis=2, function='boxcar', dmethod="", width=smooth[2]) #@todo use safer ia.rename() here. # https://casa.nrao.edu/docs/CasaRef/image.rename.html utils.rename(fnos, fno) dt.tag("specsmooth") ia.open(fno) s = ia.summary() if len(s['shape']) != 4: logging.warning("Adding dummy STOKES-I axis") fnot = fno + '_4' ia2 = ia.adddegaxes(stokes='I', outfile=fnot) ia.close() ia2.close() #@todo use safer ia.rename() here. # https://casa.nrao.edu/docs/CasaRef/image.rename.html utils.rename(fnot, fno) ia.open(fno) dt.tag("adddegaxes") else: logging.info("SHAPE: %s" % str(s['shape'])) s = ia.summary() dt.tag("summary-0") if s['hasmask'] and create_mask: logging.warning( "no extra mask created because input image already had one") create_mask = False # if a box= or edge= was given, only a subset of the cube needs to be ingested # this however complicates PB correction later on if len(box) > 0 or len(edge) > 0: if readonly: raise Exception, "Cannot use box= or edge=, data is read-only, or use an basename/alias" if len(edge) == 1: edge.append(edge[0]) nx = s['shape'][0] ny = s['shape'][1] nz = s['shape'][2] logging.info("box=%s edge=%s processing with SHAPE: %s" % (str(box), str(edge), str(s['shape']))) if len(box) == 2: # select zrange if len(edge) > 0: raise Exception, "Cannot use edge= when box=[z1,z2] is used" r1 = rg.box([0, 0, box[0]], [nx - 1, ny - 1, box[1]]) elif len(box) == 4: if len(edge) == 0: # select just an XY box r1 = rg.box([box[0], box[1]], [box[2], box[3]]) elif len(edge) == 2: # select an XY box, but remove some edge channels r1 = rg.box([box[0], box[1], edge[0]], [box[2], box[3], nz - edge[1] - 1]) else: raise Exception, "Bad edge= for len(box)=4" elif len(box) == 6: # select an XYZ box r1 = rg.box([box[0], box[1], box[2]], [box[3], box[4], box[5]]) elif len(edge) == 2: # remove some edge channels, but keep the whole XY box r1 = rg.box([0, 0, edge[0]], [nx - 1, ny - 1, nz - edge[1] - 1]) else: raise Exception, "box=%s illegal" % box logging.debug("BOX/EDGE selection: %s %s" % (str(r1['blc']), str(r1['trc']))) #if taskinit.ia.isopen(): taskinit.ia.close() logging.info("SUBIMAGE") subimage = ia.subimage(region=r1, outfile=fno + '.box', overwrite=True) ia.close() ia.done() subimage.rename(fno, overwrite=True) subimage.close() subimage.done() ia.open(fno) dt.tag("subimage-1") else: # the whole cube is passed onto ADMIT if readonly and create_mask: raise Exception, "Cannot use mask=True, data read-only, or use an alias" if file_is_casa and not readonly: # @todo a miriad file - which should be read only - will also create a useless copy here if no alias used ia.subimage(overwrite=True, outfile=fno) ia.close() ia.open(fno) dt.tag("subimage-0") if create_mask: if readonly: raise Exception, "Cannot create mask, data read-only, or use an alias" # also check out the 'fromfits::zeroblanks = False' # calcmask() will overwrite any previous pixelmask #taskinit.ia.calcmask('mask("%s") && "%s" != 0.0' % (fno,fno)) ia.calcmask('"%s" != 0.0' % fno) dt.tag("mask") s = ia.summary() dt.tag("summary-1") # do a fast statistics (no median or robust) s0 = ia.statistics() dt.tag("statistics") if len(s0['npts']) == 0: raise Exception, "No statistics possible, are there valid data in this cube?" # There may be multiple beams per plane so we can't # rely on the BEAM's 'major', 'minor', 'positionangle' being present. # ia.commonbeam() is guaranteed to return beam parameters # if present if do_cbeam and 'perplanebeams' in s: # report on the beam extremities, need to loop over all, # first and last don't need to be extremes.... n = s['perplanebeams']['nChannels'] ab0 = '*0' bb0 = s['perplanebeams']['beams'][ab0]['*0'] bmaj0 = bb0['major']['value'] bmin0 = bb0['minor']['value'] beamd = 0.0 for i in range(n): ab1 = '*%d' % i bb1 = s['perplanebeams']['beams'][ab1]['*0'] bmaj1 = bb1['major']['value'] bmin1 = bb1['minor']['value'] beamd = max(beamd, abs(bmaj0 - bmaj1), abs(bmin0 - bmin1)) logging.warning("MAX-BEAMSPREAD %f" % (beamd)) # if True: logging.info( "Applying a commonbeam from the median beam accross the band" ) # imhead is a bit slow; alternatively use ia.summary() at the half point for setrestoringbeam() h = casa.imhead(fno, mode='list') b = h['perplanebeams']['median area beam'] ia.setrestoringbeam(remove=True) ia.setrestoringbeam(beam=b) commonbeam = ia.commonbeam() else: # @todo : this will be VERY slow - code not finished, needs renaming etc. # this is however formally the better solution logging.warning("commmonbeam code not finished") cb = ia.commonbeam() ia.convolve2d(outfile='junk-common.im', major=cb['major'], minor=cb['minor'], pa=cb['pa'], targetres=True, overwrite=True) dt.tag('convolve2d') commonbeam = {} else: try: commonbeam = ia.commonbeam() except: nppb = 4.0 logging.warning( "No synthesized beam found, faking one to prevent downstream problems: nppb=%f" % nppb) s = ia.summary() cdelt2 = abs(s['incr'][0]) * 180.0 / math.pi * 3600.0 bmaj = nppb * cdelt2 # use a nominal 4 points per (round) beam bmin = nppb * cdelt2 bpa = 0.0 ia.setrestoringbeam(major='%farcsec' % bmaj, minor='%farcsec' % bmin, pa='%fdeg' % bpa) commonbeam = {} logging.info("COMMONBEAM[%d] %s" % (len(commonbeam), str(commonbeam))) first_point = ia.getchunk(blc=[0, 0, 0, 0], trc=[0, 0, 0, 0], dropdeg=True) logging.debug("DATA0*: %s" % str(first_point)) ia.close() logging.info('BASICS: [shape] npts min max: %s %d %f %f' % (s['shape'], s0['npts'][0], s0['min'][0], s0['max'][0])) logging.info('S/N (all data): %f' % (s0['max'][0] / s0['rms'][0])) npix = 1 nx = s['shape'][0] ny = s['shape'][1] nz = s['shape'][2] for n in s['shape']: npix = npix * n ngood = int(s0['npts'][0]) fgood = (1.0 * ngood) / npix logging.info('GOOD PIXELS: %d/%d (%f%% good or %f%% bad)' % (ngood, npix, 100.0 * fgood, 100.0 * (1 - fgood))) if s['hasmask']: logging.warning('MASKS: %s' % (str(s['masks']))) if not file_is_casa: b1.setkey("image", Image(images={bt.CASA: bdpfile})) if do_pb: b2.setkey("image", Image(images={bt.CASA: bdpfile2})) # cube sanity: needs to be either 4D or 2D. But p-p-v cube # alternative: ia.subimage(dropdeg = True) # see also: https://bugs.nrao.edu/browse/CAS-5406 shape = s['shape'] if len(shape) > 3: if shape[3] > 1: # @todo this happens when you ingest a fits or casa image which is ra-dec-pol-freq if nz > 1: msg = 'Ingest_AT: cannot deal with real 4D cubes yet' logging.critical(msg) raise Exception, msg else: # @todo this is not working yet when the input was a casa image, but ok when fits. go figure. fnot = fno + ".trans" if True: # this works #@todo use safer ia.rename() here. # https://casa.nrao.edu/docs/CasaRef/image.rename.html utils.rename(fno, fnot) imtrans(fnot, fno, "0132") utils.remove(fnot) else: # this does not work, what the heck imtrans(fno, fnot, "0132") #@todo use safer ia.rename() here. # https://casa.nrao.edu/docs/CasaRef/image.rename.html utils.rename(fnot, fno) nz = s['shape'][3] # get a new summary 's' ia.open(fno) s = ia.summary() ia.close() logging.warning( "Using imtrans, with nz=%d, to fix axis ordering" % nz) dt.tag("imtrans4") # @todo ensure first two axes are position, followed by frequency elif len(shape) == 3: # the current importfits() can do defaultaxes=True,defaultaxesvalues=['', '', '', 'I'] # but then appears to return a ra-dec-pol-freq cube # this branch probably never happens, since ia.fromfits() will # properly convert a 3D cube to 4D now !! # NO: when NAXIS=3 but various AXIS4's are present, that works. But not if it's pure 3D # @todo box= logging.warning("patching up a 3D to 4D cube") raise Exception, "SHOULD NEVER GET HERE" fnot = fno + ".trans" casa.importfits(fni, fnot, defaultaxes=True, defaultaxesvalues=['', '', '', 'I']) utils.remove(fno) # ieck imtrans(fnot, fno, "0132") utils.remove(fnot) dt.tag("imtrans3") logging.regression( 'CUBE: %g %g %g %d %d %d %f' % (s0['min'], s0['max'], s0['rms'], nx, ny, nz, 100.0 * (1 - fgood))) # if the cube has only 1 plane (e.g. continuum) , create a visual (png or so) # for 3D cubes, rely on something like CubeSum if nz == 1: implot = ImPlot(pmode=self._plot_mode, ptype=self._plot_type, abspath=self.dir()) implot.plotter(rasterfile=bdpfile, figname=bdpfile) # @todo needs to be registered for the BDP, right now we only have the plot # ia.summary() doesn't have this easily available, so run the more expensive imhead() h = casa.imhead(fno, mode='list') telescope = h['telescope'] # work around CASA's PIPELINE bug/feature? if 'OBJECT' is blank, try 'FIELD' srcname = h['object'] if srcname == ' ': logging.warning('FIELD used for OBJECT') srcname = casa.imhead(fno, mode='get', hdkey='field') if srcname == False: # if no FIELD either, we're doomed. yes, this did happen. srcname = 'Unknown' casa.imhead(fno, mode="put", hdkey="object", hdvalue=srcname) h['object'] = srcname logging.info('TELESCOPE: %s' % telescope) if telescope == 'UNKNOWN': msg = 'Ingest_AT: warning, an UNKNOWN telescope often results in ADMIT failing' logging.warning(msg) logging.info('OBJECT: %s' % srcname) logging.info('REFFREQTYPE: %s' % h['reffreqtype']) if h['reffreqtype'].find('TOPO') >= 0: msg = 'Ingest_AT: cannot deal with cubes with TOPOCENTRIC frequencies yet - winging it' logging.warning(msg) #raise Exception,msg # Ensure beam parameters are available if there are multiple beams # If there is just one beam, then we are just overwriting the header # variables with their identical values. if len(commonbeam) != 0: h['beammajor'] = commonbeam['major'] h['beamminor'] = commonbeam['minor'] h['beampa'] = commonbeam['pa'] # cheat add some things that need to be passed to summary.... h['badpixel'] = 1.0 - fgood if vlsr < -999998.0: vlsr = admit.VLSR().vlsr(h['object'].upper()) if vlsr == 0.0: vlsr = -999999.99 h['vlsr'] = vlsr logging.info("VLSR = %f (from source catalog)" % vlsr) taskargs = "file=" + fitsfile if create_mask == True: taskargs = taskargs + " mask=True" if len(box) > 0: taskargs = taskargs + " " + str(box) if len(edge) > 0: taskargs = taskargs + " " + str(edge) r2d = 57.29577951308232 logging.info( "RA Axis 1: %f %f %f" % (h['crval1'] * r2d, h['cdelt1'] * r2d * 3600.0, h['crpix1'])) logging.info( "DEC Axis 2: %f %f %f" % (h['crval2'] * r2d, h['cdelt2'] * r2d * 3600.0, h['crpix2'])) if nz > 1: # @todo check if this is really a freq axis (for ALMA it is, but...) t3 = h['ctype3'] df = h['cdelt3'] fc = h['crval3'] + (0.5 * (float(shape[2]) - 1) - h['crpix3'] ) * df # center freq; 0 based pixels if 'restfreq' in h: fr = float( h['restfreq'][0] ) # casa cheats, it may put 0 in here if FITS is missing it if fr == 0.0: fr = fc else: if vlsr < -999998.0: vlsr = (1 - fc / fr) * ckms h['vlsr'] = vlsr else: fr = fc fw = df * float(shape[2]) print "PJT:", fr / 1e9, fc / 1e9, fw / 1e9 dv = -df / fr * ckms logging.info("Freq Axis 3: %g %g %g" % (h['crval3'] / 1e9, h['cdelt3'] / 1e9, h['crpix3'])) logging.info( "Cube Axis 3: type=%s velocity increment=%f km/s @ fc=%f fw=%f GHz" % (t3, dv, fc / 1e9, fw / 1e9)) # @todo sort out this restfreq/vlsr # report 'reffreqtype', 'restfreq' 'telescope' # if the fits file has ALTRVAL/ALTRPIX, this is lost in CASA? # but if you do fits->casa->fits , it's back in fits (with some obvious single precision loss of digits) # @todo ZSOURCE is the proposed VLSR slot in the fits header, but this has frame issues (it's also optical) # # Another method to get the vlsr is to override the restfreq (f0) with an AT keyword # and the 'restfreq' from the header (f) is then used to compute the vlsr: v = c (1 - f/f0) # if shape[2] > 1 and 'restfreq' in h: logging.info("RESTFREQ: %g %g %g" % (fr / 1e9, h['restfreq'][0] / 1e9, restfreq)) if shape[2] > 1: # v_radio of the center of the window w.r.t. restfreq vlsrc = ckms * (1 - fc / fr) # @todo rel frame? vlsrw = dv * float(shape[2]) if restfreq > 0: vlsrf = ckms * (1 - fr / restfreq / 1e9) h['vlsr'] = vlsrf else: vlsrf = 0.0 logging.info("VLSRc = %f VLSRw = %f VLSRf = %f VLSR = %f" % (vlsrc, vlsrw, vlsrf, vlsr)) if h['vlsr'] == 0.0: # @todo! This fails if vlsr actually is zero. Need another magic number h['vlsr'] = vlsrc logging.warning( "Warning: No VLSR found, substituting VLSRc = %f" % vlsrc) else: msg = 'Ingest_AT: missing RESTFREQ' print msg # @todo LINTRN is the ALMA keyword that designates the expected line transition in a spw self._summarize(fitsfile, bdpfile, h, shape, taskargs) dt.tag("done") dt.end()
def mk_diskmodel(outname='disk', bdwidth='325MHz', direction='J2000 10h00m00.0s 20d00m00.0s', reffreq='2.8GHz', flux=660000.0, eqradius='16.166arcmin', polradius='16.166arcmin', pangle='21.1deg', index=None, cell='2.0arcsec', overwrite=True): ''' Create a blank solar disk model image (or optionally a data cube) outname String to use for part of the image and fits file names (default 'disk') direction String specifying the position of the Sun in RA and Dec. Default means use the standard string "J2000 10h00m00.0s 20d00m00.0s" reffreq The reference frequency to use for the disk model (the frequency at which the flux level applies). Default is '2.8GHz'. flux The flux density, in Jy, for the entire disk. Default is 66 sfu. eqradius The equatorial radius of the disk. Default is 16 arcmin + 10" (for typical extension of the radio limb) polradius The polar radius of the disk. Default is 16 arcmin + 10" (for typical extension of the radio limb) pangle The solar P-angle (geographic position of the N-pole of the Sun) in degrees E of N. This only matters if eqradius != polradius index The spectral index to use at other frequencies. Default None means use a constant flux density for all frequencies. cell The cell size (assumed square) to use for the image. The image size is determined from a standard radius of 960" for the Sun, divided by cell size, increased to nearest power of 512 pixels. The default is '2.0arcsec', which results in an image size of 1024 x 1024. Note that the frequency increment used is '325MHz', which is the width of EOVSA bands (not the width of individual science channels) ''' diskim = outname + reffreq + '.im' if os.path.exists(diskim): if overwrite: os.system('rm -rf {}'.format(diskim)) else: return diskim ia = iatool() cl = cltool() cl.done() ia.done() try: aspect = 1.01 # Enlarge the equatorial disk by 1% eqradius = qa.quantity(eqradius) diamajor = qa.quantity(2 * aspect * eqradius['value'], eqradius['unit']) polradius = qa.quantity(polradius) diaminor = qa.quantity(2 * polradius['value'], polradius['unit']) solrad = qa.convert(polradius, 'arcsec') except: print('Radius', eqradius, polradius, 'does not have the expected format, number + unit where unit is arcmin or arcsec') return try: cell = qa.convert(qa.quantity(cell), 'arcsec') cellsize = float(cell['value']) diskpix = solrad['value'] * 2 / cellsize cell_rad = qa.convert(cell, 'rad') except: print('Cell size', cell, 'does not have the expected format, number + unit where unit is arcmin or arcsec') return # Add 90 degrees to pangle, due to angle definition in addcomponent() -- it puts the majoraxis vertical pangle = qa.add(qa.quantity(pangle), qa.quantity('90deg')) mapsize = ((int(diskpix) / 512) + 1) * 512 # Flux density is doubled because it is split between XX and YY cl.addcomponent(dir=direction, flux=flux * 2, fluxunit='Jy', freq=reffreq, shape='disk', majoraxis=diamajor, minoraxis=diaminor, positionangle=pangle) cl.setrefdirframe(0, 'J2000') ia.fromshape(diskim, [mapsize, mapsize, 1, 1], overwrite=True) cs = ia.coordsys() cs.setunits(['rad', 'rad', '', 'Hz']) cell_rad_val = cell_rad['value'] cs.setincrement([-cell_rad_val, cell_rad_val], 'direction') epoch, ra, dec = direction.split() cs.setreferencevalue([qa.convert(ra, 'rad')['value'], qa.convert(dec, 'rad')['value']], type="direction") cs.setreferencevalue(reffreq, 'spectral') cs.setincrement(bdwidth, 'spectral') ia.setcoordsys(cs.torecord()) ia.setbrightnessunit("Jy/pixel") ia.modify(cl.torecord(), subtract=False) ia.close() ia.done() # cl.close() cl.done() return diskim
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 # tools we need ia = taskinit.iatool() # 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) nzeros = len(np.where(sigma <= 0.0)[0]) if nzeros > 0: zeroch = np.where(sigma <= 0.0)[0] logging.warning("There are %d fully masked channels (%s)" % (nzeros, str(zeroch))) # @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") # 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 # Note: log10(-minval) will fail with a runtime domain error if all values # in minval are positive , so do a check here and only call log10 if there # is at least one negative value in minval. # RuntimeWarning: invalid value encountered in log10 if (minval <= 0).all(): y4 = np.log10(ma.masked_invalid(-minval)) else: y4 = np.zeros(len(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) ia.done() # is that a good habit? dt.tag("summary") dt.end()
def imreg(vis=None, ephem=None, msinfo=None, imagefile=None, timerange=None, reftime=None, fitsfile=None, beamfile=None, offsetfile=None, toTb=None, scl100=None, verbose=False, p_ang=False, overwrite=True, usephacenter=True, deletehistory=False): ''' main routine to register CASA images Required Inputs: vis: STRING. CASA measurement set from which the image is derived imagefile: STRING or LIST. name of the input CASA image timerange: STRING or LIST. timerange used to generate the CASA image, must have the same length as the input images. Each element should be in CASA standard time format, e.g., '2012/03/03/12:00:00~2012/03/03/13:00:00' Optional Inputs: msinfo: DICTIONARY. CASA MS information, output from read_msinfo. If not provided, generate one from the supplied vis ephem: DICTIONARY. solar ephem, output from read_horizons. If not provided, query JPL Horizons based on time info of the vis (internet connection required) fitsfile: STRING or LIST. name of the output registered fits files reftime: STRING or LIST. Each element should be in CASA standard time format, e.g., '2012/03/03/12:00:00' offsetfile: optionally provide an offset with a series of solar x and y offsets with timestamps toTb: Bool. Convert the default Jy/beam to brightness temperature? scl100: Bool. If True, scale the image values up by 100 (to compensate VLA 20 dB attenuator) verbose: Bool. Show more diagnostic info if True. usephacenter: Bool -- if True, correct for the RA and DEC in the ms file based on solar empheris. Otherwise assume the phasecenter is correctly pointed to the solar disk center (EOVSA case) ''' ia = iatool() if deletehistory: msclearhistory(vis) if verbose: import time t0 = time.time() prtidx = 1 print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 if not imagefile: raise ValueError, 'Please specify input image' if not timerange: raise ValueError, 'Please specify timerange of the input image' if type(imagefile) == str: imagefile = [imagefile] if type(timerange) == str: timerange = [timerange] if not fitsfile: fitsfile = [img + '.fits' for img in imagefile] if type(fitsfile) == str: fitsfile = [fitsfile] nimg = len(imagefile) if len(timerange) != nimg: raise ValueError, 'Number of input images does not equal to number of timeranges!' if len(fitsfile) != nimg: raise ValueError, 'Number of input images does not equal to number of output fits files!' nimg = len(imagefile) if verbose: print str(nimg) + ' images to process...' if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 if reftime: # use as reference time to find solar disk RA and DEC to register the image, but not the actual timerange associated with the image if type(reftime) == str: reftime = [reftime] * nimg if len(reftime) != nimg: raise ValueError, 'Number of reference times does not match that of input images!' helio = ephem_to_helio(vis, ephem=ephem, msinfo=msinfo, reftime=reftime, usephacenter=usephacenter) else: # use the supplied timerange to register the image helio = ephem_to_helio(vis, ephem=ephem, msinfo=msinfo, reftime=timerange, usephacenter=usephacenter) if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 for n, img in enumerate(imagefile): if verbose: print 'processing image #' + str(n) fitsf = fitsfile[n] timeran = timerange[n] # obtain duration of the image as FITS header exptime try: [tbg0, tend0] = timeran.split('~') tbg_d = qa.getvalue(qa.convert(qa.totime(tbg0), 'd'))[0] tend_d = qa.getvalue(qa.convert(qa.totime(tend0), 'd'))[0] tdur_s = (tend_d - tbg_d) * 3600. * 24. dateobs = qa.time(qa.quantity(tbg_d, 'd'), form='fits', prec=10)[0] except: print 'Error in converting the input timerange: ' + str( timeran) + '. Proceeding to the next image...' continue if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 hel = helio[n] if not os.path.exists(img): raise ValueError, 'Please specify input image' if os.path.exists(fitsf) and not overwrite: raise ValueError, 'Specified fits file already exists and overwrite is set to False. Aborting...' else: p0 = hel['p0'] ia.open(img) imr = ia.rotate(pa=str(-p0) + 'deg') imr.tofits(fitsf, history=False, overwrite=overwrite) imr.close() imsum = ia.summary() ia.close() if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 # construct the standard fits header # RA and DEC of the reference pixel crpix1 and crpix2 (imra, imdec) = (imsum['refval'][0], imsum['refval'][1]) # find out the difference of the image center to the CASA phase center # RA and DEC difference in arcseconds ddec = degrees((imdec - hel['dec_fld'])) * 3600. dra = degrees((imra - hel['ra_fld']) * cos(hel['dec_fld'])) * 3600. # Convert into image heliocentric offsets prad = -radians(hel['p0']) dx = (-dra) * cos(prad) - ddec * sin(prad) dy = (-dra) * sin(prad) + ddec * cos(prad) if offsetfile: try: offset = np.load(offsetfile) except: raise ValueError, 'The specified offsetfile does not exist!' reftimes_d = offset['reftimes_d'] xoffs = offset['xoffs'] yoffs = offset['yoffs'] timg_d = hel['reftime'] ind = bisect.bisect_left(reftimes_d, timg_d) xoff = xoffs[ind - 1] yoff = yoffs[ind - 1] else: xoff = hel['refx'] yoff = hel['refy'] if verbose: print 'offset of image phase center to visibility phase center (arcsec): ', dx, dy print 'offset of visibility phase center to solar disk center (arcsec): ', xoff, yoff (crval1, crval2) = (xoff + dx, yoff + dy) # update the fits header to heliocentric coordinates if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 hdu = pyfits.open(fitsf, mode='update') if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 header = hdu[0].header (cdelt1, cdelt2) = (-header['cdelt1'] * 3600., header['cdelt2'] * 3600. ) # Original CDELT1, 2 are for RA and DEC in degrees header['cdelt1'] = cdelt1 header['cdelt2'] = cdelt2 header['cunit1'] = 'arcsec' header['cunit2'] = 'arcsec' header['crval1'] = crval1 header['crval2'] = crval2 header['ctype1'] = 'HPLN-TAN' header['ctype2'] = 'HPLT-TAN' header['date-obs'] = dateobs # begin time of the image if not p_ang: hel['p0'] = 0 try: # this works for pyfits version of CASA 4.7.0 but not CASA 4.6.0 if tdur_s: header.set('exptime', tdur_s) else: header.set('exptime', 1.) header.set('p_angle', hel['p0']) header.set('dsun_obs', sun.sunearth_distance(Time(dateobs)).to(u.meter).value) header.set( 'rsun_obs', sun.solar_semidiameter_angular_size(Time(dateobs)).value) header.set('rsun_ref', sun.constants.radius.value) header.set('hgln_obs', 0.) header.set('hglt_obs', sun.heliographic_solar_center(Time(dateobs))[1].value) except: # this works for astropy.io.fits if tdur_s: header.append(('exptime', tdur_s)) else: header.append(('exptime', 1.)) header.append(('p_angle', hel['p0'])) header.append( ('dsun_obs', sun.sunearth_distance(Time(dateobs)).to(u.meter).value)) header.append( ('rsun_obs', sun.solar_semidiameter_angular_size(Time(dateobs)).value)) header.append(('rsun_ref', sun.constants.radius.value)) header.append(('hgln_obs', 0.)) header.append( ('hglt_obs', sun.heliographic_solar_center(Time(dateobs))[1].value)) if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 # update intensity units, i.e. to brightness temperature? if toTb: # get restoring beam info (bmajs, bmins, bpas, beamunits, bpaunits) = getbeam(imagefile=imagefile, beamfile=beamfile) bmaj = bmajs[n] bmin = bmins[n] beamunit = beamunits[n] data = hdu[ 0].data # remember the data order is reversed due to the FITS convension dim = data.ndim sz = data.shape keys = header.keys() values = header.values() # which axis is frequency? faxis = keys[values.index('FREQ')][-1] faxis_ind = dim - int(faxis) if header['BUNIT'].lower() == 'jy/beam': header['BUNIT'] = 'K' header['BTYPE'] = 'Brightness Temperature' for i in range(sz[faxis_ind]): nu = header['CRVAL' + faxis] + header['CDELT' + faxis] * ( i + 1 - header['CRPIX' + faxis]) if header['CUNIT' + faxis] == 'KHz': nu *= 1e3 if header['CUNIT' + faxis] == 'MHz': nu *= 1e6 if header['CUNIT' + faxis] == 'GHz': nu *= 1e9 if len(bmaj) > 1: # multiple (per-plane) beams bmajtmp = bmaj[i] bmintmp = bmin[i] else: # one single beam bmajtmp = bmaj[0] bmintmp = bmin[0] if beamunit == 'arcsec': bmaj0 = np.radians(bmajtmp / 3600.) bmin0 = np.radians(bmajtmp / 3600.) if beamunit == 'arcmin': bmaj0 = np.radians(bmajtmp / 60.) bmin0 = np.radians(bmintmp / 60.) if beamunit == 'deg': bmaj0 = np.radians(bmajtmp) bmin0 = np.radians(bmintmp) if beamunit == 'rad': bmaj0 = bmajtmp bmin0 = bmintmp beam_area = bmaj0 * bmin0 * np.pi / (4. * log(2.)) k_b = qa.constants('k')['value'] c_l = qa.constants('c')['value'] factor = 2. * k_b * nu**2 / c_l**2 # SI unit jy_to_si = 1e-26 # print nu/1e9, beam_area, factor factor2 = 1. if scl100: factor2 = 100. if faxis == '3': data[:, i, :, :] *= jy_to_si / beam_area / factor * factor2 if faxis == '4': data[ i, :, :, :] *= jy_to_si / beam_area / factor * factor2 if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1 hdu.flush() hdu.close() if verbose: print('point {}: {}'.format(prtidx, time.time() - t0)) prtidx += 1
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})) ia = taskinit.iatool() 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= ia.open(self.dir(self._bdp_in[0].getimagefile(bt.CASA))) ia.regrid(outfile=self.dir(outfile)) ia.close() else: # 2D for now if not useClone: logging.info("OVERLAP out=%s" % outfile) ia.fromimage(infile=self.dir(self._bdp_in[0].getimagefile( bt.CASA)), outfile=self.dir(outfile), overwrite=True) 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: ia.open(self.dir(outfile)) s1 = ia.shape() s0 = [0, 0, 0, 0] rg = taskinit.rgtool() r1 = rg.box(blc=s0, trc=s1) pixeldata = out.data pixelmask = ~out.mask ia.putregion(pixels=pixeldata, pixelmask=pixelmask, region=r1) 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 imreg(vis=None, ephem=None, msinfo=None, reftime=None, imagefile=None, fitsfile=None, beamfile=None, \ offsetfile=None, toTb=None, scl100=None, verbose=False, p_ang = False, overwrite = True, usephacenter=False): ia = iatool() if not imagefile: raise ValueError, 'Please specify input image' if not reftime: raise ValueError, 'Please specify reference time corresponding to the input image' if not fitsfile: fitsfile = [img + '.fits' for img in imagefile] if len(imagefile) != len(reftime): raise ValueError, 'Number of input images does not equal to number of helio coord headers!' if len(imagefile) != len(fitsfile): raise ValueError, 'Number of input images does not equal to number of output fits files!' nimg = len(imagefile) if verbose: print str(nimg) + ' images to process...' helio = ephem_to_helio(vis, ephem=ephem, msinfo=msinfo, reftime=reftime, usephacenter=usephacenter) for n, img in enumerate(imagefile): if verbose: print 'processing image #' + str(n) fitsf = fitsfile[n] hel = helio[n] if not os.path.exists(img): raise ValueError, 'Please specify input image' if os.path.exists(fitsf) and not overwrite: raise ValueError, 'Specified fits file already exists and overwrite is set to False. Aborting...' else: p0 = hel['p0'] ia.open(img) imr = ia.rotate(pa=str(-p0) + 'deg') imr.tofits(fitsf, history=False, overwrite=overwrite) imr.close() sum = ia.summary() ia.close() # construct the standard fits header # RA and DEC of the reference pixel crpix1 and crpix2 (imra, imdec) = (sum['refval'][0], sum['refval'][1]) # find out the difference of the image center to the CASA phase center # RA and DEC difference in arcseconds ddec = degrees((imdec - hel['dec_fld'])) * 3600. dra = degrees((imra - hel['ra_fld']) * cos(hel['dec_fld'])) * 3600. # Convert into image heliocentric offsets prad = -radians(hel['p0']) dx = (-dra) * cos(prad) - ddec * sin(prad) dy = (-dra) * sin(prad) + ddec * cos(prad) if offsetfile: try: offset = np.load(offsetfile) except: raise ValueError, 'The specified offsetfile does not exist!' reftimes_d = offset['reftimes_d'] xoffs = offset['xoffs'] yoffs = offset['yoffs'] timg_d = hel['reftime'] ind = bisect.bisect_left(reftimes_d, timg_d) xoff = xoffs[ind - 1] yoff = yoffs[ind - 1] else: xoff = hel['refx'] yoff = hel['refy'] if verbose: print 'offset of image phase center to visibility phase center (arcsec): ', dx, dy print 'offset of visibility phase center to solar disk center (arcsec): ', xoff, yoff (crval1, crval2) = (xoff + dx, yoff + dy) # update the fits header to heliocentric coordinates hdu = pyfits.open(fitsf, mode='update') header = hdu[0].header (cdelt1, cdelt2) = (-header['cdelt1'] * 3600., header['cdelt2'] * 3600. ) # Original CDELT1, 2 are for RA and DEC in degrees header['cdelt1'] = cdelt1 header['cdelt2'] = cdelt2 header['cunit1'] = 'arcsec' header['cunit2'] = 'arcsec' header['crval1'] = crval1 header['crval2'] = crval2 header['ctype1'] = 'HPLN-TAN' header['ctype2'] = 'HPLT-TAN' header['date-obs'] = hel['date-obs'] #begin time of the image if not p_ang: hel['p0'] = 0 try: # this works for pyfits version of CASA 4.7.0 but not CASA 4.6.0 header.update('exptime', hel['exptime']) header.update('p_angle', hel['p0']) header.update( 'dsun_obs', sun.sunearth_distance(Time(hel['date-obs'])).to(u.meter).value) header.update( 'rsun_obs', sun.solar_semidiameter_angular_size(Time( hel['date-obs'])).value) header.update('rsun_ref', sun.constants.radius.value) header.update('hgln_obs', 0.) header.update( 'hglt_obs', sun.heliographic_solar_center(Time(hel['date-obs']))[1].value) except: # this works for astropy.io.fits header.append(('exptime', hel['exptime'])) header.append(('p_angle', hel['p0'])) header.append( ('dsun_obs', sun.sunearth_distance(Time(hel['date-obs'])).to( u.meter).value)) header.append(('rsun_obs', sun.solar_semidiameter_angular_size( Time(hel['date-obs'])).value)) header.append(('rsun_ref', sun.constants.radius.value)) header.append(('hgln_obs', 0.)) header.append(('hglt_obs', sun.heliographic_solar_center(Time( hel['date-obs']))[1].value)) # header.update('comment', 'Fits header updated to heliocentric coordinates by Bin Chen') # update intensity units, i.e. to brightness temperature? if toTb: # get restoring beam info (bmajs, bmins, bpas, beamunits, bpaunits) = getbeam(imagefile=imagefile, beamfile=beamfile) bmaj = bmajs[n] bmin = bmins[n] beamunit = beamunits[n] data = hdu[ 0].data # remember the data order is reversed due to the FITS convension dim = data.ndim sz = data.shape keys = header.keys() values = header.values() # which axis is frequency? faxis = keys[values.index('FREQ')][-1] faxis_ind = dim - int(faxis) if header['BUNIT'].lower() == 'jy/beam': header['BUNIT'] = 'K' for i in range(sz[faxis_ind]): nu = header['CRVAL' + faxis] + header['CDELT' + faxis] * \ (i + 1 - header['CRPIX' + faxis]) if header['CUNIT' + faxis] == 'KHz': nu *= 1e3 if header['CUNIT' + faxis] == 'MHz': nu *= 1e6 if header['CUNIT' + faxis] == 'GHz': nu *= 1e9 if len(bmaj) > 1: # multiple (per-plane) beams bmajtmp = bmaj[i] bmintmp = bmin[i] else: # one single beam bmajtmp = bmaj[0] bmintmp = bmin[0] if beamunit == 'arcsec': bmaj0 = np.radians(bmajtmp / 3600.) bmin0 = np.radians(bmajtmp / 3600.) if beamunit == 'arcmin': bmaj0 = np.radians(bmajtmp / 60.) bmin0 = np.radians(bmintmp / 60.) if beamunit == 'deg': bmaj0 = np.radians(bmajtmp) bmin0 = np.radians(bmintmp) if beamunit == 'rad': bmaj0 = bmajtmp bmin0 = bmintmp beam_area = bmaj0 * bmin0 * np.pi / (4. * log(2.)) k_b = qa.constants('k')['value'] c_l = qa.constants('c')['value'] factor = 2. * k_b * nu**2 / c_l**2 # SI unit jy_to_si = 1e-26 # print nu/1e9, beam_area, factor factor2 = 1. if scl100: factor2 = 100. if faxis == '3': data[:, i, :, :] *= jy_to_si / beam_area / factor * factor2 if faxis == '4': data[ i, :, :, :] *= jy_to_si / beam_area / factor * factor2 hdu.flush() hdu.close()
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 += ' <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 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()
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) ia = taskinit.iatool() ia.open(self.dir(f1)) s = 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: ia.open(self.dir(f1)) ia.continuumsub(outline=self.dir(f2), outcont=self.dir(f3a), channels=ch, fitorder=fitorder) 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 imreg(vis=None, ephem=None, msinfo=None, imagefile=None, timerange=None, reftime=None, fitsfile=None, beamfile=None, offsetfile=None, toTb=None, sclfactor=1.0, verbose=False, p_ang=False, overwrite=True, usephacenter=True, deletehistory=False, subregion=[], docompress=False): ''' main routine to register CASA images Required Inputs: vis: STRING. CASA measurement set from which the image is derived imagefile: STRING or LIST. name of the input CASA image timerange: STRING or LIST. timerange used to generate the CASA image, must have the same length as the input images. Each element should be in CASA standard time format, e.g., '2012/03/03/12:00:00~2012/03/03/13:00:00' Optional Inputs: msinfo: DICTIONARY. CASA MS information, output from read_msinfo. If not provided, generate one from the supplied vis ephem: DICTIONARY. solar ephem, output from read_horizons. If not provided, query JPL Horizons based on time info of the vis (internet connection required) fitsfile: STRING or LIST. name of the output registered fits files reftime: STRING or LIST. Each element should be in CASA standard time format, e.g., '2012/03/03/12:00:00' offsetfile: optionally provide an offset with a series of solar x and y offsets with timestamps toTb: Bool. Convert the default Jy/beam to brightness temperature? sclfactor: scale the image values up by its value (to compensate VLA 20 dB attenuator) verbose: Bool. Show more diagnostic info if True. usephacenter: Bool -- if True, correct for the RA and DEC in the ms file based on solar empheris. Otherwise assume the phasecenter is correctly pointed to the solar disk center (EOVSA case) subregion: Region selection. See 'help par.region' for details. Usage: >>> from suncasa.utils import helioimage2fits as hf >>> hf.imreg(vis='mydata.ms', imagefile='myimage.image', fitsfile='myimage.fits', timerange='2017/08/21/20:21:10~2017/08/21/20:21:18') The output fits file is 'myimage.fits' History: BC (sometime in 2014): function was first wrote, followed by a number of edits by BC and SY BC (2019-07-16): Added checks for stokes parameter. Verified that for converting from Jy/beam to brightness temperature, the convention of 2*k_b*T should always be used. I.e., for unpolarized source, stokes I, RR, LL, XX, YY, etc. in the output CASA images from (t)clean should all have same values of radio intensity (in Jy/beam) and brightness temperature (in K). ''' ia = iatool() if deletehistory: ms_clearhistory(vis) if not imagefile: raise ValueError('Please specify input image') if not timerange: raise ValueError('Please specify timerange of the input image') if type(imagefile) == str: imagefile = [imagefile] if type(timerange) == str: timerange = [timerange] if not fitsfile: fitsfile = [img + '.fits' for img in imagefile] if type(fitsfile) == str: fitsfile = [fitsfile] nimg = len(imagefile) if len(timerange) != nimg: raise ValueError( 'Number of input images does not equal to number of timeranges!') if len(fitsfile) != nimg: raise ValueError( 'Number of input images does not equal to number of output fits files!' ) nimg = len(imagefile) if verbose: print(str(nimg) + ' images to process...') if reftime: # use as reference time to find solar disk RA and DEC to register the image, but not the actual timerange associated with the image if type(reftime) == str: reftime = [reftime] * nimg if len(reftime) != nimg: raise ValueError( 'Number of reference times does not match that of input images!' ) helio = ephem_to_helio(vis, ephem=ephem, msinfo=msinfo, reftime=reftime, usephacenter=usephacenter) else: # use the supplied timerange to register the image helio = ephem_to_helio(vis, ephem=ephem, msinfo=msinfo, reftime=timerange, usephacenter=usephacenter) if toTb: (bmajs, bmins, bpas, beamunits, bpaunits) = getbeam(imagefile=imagefile, beamfile=beamfile) for n, img in enumerate(imagefile): if verbose: print('processing image #' + str(n) + ' ' + img) fitsf = fitsfile[n] timeran = timerange[n] # obtain duration of the image as FITS header exptime try: [tbg0, tend0] = timeran.split('~') tbg_d = qa.getvalue(qa.convert(qa.totime(tbg0), 'd'))[0] tend_d = qa.getvalue(qa.convert(qa.totime(tend0), 'd'))[0] tdur_s = (tend_d - tbg_d) * 3600. * 24. dateobs = qa.time(qa.quantity(tbg_d, 'd'), form='fits', prec=10)[0] except: print('Error in converting the input timerange: ' + str(timeran) + '. Proceeding to the next image...') continue hel = helio[n] if not os.path.exists(img): warnings.warn('{} does not existed!'.format(img)) else: if os.path.exists(fitsf) and not overwrite: raise ValueError( 'Specified fits file already exists and overwrite is set to False. Aborting...' ) else: p0 = hel['p0'] tb.open(img + '/logtable', nomodify=False) nobs = tb.nrows() tb.removerows([i + 1 for i in range(nobs - 1)]) tb.close() ia.open(img) imr = ia.rotate(pa=str(-p0) + 'deg') if subregion is not []: imr = imr.subimage(region=subregion) imr.tofits(fitsf, history=False, overwrite=overwrite) imr.close() imsum = ia.summary() ia.close() ia.done() # construct the standard fits header # RA and DEC of the reference pixel crpix1 and crpix2 (imra, imdec) = (imsum['refval'][0], imsum['refval'][1]) # find out the difference of the image center to the CASA phase center # RA and DEC difference in arcseconds ddec = degrees((imdec - hel['dec_fld'])) * 3600. dra = degrees((imra - hel['ra_fld']) * cos(hel['dec_fld'])) * 3600. # Convert into image heliocentric offsets prad = -radians(hel['p0']) dx = (-dra) * cos(prad) - ddec * sin(prad) dy = (-dra) * sin(prad) + ddec * cos(prad) if offsetfile: try: offset = np.load(offsetfile) except: raise ValueError( 'The specified offsetfile does not exist!') reftimes_d = offset['reftimes_d'] xoffs = offset['xoffs'] yoffs = offset['yoffs'] timg_d = hel['reftime'] ind = bisect.bisect_left(reftimes_d, timg_d) xoff = xoffs[ind - 1] yoff = yoffs[ind - 1] else: xoff = hel['refx'] yoff = hel['refy'] if verbose: print( 'offset of image phase center to visibility phase center (arcsec): dx={0:.2f}, dy={1:.2f}' .format(dx, dy)) print( 'offset of visibility phase center to solar disk center (arcsec): dx={0:.2f}, dy={1:.2f}' .format(xoff, yoff)) (crval1, crval2) = (xoff + dx, yoff + dy) # update the fits header to heliocentric coordinates hdu = pyfits.open(fitsf, mode='update') hdu[0].verify('fix') header = hdu[0].header dshape = hdu[0].data.shape ndim = hdu[0].data.ndim (cdelt1, cdelt2) = (-header['cdelt1'] * 3600., header['cdelt2'] * 3600. ) # Original CDELT1, 2 are for RA and DEC in degrees header['cdelt1'] = cdelt1 header['cdelt2'] = cdelt2 header['cunit1'] = 'arcsec' header['cunit2'] = 'arcsec' header['crval1'] = crval1 header['crval2'] = crval2 header['ctype1'] = 'HPLN-TAN' header['ctype2'] = 'HPLT-TAN' header['date-obs'] = dateobs # begin time of the image if not p_ang: hel['p0'] = 0 try: # this works for pyfits version of CASA 4.7.0 but not CASA 4.6.0 if tdur_s: header.set('exptime', tdur_s) else: header.set('exptime', 1.) header.set('p_angle', hel['p0']) header.set( 'dsun_obs', sun.sunearth_distance(Time(dateobs)).to(u.meter).value) header.set( 'rsun_obs', sun.solar_semidiameter_angular_size(Time(dateobs)).value) header.set('rsun_ref', sun.constants.radius.value) header.set('hgln_obs', 0.) header.set( 'hglt_obs', sun.heliographic_solar_center(Time(dateobs))[1].value) except: # this works for astropy.io.fits if tdur_s: header.append(('exptime', tdur_s)) else: header.append(('exptime', 1.)) header.append(('p_angle', hel['p0'])) header.append( ('dsun_obs', sun.sunearth_distance(Time(dateobs)).to(u.meter).value)) header.append( ('rsun_obs', sun.solar_semidiameter_angular_size(Time(dateobs)).value)) header.append(('rsun_ref', sun.constants.radius.value)) header.append(('hgln_obs', 0.)) header.append( ('hglt_obs', sun.heliographic_solar_center(Time(dateobs))[1].value)) # check if stokes parameter exist exist_stokes = False stokes_mapper = { 'I': 1, 'Q': 2, 'U': 3, 'V': 4, 'RR': -1, 'LL': -2, 'RL': -3, 'LR': -4, 'XX': -5, 'YY': -6, 'XY': -7, 'YX': -8 } if 'CRVAL3' in header.keys(): if header['CTYPE3'] == 'STOKES': stokenum = header['CRVAL3'] exist_stokes = True if 'CRVAL4' in header.keys(): if header['CTYPE4'] == 'STOKES': stokenum = header['CRVAL4'] exist_stokes = True if exist_stokes: stokesstr = stokes_mapper.keys()[stokes_mapper.values().index( stokenum)] if verbose: print('This image is in Stokes ' + stokesstr) else: print( 'STOKES Information does not seem to exist! Assuming Stokes I' ) stokenum = 1 # intensity units to brightness temperature if toTb: # get restoring beam info bmaj = bmajs[n] bmin = bmins[n] beamunit = beamunits[n] data = hdu[ 0].data # remember the data order is reversed due to the FITS convension keys = header.keys() values = header.values() # which axis is frequency? faxis = keys[values.index('FREQ')][-1] faxis_ind = ndim - int(faxis) # find out the polarization of this image k_b = qa.constants('k')['value'] c_l = qa.constants('c')['value'] # Always use 2*kb for all polarizations const = 2. * k_b / c_l**2 if header['BUNIT'].lower() == 'jy/beam': header['BUNIT'] = 'K' header['BTYPE'] = 'Brightness Temperature' for i in range(dshape[faxis_ind]): nu = header['CRVAL' + faxis] + header['CDELT' + faxis] * ( i + 1 - header['CRPIX' + faxis]) if header['CUNIT' + faxis] == 'KHz': nu *= 1e3 if header['CUNIT' + faxis] == 'MHz': nu *= 1e6 if header['CUNIT' + faxis] == 'GHz': nu *= 1e9 if len(bmaj) > 1: # multiple (per-plane) beams bmajtmp = bmaj[i] bmintmp = bmin[i] else: # one single beam bmajtmp = bmaj[0] bmintmp = bmin[0] if beamunit == 'arcsec': bmaj0 = np.radians(bmajtmp / 3600.) bmin0 = np.radians(bmintmp / 3600.) if beamunit == 'arcmin': bmaj0 = np.radians(bmajtmp / 60.) bmin0 = np.radians(bmintmp / 60.) if beamunit == 'deg': bmaj0 = np.radians(bmajtmp) bmin0 = np.radians(bmintmp) if beamunit == 'rad': bmaj0 = bmajtmp bmin0 = bmintmp beam_area = bmaj0 * bmin0 * np.pi / (4. * log(2.)) factor = const * nu**2 # SI unit jy_to_si = 1e-26 # print(nu/1e9, beam_area, factor) factor2 = sclfactor # if sclfactor: # factor2 = 100. if faxis == '3': data[:, i, :, :] *= jy_to_si / beam_area / factor * factor2 if faxis == '4': data[ i, :, :, :] *= jy_to_si / beam_area / factor * factor2 header = fu.headerfix(header) hdu.flush() hdu.close() if ndim - np.count_nonzero(np.array(dshape) == 1) > 3: docompress = False ''' Caveat: only 1D, 2D, or 3D images are currently supported by the astropy fits compression. If a n-dimensional image data array does not have at least n-3 single-dimensional entries, force docompress to be False ''' print( 'warning: The fits data contains more than 3 non squeezable dimensions. Skipping fits compression..' ) if docompress: fitsftmp = fitsf + ".tmp.fits" os.system("mv {} {}".format(fitsf, fitsftmp)) hdu = pyfits.open(fitsftmp) hdu[0].verify('fix') header = hdu[0].header data = hdu[0].data fu.write_compressed_image_fits(fitsf, data, header, compression_type='RICE_1', quantize_level=4.0) os.system("rm -rf {}".format(fitsftmp)) if deletehistory: ms_restorehistory(vis) return fitsfile
from taskinit import msmdtool, iatool, casalog, tbtool from tclean_cli import tclean_cli as tclean from flagdata_cli import flagdata_cli as flagdata from ft_cli import ft_cli as ft from gaincal_cli import gaincal_cli as gaincal from applycal_cli import applycal_cli as applycal from concat_cli import concat_cli as concat from importfits_cli import importfits_cli as importfits from imhead_cli import imhead_cli as imhead from makemask_cli import makemask_cli as makemask from exportfits_cli import exportfits_cli as exportfits from importfits_cli import importfits_cli as importfits from clearcal_cli import clearcal_cli as clearcal from split_cli import split_cli as split ia = iatool() msmd = msmdtool() tb = tbtool() from astropy.io import fits from astropy import wcs mses = list(Qmses.keys()) fullpath_mses = ['../' + ms[:-3] + "_continuum.ms" for ms in mses if ms in Qmses] raw_and_corr_vis = 'continuum_concatenated_raw_and_corr.ms' if not os.path.exists(raw_and_corr_vis): assert concat(vis=fullpath_mses, concatvis=raw_and_corr_vis,
def implot(rasterfile, figname, contourfile=None, plottype=PlotControl.PNG, plotmode=PlotControl.BATCH, colorwedge=False, zoom=1): """Wrapper for CASA's imview, that will also create thumbnails. Parameters ---------- rasterfile : str Fully-qualified image file to use as raster map. Optional if contourfile is given. figname : str Fully-qualified output file name. contourflie : str Fully-qualified image file to use as contour map. Contours will be overlaid on rasterfile if both are given. Optional if rasterfile is given. plottype : int Plotting format, one of util.PlotControl plot type (e.g., PlotControl.PNG). Default: PlotControl.PNG plotmode : int Plot mode, one of util.PlotControl plot mode (e.g., PlotControl.INTERACTIVE). Default: PlotControl.BATCH colorwedge : boolean True - show color wedge, False - don't show color wedge zoom : int Image zoom ratio. Returns ------- None """ # axes dictionary is ignored by imview! #xlab=NONE,ylab=NONE #can't support this until imview out= is fixed! (see below) #orientation=PlotControl.LANDSCAPE if plotmode == PlotControl.NOPLOT: return if contourfile==None and rasterfile==None: raise Exception, "You must provide rasterfile and/or contourfile" if not PlotControl.isSupportedType(plottype): raise Exception, "Unrecognized plot type %d. See util.PlotControl" % plottype if plottype == PlotControl.SVG or plottype == PlotControl.GIF: raise Exception, "CASA viewer does not support SVG and GIF format :-(" if plottype == PlotControl.JPG: raise Exception, "CASA viewer claims to support JPG but doens't :-(" if plottype != PlotControl.PNG: raise Exception, "Thumbnails not supported (by matplotlib) for types other than PNG" DEFAULT_SCALING = -1 # scaling power cycles. # Grmph. axes dictionary ignored in imview. # axes={'y':ylab, 'x':xlab} contour = {} if contourfile: contour={'file':contourfile} raster = {} if rasterfile: raster={'file' : rasterfile, 'colorwedge' : colorwedge, 'scaling' : DEFAULT_SCALING} # Grmph. Giving an 'out' dictionary to imview causes SEVERE error. #out={'file' : figname, # 'format' : PlotControl.mkext(plottype,False), # 'scale' : 2.0, # 'dpi' : 1.0, # 'orient' : orientation} #dpi/scale - DPI used for PS/EPS, scale for others. Perhaps # we will enable these options later # These are completely ignored, but imview throws an exception # axes is not provided! axes = {'x':'x','y':'y','z':'z'} # work around this axis labeling problem? ia = taskinit.iatool() ia.open(rasterfile) h = ia.summary() ia.close() #print "PJT: implot axisnames:",h['axisnames'][0],h['axisnames'][1] if h['axisnames'][1]=='Frequency': axes['y'] = 'Frequency' # this complained about 'dimensions of axes must be strings (x is not)' # axes = { 'x' : h['axisnames'][0], 'y' : h['axisnames'][1] , 'z' : 'z' } casa_imview(raster=raster, contour=contour, out=figname, axes=axes, zoom=zoom) #of = PlotControl.mkext(plottype,dot=False) #casa.viewer(outfile=outfile, infile=imagename, gui=False, plottype=of) if plotmode == PlotControl.INTERACTIVE or plotmode==PlotControl.SHOW_AT_END: casa_imview(raster=raster, contour=contour,axes=axes)