# sd task for imaging from __future__ import absolute_import import os import re import numpy import shutil import string from casatasks.private.casa_transition import is_CASA6 if is_CASA6: from casatools import quanta, ms, image, table, msmetadata, measures from casatasks import casalog from . import sdutil from . import sdbeamutil from . import mslisthelper from .task_tsdimaging import conform_mslist else: from taskinit import casalog from taskinit import msmdtool as msmetadata from taskinit import tbtool as table from taskinit import mstool as ms from taskinit import iatool as image from taskinit import qatool as quanta from taskinit import metool as measures import sdutil import sdbeamutil import recipes.mslisthelper as mslisthelper from task_tsdimaging import conform_mslist @sdutil.sdtask_decorator def sdimaging(infiles, outfile, overwrite, field, spw, antenna, scan, intent, mode, nchan, start, width, veltype, outframe, gridfunction, convsupport, truncate, gwidth, jwidth, imsize, cell, phasecenter, projection, ephemsrcname, pointingcolumn, restfreq, stokes, minweight, brightnessunit, clipminmax): with sdutil.sdtask_manager(sdimaging_worker, locals()) as worker: worker.initialize() worker.execute() worker.finalize() def is_string_type(val): """ Returns True if the argument is string type. """ return type(val) in [str, numpy.string_] def smart_remove(path): if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) def get_subtable_path(vis, name): return os.path.join(vis, name) # functions from cleanhelper class cleanhelper_minimal(object): def __init__(self, imtool='', vis='', sort_index=None, usescratch=False, casalog=casalog): if((type(vis)==list) and (len(vis)==1)): vis=vis[0] #### if isinstance(vis, str): vislist = [vis] else: vislist = vis assert not isinstance(imtool, str) self.im = imtool self.vislist = vislist assert sort_index is not None self.sortedvisindx = sort_index self.sortedvislist = [self.vislist[i] for i in sort_index] self.dataspecframe='LSRK' self.usespecframe='' self.inframe=False self._casalog = casalog def convertvf(self,vf,frame,field,restf,veltype='radio'): """ returns doppler(velocity) or frequency in string currently use first rest frequency Assume input vf (velocity or fequency in a string) and output are the same 'frame'. """ #pdb.set_trace() docalcf=False #if(frame==''): frame='LSRK' #Use datasepcframe, it is cleanhelper initialized to set #to LSRK if(frame==''): frame=self.dataspecframe qa = quanta() if(qa.quantity(vf)['unit'].find('m/s') > -1): docalcf=True elif(qa.quantity(vf)['unit'].find('Hz') > -1): docalcf=False else: if vf !=0: raise TypeError("Unrecognized unit for the velocity or frequency parameter") ##fldinds=ms.msseltoindex(self.vis, field=field)['field'].tolist() myms = ms() fldinds=myms.msseltoindex(self.vislist[self.sortedvisindx[0]], field=field)['field'].tolist() if(len(fldinds) == 0): fldid0=0 else: fldid0=fldinds[0] if restf=='': #tb.open(self.vis+'/FIELD') fldtab=get_subtable_path(self.vislist[self.sortedvisindx[0]],'FIELD') tb = table() tb.open(fldtab) nfld = tb.nrows() if nfld >= fldid0: srcid=tb.getcell('SOURCE_ID',fldid0) else: raise TypeError( "Cannot set REST_FREQUENCY from the data: " + "no SOURCE corresponding field ID=%s, please supply restfreq" % fldid0 ) tb.close() # SOUECE_ID in FIELD table = -1 if no SOURCE table if srcid==-1: raise TypeError("Rest frequency info is not supplied") #tb.open(self.vis+'/SOURCE') sourcetab=get_subtable_path(self.vislist[self.sortedvisindx[0]], 'SOURCE') tb.open(sourcetab) tb2=tb.query('SOURCE_ID==%s' % srcid) tb.close() nsrc = tb2.nrows() if nsrc > 0: rfreq=tb2.getcell('REST_FREQUENCY',0) else: raise TypeError( "Cannot set REST_FREQUENCY from the data: "+ " no SOURCE corresponding field ID=%s, please supply restfreq" % fldid0 ) tb2.close() if(rfreq<=0): raise TypeError("Rest frequency does not seems to be properly set, check the data") else: if type(restf)==str: restf=[restf] if(qa.quantity(restf[0])['unit'].find('Hz') > -1): rfreq=[qa.convert(qa.quantity(restf[0]),'Hz')['value']] #print("using user input rest freq=",rfreq) else: raise TypeError("Unrecognized unit or type for restfreq") if(vf==0): # assume just want to get a restfrequecy from the data ret=str(rfreq[0])+'Hz' else: me = measures() if(docalcf): dop=me.doppler(veltype, qa.quantity(vf)) rvf=me.tofrequency(frame, dop, qa.quantity(rfreq[0],'Hz')) else: frq=me.frequency(frame, qa.quantity(vf)) rvf=me.todoppler(veltype, frq, qa.quantity(rfreq[0],'Hz')) ret=str(rvf['m0']['value'])+rvf['m0']['unit'] return ret def setChannelizeDefault(self,mode,spw,field,nchan,start,width,frame,veltype,phasec, restf,obstime=''): """ Determine appropriate values for channelization parameters when default values are used for mode='velocity' or 'frequency' or 'channel' This makes use of ms.cvelfreqs. """ ############### # for debugging ############### debug=False ############### spectable=get_subtable_path(self.vislist[self.sortedvisindx[0]], "SPECTRAL_WINDOW") tb = table() tb.open(spectable) chanfreqscol=tb.getvarcol('CHAN_FREQ') chanwidcol=tb.getvarcol('CHAN_WIDTH') spwframe=tb.getcol('MEAS_FREQ_REF'); tb.close() # first parse spw parameter: # use MSSelect if possible if len(self.sortedvislist) > 0: invis = self.sortedvislist[0] inspw = self.vislist.index(self.sortedvislist[0]) else: invis = self.vislist[0] inspw = 0 myms = ms() myms.open(invis) if type(spw)==list: spw=spw[inspw] if spw in ('-1', '*', '', ' '): spw='*' if field=='': field='*' mssel=myms.msseltoindex(vis=invis, spw=spw, field=field) selspw=mssel['spw'] selfield=mssel['field'] chaninds=mssel['channel'].tolist() chanst0 = chaninds[0][1] # frame spw0=selspw[0] chanfreqs=chanfreqscol['r'+str(spw0+1)].transpose()[0] chanres = chanwidcol['r'+str(spw0+1)].transpose()[0] # ascending or desending data frequencies? # based on selected first spw's first CHANNEL WIDTH # ==> some MS data may have positive chan width # so changed to look at first two channels of chanfreq (TT) #if chanres[0] < 0: descending = False if len(chanfreqs) > 1 : if chanfreqs[1]-chanfreqs[0] < 0: descending = True else: if chanres[0] < 0: descending = True # set dataspecframe: elspecframe=["REST", "LSRK", "LSRD", "BARY", "GEO", "TOPO", "GALACTO", "LGROUP", "CMB"] self.dataspecframe=elspecframe[spwframe[spw0]]; # set usespecframe: user's frame if set, otherwise data's frame if(frame != ''): self.usespecframe=frame self.inframe=True else: self.usespecframe=self.dataspecframe # some start and width default handling if mode!='channel': if width==1: width='' if start==0: start='' #get restfreq if restf=='': fldtab=get_subtable_path(invis,'FIELD') tb.open(fldtab) nfld=tb.nrows() try: if nfld >= selfield[0]: srcid=tb.getcell('SOURCE_ID',selfield[0]) else: if mode=='velocity': raise TypeError( "Cannot set REST_FREQUENCY from the data: " + "no SOURCE corresponding field ID=%s, please supply restfreq" % selfield[0] ) finally: tb.close() #SOUECE_ID in FIELD table = -1 if no SOURCE table if srcid==-1: if mode=='velocity': raise TypeError("Rest frequency info is not supplied") try: srctab=get_subtable_path(invis, 'SOURCE') tb.open(srctab) tb2=tb.query('SOURCE_ID==%s' % srcid) nsrc = tb2.nrows() if nsrc > 0 and tb2.iscelldefined('REST_FREQUENCY',0): rfqs = tb2.getcell('REST_FREQUENCY',0) if len(rfqs)>0: restf=str(rfqs[0])+'Hz' else: if mode=='velocity': raise TypeError( "Cannot set REST_FREQUENCY from the data: " + "REST_FREQUENCY entry for ID %s in SOURCE table is empty, please supply restfreq" % srcid ) else: if mode=='velocity': raise TypeError( "Cannot set REST_FREQUENCY from the data: " + "no SOURCE corresponding field ID=%s, please supply restfreq" % selfield[0] ) finally: tb.close() tb2.close() if type(phasec)==list: inphasec=phasec[0] else: inphasec=phasec if type(inphasec)==str and inphasec.isdigit(): inphasec=int(inphasec) #if nchan==1: # use data chan freqs # newfreqs=chanfreqs #else: # obstime not included here if debug: print("before ms.cvelfreqs (start,width,nchan)===>",start, width, nchan) try: newfreqs=myms.cvelfreqs(spwids=selspw,fieldids=selfield,mode=mode,nchan=nchan, start=start,width=width,phasec=inphasec, restfreq=restf, outframe=self.usespecframe,veltype=veltype).tolist() except: # ms must be closed here if ms.cvelfreqs failed with an exception myms.close() raise myms.close() #print(newfreqs) descendingnewfreqs=False if len(newfreqs)>1: if newfreqs[1]-newfreqs[0] < 0: descendingnewfreqs=True try: if((nchan in [-1, "-1", "", " "]) or (start in [-1, "-1", "", " "])): frange=im.advisechansel(msname=invis, spwselection=spw, fieldid=selfield[0], getfreqrange=True) startchan=0 endchan=len(newfreqs)-1 if(descendingnewfreqs): startchan=numpy.min(numpy.where(frange['freqend'] < numpy.array(newfreqs))) endchan=numpy.min(numpy.where(frange['freqstart'] < numpy.array(newfreqs))) else: startchan=numpy.max(numpy.where(frange['freqstart'] > numpy.array(newfreqs))) endchan=numpy.max(numpy.where(frange['freqend'] > numpy.array(newfreqs))) if((start not in [-1, "-1", "", " "]) and (mode=="channel")): startchan=start if(nchan not in [-1, "-1", "", " "]): endchan=startchan+nchan-1 newfreqs=(numpy.array(newfreqs)[startchan:endchan]).tolist() except: pass if debug: print("Mode, Start, width after cvelfreqs =",mode, start,width ) if type(newfreqs)==list and len(newfreqs) ==0: raise TypeError( "Output frequency grid cannot be calculated: " + " please check start and width parameters" ) if debug: if len(newfreqs)>1: print("FRAME=",self.usespecframe) print("newfreqs[0]===>",newfreqs[0]) print("newfreqs[1]===>",newfreqs[1]) print("newfreqs[-1]===>",newfreqs[-1]) print("len(newfreqs)===>",len(newfreqs)) else: print("newfreqs=",newfreqs) # set output number of channels if nchan ==1: retnchan=1 else: if len(newfreqs)>1: retnchan=len(newfreqs) else: retnchan=nchan newfreqs=chanfreqs.tolist() # set start parameter # first analyze data order etc reverse=False negativew=False if descending: # channel mode case (width always >0) if width!="" and (type(width)==int or type(width)==float): if descendingnewfreqs: reverse=False else: reverse=True elif width=="": #default width if descendingnewfreqs and mode=="frequency": reverse=False else: reverse=True elif type(width)==str: if width.lstrip().find('-')==0: negativew=True if descendingnewfreqs: if negativew: reverse=False else: reverse=True else: if negativew: reverse=True else: reverse=False else: #ascending data # depends on sign of width only # with CAS-3117 latest change(rev.15179), velocity start # means lowest velocity for default width if width=="" and mode=="velocity": #default width # ms.cvelfreqs returns correct order so no reversing reverse=False elif type(width)==str: if width.lstrip().find('-')==0: reverse=True else: reverse=False if reverse: newfreqs.reverse() #if (start!="" and mode=='channel') or \ # (start!="" and type(start)!=int and mode!='channel'): # for now to avoid inconsistency later in imagecoordinates2 call # user's start parameter is preserved for channel mode only. # (i.e. the current code may adjust start parameter for other modes but # this probably needs to be changed, especially for multiple ms handling.) if ((start not in [-1, "", " "]) and mode=='channel'): retstart=start else: # default cases if mode=="frequency": retstart=str(newfreqs[0])+'Hz' elif mode=="velocity": #startfreq=str(newfreqs[-1])+'Hz' startfreq=(str(max(newfreqs))+'Hz') if(start=="") else (str(newfreqs[-1])+'Hz') retstart=self.convertvf(startfreq,frame,field,restf,veltype) elif mode=="channel": # default start case, use channel selection from spw retstart=chanst0 # set width parameter if width!="": retwidth=width else: if nchan==1: finc = chanres[0] else: finc = newfreqs[1]-newfreqs[0] if debug: print("finc(newfreqs1-newfreqs0)=",finc) if mode=="frequency": # It seems that this is no longer necessary... TT 2013-08-12 #if descendingnewfreqs: # finc = -finc retwidth=str(finc)+'Hz' elif mode=="velocity": # for default width assume it is vel<0 (incresing in freq) if descendingnewfreqs: ind1=-2 ind0=-1 else: ind1=-1 ind0=-2 v1 = self.convertvf(str(newfreqs[ind1])+'Hz',frame,field,restf,veltype=veltype) v0 = self.convertvf(str(newfreqs[ind0])+'Hz',frame,field,restf,veltype=veltype) ##v1 = self.convertvf(str(newfreqs[-1])+'Hz',frame,field,restf,veltype=veltype) ##v0 = self.convertvf(str(newfreqs[-2])+'Hz',frame,field,restf,veltype=veltype) #v1 = self.convertvf(str(newfreqs[1])+'Hz',frame,field,restf,veltype=veltype) #v0 = self.convertvf(str(newfreqs[0])+'Hz',frame,field,restf,veltype=veltype) qa = quanta() if(qa.lt(v0, v1) and start==""): ###user used "" as start make sure step is +ve in vel as start is min vel possible for freqs selected retwidth=qa.tos(qa.sub(v1, v0)) else: retwidth = qa.tos(qa.sub(v0, v1)) else: retwidth=1 if debug: print("setChan retwidth=",retwidth) return retnchan, retstart, retwidth class sdimaging_worker(sdutil.sdtask_template_imaging): def __init__(self, **kwargs): super(sdimaging_worker,self).__init__(**kwargs) self.imager_param = {} self.sorted_idx = [] self.image_unit = "" def parameter_check(self): # outfile check sdutil.assert_outfile_canoverwrite_or_nonexistent(self.outfile, 'im', self.overwrite) sdutil.assert_outfile_canoverwrite_or_nonexistent(self.outfile+'.weight', 'im', self.overwrite) # fix spw if type(self.spw) == str: self.spw = self.__format_spw_string(self.spw) # check unit of start and width # fix default if self.mode == 'channel': if self.start == '': self.start = 0 if self.width == '': self.width = 1 else: if self.start == 0: self.start = '' if self.width == 1: self.width = '' # fix unit if self.mode == 'frequency': myunit = 'Hz' elif self.mode == 'velocity': myunit = 'km/s' else: # channel myunit = '' for name in ['start', 'width']: param = getattr(self, name) new_param = self.__format_quantum_unit(param, myunit) if new_param == None: raise ValueError("Invalid unit for %s in mode %s: %s" % \ (name, self.mode, param)) setattr(self, name, new_param) casalog.post("mode='%s': start=%s, width=%s, nchan=%d" % \ (self.mode, self.start, self.width, self.nchan)) # check length of selection parameters if is_string_type(self.infiles): nfile = 1 self.infiles = [ self.infiles ] else: nfile = len(self.infiles) for name in ['field', 'spw', 'antenna', 'scanno']: param = getattr(self, name) if not self.__check_selection_length(param, nfile): raise ValueError("Length of %s != infiles." % (name)) # set convsupport default if self.convsupport >= 0 and self.gridfunction.upper() != 'SF': casalog.post("user defined convsupport is ignored for %s kernel" % self.gridfunction, priority='WARN') self.convsupport=-1 def __format_spw_string(self, spw): """ Returns formatted spw selection string which is accepted by imager. """ if type(spw) != str: raise ValueError("The parameter should be string.") if spw.strip() == '*': spw = '' # WORKAROUND for CAS-6422, i.e., ":X~Y" fails while "*:X~Y" works. if spw.startswith(":"): spw = '*' + spw return spw def __format_quantum_unit(self, data, unit): """ Returns False if data has an unit which in not a variation of input unit. Otherwise, returns input data as a quantum string. The input unit is added to the return value if no unit is in data. """ my_qa = quanta() if data == '' or my_qa.compare(data, unit): return data if my_qa.getunit(data) == '': casalog.post("No unit specified. Using '%s'" % unit) return '%f%s' % (data, unit) return None def __check_selection_length(self, data, nfile): """ Returns true if data is either a string, an array with length 1 or nfile """ if not is_string_type(data) and len(data) not in [1, nfile]: return False return True def get_selection_param_for_ms(self, fileid, param): """ Returns valid selection string for a certain ms Arguments fileid : file idx in infiles list param : string (array) selection value """ if is_string_type(param): return param elif len(param) == 1: return param[0] else: return param[fileid] def get_selection_idx_for_ms(self, file_idx): """ Returns a dictionary of selection indices for i-th MS in infiles Argument: file idx in infiles list """ if file_idx < len(self.infiles) and file_idx > -1: vis = self.infiles[file_idx] field = self.get_selection_param_for_ms(file_idx, self.field) spw = self.get_selection_param_for_ms(file_idx, self.spw) spw = self.__format_spw_string(spw) antenna = self.get_selection_param_for_ms(file_idx, self.antenna) if antenna == -1: antenna = '' scan = self.get_selection_param_for_ms(file_idx, self.scanno) intent = self.get_selection_param_for_ms(file_idx, self.intent) my_ms = ms() sel_ids = my_ms.msseltoindex(vis=vis, spw=spw, field=field, baseline=antenna, scan=scan) fieldid = list(sel_ids['field']) if len(sel_ids['field']) > 0 else -1 baseline = self.format_ac_baseline(sel_ids['antenna1']) scanid = list(sel_ids['scan']) if len(sel_ids['scan']) > 0 else "" # SPW (need to get a list of valid spws instead of -1) if len(sel_ids['channel']) > 0: spwid = [ chanarr[0] for chanarr in sel_ids['channel'] ] elif spw=="": # No spw selection my_ms.open(vis) try: spwinfo = my_ms.getspectralwindowinfo() except: raise finally: my_ms.close() spwid = [int(idx) for idx in spwinfo.keys()] else: raise RuntimeError("Invalid spw selction, %s ,for MS %d" % (str(spw), file_idx)) return {'field': fieldid, 'spw': spwid, 'baseline': baseline, 'scan': scanid, 'intent': intent, 'antenna1': sel_ids['antenna1']} else: raise ValueError("Invalid file index, %d" % file_idx) def format_ac_baseline(self, in_antenna): """ format auto-correlation baseline string from antenna idx list """ # exact match string if is_string_type(in_antenna): if (len(in_antenna) != 0) and (in_antenna.find('&') == -1) \ and (in_antenna.find(';')==-1): in_antenna =+ '&&&' return in_antenna # single integer -> list of int if type(in_antenna)==int: if in_antenna >=0: in_antenna = [in_antenna] else: return -1 # format auto-corr string from antenna idices. baseline = '' for idx in in_antenna: if len(baseline) > 0: baseline += ';' if idx >= 0: baseline += (str(idx) + '&&&') return baseline def compile(self): # imaging mode self.imager_param['mode'] = self.mode # Work on selection of the first table in sorted list # to get default restfreq and outframe # chronological sort sorted_vislist, sorted_timelist = mslisthelper.sort_mslist(self.infiles) self.sorted_idx = [self.infiles.index(vis) for vis in sorted_vislist] mslisthelper.report_sort_result(sorted_vislist, sorted_timelist, self.sorted_idx, mycasalog=casalog) # conform MS conform_mslist(sorted_vislist, ignore_columns=[]) selection_ids = self.get_selection_idx_for_ms(self.sorted_idx[0]) self.__update_subtable_name(self.infiles[self.sorted_idx[0]]) # field fieldid = selection_ids['field'][0] if type(selection_ids['field']) != int else selection_ids['field'] sourceid=-1 self.open_table(self.field_table) source_ids = self.table.getcol('SOURCE_ID') self.close_table() if self.field == '' or fieldid ==-1: sourceid = source_ids[0] elif fieldid >= 0 and fieldid < len(source_ids): sourceid = source_ids[fieldid] else: raise ValueError("No valid field in the first MS.") # restfreq if self.restfreq=='' and self.source_table != '': self.open_table(self.source_table) source_ids = self.table.getcol('SOURCE_ID') for i in range(self.table.nrows()): if sourceid == source_ids[i] \ and self.table.iscelldefined('REST_FREQUENCY',i) \ and (selection_ids['spw'] == -1 or \ self.table.getcell('SPECTRAL_WINDOW_ID', i) in selection_ids['spw']): rf = self.table.getcell('REST_FREQUENCY',i) if len(rf) > 0: self.restfreq=self.table.getcell('REST_FREQUENCY',i)[0] break self.close_table() casalog.post("restfreq set to %s" % self.restfreq, "INFO") # REST_FREQUENCY column is optional (need retry if not exists) self.imager_param['restfreq'] = self.restfreq # # spw (define representative spw id = spwid_ref) spwid_ref = selection_ids['spw'][0] if type(selection_ids['spw']) != int else selection_ids['spw'] # Invalid spw selection should have handled at msselectiontoindex(). # -1 means all spw are selected. self.open_table(self.spw_table) if spwid_ref < 0: for id in range(self.table.nrows()): if self.table.getcell('NUM_CHAN',id) > 0: spwid_ref = id break if spwid_ref < 0: self.close_table() msg='No valid spw id exists in the first table' raise ValueError(msg) self.allchannels = self.table.getcell('NUM_CHAN',spwid_ref) freq_chan0 = self.table.getcell('CHAN_FREQ',spwid_ref)[0] freq_inc0 = self.table.getcell('CHAN_WIDTH',spwid_ref)[0] # in case rest frequency is not defined yet. if self.restfreq=='': self.restfreq = '%fHz' % self.table.getcell('CHAN_FREQ',spwid_ref).mean() self.imager_param['restfreq'] = self.restfreq casalog.post("Using mean freq of spw %d as restfreq: %s" % (spwid_ref, self.restfreq), "INFO") self.close_table() self.imager_param['spw'] = -1 #spwid_ref # outframe (force using the current frame) self.imager_param['outframe'] = self.outframe if self.outframe == '': if len(self.infiles) > 1: # The default will be 'LSRK' casalog.post("Multiple MS inputs. The default outframe is set to 'LSRK'") self.imager_param['outframe'] = 'LSRK' else: # get from MS my_ms = ms() my_ms.open(self.infiles[0]) spwinfo = my_ms.getspectralwindowinfo() my_ms.close() del my_ms for key, spwval in spwinfo.items(): if spwval['SpectralWindowId'] == spwid_ref: self.imager_param['outframe'] = spwval['Frame'] casalog.post("Using frequency frame of MS, '%s'" % self.imager_param['outframe']) break if self.imager_param['outframe'] == '': raise Exception("Internal error of getting frequency frame of spw=%d." % spwid_ref) else: casalog.post("Using frequency frame defined by user, '%s'" % self.imager_param['outframe']) # brightness unit if len(self.brightnessunit) > 0: if self.brightnessunit.lower() == 'k': self.image_unit = 'K' elif self.brightnessunit.lower() == 'jy/beam': self.image_unit = 'Jy/beam' else: raise ValueError("Invalid brightness unit, %s" % self.brightnessunit) # # antenna # in_antenna = self.antenna # backup for future use # if type(self.antenna)==int: # if self.antenna >= 0: # self.antenna=str(self.antenna)+'&&&' # else: # if (len(self.antenna) != 0) and (self.antenna.find('&') == -1) \ # and (self.antenna.find(';')==-1): # self.antenna = self.antenna + '&&&' def _configure_map_property(self): selection_ids = self.get_selection_idx_for_ms(self.sorted_idx[0]) # stokes if self.stokes == '': self.stokes = 'I' self.imager_param['stokes'] = self.stokes # gridfunction # outfile if os.path.exists(self.outfile) and self.overwrite: smart_remove(self.outfile) if os.path.exists(self.outfile + '.weight') and self.overwrite: smart_remove(self.outfile + '.weight') # cell cell = self.cell if cell == '' or cell[0] == '': # Calc PB grid_factor = 3. casalog.post("The cell size will be calculated using PB size of antennas in the first MS") qPB = self._calc_PB(selection_ids['antenna1']) cell = '%f%s' % (qPB['value']/grid_factor, qPB['unit']) casalog.post("Using cell size = PB/%4.2F = %s" % (grid_factor, cell)) (cellx,celly) = sdutil.get_cellx_celly(cell, unit='arcmin') self.imager_param['cellx'] = cellx self.imager_param['celly'] = celly # Calculate Pointing center and extent (if necessary) # return a dictionary with keys 'center', 'width', 'height' #imsize = self.imsize imsize = sdutil._to_list(self.imsize, int) or \ sdutil._to_list(self.imsize, numpy.integer) if imsize is None: imsize = self.imsize if hasattr(self.imsize, '__iter__') else [ self.imsize ] imsize = [ int(numpy.ceil(v)) for v in imsize ] casalog.post("imsize is not integers. force converting to integer pixel numbers.", priority="WARN") casalog.post("rounded-up imsize: %s --> %s" % (str(self.imsize), str(imsize))) phasecenter = self.phasecenter if self.phasecenter == "" or \ len(imsize) == 0 or imsize[0] < 1: map_param = self._get_pointing_extent() # imsize if len(imsize) == 0 or imsize[0] < 1: imsize = self._get_imsize(map_param['width'], map_param['height'], cellx, celly) if self.phasecenter != "": casalog.post("You defined phasecenter but not imsize. The image will cover as wide area as pointing in MS extends, but be centered at phasecenter. This could result in a strange image if your phasecenter is a part from the center of pointings", priority='WARN') if imsize[0] > 1024 or imsize[1] > 1024: casalog.post("The calculated image pixel number is larger than 1024. It could take time to generate the image depending on your computer resource. Please wait...", priority='WARN') # phasecenter # if empty, it should be determined here... if self.phasecenter == "": phasecenter = map_param['center'] # imsize (nx,ny) = sdutil.get_nx_ny(imsize) self.imager_param['nx'] = nx self.imager_param['ny'] = ny # phasecenter self.imager_param['phasecenter'] = phasecenter self.imager_param['movingsource'] = self.ephemsrcname # channel map sorted_vislist, _ = mslisthelper.sort_mslist(self.infiles) self.sorted_idx = [self.infiles.index(vis) for vis in sorted_vislist] imhelper = cleanhelper_minimal(self.imager, self.infiles, sort_index=self.sorted_idx, casalog=casalog) spwsel = str(',').join([str(spwid) for spwid in selection_ids['spw']]) srestf = self.imager_param['restfreq'] if is_string_type(self.imager_param['restfreq']) else "%fHz" % self.imager_param['restfreq'] (imnchan, imstart, imwidth) = imhelper.setChannelizeDefault( self.mode, spwsel, self.field, self.nchan, self.start, self.width, self.imager_param['outframe'], self.veltype, self.imager_param['phasecenter'], srestf ) del imhelper # start and width if self.mode == 'velocity': startval = [self.imager_param['outframe'], imstart] widthval = imwidth elif self.mode == 'frequency': startval = [self.imager_param['outframe'], imstart] widthval = imwidth else: #self.mode==channel startval = int(self.start) widthval = int(self.width) if self.nchan < 0: self.nchan = self.allchannels self.imager_param['start'] = startval self.imager_param['step'] = widthval self.imager_param['nchan'] = imnchan #self.nchan self.imager_param['projection'] = self.projection def execute(self): # imaging casalog.post("Start imaging...", "INFO") if len(self.infiles) == 1: self.open_imager(self.infiles[0]) selection_ids = self.get_selection_idx_for_ms(0) spwsel = self.get_selection_param_for_ms(0, self.spw) if spwsel.strip() in ['', '*']: spwsel = selection_ids['spw'] ### TODO: channel selection based on spw ok = self.imager.selectvis(field=selection_ids['field'], #spw=selection_ids['spw'], spw=spwsel, nchan=-1, start=0, step=1, baseline=selection_ids['baseline'], scan=selection_ids['scan'], intent=selection_ids['intent']) if not ok: raise ValueError("Selection is empty: you may want to review this MS selection") else: self.close_imager() #self.sorted_idx.reverse() for idx in self.sorted_idx.__reversed__(): name = self.infiles[idx] selection_ids = self.get_selection_idx_for_ms(idx) spwsel = self.get_selection_param_for_ms(idx, self.spw) if spwsel.strip() in ['', '*']: spwsel = selection_ids['spw'] ### TODO: channel selection based on spw self.imager.selectvis(vis=name, field=selection_ids['field'], #spw=selection_ids['spw'], spw = spwsel, nchan=-1, start=0, step=1, baseline=selection_ids['baseline'], scan=selection_ids['scan'], intent=selection_ids['intent']) # need to do this self.is_imager_opened = True # it should be called after infiles are registered to imager self._configure_map_property() casalog.post("Using phasecenter \"%s\""%(self.imager_param['phasecenter']), "INFO") self.imager.defineimage(**self.imager_param)#self.__get_param()) self.imager.setoptions(ftmachine='sd', gridfunction=self.gridfunction) self.imager.setsdoptions(pointingcolumntouse=self.pointingcolumn, convsupport=self.convsupport, truncate=self.truncate, gwidth=self.gwidth, jwidth=self.jwidth, minweight = 0., clipminmax=self.clipminmax) # Create images imgtype_suffix = {'singledish': '', 'coverage' : '.weight'} for img_type, img_suffix in imgtype_suffix.items(): img_file = self.outfile + img_suffix msg_fmt = string.Template("$state {img_type} image {img_file}".format( img_type=img_type, img_file=img_file)) casalog.post(msg_fmt.substitute(state="Generating"), "INFO") self.imager.makeimage(type=img_type, image=img_file) if not os.path.exists(img_file): raise RuntimeError(msg_fmt.substitute(state="Failed to generate")) casalog.post(msg_fmt.substitute(state="Generated"), "INFO") # Close imager self.close_imager() # Convert output images to proper output frame and set brightness unit (if necessary) my_ia = image() my_ia.open(self.outfile) csys = my_ia.coordsys() csys.setconversiontype(spectral=csys.referencecode('spectra')[0]) my_ia.setcoordsys(csys.torecord()) if len(self.image_unit) > 0: casalog.post("Setting brightness unit '%s' to image." % self.image_unit) my_ia.setbrightnessunit(self.image_unit) csys.done() my_ia.close() # CAS-12984 set brightness unit for weight image to '' weightfile = self.outfile + imgtype_suffix['coverage'] my_ia.open(weightfile) my_ia.setbrightnessunit('') my_ia.close() # Mask image pixels whose weight are smaller than minweight. # Weight image should have 0 weight for pixels below < minweight casalog.post("Start masking the map using minweight = %f" % \ self.minweight, "INFO") my_ia.open(weightfile) try: stat=my_ia.statistics(mask="'"+weightfile+"' > 0.0", robust=True) valid_pixels=stat['npts'] except RuntimeError as e: if str(e).find('No valid data found.') >= 0: valid_pixels = [0] else: raise if len(valid_pixels) == 0 or valid_pixels[0] == 0: my_ia.close() casalog.post("All pixels have zero weight. This means the imaged region contains no MS data. Mask will not be set. Please check your image parameters.","WARN") return median_weight = stat['median'][0] weight_threshold = median_weight * self.minweight casalog.post("Median of weight in the map is %f" % median_weight, \ "INFO") casalog.post("Pixels in map with weight <= median(weight)*minweight = %f will be masked." % \ (weight_threshold),"INFO") ###Leaving the original logic to calculate the number of masked pixels via ###product of median of and min_weight (which i don't understand the logic) ### if one wanted to find how many pixel were masked one could easily count the ### number of pixels set to false ### e.g after masking self.outfile below one could just do this ### nmasked_pixels=tb.calc('[select from "'+self.outfile+'"/mask0'+'" giving [nfalse(PagedArray )]]') my_tb = table() nmask_pixels=0 nchan=stat['trc'][3]+1 casalog.filter('ERROR') ### hide the useless message of tb.calc ### doing it by channel to make sure it does not go out of memory ####tab.calc try to load the whole chunk in ram for k in range(nchan): nmask_pixels += my_tb.calc('[select from "'+weightfile+'" giving [ntrue(map[,,,'+str(k)+'] <='+str(median_weight*self.minweight)+')]]')['0'][0] casalog.filter() ####set logging back to normal casalog.filter() ####set logging back to normal imsize=numpy.product(my_ia.shape()) my_ia.close() # Modify default mask my_ia.open(self.outfile) my_ia.calcmask("'%s'>%f" % (weightfile,weight_threshold), asdefault=True) my_ia.close() masked_fraction = 100.*(1. - (imsize - nmask_pixels) / float(valid_pixels[0]) ) casalog.post("This amounts to %5.1f %% of the area with nonzero weight." % \ ( masked_fraction ),"INFO") casalog.post("The weight image '%s' is returned by this task, if the user wishes to assess the results in detail." \ % (weightfile), "INFO") # Calculate theoretical beam size casalog.post("Calculating image beam size.") if self.gridfunction.upper() not in ['SF']: casalog.post("Beam size definition for '%s' kernel is experimental." % self.gridfunction, priority='WARN') casalog.post("You may want to take careful look at the restoring beam in the image.",priority='WARN') my_msmd = msmetadata() # antenna diameter and blockage ref_ms_idx = self.sorted_idx[0] ref_ms_name = self.infiles[ref_ms_idx] selection_ids = self.get_selection_idx_for_ms(ref_ms_idx) ant_idx = selection_ids['antenna1'] diameter = self._get_average_antenna_diameter(ant_idx) my_msmd.open(ref_ms_name) ant_name = my_msmd.antennanames(ant_idx) my_msmd.close() is_alma = False for name in ant_name: if name[0:2] in ["PM", "DV", "DA", "CM"]: is_alma = True break blockage = "0.75m" if is_alma else "0.0m" # unknown blockage diameter # output reference code my_ia.open(self.outfile) csys = my_ia.coordsys() my_ia.close() outref = csys.referencecode('direction')[0] cell = list(csys.increment(type='direction',format='s')['string']) csys.done() # pointing sampling ref_ms_spw = self.get_selection_param_for_ms(ref_ms_idx,self.spw) ref_ms_field = self.get_selection_param_for_ms(ref_ms_idx,self.field) ref_ms_scan = self.get_selection_param_for_ms(ref_ms_idx,self.scanno) # xSampling, ySampling, angle = sdutil.get_ms_sampling_arcsec(ref_ms_name, spw=ref_ms_spw, # antenna=selection_ids['baseline'], # field=ref_ms_field, # scan=ref_ms_scan,#timerange='', # outref=outref) # obtain sampling interval for beam calculation. self.open_imager(ref_ms_name) ok = self.imager.selectvis(field=ref_ms_field, #spw=selection_ids['spw'], spw=ref_ms_spw, nchan=-1, start=0, step=1, baseline=selection_ids['baseline'], scan=ref_ms_scan, intent=selection_ids['intent']) if len(ant_idx) > 1: casalog.post("Using only antenna %s to calculate sampling interval" % ant_name[0]) ptg_samp = self.imager.pointingsampling(pattern='raster', ref=outref, movingsource=self.ephemsrcname, pointingcolumntouse=self.pointingcolumn, antenna=('%s&&&' % ant_name[0])) self.close_imager() my_qa = quanta() xSampling, ySampling = my_qa.getvalue(my_qa.convert(ptg_samp['sampling'], 'arcsec')) angle = my_qa.getvalue(my_qa.convert(ptg_samp['angle'], "deg"))[0] casalog.post("Detected raster sampling = [{x:f}, {y:f}] arcsec".format( x=xSampling, y=ySampling )) # handling of failed sampling detection valid_sampling = True sampling = [xSampling, ySampling] if abs(xSampling) < 2.2e-3 or not numpy.isfinite(xSampling): casalog.post("Invalid sampling=%s arcsec. Using the value of orthogonal direction=%s arcsec" % (xSampling, ySampling), priority="WARN") sampling = [ ySampling ] angle = 0.0 valid_sampling = False if abs(ySampling) < 1.0e-3 or not numpy.isfinite(ySampling): if valid_sampling: casalog.post("Invalid sampling=%s arcsec. Using the value of orthogonal direction=%s arcsec" % (ySampling, xSampling), priority="WARN") sampling = [ xSampling ] angle = 0.0 valid_sampling = True # reduce sampling and cell if it's possible if len(sampling)>1 and abs(sampling[0]-sampling[1]) <= 0.01*abs(sampling[0]): sampling = [sampling[0]] angle = 0.0 if cell[0]==cell[1]: cell = [cell[0]] if valid_sampling: # actual calculation of beam size bu = sdbeamutil.TheoreticalBeam() bu.set_antenna(diameter,blockage) bu.set_sampling(sampling, "%fdeg" % angle) bu.set_image_param(cell, self.restfreq, self.gridfunction, self.convsupport, self.truncate, self.gwidth, self.jwidth,is_alma) bu.summary() imbeam_dict = bu.get_beamsize_image() casalog.post("Setting image beam: major={major}, minor={minor}, pa={pa}".format( major=imbeam_dict['major'], minor=imbeam_dict['minor'], pa=imbeam_dict['pa'] )) # set beam size to image my_ia.open(self.outfile) my_ia.setrestoringbeam(**imbeam_dict) my_ia.close() else: #BOTH sampling was invalid casalog.post("Could not detect valid raster sampling. Exitting without setting beam size to image", priority='WARN') def _calc_PB(self, antenna): """ Calculate the primary beam size of antenna, using dish diamenter and rest frequency Average antenna diamter and reference frequency are adopted for calculation. The input argument should be a list of antenna IDs. """ casalog.post("Calculating Pirimary beam size:") # CAS-5410 Use private tools inside task scripts my_qa = quanta() pb_factor = 1.175 # Reference frequency ref_freq = self.restfreq if type(ref_freq) in [float, numpy.float64]: ref_freq = my_qa.tos(my_qa.quantity(ref_freq, 'Hz')) if not my_qa.compare(ref_freq, 'Hz'): msg = "Could not get the reference frequency. " + \ "Your data does not seem to have valid one in selected field.\n" + \ "PB is not calculated.\n" + \ "Please set restreq or cell manually to generate an image." raise Exception(msg) # Antenna diameter antdiam_ave = self._get_average_antenna_diameter(antenna) # Calculate PB wave_length = 0.2997924 / my_qa.convert(my_qa.quantity(ref_freq),'GHz')['value'] D_m = my_qa.convert(antdiam_ave, 'm')['value'] lambda_D = wave_length / D_m * 3600. * 180 / numpy.pi PB = my_qa.quantity(pb_factor*lambda_D, 'arcsec') # Summary casalog.post("- Antenna diameter: %s m" % D_m) casalog.post("- Reference Frequency: %s" % ref_freq) casalog.post("PB size = %5.3f * lambda/D = %s" % (pb_factor, my_qa.tos(PB))) return PB def _get_imsize(self, width, height, dx, dy): casalog.post("Calculating pixel size.") # CAS-5410 Use private tools inside task scripts my_qa = quanta() ny = numpy.ceil( ( my_qa.convert(height, my_qa.getunit(dy))['value'] / \ my_qa.getvalue(dy) ) ) nx = numpy.ceil( ( my_qa.convert(width, my_qa.getunit(dx))['value'] / \ my_qa.getvalue(dx) ) ) casalog.post("- Map extent: [%s, %s]" % (my_qa.tos(width), my_qa.tos(height))) casalog.post("- Cell size: [%s, %s]" % (my_qa.tos(dx), my_qa.tos(dy))) casalog.post("Image pixel numbers to cover the extent: [%d, %d] (projected)" % \ (nx+1, ny+1)) return (int(nx+1), int(ny+1)) def _get_pointing_extent(self): ### MS selection is ignored. This is not quite right. casalog.post("Calculating map extent from pointings.") # CAS-5410 Use private tools inside task scripts my_qa = quanta() ret_dict = {} colname = self.pointingcolumn.upper() # MSs should be registered to imager if not self.is_imager_opened: raise RuntimeError('Internal error: imager should be opened here.') if self.phasecenter == "": # defaut is J2000 base_mref = 'J2000' elif isinstance(self.phasecenter, int) or self.phasecenter.isdigit(): # may be field id self.open_table(self.field_table) base_mref = self.table.getcolkeyword('PHASE_DIR', 'MEASINFO')['Ref'] self.close_table() else: # may be phasecenter is explicitly specified pattern = '^([\-\+]?[0-9.]+([eE]?-?[0-9])?)|([\-\+]?[0-9][:h][0-9][:m][0-9.]s?)|([\-\+]?[0-9][.d][0-9][.d][0-9.]s?)$' items = self.phasecenter.split() base_mref = 'J2000' for i in items: s = i.strip() if re.match(pattern, s) is None: base_mref = s break mapextent = self.imager.mapextent(ref=base_mref, movingsource=self.ephemsrcname, pointingcolumntouse=colname) if mapextent['status'] is True: qheight = my_qa.quantity(mapextent['extent'][1], 'rad') qwidth = my_qa.quantity(mapextent['extent'][0], 'rad') qcent0 = my_qa.quantity(mapextent['center'][0], 'rad') qcent1 = my_qa.quantity(mapextent['center'][1], 'rad') scenter = '{ref} {longitude} {latitude}'.format( ref=base_mref, longitude=my_qa.formxxx(qcent0, 'hms'), latitude=my_qa.formxxx(qcent1, 'dms') ) casalog.post("- Pointing center: {center}".format(center=scenter)) casalog.post("- Pointing extent: [{width}, {height}] (projected)".format( width=my_qa.tos(qwidth), height=my_qa.tos(qheight) )) ret_dict['center'] = scenter ret_dict['width'] = qwidth ret_dict['height'] = qheight else: casalog.post('Failed to derive map extent from the MSs registered to the imager probably due to mising valid data.', priority='SEVERE') ret_dict['center'] = '' ret_dict['width'] = my_qa.quantity(0.0, 'rad') ret_dict['height'] = my_qa.quantity(0.0, 'rad') return ret_dict def _get_x_minmax(self, x): # assumed the x is in unit of rad. pi2 = 2. * numpy.pi x = (x % pi2) npart = 4 dlon = pi2/float(npart) pos = [int(v/dlon) for v in x] voids = [False for dummy in range(npart)] for ipos in range(npart): try: dummy = pos.index(ipos) except: voids[ipos] = True if not any(voids): raise Exception("Failed to find global pointing gap. The algorithm requires at least 2PI/%d of pointing gap" % npart) rot_pos = [] if (not voids[0]) and (not voids[npart-1]): gmax = -1 for idx in range(npart-2, 0, -1): if voids[idx]: gmax = idx break if gmax < 0: raise Exception("Failed to detect gap max") rot_pos = range(gmax+1, npart) for idx in range(len(x)): x[idx] = (x[idx] - pi2) if pos[idx] in rot_pos else x[idx] return (x.min(), x.max()) def __update_subtable_name(self, msname): self.open_table(msname) keys = self.table.getkeywords() self.close_table() self.field_table = sdutil.get_subtable_name(keys['FIELD']) self.spw_table = sdutil.get_subtable_name(keys['SPECTRAL_WINDOW']) self.source_table = sdutil.get_subtable_name(keys['SOURCE']) self.antenna_table = sdutil.get_subtable_name(keys['ANTENNA']) self.polarization_table = sdutil.get_subtable_name(keys['POLARIZATION']) self.observation_table = sdutil.get_subtable_name(keys['OBSERVATION']) self.pointing_table = sdutil.get_subtable_name(keys['POINTING']) self.data_desc_table = sdutil.get_subtable_name(keys['DATA_DESCRIPTION']) def _get_average_antenna_diameter(self, antenna): my_qa = quanta() self.open_table(self.antenna_table) try: antdiam_unit = self.table.getcolkeyword('DISH_DIAMETER', 'QuantumUnits')[0] diams = self.table.getcol('DISH_DIAMETER') finally: self.close_table() if len(antenna) == 0: antdiam_ave = my_qa.quantity(diams.mean(), antdiam_unit) else: d_ave = sum([diams[idx] for idx in antenna])/float(len(antenna)) antdiam_ave = my_qa.quantity(d_ave, antdiam_unit) return antdiam_ave