Coverage for /wheeldirectory/casa-CAS-14623-1-py3.12.el8/lib/py/lib/python3.12/site-packages/casatasks/private/imagerhelpers/input_parameters.py: 67%

554 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-09 01:03 +0000

1import os 

2import math 

3import shutil 

4import string 

5import time 

6import re 

7import copy 

8import pprint 

9import functools 

10import inspect 

11from collections import OrderedDict 

12import numpy as np 

13from typing import Tuple 

14import filecmp 

15 

16 

17from casatools import synthesisutils 

18from casatools import table, ms, synthesisutils, quanta 

19from casatools import calibrater 

20from casatasks import casalog 

21from casatasks.private.mslisthelper import check_mslist 

22from casatasks.private.mslisthelper import sort_mslist 

23 

24 

25""" 

26A set of helper functions for the tasks tclean 

27 

28Summary... 

29  

30""" 

31 

32 

33###################################################### 

34###################################################### 

35###################################################### 

36 

37class ImagerParameters(): 

38 def __init__( 

39 self, 

40 # Input Data: what gets in 

41 msname='', 

42 

43 # Output Data: what goes out 

44 imagename='', 

45 

46 # The remaining parameters are Control Parameters: 

47 # they control How what gets in goes out 

48 

49 # Data Selection 

50 field='', 

51 spw='', 

52 timestr='', 

53 uvdist='', 

54 antenna='', 

55 scan='', 

56 obs='', 

57 state='', 

58 datacolumn='corrected', 

59 # Image Definition 

60 imsize=[1, 1], 

61 cell=[10.0, 10.0], 

62 phasecenter='', 

63 stokes='I', 

64 projection='SIN', 

65 startmodel='', 

66 # Spectral Parameters 

67 specmode='mfs', 

68 reffreq='', 

69 nchan=1, 

70 start='', 

71 width='', 

72 outframe='LSRK', 

73 veltype='radio', 

74 restfreq=[''], 

75 sysvel='', 

76 sysvelframe='', 

77 interpolation='nearest', 

78 perchanweightdensity=False, 

79 gridder="standard", 

80 # ftmachine='gridft', 

81 facets=1, 

82 chanchunks=1, 

83 

84 wprojplanes=1, 

85 

86 vptable="", 

87 usepointing=False, 

88 mosweight=False, 

89 aterm=True, 

90 psterm=True, 

91 mterm=True, 

92 wbawp=True, 

93 cfcache="", 

94 dopbcorr=True, 

95 conjbeams=True, 

96 computepastep=360.0, 

97 rotatepastep=360.0, 

98 pointingoffsetsigdev=[30.0, 30.0], 

99 

100 # Normalizer group 

101 pblimit=0.01, 

102 normtype='flatnoise', 

103 psfcutoff=0.35, 

104 makesingledishnormalizer=False, 

105 

106 outlierfile='', 

107 restart=True, 

108 

109 weighting='natural', 

110 robust=0.5, 

111 noise='0.0Jy', 

112 npixels=0, 

113 uvtaper=[], 

114 

115 niter=0, 

116 cycleniter=0, 

117 loopgain=0.1, 

118 threshold='0.0Jy', 

119 nsigma=0.0, 

120 cyclefactor=1.0, 

121 minpsffraction=0.1, 

122 maxpsffraction=0.8, 

123 interactive=False, 

124 fullsummary=False, 

125 nmajor=-1, 

126 

127 deconvolver='hogbom', 

128 scales=[], 

129 nterms=1, 

130 scalebias=0.0, 

131 restoringbeam=[], 

132 # mtype='default', 

133 

134 usemask='user', 

135 mask='', 

136 pbmask=0.0, 

137 maskthreshold='', 

138 maskresolution='', 

139 nmask=0, 

140 # autoadjust=False, 

141 

142 sidelobethreshold=5.0, 

143 noisethreshold=3.0, 

144 lownoisethreshold=3.0, 

145 negativethreshold=0.0, 

146 smoothfactor=1.0, 

147 minbeamfrac=0.3, 

148 cutthreshold=0.01, 

149 growiterations=100, 

150 dogrowprune=True, 

151 minpercentchange=0.0, 

152 verbose=False, 

153 fastnoise=True, 

154 fusedthreshold=0.0, 

155 largestscale=-1, 

156 

157 # usescratch=True, 

158 # readonly=True, 

159 savemodel="none", 

160 parallel=False, 

161 

162 workdir='', 

163 

164 # CFCache params 

165 cflist=[], 

166 

167 # single-dish imaging params 

168 gridfunction='SF', 

169 convsupport=-1, 

170 truncate="-1", 

171 gwidth="-1", 

172 jwidth="-1", 

173 pointingcolumntouse='direction', 

174 convertfirst='never', 

175 minweight=0.0, 

176 clipminmax=False): 

177 

178 self.allparameters = dict(locals()) 

179 del self.allparameters['self'] 

180 

181 self.defaultKey = "0" 

182 # ---- Selection params. For multiple MSs, all are lists. 

183 # For multiple nodes, the selection parameters are modified inside 

184 # PySynthesisImager 

185 self.allselpars = { 

186 'msname': msname, 'field': field, 'spw': spw, 'scan': scan, 

187 'timestr': timestr, 'uvdist': uvdist, 'antenna': antenna, 

188 'obs': obs, 'state': state, 

189 'datacolumn': datacolumn, 

190 'savemodel': savemodel 

191 } 

192 # ---- Imaging/deconvolution parameters 

193 # The outermost dictionary index is image field. 

194 # The '0' or main field's parameters come from the task parameters 

195 # The outlier '1', '2', .... parameters come from the outlier file 

196 self.outlierfile = outlierfile 

197 # Initialize the parameter lists with the 'main' or '0' field's 

198 # parameters 

199 # ---- Image definition 

200 self.allimpars = { 

201 self.defaultKey: { 

202 # Image 

203 'imagename': imagename, 

204 'nchan': nchan, 

205 'imsize': imsize, 

206 'cell': cell, 

207 'phasecenter': phasecenter, 

208 'stokes': stokes, 

209 # Frequency axis 

210 'specmode': specmode, 

211 'start': start, 

212 'width': width, 

213 'veltype': veltype, 

214 'nterms': nterms, 

215 'restfreq': restfreq, 

216 # Output frame 

217 'outframe': outframe, 

218 'reffreq': reffreq, 

219 'sysvel': sysvel, 

220 'sysvelframe': sysvelframe, 

221 # Projection 

222 'projection': projection, 

223 # Deconvolution 

224 'restart': restart, 

225 'startmodel': startmodel, 

226 'deconvolver': deconvolver 

227 } 

228 } 

229 # ---- Gridding 

230 self.allgridpars = { 

231 self.defaultKey: { 

232 'gridder': gridder, 

233 # aterm group 

234 'aterm': aterm, 

235 'psterm': psterm, 

236 'mterm': mterm, 

237 'wbawp': wbawp, 

238 # cfcache group 

239 'cfcache': cfcache, 

240 'usepointing': usepointing, 

241 'dopbcorr': dopbcorr, 

242 # conjbeams group 

243 'conjbeams': conjbeams, 

244 'computepastep': computepastep, 

245 # 

246 'rotatepastep': rotatepastep, #'mtype':mtype, # 'weightlimit':weightlimit, 

247 'pointingoffsetsigdev': pointingoffsetsigdev, 

248 # facets group 

249 'facets': facets, 

250 'chanchunks': chanchunks, 

251 # interpolation group 

252 'interpolation': interpolation, 

253 'wprojplanes': wprojplanes, 

254 # deconvolver group 

255 'deconvolver': deconvolver, 

256 'vptable': vptable, 

257 'imagename': imagename, 

258 # single-dish specific parameters 

259 # ---- spatial coordinates 

260 'pointingcolumntouse': pointingcolumntouse, 

261 'convertfirst': convertfirst, 

262 # ---- convolution function 

263 'convfunc': gridfunction, 

264 'convsupport': convsupport, 

265 # ---- truncate group 

266 'truncate': truncate, 

267 'gwidth': gwidth, 

268 'jwidth': jwidth, 

269 # ---- minweight group 

270 'minweight': minweight, 

271 'clipminmax': clipminmax 

272 } 

273 } 

274 # ---- Weighting 

275 if True: # Compute rmode and self.weightpars 

276 rmode = 'none' 

277 if (weighting == 'briggsabs'): 

278 rmode = 'abs' 

279 weighting = 'briggs' 

280 elif (weighting == 'briggs'): 

281 rmode = 'norm' 

282 elif (weighting == 'briggsbwtaper'): 

283 rmode = 'bwtaper' 

284 weighting = 'briggs' 

285 

286 self.weightpars = { 

287 'type': weighting, 

288 'rmode': rmode, 

289 'robust': robust, 

290 'noise': noise, 

291 'npixels': npixels, 

292 'uvtaper': uvtaper, 

293 'multifield': mosweight, 

294 'usecubebriggs': perchanweightdensity 

295 } 

296 # ---- Normalizers ( this is where flat noise, flat sky rules will go... ) 

297 self.allnormpars = { 

298 self.defaultKey: { 

299 # pblimit group 

300 'pblimit': pblimit, 

301 'nterms': nterms, 

302 'facets': facets, 

303 # normtype group 

304 'normtype': normtype, 

305 'workdir': workdir, 

306 # deconvolver group 

307 'deconvolver': deconvolver, 

308 'imagename': imagename, 

309 'restoringbeam': restoringbeam, 

310 'psfcutoff': psfcutoff, 

311 'makesingledishnormalizer': makesingledishnormalizer 

312 } 

313 } 

314 # ---- Deconvolution 

315 self.alldecpars = { 

316 self.defaultKey: { 

317 'id':0, 'deconvolver':deconvolver, 'nterms':nterms, 

318 'scales':scales, 'scalebias':scalebias, 'restoringbeam':restoringbeam, 'usemask':usemask, 

319 'mask':mask, 'pbmask':pbmask, 'maskthreshold':maskthreshold, 

320 'maskresolution':maskresolution, 'nmask':nmask, 

321 #'maskresolution':maskresolution, 'nmask':nmask,'autoadjust':autoadjust, 

322 'sidelobethreshold':sidelobethreshold, 'noisethreshold':noisethreshold, 

323 'lownoisethreshold':lownoisethreshold, 'negativethreshold':negativethreshold,'smoothfactor':smoothfactor, 

324 'fusedthreshold':fusedthreshold, 'specmode':specmode,'largestscale':largestscale, 

325 

326 'minbeamfrac':minbeamfrac, 'cutthreshold':cutthreshold, 'growiterations':growiterations, 

327 'dogrowprune':dogrowprune, 'minpercentchange':minpercentchange, 'verbose':verbose, 'fastnoise':fastnoise, 

328 'interactive':interactive, 'startmodel':startmodel, 'nsigma':nsigma, 'imagename':imagename, 'fullsummary':fullsummary 

329 } 

330 } 

331 # ---- Iteration control 

332 self.iterpars = { 

333 'niter':niter, 'cycleniter':cycleniter, 'threshold':threshold, 

334 'loopgain':loopgain, 'interactive':interactive, 

335 'cyclefactor':cyclefactor, 'minpsffraction':minpsffraction, 

336 'maxpsffraction':maxpsffraction, 

337 'savemodel':savemodel,'nsigma':nsigma, 'nmajor':nmajor, 'fullsummary':fullsummary 

338 } 

339 # ---- CFCache params 

340 self.cfcachepars = { 

341 'cflist': cflist 

342 } 

343 # ---- Parameters that may be internally modified for savemodel behavior 

344 self.inpars = { 

345 'savemodel': savemodel, 

346 'interactive': interactive, 

347 'nsigma': nsigma, 

348 'usemask': usemask 

349 } 

350 # ---- List of supported parameters in outlier files. 

351 # All other parameters will default to the global values. 

352 self.outimparlist = [ 

353 'imagename','nchan','imsize','cell','phasecenter','startmodel', 

354 'start','width', 

355 'nterms','reffreq','specmode' 

356 ] 

357 self.outgridparlist = ['gridder','deconvolver','wprojplanes'] 

358 self.outweightparlist = [] 

359 self.outdecparlist = [ 

360 'deconvolver','startmodel','nterms','usemask','mask' 

361 ] 

362 self.outnormparlist = ['deconvolver','weightlimit','nterms'] 

363 

364 ret = self.checkParameters(parallel) 

365 if ret == False: 

366 casalog.post( 

367 'Found errors in input parameters. Please check.', 'WARN' 

368 ) 

369 

370 self.printParameters() 

371 

372 def resetParameters(self): 

373 """reset parameters to the original settting for interactive, nsigma, auto-multithresh when savemodel!='none'""" 

374 if self.inpars["savemodel"] != "none" and ( 

375 self.inpars["interactive"] == True 

376 or self.inpars["usemask"] == "auto-multithresh" 

377 or self.inpars["nsigma"] > 0.0 

378 ): 

379 # in checkAndFixIterationPars(), when saving model is on, the internal params, readonly and usescrath are set to True and False, 

380 # respectively. So this needs to be undone before calling predictModel. 

381 self.iterpars["savemodel"] = self.inpars["savemodel"] 

382 if self.inpars["savemodel"] == "modelcolumn": 

383 for key in self.allselpars: # for all MSes 

384 self.allselpars[key]["readonly"] = False 

385 self.allselpars[key]["usescratch"] = True 

386 

387 elif self.inpars["savemodel"] == "virtual": 

388 for key in self.allselpars: # for all MSes 

389 self.allselpars[key]["readonly"] = False 

390 self.allselpars[key]["usescratch"] = False 

391 

392 def getAllPars(self): 

393 """Return the state of all parameters""" 

394 return self.allparameters 

395 

396 def getSelPars(self): 

397 return self.allselpars 

398 

399 def getImagePars(self): 

400 return self.allimpars 

401 

402 def getGridPars(self): 

403 return self.allgridpars 

404 

405 def getWeightPars(self): 

406 return self.weightpars 

407 

408 def getDecPars(self): 

409 return self.alldecpars 

410 

411 def getIterPars(self): 

412 return self.iterpars 

413 

414 def getNormPars(self): 

415 return self.allnormpars 

416 

417 def getCFCachePars(self): 

418 return self.cfcachepars 

419 

420 def setSelPars(self, selpars): 

421 for key in selpars.keys(): 

422 self.allselpars[key] = selpars[key] 

423 

424 def setImagePars(self, impars): 

425 for key in impars.keys(): 

426 self.allimpars[key] = impars[key] 

427 

428 def setGridPars(self, gridpars): 

429 for key in gridpars.keys(): 

430 self.allgridpars[key] = gridpars[key] 

431 

432 def setWeightPars(self, weightpars): 

433 for key in weightpars.keys(): 

434 self.weightpars[key] = weightpars[key] 

435 

436 def setDecPars(self, decpars): 

437 for key in decpars.keys(): 

438 self.alldecpars[key] = decpars[key] 

439 

440 def setIterPars(self, iterpars): 

441 for key in iterpars.keys(): 

442 self.iterpars[key] = iterpars[key] 

443 

444 def setNormPars(self, normpars): 

445 for key in normpars.keys(): 

446 self.allnormpars[key] = normpars[key] 

447 

448 def checkParameters(self, parallel=False): 

449 # casalog.origin('refimagerhelper.checkParameters') 

450 casalog.post("Verifying Input Parameters") 

451 # Init the error-string 

452 errs = "" 

453 try: 

454 errs += self.checkAndFixSelectionPars() 

455 errs += self.makeImagingParamLists(parallel) 

456 errs += self.checkAndFixIterationPars() 

457 errs += self.checkAndFixNormPars() 

458 

459 for mss in sorted(self.allselpars.keys()): 

460 if self.allimpars["0"]["specmode"] == "cubedata": 

461 self.allselpars[mss]["outframe"] = "Undefined" 

462 if self.allimpars["0"]["specmode"] == "cubesource": 

463 self.allselpars[mss]["outframe"] = "REST" 

464 ### MOVE this segment of code to the constructor so that it's clear which parameters go where ! 

465 ### Copy them from 'impars' to 'normpars' and 'decpars' 

466 self.iterpars["allimages"] = {} 

467 for immod in self.allimpars.keys(): 

468 self.allnormpars[immod]["imagename"] = self.allimpars[immod][ 

469 "imagename" 

470 ] 

471 self.alldecpars[immod]["imagename"] = self.allimpars[immod]["imagename"] 

472 self.allgridpars[immod]["imagename"] = self.allimpars[immod][ 

473 "imagename" 

474 ] 

475 self.iterpars["allimages"][immod] = { 

476 "imagename": self.allimpars[immod]["imagename"], 

477 "multiterm": (self.alldecpars[immod]["deconvolver"] == "mtmfs"), 

478 } 

479 

480 ## Integers need to be NOT numpy versions. 

481 self.fixIntParam(self.allimpars, "imsize") 

482 self.fixIntParam(self.allimpars, "nchan") 

483 self.fixIntParam(self.allimpars, "nterms") 

484 self.fixIntParam(self.allnormpars, "nterms") 

485 self.fixIntParam(self.alldecpars, "nterms") 

486 self.fixIntParam(self.allgridpars, "facets") 

487 self.fixIntParam(self.allgridpars, "chanchunks") 

488 except Exception as exc: 

489 if len(errs) > 0: 

490 # errs string indicates that maybe this exception was our fault, indicate as such and provide the errs string to the user 

491 raise Exception( 

492 "Parameter Errors : \n{}\nThese errors may have caused the '{}'".format( 

493 errs, type(exc) 

494 ) 

495 ) 

496 else: 

497 # something unforseen happened, just re-throw the exception 

498 raise 

499 

500 ## If there are errors, print a message and exit. 

501 if len(errs) > 0: 

502 # casalog.post('Parameter Errors : \n' + errs,'WARN') 

503 raise Exception("Parameter Errors : \n" + errs) 

504 return True 

505 

506 ###### Start : Parameter-checking functions ################## 

507 

508 def checkAndFixSelectionPars(self): 

509 errs = "" 

510 

511 # If it's already a dict with ms0,ms1,etc...leave it be. 

512 ok = True 

513 for kk in self.allselpars.keys(): 

514 if kk.find("ms") != 0: 

515 ok = False 

516 

517 if ok == True: 

518 # casalog.post("Already in correct format") 

519 return errs 

520 

521 #print("allselpars=",self.allselpars) 

522 # msname, field, spw, etc must all be equal-length lists of strings, or all except msname must be of length 1. 

523 if not "msname" in self.allselpars: 

524 errs = errs + "MS name(s) not specified" 

525 else: 

526 if type(self.allselpars['msname']) == list: 

527 #(timesortedvislist, times) = sort_mslist(self.allselpars['msname']) 

528 (timesortedvislist, times, newindex) = self.mslist_timesorting(self.allselpars['msname']) 

529 if timesortedvislist != self.allselpars['msname']: 

530 self.allselpars['msname'] = timesortedvislist 

531 casalog.post("Sorting the vis list by time. The new vis list:"+ str(self.allselpars['msname'])) 

532 for selp in ['spw','field','timestr','uvdist','antenna','scan','obs','state']: 

533 if type(self.allselpars[selp]) == list and len(self.allselpars[selp]) == len(newindex): 

534 self.allselpars[selp] = [self.allselpars[selp][i] for i in newindex] 

535 

536 #msdiff = check_mslist(self.allselpars['msname'], ignore_tables=['SORTED_TABLE', 'ASDM*']) 

537 msdiff = check_mslist(self.allselpars['msname'], ignore_tables=['SORTED_TABLE', 'ASDM*'], testcontent=False) 

538 

539 # Only call this if vis == list and there is mismatch in wtspec columns 

540 # Maybe expanded for other checks later... 

541 if msdiff != {}: 

542 #print("MS diff===",msdiff) 

543 noWtspecmslist=[] 

544 for msfile, diff_info in msdiff.items(): 

545 # check Main  

546 if 'Main' in diff_info: 

547 for diffkey in diff_info['Main']: 

548 if diffkey == "missingcol_a" or diffkey == "missingcol_b": 

549 if ('WEIGHT_SPECTRUM' in diff_info['Main']['missingcol_a'] and 

550 self.allselpars['msname'][0] not in noWtspecmslist): 

551 noWtspecmslist.append(self.allselpars['msname'][0]) 

552 if ('WEIGHT_SPECTRUM' in diff_info['Main']['missingcol_b'] and 

553 msfile not in noWtspecmslist): 

554 noWtspecmslist.append(msfile) 

555 # repalce this by addwtspec(list_of_ms_withoutWtSpec) 

556 #self.checkmsforwtspec` 

557 if noWtspecmslist!=[]: 

558 #print ("OK addwtspec to "+str(noWtspecmslist)) 

559 self.addwtspec(noWtspecmslist) 

560 

561 selkeys = self.allselpars.keys() 

562 

563 # Convert all non-list parameters into lists. 

564 for par in selkeys: 

565 if type(self.allselpars[par]) != list: 

566 self.allselpars[par] = [self.allselpars[par]] 

567 

568 # Check that all are the same length as nvis 

569 # If not, and if they're single, replicate them nvis times 

570 nvis = len(self.allselpars["msname"]) 

571 

572 if nvis==0: 

573 errs = errs + "Input MS list is empty" 

574 return errs 

575 

576 for par in selkeys: 

577 if len(self.allselpars[par]) > 1 and len(self.allselpars[par]) != nvis: 

578 errs = ( 

579 errs 

580 + str(par) 

581 + " must have a single entry, or " 

582 + str(nvis) 

583 + " entries to match vis list \n" 

584 ) 

585 return errs 

586 else: # Replicate them nvis times if needed. 

587 if len(self.allselpars[par]) == 1: 

588 for ms in range(1, nvis): 

589 self.allselpars[par].append(self.allselpars[par][0]) 

590 

591 # Now, all parameters are lists of strings each of length 'nvis'. 

592 # Put them into separate dicts per MS. 

593 selparlist = {} 

594 for ms in range(0, nvis): 

595 selparlist["ms" + str(ms)] = {} 

596 for par in selkeys: 

597 selparlist["ms" + str(ms)][par] = self.allselpars[par][ms] 

598 

599 synu = synthesisutils() 

600 selparlist["ms" + str(ms)] = synu.checkselectionparams( 

601 selparlist["ms" + str(ms)] 

602 ) 

603 synu.done() 

604 

605 # casalog.post(selparlist) 

606 self.allselpars = selparlist 

607 

608 return errs 

609 

610 def makeImagingParamLists(self, parallel): 

611 errs = "" 

612 # casalog.post("specmode=",self.allimpars['0']['specmode'], " parallel=",parallel) 

613 ## Multiple images have been specified. 

614 ## (1) Parse the outlier file and fill a list of imagedefinitions 

615 ## OR (2) Parse lists per input parameter into a list of parameter-sets (imagedefinitions) 

616 ### The code below implements (1) 

617 outlierpars = [] 

618 parseerrors = "" 

619 if len(self.outlierfile) > 0: 

620 outlierpars, parseerrors = self.parseOutlierFile(self.outlierfile) 

621 if parallel: 

622 casalog.post("CALLING checkParallelMFMixModes...") 

623 errs = self.checkParallelMFMixedModes(self.allimpars, outlierpars) 

624 if len(errs): 

625 return errs 

626 

627 if len(parseerrors) > 0: 

628 errs = errs + "Errors in parsing outlier file : " + parseerrors 

629 return errs 

630 

631 # Initialize outlier parameters with defaults 

632 # Update outlier parameters with modifications from outlier files 

633 for immod in range(0, len(outlierpars)): 

634 modelid = str(immod + 1) 

635 self.allimpars[modelid] = copy.deepcopy(self.allimpars["0"]) 

636 self.allimpars[modelid].update(outlierpars[immod]["impars"]) 

637 self.allgridpars[modelid] = copy.deepcopy(self.allgridpars["0"]) 

638 self.allgridpars[modelid].update(outlierpars[immod]["gridpars"]) 

639 self.alldecpars[modelid] = copy.deepcopy(self.alldecpars["0"]) 

640 self.alldecpars[modelid].update(outlierpars[immod]["decpars"]) 

641 self.allnormpars[modelid] = copy.deepcopy(self.allnormpars["0"]) 

642 self.allnormpars[modelid].update(outlierpars[immod]["normpars"]) 

643 self.alldecpars[modelid]["id"] = immod + 1 ## Try to eliminate. 

644 

645 # casalog.post(self.allimpars) 

646 

647 # 

648 # casalog.post("REMOVING CHECKS to check...") 

649 #### This does not handle the conversions of the csys correctly..... 

650 #### 

651 # for immod in self.allimpars.keys() : 

652 # tempcsys = {} 

653 # if 'csys' in self.allimpars[immod]: 

654 # tempcsys = self.allimpars[immod]['csys'] 

655 # 

656 # synu = synthesisutils() 

657 # self.allimpars[immod] = synu.checkimageparams( self.allimpars[immod] ) 

658 # synu.done() 

659 # 

660 # if len(tempcsys.keys())==0: 

661 # self.allimpars[immod]['csys'] = tempcsys 

662 

663 ## Check for name increments, and copy from impars to decpars and normpars. 

664 self.handleImageNames() 

665 

666 return errs 

667 

668 def handleImageNames(self): 

669 

670 for immod in self.allimpars.keys(): 

671 inpname = self.allimpars[immod]["imagename"] 

672 

673 ### If a directory name is embedded in the image name, check that the dir exists. 

674 if inpname.count("/"): 

675 splitname = inpname.split("/") 

676 prefix = splitname[len(splitname) - 1] 

677 dirname = inpname[0 : len(inpname) - len(prefix)] # has '/' at end 

678 if not os.path.exists(dirname): 

679 casalog.post("Making directory : " + dirname, "INFO") 

680 os.mkdir(dirname) 

681 

682 ### Check for name increments 

683 # if self.reusename == False: 

684 

685 if ( 

686 self.allimpars["0"]["restart"] == False 

687 ): # Later, can change this to be field dependent too. 

688 ## Get a list of image names for all fields (to sync name increment ids across fields) 

689 inpnamelist = {} 

690 for immod in self.allimpars.keys(): 

691 inpnamelist[immod] = self.allimpars[immod]["imagename"] 

692 

693 newnamelist = self.incrementImageNameList(inpnamelist) 

694 

695 if len(newnamelist) != len(self.allimpars.keys()): 

696 casalog.post( 

697 "Internal Error : Non matching list lengths in refimagerhelper::handleImageNames. Not updating image names", 

698 "WARN", 

699 ) 

700 else: 

701 for immod in self.allimpars.keys(): 

702 self.allimpars[immod]["imagename"] = newnamelist[immod] 

703 

704 def checkAndFixIterationPars(self): 

705 errs = "" 

706 

707 # Bother checking only if deconvolution iterations are requested 

708 if self.iterpars["niter"] > 0: 

709 # Make sure cycleniter is less than or equal to niter. 

710 if ( 

711 self.iterpars["cycleniter"] <= 0 

712 or self.iterpars["cycleniter"] > self.iterpars["niter"] 

713 ): 

714 if self.iterpars["interactive"] == False: 

715 self.iterpars["cycleniter"] = self.iterpars["niter"] 

716 else: 

717 self.iterpars["cycleniter"] = min(self.iterpars["niter"], 100) 

718 

719 # saving model is done separately outside of iter. control for interactive clean and or automasking cases 

720 

721 if self.iterpars['savemodel']!='none': 

722 if self.iterpars['interactive']==True or self.alldecpars['0']['usemask']=='auto-multithresh' or \ 

723 self.alldecpars['0']['nsigma']>0.0: 

724 self.iterpars['savemodel']='none' 

725 for visid in self.allselpars: 

726 self.allselpars[visid]['readonly']=True 

727 self.allselpars[visid]['usescratch']=False 

728 

729 

730 return errs 

731 

732 def checkAndFixNormPars(self): 

733 errs = "" 

734 

735 # for modelid in self.allnormpars.keys(): 

736 # if len(self.allnormpars[modelid]['workdir'])==0: 

737 # self.allnormpars[modelid]['workdir'] = self.allnormpars['0']['imagename'] + '.workdir' 

738 

739 return errs 

740 

741 ###### End : Parameter-checking functions ################## 

742 

743 ## Parse outlier file and construct a list of imagedefinitions (dictionaries). 

744 def parseOutlierFile(self, outlierfilename=""): 

745 returnlist = [] 

746 errs = "" # must be empty for no error 

747 

748 if len(outlierfilename) > 0 and not os.path.exists(outlierfilename): 

749 errs += "Cannot find outlier file : " + outlierfilename + "\n" 

750 return returnlist, errs 

751 

752 fp = open(outlierfilename, "r") 

753 thelines = fp.readlines() 

754 tempimpar = {} 

755 tempgridpar = {} 

756 tempweightpar = {} 

757 tempdecpar = {} 

758 tempnormpar = {} 

759 for oneline in thelines: 

760 aline = oneline.replace("\n", "") 

761 # aline = oneline.replace(' ','').replace('\n','') 

762 if len(aline) > 0 and aline.find("#") != 0: 

763 parpair = aline.split("=") 

764 parpair[0] = parpair[0].replace(" ", "") 

765 # casalog.post(parpair) 

766 if len(parpair) != 2: 

767 errs += "Error in line containing : " + oneline + "\n" 

768 if parpair[0] == "imagename" and tempimpar != {}: 

769 # returnlist.append({'impars':tempimpar, 'gridpars':tempgridpar, 'weightpars':tempweightpar, 'decpars':tempdecpar} ) 

770 returnlist.append( 

771 { 

772 "impars": tempimpar, 

773 "gridpars": tempgridpar, 

774 "weightpars": tempweightpar, 

775 "decpars": tempdecpar, 

776 "normpars": tempnormpar, 

777 } 

778 ) 

779 tempimpar = {} 

780 tempgridpar = {} 

781 tempweightpar = {} 

782 tempdecpar = {} 

783 tempnormpar = {} 

784 usepar = False 

785 if parpair[0] in self.outimparlist: 

786 tempimpar[parpair[0]] = parpair[1] 

787 usepar = True 

788 if parpair[0] in self.outgridparlist: 

789 tempgridpar[parpair[0]] = parpair[1] 

790 usepar = True 

791 if parpair[0] in self.outweightparlist: 

792 tempweightpar[parpair[0]] = parpair[1] 

793 usepar = True 

794 if parpair[0] in self.outdecparlist: 

795 tempdecpar[parpair[0]] = parpair[1] 

796 usepar = True 

797 if parpair[0] in self.outnormparlist: 

798 tempnormpar[parpair[0]] = parpair[1] 

799 usepar = True 

800 if usepar == False: 

801 casalog.post("Ignoring unknown parameter pair : " + oneline) 

802 

803 if len(errs) == 0: 

804 returnlist.append( 

805 { 

806 "impars": tempimpar, 

807 "gridpars": tempgridpar, 

808 "weightpars": tempweightpar, 

809 "decpars": tempdecpar, 

810 "normpars": tempnormpar, 

811 } 

812 ) 

813 

814 ## Extra parsing for a few parameters. 

815 returnlist = self.evalToTarget(returnlist, "impars", "imsize", "intvec") 

816 returnlist = self.evalToTarget(returnlist, "impars", "nchan", "int") 

817 returnlist = self.evalToTarget(returnlist, "impars", "cell", "strvec") 

818 returnlist = self.evalToTarget(returnlist, "impars", "nterms", "int") 

819 returnlist = self.evalToTarget(returnlist, "decpars", "nterms", "int") 

820 returnlist = self.evalToTarget(returnlist, "normpars", "nterms", "int") 

821 returnlist = self.evalToTarget(returnlist, "gridpars", "wprojplanes", "int") 

822 # returnlist = self.evalToTarget( returnlist, 'impars', 'reffreq', 'strvec' ) 

823 

824 # casalog.post(returnlist) 

825 return returnlist, errs 

826 

827 def evalToTarget(self, globalpars, subparkey, parname, dtype="int"): 

828 try: 

829 for fld in range(0, len(globalpars)): 

830 if parname in globalpars[fld][subparkey]: 

831 if dtype == "int" or dtype == "intvec": 

832 val_e = eval(globalpars[fld][subparkey][parname]) 

833 if dtype == "strvec": 

834 tcell = globalpars[fld][subparkey][parname] 

835 tcell = ( 

836 tcell.replace(" ", "") 

837 .replace("[", "") 

838 .replace("]", "") 

839 .replace("'", "") 

840 ) 

841 tcells = tcell.split(",") 

842 val_e = [] 

843 for cell in tcells: 

844 val_e.append(cell) 

845 

846 globalpars[fld][subparkey][parname] = val_e 

847 except: 

848 casalog.post( 

849 'Cannot evaluate outlier field parameter "' + parname + '"', "ERROR" 

850 ) 

851 

852 return globalpars 

853 

854 def printParameters(self): 

855 casalog.post("SelPars : " + str(self.allselpars), "INFO2") 

856 casalog.post("ImagePars : " + str(self.allimpars), "INFO2") 

857 casalog.post("GridPars : " + str(self.allgridpars), "INFO2") 

858 casalog.post("NormPars : " + str(self.allnormpars), "INFO2") 

859 casalog.post("Weightpars : " + str(self.weightpars), "INFO2") 

860 casalog.post("DecPars : " + str(self.alldecpars), "INFO2") 

861 casalog.post("IterPars : " + str(self.iterpars), "INFO2") 

862 

863 def incrementImageName(self, imagename): 

864 dirname = "." 

865 prefix = imagename 

866 

867 if imagename.count("/"): 

868 splitname = imagename.split("/") 

869 prefix = splitname[len(splitname) - 1] 

870 ### if it has a leading / then absolute path is assumed 

871 dirname = ( 

872 (imagename[0 : len(imagename) - len(prefix)]) 

873 if (imagename[0] == "/") 

874 else ("./" + imagename[0 : len(imagename) - len(prefix)]) 

875 ) # has '/' at end 

876 

877 inamelist = [fn for fn in os.listdir(dirname) if any([fn.startswith(prefix)])] 

878 

879 if len(inamelist) == 0: 

880 newimagename = dirname[2:] + prefix 

881 else: 

882 nlen = len(prefix) 

883 maxid = 1 

884 for iname in inamelist: 

885 startind = iname.find(prefix + "_") 

886 if startind == 0: 

887 idstr = (iname[nlen + 1 : len(iname)]).split(".")[0] 

888 if idstr.isdigit(): 

889 val = eval(idstr) 

890 if val > maxid: 

891 maxid = val 

892 newimagename = dirname[2:] + prefix + "_" + str(maxid + 1) 

893 

894 casalog.post("Using : {}".format(newimagename)) 

895 return newimagename 

896 

897 def incrementImageNameList(self, inpnamelist): 

898 

899 dirnames = {} 

900 prefixes = {} 

901 

902 for immod in inpnamelist.keys(): 

903 imagename = inpnamelist[immod] 

904 dirname = "." 

905 prefix = imagename 

906 

907 if imagename.count("/"): 

908 splitname = imagename.split("/") 

909 prefix = splitname[len(splitname) - 1] 

910 dirname = ( 

911 (imagename[0 : len(imagename) - len(prefix)]) 

912 if (imagename[0] == "/") 

913 else ("./" + imagename[0 : len(imagename) - len(prefix)]) 

914 ) # has '/' at end 

915 

916 dirnames[immod] = dirname 

917 prefixes[immod] = prefix 

918 

919 maxid = 0 

920 for immod in inpnamelist.keys(): 

921 prefix = prefixes[immod] 

922 inamelist = [ 

923 fn for fn in os.listdir(dirnames[immod]) if any([fn.startswith(prefix)]) 

924 ] 

925 nlen = len(prefix) 

926 

927 if len(inamelist) == 0: 

928 locmax = 0 

929 else: 

930 locmax = 1 

931 

932 cleanext = [".image", ".residual", ".model", ".psf", ".sumwt", ".tt0"] 

933 incremented = False 

934 for iname in inamelist: 

935 rootname, ext = os.path.splitext(iname) 

936 if ext in cleanext: 

937 startind = iname.find(prefix + "_") 

938 if startind == 0: 

939 idstr = (iname[nlen + 1 : len(iname)]).split(".")[0] 

940 if idstr.isdigit(): 

941 val = eval(idstr) 

942 incremented = True 

943 if val > locmax: 

944 locmax = val 

945 elif startind == -1: 

946 if ext == ".tt0": 

947 # need one more pass to extract rootname 

948 rootname, ext = os.path.splitext(rootname) 

949 if rootname == prefix: 

950 # the file name with root file name only 

951 incremented = True 

952 

953 if not incremented: 

954 locmax = 0 

955 if locmax > maxid: 

956 maxid = locmax 

957 

958 newimagenamelist = {} 

959 for immod in inpnamelist.keys(): 

960 if maxid == 0: 

961 newimagenamelist[immod] = inpnamelist[immod] 

962 else: 

963 newimagenamelist[immod] = ( 

964 dirnames[immod][2:] + prefixes[immod] + "_" + str(maxid + 1) 

965 ) 

966 

967 # casalog.post('Input : ', inpnamelist) 

968 # casalog.post('Dirs : ', dirnames) 

969 # casalog.post('Pre : ', prefixes) 

970 # casalog.post('Max id : ', maxid) 

971 # casalog.post('Using : ', newimagenamelist) 

972 return newimagenamelist 

973 

974 ## Guard against numpy int32,int64 types which don't convert well across tool boundary. 

975 ## For CAS-8250. Remove when CAS-6682 is done. 

976 def fixIntParam(self, allpars, parname): 

977 for immod in allpars.keys(): 

978 if parname in allpars[immod]: 

979 ims = allpars[immod][parname] 

980 if type(ims) != list: 

981 ims = int(ims) 

982 else: 

983 for el in range(0, len(ims)): 

984 ims[el] = int(ims[el]) 

985 allpars[immod][parname] = ims 

986 

987 # check for non-supported multifield in mixed modes in parallel 

988 # (e.g. combination cube and continuum for main and outlier fields) 

989 def checkParallelMFMixedModes(self, allimpars, outlierpars): 

990 errmsg = "" 

991 casalog.post("outlierpars=={}".format(outlierpars)) 

992 mainspecmode = allimpars["0"]["specmode"] 

993 mainnchan = allimpars["0"]["nchan"] 

994 casalog.post("mainspecmode={} mainnchan={}".format(mainspecmode, mainnchan)) 

995 cubeoutlier = False 

996 contoutlier = False 

997 isnchanmatch = True 

998 for immod in range(0, len(outlierpars)): 

999 if "impars" in outlierpars[immod]: 

1000 if "nchan" in outlierpars[immod]["impars"]: 

1001 if outlierpars[immod]["impars"]["nchan"] > 1: 

1002 cubeoutlier = True 

1003 if outlierpars[immod]["impars"]["nchan"] != mainnchan: 

1004 isnchanmatch = False 

1005 else: 

1006 contoutlier = True 

1007 else: 

1008 if "specmode" in outlierpars[immod]["impars"]: 

1009 if outlierpars[immod]["impars"]["specmode"] == "mfs": 

1010 contoutlier = True 

1011 if mainspecmode.find("cube") == 0: 

1012 if contoutlier: 

1013 errmsg = "Mixed cube and continuum mode for multifields is currently not supported for parallel mode" 

1014 else: # all cube modes, but need to check if the nchans are the same 

1015 if not isnchanmatch: 

1016 errmsg = "Cubes for multifields with different nchans are currently not supported for parallel mode " 

1017 else: # mfs 

1018 if cubeoutlier: 

1019 errmsg = "Mixed continuum and cube mode for multifields is currently not supported for parallel mode" 

1020 errs = errmsg 

1021 return errs 

1022 

1023 def checkmsforwtspec(self): 

1024 ''' check if WEIGHT_SPECTRUM column exist when  

1025 a list of vis is given. Add the column for an MS 

1026 which does not have one if other MSs have the column. 

1027 This is a workaround for the issue probably in Vi/VB2 

1028 not handling the state change for the optional column 

1029 when dealing with multiples MSs 

1030 ''' 

1031 mycb = calibrater() 

1032 mytb = table() 

1033 haswtspec=False 

1034 mswithnowtspec=[] 

1035 nms = 1 

1036 if type(self.allselpars['msname'])==list: 

1037 nms = len(self.allselpars['msname']) 

1038 

1039 if nms > 1: 

1040 for inms in self.allselpars['msname']: 

1041 mytb.open(inms) 

1042 cols = mytb.colnames() 

1043 mytb.close() 

1044 if 'WEIGHT_SPECTRUM' in cols: 

1045 haswtspec=True 

1046 else: 

1047 mswithnowtspec.append(inms) 

1048 if haswtspec and len(mswithnowtspec) > 0: 

1049 casalog.post("Some of the MSes donot have WEIGHT_SPECTRUM while some other do."+ 

1050 " Automatically adding the column and initialize for those don't to avoid a process failure.","WARN") 

1051 for inms in mswithnowtspec: 

1052 mycb.open(inms, addcorr=False, addmodel=False) 

1053 mycb.initweights(wtmode='weight', dowtsp=True) 

1054 mycb.close() 

1055 # noOp for nms==1  

1056 

1057 def mslist_timesorting(self, mslist): 

1058 '''  

1059 wrapper for mslisthelper.sort_mslist to get a sorting order w.r.t the original 

1060 ''' 

1061 (thenewmslist, times) = sort_mslist(mslist) 

1062 theindex = [] 

1063 for vnew in thenewmslist: 

1064 for vold in mslist: 

1065 if vnew == vold: 

1066 theindex.append(mslist.index(vnew)) 

1067 return (thenewmslist, times, theindex) 

1068 

1069 def addwtspec(self, mslist): 

1070 '''  

1071 Add the column for an MS which does not have one if other MSs have the column. 

1072 This is a workaround for the issue probably in Vi/VB2 

1073 not handling the state change for the optional column 

1074 when dealing with multiples MSs 

1075 ''' 

1076 mycb = calibrater() 

1077 

1078 if len(mslist) > 0: 

1079 casalog.post("Some of the MSes donot have WEIGHT_SPECTRUM while some other do."+ 

1080 " Automatically adding the column and initialize using the existing WEIGHT column for those don't to avoid a process failure.","WARN") 

1081 casalog.post("Adding WEIGHT_SPECTRUM in the following MS(s): "+str(mslist),"WARN") 

1082 for inms in mslist: 

1083 mycb.open(inms, addcorr=False, addmodel=False) 

1084 mycb.initweights(wtmode='weight', dowtsp=True) 

1085 mycb.close() 

1086 mycb.done() 

1087 # noOp for len(mlist) ==0 

1088 

1089 ############################ 

1090 

1091 

1092################################################################################################# 

1093def backupoldfile(thefile=""): 

1094 import copy 

1095 import shutil 

1096 

1097 if thefile == "" or (not os.path.exists(thefile)): 

1098 return 

1099 outpathdir = os.path.realpath(os.path.dirname(thefile)) 

1100 outpathfile = outpathdir + os.path.sep + os.path.basename(thefile) 

1101 k = 0 

1102 backupfile = outpathfile + "." + str(k) 

1103 prevfile = "--------" 

1104 while os.path.exists(backupfile): 

1105 k = k + 1 

1106 prevfile = copy.copy(backupfile) 

1107 if os.path.exists(prevfile) and filecmp.cmp(outpathfile, prevfile): 

1108 ##avoid making multiple copies of the same file 

1109 return 

1110 backupfile = outpathfile + "." + str(k) 

1111 shutil.copy2(outpathfile, backupfile) 

1112 

1113 

1114def saveparams2last(func=None, multibackup=True): 

1115 """This function is a decorator function that allows for 

1116 task.last to be saved even if calling without casashell. Also 

1117 saves unique revisions ...just like the vax/vms style of revision saving 

1118 by default. set multibackup=False to no not have old revisions kept 

1119 """ 

1120 if not func: 

1121 return functools.partial(saveparams2last, multibackup=multibackup) 

1122 

1123 @functools.wraps(func) 

1124 def wrapper_saveparams(*args, **kwargs): 

1125 # multibackup=True 

1126 outfile = func.__name__ + ".last" 

1127 # print('args {} and kwargs {}'.format(args, kwargs)) 

1128 # print('length of args {}, and kwargs {}'.format(len(args), len(kwargs))) 

1129 params = {} 

1130 byIndex = list() 

1131 if len(kwargs) == 0: 

1132 paramsname = list(inspect.signature(func).parameters) 

1133 # params={paramsname[i]: args[i] for i in range(len(args))} 

1134 params = OrderedDict(zip(paramsname, args)) 

1135 byIndex = list(params) 

1136 else: 

1137 params = kwargs 

1138 byIndex = list(params) 

1139 ###for some reason the dictionary is in reverse 

1140 byIndex.reverse() 

1141 # print('@@@@MULTIBACKUP {}, params {}'.format(multibackup, params)) 

1142 if multibackup: 

1143 backupoldfile(outfile) 

1144 with open(outfile, "w") as _f: 

1145 for _i in range(len(byIndex)): 

1146 _f.write("%-20s = %s\n" % (byIndex[_i], repr(params[byIndex[_i]]))) 

1147 _f.write("#" + func.__name__ + "( ") 

1148 for _i in range(len(byIndex)): 

1149 _f.write("%s=%s" % (byIndex[_i], repr(params[byIndex[_i]]))) 

1150 if _i < len(params) - 1: 

1151 _f.write(",") 

1152 _f.write(" )\n") 

1153 ###End of stuff before task is called 

1154 retval = func(*args, **kwargs) 

1155 ###we could do something here post task 

1156 return retval 

1157 

1158 return wrapper_saveparams 

1159 

1160 

1161###################################################### 

1162 

1163 

1164def determineFreqRange( 

1165 vis: str = "", fieldid: int = 0, spw: str = "*" 

1166) -> Tuple[np.double, np.double]: 

1167 _tb = table() 

1168 _ms = ms() 

1169 _su = synthesisutils() 

1170 _qa = quanta() 

1171 minFreq = 1.0e20 

1172 maxFreq = 0.0 

1173 _tb.open(vis) 

1174 fieldids = _tb.getcol("FIELD_ID") 

1175 _tb.done() 

1176 # advisechansel does not work on fieldids not in main 

1177 if fieldid not in fieldids: 

1178 fieldid = fieldids[0] 

1179 frange = _su.advisechansel( 

1180 msname=vis, getfreqrange=True, fieldid=fieldid, spwselection=spw 

1181 ) 

1182 if minFreq > _qa.convert(frange["freqstart"], "Hz")["value"]: 

1183 minFreq = _qa.convert(frange["freqstart"], "Hz")["value"] 

1184 if maxFreq < _qa.convert(frange["freqend"], "Hz")["value"]: 

1185 maxFreq = _qa.convert(frange["freqend"], "Hz")["value"] 

1186 

1187 if minFreq > maxFreq: 

1188 raise Exception(f"Failed to determine frequency range in ms {vis}") 

1189 freqwidth = maxFreq - minFreq 

1190 return (minFreq, freqwidth)