Coverage for ligo/followup_advocate/__init__.py: 91%

393 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-11 21:40 +0000

1import math as mth 

2import os 

3import shutil 

4import tempfile 

5import urllib 

6import webbrowser 

7 

8import astropy.coordinates as coord 

9import astropy.time 

10import astropy.units as u 

11import healpy as hp 

12import lxml.etree 

13import numpy as np 

14from astropy.io.fits import getheader 

15from ligo.gracedb import rest 

16from ligo.skymap.io.fits import read_sky_map 

17from ligo.skymap.postprocess.ellipse import find_ellipse 

18from ligo.skymap.postprocess.crossmatch import crossmatch 

19 

20from .jinja import env 

21from . import _version 

22__version__ = _version.get_versions()['version'] 

23 

24 

25def authors(authors, service=rest.DEFAULT_SERVICE_URL): 

26 """Write GCN Circular author list""" 

27 return env.get_template('authors.jinja2').render(authors=authors).strip() 

28 

29 

30def guess_skyloc_pipeline(filename): 

31 keys = ['cWB', 'BAYESTAR', 'Bilby', 'LIB', 'LALInference', 

32 'oLIB', 'MLy', 'UNKNOWN'] 

33 skyloc_pipelines_dict = dict(zip([x.lower() for x in keys], keys)) 

34 skyloc_pipelines_dict['rapidpe_rift'] = 'RapidPE-RIFT' 

35 try: 

36 return skyloc_pipelines_dict[filename.split('.')[0].lower()] 

37 except KeyError: 

38 return filename.split('.')[0] 

39 

40 

41def text_width(remove_text_wrap): 

42 """Return width of text wrap based on whether we wish to wrap the lines or 

43 not.""" 

44 return 9999 if remove_text_wrap else 79 

45 

46 

47def main_dict(gracedb_id, client): 

48 """Create general dictionary to pass to compose circular""" 

49 

50 event = client.superevent(gracedb_id).json() 

51 preferred_event = event['preferred_event_data'] 

52 preferred_pipeline = preferred_event['pipeline'] 

53 early_warning_pipelines = [] 

54 pipelines = [] 

55 gw_events = event['gw_events'] 

56 early_warning_alert = False 

57 

58 for gw_event in gw_events: 

59 gw_event_dict = client.event(gw_event).json() 

60 pipeline = gw_event_dict['pipeline'] 

61 search = gw_event_dict['search'] 

62 if pipeline not in pipelines: 

63 pipelines.append(pipeline) 

64 if pipeline not in early_warning_pipelines and \ 

65 search == 'EarlyWarning': 

66 early_warning_pipelines.append(pipeline) 

67 # Sort to get alphabetical order 

68 pipelines.sort(key=str.lower) 

69 early_warning_pipelines.sort(key=str.lower) 

70 

71 voevents = client.voevents(gracedb_id).json()['voevents'] 

72 if not voevents: 

73 raise ValueError( 

74 "{} has no VOEvent to generate circulars from.".format( 

75 gracedb_id)) 

76 

77 citation_index = {pipeline.lower(): pipelines.index(pipeline) + 1 for 

78 pipeline in pipelines} 

79 for pipeline in early_warning_pipelines: 

80 if pipeline.lower() != 'mbta': 

81 citation_index[pipeline.lower() + '_earlywarning'] = \ 

82 max(citation_index.values()) + 1 

83 

84 gpstime = float(preferred_event['gpstime']) 

85 event_time = astropy.time.Time(gpstime, format='gps').utc 

86 

87 # Grab latest p_astro and em_bright values from lastest VOEvent 

88 voevent_text = client.files(gracedb_id, voevents[-1]['filename']).read() 

89 root = lxml.etree.fromstring(voevent_text) 

90 p_astros = root.find('./What/Group[@name="Classification"]') 

91 em_brights = root.find('./What/Group[@name="Properties"]') 

92 classifications = {} 

93 source_classification = {} 

94 # Only try to load if present to prevent errors with .getchildren() 

95 p_astro_pipeline = None 

96 if p_astros: 

97 for p_astro in p_astros.getchildren(): 

98 if p_astro.attrib.get('value'): 

99 classifications[p_astro.attrib['name']] = \ 

100 float(p_astro.attrib['value']) * 100 

101 # Figure out which pipeline uploaded p_astro, usually the first one 

102 # FIXME: Replace with more efficient method in the future 

103 logs = client.logs(gracedb_id).json()['log'] 

104 for message in reversed(logs): 

105 filename = message['filename'] 

106 if filename and '.p_astro.json' in filename and \ 

107 filename != 'p_astro.json': 

108 p_astro = client.files(gracedb_id, filename).json() 

109 if all(mth.isclose(p_astro[key] * 100, classifications[key]) 

110 for key in classifications.keys()): 

111 p_astro_pipeline = filename.split('.')[0].lower() 

112 break 

113 if p_astro_pipeline == 'rapidpe_rift': 

114 citation_index['rapidpe_rift'] = max(citation_index.values()) + 1 

115 if em_brights: 

116 for em_bright in em_brights.getchildren(): 

117 if em_bright.attrib.get('value'): 

118 source_classification[em_bright.attrib['name']] = \ 

119 float(em_bright.attrib['value']) * 100 

120 citation_index['em_bright'] = max(citation_index.values()) + 1 

121 

122 skymaps = {} 

123 for voevent in voevents: 

124 voevent_text = client.files(gracedb_id, voevent['filename']).read() 

125 root = lxml.etree.fromstring(voevent_text) 

126 alert_type = root.find( 

127 './What/Param[@name="AlertType"]').attrib['value'].lower() 

128 if alert_type == 'earlywarning': 

129 # Add text for early warning detection if one early warning alert 

130 early_warning_alert = True 

131 url = root.find('./What/Group/Param[@name="skymap_fits"]') 

132 if url is None: 

133 continue 

134 url = url.attrib['value'] 

135 _, filename = os.path.split(url) 

136 skyloc_pipeline = guess_skyloc_pipeline(filename) 

137 issued_time = astropy.time.Time(root.find('./Who/Date').text).gps 

138 if filename not in skymaps: 

139 skymaps[filename] = dict( 

140 alert_type=alert_type, 

141 pipeline=skyloc_pipeline, 

142 filename=filename, 

143 latency=issued_time-event_time.gps) 

144 if skyloc_pipeline.lower() not in citation_index: 

145 citation_index[skyloc_pipeline.lower()] = \ 

146 max(citation_index.values()) + 1 

147 skymaps = list(skymaps.values()) 

148 

149 o = urllib.parse.urlparse(client.service_url) 

150 

151 kwargs = dict( 

152 subject='Identification', 

153 gracedb_id=gracedb_id, 

154 gracedb_service_url=urllib.parse.urlunsplit( 

155 (o.scheme, o.netloc, '/superevents/', '', '')), 

156 group=preferred_event['group'], 

157 pipeline=preferred_pipeline, 

158 all_pipelines=pipelines, 

159 early_warning_alert=early_warning_alert, 

160 early_warning_pipelines=early_warning_pipelines, 

161 gpstime='{0:.03f}'.format(round(float(preferred_event['gpstime']), 3)), 

162 search=preferred_event.get('search', ''), 

163 far=preferred_event['far'], 

164 utctime=event_time.iso, 

165 instruments=preferred_event['instruments'].split(','), 

166 skymaps=skymaps, 

167 prob_has_ns=source_classification.get('HasNS'), 

168 prob_has_remnant=source_classification.get('HasRemnant'), 

169 prob_has_massgap=source_classification.get('HasMassGap'), 

170 include_ellipse=None, 

171 classifications=classifications, 

172 p_astro_pipeline=p_astro_pipeline, 

173 citation_index=citation_index) 

174 

175 if skymaps: 

176 preferred_skymap = skymaps[-1]['filename'] 

177 cls = [50, 90] 

178 include_ellipse, ra, dec, a, b, pa, area, greedy_area = \ 

179 uncertainty_ellipse(gracedb_id, preferred_skymap, client, cls=cls) 

180 kwargs.update( 

181 preferred_skymap=preferred_skymap, 

182 cl=cls[-1], 

183 include_ellipse=include_ellipse, 

184 ra=coord.Longitude(ra*u.deg), 

185 dec=coord.Latitude(dec*u.deg), 

186 a=coord.Angle(a*u.deg), 

187 b=coord.Angle(b*u.deg), 

188 pa=coord.Angle(pa*u.deg), 

189 ellipse_area=area, 

190 greedy_area=greedy_area) 

191 try: 

192 distmu, distsig = get_distances_skymap_gracedb(gracedb_id, 

193 preferred_skymap, 

194 client) 

195 kwargs.update( 

196 distmu=distmu, 

197 distsig=distsig) 

198 except TypeError: 

199 pass 

200 

201 return kwargs 

202 

203 

204def compose(gracedb_id, authors=(), mailto=False, remove_text_wrap=False, 

205 service=rest.DEFAULT_SERVICE_URL, client=None): 

206 """Compose GCN Circular draft""" 

207 

208 if client is None: 

209 client = rest.GraceDb(service) 

210 

211 kwargs = main_dict(gracedb_id, client=client) 

212 kwargs.update(authors=authors) 

213 kwargs.update(change_significance_statement=False) 

214 kwargs.update(text_width=text_width(remove_text_wrap)) 

215 

216 subject = env.get_template('subject.jinja2').render(**kwargs).strip() 

217 body = env.get_template('initial_circular.jinja2').render(**kwargs).strip() 

218 

219 if mailto: 

220 pattern = 'mailto:emfollow@ligo.org,dac@ligo.org?subject={0}&body={1}' 

221 url = pattern.format( 

222 urllib.parse.quote(subject), 

223 urllib.parse.quote(body)) 

224 webbrowser.open(url) 

225 else: 

226 return '{0}\n\n{1}'.format(subject, body) 

227 

228 

229def compose_raven(gracedb_id, authors=(), remove_text_wrap=False, 

230 service=rest.DEFAULT_SERVICE_URL, client=None): 

231 """Compose EM_COINC RAVEN GCN Circular draft""" 

232 

233 if client is None: 

234 client = rest.GraceDb(service) 

235 

236 kwargs = dict() 

237 kwargs = _update_raven_parameters(gracedb_id, kwargs, client) 

238 kwargs.update(main_dict(gracedb_id, client=client)) 

239 kwargs.update(update_alert=False) 

240 kwargs.update(text_width=text_width(remove_text_wrap)) 

241 # Add RAVEN citation 

242 citation_index = kwargs['citation_index'] 

243 citation_index['raven'] = max(citation_index.values()) + 1 

244 kwargs['citation_index'] = citation_index 

245 

246 subject = ( 

247 env.get_template('RAVEN_subject.jinja2').render(**kwargs) 

248 .strip()) 

249 body = ( 

250 env.get_template('RAVEN_circular.jinja2').render(**kwargs) 

251 .strip()) 

252 return '{0}\n\n{1}'.format(subject, body) 

253 

254 

255def compose_llama( 

256 gracedb_id, authors=(), service=rest.DEFAULT_SERVICE_URL, 

257 icecube_alert=None, remove_text_wrap=False, 

258 client=None): 

259 """Compose GRB LLAMA GCN Circular draft. 

260 Here, gracedb_id will be a GRB superevent ID in GraceDb.""" 

261 

262 if client is None: 

263 client = rest.GraceDb(service) 

264 

265 superevent = client.superevent(gracedb_id).json() 

266 

267 gpstime = float(superevent['t_0']) 

268 tl, th = gpstime - 500, gpstime + 500 

269 event_time = astropy.time.Time(gpstime, format='gps').utc 

270 tl_datetime = str(astropy.time.Time( 

271 tl, format='gps').isot).replace('T', ' ') 

272 th_datetime = str(astropy.time.Time( 

273 th, format='gps').isot).replace('T', ' ') 

274 

275 o = urllib.parse.urlparse(client.service_url) 

276 kwargs = dict( 

277 gracedb_service_url=urllib.parse.urlunsplit( 

278 (o.scheme, o.netloc, '/superevents/', '', '')), 

279 gracedb_id=gracedb_id, 

280 llama=True, 

281 icecube_alert=icecube_alert, 

282 event_time=event_time, 

283 tl_datetime=tl_datetime, 

284 th_datetime=th_datetime, 

285 authors=authors) 

286 kwargs.update(text_width=text_width(remove_text_wrap)) 

287 

288 citation_index = {'llama': 1, 'llama_stat': 2} 

289 kwargs.update(citation_index=citation_index) 

290 

291 files = client.files(gracedb_id).json() 

292 

293 llama_stat_filename = 'significance_subthreshold_lvc-i3.json' 

294 if llama_stat_filename in files: 

295 llama_stat_file = client.files(gracedb_id, llama_stat_filename).json() 

296 llama_fap = llama_stat_file["p_value"] 

297 neutrinos = llama_stat_file["inputs"]["neutrino_info"] 

298 lines = [] 

299 for neutrino in neutrinos: 

300 # Build aligned string 

301 vals = [] 

302 dt = (event_time - 

303 astropy.time.Time(neutrino['mjd'], 

304 format='mjd')).to(u.s).value 

305 vals.append('{:.2f}'.format(dt)) 

306 vals.append('{:.2f}'.format(neutrino['ra'])) 

307 vals.append('{:.2f}'.format(neutrino['dec'])) 

308 vals.append('{:.2f}'.format(neutrino['sigma'])) 

309 vals.append('{:.4f}'.format(llama_fap)) 

310 lines.append(align_number_string(vals, [0, 11, 21, 40, 59])) 

311 

312 kwargs.update(llama_fap=llama_fap, 

313 neutrinos=lines) 

314 

315 subject = ( 

316 env.get_template('llama_subject.jinja2').render(**kwargs) 

317 .strip()) 

318 if icecube_alert: 

319 body = ( 

320 env.get_template('llama_alert_circular.jinja2').render(**kwargs) 

321 .strip()) 

322 else: 

323 body = ( 

324 env.get_template('llama_track_circular.jinja2').render(**kwargs) 

325 .strip()) 

326 return '{0}\n\n{1}'.format(subject, body) 

327 

328 

329def compose_grb_medium_latency( 

330 gracedb_id, authors=(), service=rest.DEFAULT_SERVICE_URL, 

331 use_detection_template=None, remove_text_wrap=False, client=None): 

332 """Compose GRB Medium Latency GCN Circular draft. 

333 Here, gracedb_id will be a GRB external trigger ID in GraceDb.""" 

334 

335 if client is None: 

336 client = rest.GraceDb(service) 

337 

338 event = client.event(gracedb_id).json() 

339 search = event['search'] 

340 

341 if search != 'GRB': 

342 return 

343 

344 group = event['group'] 

345 pipeline = event['pipeline'] 

346 external_trigger = event['extra_attributes']['GRB']['trigger_id'] 

347 

348 o = urllib.parse.urlparse(client.service_url) 

349 kwargs = dict( 

350 gracedb_service_url=urllib.parse.urlunsplit( 

351 (o.scheme, o.netloc, '/events/', '', '')), 

352 gracedb_id=gracedb_id, 

353 group=group, 

354 grb=True, 

355 pipeline=pipeline, 

356 external_trigger=external_trigger, 

357 exclusions=[], 

358 detections=[]) 

359 kwargs.update(text_width=text_width(remove_text_wrap)) 

360 

361 files = client.files(gracedb_id).json() 

362 

363 citation_index = {} 

364 index = 1 

365 xpipeline_fap_file = 'false_alarm_probability_x.json' 

366 if xpipeline_fap_file in files: 

367 xpipeline_fap = client.files(gracedb_id, xpipeline_fap_file).json() 

368 online_xpipeline_fap = xpipeline_fap.get('Online Xpipeline') 

369 # Create detection/exclusion circular based on given argument 

370 # Use default cutoff if not provided 

371 xpipeline_detection = (use_detection_template if use_detection_template 

372 is not None else online_xpipeline_fap < 0.001) 

373 if xpipeline_detection: 

374 kwargs['detections'].append('xpipeline') 

375 kwargs.update(online_xpipeline_fap=online_xpipeline_fap) 

376 else: 

377 kwargs['exclusions'].append('xpipeline') 

378 xpipeline_distances_file = 'distances_x.json' 

379 xpipeline_distances = client.files(gracedb_id, 

380 xpipeline_distances_file).json() 

381 burst_exclusion = xpipeline_distances.get('Burst Exclusion') 

382 kwargs.update(burst_exclusion=burst_exclusion) 

383 citation_index['xpipeline'] = index 

384 index += 1 

385 

386 pygrb_fap_file = 'false_alarm_probability_pygrb.json' 

387 if pygrb_fap_file in files: 

388 pygrb_fap = client.files(gracedb_id, pygrb_fap_file).json() 

389 online_pygrb_fap = pygrb_fap.get('Online PyGRB') 

390 # Create detection/exclusion circular based on given argument 

391 # Use default cutoff if not provided 

392 pygrb_detection = (use_detection_template if use_detection_template 

393 is not None else online_pygrb_fap < 0.01) 

394 if pygrb_detection: 

395 kwargs['detections'].append('pygrb') 

396 kwargs.update(online_pygrb_fap=online_pygrb_fap) 

397 else: 

398 kwargs['exclusions'].append('pygrb') 

399 pygrb_distances_file = 'distances_pygrb.json' 

400 pygrb_distances = client.files(gracedb_id, 

401 pygrb_distances_file).json() 

402 nsns_exclusion = pygrb_distances.get('NSNS Exclusion') 

403 nsbh_exclusion = pygrb_distances.get('NSBH Exclusion') 

404 kwargs.update(nsbh_exclusion=nsbh_exclusion, 

405 nsns_exclusion=nsns_exclusion) 

406 citation_index['pygrb'] = index 

407 

408 kwargs.update(citation_index=citation_index) 

409 

410 subject = ( 

411 env.get_template('medium_latency_grb_subject.jinja2').render(**kwargs) 

412 .strip()) 

413 body = ( 

414 env.get_template('medium_latency_grb_circular.jinja2').render(**kwargs) 

415 .strip()) 

416 return '{0}\n\n{1}'.format(subject, body) 

417 

418 

419def compose_update(gracedb_id, authors=(), 

420 service=rest.DEFAULT_SERVICE_URL, 

421 update_types=['sky_localization', 'p_astro', 

422 'em_bright', 'raven'], 

423 remove_text_wrap=False, 

424 client=None): 

425 """Compose GCN update circular""" 

426 if client is None: 

427 client = rest.GraceDb(service) 

428 

429 kwargs = main_dict(gracedb_id, client=client) 

430 kwargs.pop('citation_index', None) 

431 kwargs.update(text_width=text_width(remove_text_wrap)) 

432 

433 if isinstance(update_types, str): 

434 update_types = update_types.split(',') 

435 

436 # Adjust files for update type alert 

437 citation_index = {} 

438 skymaps = [] 

439 index = 1 

440 

441 updated_skymap = kwargs.get('skymaps')[-1] 

442 kwargs.update(updated_skymap=updated_skymap) 

443 skymaps = [updated_skymap] 

444 if 'sky_localization' in update_types: 

445 citation_index[updated_skymap['pipeline'].lower()] = index 

446 index += 1 

447 if 'p_astro' in update_types and \ 

448 kwargs.get('p_astro_pipeline') == 'rapidpe_rift': 

449 citation_index['rapidpe_rift'] = index 

450 index += 1 

451 if 'em_bright' in update_types: 

452 # If not already cited, cite sky map pipeline for em_bright 

453 if updated_skymap['pipeline'].lower() not in citation_index.keys(): 

454 citation_index[updated_skymap['pipeline'].lower()] = index 

455 index += 1 

456 citation_index['em_bright'] = index 

457 index += 1 

458 if 'raven' in update_types: 

459 citation_index['raven'] = index 

460 

461 kwargs.update(skymaps=skymaps, 

462 citation_index=citation_index, 

463 all_pipelines=[], 

464 update_alert=True) 

465 

466 if 'raven' in update_types: 

467 kwargs = _update_raven_parameters(gracedb_id, kwargs, client) 

468 

469 kwargs.update(authors=authors) 

470 kwargs.update(change_significance_statement=False) 

471 kwargs.update(subject='Update') 

472 kwargs.update(update_types=update_types) 

473 

474 subject = env.get_template('subject.jinja2').render(**kwargs).strip() 

475 body = env.get_template( 

476 'update_circular.jinja2').render(**kwargs).strip() 

477 return '{0}\n\n{1}'.format(subject, body) 

478 

479 

480def compose_retraction(gracedb_id, authors=(), remove_text_wrap=False, 

481 service=rest.DEFAULT_SERVICE_URL, client=None): 

482 """Compose GCN retraction circular""" 

483 if client is None: 

484 client = rest.GraceDb(service) 

485 event = client.superevent(gracedb_id).json() 

486 preferred_event = event['preferred_event_data'] 

487 labels = event['labels'] 

488 earlywarning = \ 

489 ('EARLY_WARNING' in labels and 

490 {'EM_SelectedConfident', 'SIGNIF_LOCKED'}.isdisjoint(labels)) 

491 

492 kwargs = dict( 

493 subject='Retraction', 

494 gracedb_id=gracedb_id, 

495 group=preferred_event['group'], 

496 earlywarning=earlywarning, 

497 authors=authors 

498 ) 

499 kwargs.update(text_width=text_width(remove_text_wrap)) 

500 

501 subject = env.get_template('subject.jinja2').render(**kwargs).strip() 

502 body = env.get_template('retraction.jinja2').render(**kwargs).strip() 

503 return '{0}\n\n{1}'.format(subject, body) 

504 

505 

506def read_map_gracedb(graceid, filename, client): 

507 with tempfile.NamedTemporaryFile(mode='w+b') as localfile: 

508 remotefile = client.files(graceid, filename, raw=True) 

509 shutil.copyfileobj(remotefile, localfile) 

510 localfile.flush() 

511 return read_sky_map(localfile.name, moc=True) 

512 

513 

514def get_distances_skymap_gracedb(graceid, filename, client): 

515 with tempfile.NamedTemporaryFile(mode='w+b') as localfile: 

516 remotefile = client.files(graceid, filename, raw=True) 

517 shutil.copyfileobj(remotefile, localfile) 

518 localfile.flush() 

519 header = getheader(localfile.name, 1) 

520 try: 

521 return header['distmean'], header['diststd'] 

522 except KeyError: 

523 pass 

524 

525 

526def read_map_from_path(path, client): 

527 return read_map_gracedb(*path.split('/'), client)[0] 

528 

529 

530def align_number_string(nums, positions): 

531 positions.append(len(nums[-1])) 

532 gen = (val + ' ' * (positions[i+1]-positions[i]-len(val)) 

533 for i, val in enumerate(nums)) 

534 return ''.join(gen) 

535 

536 

537def mask_cl(p, level=90): 

538 pflat = p.ravel() 

539 i = np.flipud(np.argsort(p)) 

540 cs = np.cumsum(pflat[i]) 

541 cls = np.empty_like(pflat) 

542 cls[i] = cs 

543 cls = cls.reshape(p.shape) 

544 return cls <= 1e-2 * level 

545 

546 

547def compare_skymaps(paths, service=rest.DEFAULT_SERVICE_URL, client=None): 

548 """Produce table of sky map overlaps""" 

549 if client is None: 

550 client = rest.GraceDb(service) 

551 filenames = [path.split('/')[1] for path in paths] 

552 pipelines = [guess_skyloc_pipeline(filename) for filename in filenames] 

553 probs = [read_map_from_path(path, client) for path in paths] 

554 npix = max(len(prob) for prob in probs) 

555 nside = hp.npix2nside(npix) 

556 deg2perpix = hp.nside2pixarea(nside, degrees=True) 

557 probs = [hp.ud_grade(prob, nside, power=-2) for prob in probs] 

558 masks = [mask_cl(prob) for prob in probs] 

559 areas = [mask.sum() * deg2perpix for mask in masks] 

560 joint_areas = [(mask & masks[-1]).sum() * deg2perpix for mask in masks] 

561 

562 kwargs = dict(params=zip(filenames, pipelines, areas, joint_areas)) 

563 

564 return env.get_template('compare_skymaps.jinja2').render(**kwargs) 

565 

566 

567def uncertainty_ellipse(graceid, filename, client, cls=[50, 90], 

568 ratio_ellipse_cl_areas=1.35): 

569 """Compute uncertainty ellipses for a given sky map 

570 

571 Parameters 

572 ---------- 

573 graceid: str 

574 ID of the trigger used by GraceDB 

575 filename: str 

576 File name of sky map 

577 client: class 

578 REST API client for HTTP connection 

579 cls: array-like 

580 List of percentage of minimal credible area used to check whether the 

581 areas are close to an ellipse, returning the values of the final item 

582 ratio_ellipse_cl_areas: float 

583 Ratio between ellipse area and minimal credible area from cl 

584 """ 

585 if filename.endswith('.gz'): 

586 # Try using the multi-res sky map if it exists 

587 try: 

588 new_filename = filename.replace('.fits.gz', '.multiorder.fits') 

589 skymap = read_map_gracedb(graceid, new_filename, client) 

590 except (IOError, rest.HTTPError): 

591 skymap = read_map_gracedb(graceid, filename, client) 

592 else: 

593 skymap = read_map_gracedb(graceid, filename, client) 

594 

595 # Convert to an array if necessary 

596 if np.isscalar(cls): 

597 cls = [cls] 

598 cls = np.asarray(cls) 

599 

600 # Pass array of contour inteverals to get areas 

601 result = crossmatch(skymap, contours=cls / 100) 

602 greedy_areas = np.asarray(result.contour_areas) 

603 ra, dec, a, b, pa, ellipse_areas = find_ellipse(skymap, cl=cls) 

604 a, b = np.asarray(a), np.asarray(b) 

605 

606 # Only use ellipse if every confidence interval passes 

607 use_ellipse = \ 

608 np.all(ellipse_areas <= ratio_ellipse_cl_areas * greedy_areas) 

609 return (use_ellipse, ra, dec, a[-1], b[-1], pa, ellipse_areas[-1], 

610 greedy_areas[-1]) 

611 

612 

613def _update_raven_parameters(gracedb_id, kwargs, client): 

614 """Update kwargs with parameters for RAVEN coincidence""" 

615 

616 event = client.superevent(gracedb_id).json() 

617 

618 if 'EM_COINC' not in event['labels']: 

619 raise ValueError("No EM_COINC label for {}".format(gracedb_id)) 

620 

621 preferred_event = event['preferred_event_data'] 

622 group = preferred_event['group'] 

623 gpstime = float(preferred_event['gpstime']) 

624 event_time = astropy.time.Time(gpstime, format='gps').utc 

625 

626 em_event_id = event['em_type'] 

627 em_event = client.event(em_event_id).json() 

628 em_event_gpstime = float(em_event['gpstime']) 

629 external_pipeline = em_event['pipeline'] 

630 # Get all other pipelines, removing duplicates 

631 other_ext_pipelines = \ 

632 [*set(client.event(id).json()['pipeline'] for id 

633 in event['em_events'])] 

634 # Remove preferred pipeline 

635 other_ext_pipelines.remove(external_pipeline) 

636 # FIXME in GraceDb: Even SNEWS triggers have an extra attribute GRB. 

637 external_trigger_id = em_event['extra_attributes']['GRB']['trigger_id'] 

638 snews = (em_event['pipeline'] == 'SNEWS') 

639 grb = (em_event['search'] in ['GRB', 'SubGRB', 'SubGRBTargeted', 'MDC'] 

640 and not snews) 

641 subthreshold = em_event['search'] in ['SubGRB', 'SubGRBTargeted'] 

642 subthreshold_targeted = em_event['search'] == 'SubGRBTargeted' 

643 far_grb = em_event['far'] 

644 

645 o = urllib.parse.urlparse(client.service_url) 

646 kwargs.update( 

647 gracedb_service_url=urllib.parse.urlunsplit( 

648 (o.scheme, o.netloc, '/superevents/', '', '')), 

649 gracedb_id=gracedb_id, 

650 group=group, 

651 external_pipeline=external_pipeline, 

652 external_trigger=external_trigger_id, 

653 snews=snews, 

654 grb=grb, 

655 subthreshold=subthreshold, 

656 subthreshold_targeted=subthreshold_targeted, 

657 other_ext_pipelines=other_ext_pipelines, 

658 far_grb=far_grb, 

659 latency=abs(round(em_event_gpstime-gpstime, 1)), 

660 beforeafter='before' if gpstime > em_event_gpstime else 'after') 

661 

662 if grb: 

663 # Grab GRB coincidence FARs 

664 time_coinc_far = event['time_coinc_far'] 

665 space_time_coinc_far = event['space_coinc_far'] 

666 kwargs.update( 

667 time_coinc_far=time_coinc_far, 

668 space_time_coinc_far=space_time_coinc_far, 

669 ext_ra=em_event['extra_attributes']['GRB']['ra'], 

670 ext_dec=em_event['extra_attributes']['GRB']['dec'], 

671 ext_error=em_event['extra_attributes']['GRB']['error_radius']) 

672 

673 # Find combined sky maps for GRB 

674 voevents = client.voevents(gracedb_id).json()['voevents'] 

675 combined_skymaps = {} 

676 if not voevents: 

677 raise ValueError( 

678 "{} has no VOEvent to generate circulars from.".format( 

679 gracedb_id)) 

680 for i, voevent in enumerate(voevents): 

681 voevent_text = client.files(gracedb_id, voevent['filename']).read() 

682 root = lxml.etree.fromstring(voevent_text) 

683 alert_type = root.find( 

684 './What/Param[@name="AlertType"]').attrib['value'].lower() 

685 # Check if significant GW alert already queued or sent 

686 change_significance_statement = \ 

687 {'EM_SelectedConfident', 'SIGNIF_LOCKED'}.isdisjoint( 

688 event['labels']) 

689 url = root.find('./What/Group/Param[@name="joint_skymap_fits"]') 

690 if url is None: 

691 continue 

692 url = url.attrib['value'] 

693 _, filename = os.path.split(url) 

694 issued_time = astropy.time.Time( 

695 root.find('./Who/Date').text).gps 

696 if filename not in combined_skymaps: 

697 combined_skymaps[filename] = dict( 

698 alert_type=alert_type, 

699 filename=filename, 

700 latency=issued_time-event_time.gps) 

701 

702 if combined_skymaps: 

703 combined_skymaps = list(combined_skymaps.values()) 

704 combined_skymap = combined_skymaps[-1]['filename'] 

705 cls = [50, 90] 

706 include_ellipse, ra, dec, a, b, pa, area, greedy_area = \ 

707 uncertainty_ellipse(gracedb_id, combined_skymap, client, 

708 cls=cls) 

709 kwargs.update( 

710 combined_skymap=combined_skymap, 

711 combined_skymaps=combined_skymaps, 

712 cl=cls[-1], 

713 combined_skymap_include_ellipse=include_ellipse, 

714 combined_skymap_ra=coord.Longitude(ra*u.deg), 

715 combined_skymap_dec=coord.Latitude(dec*u.deg), 

716 combined_skymap_a=coord.Angle(a*u.deg), 

717 combined_skymap_b=coord.Angle(b*u.deg), 

718 combined_skymap_pa=coord.Angle(pa*u.deg), 

719 combined_skymap_ellipse_area=area, 

720 combined_skymap_greedy_area=greedy_area, 

721 change_significance_statement=change_significance_statement) 

722 

723 return kwargs