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
« 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
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
25"""
26A set of helper functions for the tasks tclean
28Summary...
30"""
33######################################################
34######################################################
35######################################################
37class ImagerParameters():
38 def __init__(
39 self,
40 # Input Data: what gets in
41 msname='',
43 # Output Data: what goes out
44 imagename='',
46 # The remaining parameters are Control Parameters:
47 # they control How what gets in goes out
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,
84 wprojplanes=1,
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],
100 # Normalizer group
101 pblimit=0.01,
102 normtype='flatnoise',
103 psfcutoff=0.35,
104 makesingledishnormalizer=False,
106 outlierfile='',
107 restart=True,
109 weighting='natural',
110 robust=0.5,
111 noise='0.0Jy',
112 npixels=0,
113 uvtaper=[],
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,
127 deconvolver='hogbom',
128 scales=[],
129 nterms=1,
130 scalebias=0.0,
131 restoringbeam=[],
132 # mtype='default',
134 usemask='user',
135 mask='',
136 pbmask=0.0,
137 maskthreshold='',
138 maskresolution='',
139 nmask=0,
140 # autoadjust=False,
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,
157 # usescratch=True,
158 # readonly=True,
159 savemodel="none",
160 parallel=False,
162 workdir='',
164 # CFCache params
165 cflist=[],
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):
178 self.allparameters = dict(locals())
179 del self.allparameters['self']
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'
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,
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']
364 ret = self.checkParameters(parallel)
365 if ret == False:
366 casalog.post(
367 'Found errors in input parameters. Please check.', 'WARN'
368 )
370 self.printParameters()
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
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
392 def getAllPars(self):
393 """Return the state of all parameters"""
394 return self.allparameters
396 def getSelPars(self):
397 return self.allselpars
399 def getImagePars(self):
400 return self.allimpars
402 def getGridPars(self):
403 return self.allgridpars
405 def getWeightPars(self):
406 return self.weightpars
408 def getDecPars(self):
409 return self.alldecpars
411 def getIterPars(self):
412 return self.iterpars
414 def getNormPars(self):
415 return self.allnormpars
417 def getCFCachePars(self):
418 return self.cfcachepars
420 def setSelPars(self, selpars):
421 for key in selpars.keys():
422 self.allselpars[key] = selpars[key]
424 def setImagePars(self, impars):
425 for key in impars.keys():
426 self.allimpars[key] = impars[key]
428 def setGridPars(self, gridpars):
429 for key in gridpars.keys():
430 self.allgridpars[key] = gridpars[key]
432 def setWeightPars(self, weightpars):
433 for key in weightpars.keys():
434 self.weightpars[key] = weightpars[key]
436 def setDecPars(self, decpars):
437 for key in decpars.keys():
438 self.alldecpars[key] = decpars[key]
440 def setIterPars(self, iterpars):
441 for key in iterpars.keys():
442 self.iterpars[key] = iterpars[key]
444 def setNormPars(self, normpars):
445 for key in normpars.keys():
446 self.allnormpars[key] = normpars[key]
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()
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 }
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
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
506 ###### Start : Parameter-checking functions ##################
508 def checkAndFixSelectionPars(self):
509 errs = ""
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
517 if ok == True:
518 # casalog.post("Already in correct format")
519 return errs
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]
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)
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)
561 selkeys = self.allselpars.keys()
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]]
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"])
572 if nvis==0:
573 errs = errs + "Input MS list is empty"
574 return errs
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])
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]
599 synu = synthesisutils()
600 selparlist["ms" + str(ms)] = synu.checkselectionparams(
601 selparlist["ms" + str(ms)]
602 )
603 synu.done()
605 # casalog.post(selparlist)
606 self.allselpars = selparlist
608 return errs
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
627 if len(parseerrors) > 0:
628 errs = errs + "Errors in parsing outlier file : " + parseerrors
629 return errs
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.
645 # casalog.post(self.allimpars)
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
663 ## Check for name increments, and copy from impars to decpars and normpars.
664 self.handleImageNames()
666 return errs
668 def handleImageNames(self):
670 for immod in self.allimpars.keys():
671 inpname = self.allimpars[immod]["imagename"]
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)
682 ### Check for name increments
683 # if self.reusename == False:
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"]
693 newnamelist = self.incrementImageNameList(inpnamelist)
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]
704 def checkAndFixIterationPars(self):
705 errs = ""
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)
719 # saving model is done separately outside of iter. control for interactive clean and or automasking cases
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
730 return errs
732 def checkAndFixNormPars(self):
733 errs = ""
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'
739 return errs
741 ###### End : Parameter-checking functions ##################
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
748 if len(outlierfilename) > 0 and not os.path.exists(outlierfilename):
749 errs += "Cannot find outlier file : " + outlierfilename + "\n"
750 return returnlist, errs
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)
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 )
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' )
824 # casalog.post(returnlist)
825 return returnlist, errs
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)
846 globalpars[fld][subparkey][parname] = val_e
847 except:
848 casalog.post(
849 'Cannot evaluate outlier field parameter "' + parname + '"', "ERROR"
850 )
852 return globalpars
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")
863 def incrementImageName(self, imagename):
864 dirname = "."
865 prefix = imagename
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
877 inamelist = [fn for fn in os.listdir(dirname) if any([fn.startswith(prefix)])]
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)
894 casalog.post("Using : {}".format(newimagename))
895 return newimagename
897 def incrementImageNameList(self, inpnamelist):
899 dirnames = {}
900 prefixes = {}
902 for immod in inpnamelist.keys():
903 imagename = inpnamelist[immod]
904 dirname = "."
905 prefix = imagename
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
916 dirnames[immod] = dirname
917 prefixes[immod] = prefix
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)
927 if len(inamelist) == 0:
928 locmax = 0
929 else:
930 locmax = 1
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
953 if not incremented:
954 locmax = 0
955 if locmax > maxid:
956 maxid = locmax
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 )
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
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
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
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'])
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
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)
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()
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
1089 ############################
1092#################################################################################################
1093def backupoldfile(thefile=""):
1094 import copy
1095 import shutil
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)
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)
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
1158 return wrapper_saveparams
1161######################################################
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"]
1187 if minFreq > maxFreq:
1188 raise Exception(f"Failed to determine frequency range in ms {vis}")
1189 freqwidth = maxFreq - minFreq
1190 return (minFreq, freqwidth)