Coverage for /wheeldirectory/casa-6.7.0-12-py3.10.el8/lib/py/lib/python3.10/site-packages/casatasks/private/jyperk.py: 79%

381 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-31 19:10 +0000

1import collections 

2import csv 

3import json 

4import os 

5import re 

6from socket import timeout as socket_timeout 

7import ssl 

8import string 

9from time import sleep 

10from urllib.error import HTTPError, URLError 

11from urllib.parse import urlencode 

12from urllib.request import urlopen 

13 

14import certifi 

15import numpy as np 

16 

17from casatasks import casalog 

18from casatasks.private.sdutil import (table_manager, table_selector, 

19 tool_manager) 

20from casatools import measures 

21from casatools import ms as mstool 

22from casatools import msmetadata, quanta 

23 

24 

25# Jy/K DB part 

26def gen_factor_via_web_api(vis, spw='*', 

27 endpoint='asdm', 

28 timeout=180, retry=3, retry_wait_time=5): 

29 """Generate factors via Jy/K Web API. 

30 

31 This function will be used task_gencal. 

32 

33 Arguments: 

34 vis {str} -- The file path of the visibility data. 

35 spw {str} -- Spectral windows. 

36 endpoint {str} -- The endpoint of Jy/K DB Web API to access. Options are 

37 'asdm' (default), 'model-fit', 'interpolation'. 

38 timeout {int} -- Maximum waiting time [sec] for the Web API access, defaults 

39 to 180 sec. 

40 retry {int} -- Number of retry when the Web API access fails, defaults to 3 

41 times. 

42 retry_wait_time {int} -- Waiting time [sec] until next query when the Web API 

43 access fails, defaults to 5 sec. 

44 """ 

45 if spw == '': 

46 spw = '*' 

47 

48 assert endpoint in ['asdm', 'model-fit', 'interpolation'], \ 

49 'The JyPerKDatabaseClient class requires one of endpoint: asdm, model-fit or interpolation' 

50 

51 return __factor_creator_via_jy_per_k_db(endpoint=endpoint, vis=vis, spw=spw, 

52 factory=__jyperk_factory[endpoint], 

53 timeout=timeout, retry=retry, 

54 retry_wait_time=retry_wait_time) 

55 

56 

57def __factor_creator_via_jy_per_k_db(endpoint='', vis=None, spw='*', 

58 factory=None, 

59 timeout=180, retry=3, retry_wait_time=5): 

60 params_generator = factory[0] 

61 response_translator = factory[1] 

62 

63 params = params_generator.get_params(vis, spw=spw) 

64 client = JyPerKDatabaseClient(endpoint, 

65 timeout=timeout, retry=retry, 

66 retry_wait_time=retry_wait_time) 

67 manager = RequestsManager(client) 

68 resps = manager.get(params) 

69 return response_translator.convert(resps, vis, spw=spw) 

70 

71 

72QueryStruct = collections.namedtuple('QueryStruct', ['param', 'subparam']) 

73ResponseStruct = collections.namedtuple('ResponseStruct', ['response', 'subparam']) 

74 

75 

76class ASDMParamsGenerator(): 

77 """A class to generate required parameters for Jy/K Web API with asdm. 

78 

79 Usage: 

80 vis = "./uid___A002_X85c183_X36f.ms" 

81 params = ASDMParamsGenerator.get_params(vis) 

82 """ 

83 

84 @classmethod 

85 def get_params(cls, vis, spw=None): 

86 """Generate required parameters for Jy/K Web API. 

87 

88 Arguments: 

89 vis {str} -- The file path of the visibility data. 

90 spw {str} -- This parameter is not used. It is provided to align the 

91 calling method with other classes (InterpolationParamsGenerator and 

92 ModelFitParamsGenerator). 

93 

94 Returns: 

95 Generator Object -- yield QueryStruct() object. A sample like below. 

96 QueryStruct( 

97 param={'uid': 'uid://A002/X85c183/X36f'}, 

98 subparam='./uid___A002_X85c183_X36f.ms' 

99 ) 

100 """ 

101 yield QueryStruct(param={'uid': cls._vis_to_uid(vis)}, subparam=vis) 

102 

103 @staticmethod 

104 def _vis_to_uid(vis): 

105 """Convert MS name like 'uid___A002_Xabcd_X012 into uid://A002/Xabcd/X012'. 

106 

107 Arguments: 

108 vis {str} -- The file path of the visibility data. 

109 

110 Returns: 

111 str -- Corresponding ASDM uid. 

112 """ 

113 basename = os.path.basename(os.path.abspath(vis)) 

114 pattern = r'^uid___A[0-9][0-9][0-9]_X[0-9a-f]+_X[0-9a-f]+\.ms$' 

115 if re.match(pattern, basename): 

116 return basename.replace('___', '://').replace('_', '/').replace('.ms', '') 

117 else: 

118 raise RuntimeError('MS name is not appropriate for DB query: {}'.format(basename)) 

119 

120 

121class InterpolationParamsGenerator(): 

122 """A class to generate required parameters for Jy/K Web API with interpolation. 

123 

124 Usage: 

125 vis = './uid___A002_X85c183_X36f.ms' 

126 params = InterpolationParamsGenerator.get_params(vis) 

127 """ 

128 

129 @classmethod 

130 def get_params(cls, vis, spw='*'): 

131 if spw == '': 

132 spw = '*' 

133 

134 if spw == '*': 

135 spw = cls._get_available_spw(vis, spw) 

136 

137 params = {} 

138 

139 science_windows = cls._get_science_windows(vis, spw) 

140 timerange, antenna_names, basebands, mean_freqs, spwnames = \ 

141 cls._extract_msmetadata(science_windows, vis) 

142 

143 mean_freqs = cls._get_mean_freqs(vis, science_windows) 

144 bands = Bands.get(science_windows, spwnames, mean_freqs, vis) 

145 

146 params['date'] = cls._mjd_to_datestring(timerange['begin']) 

147 params['temperature'] = cls._get_mean_temperature(vis) 

148 params.update(cls._get_aux_params()) 

149 

150 for antenna_id, antenna_name in enumerate(antenna_names): 

151 params['antenna'] = antenna_name 

152 params['elevation'] = MeanElevation.get(vis, antenna_id) 

153 

154 for sw_id in science_windows: 

155 params['band'] = bands[sw_id] 

156 params['baseband'] = basebands[sw_id] 

157 params['frequency'] = mean_freqs[sw_id] 

158 subparam = {'vis': vis, 'spwid': int(sw_id)} 

159 yield QueryStruct(param=params, subparam=subparam) 

160 

161 @staticmethod 

162 def _get_science_windows(vis, spw): 

163 ms = mstool() 

164 selected = ms.msseltoindex(vis, spw=spw) 

165 science_windows = selected['spw'] 

166 return science_windows 

167 

168 @staticmethod 

169 def _extract_msmetadata(science_windows, vis): 

170 with tool_manager(vis, msmetadata) as msmd: 

171 timerange = msmd.timerangeforobs(0) 

172 antenna_names = msmd.antennanames() 

173 basebands = dict((i, msmd.baseband(i)) for i in science_windows) 

174 mean_freqs = dict((i, msmd.meanfreq(i)) for i in science_windows) 

175 spwnames = msmd.namesforspws(science_windows) 

176 

177 return timerange, antenna_names, basebands, mean_freqs, spwnames 

178 

179 @staticmethod 

180 def _get_available_spw(vis, spw): 

181 science_windows = InterpolationParamsGenerator._get_science_windows(vis, spw=spw) 

182 with tool_manager(vis, msmetadata) as msmd: 

183 spwnames = msmd.namesforspws(science_windows) 

184 

185 spw = ','.join( 

186 map(str, [i for i, name in enumerate(spwnames) 

187 if not name.startswith('WVR')])) 

188 return spw 

189 

190 @staticmethod 

191 def _mjd_to_datestring(epoch): 

192 me = measures() 

193 qa = quanta() 

194 

195 if epoch['refer'] != 'UTC': 

196 try: 

197 epoch = me.measure(epoch, 'UTC') 

198 finally: 

199 me.done() 

200 

201 datestring = qa.time(epoch['m0'], form='fits') 

202 return datestring[0] 

203 

204 @staticmethod 

205 def _get_mean_temperature(vis): 

206 with table_manager(os.path.join(vis, 'WEATHER')) as tb: 

207 valid_temperatures = np.ma.masked_array( 

208 tb.getcol("TEMPERATURE"), 

209 tb.getcol("TEMPERATURE_FLAG") 

210 ) 

211 return valid_temperatures.mean() 

212 

213 @staticmethod 

214 def _get_mean_freqs(vis, science_windows): 

215 with table_manager(os.path.join(vis, 'SPECTRAL_WINDOW')) as tb: 

216 mean_freqs = dict((i, tb.getcell('CHAN_FREQ', i).mean()) for i in science_windows) 

217 return mean_freqs 

218 

219 @staticmethod 

220 def _get_aux_params(): 

221 return {'delta_days': 1000} 

222 

223 

224class ModelFitParamsGenerator(InterpolationParamsGenerator): 

225 """A class to generate required parameters for Jy/K Web API with model-fit.""" 

226 

227 @staticmethod 

228 def _get_aux_params(): 

229 return {} 

230 

231 

232class Bands(): 

233 """A class to extract all bands corresponding from VIS file. 

234 

235 Usage: 

236 bands = Bands.get(science_windows, spwnames, mean_freqs, vis) 

237 """ 

238 

239 @classmethod 

240 def get(cls, science_windows, spwnames, mean_freqs, vis): 

241 """Return all bands corresponding to the 'science_window' given in the input. 

242 

243 First the method scan 'spwnames', if the band can be detect, the method will 

244 adopt this value. In other case, the method compare the freq with the 'mean_freqs' 

245 at which the band was detect, the method detect the band from the frequencies 

246 that are closest to the result. 

247 """ 

248 bands = cls._extract_bands_from_spwnames(science_windows, spwnames) 

249 mean_freqs_with_undetected_band = cls._filter_mean_freqs_with_undetected_band( 

250 science_windows, spwnames, mean_freqs) 

251 if len(mean_freqs_with_undetected_band) > 0: 

252 bands.update( 

253 cls._detect_bands_from_mean_freqs(mean_freqs_with_undetected_band, vis) 

254 ) 

255 return bands 

256 

257 @staticmethod 

258 def _extract_bands_from_spwnames(science_windows, spwnames): 

259 """Extract bands that contain band information in the spwname. 

260 

261 The spwnames is like 'X835577456#ALMA_RB_06#BB_2#SW-01#CH_AVG'. 

262 """ 

263 bands = {} 

264 for sw, spwname in zip(science_windows, spwnames): 

265 if 'ALMA_RB_' in spwname: 

266 bands[sw] = int(re.findall(r'^.*?ALMA_RB_(\d+)#.*', spwname)[0]) 

267 return bands 

268 

269 @staticmethod 

270 def _filter_mean_freqs_with_undetected_band(science_windows, spwnames, mean_freqs): 

271 """Filter mean freqs without 'ALMA_RB_'.""" 

272 filtered_mean_freqs = {} 

273 for sw, spwname in zip(science_windows, spwnames): 

274 if 'ALMA_RB_' not in spwname: 

275 filtered_mean_freqs[sw] = mean_freqs[sw] 

276 return filtered_mean_freqs 

277 

278 @staticmethod 

279 def _detect_bands_from_mean_freqs(target_mean_freqs, vis): 

280 """Extract bands using the mean freqs. 

281 

282 Params: 

283 target_mean_freqs {dict} -- The mean freqs which does not been detected the bands. 

284 vis {str} -- The file path of the visibility data. 

285 """ 

286 known_bands = Bands._get_known_bands(vis) 

287 science_windows = list(known_bands.keys()) 

288 mean_freqs = Bands._extract_mean_freqs(science_windows, vis) 

289 

290 extracted_bands = {} 

291 for spw, target_mean_freq in target_mean_freqs.items(): 

292 nearest_spw = Bands._calc_nearest_spw(mean_freqs, target_mean_freq) 

293 extracted_bands[spw] = known_bands[nearest_spw] 

294 return extracted_bands 

295 

296 @staticmethod 

297 def _calc_nearest_spw(available_mean_freqs, mean_freq): 

298 available_mean_freqs_list = list(available_mean_freqs.values()) 

299 available_spw = list(available_mean_freqs.keys()) 

300 nearest_i = np.argmin(np.abs(np.array(available_mean_freqs_list) - mean_freq)) 

301 return available_spw[nearest_i] 

302 

303 @staticmethod 

304 def _get_spwnames(vis, science_windows): 

305 with tool_manager(vis, msmetadata) as msmd: 

306 spwnames = msmd.namesforspws(science_windows) 

307 return spwnames 

308 

309 @staticmethod 

310 def _get_known_bands(vis): 

311 science_windows = Bands._get_all_science_windows(vis) 

312 with tool_manager(vis, msmetadata) as msmd: 

313 spwnames = msmd.namesforspws(science_windows) 

314 bands = Bands._extract_bands_from_spwnames(science_windows, spwnames) 

315 return bands 

316 

317 @staticmethod 

318 def _get_all_science_windows(vis): 

319 ms = mstool() 

320 selected = ms.msseltoindex(vis, spw='*') 

321 science_windows = selected['spw'] 

322 return science_windows 

323 

324 @staticmethod 

325 def _extract_mean_freqs(science_windows, vis): 

326 with tool_manager(vis, msmetadata) as msmd: 

327 mean_freqs = dict((i, msmd.meanfreq(i)) for i in science_windows) 

328 return mean_freqs 

329 

330 

331class MeanElevation(): 

332 """A class to extract elevations from the VIS file and calcurate elevations average. 

333 

334 Usage: 

335 mean_elevation = MeanElevation.get(vis, antenna_id) 

336 """ 

337 

338 @classmethod 

339 def get(cls, vis, antenna_id): 

340 """Get elevations aveage.""" 

341 stateid = cls._get_stateid(vis) 

342 science_dd = cls._get_science_dd(vis) 

343 rows = cls._query_rows(vis, science_dd, stateid.tolist(), antenna_id) 

344 

345 return cls._calc_elevation_mean(rows, vis) 

346 

347 @staticmethod 

348 def _get_stateid(vis): 

349 with tool_manager(vis, mstool) as ms: 

350 ms.msselect({'scanintent': 'OBSERVE_TARGET#ON_SOURCE'}) 

351 selected = ms.msselectedindices() 

352 

353 stateid = selected['stateid'] 

354 return stateid 

355 

356 @staticmethod 

357 def _get_science_dd(vis): 

358 with tool_manager(vis, msmetadata) as msmd: 

359 science_spw = list(np.intersect1d( 

360 msmd.almaspws(tdm=True, fdm=True), 

361 msmd.spwsforintent('OBSERVE_TARGET#ON_SOURCE') 

362 )) 

363 science_dd = [msmd.datadescids(spw=i)[0] for i in science_spw] 

364 

365 return science_dd 

366 

367 @staticmethod 

368 def _query_rows(vis, science_dd, stateid, antenna_id): 

369 query = f'ANTENNA1=={antenna_id}&&ANTENNA2=={antenna_id}&&DATA_DESC_ID=={science_dd[0]}' + \ 

370 f'&&STATE_ID IN {list(stateid)}' 

371 with table_selector(vis, query) as tb: 

372 rows = tb.rownumbers() 

373 

374 return rows 

375 

376 @staticmethod 

377 def _calc_elevation_mean(rows, vis): 

378 elevations = [] 

379 qa = quanta() 

380 

381 with tool_manager(vis, msmetadata) as msmd: 

382 for row in rows: 

383 dirs = msmd.pointingdirection(row, initialrow=row) 

384 assert dirs['antenna1']['pointingdirection']['refer'].startswith('AZEL') 

385 el_deg = qa.convert(dirs['antenna1']['pointingdirection']['m1'], 'deg') 

386 elevations.append(el_deg['value']) 

387 

388 elevations = np.asarray(elevations) 

389 

390 if len(elevations) == 0: 

391 elevation_mean = np.nan 

392 else: 

393 elevation_mean = elevations.mean() 

394 

395 return elevation_mean 

396 

397 

398class RequestsManager(): 

399 """A class to manage the Jy/K Database access by the param. 

400 

401 Usage: 

402 vis = "./uid___A002_Xb32033_X9067.ms" 

403 client = JyPerKDatabaseClient('asdm') 

404 params = ASDMParamsGenerator.get_params(vis) 

405 manager = RequestsManager(client) 

406 manager.get(params) 

407 """ 

408 

409 def __init__(self, client): 

410 """Set client.""" 

411 self.client = client 

412 

413 def get(self, params): 

414 """Get the responses of the Jy/K DB.""" 

415 dataset = [ 

416 {'response': self.client.get(param.param), 'aux': param.subparam} 

417 for param in params] 

418 return self._filter_success_is_true(dataset) 

419 

420 def _filter_success_is_true(self, dataset): 

421 return [data for data in dataset if data['response']['success']] 

422 

423 

424class JyPerKDatabaseClient(): 

425 """A class to get values from Jy/K Web API. 

426 

427 The Jy/K Web API address is 'https://asa.alma.cl/science/jy-kelvins'. The address 

428 can be changed with the environment variable 'JYPERKDB_URL'. 

429 """ 

430 

431 BASE_URL = os.getenv('JYPERKDB_URL', 'https://asa.alma.cl/science/jy-kelvins') 

432 

433 def __init__(self, endpoint, timeout=180, retry=3, retry_wait_time=5): 

434 """Set the parameters to be used when accessing the Web API. 

435 

436 Arguments: 

437 endpoint {str} -- The endpoint of Jy/K DB Web API to access. Options are 

438 'asdm' (default), 'model-fit', 'interpolation'. 

439 timeout {int} --- Maximum waiting time [sec] for the Web API access, defaults 

440 to 180 sec. 

441 retry {int} -- Number of retry when the Web API access fails, defaults to 3 

442 times. 

443 retry_wait_time {int} -- Waiting time [sec] until next query when the Web API 

444 access fails, defaults to 5 sec. 

445 """ 

446 assert endpoint in ['asdm', 'model-fit', 'interpolation'], \ 

447 'The JyPerKDatabaseClient class requires one of endpoint: ' \ 

448 'asdm, model-fit or interpolation' 

449 self.web_api_url = self._generate_web_api_url(endpoint) 

450 self.timeout = timeout 

451 self.retry = retry 

452 self.retry_wait_time = retry_wait_time 

453 

454 def get(self, param): 

455 """Get the Web API response. 

456 

457 Arguments: 

458 param {dict} -- The parameters used in the Web API. 

459 """ 

460 request_url = self._generate_url(param) 

461 body = self._try_to_get_response(request_url) 

462 retval = self._convert_to_json(body) 

463 self._check_retval(retval) 

464 return retval 

465 

466 def _generate_web_api_url(self, endpoint_type): 

467 web_api_url = '/'.join([self.BASE_URL, endpoint_type]) 

468 if not web_api_url.endswith('/'): 

469 web_api_url += '/' 

470 return web_api_url 

471 

472 def _generate_url(self, param): 

473 # encode params 

474 encoded = urlencode(param) 

475 query = '?'.join([self.web_api_url, encoded]) 

476 return query 

477 

478 def _retrieve(self, url): 

479 """Access to Jy/K DB and return response. 

480 

481 Arguments: 

482 url {str} -- url to retrieve in the Jy/K Web API. 

483 

484 Returns: 

485 dict -- If the request is successfull, dictionary contain below information. 

486 success {bool} -- Boolean stating whether the request succeeded or not. 

487 timestamp {str} -- Timestamp of when the request was received by the Jy/K Service. 

488 elapsed {bool} -- Boolean stating whether the request succeeded or not. 

489 error {dict} -- Error field which has a message describing the problem. 

490 query {dict} -- Dictionary with all the parameters passed to the request. 

491 data {dict} -- Data field which varies for each endpoint. 

492 

493 Please check the following source for details. 

494 https://confluence.alma.cl/pages/viewpage.action?pageId=35258466 

495 """ 

496 try: 

497 ssl_context = ssl.create_default_context(cafile=certifi.where()) 

498 with urlopen(url, context=ssl_context, timeout=self.timeout) as resp: 

499 body = resp.read() 

500 return {'status': 'Success', 'err_msg': None, 'body': body} 

501 except HTTPError as e: # 4xx, 5xx 

502 msg = 'Failed to load URL: {0}\n'.format(url) \ 

503 + 'Error Message: HTTPError(code={0}, Reason="{1}")\n'.format(e.code, e.reason) 

504 casalog.post(msg) 

505 return {'status': 'HTTPError', 'err_msg': msg} 

506 except URLError as e: # not connect 

507 msg = 'Failed to load URL: {0}\n'.format(url) \ 

508 + 'Error Message: URLError(Reason="{0}")\n'.format(e.reason) 

509 casalog.post(msg) 

510 return {'status': 'URLError', 'err_msg': msg} 

511 except socket_timeout as e: # not connect 

512 msg = 'Failed to load URL: {0}\n'.format(url) \ 

513 + 'Error Message: URLError(Reason="{0}")\n'.format(e) 

514 casalog.post(msg) 

515 return {'status': 'URLError', 'err_msg': msg} 

516 

517 def _try_to_get_response(self, url): 

518 casalog.post(f'Accessing Jy/K DB: request URL is "{url}"') 

519 for i in range(self.retry): 

520 response_with_tag = self._retrieve(url) 

521 if response_with_tag['status'] == 'Success': 

522 casalog.post('Got a response successfully') 

523 return response_with_tag['body'] 

524 

525 if i < self.retry - 1: 

526 casalog.post(response_with_tag['err_msg']) 

527 casalog.post( 

528 f'Sleeping for {str(self.retry_wait_time)} seconds because the request failed') 

529 sleep(self.retry_wait_time) 

530 casalog.post(f'Retry to access Jy/K DB ({str(i + 2)}/{str(self.retry)})') 

531 

532 if response_with_tag['status'] != 'Success': 

533 raise RuntimeError(response_with_tag['err_msg']) 

534 

535 def _convert_to_json(self, response): 

536 try: 

537 return json.loads(response) 

538 

539 except json.JSONDecodeError as e: 

540 msg = 'Failed to get Jy/K factors from DB: JSON Syntax error. {}'.format(e) 

541 casalog.post(msg) 

542 raise RuntimeError(msg) 

543 

544 def _check_retval(self, retval): 

545 """Check if 'success' of retval dict is True. 

546 

547 This method only checks if the api was able to complete the process successfully or not. 

548 It is expected that 'success' will be False as a response in case if query was successful 

549 but response contains nothing due to any error in the server. In that case, this method 

550 raises RuntimeError to propagate server side error to the user. 

551 

552 Arguments: 

553 retval: dictionary translated from JSON response from the server 

554 

555 Raises: 

556 RuntimeError: response contains nothing due to any error in the server 

557 """ 

558 if not retval['success']: 

559 msg = 'Failed to get Jy/K factors from DB: {}'.format(retval['error']) 

560 casalog.post(msg, priority='ERROR') 

561 raise RuntimeError(msg) 

562 

563 

564class Translator(): 

565 """A class containing the methods required to convert Jy/K DB responses into factors.""" 

566 

567 @staticmethod 

568 def format_cal_table_format(factors): # _format_jyperk 

569 """Format given dictionary to the formatted list. 

570 

571 Sample formated list: 

572 [['MS_name', 'antenna_name', 'spwid', 'pol string', 'factor'], 

573 ['MS_name', 'antenna_name', 'spwid', 'pol string', 'factor'], 

574 ... 

575 ['MS_name', 'antenna_name', 'spwid', 'pol string', 'factor']] 

576 

577 Arguments: 

578 factors {dict} -- Dictionary containing Jy/K factors with meta data. 

579 

580 Returns: 

581 list -- Formatted list of Jy/K factors. 

582 """ 

583 template = string.Template('$MS $Antenna $Spwid I $factor') 

584 factors = [list(map(str, template.safe_substitute(**factor).split())) for factor in factors] 

585 return factors 

586 

587 @staticmethod 

588 def filter_jyperk(vis, factors, spw): 

589 """Filter factors using spw.""" 

590 ms = mstool() 

591 selected = ms.msseltoindex(vis=vis, spw=spw) 

592 science_windows = selected['spw'] 

593 filtered = [ 

594 i for i in factors if (len(i) == 5) 

595 and ( 

596 i[0] == os.path.basename(os.path.abspath(vis)) and (int(i[2]) in science_windows) 

597 ) 

598 ] 

599 return filtered 

600 

601 

602class ASDMRspTranslator(): 

603 """A class to convert the response for asdm from the Jy/K DB to factors.""" 

604 

605 @classmethod 

606 def convert(cls, data, vis, spw='*'): 

607 """Convert from the response to list with factor. 

608 

609 Arguments: 

610 spw {str} -- This parameter is not used. It is provided to align the 

611 calling method with other classes (InterpolationRspTranslator and 

612 ModelFitRspTranslator). 

613 

614 Returns: 

615 list -- List of Jy/K conversion factors with meta data. 

616 """ 

617 assert len(data) == 1 

618 data = data[0] 

619 

620 factors = cls._extract_factors(data) 

621 formatted = Translator.format_cal_table_format(factors) 

622 

623 return Translator.filter_jyperk(vis, formatted, spw) 

624 

625 @staticmethod 

626 def _extract_factors(data): 

627 return data['response']['data']['factors'] 

628 

629 

630class InterpolationRspTranslator(): 

631 """A class to convert the responses for interpolation from the Jy/K DB to factors.""" 

632 

633 @classmethod 

634 def convert(cls, data_set, vis, spw='*'): 

635 """Convert from the response to list with factor. 

636 

637 Arguments: 

638 data_set {dict} -- The result of the Web API. 

639 vis {str} -- The file path of the visibility data. 

640 spw {str} -- Spectral windows. 

641 

642 Returns: 

643 list -- List of Jy/K conversion factors with meta data. 

644 """ 

645 cal_dict = cls._dataset_to_cal_dict(data_set, cls._extract_factor) 

646 formatted = Translator.format_cal_table_format(cal_dict) 

647 

648 return Translator.filter_jyperk(vis, formatted, spw) 

649 

650 @staticmethod 

651 def _extract_factor(data): 

652 if data['response']['query']['elevation'] == 'nan': 

653 casalog.post('The elevation mean is np.nan, so the factor is set 1.', 

654 priority='WARN', 

655 origin='jyperk.MeanElevation._calc_elevation_mean') 

656 return 1 

657 

658 return data['response']['data']['factor']['mean'] 

659 

660 @staticmethod 

661 def _dataset_to_cal_dict(dataset, _extract_factor): 

662 return_data = [] 

663 

664 for data in dataset: 

665 # aux is dictionary holding vis and spw id 

666 aux = data['aux'] 

667 if not isinstance(aux, dict): 

668 raise TypeError('The response.aux in the JSON obtained from Jy/K db must be dict.') 

669 

670 if 'vis' not in aux: 

671 raise KeyError( 

672 'The response.aux in the JSON obtained from Jy/K db must contain vis.') 

673 

674 if 'spwid' not in aux: 

675 raise KeyError( 

676 'The response.aux in the JSON obtained from Jy/K db must contain spwid.') 

677 

678 spwid = aux['spwid'] 

679 if not isinstance(spwid, int): 

680 raise TypeError( 

681 'The response.aux.spwid in the JSON obtained from Jy/K db must be int.') 

682 

683 vis = aux['vis'] 

684 if not isinstance(vis, str): 

685 raise TypeError( 

686 'The response.aux.vis in the JSON obtained from Jy/K db must be str.') 

687 

688 basename = os.path.basename(os.path.abspath(vis)) 

689 

690 factor = _extract_factor(data) 

691 polarization = 'I' 

692 antenna = data['response']['query']['antenna'] 

693 

694 return_data.append({'MS': basename, 'Antenna': antenna, 'Spwid': spwid, 

695 'Polarization': polarization, 'factor': factor}) 

696 return return_data 

697 

698 

699class ModelFitRspTranslator(InterpolationRspTranslator): 

700 """A class to convert the responses for model-fit from the Jy/K DB to factors.""" 

701 

702 @staticmethod 

703 def _extract_factor(data): 

704 return data['response']['data']['factor'] 

705 

706 

707__jyperk_factory = { 

708 'asdm': (ASDMParamsGenerator, ASDMRspTranslator), 

709 'interpolation': (InterpolationParamsGenerator, InterpolationRspTranslator), 

710 'model-fit': (ModelFitParamsGenerator, ModelFitRspTranslator), 

711} 

712 

713 

714# file part 

715class JyPerKReader4File(): 

716 """A class to read from CSV file and format factors. 

717 

718 This function will be used task_gencal. 

719 """ 

720 

721 def __init__(self, infile): 

722 """Set a parameter. 

723 

724 Arguments: 

725 infile {str} -- The file path of CSV which the factors are stored. 

726 """ 

727 self.infile = infile 

728 

729 def get(self): 

730 """Read jyperk factors from a file and returns a string list. 

731 

732 Returns: 

733 list -- [['MS','ant','spwid','polid','factor'], ...] 

734 """ 

735 if not os.path.isfile(self.infile): 

736 raise OSError(f'There is no Jy/K db-derived factor file: {self.infile}') 

737 

738 with open(self.infile, 'r') as f: 

739 return list(self._extract_jyperk_from_csv(f)) 

740 

741 def _extract_jyperk_from_csv(self, stream): 

742 reader = csv.reader(stream) 

743 # Check if first line is header or not 

744 line = next(reader) 

745 if len(line) == 0 or line[0].strip().upper() == 'MS' or line[0].strip()[0] == '#': 

746 # must be a header, commented line, or empty line 

747 pass 

748 elif len(line) == 5: 

749 # may be a data record 

750 yield line 

751 else: 

752 casalog.post('Jy/K factor file is invalid format') 

753 for line in reader: 

754 if len(line) == 0 or len(line[0]) == 0 or line[0][0] == '#': 

755 continue 

756 elif len(line) == 5: 

757 yield line 

758 else: 

759 casalog.post('Jy/K factor file is invalid format')