1 + | |
2 + | |
3 + | |
4 + | |
5 + | |
6 + | |
7 + | |
8 + | |
9 + | |
10 + | |
11 + | |
12 + | |
13 + | |
14 + | |
15 + | |
16 + | |
17 + | |
18 + | |
19 + | |
20 + | |
21 + | |
22 + | |
23 + | |
24 + | |
25 + | |
26 + | |
27 + | |
28 + | |
29 + | |
30 + | |
31 + | |
32 + | |
33 + | |
34 + | |
35 + | |
36 + | |
37 + | import numpy |
38 + | from casatools import table as tbtool |
39 + | from casatools import quanta as qatool |
40 + | from casatools import msmetadata as msmdtool |
41 + | from functools import reduce |
42 + | from casatasks import casalog |
43 + | import os |
44 + | import csv |
45 + | |
46 + | class SpwMap: |
47 + | """ |
48 + | This object is basically set up to hold the information needed |
49 + | """ |
50 + | def __init__(self,calSpwId): |
51 + | self.calSpwId = calSpwId |
52 + | self.validFreqRange = [] |
53 + | self.mapsToSpw = [] |
54 + | self.bbNo = None |
55 + | |
56 + | class SpwInfo: |
57 + | def __init__(self,msfile,spwId) : |
58 + | self.tb = tbtool() |
59 + | self.setTableAndSpwId(msfile,spwId) |
60 + | |
61 + | def setTableAndSpwId(self,msfile,spwId) : |
62 + | self.setTable(msfile) |
63 + | self.setSpwId(spwId) |
64 + | |
65 + | def setTable(self,msfile) : |
66 + | self.tableName = "%s/SPECTRAL_WINDOW" % msfile |
67 + | self.tb.open(self.tableName) |
68 + | self.parameters = self.tb.colnames() |
69 + | self.tb.close() |
70 + | |
71 + | def setSpwId(self,spwId) : |
72 + | self.tb.open(self.tableName) |
73 + | self.values = {} |
74 + | for i in self.parameters : |
75 + | self.values[i] = self.tb.getcell(i,spwId) |
76 + | self.tb.close() |
77 + | |
78 + | def areIdentical(spwInfo1,spwInfo2) : |
79 + | if len(spwInfo1.parameters) <= len(spwInfo2.parameters) : |
80 + | minState = spwInfo1 ; maxState = spwInfo2 |
81 + | else : |
82 + | minState = spwInfo2 ; maxState = spwInfo1 |
83 + | valueCompare = [] |
84 + | for i in minState.parameters : |
85 + | compare = (minState.values[i] == maxState.values[i]) |
86 + | if numpy.ndarray not in [type(compare)] : |
87 + | compare = numpy.array(compare) |
88 + | if compare.all() : valueCompare.append(True) |
89 + | else : valueCompare.append(False) |
90 + | if False in valueCompare : return False |
91 + | else : return True |
92 + | |
93 + | def trimSpwmap(spwMap) : |
94 + | compare = range(len(spwMap)) |
95 + | evenPoint = compare[-1] |
96 + | for i in compare : |
97 + | if compare[i:] == spwMap[i:] : |
98 + | evenPoint = i |
99 + | break |
100 + | return spwMap[:i] |
101 + | |
102 + | |
103 + | def tsysspwmap(vis,tsystable,trim=True,relax=False, tsysChanTol=0): |
104 + | """ |
105 + | Generate default spwmap for ALMA Tsys, including TDM->FDM associations |
106 + | Input: |
107 + | vis the target MeasurementSet |
108 + | tsystable the input Tsys caltable (w/ TDM Tsys measurements) |
109 + | trim if True (the default), return minimum-length spwmap; |
110 + | otherwise the spwmap will be exhaustive and include |
111 + | the high-numbered (and usually irrelevant) wvr |
112 + | spws |
113 + | relax (not yet implemented) |
114 + | Output: |
115 + | spw list to use in applycal spwmap parameter for the Tsys caltable |
116 + | |
117 + | This function takes the Tsys Caltable you wish to apply to a |
118 + | MeasurementSet and generates a "applycal-ready" spwmap that |
119 + | provides the appropriate information regarding the transfer |
120 + | Tsys calibration from TDM spectral windows to FDM spectral |
121 + | windows. To execute the function: |
122 + | |
123 + | tsysmap=tsysspwmap(vis='my.ms',tsystable='mytsys.cal') |
124 + | |
125 + | tsysmap can then be supplied to the applycal spwmap parameter |
126 + | to ensure proper Tsys calibration application. |
127 + | |
128 + | """ |
129 + | |
130 + | localTb = tbtool() |
131 + | spwMaps = [] |
132 + | |
133 + | localTb.open(tsystable) |
134 + | measuredTsysSpw = numpy.unique(localTb.getcol("SPECTRAL_WINDOW_ID")) |
135 + | localTb.close() |
136 + | |
137 + | localTb.open("%s/SPECTRAL_WINDOW" % tsystable) |
138 + | for i in measuredTsysSpw: |
139 + | spwMap = SpwMap(i) |
140 + | chanFreqs = localTb.getcell("CHAN_FREQ",i) |
141 + | chanWidth = abs(chanFreqs[1]-chanFreqs[0]) |
142 + | spwMap.chanWidth = chanWidth |
143 + | spwMap.validFreqRange = [chanFreqs.min()-0.5*chanWidth,\ |
144 + | chanFreqs.max()+0.5*chanWidth] |
145 + | spwMaps.append(spwMap) |
146 + | localTb.close() |
147 + | |
148 + | |
149 + | localTb.open("%s/SPECTRAL_WINDOW" % vis) |
150 + | it = localTb.nrows() |
151 + | localTb.close() |
152 + | for j in spwMaps : |
153 + | localTb.open("%s/SPECTRAL_WINDOW" % vis) |
154 + | j.bbNo = localTb.getcell("BBC_NO",j.calSpwId) |
155 + | localTb.close() |
156 + | for i in range(it) : |
157 + | localTb.open("%s/SPECTRAL_WINDOW" % vis) |
158 + | chanFreqs = localTb.getcell("CHAN_FREQ",i) |
159 + | if len(chanFreqs) > 1 : |
160 + | chanWidth = localTb.getcell("CHAN_WIDTH",i)[0] |
161 + | freqMin = chanFreqs.min()-0.5*chanWidth |
162 + | freqMax = chanFreqs.max()+0.5*chanWidth |
163 + | else : |
164 + | chanWidth = localTb.getcell("CHAN_WIDTH",i) |
165 + | freqMin = chanFreqs-0.5*chanWidth |
166 + | freqMax = chanFreqs+0.5*chanWidth |
167 + | msSpw = SpwInfo(vis,i) |
168 + | if j.bbNo == msSpw.values['BBC_NO']: |
169 + | if freqMin >= j.validFreqRange[0]-tsysChanTol*j.chanWidth and \ |
170 + | freqMax <= j.validFreqRange[1]+tsysChanTol*j.chanWidth : |
171 + | j.mapsToSpw.append(i) |
172 + | localTb.close() |
173 + | applyCalSpwMap = [] |
174 + | spwWithoutMatch = [] |
175 + | localTb.open("%s/SPECTRAL_WINDOW" % vis) |
176 + | for i in range(it) : |
177 + | useSpw = None |
178 + | for j in spwMaps : |
179 + | if i in j.mapsToSpw : |
180 + | if useSpw is not None : |
181 + | if localTb.getcell("BBC_NO") == j.bbNo : |
182 + | useSpw = j.calSpwId |
183 + | else : |
184 + | useSpw = j.calSpwId |
185 + | if useSpw == None : |
186 + | useSpw = i |
187 + | spwWithoutMatch.append(i) |
188 + | applyCalSpwMap.append(int(useSpw)) |
189 + | if len(spwWithoutMatch) != 0: |
190 + | casalog.post('Found no match for following spw ids: '+str(spwWithoutMatch)) |
191 + | if trim : |
192 + | return trimSpwmap(applyCalSpwMap) |
193 + | else : |
194 + | return applyCalSpwMap |
195 + | |
196 + | |
197 + | def fixsyscaltimes(vis,newinterval=2.0): |
198 + | """ |
199 + | Fix TIME,INTERVAL columns in MS SYSCAL subtable |
200 + | Input: |
201 + | vis the MS containing the offending SYSCAL subtable |
202 + | newinterval the interval to use in revised entries |
203 + | |
204 + | This function is intended to repair MS SYSCAL tables that suffer from |
205 + | multiple TIME values (over antennas) per Tsys measurement. The gencal |
206 + | task (mode='tsys' expects all antennas to share the same TIME value |
207 + | for each Tsys measurement (and this is usually true). The function |
208 + | finds those measurements that have multiple TIMEs and replaces them |
209 + | with a common TIME value which takes the value |
210 + | mean(oldTIME-INTERVAL/2)+newinterval/2. |
211 + | Usually (always?), oldTIME-INTERVAL/2 is constant over antennas |
212 + | and represents the physical timestamp of the Tsys measurment. |
213 + | If the function finds no pathological timestamps, it does not |
214 + | revise the table. |
215 + | """ |
216 + | |
217 + | import pylab as mypl |
218 + | import math as mymath |
219 + | myqa=qatool() |
220 + | mytb=tbtool() |
221 + | mytb.open(vis+'/SYSCAL',nomodify=False) |
222 + | |
223 + | spws=mypl.unique(mytb.getcol("SPECTRAL_WINDOW_ID")) |
224 + | |
225 + | |
226 + | fixapplied=False |
227 + | |
228 + | for ispw in spws: |
229 + | st=mytb.query('SPECTRAL_WINDOW_ID=='+str(ispw),name='byspw') |
230 + | times=st.getcol('TIME') |
231 + | interval=st.getcol('INTERVAL') |
232 + | timestamps=times-interval/2 |
233 + | t0=86400.0*mymath.floor(timestamps[0]/86400.0) |
234 + | |
235 + | utimes=mypl.unique(times-t0) |
236 + | nT=len(utimes) |
237 + | utimestamps=mypl.unique(mypl.floor(timestamps)-t0) |
238 + | nTS=len(utimestamps) |
239 + | |
240 + | msg='In spw='+str(ispw)+' found '+str(nTS)+' Tsys measurements with '+str(nT)+' TIMEs...' |
241 + | if nT==nTS: |
242 + | msg+='OK.' |
243 + | print(msg) |
244 + | |
245 + | else: |
246 + | |
247 + | fixapplied=True |
248 + | |
249 + | msg+=' which is too many, so fixing it:' |
250 + | print(msg) |
251 + | |
252 + | for uts in utimestamps: |
253 + | mask = ((mypl.floor(timestamps))-t0==uts) |
254 + | uTIMEs=mypl.unique(times[mask]) |
255 + | nTIMEs=len(uTIMEs) |
256 + | newtime = mypl.mean(times[mask]-interval[mask]/2) + newinterval/2 |
257 + | msg=' Found '+str(nTIMEs)+' TIMEs at timestamp='+str(myqa.time(str(newtime-newinterval/2)+'s',form='ymd')[0]) |
258 + | if nTIMEs>1: |
259 + | msg+=':' |
260 + | print(msg) |
261 + | print(' TIMEs='+str([myqa.time(str(t)+'s',form='ymd')[0] for t in uTIMEs])+' --> '+str(myqa.time(str(newtime)+'s',form='ymd')[0])+' w/ INTERVAL='+str(newinterval)) |
262 + | times[mask]=newtime |
263 + | interval[mask]=newinterval |
264 + | st.putcol('TIME',times) |
265 + | st.putcol('INTERVAL',interval) |
266 + | else: |
267 + | msg+='...ok.' |
268 + | print(msg) |
269 + | st.close() |
270 + | mytb.close() |
271 + | |
272 + | if fixapplied: |
273 + | print('Some SYSCAL time corrections have been applied.') |
274 + | else: |
275 + | print('All SYSCAL times seem correct; no changes required.') |
276 + | |
277 + | |
278 + | return fixapplied |
279 + | |
280 + | |
281 + | def calantsub(incaltable,outcaltable='', |
282 + | spw='',scan='', |
283 + | ant='',subant=''): |
284 + | |
285 + | """ |
286 + | Substitute cal solutions by antenna |
287 + | Input: |
288 + | incaltable Input caltable |
289 + | outcaltable Output caltable (if '', overwrite result on incaltable) |
290 + | spw Spectral Window selection (no channel selection permitted) |
291 + | scan Scan selection |
292 + | ant Antenna (indices) which need replaced solutions |
293 + | subant Antenna (indices) with which to replace those in ant |
294 + | |
295 + | This function provides a means to replace solutions by antenna, |
296 + | e.g., to substitute one antenna's Tsys spectra with another. |
297 + | |
298 + | The processing can be limited to specific spectral windows and/or |
299 + | scans. The spw and scan parameters should be specified in the |
300 + | standard MS selection manner (comma-separated integers in a string), |
301 + | except no channel selection is supported. |
302 + | |
303 + | The ant parameter specifies one or more antenna indices |
304 + | (comma-separated in a string) for which solutions are to be |
305 + | replaced. The subant parameter lists the antenna indices |
306 + | from which the substitute solutions are to be obtained. E.g., |
307 + | ant='3,5,7',subant='6,8,10' will cause the solutions from |
308 + | antenna id 6 to be copied to antenna id 5, id 8 to id 5 and id 10 |
309 + | to id 7. The number of antennas specified in ant and subant |
310 + | must match. |
311 + | |
312 + | """ |
313 + | |
314 + | import pylab as mypl |
315 + | |
316 + | |
317 + | if len(ant)==0 or len(subant)==0: |
318 + | raise Exception("Must specify at least one ant and subant.") |
319 + | |
320 + | antlist=ant.split(',') |
321 + | sublist=subant.split(',') |
322 + | |
323 + | |
324 + | nant=len(antlist) |
325 + | nsub=len(sublist) |
326 + | if nant!=nsub: |
327 + | raise Exception("Must specify equal number of ant and subant.") |
328 + | |
329 + | |
330 + | mytb=tbtool() |
331 + | |
332 + | |
333 + | selstr='' |
334 + | if len(spw)>0: |
335 + | selstr+='SPECTRAL_WINDOW_ID IN ['+spw+']' |
336 + | if len(scan)>0: |
337 + | selstr+=' && ' |
338 + | if len(scan)>0: |
339 + | selstr+='SCAN_NUMBER IN ['+scan+']' |
340 + | print("selstr = '"+selstr+"'") |
341 + | |
342 + | |
343 + | if len(selstr)>0: |
344 + | mytb.open(incaltable) |
345 + | st=mytb.query(query=selstr) |
346 + | nselrows=st.nrows() |
347 + | st.close() |
348 + | mytb.close() |
349 + | if nselrows==0: |
350 + | raise Exception('Error: scan and/or spw selection selects no rows!') |
351 + | |
352 + | |
353 + | if outcaltable=='': |
354 + | outcaltable=incaltable |
355 + | print("No outcaltable specified; will overwrite incaltable.") |
356 + | if outcaltable!=incaltable: |
357 + | os.system('cp -r '+incaltable+' '+outcaltable) |
358 + | |
359 + | |
360 + | mytb.open(outcaltable,nomodify=False) |
361 + | |
362 + | stsel=mytb |
363 + | if len(selstr)>0: |
364 + | stsel=mytb.query(query=selstr,name='selected') |
365 + | |
366 + | |
367 + | collist=['TIME','INTERVAL','PARAMERR','SNR','FLAG'] |
368 + | cols=mytb.colnames() |
369 + | if cols.count('CPARAM')>0: |
370 + | collist.append('CPARAM') |
371 + | else: |
372 + | collist.append('FPARAM') |
373 + | |
374 + | |
375 + | scans=mypl.unique(stsel.getcol('SCAN_NUMBER')) |
376 + | |
377 + | print('Found scans = ',scans) |
378 + | |
379 + | |
380 + | for scan in scans: |
381 + | st1=stsel.query(query='SCAN_NUMBER=='+str(scan),name='byscan') |
382 + | spws=mypl.unique(st1.getcol('SPECTRAL_WINDOW_ID')) |
383 + | |
384 + | print('Scan '+str(scan)+' has spws='+str(spws)) |
385 + | |
386 + | |
387 + | for ispw in spws: |
388 + | st2=st1.query(query='SPECTRAL_WINDOW_ID=='+str(ispw),name='byspw'); |
389 + | |
390 + | for ia in range(nant): |
391 + | stsub=st2.query(query='ANTENNA1=='+sublist[ia], |
392 + | name='subant') |
393 + | stant=st2.query(query='ANTENNA1=='+antlist[ia], |
394 + | name='ant') |
395 + | |
396 + | |
397 + | |
398 + | if stant.nrows()<1: |
399 + | continue |
400 + | |
401 + | print(' scan='+str(scan)+' spw='+str(ispw)+' ants: '+str(sublist[ia])+'->'+str(antlist[ia])) |
402 + | |
403 + | |
404 + | if stsub.nrows()!=stant.nrows(): |
405 + | raise Exception("In spw "+str(ispw)+" antenna ids "+str(antlist[ia])+" and "+str(sublist[ia])+" have a different number of solutions.") |
406 + | |
407 + | |
408 + | for col in collist: |
409 + | stant.putcol(col,stsub.getcol(col)) |
410 + | |
411 + | stsub.close() |
412 + | stant.close() |
413 + | st2.close() |
414 + | st1.close() |
415 + | stsel.close() |
416 + | mytb.close() |
417 + | |
418 + | def editIntentscsv(intentcsv, dryrun=False, verbose=False): |
419 + | """ |
420 + | Reads a list of parameters from a csv text file and runs editIntents on them. |
421 + | File format: |
422 + | # any number of comment lines |
423 + | vis,field,scan,"WVR,BANDPASS" |
424 + | vis,field,,'FLUX,WVR' |
425 + | ... |
426 + | |
427 + | * field is a required argument, while scan is optional |
428 + | * scan: can be a single integer, integer list, or comma-delimited string list |
429 + | multiple scans or intents must be quoted, either single or double quote |
430 + | -Todd Hunter |
431 + | """ |
432 + | if (not os.path.exists(intentcsv)): |
433 + | print("Could not find intentcsv file: ", intentcsv) |
434 + | return |
435 + | f = open(intentcsv,'r') |
436 + | lines = f.readlines() |
437 + | f.close() |
438 + | for originalLine in lines: |
439 + | if (originalLine[0] == '#'): continue |
440 + | line = originalLine.replace("'",'"').replace(' ','') |
441 + | if verbose: |
442 + | print("Parsing reformatted line: ", line[:-1]) |
443 + | if (len(line.split(',')) > 3): |
444 + | token = [] |
445 + | for i in csv.reader([line]): |
446 + | token.append(i) |
447 + | token = token[0] |
448 + | if (len(token) < 4): |
449 + | print("Skipping invalid line: ", originalLine) |
450 + | continue |
451 + | vis = token[0].strip() |
452 + | field = token[1].strip() |
453 + | scan = token[2].strip() |
454 + | intents = token[3].strip() |
455 + | if (os.path.exists(vis)): |
456 + | cmd = "au.editIntents('%s','%s','%s','%s')" % (vis,field,scan,intents) |
457 + | if dryrun: |
458 + | print("Would call %s\n" % (cmd)) |
459 + | else: |
460 + | print("Calling %s" % (cmd)) |
461 + | editIntents(vis,field,scan,intents,verbose=verbose) |
462 + | else: |
463 + | print("Could not find requested measurement set: ", vis) |
464 + | |
465 + | def editIntents(msName='', field='', scan='', newintents='', |
466 + | append=False, verbose=True): |
467 + | """ |
468 + | Change the observation intents for a specified field. |
469 + | Inputs: |
470 + | * field: required argument (integer or string ID, or name) |
471 + | * scan: optional, can be a single integer, integer list, or comma-delimited string list |
472 + | * append: set to True to add the specified intent to the existing intents |
473 + | * newintents: enter as a python list, or as single comma-delimited string |
474 + | * valid intents: the first 12 are simply a shorthand way to specify the latter 12 |
475 + | 'AMPLITUDE', 'ATMOSPHERE', 'BANDPASS', 'CHECK_SOURCE', 'WVR', |
476 + | 'DELAY', 'FLUX', 'FOCUS', 'PHASE', 'POINTING', 'SIDEBAND_RATIO', 'TARGET', |
477 + | 'CALIBRATE_AMPLI', 'CALIBRATE_ATMOSPHERE', 'CALIBRATE_BANDPASS', |
478 + | 'CALIBRATE_DELAY', 'CALIBRATE_FLUX', 'CALIBRATE_FOCUS', |
479 + | 'CALIBRATE_PHASE', 'CALIBRATE_POINTING', 'CALIBRATE_SIDEBAND_RATIO', |
480 + | 'OBSERVE_TARGET', 'CALIBRATE_WVR', 'OBSERVE_CHECK_SOURCE' |
481 + | The modifier #ON_SOURCE will be added to all specified intents. |
482 + | - T. Hunter |
483 + | """ |
484 + | validIntents = ['AMPLITUDE','ATMOSPHERE','BANDPASS','CHECK_SOURCE', |
485 + | 'DELAY','FLUX','FOCUS', 'PHASE','POINTING', |
486 + | 'SIDEBAND_RATIO', 'TARGET','WVR', 'CALIBRATE_AMPLI', |
487 + | 'CALIBRATE_ATMOSPHERE', 'CALIBRATE_BANDPASS', |
488 + | 'CALIBRATE_DELAY', 'CALIBRATE_FLUX', 'CALIBRATE_FOCUS', |
489 + | 'CALIBRATE_PHASE', 'CALIBRATE_POINTING', |
490 + | 'CALIBRATE_SIDEBAND_RATIO','OBSERVE_TARGET', |
491 + | 'CALIBRATE_WVR', 'OBSERVE_CHECK_SOURCE' |
492 + | ] |
493 + | if (msName == ''): |
494 + | print("You must specify a measurement set.") |
495 + | return |
496 + | if (field == ''): |
497 + | print("You must specify a field ID or name.") |
498 + | return |
499 + | mytb = tbtool() |
500 + | mytb.open(msName + '/FIELD') |
501 + | fieldnames = mytb.getcol('NAME') |
502 + | if verbose: |
503 + | print("Found fieldnames = ", fieldnames) |
504 + | mytb.close() |
505 + | |
506 + | mytb.open(msName + '/STATE') |
507 + | intentcol = mytb.getcol('OBS_MODE') |
508 + | intentcol = intentcol |
509 + | mytb.close() |
510 + | |
511 + | mytb.open(msName, nomodify=False) |
512 + | naddedrows = 0 |
513 + | if (type(newintents)==list): |
514 + | desiredintents = '' |
515 + | for n in newintents: |
516 + | desiredintents += n |
517 + | if (n != newintents[-1]): |
518 + | desiredintents += ',' |
519 + | else: |
520 + | desiredintents = newintents |
521 + | desiredintentsList = desiredintents.split(',') |
522 + | |
523 + | for intent in desiredintentsList: |
524 + | if ((intent in validIntents)==False): |
525 + | print("Invalid intent = %s. Valid intents = %s" % (intent,validIntents)) |
526 + | mytb.close() |
527 + | return |
528 + | foundField = False |
529 + | if (type(scan) != list): |
530 + | scan = str(scan) |
531 + | for id,name in enumerate(fieldnames): |
532 + | if (name == field or str(id) == str(field)): |
533 + | foundField = True |
534 + | if verbose: |
535 + | print('FIELD_ID %s has name %s' % (id, name)) |
536 + | if scan == '': |
537 + | s = mytb.query('FIELD_ID==%s' % id) |
538 + | if verbose: |
539 + | print("Got %d rows in the ms matching field=%s" % (s.nrows(),id)) |
540 + | else: |
541 + | if (type(scan) == str): |
542 + | scans = [int(x) for x in scan.split(',')] |
543 + | elif (type(scan) != list): |
544 + | scans = [scan] |
545 + | else: |
546 + | scans = scan |
547 + | if verbose: |
548 + | print("Querying: 'FIELD_ID==%s AND SCAN_NUMBER in %s'" % (id, str(scans))) |
549 + | s = mytb.query('FIELD_ID==%s AND SCAN_NUMBER in %s' % (id, str(scans))) |
550 + | if verbose: |
551 + | print("Got %d rows in the ms matching field=%s and scan in %s" % (s.nrows(),id,str(scans))) |
552 + | if (s.nrows() == 0): |
553 + | mytb.close() |
554 + | print("Found zero rows for this field (and/or scan). Stopping.") |
555 + | return |
556 + | state_ids = s.getcol('STATE_ID') |
557 + | states = [] |
558 + | for state_id in state_ids: |
559 + | if state_id not in states: |
560 + | states.append(state_id) |
561 + | for ij in range(len(states)): |
562 + | states[ij] = intentcol[states[ij]] |
563 + | |
564 + | print('current intents are:') |
565 + | for state in states: |
566 + | print(state) |
567 + | |
568 + | if append == False: states = [] |
569 + | for desiredintent in desiredintentsList: |
570 + | if desiredintent.find('TARGET')>=0: |
571 + | states.append('OBSERVE_TARGET#ON_SOURCE') |
572 + | elif desiredintent.find('CHECK_SOURCE')>=0: |
573 + | states.append('OBSERVE_CHECK_SOURCE#ON_SOURCE') |
574 + | elif desiredintent.find('BANDPASS')>=0: |
575 + | states.append('CALIBRATE_BANDPASS#ON_SOURCE') |
576 + | elif desiredintent.find('PHASE')>=0: |
577 + | states.append('CALIBRATE_PHASE#ON_SOURCE') |
578 + | elif desiredintent.find('AMPLI')>=0: |
579 + | states.append('CALIBRATE_AMPLI#ON_SOURCE') |
580 + | elif desiredintent.find('FLUX')>=0: |
581 + | states.append('CALIBRATE_FLUX#ON_SOURCE') |
582 + | elif desiredintent.find('ATMOSPHERE')>=0: |
583 + | states.append('CALIBRATE_ATMOSPHERE#ON_SOURCE') |
584 + | elif desiredintent.find('WVR')>=0: |
585 + | states.append('CALIBRATE_WVR#ON_SOURCE') |
586 + | elif desiredintent.find('SIDEBAND_RATIO')>=0: |
587 + | states.append('CALIBRATE_SIDEBAND_RATIO#ON_SOURCE') |
588 + | elif desiredintent.find('DELAY')>=0: |
589 + | states.append('CALIBRATE_DELAY#ON_SOURCE') |
590 + | elif desiredintent.find('FOCUS')>=0: |
591 + | states.append('CALIBRATE_FOCUS#ON_SOURCE') |
592 + | elif desiredintent.find('POINTING')>=0: |
593 + | states.append('CALIBRATE_POINTING#ON_SOURCE') |
594 + | else: |
595 + | print("Unrecognized intent = %s" % desiredintent) |
596 + | continue |
597 + | print('setting %s' % (states[-1])) |
598 + | |
599 + | if states != []: |
600 + | state = reduce(lambda x,y: '%s,%s' % (x,y), states) |
601 + | if state not in intentcol: |
602 + | if verbose: |
603 + | print('adding intent to state table') |
604 + | intentcol = list(intentcol) |
605 + | intentcol.append(state) |
606 + | intentcol = numpy.array(intentcol) |
607 + | state_id = len(intentcol) - 1 |
608 + | naddedrows += 1 |
609 + | if verbose: |
610 + | print('state_id is', state_id) |
611 + | state_ids[:] = state_id |
612 + | else: |
613 + | if verbose: |
614 + | print('intent already in state table') |
615 + | state_id = numpy.arange(len(intentcol))[intentcol==state] |
616 + | if verbose: |
617 + | print('state_id is', state_id) |
618 + | if (type(state_id) == list or type(state_id)==numpy.ndarray): |
619 + | |
620 + | |
621 + | state_ids[:] = state_id[0] |
622 + | else: |
623 + | state_ids[:] = state_id |
624 + | s.putcol('STATE_ID', state_ids) |
625 + | if (foundField == False): |
626 + | print("Field not found") |
627 + | return |
628 + | mytb.close() |
629 + | |
630 + | if verbose: |
631 + | print('writing new STATE table') |
632 + | mytb.open(msName + '/STATE', nomodify=False) |
633 + | if naddedrows > 0: |
634 + | mytb.addrows(naddedrows) |
635 + | mytb.putcol('OBS_MODE', intentcol) |
636 + | mytb.close() |
637 + | |
638 + | def genImageName(vis='', spw='', field='', imtype='mfs', targettype='sci', stokes='I', mous='', modtext='manual'): |
639 + | |
640 + | |
641 + | """ |
642 + | Generate image name to be used in clean command |
643 + | following the ALMA archive conventions (see SCIREQ-110, draft recommendations v4.8). |
644 + | vis = MS to be imaged (to extract object name, SPW frequencies |
645 + | spw = ID of SPW to be imaged |
646 + | If several SPWs are imaged at once, enter list of SPWs. |
647 + | field = ID of field to be imaged (to extract object name). |
648 + | in case of mosaic, give any ID of field belonging to the mosaic |
649 + | imtype = image type, 'mfs' (default) - aggregate bandwidth |
650 + | 'cont' - mfs with lines excluded |
651 + | 'cube' - spectrally resolved cube |
652 + | targettype = 'sci' (default) for science target |
653 + | 'bp' for bandpass calibrator |
654 + | 'ph' for phase calibrator |
655 + | 'chk' for checksource |
656 + | 'amp' for flux calibrator |
657 + | stokes = 'I' (default) for Stokes I image |
658 + | any combination of 'I', 'Q', 'U', 'V', 'P', 'A' |
659 + | mous = '' (default) normally this is filled by the archive. |
660 + | But if you know the MOUS UID, enter it in the format with "_" |
661 + | e.g. uid___A001_X888_X66 |
662 + | modtext = 'manual' (default) - this string can be used to add text, |
663 + | i.e. a modification, to the standard file name. |
664 + | It is appended at the end of the name. |
665 + | """ |
666 + | |
667 + | themous = '' |
668 + | theobject = '' |
669 + | thetargettype = '' |
670 + | targettypes = ['sci', 'bp', 'ph', 'chk', 'amp'] |
671 + | thespwid = '' |
672 + | theimtype = '' |
673 + | imtypes = ['mfs', 'cont', 'cube'] |
674 + | thestokes = '' |
675 + | stokess = ['I', 'Q', 'U', 'V', 'IQU', 'IQUV', 'P', 'A'] |
676 + | themodtext = '' |
677 + | |
678 + | rval = '' |
679 + | |
680 + | mymsmd = msmdtool() |
681 + | |
682 + | |
683 + | if (mous != ''): |
684 + | themous = mous+'.' |
685 + | |
686 + | |
687 + | try: |
688 + | mymsmd.open(vis) |
689 + | except: |
690 + | mymsmd.done() |
691 + | raise Exception("ERROR: problem opening vis") |
692 + | |
693 + | myfieldnames = mymsmd.fieldnames() |
694 + | if type(field)==str and (field in myfieldnames): |
695 + | theobject = field |
696 + | elif field in range(mymsmd.nfields()): |
697 + | theobject = myfieldnames[field] |
698 + | else: |
699 + | print("ERROR: invalid field: "+field) |
700 + | print("Valid entries are ", myfieldnames) |
701 + | print(" or ", range(mymsmd.nfields())) |
702 + | raise Exception("ERROR: invalid field: "+field) |
703 + | myspws = mymsmd.spwsforfield(theobject) |
704 + | mymsmd.close() |
705 + | |
706 + | |
707 + | if targettype in targettypes: |
708 + | thetargettype = targettype |
709 + | else: |
710 + | print("ERROR: invalid targettype ", targettype) |
711 + | print(" Valid entries are ", targettypes) |
712 + | raise Exception("ERROR: invalid targettype ", targettype) |
713 + | |
714 + | |
715 + | if type(spw) != type([]): |
716 + | spw = [spw] |
717 + | for myspw in spw: |
718 + | if myspw in myspws: |
719 + | if thespwid=='': |
720 + | thespwid = str(myspw) |
721 + | else: |
722 + | thespwid = thespwid+'_'+str(myspw) |
723 + | else: |
724 + | if not type(myspw) == int: |
725 + | print("ERROR: invalid spw: "+str(myspw)+". Valid entries are ", myspws) |
726 + | raise Exception("ERROR: invalid spw: "+str(myspw)+' Data type must be int.') |
727 + | else: |
728 + | print("ERROR: invalid spw: "+str(myspw)+". Valid entries are ", myspws) |
729 + | raise Exception("ERROR: invalid spw: "+str(myspw)) |
730 + | |
731 + | |
732 + | if imtype in imtypes: |
733 + | theimtype = imtype |
734 + | else: |
735 + | print("ERROR: invalid imtype ", imtype) |
736 + | print(" Valid entries are ", imtypes) |
737 + | raise Exception( "ERROR: invalid imtype "+str(imtype)) |
738 + | |
739 + | |
740 + | if stokes in stokess: |
741 + | thestokes = stokes |
742 + | else: |
743 + | print("ERROR: invalid stokes ", stokes) |
744 + | print(" Valid entries are ", stokess) |
745 + | raise Exception("ERROR: invalid stokes "+str(stokes)) |
746 + | |
747 + | |
748 + | |
749 + | if modtext != '': |
750 + | themodtext = '.'+modtext |
751 + | if ' ' in themodtext: |
752 + | raise Exception("ERROR: modtext must not contain spaces") |
753 + | |
754 + | rval = themous+theobject+'_'+thetargettype+'.spw'+thespwid+'.'+theimtype+'.'+thestokes+themodtext |
755 + | |
756 + | if ' ' in rval: |
757 + | raise Exception("ERROR: generated name contains spaces: \""+rval+"\"") |
758 + | if '/' in rval: |
759 + | raise Exception("ERROR: generated name contains slash: \""+rval+"\"") |
760 + | if '*' in rval: |
761 + | raise Exception("ERROR: generated name contains star: \""+rval+"\"") |
762 + | if '?' in rval: |
763 + | raise Exception("ERROR: generated name contains question mark: \""+rval+"\"") |
764 + | |
765 + | return rval |
766 + | |