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

658 statements  

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

1import numpy as np 

2import shutil 

3import os 

4from io import StringIO 

5import math, re, sys, logging 

6from functools import reduce 

7import time as tm 

8import datetime as dt 

9import casatools 

10msmd = casatools.msmetadata() 

11tb = casatools.table() 

12 

13desc = {'ANTENNA_ID': {'comment': 'ID of antenna in this array', 

14 'dataManagerGroup': 'StandardStMan', 

15 'dataManagerType': 'StandardStMan', 

16 'keywords': {}, 

17 'maxlen': 0, 

18 'option': 0, 

19 'valueType': 'int'}, 

20 'FEED_ID': {'comment': 'Feed id', 

21 'dataManagerGroup': 'StandardStMan', 

22 'dataManagerType': 'StandardStMan', 

23 'keywords': {}, 

24 'maxlen': 0, 

25 'option': 0, 

26 'valueType': 'int'}, 

27 'INTERVAL': {'comment': 'Interval for which this set of parameters is accurate', 

28 'dataManagerGroup': 'StandardStMan', 

29 'dataManagerType': 'StandardStMan', 

30 'keywords': {'QuantumUnits': np.array(['s'], dtype='<U1')}, 

31 'maxlen': 0, 

32 'option': 0, 

33 'valueType': 'double'}, 

34 'SPECTRAL_WINDOW_ID': {'comment': 'ID for this spectral window setup', 

35 'dataManagerGroup': 'StandardStMan', 

36 'dataManagerType': 'StandardStMan', 

37 'keywords': {}, 

38 'maxlen': 0, 

39 'option': 0, 

40 'valueType': 'int'}, 

41 'TIME': {'comment': 'Midpoint of time for which this set of parameters is accurate', 

42 'dataManagerGroup': 'StandardStMan', 

43 'dataManagerType': 'StandardStMan', 

44 'keywords': {'MEASINFO': {'Ref': 'UTC', 'type': 'epoch'}, 

45 'QuantumUnits': np.array(['s'], dtype='<U1')}, 

46 'maxlen': 0, 

47 'option': 0, 

48 'valueType': 'double'}, 

49 'TSYS': {'comment': 'System temp. for each of the two receptors', 

50 'dataManagerGroup': 'StandardStMan', 

51 'dataManagerType': 'StandardStMan', 

52 'keywords': {'QuantumUnits': np.array(['K'], dtype='<U1')}, 

53 'maxlen': 0, 

54 'ndim': -1, 

55 'option': 0, 

56 'valueType': 'float'}, 

57 '_define_hypercolumn_': {}, 

58 '_keywords_': {}, 

59 '_private_keywords_': {}} 

60dminfo = {'*1': {'COLUMNS': np.array(['ANTENNA_ID', 'FEED_ID', 'INTERVAL', 'SPECTRAL_WINDOW_ID', 'TIME', 

61 'TSYS'], dtype='<U18'), 

62 'NAME': 'StandardStMan', 

63 'SEQNR': 0, 

64 'SPEC': {'BUCKETSIZE': 1152, 

65 'IndexLength': 7486, 

66 'MaxCacheSize': 2, 

67 'PERSCACHESIZE': 2}, 

68 'TYPE': 'StandardStMan'}} 

69desc_gc = {'ANTENNA_ID': {'comment': 'Antenna identifier', 

70 'dataManagerGroup': 'StandardStMan', 

71 'dataManagerType': 'StandardStMan', 

72 'keywords': {}, 

73 'maxlen': 0, 

74 'option': 0, 

75 'valueType': 'int'}, 

76 'FEED_ID': {'comment': 'Feed identifier', 

77 'dataManagerGroup': 'StandardStMan', 

78 'dataManagerType': 'StandardStMan', 

79 'keywords': {}, 

80 'maxlen': 0, 

81 'option': 0, 

82 'valueType': 'int'}, 

83 'GAIN': {'comment': 'Gain polynomial', 

84 'dataManagerGroup': 'StandardStMan', 

85 'dataManagerType': 'StandardStMan', 

86 'keywords': {}, 

87 'maxlen': 0, 

88 'ndim': -1, 

89 'option': 0, 

90 'valueType': 'float'}, 

91 'INTERVAL': {'comment': 'Interval for which this set of parameters is accurate', 

92 'dataManagerGroup': 'StandardStMan', 

93 'dataManagerType': 'StandardStMan', 

94 'keywords': {'QuantumUnits': np.array(['s'], dtype='<U1')}, 

95 'maxlen': 0, 

96 'option': 0, 

97 'valueType': 'double'}, 

98 'NUM_POLY': {'comment': 'Number of terms in polynomial', 

99 'dataManagerGroup': 'StandardStMan', 

100 'dataManagerType': 'StandardStMan', 

101 'keywords': {}, 

102 'maxlen': 0, 

103 'option': 0, 

104 'valueType': 'int'}, 

105 'SENSITIVITY': {'comment': 'Antenna sensitivity', 

106 'dataManagerGroup': 'StandardStMan', 

107 'dataManagerType': 'StandardStMan', 

108 'keywords': {'QuantumUnits': np.array(['K/Jy'], dtype='<U4')}, 

109 'maxlen': 0, 

110 'ndim': -1, 

111 'option': 0, 

112 'valueType': 'float'}, 

113 'SPECTRAL_WINDOW_ID': {'comment': 'Spectral window identifier', 

114 'dataManagerGroup': 'StandardStMan', 

115 'dataManagerType': 'StandardStMan', 

116 'keywords': {}, 

117 'maxlen': 0, 

118 'option': 0, 

119 'valueType': 'int'}, 

120 'TIME': {'comment': 'Midpoint of time for which this set of parameters is accurate', 

121 'dataManagerGroup': 'StandardStMan', 

122 'dataManagerType': 'StandardStMan', 

123 'keywords': {'MEASINFO': {'Ref': 'UTC', 'type': 'epoch'}, 

124 'QuantumUnits': np.array(['s'], dtype='<U1')}, 

125 'maxlen': 0, 

126 'option': 0, 

127 'valueType': 'double'}, 

128 'TYPE': {'comment': 'Gain curve type', 

129 'dataManagerGroup': 'StandardStMan', 

130 'dataManagerType': 'StandardStMan', 

131 'keywords': {}, 

132 'maxlen': 0, 

133 'option': 0, 

134 'valueType': 'string'}, 

135 '_define_hypercolumn_': {}, 

136 '_keywords_': {}, 

137 '_private_keywords_': {}} 

138dminfo_gc = {'*1': {'COLUMNS': np.array(['ANTENNA_ID', 'FEED_ID', 'GAIN', 'INTERVAL', 'NUM_POLY', 

139 'SENSITIVITY', 'SPECTRAL_WINDOW_ID', 'TIME', 'TYPE'], dtype='<U18'), 

140 'NAME': 'StandardStMan', 

141 'SEQNR': 0, 

142 'SPEC': {'BUCKETSIZE': 1920, 

143 'IndexLength': 142, 

144 'MaxCacheSize': 2, 

145 'PERSCACHESIZE': 2}, 

146 'TYPE': 'StandardStMan'}} 

147 

148################################################################################################# 

149# Main task 

150def appendantab(vis=None, outvis=None, antab=None, 

151 overwrite=False, append_tsys=True, append_gc=True): 

152 

153 # disallow writing to the input vis 

154 if vis == outvis: 

155 print("Please provide a path for outvis different to the input vis") 

156 return 

157 

158 # Require an outvis to be specified 

159 if not outvis: 

160 print('Please provide an outvis name to write to') 

161 return 

162 

163 # get info from vis 

164 msmd.open(vis) 

165 ant_names = msmd.antennastations() 

166 n_band = len(msmd.bandwidths()) 

167 spws = msmd.spwfordatadesc() 

168 msmd.close() 

169 

170 # get start time and end time from ms 

171 tb.open(vis) 

172 times = tb.getcol('TIME') 

173 first_time = times[0] 

174 last_time = times[-1] 

175 tb.close() 

176 

177 # Check if the outvis already exists 

178 if os.path.exists(outvis): 

179 print("File ", outvis, " already exists, change outvis name") 

180 return 

181 # run the antab parsing and table filling 

182 shutil.copytree(vis, outvis) 

183 interpreter = AntabInterp(vis, outvis, antab, ant_names, n_band, spws, 

184 first_time, last_time, 

185 append_tsys, append_gc, overwrite) 

186 #antab_interp(vis, outvis, antab, ant_names, n_band, spws, 

187 # first_time, last_time, 

188 # append_tsys, append_gc, overwrite) 

189 interpreter.antab_interp() 

190 

191 

192 

193################################################################################################# 

194# scanner regex from casavlbitools 

195def s_err(scanner, error): 

196 raise RuntimeError("line: %d" % scanner.line_no) 

197 

198def s_keyword(scanner, token): 

199 if len(token) > 9 or '.' in token: 

200 res = ('value', token) 

201 else: 

202 res = ('key', token.upper()) 

203 return res 

204 

205def s_newline(scanner, token): 

206 scanner.line_no += 1 

207 return None 

208 

209def s_quote(scanner, token): 

210 return ('quote', token[1:-1]) 

211 

212def s_number(scanner, token): 

213 return ('number', float(token)) 

214 

215def s_angle(scanner, token): # also time 

216 l = token.split(":") 

217 ## It's not python to use reduce. 

218 ## But I neither remember nor care what is. 

219 val = reduce(lambda acc, x: 60*acc+math.copysign(acc, float(x)), l, 0.0) 

220 return ('number', val) 

221 

222def s_value(scanner, token): 

223 return ('value', token) 

224 

225def s_string(scanner, token): 

226 return ('value', token) 

227 

228def s_misc(scanner, token): 

229 logging.debug("Misc token %s at line %d" % (str(token), scanner.line_no)) 

230 return ('misc', token) 

231 

232def s_comment(scanner, token): # was only used for debugging. 

233 scanner.line_no += 1 

234 return ('comment', token) 

235 

236scanner = re.Scanner([ 

237 ("\!.*\n", s_newline), 

238 ("[ \t]+", None), 

239 ("\n", s_newline), 

240 ("\r\n", s_newline), 

241 ("=", lambda s, t: ('equal',)), 

242 ("'[^'\n]*'", s_quote), 

243 ("\"[^'\n]*\"", s_quote), # Sigh. Double quotes used in freq.dat 

244 ("/", lambda s, t: ('end_chunk',)), 

245 (",", lambda s, t: ('comma',)), 

246 ("[+-]?[0-9]+:[0-9]+:[0-9]+(.[0-9]*)?", s_angle), 

247 ("[+-]?[0-9]+:[0-9]+(.[0-9]*)?", s_angle), 

248 ("[+-]?[0-9]*\.[0-9]+([Ee][+-][0-9]{1,3})?(?![A-Za-z_0-9()])", s_number), 

249 ("[+-]?[0-9]+\.?(?![A-Za-z_0-9()])", s_number), 

250 ## apparently parens and unquoted minus signs are allowed in keywords? 

251 ("[A-Za-z.0-9]([()A-Za-z_0-9._+-]+)?", s_keyword), 

252 (".*", s_misc) 

253]) 

254 

255def p_all(): 

256 global tok 

257 chunks = [] 

258 try: 

259 while True: 

260 if tok=='EOF':break 

261 chunks.append(p_chunk()) 

262 except StopIteration: 

263 pass 

264 return chunks 

265 

266def p_chunk(): 

267 global tok 

268 entries = [] 

269 while tok[0] != 'end_chunk': 

270 entries.append(p_item()) 

271 logging.debug("p_chunk %s", str(tok)) 

272 tok = next(tokIt) 

273 return entries 

274 

275def p_item(): 

276 global tok 

277 lhs = p_key() 

278 

279 if tok==('equal',): 

280 logging.debug("p_item %s", str(tok)) 

281 tok = next(tokIt) 

282 rhs = p_rhs() 

283 elif tok[0] in ['value', 'quote', 'number']: 

284 rhs = p_rhs() 

285 else: 

286 rhs = True # for unitary expressions. 

287 return (lhs, rhs) 

288 

289def p_key(): 

290 global tok 

291 logging.debug("p_key: %s", str(tok)) 

292 if tok[0] == 'key': 

293 res = tok 

294 tok = next(tokIt) 

295 else: 

296 raise RuntimeError("Expected key token, got %s" % str(tok)) 

297 return res[1] 

298 

299def p_rhs(): 

300 global tok 

301 val = p_value() 

302 rhs = [val] 

303 while tok == ('comma',): 

304 logging.debug("p_rhs: %s", str(tok)) 

305 tok = next(tokIt) 

306 rhs.append(p_value()) # p_value advances tok beyond the value. 

307 if len(rhs)==1: 

308 rhs = rhs[0] 

309 return rhs 

310 

311def p_value(): 

312 global tok 

313 if tok[0] not in ['value', 'quote', 'number', 'key']: 

314 raise RuntimeError("Unexpected RHS token %s" % str(tok)) 

315 val = tok 

316 logging.debug("p_value: %s", str(val)) 

317 tok = next(tokIt) 

318 return val[1] 

319 

320scanner.line_no = 0 

321################################################################################################# 

322# data table Classes 

323 

324class TSysTable: 

325 def __init__(self): 

326 self.time = [] 

327 self.time_interval = [] 

328 self.source_id = [] 

329 self.antenna_no = [] 

330 self.array = [] 

331 self.freqid = [] 

332 self.tsys_1 = [] 

333 self.tsys_2 = [] 

334 self.tant = [] 

335 return 

336 

337class GainCurveTable: 

338 def __init__(self): 

339 self.antenna_no = [] 

340 self.array = [] 

341 self.freqid = [] 

342 self.spwid = [] 

343 self.nterm = [] 

344 self.y_typ = [] 

345 self.gain = [] 

346 self.sens_1 = [] 

347 self.sens_2 = [] 

348 return 

349 

350################################################################## 

351 

352 

353def read_keyfile(f): 

354 global tok, tokIt 

355 scanner.line_no = 0 

356 

357 try: 

358 res = scanner.scan(f.read()) 

359 if res[1]!='': 

360 raise RuntimeError("Unparsed text: %s." % (res[1][:20])) 

361 tokIt = iter(res[0]+['EOF']) 

362 try: 

363 tok = next(tokIt) 

364 res = p_all() 

365 except StopIteration: # empty file 

366 res = '' 

367 except RuntimeError as txt: 

368 #print("line %d: %s" % (scanner.line_no, txt), file=sys.stderr) 

369 raise RuntimeError 

370 return res 

371 

372def update_map(pols, spws, spwmap, index): 

373 idx = 0 

374 if not isinstance(index, (list, tuple)): 

375 index = [index] 

376 pass 

377 for labels in index: 

378 for label in labels.split('|'): 

379 pol = label[0] 

380 rng = label[1:].split(':') 

381 if pol != 'X': 

382 if not pol in pols: 

383 pols.append(pol) 

384 pass 

385 if len(rng) == 1: 

386 rng.append(rng[0]) 

387 pass 

388 rng = [int(x) - 1 for x in rng] 

389 for spw in range(rng[0], rng[1] + 1): 

390 if not spw in spws: 

391 spws.append(spw) 

392 pass 

393 spwmap[(pol, spw)] = idx 

394 continue 

395 pass 

396 continue 

397 idx += 1 

398 continue 

399 spws = sorted(spws) 

400 return 

401 

402def find_antenna(keys, ignore): 

403 for key in keys[1:]: 

404 if not type(key[1]) is bool: 

405 continue 

406 if key[0] in ignore: 

407 continue 

408 return key[0] 

409 return None 

410 

411def skip_values(infp): 

412 for line in infp: 

413 if line.startswith('!'): 

414 continue 

415 if line.strip().endswith('/'): 

416 break 

417 continue 

418 return 

419 

420def get_antenna_index(antenna_name, ant_names): 

421 return ant_names.index(antenna_name) 

422 

423def mjd_to_date(mjd): 

424 # Method used in FITSDateUtil 

425 mjd_epoch = dt.datetime(1858, 11 , 17, 0, 0, 0) 

426 

427 mjd_int = int(mjd / 86400) 

428 delta = dt.timedelta(days=mjd_int) 

429 return mjd_epoch + delta 

430 

431def mjd_seconds(yy, mm, dd, d=0): 

432 if (mm < 3): 

433 yy -= 1 

434 mm += 12 

435 

436 dd += d 

437 b = 0 

438 

439 if (yy>1582 or (yy==1582 and (mm>10 or (mm==10 and dd >= 15)))): 

440 b = np.floor(yy/100.) 

441 b = 2 - b + int(b/4) 

442 

443 val = np.floor(365.25*yy) + np.floor(30.6001*(mm+1)) + dd - 679006.0 + b 

444 return val 

445 

446 

447def get_timetuple(ts): 

448 # ts as string with these possible formats: 

449 # hh.hh  

450 # hh:mm.mm  

451 # hh:mm:ss.ss 

452 # NOTE: Regexs below will match any number of decimals on the last quantity (e.g. 19.8222222 and 19.8 both work) 

453 if re.match(r"[0-9]{2}\.[0-9]+", ts): 

454 # hh.hh  

455 tm_hour = int(ts.split('.')[0]) 

456 tm_min = math.modf(60*float(ts.split('.')[1])) 

457 tm_sec = int(60 * tm_min[0]) 

458 tm_min = int(tm_min[1]) 

459 elif re.match(r"[0-9]{2}:[0-9]{2}\.[0-9]+", ts): 

460 # hh:mm.mm  

461 tm_hour = int(ts.split(':')[0]) 

462 tm_min = math.modf(float(ts.split(':')[1])) 

463 tm_sec = int(60 * tm_min[0]) 

464 tm_min = int(tm_min[1]) 

465 elif re.match(r"[0-9]{2}:[0-9]{2}:[0-9]{2}$", ts): 

466 # hh:mm:ss 

467 tm_hour = int(ts.split(':')[0]) 

468 tm_min = int(ts.split(':')[1]) 

469 tm_sec = int(ts.split(':')[2]) 

470 elif re.match(r"[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+", ts): 

471 # hh:mm:ss.ss 

472 tm_hour = int(ts.split(':')[0]) 

473 tm_min = int(ts.split(':')[1]) 

474 tm_sec = float(ts.split(':')[2]) 

475 return tm_hour, tm_min, tm_sec 

476 

477############## interp class ################### 

478 

479class AntabInterp: 

480 def __init__(self, vis, outvis, antab, 

481 ant_names, n_band, spws, 

482 first_time, last_time, 

483 append_tsys, append_gc, 

484 overwrite): 

485 

486 # Passed to class when constructing 

487 self.vis = vis 

488 self.outvis = outvis 

489 self.antab = antab 

490 self.ant_names = ant_names 

491 self.n_band = n_band 

492 self.first_time = first_time 

493 self.last_time = last_time 

494 self.spws = spws 

495 self.append_tsys = append_tsys 

496 self.append_gc = append_gc 

497 self.overwrite = overwrite 

498 

499 # used during procesing 

500 self.keys = None 

501 self.data = None 

502 self.data_gc = None 

503 self.pols = None 

504 self.spw_ranges = {} 

505 self.gain_keys = [ 'EQUAT', 'ALTAZ', 'ELEV', 'GCNRAO', 'TABLE', 'RCP', 'LCP' ] 

506 self.replacements = dict() 

507 

508 

509 # Core antab interp function 

510 def antab_interp(self): 

511 self.pols = [] 

512 self.data = TSysTable() 

513 self.data_gc = GainCurveTable() 

514 keys = StringIO() 

515 fp = open(self.antab, 'r') 

516 

517 for line in fp: 

518 #print(line) 

519 if line.startswith('!'): 

520 continue 

521 keys.write(line) 

522 if line.strip().endswith('/'): 

523 keys.seek(0) 

524 try: 

525 tsys = read_keyfile(keys) 

526 #print('TSYS: ', tsys) 

527 except RuntimeError: 

528 print("\n", keys.getvalue(), file=sys.stderr) 

529 raise RuntimeError('error parsing ANTAB file') 

530 if tsys and tsys[0] and tsys[0][0][0] == 'TSYS': 

531 self.process_tsys(fp, tsys) 

532 pass 

533 elif tsys and tsys[0] and tsys[0][0][0] == 'GAIN': 

534 #print('ENTER PROCESS GC') 

535 self.process_gc(fp, tsys) 

536 keys = StringIO() 

537 continue 

538 continue 

539 

540 # Create the subtable for syscal if selected in params 

541 if self.append_tsys: 

542 self.create_syscal_subtable() 

543 if self.append_gc: 

544 self.create_gaincurve_subtable() 

545 

546 def get_spw_ranges(self): 

547 # Get ranges for each spectral window 

548 try: 

549 tb.open(f'{self.vis}/SPECTRAL_WINDOW') 

550 for spw in self.spws: 

551 chan_freq = tb.getcol('CHAN_FREQ')[:,spw] 

552 min_freq = min(chan_freq) / (1e6) 

553 max_freq = max(chan_freq) / (1e6) 

554 self.spw_ranges[spw] = (min_freq, max_freq) 

555 tb.close() 

556 except Exception as e: 

557 print("ERROR FAILED TO GET SPW FREQ RANGES") 

558 

559 def check_which_spw(self, freq): 

560 in_spw = [] 

561 for spw, ranges in self.spw_ranges.items(): 

562 if ranges[0] >= freq[0] and ranges[1] <= freq[1]: 

563 in_spw.append(spw) 

564 # If there is no spw that this belongs to default to what we were setting this value to before 

565 if not in_spw: 

566 print("WARNING: NO CORRESPONDING SPW FOUND FOR FREQ {}".format(freq)) 

567 return [-1] 

568 else: 

569 return in_spw 

570 

571 def process_tsys(self, infp, keys): 

572 # Get the antenna name 

573 antenna_name = find_antenna(keys[0], ['SRC/SYS']) 

574 # Skip if no antenna name found 

575 if not antenna_name: 

576 print('ANTENNA missing from TSYS group') 

577 skip_values(infp) 

578 return 

579 try: 

580 antenna = get_antenna_index(antenna_name, self.ant_names) 

581 except: 

582 print('Antenna {0} not present in the Measurement Set. {0} values will be ignored.'.format(antenna_name)) 

583 skip_values(infp) 

584 return 

585 

586 try: 

587 self.get_spw_ranges() 

588 spw_ids = self.check_which_spw(keys['FREQ']) 

589 except Exception as e: 

590 spw_ids = [-1] 

591 print('ANTAB HAS NO FREQ INFORMATION DEFAULTING TO FREQ ID -1') 

592 

593 keys = dict(keys[0]) 

594 spws = [] 

595 spwmap = {} 

596 

597 if 'INDEX' in keys: 

598 update_map(self.pols, spws, spwmap, keys['INDEX']) 

599 if 'INDEX2' in keys: 

600 update_map(self.pols, spws, spwmap, keys['INDEX2']) 

601 pass 

602 if len(spws) != self.n_band: 

603 print('INDEX for antenna %s does not match FITS-IDI file' 

604 % antenna_name, file=sys.stderr) 

605 pass 

606 

607 spws = range(self.n_band) 

608 timeoff = 0 

609 if 'TIMEOFF' in keys: 

610 timeoff = float(keys['TIMEOFF']) 

611 

612 for line in infp: 

613 if line.startswith('!'): 

614 continue 

615 

616 fields = line.split() 

617 

618 if len(fields) > 1: 

619 tm_yday = int(fields[0]) 

620 # Get timestamp data depending on data format 

621 obs_date = mjd_to_date(self.first_time) 

622 tm_year = obs_date.year 

623 idi_time = tm.strptime(f"{obs_date.year}-{obs_date.month}-{obs_date.day}","%Y-%m-%d") 

624 idi_ref = tm.mktime(idi_time) 

625 

626 # Get the start time in sec for MJD using the method from the table filler 

627 mjd_sec = mjd_seconds(int(obs_date.year), int(obs_date.month), int(obs_date.day)) * 86400 

628 

629 tm_hour, tm_min, tm_sec = get_timetuple(fields[1]) 

630 days = (tm_hour*3600 + tm_min*60 + tm_sec) / 86400 

631 t = "%dy%03dd%02dh%02dm%02ds" % \ 

632 (tm_year, tm_yday, tm_hour, tm_min, tm_sec) 

633 t = tm.mktime(tm.strptime(t, "%Yy%jd%Hh%Mm%Ss")) 

634 days = (t + timeoff - idi_ref) / 86400 

635 

636 curr_time = mjd_sec + (days*86400) 

637 

638 source = 0 

639 if (days) > ((self.last_time-mjd_sec)/86400) or (days) < ((self.first_time-mjd_sec)/86400): 

640 source = -1 

641 

642 #print(deg - mjd_sec) 

643 values = fields[2:] 

644 tsys = {'R': [], 'L': []} 

645 for spw in spws: 

646 for pol in ['R', 'L']: 

647 try: 

648 value = float(values[spwmap[(pol, spw)]]) 

649 if value == 999.9: 

650 value = float(-999.9) 

651 pass 

652 except: 

653 value = float(-999.9) 

654 pass 

655 tsys[pol].append(value) 

656 continue 

657 continue 

658 if source != -1: 

659 time_val = mjd_sec + (days * 86400) 

660 self.data.time.append(time_val) 

661 self.data.time_interval.append(0.0) 

662 self.data.antenna_no.append(antenna) 

663 self.data.tsys_1.append(tsys['R']) 

664 self.data.tsys_2.append(tsys['L']) 

665 self.data.freqid.append(spw_ids) 

666 

667 pass 

668 pass 

669 

670 if line.strip().endswith('/'): 

671 break 

672 continue 

673 return 

674 

675 def process_gc(self, infp, keys): 

676 antenna_name = find_antenna(keys[0], self.gain_keys) 

677 if not antenna_name: 

678 print('Antenna missing from GAIN group') 

679 skip_values(infp) 

680 return 

681 try: 

682 antenna = get_antenna_index(antenna_name, self.ant_names) 

683 except: 

684 print('Antenna {0} not present in the Measurement Set. {0} values will be ignored.'.format(antenna_name)) 

685 skip_values(infp) 

686 return 

687 keys = dict(keys[0]) 

688 

689 try: 

690 freq = keys['FREQ'] 

691 self.get_spw_ranges() 

692 spw_ids = self.check_which_spw(keys['FREQ']) 

693 except Exception as e: 

694 spw_ids = [-1] 

695 freq = [0, 0] 

696 print('ANTAB HAS NO FREQ INFORMATION DEFAULTING TO SPW ID -1 and freq to [0, 0]') 

697 

698 dpfu = {} 

699 try: 

700 dpfu['R'] = keys['DPFU'][0] 

701 dpfu['L'] = keys['DPFU'][1] 

702 self.pols.append('R') 

703 self.pols.append('L') 

704 except: 

705 dpfu['R'] = dpfu['L'] = keys['DPFU'] 

706 self.pols.append('X') 

707 pass 

708 try: 

709 value = keys['POLY'][0] 

710 except: 

711 keys['POLY'] = [keys['POLY']] 

712 pass 

713 

714 y_typ = 0 

715 if 'ELEV' in keys: 

716 y_typ = 1 

717 elif 'EQUAT' in keys: 

718 y_typ = 1 

719 elif 'ALTAZ' in keys: 

720 y_typ = 2 

721 else: 

722 print('Unknown gain curve type for antenna %s' % antenna_name) 

723 return 

724 

725 poly = keys['POLY'] 

726 self.data_gc.antenna_no.append(antenna) 

727 self.data_gc.array.append(1) 

728 self.data_gc.freqid.append(freq) 

729 self.data_gc.spwid.append(spw_ids) 

730 self.data_gc.y_typ.append(y_typ) 

731 self.data_gc.nterm.append(len(poly)) 

732 self.data_gc.gain.append(poly) 

733 self.data_gc.sens_1.append(dpfu['R']) 

734 self.data_gc.sens_2.append(dpfu['L']) 

735 return 

736 

737 def create_existing_data_table(self): 

738 existing_data = dict() 

739 self.replacements = dict() 

740 if os.path.exists(f'{self.outvis}/GAIN_CURVE'): 

741 tb.open(f'{self.outvis}/GAIN_CURVE') 

742 antennas = tb.getcol('ANTENNA_ID') 

743 times = tb.getcol('TIME') 

744 spws = tb.getcol('SPECTRAL_WINDOW_ID') 

745 

746 # add a set of times for each antenna 

747 # add a list of rows and spws for each antenna 

748 for i in range(len(antennas)): 

749 if antennas[i] not in existing_data: 

750 existing_data[antennas[i]] = dict() 

751 existing_data[antennas[i]]['times'] = set() 

752 existing_data[antennas[i]]['rows'] = [] 

753 existing_data[antennas[i]]['spws'] = [] 

754 

755 existing_data[antennas[i]]['times'].add(times[i]) 

756 existing_data[antennas[i]]['rows'].append(i) 

757 existing_data[antennas[i]]['spws'].append(spws[i]) 

758 else: 

759 existing_data[antennas[i]]['times'].add(times[i]) 

760 existing_data[antennas[i]]['rows'].append(i) 

761 existing_data[antennas[i]]['spws'].append(spws[i]) 

762 

763 tb.close() 

764 

765 return existing_data 

766 

767 def create_gaincurve_subtable(self): 

768 # If no subtable exists add one 

769 if not os.path.exists(f'{self.outvis}/GAIN_CURVE'): 

770 # Create the subtable for gain curve 

771 tb.create(f'{self.outvis}/GAIN_CURVE', desc_gc, dminfo=dminfo_gc) 

772 tb.putkeyword('GAIN_CURVE', f'Table: {self.outvis}/GAIN_CURVE') 

773 

774 # assemble columns 

775 ANTENNA_ID = [] 

776 FEED_ID = [] 

777 SPECTRAL_WINDOW_ID = [] 

778 TIME = [] 

779 INTERVAL = [] 

780 TYPE = [] 

781 NUM_POLY = [] 

782 GAIN = [] 

783 SENSITIVITY = [[],[]] 

784 

785 # If a subtable exists then read contents into assembed data 

786 # Create data structure for antenna an time stamps to ignore if not overwriting 

787 existing_data = self.create_existing_data_table() 

788 

789 # try getting channel freq information 

790 tb.open(f'{self.vis}/SPECTRAL_WINDOW') 

791 chan_freqs = tb.getcol('CHAN_FREQ') 

792 tb.close() 

793 

794 # Get interval information from the observation table instead of meta-data 

795 tb.open(f'{self.outvis}/OBSERVATION') 

796 time_range = tb.getcol("TIME_RANGE") 

797 gc_interval = (time_range[1] - time_range[0])[0] 

798 gc_time = np.mean(time_range) 

799 tb.close() 

800 

801 # New Array filler 

802 for row in range(len(self.data_gc.gain)): 

803 # iterate over spw range for non-overlapping case 

804 for j in self.spws: 

805 # get antenna and freqency range from antab for this row 

806 ant = self.data_gc.antenna_no[row] 

807 freq_range = self.data_gc.freqid[row] 

808 

809 # If there is already data for that antenna and time and overwrite = False then skip 

810 if ant in existing_data and gc_time in existing_data[ant]['times'] and not self.overwrite: 

811 print("antenna: {} has data present for the time {}. The antab data for this value will be ignored".format(ant, gc_time)) 

812 continue 

813 

814 # If there is data present and overwrite is true then fill the replacement table 

815 elif ant in existing_data and gc_time in existing_data[ant]['times'] and self.overwrite: 

816 # Get all the relevant data to put in replacements 

817 rows = existing_data[ant]['rows'] 

818 spws = existing_data[ant]['spws'] 

819 # For each row and get chan freq of corrisponding spw 

820 for i in range(len(rows)): 

821 # r is the row that the data exists in the original GAIN_CURVE table 

822 r = rows[i] 

823 spw = spws[i] 

824 freq = chan_freqs[row, spw] / (1e6) 

825 # get the sensitivity from the proper freqency range 

826 if freq >= freq_range[0] and freq <= freq_range[1]: 

827 sens = [self.data_gc.sens_1[row], self.data_gc.sens_2[row]] 

828 else: 

829 continue 

830 

831 # Make an entry for row r in the replacments table 

832 # r is the row in the gaincurve table that will be replaced 

833 # row is the row in the antab where the data is present 

834 self.replacements[r] = {'ANTENNA_ID':ant, 'FEED_ID':-1, 'INTERVAL':gc_interval, 

835 'TYPE':self.data_gc.y_typ[row], 'NUM_POLY':self.data_gc.nterm[row], 'GAIN':self.data_gc.gain[row], 

836 'TIME':gc_time, 'SPECTRAL_WINDOW_ID':spw, 'SENSITIVITY':sens} 

837 continue 

838 

839 # If the data doesn't overlap with existing data append as normal 

840 else: 

841 TIME.append(gc_time) 

842 ANTENNA_ID.append(self.data_gc.antenna_no[row]) 

843 FEED_ID.append(-1) 

844 INTERVAL.append(gc_interval) 

845 TYPE.append(str(self.data_gc.y_typ[row])) 

846 NUM_POLY.append(self.data_gc.nterm[row]) 

847 GAIN.append(self.data_gc.gain[row]) 

848 SPECTRAL_WINDOW_ID.append(int(j)) 

849 SENSITIVITY[0].append(self.data_gc.sens_1[row]) 

850 SENSITIVITY[1].append(self.data_gc.sens_2[row]) 

851 

852 # If appending get existing cols and append values to it 

853 tb.open(f'{self.outvis}/GAIN_CURVE', nomodify=False) 

854 if not self.overwrite and tb.nrows() > 0: 

855 #tb.addrows(len(TIME)) 

856 TIME = np.append(tb.getcol('TIME'), TIME) 

857 ANTENNA_ID = np.append(tb.getcol('ANTENNA_ID'), ANTENNA_ID) 

858 FEED_ID = np.append(tb.getcol('FEED_ID'), FEED_ID) 

859 INTERVAL = np.append(tb.getcol('INTERVAL'), INTERVAL) 

860 TYPE = np.append(tb.getcol('TYPE'), TYPE) 

861 NUM_POLY = np.append(tb.getcol('NUM_POLY'), NUM_POLY) 

862 

863 # Can't call getcol on Gain since dimensions can vary 

864 tmp_GAIN = [] 

865 # TODO ISSUE: Should only apply the gains for certain frequency ranges? 

866 for row in range(tb.nrows()): 

867 tmp_GAIN.append(tb.getcell('GAIN', row)) 

868 # re-assembled the old and new gain col 

869 GAIN = tmp_GAIN + GAIN 

870 SPECTRAL_WINDOW_ID = np.append(tb.getcol('SPECTRAL_WINDOW_ID'), SPECTRAL_WINDOW_ID) 

871 tmp_0 = np.append(tb.getcol('SENSITIVITY')[0], SENSITIVITY[0]) 

872 tmp_1 = np.append(tb.getcol('SENSITIVITY')[1], SENSITIVITY[1]) 

873 SENSITIVITY = np.asarray([tmp_0, tmp_1]) 

874 

875 # If overwriting then go over the existing rows to overwrite 

876 if self.overwrite: 

877 TIME = np.append(tb.getcol('TIME'), TIME) 

878 ANTENNA_ID = np.append(tb.getcol('ANTENNA_ID'), ANTENNA_ID) 

879 FEED_ID = np.append(tb.getcol('FEED_ID'), FEED_ID) 

880 INTERVAL = np.append(tb.getcol('INTERVAL'), INTERVAL) 

881 TYPE = np.append(tb.getcol('TYPE'), TYPE) 

882 NUM_POLY = np.append(tb.getcol('NUM_POLY'), NUM_POLY) 

883 # Can't call getcol on Gain since dimensions can vary 

884 tmp_GAIN = [] 

885 # TODO ISSUE: Should only apply the gains for certain frequency ranges? 

886 for row in range(tb.nrows()): 

887 tmp_GAIN.append(tb.getcell('GAIN', row)) 

888 # re-assembled the old and new gain col 

889 GAIN = tmp_GAIN + GAIN 

890 SPECTRAL_WINDOW_ID = np.append(tb.getcol('SPECTRAL_WINDOW_ID'), SPECTRAL_WINDOW_ID) 

891 tmp_0 = np.append(tb.getcol('SENSITIVITY')[0], SENSITIVITY[0]) 

892 tmp_1 = np.append(tb.getcol('SENSITIVITY')[1], SENSITIVITY[1]) 

893 SENSITIVITY = np.asarray([tmp_0, tmp_1]) 

894 

895 for row, data in self.replacements.items(): 

896 TIME[row] = data['TIME'] 

897 ANTENNA_ID[row] = data['ANTENNA_ID'] 

898 FEED_ID[row] = data['FEED_ID'] 

899 INTERVAL[row] = data['INTERVAL'] 

900 TYPE[row] = data['TYPE'] 

901 NUM_POLY[row] = data['NUM_POLY'] 

902 GAIN[row] = data['GAIN'] 

903 SPECTRAL_WINDOW_ID[row] = data['SPECTRAL_WINDOW_ID'] 

904 SENSITIVITY[:, row] = [data['SENSITIVITY'][0], data['SENSITIVITY'][1]] 

905 

906 tb.close() 

907 

908 # Add the columns to the table 

909 tb.open(f'{self.outvis}/GAIN_CURVE', nomodify=False) 

910 to_add = len(TIME) - tb.nrows() 

911 tb.addrows(to_add) 

912 tb.putcol('TIME', TIME) 

913 tb.putcol('ANTENNA_ID', ANTENNA_ID) 

914 tb.putcol('FEED_ID', FEED_ID) 

915 tb.putcol('SPECTRAL_WINDOW_ID', SPECTRAL_WINDOW_ID) 

916 tb.putcol('INTERVAL', INTERVAL) 

917 # Type 1 POWER(EL) Type 2 POWER(ZA) 

918 tb.putcol('NUM_POLY', NUM_POLY) 

919 

920 #Fill gain row by row 

921 for i in range(len(GAIN)): 

922 # If the gain col shape doesn't match what the table filler does 

923 if len(np.shape(GAIN[i])) < 2: 

924 tb.putcell('GAIN', i, [GAIN[i],GAIN[i]]) 

925 else: 

926 tb.putcell('GAIN', i, GAIN[i]) 

927 

928 # Convert Type to proper string 

929 if TYPE[i] == '1': 

930 TYPE[i] = 'POWER(EL)' 

931 elif TYPE[i] == '2': 

932 TYPE[i] = 'POWER(ZA)' 

933 

934 tb.putcol('TYPE', TYPE) 

935 tb.putcol('SENSITIVITY', SENSITIVITY) 

936 #tb.flush() 

937 tb.close() 

938 

939 

940 def create_syscal_subtable(self): 

941 # If no subtable exists 

942 if not os.path.exists(f'{self.outvis}/SYSCAL'): 

943 # Create the subtable for syscal 

944 tb.create(f'{self.outvis}/SYSCAL', desc, dminfo=dminfo) 

945 tb.putkeyword('SYSCAL', f'Table: {self.outvis}/SYSCAL') 

946 

947 # Assemble columns 

948 ANTENNA_ID = [] 

949 FEED_ID = [] 

950 INTERVAL = [] 

951 SPW_ID = [] 

952 TIME = [] 

953 TSYS = [[],[]] 

954 self.data.tsys_1 = np.asarray(self.data.tsys_1) 

955 self.data.tsys_2 = np.asarray(self.data.tsys_2) 

956 

957 # If a subtable exists then read contents into assembed data 

958 # Create data structure for antenna an time stamps to ignore if not overwriting 

959 existing_data = self.create_existing_data_table() 

960 

961 # Fill the arrays 

962 to_skip = [] 

963 self.replacements = dict() 

964 for i in range(len(self.data.time)): 

965 # if the antenna has been visited already with the same time stamp then ignore 

966 if self.data.antenna_no[i] in existing_data and self.data.time[i] in existing_data[self.data.antenna_no[i]]['times'] and not self.overwrite: 

967 # This data is already in the subtable so ignore 

968 print("antenna: {} has data present for the time {}. The antab data for this value will be ignored".format(self.data.antenna_no[i], self.data.time[i])) 

969 to_skip.append(True) 

970 continue 

971 else: 

972 to_skip.append(False) 

973 

974 for j in self.spws: 

975 # get antenna from antab for this row 

976 ant = self.data.antenna_no[i] 

977 

978 # Check that this antenna is in this spectral window 

979 if j not in self.data.freqid[i] and self.data.freqid[i] != [-1]: 

980 continue 

981 if ant in existing_data and self.data.time[i] in existing_data[ant]['times'] and self.overwrite: 

982 # Get the row and information to overwrite the row from the ms 

983 # Get all the relevant data to put in replacements 

984 rows = existing_data[ant]['rows'] 

985 spws = existing_data[ant]['spws'] 

986 

987 for r in range(len(rows)): 

988 row = rows[r] 

989 spw = spws[r] 

990 

991 self.replacements[row] = {'ANTENNA':self.data.antenna_no[i], 'FEED_ID':0, 'INTERVAL':self.data.time_interval[i], 

992 'SPW_ID':spw, 'TIME':self.data.time[i], 'TSYS':[[],[]]} 

993 print(row, spw, int(j)) 

994 #row = existing_data[ant]['row'] 

995 to_skip.append(True) 

996 

997 #self.replacements[row] = {'ANTENNA':self.data.antenna_no[i], 'FEED_ID':0, 'INTERVAL':self.data.time_interval[i], 

998 # 'SPW_ID':int(j), 'TIME':self.data.time[i], 'TSYS':[[],[]]} 

999 

1000 continue 

1001 

1002 ANTENNA_ID.append(self.data.antenna_no[i]) 

1003 FEED_ID.append(0) 

1004 INTERVAL.append(self.data.time_interval[i]) 

1005 SPW_ID.append(int(j)) 

1006 TIME.append(self.data.time[i]) 

1007 

1008 for i in range(len(self.data.tsys_1)): 

1009 if to_skip[i] and not self.overwrite: 

1010 continue 

1011 elif to_skip[i] and self.overwrite: 

1012 self.replacements[i]['TSYS'][0].extend(self.data.tsys_1[i]) 

1013 self.replacements[i]['TSYS'][1].extend(self.data.tsys_2[i]) 

1014 continue 

1015 TSYS[0].extend(self.data.tsys_1[i]) 

1016 TSYS[1].extend(self.data.tsys_2[i]) 

1017 

1018 # If appending get existing cols and append values to it also check if empty 

1019 tb.open(f'{self.outvis}/SYSCAL', nomodify=False) 

1020 if not self.overwrite and tb.nrows() > 0: 

1021 TIME = np.append(tb.getcol('TIME'), TIME) 

1022 ANTENNA_ID = np.append(tb.getcol('ANTENNA_ID'), ANTENNA_ID) 

1023 INTERVAL = np.append(tb.getcol('INTERVAL'), INTERVAL) 

1024 SPW_ID = np.append(tb.getcol('SPECTRAL_WINDOW_ID'), SPW_ID) 

1025 tmp_0 = np.append(tb.getcol('TSYS')[0], TSYS[0]) 

1026 tmp_1 = np.append(tb.getcol('TSYS')[1], TSYS[1]) 

1027 TSYS = np.asarray([tmp_0, tmp_1]) 

1028 

1029 # If overwriting then go over the existing rows to overwrite 

1030 if self.overwrite: 

1031 for row, data in self.replacements.items(): 

1032 TIME[row] = data['TIME'] 

1033 ANTENNA_ID[row] = data['ANTENNA'] 

1034 INTERVAL[row] = data['INTERVAL'] 

1035 SPW_ID[row] = data['SPW_ID'] 

1036 #print(TSYS[0][row], TSYS[1][row]) 

1037 TSYS[0][row] = data['TSYS'][0] 

1038 TSYS[1][row] = data['TSYS'][1] 

1039 tb.close() 

1040 

1041 # Add the columns to the table 

1042 tb.open(f'{self.outvis}/SYSCAL', nomodify=False) 

1043 to_add = len(TIME) - tb.nrows() 

1044 tb.addrows(to_add) 

1045 tb.putcol('TIME', TIME) 

1046 tb.putcol('ANTENNA_ID', ANTENNA_ID) 

1047 tb.putcol('INTERVAL', INTERVAL) 

1048 tb.putcol('SPECTRAL_WINDOW_ID', SPW_ID) 

1049 tb.putcol('TSYS', TSYS) 

1050 #tb.flush() 

1051 tb.close()