def pretty_plot(map_path, ra, dec, cand_name='name'): # loading relevant info prob = hp.read_map(map_path) ipix = np.argmax(prob) npix = len(prob) nside = hp.npix2nside(npix) theta, phi = hp.pix2ang(nside, ipix) # getting the center ra_max = np.rad2deg(phi) dec_max = np.rad2deg(0.5 * np.pi - theta) - 10 center = str(int(SkyCoord( ra_max, dec_max, unit='deg').ra.hms.h)) + 'h ' + str( int(SkyCoord(ra_max, dec_max, unit='deg').dec.dms.d)) + 'd' sky_area_per_pix = 4 * 180**2 / np.pi / npix area_95 = np.sum(np.cumsum(-np.sort(-prob)) <= 0.95) * sky_area_per_pix area_50 = np.sum(np.cumsum(-np.sort(-prob)) <= 0.5) * sky_area_per_pix print('AREA 50% ', area_50) print('AREA 95% ', area_95) #plotting fig = plt.figure() ax = plt.axes(projection='astro globe', center=center) ax.imshow_hpx(prob, cmap='cylon') cl = postprocess.find_greedy_credible_levels(prob) * 100 ax.contour_hpx(cl, levels=[50, 95], colors=['black', 'black'], linewidths=[0.5, 0.5]) ax.grid() ax.scatter(ra, dec, marker='*', color='blue', transform=ax.get_transform('world'), zorder=10) ax.set_title( fits.open(map_path)[0].header['OBJECT'] + ' / ' + str(fits.open(map_path)[0].header['TRIGTIME']) + ' \n' + cand_name) return fig
def contour(localization_name, dateobs): localization = models.Localization.query.filter_by( dateobs=dateobs, localization_name=localization_name).one() # Calculate credible levels. prob = localization.flat_2d cls = 100 * postprocess.find_greedy_credible_levels(prob) # Construct contours and return as a GeoJSON feature collection. levels = [50, 90] paths = postprocess.contour(cls, levels, degrees=True, simplify=True) center = postprocess.posterior_max(prob) localization.contour = { 'type': 'FeatureCollection', 'features': [ { 'type': 'Feature', 'geometry': { 'type': 'Point', 'coordinates': [center.ra.deg, center.dec.deg] }, 'properties': { 'credible_level': 0 } } ] + [ { 'type': 'Feature', 'properties': { 'credible_level': level }, 'geometry': { 'type': 'MultiLineString', 'coordinates': path } } for level, path in zip(levels, paths) ] } models.db.session.merge(localization) models.db.session.commit()
def do_getfields(healpix, FOV=60 / 3600.0, ra=None, dec=None, radius=None, level=None, names=None): from ligo.skymap import postprocess import matplotlib ras, decs = list(float(r) for r in ra), list(float(d) for d in dec) if (not level is None): cls = 100 * postprocess.find_greedy_credible_levels(healpix) paths = postprocess.contour(cls, [level], degrees=True, simplify=True) paths = paths[0] pts = np.vstack((ras, decs)).T idx = np.zeros((len(ras))) for path in paths: polygon = matplotlib.path.Path(path) check = polygon.contains_points(pts) check = list(map(int, check)) idx = np.maximum(idx, check) idx = np.where(idx == 1)[0] ras, decs = np.array(ras), np.array(decs) ras, decs = ras[idx], decs[idx] print( f"-> Sources included in the {level}% probability contour: {len(ras)}/{len(ra)}" ) if (not names is None) and (len(ras) > 0): names = np.array(names) names_out = names[idx] print(f"-> Specifically: {list(x for x in names_out)}") else: names_out = [] #for rr, dd, nn in zip(ras, decs, names_out): # print(f"{nn}, {rr}, {dd}") print("number of candidates: ", len(names_out)) return ras, decs, names_out
def plot_skymap(self, maxpts=None, trials=5, jobs=1, enable_multiresolution=True, objid=None, instruments=None, geo=False, dpi=600, transparent=False, colorbar=False, contour=[50, 90], annotate=True, cmap='cylon', load_pickle=False): """ Generate a fits file and sky map from a result Code adapted from ligo.skymap.tool.ligo_skymap_from_samples and ligo.skymap.tool.plot_skymap. Note, the use of this additionally required the installation of ligo.skymap. Parameters ---------- maxpts: int Maximum number of samples to use, if None all samples are used trials: int Number of trials at each clustering number jobs: int Number of multiple threads enable_multiresolution: bool Generate a multiresolution HEALPix map (default: True) objid: str Event ID to store in FITS header instruments: str Name of detectors geo: bool Plot in geographic coordinates (lat, lon) instead of RA, Dec dpi: int Resolution of figure in fots per inch transparent: bool Save image with transparent background colorbar: bool Show colorbar contour: list List of contour levels to use annotate: bool Annotate image with details cmap: str Name of the colormap to use load_pickle: bool, str If true, load the cached pickle file (default name), or the pickle-file give as a path. """ try: from astropy.time import Time from ligo.skymap import io, version, plot, postprocess, bayestar, kde import healpy as hp except ImportError as e: logger.info("Unable to generate skymap: error {}".format(e)) return check_directory_exists_and_if_not_mkdir(self.outdir) logger.info('Reading samples for skymap') data = self.posterior if maxpts is not None and maxpts < len(data): logger.info('Taking random subsample of chain') data = data.sample(maxpts) default_obj_filename = os.path.join( self.outdir, '{}_skypost.obj'.format(self.label)) if load_pickle is False: try: pts = data[['ra', 'dec', 'luminosity_distance']].values confidence_levels = kde.Clustered2Plus1DSkyKDE distance = True except KeyError: logger.warning( "The results file does not contain luminosity_distance") pts = data[['ra', 'dec']].values confidence_levels = kde.Clustered2DSkyKDE distance = False logger.info('Initialising skymap class') skypost = confidence_levels(pts, trials=trials, jobs=jobs) logger.info('Pickling skymap to {}'.format(default_obj_filename)) with open(default_obj_filename, 'wb') as out: pickle.dump(skypost, out) else: if isinstance(load_pickle, str): obj_filename = load_pickle else: obj_filename = default_obj_filename logger.info('Reading from pickle {}'.format(obj_filename)) with open(obj_filename, 'rb') as file: skypost = pickle.load(file) skypost.jobs = jobs distance = isinstance(skypost, kde.Clustered2Plus1DSkyKDE) logger.info('Making skymap') hpmap = skypost.as_healpix() if not enable_multiresolution: hpmap = bayestar.rasterize(hpmap) hpmap.meta.update(io.fits.metadata_for_version_module(version)) hpmap.meta['creator'] = "bilby" hpmap.meta['origin'] = 'LIGO/Virgo' hpmap.meta['gps_creation_time'] = Time.now().gps hpmap.meta['history'] = "" if objid is not None: hpmap.meta['objid'] = objid if instruments: hpmap.meta['instruments'] = instruments if distance: hpmap.meta['distmean'] = np.mean(data['luminosity_distance']) hpmap.meta['diststd'] = np.std(data['luminosity_distance']) try: time = data['geocent_time'] hpmap.meta['gps_time'] = time.mean() except KeyError: logger.warning('Cannot determine the event time from geocent_time') fits_filename = os.path.join(self.outdir, "{}_skymap.fits".format(self.label)) logger.info('Saving skymap fits-file to {}'.format(fits_filename)) io.write_sky_map(fits_filename, hpmap, nest=True) skymap, metadata = io.fits.read_sky_map(fits_filename, nest=None) nside = hp.npix2nside(len(skymap)) # Convert sky map from probability to probability per square degree. deg2perpix = hp.nside2pixarea(nside, degrees=True) probperdeg2 = skymap / deg2perpix if geo: obstime = Time(metadata['gps_time'], format='gps').utc.isot ax = plt.axes(projection='geo degrees mollweide', obstime=obstime) else: ax = plt.axes(projection='astro hours mollweide') ax.grid() # Plot sky map. vmax = probperdeg2.max() img = ax.imshow_hpx((probperdeg2, 'ICRS'), nested=metadata['nest'], vmin=0., vmax=vmax, cmap=cmap) # Add colorbar. if colorbar: cb = plot.colorbar(img) cb.set_label(r'prob. per deg$^2$') if contour is not None: confidence_levels = 100 * postprocess.find_greedy_credible_levels( skymap) contours = ax.contour_hpx((confidence_levels, 'ICRS'), nested=metadata['nest'], colors='k', linewidths=0.5, levels=contour) fmt = r'%g\%%' if rcParams['text.usetex'] else '%g%%' plt.clabel(contours, fmt=fmt, fontsize=6, inline=True) # Add continents. if geo: geojson_filename = os.path.join(os.path.dirname(plot.__file__), 'ne_simplified_coastline.json') with open(geojson_filename, 'r') as geojson_file: geoms = json.load(geojson_file)['geometries'] verts = [ coord for geom in geoms for coord in zip(*geom['coordinates']) ] plt.plot(*verts, color='0.5', linewidth=0.5, transform=ax.get_transform('world')) # Add a white outline to all text to make it stand out from the background. plot.outline_text(ax) if annotate: text = [] try: objid = metadata['objid'] except KeyError: pass else: text.append('event ID: {}'.format(objid)) if contour: pp = np.round(contour).astype(int) ii = np.round( np.searchsorted(np.sort(confidence_levels), contour) * deg2perpix).astype(int) for i, p in zip(ii, pp): text.append(u'{:d}% area: {:d} deg$^2$'.format(p, i)) ax.text(1, 1, '\n'.join(text), transform=ax.transAxes, ha='right') filename = os.path.join(self.outdir, "{}_skymap.png".format(self.label)) logger.info("Generating 2D projected skymap to {}".format(filename)) safe_save_figure(fig=plt.gcf(), filename=filename, dpi=dpi)
def credible_levels_2d(self): return find_greedy_credible_levels(self.flat_2d)
gwtc1 = CWD + '/gwtc1-skymaps' #gwtc2 = CWD+'/all_skymaps' all_files = os.listdir(gwtc1) fnames = [] for file in all_files: if file.endswith('.fits'): fnames.append(file) #can remove the loop if you just want one file, in that case you give the filename instead of the loop # ras = [] # decs = [] for fname in fnames: print('processing ' + fname + '...') fits_file = gwtc1 + '/' + fname skymap, metadata = fits.read_sky_map(fits_file, nest=None) cls = 100 * postprocess.find_greedy_credible_levels(skymap) contour_levels = [50, 90, 99] #this was for Jamie trying to find the 'center' of the contours, ignore it # mx = np.max(skymap) # mean = np.mean(skymap) # std = np.std(skymap) # idx = np.where(skymap == mx) # print(mx, idx) # print('mean: {}'.format(mean)) # print('std: {}'.format(std)) # dec, ra= IndexToDeclRa(idx) # print('ra=',ra, 'dec=',dec) # ras.append(np.radians(ra[0]))
def process_gcn(payload, root): if write_event: event_log = open(logfile, 'a+') # Respond only to 'test' events. # VERY IMPORTANT! Replace with the following code # to respond to only real 'observation' events. print("starting process") print(datetime.utcnow()) if write_event: event_log.write("starting process") event_log.write('\n') event_log.write(root.attrib['role']) event_log.write('\n') print(root.attrib['role']) """ Uncomment when we only want to do real observations if root.attrib['role'] != 'observation': return """ # Read all of the VOEvent parameters from the "What" section. params = {elem.attrib['name']: elem.attrib['value'] for elem in root.iterfind('.//Param')} # Respond only to 'CBC' events. Change 'CBC' to "Burst' # to respond to only unmodeled burst events. print(params['Group']) if write_event: event_log.write(params['Group']) event_log.write('\n') if params['Group'] != 'CBC': print('not CBC') return if params['AlertType'] != 'Preliminary': print('not Preliminary') return if 'Pkt_Ser_Num' in params: # Test events send same event twice, only choose first one. check = float(params['Pkt_Ser_Num']) if check > 1.1: return """ COMPLETE TO DEFINE WHICH EVENTS WE ARE LOOKING FOR if 'BNS' in params: check = float(params['BNS']) if check < 50.0: print("not BNS event, skipping") return else: print("not BNS event, skipping") return """ # Print all parameters. for key, value in params.items(): print(key, '=', value) if write_event: event_log.writelines(str(key + '=' + value)) if 'skymap_fits' in params: # Read the HEALPix sky map and the FITS header.i skymap, header = hp.read_map(params['skymap_fits'], h=True, verbose=False) header = dict(header) # Print some values from the FITS header. print('Distance =', header['DISTMEAN'], '+/-', header['DISTSTD']) if write_event: event_log.writelines('Distance =' + str(header['DISTMEAN']) + '+/-' + str(header['DISTSTD'])) event_log.write('\n') # write event information to SQL datadict = {'gracedb_id': [params['GraceID']], 'link': [params['EventPage']], 'dist': [header['DISTMEAN']], 'dist_err': [header['DISTSTD']]} sql_engine = sql.create_engine(connect_string) headers = ['gracedb_id', 'link', 'dist', 'dist_err'] dtypes = {'gracedb_id': 'str', 'link': 'str', 'dist': 'float64', 'dist_err': 'float64'} df = pd.DataFrame.from_dict(datadict) df.to_sql('events', sql_engine, if_exists='append', index=False) # get event ID back query = "select * from events ORDER BY 'ID' DESC" df = pd.read_sql_query(query, sql_engine) event = df['ID'].iloc[-1] credible_levels = find_greedy_credible_levels(skymap) if send_slack: slackmsg = "" for key, value in params.items(): slackmsg = slackmsg + str(key) + '=' + str(value) + '\n' slack_client.chat_postMessage(channel=SLACK_CHANNEL, text=slackmsg) # get df of galaxies from SQL query = "select * from galaxies" galaxies = pd.read_sql_query(query, sql_engine) gal_ipix = getPixel(skymap, galaxies['ra'], galaxies['dec']) """ offset = ephem.Observer() offset.lat = '0.0' offset.long = '180' offset.date = datetime.utcnow() offangle = float(offset.sidereal_time() * 180 / np.pi) """ if plot_map: #ax = plt.axes(projection=ccrs.Mollweide(central_longitude=-1 * offangle)) ax = plt.axes(projection=ccrs.Mollweide()) ax.stock_img() galaxies['credible'] = credible_levels[gal_ipix] banana_galaxies = galaxies[(galaxies.credible <= prob_check)] banana_galaxies.sort_values(by=['credible'], inplace=True) #get list of observatories from SQL query = "select * from observers" observatories = pd.read_sql_query(query, sql_engine) #Find Sun RA/DEC sun = ephem.Sun() #setup pyephem observer telescope = ephem.Observer() telescope.date = datetime.utcnow() #create a copy of galaxy list in case it ends up depleted galaxy_list = copy.deepcopy(banana_galaxies) galaxy_list = galaxy_list.values.tolist() galaxy_depleted = False slackmsg = '' matches = [] slack_count = 0 for x in range(0, len(observatories)): extra_matches = [] # Check if all galaxies have been assigned, if so start assigning duplicates if len(galaxy_list) == 0: galaxy_list = copy.deepcopy(banana_galaxies) galaxy_list = galaxy_list.values.tolist() galaxy_depeleted = True telescope.lat = str(observatories['lat'][x]).strip() telescope.long = str(observatories['lon'][x]).strip() sun.compute(telescope) # if sun is up, observatory can be skipped if check_sun: if sun.alt > max_sun: # print(str(observatories['Code'][x]) + " skipped, Sun is up") continue for i in range(0, len(galaxy_list)): star = ephem.FixedBody() star._ra = ephem.degrees(str(galaxy_list[i][2])) star._dec = ephem.degrees(str(galaxy_list[i][3])) star.compute(telescope) # if the first object is more than 20 degrees below elevation, unlikely any objects will be higher than 45 degrees if check_minalt: if star.alt < min_alt: # print(str(observatories['Code'][x]) + " skipped " + str(star.alt) + " is less than -20 degrees") break # assign galaxy to observer if star.alt > max_alt: matches.append( {'event_id': event, 'obs_ID': observatories['ID'][x], 'galaxy_id': galaxy_list[i][0]}) # print(galaxy_list[i]) listing = WEBSITE_URL+'/observe?key=' + str( observatories['key'][x]) + '&event=' + str(event) + '&obs=' + str( observatories['ID'][x]) + '&gal=' + str(galaxy_list[i][0]) + '&ra=' + str( galaxy_list[i][2]) + '&dec=' + str(galaxy_list[i][3]) + '&fov=' + str(observatories['fov'][x]) print(listing) if write_event: event_log.writelines(listing) event_log.write('\n') #only writing the first 5 urls to slack if send_slack: if slack_count < 5: slack_count += 1 slackmsg = slackmsg + listing + '\n' #find extra galaxies within FOV j = i+1 y = [] print(len(galaxy_list[j:])) if galinfov: for z in range(j,len(galaxy_list)): extra = ephem.FixedBody() extra._ra = ephem.degrees(str(galaxy_list[z][2])) extra._dec = ephem.degrees(str(galaxy_list[z][3])) extra.compute(telescope) sep = float(ephem.separation(star,extra))*180/np.pi if sep < 0.5/2: #if sep < observatories['fov'][x]/2: extra_matches.append({'event_id': event, 'obs_ID': observatories['ID'][x], 'galaxy_id': galaxy_list[z][0]}) print(z,sep,ephem.separation(star,extra)) y.append(z) if len(y)>1: print(y) y.reverse() for k in y: del galaxy_list[k] # print(str(observatories['ID'][x]) + " " + observatories['name'][x].strip()[15:] + " at Lat:" +str(telescope.lat) + ",Lon:" + str(telescope.lon) + " gets galaxy " + str(galaxy_list[i][1]) + ' ra='+str(star.ra) + ' dec=' + str(star.dec) + ' alt='+str(star.alt)) del galaxy_list[i] if plot_map: plt.scatter(telescope.lon * 180 / np.pi, telescope.lat * 180 / np.pi, color='red', transform=ccrs.Geodetic()) break if len(extra_matches) > 0: extra_matches = pd.DataFrame(extra_matches) extra_matches.to_sql('matches_extra', sql_engine, if_exists='append', index=False) matches = pd.DataFrame(matches) matches.to_sql('matches', sql_engine, if_exists='append', index=False) event_log.close() if send_slack: if len(slackmsg) > 0: slack_client.chat_postMessage(channel=SLACK_CHANNEL, text=slackmsg) if galaxy_depleted: text = "All" + str(len(banana_galaxies)) + " galaxies assigned" query = "update events set assigned = " + str(len(banana_galaxies)) + ", possible = " + str( len(banana_galaxies)) + " where ID = " + str(event) else: text = str(len(banana_galaxies) - len(galaxy_list)) + " galaxies assigned out of " + str( len(banana_galaxies)) query = "update events set assigned = " + str( len(banana_galaxies) - len(galaxy_list)) + ", possible = " + str( len(banana_galaxies)) + " where ID = " + str(event) slack_client.chat_postMessage(channel=SLACK_CHANNEL, text=text) sql_engine.execute(query) else: slack_client.chat_postMessage(channel=SLACK_CHANNEL, text="No Galaxies Assigned") if plot_map: ax.add_feature(Nightshade(datetime.utcnow(), alpha=0.2)) plt.title("Map of Observable Sites") plt.savefig("map.png") if send_slack: slack_client.files_upload(channels=SLACK_CHANNEL, file="map.png", title=params['GraceID']) filename = download_file(params['skymap_fits'], cache=True) skyplot([filename, '--annotate', '--geo', '--contour', '50', '90']) plt.savefig("hp.png") if send_slack: slack_client.files_upload(channels=SLACK_CHANNEL, file="hp.png", title=params['GraceID']) return
def update_skymap(grb_dic, output_dic, conf_dic): """ Function to download skymap and fill the missing info in grb_dic This will also initiate gwemopt :param grb_dic: dictionary with GRB related infos :param output_dic: dictionary with output architecture :param conf_dic: dictionary with info for selection :return: parms, updated param dictionary after loading skymap :return: map_struct, internal gwemopt structure filled when loading skymap avoid to reload it later """ # initiate gwemopt dictionary configuration # need to use absolute path dir_path = os.path.dirname(os.path.abspath(__file__)) + "/" grb_dic["skymap"]["nside"] = conf_dic["nside_flat"] params = utils_too.init_gwemopt_observation_plan( dir_path + conf_dic["config_gwemopt"]) # include in it full path to sky map fits file in the dictionary params["skymap"] = grb_dic["skymap"] # include nside from the sky map file params["nside"] = grb_dic["skymap"]["nside"] # update it with GRB specific part params = update_gwemoptconfig(grb_dic, conf_dic, params) # close the fits file # hdul.close() print("Loading skymap...") # read map to compute error regions map_struct = gwemopt.utils.read_skymap(params, is3D=params["do3D"], map_struct=params['map_struct']) # error regions i = np.flipud(np.argsort(params['map_struct']['prob'])) credible_levels = find_greedy_credible_levels( params['map_struct']['prob'][i]) cr50 = np.round( np.sum(credible_levels <= 0.5) * hp.nside2pixarea(grb_dic["skymap"]["nside"], degrees=True), 1) cr90 = np.round( np.sum(credible_levels <= 0.9) * hp.nside2pixarea(grb_dic["skymap"]["nside"], degrees=True), 1) # sorted_credible_levels = np.cumsum(grb_dic["skymap"]["probdensity"][i]) # credible_levels = np.empty_like(sorted_credible_levels) idx50 = map_struct["cumprob"] < 0.50 cr50 = len(map_struct["cumprob"][idx50]) idx90 = map_struct["cumprob"] < 0.90 cr90 = len(map_struct["cumprob"][idx90]) grb_dic["50cr"] = cr50 grb_dic["90cr"] = cr90 return params, map_struct
## Test if a Sky Location is in the 90% Credible Region i = np.flipud(np.argsort(hpx)) sorted_credible_levels = np.cumsum(hpx[i]) credible_levels = np.empty_like(sorted_credible_levels) credible_levels[i] = sorted_credible_levels credible_levels ## N.B. !! ## Observe that the values in the resulting credible level map vary ## inversely with probability density: the most probable pixel is ## assigned to the credible level 0.0, and the least likely pixel is ## assigned the credible level 1.0. credible_levels = find_greedy_credible_levels(hpx) credible_levels ## To check if the pixel that we identified in the previous section is ## within the 90% credible level, simply test if the value of the ## credible level map is less than or equal to 0.9 at that pixel: credible_levels[ipix] credible_levels[ipix] <=0.9 ##Find the Area of the 90% Credible Region ## Since we just found the credible level map, it’s easy to compute ## the 90% credible area by counting the number of pixels inside the ## 90% credible region and multiplying by the area per pixel. ## In the Python expression below, note that (credible_levels <= 0.9) ## evaluates to a binary array; when it is summed over, true values
print('S190828j/bayestar.fits.gz read-in...') print() print() #hpx, header = hp.read_map(inputdata, h=True) S190828l_npix = len(S190828l_prob) S190828j_npix = len(S190828j_prob) S190828l_nside = hp.npix2nside(S190828l_npix) S190828j_nside = hp.npix2nside(S190828j_npix) S190828l_pixarea_deg2 = hp.nside2pixarea(S190828l_nside, degrees=True) S190828j_pixarea_deg2 = hp.nside2pixarea(S190828j_nside, degrees=True) S190828l_credible_levels = find_greedy_credible_levels(S190828l_prob) S190828l_npix50 = np.count_nonzero(S190828l_credible_levels <= 0.50) S190828l_area50 = np.sum(S190828l_credible_levels <= 0.50) * hp.nside2pixarea( S190828l_nside, degrees=True) S190828l_boolArr50 = (S190828l_credible_levels <= 0.50) S190828l_result50 = np.where(S190828l_boolArr50) S190828l_npix90 = np.count_nonzero(S190828l_credible_levels <= 0.90) S190828l_area90 = np.sum(S190828l_credible_levels <= 0.90) * hp.nside2pixarea( S190828l_nside, degrees=True) S190828l_boolArr90 = (S190828l_credible_levels <= 0.90) S190828l_result90 = np.where(S190828l_boolArr90) print('S190828l: npix50, area50 ', S190828l_npix50, S190828l_area50, ' npix90, area90 ', S190828l_npix90, S190828l_area90) S190828j_credible_levels = find_greedy_credible_levels(S190828j_prob) S190828j_npix50 = np.count_nonzero(S190828j_credible_levels <= 0.50)
def load_healpix_map(outdir, fitsoutname='skymap.fits.gz', radecs=[], contour=None, annotate=True, inset=False): # Load skymap skymap, metadata = io.fits.read_sky_map(os.path.join(outdir, fitsoutname), nest=None) # Convert sky map from probability to probability per square degree. nside = hp.npix2nside(len(skymap)) deg2perpix = hp.nside2pixarea(nside, degrees=True) probperdeg2 = skymap / deg2perpix # Projection type ax = plt.axes(projection='astro hours mollweide') ax.grid() # Plot sky map. vmax = probperdeg2.max() img = ax.imshow_hpx((probperdeg2, 'ICRS'), nested=metadata['nest'], vmin=0., vmax=vmax) # Add colorbar. cb = plot.colorbar(img) cb.set_label(r'Prob. per deg²') if contour: # Add contours. cls = 100 * postprocess.find_greedy_credible_levels(skymap) cs = ax.contour_hpx((cls, 'ICRS'), nested=metadata['nest'], colors='k', linewidths=0.5, levels=contour) fmt = r'%g\%%' if rcParams['text.usetex'] else '%g%%' plt.clabel(cs, fmt=fmt, fontsize=6, inline=True) # # Add markers (e.g., for injections or external triggers). # for ra, dec in radecs: # ax.plot_coord( # SkyCoord(ra, dec, unit='deg'), '*', # markerfacecolor='white', markeredgecolor='black', markersize=10) # Try to add a zoom inset if inset: ra, dec = radecs center = SkyCoord(ra * u.deg, dec * u.deg) ax_inset = plt.axes([0.59, 0.3, 0.4, 0.4], projection='astro zoom', center=center, radius=10 * u.deg) for key in ['ra', 'dec']: ax_inset.coords[key].set_ticklabel_visible(False) ax_inset.coords[key].set_ticks_visible(False) ax.grid() ax.mark_inset_axes(ax_inset) ax.connect_inset_axes(ax_inset, 'upper left') ax.connect_inset_axes(ax_inset, 'lower left') ax_inset.scalebar((0.1, 0.1), 5 * u.deg).label() ax_inset.compass(0.9, 0.1, 0.2) ax_inset.imshow_hpx((probperdeg2, 'ICRS'), nested=metadata['nest'], vmin=0., vmax=vmax) # , cmap='cylon') ax_inset.plot(center.ra.deg, center.dec.deg, transform=ax_inset.get_transform('world'), marker=plot.reticle(), color='white', markersize=30, markeredgewidth=3) # Add a white outline to all text to make it stand out from the background. plot.outline_text(ax) ax.grid() if annotate: text = [] try: objid = metadata['objid'] except KeyError: pass else: text.append('event ID: {}'.format(objid)) if contour: pp = np.round(contour).astype(int) ii = np.round(np.searchsorted(np.sort(cls), contour) * deg2perpix).astype(int) for i, p in zip(ii, pp): # FIXME: use Unicode symbol instead of TeX '$^2$' # because of broken fonts on Scientific Linux 7. text.append(u'{:d}% area: {:d} deg²'.format(p, i, grouping=True)) ax.text(1, 1, '\n'.join(text), transform=ax.transAxes, ha='right') plt.show()