Coverage for /home/casatest/venv/lib/python3.12/site-packages/casatasks/private/task_sdimaging.py: 5%

801 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-21 07:43 +0000

1# sd task for imaging 

2import os 

3import re 

4import shutil 

5import string 

6 

7import numpy 

8 

9from casatasks import casalog 

10from casatools import image, measures, ms, msmetadata, quanta, table 

11 

12from . import mslisthelper, sdbeamutil, sdutil 

13from .task_tsdimaging import conform_mslist 

14 

15 

16@sdutil.sdtask_decorator 

17def sdimaging(infiles, outfile, overwrite, field, spw, antenna, scan, intent, 

18 mode, nchan, start, width, veltype, outframe, 

19 gridfunction, convsupport, truncate, gwidth, jwidth, 

20 imsize, cell, phasecenter, projection, ephemsrcname, 

21 pointingcolumn, restfreq, stokes, minweight, brightnessunit, clipminmax, 

22 # Performances optimization options 

23 enablecache, convertfirst, 

24 interpolation): 

25 

26 casalog.post( 

27 'The sdimaging task is deprecated and will be removed in future releases. ' 

28 'Please use tsdimaging instead.', 

29 "WARN") 

30 with sdimaging_worker(**locals()) as worker: 

31 worker.initialize() 

32 worker.execute() 

33 worker.finalize() 

34 

35 

36def is_string_type(val): 

37 """Return True if the argument is string type.""" 

38 return type(val) in [str, numpy.str_] 

39 

40 

41def smart_remove(path): 

42 if os.path.isdir(path): 

43 shutil.rmtree(path) 

44 else: 

45 os.remove(path) 

46 

47 

48def get_subtable_path(vis, name): 

49 return os.path.join(vis, name) 

50 

51 

52# functions from cleanhelper 

53class cleanhelper_minimal(object): 

54 def __init__(self, imtool='', vis='', sort_index=None, usescratch=False, casalog=casalog): 

55 if((type(vis)==list) and (len(vis)==1)): 

56 vis=vis[0] 

57 #### 

58 if isinstance(vis, str): 

59 vislist = [vis] 

60 else: 

61 vislist = vis 

62 assert not isinstance(imtool, str) 

63 self.im = imtool 

64 self.vislist = vislist 

65 assert sort_index is not None 

66 self.sortedvisindx = sort_index 

67 self.sortedvislist = [self.vislist[i] for i in sort_index] 

68 

69 self.dataspecframe='LSRK' 

70 self.usespecframe='' 

71 self.inframe=False 

72 self._casalog = casalog 

73 

74 def convertvf(self,vf,frame,field,restf,veltype='radio'): 

75 """ 

76 returns doppler(velocity) or frequency in string 

77 currently use first rest frequency 

78 Assume input vf (velocity or fequency in a string) and 

79 output are the same 'frame'. 

80 """ 

81 #pdb.set_trace() 

82 docalcf=False 

83 #if(frame==''): frame='LSRK' 

84 #Use datasepcframe, it is cleanhelper initialized to set 

85 #to LSRK 

86 if(frame==''): frame=self.dataspecframe 

87 qa = quanta() 

88 if(qa.quantity(vf)['unit'].find('m/s') > -1): 

89 docalcf=True 

90 elif(qa.quantity(vf)['unit'].find('Hz') > -1): 

91 docalcf=False 

92 else: 

93 if vf !=0: 

94 raise TypeError("Unrecognized unit for the velocity or frequency parameter") 

95 ##fldinds=ms.msseltoindex(self.vis, field=field)['field'].tolist() 

96 myms = ms() 

97 fldinds=myms.msseltoindex(self.vislist[self.sortedvisindx[0]], field=field)['field'].tolist() 

98 if(len(fldinds) == 0): 

99 fldid0=0 

100 else: 

101 fldid0=fldinds[0] 

102 if restf=='': 

103 #tb.open(self.vis+'/FIELD') 

104 fldtab=get_subtable_path(self.vislist[self.sortedvisindx[0]],'FIELD') 

105 tb = table() 

106 tb.open(fldtab) 

107 nfld = tb.nrows() 

108 if nfld >= fldid0: 

109 srcid=tb.getcell('SOURCE_ID',fldid0) 

110 else: 

111 raise TypeError( "Cannot set REST_FREQUENCY from the data: " + 

112 "no SOURCE corresponding field ID=%s, please supply restfreq" % fldid0 ) 

113 tb.close() 

114 # SOUECE_ID in FIELD table = -1 if no SOURCE table 

115 if srcid==-1: 

116 raise TypeError("Rest frequency info is not supplied") 

117 #tb.open(self.vis+'/SOURCE') 

118 sourcetab=get_subtable_path(self.vislist[self.sortedvisindx[0]], 'SOURCE') 

119 tb.open(sourcetab) 

120 tb2=tb.query('SOURCE_ID==%s' % srcid) 

121 tb.close() 

122 nsrc = tb2.nrows() 

123 if nsrc > 0: 

124 rfreq=tb2.getcell('REST_FREQUENCY',0) 

125 else: 

126 raise TypeError( "Cannot set REST_FREQUENCY from the data: "+ 

127 " no SOURCE corresponding field ID=%s, please supply restfreq" % fldid0 ) 

128 tb2.close() 

129 if(rfreq<=0): 

130 raise TypeError("Rest frequency does not seems to be properly set, check the data") 

131 else: 

132 if type(restf)==str: restf=[restf] 

133 if(qa.quantity(restf[0])['unit'].find('Hz') > -1): 

134 rfreq=[qa.convert(qa.quantity(restf[0]),'Hz')['value']] 

135 #print("using user input rest freq=",rfreq) 

136 else: 

137 raise TypeError("Unrecognized unit or type for restfreq") 

138 if(vf==0): 

139 # assume just want to get a restfrequecy from the data 

140 ret=str(rfreq[0])+'Hz' 

141 else: 

142 me = measures() 

143 if(docalcf): 

144 dop=me.doppler(veltype, qa.quantity(vf)) 

145 rvf=me.tofrequency(frame, dop, qa.quantity(rfreq[0],'Hz')) 

146 else: 

147 frq=me.frequency(frame, qa.quantity(vf)) 

148 rvf=me.todoppler(veltype, frq, qa.quantity(rfreq[0],'Hz')) 

149 ret=str(rvf['m0']['value'])+rvf['m0']['unit'] 

150 return ret 

151 

152 

153 def setChannelizeDefault(self,mode,spw,field,nchan,start,width,frame,veltype,phasec, restf,obstime=''): 

154 """ 

155 Determine appropriate values for channelization 

156 parameters when default values are used 

157 for mode='velocity' or 'frequency' or 'channel' 

158 This makes use of ms.cvelfreqs. 

159 """ 

160 ############### 

161 # for debugging 

162 ############### 

163 debug=False 

164 ############### 

165 spectable=get_subtable_path(self.vislist[self.sortedvisindx[0]], "SPECTRAL_WINDOW") 

166 tb = table() 

167 tb.open(spectable) 

168 chanfreqscol=tb.getvarcol('CHAN_FREQ') 

169 chanwidcol=tb.getvarcol('CHAN_WIDTH') 

170 spwframe=tb.getcol('MEAS_FREQ_REF'); 

171 tb.close() 

172 # first parse spw parameter: 

173 # use MSSelect if possible 

174 if len(self.sortedvislist) > 0: 

175 invis = self.sortedvislist[0] 

176 inspw = self.vislist.index(self.sortedvislist[0]) 

177 else: 

178 invis = self.vislist[0] 

179 inspw = 0 

180 myms = ms() 

181 myms.open(invis) 

182 if type(spw)==list: 

183 spw=spw[inspw] 

184 if spw in ('-1', '*', '', ' '): 

185 spw='*' 

186 if field=='': 

187 field='*' 

188 mssel=myms.msseltoindex(vis=invis, spw=spw, field=field) 

189 selspw=mssel['spw'] 

190 selfield=mssel['field'] 

191 chaninds=mssel['channel'].tolist() 

192 chanst0 = chaninds[0][1] 

193 

194 # frame 

195 spw0=selspw[0] 

196 chanfreqs=chanfreqscol['r'+str(spw0+1)].transpose()[0] 

197 chanres = chanwidcol['r'+str(spw0+1)].transpose()[0] 

198 

199 # ascending or desending data frequencies? 

200 # based on selected first spw's first CHANNEL WIDTH 

201 # ==> some MS data may have positive chan width 

202 # so changed to look at first two channels of chanfreq (TT) 

203 #if chanres[0] < 0: 

204 descending = False 

205 if len(chanfreqs) > 1 : 

206 if chanfreqs[1]-chanfreqs[0] < 0: 

207 descending = True 

208 else: 

209 if chanres[0] < 0: 

210 descending = True 

211 

212 # set dataspecframe: 

213 elspecframe=["REST", 

214 "LSRK", 

215 "LSRD", 

216 "BARY", 

217 "GEO", 

218 "TOPO", 

219 "GALACTO", 

220 "LGROUP", 

221 "CMB"] 

222 self.dataspecframe=elspecframe[spwframe[spw0]]; 

223 

224 # set usespecframe: user's frame if set, otherwise data's frame 

225 if(frame != ''): 

226 self.usespecframe=frame 

227 self.inframe=True 

228 else: 

229 self.usespecframe=self.dataspecframe 

230 

231 # some start and width default handling 

232 if mode!='channel': 

233 if width==1: 

234 width='' 

235 if start==0: 

236 start='' 

237 

238 #get restfreq 

239 if restf=='': 

240 fldtab=get_subtable_path(invis,'FIELD') 

241 tb.open(fldtab) 

242 nfld=tb.nrows() 

243 try: 

244 if nfld >= selfield[0]: 

245 srcid=tb.getcell('SOURCE_ID',selfield[0]) 

246 else: 

247 if mode=='velocity': 

248 raise TypeError( "Cannot set REST_FREQUENCY from the data: " + 

249 "no SOURCE corresponding field ID=%s, please supply restfreq" % selfield[0] ) 

250 finally: 

251 tb.close() 

252 #SOUECE_ID in FIELD table = -1 if no SOURCE table 

253 if srcid==-1: 

254 if mode=='velocity': 

255 raise TypeError("Rest frequency info is not supplied") 

256 try: 

257 srctab=get_subtable_path(invis, 'SOURCE') 

258 tb.open(srctab) 

259 tb2=tb.query('SOURCE_ID==%s' % srcid) 

260 nsrc = tb2.nrows() 

261 if nsrc > 0 and tb2.iscelldefined('REST_FREQUENCY',0): 

262 rfqs = tb2.getcell('REST_FREQUENCY',0) 

263 if len(rfqs)>0: 

264 restf=str(rfqs[0])+'Hz' 

265 else: 

266 if mode=='velocity': 

267 raise TypeError( "Cannot set REST_FREQUENCY from the data: " + 

268 "REST_FREQUENCY entry for ID %s in SOURCE table is empty, please supply restfreq" % srcid ) 

269 else: 

270 if mode=='velocity': 

271 raise TypeError( "Cannot set REST_FREQUENCY from the data: " + 

272 "no SOURCE corresponding field ID=%s, please supply restfreq" % selfield[0] ) 

273 finally: 

274 tb.close() 

275 tb2.close() 

276 

277 if type(phasec)==list: 

278 inphasec=phasec[0] 

279 else: 

280 inphasec=phasec 

281 if type(inphasec)==str and inphasec.isdigit(): 

282 inphasec=int(inphasec) 

283 #if nchan==1: 

284 # use data chan freqs 

285 # newfreqs=chanfreqs 

286 #else: 

287 # obstime not included here 

288 if debug: print("before ms.cvelfreqs (start,width,nchan)===>",start, width, nchan) 

289 try: 

290 newfreqs=myms.cvelfreqs(spwids=selspw,fieldids=selfield,mode=mode,nchan=nchan, 

291 start=start,width=width,phasec=inphasec, restfreq=restf, 

292 outframe=self.usespecframe,veltype=veltype).tolist() 

293 except: 

294 # ms must be closed here if ms.cvelfreqs failed with an exception 

295 myms.close() 

296 raise 

297 myms.close() 

298 

299 #print(newfreqs) 

300 descendingnewfreqs=False 

301 if len(newfreqs)>1: 

302 if newfreqs[1]-newfreqs[0] < 0: 

303 descendingnewfreqs=True 

304 

305 

306 try: 

307 if((nchan in [-1, "-1", "", " "]) or (start in [-1, "-1", "", " "])): 

308 frange=im.advisechansel(msname=invis, spwselection=spw, fieldid=selfield[0], getfreqrange=True) 

309 startchan=0 

310 endchan=len(newfreqs)-1 

311 if(descendingnewfreqs): 

312 startchan=numpy.min(numpy.where(frange['freqend'] < numpy.array(newfreqs))) 

313 endchan=numpy.min(numpy.where(frange['freqstart'] < numpy.array(newfreqs))) 

314 else: 

315 startchan=numpy.max(numpy.where(frange['freqstart'] > numpy.array(newfreqs))) 

316 endchan=numpy.max(numpy.where(frange['freqend'] > numpy.array(newfreqs))) 

317 if((start not in [-1, "-1", "", " "]) and (mode=="channel")): 

318 startchan=start 

319 if(nchan not in [-1, "-1", "", " "]): 

320 endchan=startchan+nchan-1 

321 newfreqs=(numpy.array(newfreqs)[startchan:endchan]).tolist() 

322 except: 

323 pass 

324 if debug: print("Mode, Start, width after cvelfreqs =",mode, start,width ) 

325 if type(newfreqs)==list and len(newfreqs) ==0: 

326 raise TypeError( "Output frequency grid cannot be calculated: " + 

327 " please check start and width parameters" ) 

328 if debug: 

329 if len(newfreqs)>1: 

330 print("FRAME=",self.usespecframe) 

331 print("newfreqs[0]===>",newfreqs[0]) 

332 print("newfreqs[1]===>",newfreqs[1]) 

333 print("newfreqs[-1]===>",newfreqs[-1]) 

334 print("len(newfreqs)===>",len(newfreqs)) 

335 else: 

336 print("newfreqs=",newfreqs) 

337 

338 # set output number of channels 

339 if nchan ==1: 

340 retnchan=1 

341 else: 

342 if len(newfreqs)>1: 

343 retnchan=len(newfreqs) 

344 else: 

345 retnchan=nchan 

346 newfreqs=chanfreqs.tolist() 

347 

348 # set start parameter 

349 # first analyze data order etc 

350 reverse=False 

351 negativew=False 

352 if descending: 

353 # channel mode case (width always >0) 

354 if width!="" and (type(width)==int or type(width)==float): 

355 if descendingnewfreqs: 

356 reverse=False 

357 else: 

358 reverse=True 

359 elif width=="": #default width 

360 if descendingnewfreqs and mode=="frequency": 

361 reverse=False 

362 else: 

363 reverse=True 

364 

365 elif type(width)==str: 

366 if width.lstrip().find('-')==0: 

367 negativew=True 

368 if descendingnewfreqs: 

369 if negativew: 

370 reverse=False 

371 else: 

372 reverse=True 

373 else: 

374 if negativew: 

375 reverse=True 

376 else: 

377 reverse=False 

378 else: #ascending data 

379 # depends on sign of width only 

380 # with CAS-3117 latest change(rev.15179), velocity start 

381 # means lowest velocity for default width 

382 if width=="" and mode=="velocity": #default width 

383 # ms.cvelfreqs returns correct order so no reversing 

384 reverse=False 

385 elif type(width)==str: 

386 if width.lstrip().find('-')==0: 

387 reverse=True 

388 else: 

389 reverse=False 

390 

391 if reverse: 

392 newfreqs.reverse() 

393 #if (start!="" and mode=='channel') or \ 

394 # (start!="" and type(start)!=int and mode!='channel'): 

395 # for now to avoid inconsistency later in imagecoordinates2 call 

396 # user's start parameter is preserved for channel mode only. 

397 # (i.e. the current code may adjust start parameter for other modes but 

398 # this probably needs to be changed, especially for multiple ms handling.) 

399 if ((start not in [-1, "", " "]) and mode=='channel'): 

400 retstart=start 

401 else: 

402 # default cases 

403 if mode=="frequency": 

404 retstart=str(newfreqs[0])+'Hz' 

405 elif mode=="velocity": 

406 #startfreq=str(newfreqs[-1])+'Hz' 

407 startfreq=(str(max(newfreqs))+'Hz') if(start=="") else (str(newfreqs[-1])+'Hz') 

408 retstart=self.convertvf(startfreq,frame,field,restf,veltype) 

409 elif mode=="channel": 

410 # default start case, use channel selection from spw 

411 retstart=chanst0 

412 

413 # set width parameter 

414 if width!="": 

415 retwidth=width 

416 else: 

417 if nchan==1: 

418 finc = chanres[0] 

419 else: 

420 finc = newfreqs[1]-newfreqs[0] 

421 if debug: print("finc(newfreqs1-newfreqs0)=",finc) 

422 if mode=="frequency": 

423 # It seems that this is no longer necessary... TT 2013-08-12 

424 #if descendingnewfreqs: 

425 # finc = -finc 

426 retwidth=str(finc)+'Hz' 

427 elif mode=="velocity": 

428 # for default width assume it is vel<0 (incresing in freq) 

429 if descendingnewfreqs: 

430 ind1=-2 

431 ind0=-1 

432 else: 

433 ind1=-1 

434 ind0=-2 

435 v1 = self.convertvf(str(newfreqs[ind1])+'Hz',frame,field,restf,veltype=veltype) 

436 v0 = self.convertvf(str(newfreqs[ind0])+'Hz',frame,field,restf,veltype=veltype) 

437 ##v1 = self.convertvf(str(newfreqs[-1])+'Hz',frame,field,restf,veltype=veltype) 

438 ##v0 = self.convertvf(str(newfreqs[-2])+'Hz',frame,field,restf,veltype=veltype) 

439 #v1 = self.convertvf(str(newfreqs[1])+'Hz',frame,field,restf,veltype=veltype) 

440 #v0 = self.convertvf(str(newfreqs[0])+'Hz',frame,field,restf,veltype=veltype) 

441 qa = quanta() 

442 if(qa.lt(v0, v1) and start==""): 

443 ###user used "" as start make sure step is +ve in vel as start is min vel possible for freqs selected 

444 retwidth=qa.tos(qa.sub(v1, v0)) 

445 else: 

446 retwidth = qa.tos(qa.sub(v0, v1)) 

447 else: 

448 retwidth=1 

449 if debug: print("setChan retwidth=",retwidth) 

450 return retnchan, retstart, retwidth 

451 

452 

453class sdimaging_worker(sdutil.sdtask_template_imaging): 

454 def __init__(self, **kwargs): 

455 super(sdimaging_worker, self).__init__(**kwargs) 

456 self.imager_param = {} 

457 self.sorted_idx = [] 

458 self.image_unit = "" 

459 

460 def __exit__(self, exc_type, exc_val, exc_tb): 

461 self.__del__() 

462 if exc_type: 

463 return False 

464 else: 

465 return True 

466 

467 def parameter_check(self): 

468 # outfile check 

469 sdutil.assert_outfile_canoverwrite_or_nonexistent(self.outfile, 

470 'im', 

471 self.overwrite) 

472 sdutil.assert_outfile_canoverwrite_or_nonexistent(self.outfile + '.weight', 

473 'im', 

474 self.overwrite) 

475 # fix spw 

476 if type(self.spw) == str: 

477 self.spw = self.__format_spw_string(self.spw) 

478 # check unit of start and width 

479 # fix default 

480 if self.mode == 'channel': 

481 if self.start == '': 

482 self.start = 0 

483 if self.width == '': 

484 self.width = 1 

485 else: 

486 if self.start == 0: 

487 self.start = '' 

488 if self.width == 1: 

489 self.width = '' 

490 # fix unit 

491 if self.mode == 'frequency': 

492 myunit = 'Hz' 

493 elif self.mode == 'velocity': 

494 myunit = 'km/s' 

495 else: # channel 

496 myunit = '' 

497 

498 for name in ['start', 'width']: 

499 param = getattr(self, name) 

500 new_param = self.__format_quantum_unit(param, myunit) 

501 if new_param is None: 

502 raise ValueError("Invalid unit for %s in mode %s: %s" % 

503 (name, self.mode, param)) 

504 setattr(self, name, new_param) 

505 

506 casalog.post("mode='%s': start=%s, width=%s, nchan=%d" % 

507 (self.mode, self.start, self.width, self.nchan)) 

508 

509 # check length of selection parameters 

510 if is_string_type(self.infiles): 

511 nfile = 1 

512 self.infiles = [self.infiles] 

513 else: 

514 nfile = len(self.infiles) 

515 

516 for name in ['field', 'spw', 'antenna', 'scanno']: 

517 param = getattr(self, name) 

518 if not self.__check_selection_length(param, nfile): 

519 raise ValueError("Length of %s != infiles." % (name)) 

520 # set convsupport default 

521 if self.convsupport >= 0 and self.gridfunction.upper() != 'SF': 

522 casalog.post( 

523 f"user defined convsupport is ignored for {self.gridfunction} kernel", 

524 priority='WARN') 

525 self.convsupport = -1 

526 

527 def __format_spw_string(self, spw): 

528 """Return formatted spw selection string which is accepted by imager.""" 

529 if type(spw) != str: 

530 raise ValueError("The parameter should be string.") 

531 if spw.strip() == '*': 

532 spw = '' 

533 # WORKAROUND for CAS-6422, i.e., ":X~Y" fails while "*:X~Y" works. 

534 if spw.startswith(":"): 

535 spw = '*' + spw 

536 return spw 

537 

538 def __format_quantum_unit(self, data, unit): 

539 """Format quantity data. 

540 

541 Returns False if data has an unit which in not a variation of 

542 input unit. 

543 Otherwise, returns input data as a quantum string. The input 

544 unit is added to the return value if no unit is in data. 

545 """ 

546 my_qa = quanta() 

547 if data == '' or my_qa.compare(data, unit): 

548 return data 

549 if my_qa.getunit(data) == '': 

550 casalog.post("No unit specified. Using '%s'" % unit) 

551 return '%f%s' % (data, unit) 

552 return None 

553 

554 def __check_selection_length(self, data, nfile): 

555 """Check length of the data. 

556 

557 Returns true if data is either a string, an array with length 

558 1 or nfile 

559 """ 

560 if not is_string_type(data) and len(data) not in [1, nfile]: 

561 return False 

562 return True 

563 

564 def get_selection_param_for_ms(self, fileid, param): 

565 """Return valid selection string for a certain ms. 

566 

567 Arguments 

568 fileid : file idx in infiles list 

569 param : string (array) selection value 

570 """ 

571 if is_string_type(param): 

572 return param 

573 elif len(param) == 1: 

574 return param[0] 

575 else: 

576 return param[fileid] 

577 

578 def get_selection_idx_for_ms(self, file_idx): 

579 """Return a dictionary of selection indices for i-th MS in infiles. 

580 

581 Argument: file idx in infiles list 

582 """ 

583 if file_idx < len(self.infiles) and file_idx > -1: 

584 vis = self.infiles[file_idx] 

585 field = self.get_selection_param_for_ms(file_idx, self.field) 

586 spw = self.get_selection_param_for_ms(file_idx, self.spw) 

587 spw = self.__format_spw_string(spw) 

588 antenna = self.get_selection_param_for_ms(file_idx, self.antenna) 

589 if antenna == -1: 

590 antenna = '' 

591 scan = self.get_selection_param_for_ms(file_idx, self.scanno) 

592 intent = self.get_selection_param_for_ms(file_idx, self.intent) 

593 my_ms = ms() 

594 sel_ids = my_ms.msseltoindex(vis=vis, spw=spw, field=field, 

595 baseline=antenna, scan=scan) 

596 fieldid = list(sel_ids['field']) if len(sel_ids['field']) > 0 else -1 

597 baseline = self.format_ac_baseline(sel_ids['antenna1']) 

598 scanid = list(sel_ids['scan']) if len(sel_ids['scan']) > 0 else "" 

599 # SPW (need to get a list of valid spws instead of -1) 

600 if len(sel_ids['channel']) > 0: 

601 spwid = [chanarr[0] for chanarr in sel_ids['channel']] 

602 elif spw == "": # No spw selection 

603 my_ms.open(vis) 

604 try: 

605 spwinfo = my_ms.getspectralwindowinfo() 

606 except Exception: 

607 raise 

608 finally: 

609 my_ms.close() 

610 

611 spwid = [int(idx) for idx in spwinfo.keys()] 

612 else: 

613 raise RuntimeError("Invalid spw selction, %s ,for MS %d" % (str(spw), file_idx)) 

614 

615 return {'field': fieldid, 'spw': spwid, 'baseline': baseline, 'scan': scanid, 

616 'intent': intent, 'antenna1': sel_ids['antenna1']} 

617 else: 

618 raise ValueError("Invalid file index, %d" % file_idx) 

619 

620 def format_ac_baseline(self, in_antenna): 

621 """Format auto-correlation baseline string from antenna idx list.""" 

622 # exact match string 

623 if is_string_type(in_antenna): 

624 # return sdutil.convert_antenna_spec_autocorr(in_antenna) 

625 if (len(in_antenna) != 0) and (in_antenna.find('&') == -1) \ 

626 and (in_antenna.find(';') == -1): 

627 in_antenna = + '&&&' 

628 return in_antenna 

629 # single integer -> list of int 

630 if type(in_antenna) == int: 

631 if in_antenna >= 0: 

632 in_antenna = [in_antenna] 

633 else: 

634 return -1 

635 # format auto-corr string from antenna idices. 

636 baseline = '' 

637 for idx in in_antenna: 

638 if len(baseline) > 0: 

639 baseline += ';' 

640 if idx >= 0: 

641 baseline += (str(idx) + '&&&') 

642 return baseline 

643 

644 def compile(self): 

645 # imaging mode 

646 self.imager_param['mode'] = self.mode 

647 

648 # Work on selection of the first table in sorted list 

649 # to get default restfreq and outframe 

650 # chronological sort 

651 sorted_vislist, sorted_timelist = mslisthelper.sort_mslist(self.infiles) 

652 self.sorted_idx = [self.infiles.index(vis) for vis in sorted_vislist] 

653 mslisthelper.report_sort_result(sorted_vislist, sorted_timelist, self.sorted_idx, mycasalog=casalog) 

654 # conform MS 

655 conform_mslist(sorted_vislist, ignore_columns=[]) 

656 selection_ids = self.get_selection_idx_for_ms(self.sorted_idx[0]) 

657 self.__update_subtable_name(self.infiles[self.sorted_idx[0]]) 

658 # field 

659 fieldid = selection_ids['field'][0] if type( 

660 selection_ids['field']) != int else selection_ids['field'] 

661 sourceid = -1 

662 self.open_table(self.field_table) 

663 source_ids = self.table.getcol('SOURCE_ID') 

664 self.close_table() 

665 if self.field == '' or fieldid == -1: 

666 sourceid = source_ids[0] 

667 elif fieldid >= 0 and fieldid < len(source_ids): 

668 sourceid = source_ids[fieldid] 

669 else: 

670 raise ValueError("No valid field in the first MS.") 

671 

672 # restfreq 

673 if self.restfreq == '' and self.source_table != '': 

674 self.open_table(self.source_table) 

675 source_ids = self.table.getcol('SOURCE_ID') 

676 for i in range(self.table.nrows()): 

677 if sourceid == source_ids[i] \ 

678 and self.table.iscelldefined('REST_FREQUENCY', i) \ 

679 and (selection_ids['spw'] == -1 or 

680 self.table.getcell('SPECTRAL_WINDOW_ID', i) in selection_ids['spw']): 

681 rf = self.table.getcell('REST_FREQUENCY', i) 

682 if len(rf) > 0: 

683 self.restfreq = self.table.getcell('REST_FREQUENCY', i)[0] 

684 break 

685 self.close_table() 

686 casalog.post("restfreq set to %s" % self.restfreq, "INFO") 

687 # REST_FREQUENCY column is optional (need retry if not exists) 

688 self.imager_param['restfreq'] = self.restfreq 

689 

690 # 

691 # spw (define representative spw id = spwid_ref) 

692 spwid_ref = selection_ids['spw'][0] \ 

693 if type(selection_ids['spw']) != int else selection_ids['spw'] 

694 # Invalid spw selection should have handled at msselectiontoindex(). 

695 # -1 means all spw are selected. 

696 self.open_table(self.spw_table) 

697 if spwid_ref < 0: 

698 for id in range(self.table.nrows()): 

699 if self.table.getcell('NUM_CHAN', id) > 0: 

700 spwid_ref = id 

701 break 

702 if spwid_ref < 0: 

703 self.close_table() 

704 msg = 'No valid spw id exists in the first table' 

705 raise ValueError(msg) 

706 self.allchannels = self.table.getcell('NUM_CHAN', spwid_ref) 

707 # freq_chan0 = self.table.getcell('CHAN_FREQ', spwid_ref)[0] 

708 # freq_inc0 = self.table.getcell('CHAN_WIDTH', spwid_ref)[0] 

709 # in case rest frequency is not defined yet. 

710 if self.restfreq == '': 

711 self.restfreq = '%fHz' % self.table.getcell('CHAN_FREQ', spwid_ref).mean() 

712 self.imager_param['restfreq'] = self.restfreq 

713 casalog.post("Using mean freq of spw %d as restfreq: %s" % 

714 (spwid_ref, self.restfreq), "INFO") 

715 self.close_table() 

716 self.imager_param['spw'] = -1 # spwid_ref 

717 

718 # outframe (force using the current frame) 

719 self.imager_param['outframe'] = self.outframe 

720 if self.outframe == '': 

721 if len(self.infiles) > 1: 

722 # The default will be 'LSRK' 

723 casalog.post("Multiple MS inputs. The default outframe is set to 'LSRK'") 

724 self.imager_param['outframe'] = 'LSRK' 

725 else: 

726 # get from MS 

727 my_ms = ms() 

728 my_ms.open(self.infiles[0]) 

729 spwinfo = my_ms.getspectralwindowinfo() 

730 my_ms.close() 

731 del my_ms 

732 for key, spwval in spwinfo.items(): 

733 if spwval['SpectralWindowId'] == spwid_ref: 

734 self.imager_param['outframe'] = spwval['Frame'] 

735 casalog.post( 

736 f"Using frequency frame of MS, '{self.imager_param['outframe']}'") 

737 break 

738 if self.imager_param['outframe'] == '': 

739 raise Exception("Internal error of getting frequency frame of spw=%d." % spwid_ref) 

740 else: 

741 casalog.post( 

742 f"Using frequency frame defined by user, '{self.imager_param['outframe']}'") 

743 

744 # brightness unit 

745 if len(self.brightnessunit) > 0: 

746 if self.brightnessunit.lower() == 'k': 

747 self.image_unit = 'K' 

748 elif self.brightnessunit.lower() == 'jy/beam': 

749 self.image_unit = 'Jy/beam' 

750 else: 

751 raise ValueError("Invalid brightness unit, %s" % self.brightnessunit) 

752 

753# # antenna 

754# in_antenna = self.antenna # backup for future use 

755# if type(self.antenna)==int: 

756# if self.antenna >= 0: 

757# self.antenna=str(self.antenna)+'&&&' 

758# else: 

759# if (len(self.antenna) != 0) and (self.antenna.find('&') == -1) \ 

760# and (self.antenna.find(';')==-1): 

761# self.antenna = self.antenna + '&&&' 

762 

763 def _configure_map_property(self): 

764 selection_ids = self.get_selection_idx_for_ms(self.sorted_idx[0]) 

765 

766 # stokes 

767 if self.stokes == '': 

768 self.stokes = 'I' 

769 self.imager_param['stokes'] = self.stokes 

770 

771 # gridfunction 

772 

773 # outfile 

774 if os.path.exists(self.outfile) and self.overwrite: 

775 smart_remove(self.outfile) 

776 if os.path.exists(self.outfile + '.weight') and self.overwrite: 

777 smart_remove(self.outfile + '.weight') 

778 

779 # cell 

780 cell = self.cell 

781 if cell == '' or cell[0] == '': 

782 # Calc PB 

783 grid_factor = 3. 

784 casalog.post( 

785 "The cell size will be calculated using PB size of antennas in the first MS") 

786 qPB = self._calc_PB(selection_ids['antenna1']) 

787 cell = '%f%s' % (qPB['value'] / grid_factor, qPB['unit']) 

788 casalog.post("Using cell size = PB/%4.2F = %s" % (grid_factor, cell)) 

789 

790 (cellx, celly) = sdutil.get_cellx_celly(cell, unit='arcmin') 

791 self.imager_param['cellx'] = cellx 

792 self.imager_param['celly'] = celly 

793 

794 # Calculate Pointing center and extent (if necessary) 

795 # return a dictionary with keys 'center', 'width', 'height' 

796 imsize = sdutil.to_list(self.imsize, int) or \ 

797 sdutil.to_list(self.imsize, numpy.integer) 

798 if imsize is None: 

799 imsize = self.imsize if hasattr(self.imsize, '__iter__') else [self.imsize] 

800 imsize = [int(numpy.ceil(v)) for v in imsize] 

801 casalog.post( 

802 "imsize is not integers. force converting to integer pixel numbers.", 

803 priority="WARN") 

804 casalog.post("rounded-up imsize: %s --> %s" % (str(self.imsize), str(imsize))) 

805 

806 phasecenter = self.phasecenter 

807 if self.phasecenter == "" or \ 

808 len(imsize) == 0 or imsize[0] < 1: 

809 map_param = self._get_pointing_extent() 

810 # imsize 

811 if len(imsize) == 0 or imsize[0] < 1: 

812 imsize = self._get_imsize(map_param['width'], map_param['height'], cellx, celly) 

813 if self.phasecenter != "": 

814 casalog.post( 

815 "You defined phasecenter but not imsize. " 

816 "The image will cover as wide area as pointing in MS extends, " 

817 "but be centered at phasecenter. " 

818 "This could result in a strange image if your phasecenter is " 

819 "apart from the center of pointings", 

820 priority='WARN') 

821 if imsize[0] > 1024 or imsize[1] > 1024: 

822 casalog.post( 

823 "The calculated image pixel number is larger than 1024. " 

824 "It could take time to generate the image depending on " 

825 "your computer resource. Please wait...", 

826 priority='WARN') 

827 

828 # phasecenter 

829 # if empty, it should be determined here... 

830 if self.phasecenter == "": 

831 phasecenter = map_param['center'] 

832 

833 # imsize 

834 (nx, ny) = sdutil.get_nx_ny(imsize) 

835 self.imager_param['nx'] = nx 

836 self.imager_param['ny'] = ny 

837 

838 # phasecenter 

839 self.imager_param['phasecenter'] = phasecenter 

840 

841 self.imager_param['movingsource'] = self.ephemsrcname 

842 

843 # channel map 

844 sorted_vislist, _ = mslisthelper.sort_mslist(self.infiles) 

845 self.sorted_idx = [self.infiles.index(vis) for vis in sorted_vislist] 

846 imhelper = cleanhelper_minimal(self.imager, self.infiles, sort_index=self.sorted_idx, casalog=casalog) 

847 spwsel = str(',').join([str(spwid) for spwid in selection_ids['spw']]) 

848 srestf = self.imager_param['restfreq'] \ 

849 if is_string_type(self.imager_param['restfreq']) \ 

850 else "%fHz" % self.imager_param['restfreq'] 

851 (imnchan, imstart, imwidth) = imhelper.setChannelizeDefault( 

852 self.mode, spwsel, self.field, self.nchan, 

853 self.start, self.width, self.imager_param['outframe'], 

854 self.veltype, self.imager_param['phasecenter'], srestf) 

855 del imhelper 

856 

857 # start and width 

858 if self.mode == 'velocity': 

859 startval = [self.imager_param['outframe'], imstart] 

860 widthval = imwidth 

861 elif self.mode == 'frequency': 

862 startval = [self.imager_param['outframe'], imstart] 

863 widthval = imwidth 

864 else: # self.mode==channel 

865 startval = int(self.start) 

866 widthval = int(self.width) 

867 

868 if self.nchan < 0: 

869 self.nchan = self.allchannels 

870 self.imager_param['start'] = startval 

871 self.imager_param['step'] = widthval 

872 self.imager_param['nchan'] = imnchan # self.nchan 

873 

874 self.imager_param['projection'] = self.projection 

875 

876 def execute(self): 

877 # imaging 

878 casalog.post("Start imaging...", "INFO") 

879 if len(self.infiles) == 1: 

880 self.open_imager(self.infiles[0]) 

881 selection_ids = self.get_selection_idx_for_ms(0) 

882 spwsel = self.get_selection_param_for_ms(0, self.spw) 

883 if spwsel.strip() in ['', '*']: 

884 spwsel = selection_ids['spw'] 

885 # TODO: channel selection based on spw 

886 ok = self.imager.selectvis(field=selection_ids['field'], 

887 # spw=selection_ids['spw'], 

888 spw=spwsel, 

889 nchan=-1, start=0, step=1, 

890 baseline=selection_ids['baseline'], 

891 scan=selection_ids['scan'], 

892 intent=selection_ids['intent']) 

893 if not ok: 

894 raise ValueError("Selection is empty: you may want to review this MS selection") 

895 else: 

896 self.close_imager() 

897 # self.sorted_idx.reverse() 

898 for idx in self.sorted_idx.__reversed__(): 

899 name = self.infiles[idx] 

900 selection_ids = self.get_selection_idx_for_ms(idx) 

901 spwsel = self.get_selection_param_for_ms(idx, self.spw) 

902 if spwsel.strip() in ['', '*']: 

903 spwsel = selection_ids['spw'] 

904 # TODO: channel selection based on spw 

905 self.imager.selectvis(vis=name, field=selection_ids['field'], 

906 # spw=selection_ids['spw'], 

907 spw=spwsel, 

908 nchan=-1, start=0, step=1, 

909 baseline=selection_ids['baseline'], 

910 scan=selection_ids['scan'], 

911 intent=selection_ids['intent']) 

912 # need to do this 

913 self.is_imager_opened = True 

914 

915 # it should be called after infiles are registered to imager 

916 self._configure_map_property() 

917 

918 casalog.post(f"Using phasecenter {self.imager_param['phasecenter']}", "INFO") 

919 

920 self.imager.defineimage(**self.imager_param) # self.__get_param()) 

921 self.imager.setoptions(ftmachine='sd', gridfunction=self.gridfunction, freqinterp=self.interpolation) 

922 self.imager.setsdoptions( 

923 pointingcolumntouse=self.pointingcolumn, 

924 convsupport=self.convsupport, 

925 truncate=self.truncate, 

926 gwidth=self.gwidth, 

927 jwidth=self.jwidth, 

928 minweight=0., 

929 clipminmax=self.clipminmax, 

930 enablecache=self.enablecache, 

931 convertfirst=self.convertfirst 

932 ) 

933 

934 # Create images 

935 imgtype_suffix = {'singledish': '', 'coverage': '.weight'} 

936 for img_type, img_suffix in imgtype_suffix.items(): 

937 img_file = self.outfile + img_suffix 

938 msg_fmt = string.Template(f"$state {img_type} image {img_file}") 

939 casalog.post(msg_fmt.substitute(state="Generating"), "INFO") 

940 self.imager.makeimage(type=img_type, image=img_file) 

941 if not os.path.exists(img_file): 

942 raise RuntimeError(msg_fmt.substitute(state="Failed to generate")) 

943 casalog.post(msg_fmt.substitute(state="Generated"), "INFO") 

944 # Close imager 

945 self.close_imager() 

946 

947 # Convert output images to proper output frame and set brightness unit (if necessary) 

948 my_ia = image() 

949 my_ia.open(self.outfile) 

950 csys = my_ia.coordsys() 

951 csys.setconversiontype(spectral=csys.referencecode('spectra')[0]) 

952 my_ia.setcoordsys(csys.torecord()) 

953 if len(self.image_unit) > 0: 

954 casalog.post("Setting brightness unit '%s' to image." % self.image_unit) 

955 my_ia.setbrightnessunit(self.image_unit) 

956 csys.done() 

957 my_ia.close() 

958 

959 # CAS-12984 set brightness unit for weight image to '' 

960 weightfile = self.outfile + imgtype_suffix['coverage'] 

961 my_ia.open(weightfile) 

962 my_ia.setbrightnessunit('') 

963 my_ia.close() 

964 

965 # Mask image pixels whose weight are smaller than minweight. 

966 # Weight image should have 0 weight for pixels below < minweight 

967 casalog.post("Start masking the map using minweight = %f" % 

968 self.minweight, "INFO") 

969 my_ia.open(weightfile) 

970 try: 

971 stat = my_ia.statistics(mask="'" + weightfile + "' > 0.0", robust=True) 

972 valid_pixels = stat['npts'] 

973 except RuntimeError as e: 

974 if str(e).find('No valid data found.') >= 0: 

975 valid_pixels = [0] 

976 else: 

977 raise 

978 if len(valid_pixels) == 0 or valid_pixels[0] == 0: 

979 my_ia.close() 

980 casalog.post( 

981 "All pixels weight zero. This indicates no data in MS is in image area. " 

982 "Mask will not be set. Please check your image parameters.", 

983 priority="WARN") 

984 return 

985 median_weight = stat['median'][0] 

986 weight_threshold = median_weight * self.minweight 

987 casalog.post("Median of weight in the map is %f" % median_weight, 

988 "INFO") 

989 casalog.post("Pixels in map with weight <= median(weight)*minweight = %f will be masked." % 

990 (weight_threshold), "INFO") 

991 # Leaving the original logic to calculate the number of masked pixels via 

992 # product of median of and min_weight (which i don't understand the logic) 

993 # if one wanted to find how many pixel were masked one could easily count the 

994 # number of pixels set to false 

995 # e.g after masking self.outfile below one could just do this 

996 # nmasked_pixels=tb.calc( 

997 # '[select from "'+self.outfile+'"/mask0'+'" giving [nfalse(PagedArray)]]') 

998 my_tb = table() 

999 nmask_pixels = 0 

1000 nchan = stat['trc'][3] + 1 

1001 casalog.filter('ERROR') # hide the useless message of tb.calc 

1002 

1003 # doing it by channel to make sure it does not go out of memory 

1004 # tab.calc try to load the whole chunk in ram 

1005 for k in range(nchan): 

1006 nmask_pixels += my_tb.calc( 

1007 '[select from "' + weightfile + '" giving [ntrue(map[,,,' + 

1008 str(k) + '] <=' + str(median_weight * self.minweight) + ')]]')['0'][0] 

1009 casalog.filter() # set logging back to normal 

1010 

1011 casalog.filter() # set logging back to normal 

1012 imsize = numpy.prod(my_ia.shape()) 

1013 my_ia.close() 

1014 # Modify default mask 

1015 my_ia.open(self.outfile) 

1016 my_ia.calcmask("'%s'>%f" % (weightfile, weight_threshold), asdefault=True) 

1017 my_ia.close() 

1018 masked_fraction = 100. * (1. - (imsize - nmask_pixels) / float(valid_pixels[0])) 

1019 casalog.post("This amounts to %5.1f %% of the area with nonzero weight." % 

1020 (masked_fraction), "INFO") 

1021 casalog.post( 

1022 f"The weight image '{weightfile}' is returned by this task, " 

1023 "if the user wishes to assess the results in detail.", 

1024 priority="INFO") 

1025 

1026 # Calculate theoretical beam size 

1027 casalog.post("Calculating image beam size.") 

1028 if self.gridfunction.upper() not in ['SF']: 

1029 casalog.post( 

1030 f"Beam size definition for '{self.gridfunction}' kernel is experimental.", 

1031 priority='WARN') 

1032 casalog.post( 

1033 "You may want to take careful look at the restoring beam in the image.", 

1034 priority='WARN') 

1035 my_msmd = msmetadata() 

1036 # antenna diameter and blockage 

1037 ref_ms_idx = self.sorted_idx[0] 

1038 ref_ms_name = self.infiles[ref_ms_idx] 

1039 selection_ids = self.get_selection_idx_for_ms(ref_ms_idx) 

1040 ant_idx = selection_ids['antenna1'] 

1041 diameter = self._get_average_antenna_diameter(ant_idx) 

1042 my_msmd.open(ref_ms_name) 

1043 ant_name = my_msmd.antennanames(ant_idx) 

1044 my_msmd.close() 

1045 is_alma = False 

1046 for name in ant_name: 

1047 if name[0:2] in ["PM", "DV", "DA", "CM"]: 

1048 is_alma = True 

1049 break 

1050 blockage = "0.75m" if is_alma else "0.0m" # unknown blockage diameter 

1051 # output reference code 

1052 my_ia.open(self.outfile) 

1053 csys = my_ia.coordsys() 

1054 my_ia.close() 

1055 outref = csys.referencecode('direction')[0] 

1056 cell = list(csys.increment(type='direction', format='s')['string']) 

1057 csys.done() 

1058 # pointing sampling 

1059 ref_ms_spw = self.get_selection_param_for_ms(ref_ms_idx, self.spw) 

1060 ref_ms_field = self.get_selection_param_for_ms(ref_ms_idx, self.field) 

1061 ref_ms_scan = self.get_selection_param_for_ms(ref_ms_idx, self.scanno) 

1062 # xSampling, ySampling, angle = sdutil.get_ms_sampling_arcsec(ref_ms_name, spw=ref_ms_spw, 

1063 # antenna=selection_ids['baseline'], 

1064 # field=ref_ms_field, 

1065 # scan=ref_ms_scan,#timerange='', 

1066 # outref=outref) 

1067 

1068 # obtain sampling interval for beam calculation. 

1069 self.open_imager(ref_ms_name) 

1070 ok = self.imager.selectvis(field=ref_ms_field, 

1071 # spw=selection_ids['spw'], 

1072 spw=ref_ms_spw, 

1073 nchan=-1, start=0, step=1, 

1074 baseline=selection_ids['baseline'], 

1075 scan=ref_ms_scan, 

1076 intent=selection_ids['intent']) 

1077 if len(ant_idx) > 1: 

1078 casalog.post("Using only antenna %s to calculate sampling interval" % ant_name[0]) 

1079 ptg_samp = self.imager.pointingsampling(pattern='raster', ref=outref, 

1080 movingsource=self.ephemsrcname, 

1081 pointingcolumntouse=self.pointingcolumn, 

1082 antenna=('%s&&&' % ant_name[0])) 

1083 self.close_imager() 

1084 my_qa = quanta() 

1085 xSampling, ySampling = my_qa.getvalue(my_qa.convert(ptg_samp['sampling'], 'arcsec')) 

1086 angle = my_qa.getvalue(my_qa.convert(ptg_samp['angle'], "deg"))[0] 

1087 

1088 casalog.post("Detected raster sampling = [{x:f}, {y:f}] arcsec".format( 

1089 x=xSampling, 

1090 y=ySampling 

1091 )) 

1092 

1093 # handling of failed sampling detection 

1094 valid_sampling = True 

1095 sampling = [xSampling, ySampling] 

1096 if abs(xSampling) < 2.2e-3 or not numpy.isfinite(xSampling): 

1097 casalog.post( 

1098 f"Invalid sampling={xSampling} arcsec. " 

1099 f"Using the value of orthogonal direction={ySampling} arcsec", 

1100 priority="WARN") 

1101 sampling = [ySampling] 

1102 angle = 0.0 

1103 valid_sampling = False 

1104 if abs(ySampling) < 1.0e-3 or not numpy.isfinite(ySampling): 

1105 if valid_sampling: 

1106 casalog.post( 

1107 f"Invalid sampling={ySampling} arcsec. " 

1108 f"Using the value of orthogonal direction={xSampling} arcsec", 

1109 priority="WARN") 

1110 sampling = [xSampling] 

1111 angle = 0.0 

1112 valid_sampling = True 

1113 # reduce sampling and cell if it's possible 

1114 if len(sampling) > 1 and abs(sampling[0] - sampling[1]) <= 0.01 * abs(sampling[0]): 

1115 sampling = [sampling[0]] 

1116 angle = 0.0 

1117 if cell[0] == cell[1]: 

1118 cell = [cell[0]] 

1119 if valid_sampling: 

1120 # actual calculation of beam size 

1121 bu = sdbeamutil.TheoreticalBeam() 

1122 bu.set_antenna(diameter, blockage) 

1123 bu.set_sampling(sampling, "%fdeg" % angle) 

1124 bu.set_image_param(cell, self.restfreq, self.gridfunction, 

1125 self.convsupport, self.truncate, self.gwidth, 

1126 self.jwidth, is_alma) 

1127 bu.summary() 

1128 imbeam_dict = bu.get_beamsize_image() 

1129 casalog.post("Setting image beam: major={major}, minor={minor}, pa={pa}".format( 

1130 major=imbeam_dict['major'], 

1131 minor=imbeam_dict['minor'], 

1132 pa=imbeam_dict['pa'] 

1133 )) 

1134 # set beam size to image 

1135 my_ia.open(self.outfile) 

1136 my_ia.setrestoringbeam(**imbeam_dict) 

1137 my_ia.close() 

1138 else: 

1139 # BOTH sampling was invalid 

1140 casalog.post( 

1141 "Could not detect valid raster sampling. " 

1142 "Exitting without setting beam size to image", 

1143 priority='WARN') 

1144 

1145 def _calc_PB(self, antenna): 

1146 """Calculate the primary beam size of antenna. 

1147 

1148 Calculate the primary beam size of antenna, using dish diamenter 

1149 and rest frequency 

1150 Average antenna diamter and reference frequency are adopted for 

1151 calculation. 

1152 The input argument should be a list of antenna IDs. 

1153 """ 

1154 casalog.post("Calculating Primary beam size:") 

1155 # CAS-5410 Use private tools inside task scripts 

1156 my_qa = quanta() 

1157 

1158 pb_factor = 1.175 

1159 # Reference frequency 

1160 ref_freq = self.restfreq 

1161 if type(ref_freq) in [float, numpy.float64]: 

1162 ref_freq = my_qa.tos(my_qa.quantity(ref_freq, 'Hz')) 

1163 if not my_qa.compare(ref_freq, 'Hz'): 

1164 msg = "Could not get the reference frequency. " + \ 

1165 "Your data does not seem to have valid one in selected field.\n" + \ 

1166 "PB is not calculated.\n" + \ 

1167 "Please set restreq or cell manually to generate an image." 

1168 raise Exception(msg) 

1169 # Antenna diameter 

1170 antdiam_ave = self._get_average_antenna_diameter(antenna) 

1171 # Calculate PB 

1172 wave_length = 0.2997924 / my_qa.convert(my_qa.quantity(ref_freq), 'GHz')['value'] 

1173 D_m = my_qa.convert(antdiam_ave, 'm')['value'] 

1174 lambda_D = wave_length / D_m * 3600. * 180 / numpy.pi 

1175 PB = my_qa.quantity(pb_factor * lambda_D, 'arcsec') 

1176 # Summary 

1177 casalog.post("- Antenna diameter: %s m" % D_m) 

1178 casalog.post("- Reference Frequency: %s" % ref_freq) 

1179 casalog.post("PB size = %5.3f * lambda/D = %s" % (pb_factor, my_qa.tos(PB))) 

1180 return PB 

1181 

1182 def _get_imsize(self, width, height, dx, dy): 

1183 casalog.post("Calculating pixel size.") 

1184 # CAS-5410 Use private tools inside task scripts 

1185 my_qa = quanta() 

1186 ny = numpy.ceil((my_qa.convert(height, my_qa.getunit(dy))['value'] / 

1187 my_qa.getvalue(dy))) 

1188 nx = numpy.ceil((my_qa.convert(width, my_qa.getunit(dx))['value'] / 

1189 my_qa.getvalue(dx))) 

1190 casalog.post("- Map extent: [%s, %s]" % (my_qa.tos(width), my_qa.tos(height))) 

1191 casalog.post("- Cell size: [%s, %s]" % (my_qa.tos(dx), my_qa.tos(dy))) 

1192 casalog.post("Image pixel numbers to cover the extent: [%d, %d] (projected)" % 

1193 (nx + 1, ny + 1)) 

1194 return (int(nx + 1), int(ny + 1)) 

1195 

1196 def _get_pointing_extent(self): 

1197 # MS selection is ignored. This is not quite right. 

1198 casalog.post("Calculating map extent from pointings.") 

1199 # CAS-5410 Use private tools inside task scripts 

1200 my_qa = quanta() 

1201 ret_dict = {} 

1202 

1203 colname = self.pointingcolumn.upper() 

1204 

1205 # MSs should be registered to imager 

1206 if not self.is_imager_opened: 

1207 raise RuntimeError('Internal error: imager should be opened here.') 

1208 

1209 if self.phasecenter == "": 

1210 # defaut is J2000 

1211 base_mref = 'J2000' 

1212 elif isinstance(self.phasecenter, int) or self.phasecenter.isdigit(): 

1213 # may be field id 

1214 self.open_table(self.field_table) 

1215 base_mref = self.table.getcolkeyword('PHASE_DIR', 'MEASINFO')['Ref'] 

1216 self.close_table() 

1217 else: 

1218 # may be phasecenter is explicitly specified 

1219 # numeric value: 3.14, -.3e1, etc. 

1220 numeric_pattern = r'[-+]?([0-9]+(.[0-9]*)?|\.[0-9]+)([eE]-?[0-9])?' 

1221 # HMS string: 9:15:29, -9h15m29 

1222 hms_pattern = r'[-+]?[0-9]+[:h][0-9]+[:m][0-9.]+s?' 

1223 # DMS string: 9.15.29, -9d15m29s 

1224 dms_pattern = r'[-+]?[0-9]+[.d][0-9]+[.m][0-9.]+s?' 

1225 # composite pattern 

1226 pattern = fr'^({numeric_pattern}|{hms_pattern}|{dms_pattern})$' 

1227 items = self.phasecenter.split() 

1228 base_mref = 'J2000' 

1229 for i in items: 

1230 s = i.strip() 

1231 if re.match(pattern, s) is None: 

1232 base_mref = s 

1233 break 

1234 

1235 mapextent = self.imager.mapextent(ref=base_mref, movingsource=self.ephemsrcname, 

1236 pointingcolumntouse=colname) 

1237 if mapextent['status']: 

1238 qheight = my_qa.quantity(mapextent['extent'][1], 'rad') 

1239 qwidth = my_qa.quantity(mapextent['extent'][0], 'rad') 

1240 qcent0 = my_qa.quantity(mapextent['center'][0], 'rad') 

1241 qcent1 = my_qa.quantity(mapextent['center'][1], 'rad') 

1242 scenter = '%s %s %s' % (base_mref, my_qa.formxxx(qcent0, 'hms'), 

1243 my_qa.formxxx(qcent1, 'dms')) 

1244 

1245 casalog.post("- Pointing center: %s" % scenter) 

1246 casalog.post("- Pointing extent: [%s, %s] (projected)" % (my_qa.tos(qwidth), 

1247 my_qa.tos(qheight))) 

1248 ret_dict['center'] = scenter 

1249 ret_dict['width'] = qwidth 

1250 ret_dict['height'] = qheight 

1251 else: 

1252 casalog.post( 

1253 'Failed to derive map extent from the MSs registered to the imager probably ' 

1254 'due to missing valid data.', 

1255 priority='SEVERE') 

1256 ret_dict['center'] = '' 

1257 ret_dict['width'] = my_qa.quantity(0.0, 'rad') 

1258 ret_dict['height'] = my_qa.quantity(0.0, 'rad') 

1259 return ret_dict 

1260 

1261 def _get_x_minmax(self, x): 

1262 # assumed the x is in unit of rad. 

1263 pi2 = 2. * numpy.pi 

1264 x = (x % pi2) 

1265 npart = 4 

1266 dlon = pi2 / float(npart) 

1267 pos = [int(v / dlon) for v in x] 

1268 voids = [False for dummy in range(npart)] 

1269 for ipos in range(npart): 

1270 try: 

1271 pos.index(ipos) 

1272 except Exception: 

1273 voids[ipos] = True 

1274 if not any(voids): 

1275 raise Exception( 

1276 "Failed to find global pointing gap. " 

1277 f"The algorithm requires at least 2PI/{npart} of pointing gap") 

1278 rot_pos = [] 

1279 if (not voids[0]) and (not voids[npart - 1]): 

1280 gmax = -1 

1281 for idx in range(npart - 2, 0, -1): 

1282 if voids[idx]: 

1283 gmax = idx 

1284 break 

1285 if gmax < 0: 

1286 raise Exception("Failed to detect gap max") 

1287 rot_pos = range(gmax + 1, npart) 

1288 for idx in range(len(x)): 

1289 x[idx] = (x[idx] - pi2) if pos[idx] in rot_pos else x[idx] 

1290 

1291 return (x.min(), x.max()) 

1292 

1293 def __update_subtable_name(self, msname): 

1294 self.open_table(msname) 

1295 keys = self.table.getkeywords() 

1296 self.close_table() 

1297 self.field_table = sdutil.get_subtable_name(keys['FIELD']) 

1298 self.spw_table = sdutil.get_subtable_name(keys['SPECTRAL_WINDOW']) 

1299 self.source_table = sdutil.get_subtable_name(keys['SOURCE']) 

1300 self.antenna_table = sdutil.get_subtable_name(keys['ANTENNA']) 

1301 self.polarization_table = sdutil.get_subtable_name(keys['POLARIZATION']) 

1302 self.observation_table = sdutil.get_subtable_name(keys['OBSERVATION']) 

1303 self.pointing_table = sdutil.get_subtable_name(keys['POINTING']) 

1304 self.data_desc_table = sdutil.get_subtable_name(keys['DATA_DESCRIPTION']) 

1305 

1306 def _get_average_antenna_diameter(self, antenna): 

1307 my_qa = quanta() 

1308 self.open_table(self.antenna_table) 

1309 try: 

1310 antdiam_unit = self.table.getcolkeyword('DISH_DIAMETER', 'QuantumUnits')[0] 

1311 diams = self.table.getcol('DISH_DIAMETER') 

1312 finally: 

1313 self.close_table() 

1314 

1315 if len(antenna) == 0: 

1316 antdiam_ave = my_qa.quantity(diams.mean(), antdiam_unit) 

1317 else: 

1318 d_ave = sum([diams[idx] for idx in antenna]) / float(len(antenna)) 

1319 antdiam_ave = my_qa.quantity(d_ave, antdiam_unit) 

1320 return antdiam_ave