icecube-skyreader 1.3.0__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/PKG-INFO +1 -1
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/PKG-INFO +1 -1
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/requires.txt +1 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/setup.cfg +1 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/__init__.py +1 -1
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/plot/plot.py +105 -96
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/plot/plotting_tools.py +8 -6
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/result.py +7 -3
- icecube-skyreader-1.3.2/skyreader/utils/handle_map_data.py +407 -0
- icecube-skyreader-1.3.0/skyreader/utils/handle_map_data.py +0 -202
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/LICENSE +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/README.md +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/SOURCES.txt +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/dependency_links.txt +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/top_level.txt +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/setup.py +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/event_metadata.py +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/plot/__init__.py +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/py.typed +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/utils/__init__.py +0 -0
- {icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/skyreader/utils/areas.py +0 -0
|
@@ -17,7 +17,7 @@ __all__ = [
|
|
|
17
17
|
# is zero for an official release, positive for a development branch,
|
|
18
18
|
# or negative for a release candidate or beta (after the base version
|
|
19
19
|
# number has been incremented)
|
|
20
|
-
__version__ = "1.3.
|
|
20
|
+
__version__ = "1.3.2"
|
|
21
21
|
version_info = (
|
|
22
22
|
int(__version__.split(".")[0]),
|
|
23
23
|
int(__version__.split(".")[1]),
|
|
@@ -8,8 +8,8 @@ import pickle
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import List, Union
|
|
10
10
|
|
|
11
|
+
import copy
|
|
11
12
|
import healpy # type: ignore[import]
|
|
12
|
-
# import mhealpy
|
|
13
13
|
import matplotlib # type: ignore[import]
|
|
14
14
|
import meander # type: ignore[import]
|
|
15
15
|
import numpy as np
|
|
@@ -28,7 +28,12 @@ from .plotting_tools import (
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
from ..utils.areas import calculate_area, get_contour_areas
|
|
31
|
-
from ..utils.handle_map_data import
|
|
31
|
+
from ..utils.handle_map_data import (
|
|
32
|
+
extract_map,
|
|
33
|
+
get_contour_levels,
|
|
34
|
+
prepare_flattened_map,
|
|
35
|
+
prepare_multiorder_map,
|
|
36
|
+
)
|
|
32
37
|
from ..result import SkyScanResult
|
|
33
38
|
|
|
34
39
|
LOGGER = logging.getLogger("skyreader.plot")
|
|
@@ -92,10 +97,11 @@ class SkyScanPlotter:
|
|
|
92
97
|
plot_filename = f"{unique_id}.{addition_to_filename}pdf"
|
|
93
98
|
LOGGER.info(f"saving plot to {plot_filename}")
|
|
94
99
|
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
grid_value, grid_ra, grid_dec, equatorial_map, _ = extract_map(
|
|
101
|
+
result,
|
|
102
|
+
llh_map,
|
|
103
|
+
angular_error_floor,
|
|
104
|
+
remove_min_val=not llh_map,
|
|
99
105
|
)
|
|
100
106
|
|
|
101
107
|
grid_pix = healpy.ang2pix(max(nsides), np.pi/2. - DEC, RA)
|
|
@@ -129,9 +135,17 @@ class SkyScanPlotter:
|
|
|
129
135
|
self.PLOT_COLORMAP.name.rstrip('_r')
|
|
130
136
|
]
|
|
131
137
|
text_colorbar = r"log10$(p)$"
|
|
132
|
-
vmin = np.
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
vmin = np.nanmin(
|
|
139
|
+
np.log10(equatorial_map[equatorial_map != 0.])
|
|
140
|
+
)
|
|
141
|
+
vmax = np.nanmax(
|
|
142
|
+
np.log10(equatorial_map[equatorial_map != 0.])
|
|
143
|
+
)
|
|
144
|
+
map_to_plot = copy.copy(plotting_map)
|
|
145
|
+
map_to_plot[plotting_map != 0.] = np.log10(
|
|
146
|
+
plotting_map[plotting_map != 0.]
|
|
147
|
+
)
|
|
148
|
+
map_to_plot[plotting_map == 0.] = np.nan
|
|
135
149
|
equatorial_map = np.ma.masked_invalid(equatorial_map)
|
|
136
150
|
map_to_plot = np.ma.masked_invalid(map_to_plot)
|
|
137
151
|
|
|
@@ -378,20 +392,20 @@ class SkyScanPlotter:
|
|
|
378
392
|
return Theta, Phi
|
|
379
393
|
|
|
380
394
|
def create_plot_zoomed(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
self,
|
|
396
|
+
result: SkyScanResult,
|
|
397
|
+
extra_ra=np.nan,
|
|
398
|
+
extra_dec=np.nan,
|
|
399
|
+
extra_radius=np.nan,
|
|
400
|
+
systematics=False,
|
|
401
|
+
plot_bounding_box=False,
|
|
402
|
+
plot_4fgl=False,
|
|
403
|
+
circular=False,
|
|
404
|
+
circular_err50=0.2,
|
|
405
|
+
circular_err90=0.7,
|
|
406
|
+
angular_error_floor=None, # if not None, sigma of the
|
|
407
|
+
# gaussian to convolute the map with in deg.
|
|
408
|
+
llh_map=True
|
|
395
409
|
):
|
|
396
410
|
"""Uses healpy to plot a map."""
|
|
397
411
|
|
|
@@ -427,7 +441,7 @@ class SkyScanPlotter:
|
|
|
427
441
|
plot_filename = unique_id + ".plot_zoomed.pdf"
|
|
428
442
|
|
|
429
443
|
(
|
|
430
|
-
grid_value, grid_ra, grid_dec, equatorial_map
|
|
444
|
+
grid_value, grid_ra, grid_dec, equatorial_map, uniq_array
|
|
431
445
|
) = extract_map(result, llh_map, angular_error_floor)
|
|
432
446
|
min_dec = grid_dec[0]
|
|
433
447
|
min_ra = grid_ra[0]
|
|
@@ -444,11 +458,24 @@ class SkyScanPlotter:
|
|
|
444
458
|
sample_points = np.array([np.pi/2 - grid_dec, grid_ra]).T
|
|
445
459
|
|
|
446
460
|
if not circular:
|
|
461
|
+
grid_values_for_contours = copy.copy(grid_value)
|
|
447
462
|
if llh_map:
|
|
448
|
-
|
|
463
|
+
# get rid of nan values only for the contours
|
|
464
|
+
# this avoids crashes during plotting
|
|
465
|
+
grid_values_for_contours[
|
|
466
|
+
np.isnan(grid_values_for_contours)
|
|
467
|
+
] = np.nanmax(equatorial_map)
|
|
449
468
|
contour_levels_for_contours = contour_levels
|
|
450
469
|
else:
|
|
451
|
-
grid_values_for_contours
|
|
470
|
+
grid_values_for_contours[
|
|
471
|
+
np.logical_or(
|
|
472
|
+
np.isnan(grid_values_for_contours),
|
|
473
|
+
grid_values_for_contours == 0.
|
|
474
|
+
)
|
|
475
|
+
] = np.nanmin(equatorial_map[equatorial_map > 0.])
|
|
476
|
+
grid_values_for_contours = np.log(
|
|
477
|
+
grid_values_for_contours
|
|
478
|
+
)
|
|
452
479
|
contour_levels_for_contours = np.log(contour_levels)
|
|
453
480
|
# Get contours from healpix map
|
|
454
481
|
contours_by_level = meander.spherical_contours(
|
|
@@ -599,8 +626,7 @@ class SkyScanPlotter:
|
|
|
599
626
|
contour_colors,
|
|
600
627
|
contours_by_level
|
|
601
628
|
):
|
|
602
|
-
contour_label = contour_label + ' - area: {
|
|
603
|
-
contour_area_sqdeg)
|
|
629
|
+
contour_label = contour_label + f' - area: {contour_area_sqdeg:.2f} sqdeg'
|
|
604
630
|
first = True
|
|
605
631
|
for contour in contours:
|
|
606
632
|
theta, phi = contour.T
|
|
@@ -640,7 +666,6 @@ class SkyScanPlotter:
|
|
|
640
666
|
rot=(lon, lat, 0),
|
|
641
667
|
bounds=(lower_lon, upper_lon, lower_lat, upper_lat)
|
|
642
668
|
)
|
|
643
|
-
|
|
644
669
|
if plot_4fgl:
|
|
645
670
|
# Overlay 4FGL sources
|
|
646
671
|
plot_catalog(
|
|
@@ -669,13 +694,11 @@ class SkyScanPlotter:
|
|
|
669
694
|
print(contain_txt)
|
|
670
695
|
|
|
671
696
|
print(
|
|
672
|
-
"Contour Area (50%):",
|
|
673
|
-
contour_areas[0],
|
|
697
|
+
f"Contour Area (50%): {contour_areas[0]}",
|
|
674
698
|
"square degrees (scaled)"
|
|
675
699
|
)
|
|
676
700
|
print(
|
|
677
|
-
"Contour Area (90%):",
|
|
678
|
-
contour_areas[1],
|
|
701
|
+
f"Contour Area (90%): {contour_areas[1]}",
|
|
679
702
|
"square degrees (scaled)"
|
|
680
703
|
)
|
|
681
704
|
|
|
@@ -712,7 +735,6 @@ class SkyScanPlotter:
|
|
|
712
735
|
# join end to beginning
|
|
713
736
|
bounding_ras_list.append(bounding_ras_list[0])
|
|
714
737
|
bounding_decs_list.append(bounding_decs_list[0])
|
|
715
|
-
|
|
716
738
|
bounding_ras: np.ndarray = np.asarray(bounding_ras_list)
|
|
717
739
|
bounding_decs: np.ndarray = np.asarray(bounding_decs_list)
|
|
718
740
|
bounding_phi = np.radians(bounding_ras)
|
|
@@ -723,7 +745,7 @@ class SkyScanPlotter:
|
|
|
723
745
|
# convert to square-degrees
|
|
724
746
|
bounding_contour_area *= (180.*180.)/(np.pi*np.pi)
|
|
725
747
|
contour_label = r'90% Bounding rectangle' + \
|
|
726
|
-
' - area: {
|
|
748
|
+
f' - area: {bounding_contour_area:.2f} sqdeg'
|
|
727
749
|
healpy.projplot(
|
|
728
750
|
bounding_theta,
|
|
729
751
|
bounding_phi,
|
|
@@ -733,32 +755,6 @@ class SkyScanPlotter:
|
|
|
733
755
|
label=contour_label
|
|
734
756
|
)
|
|
735
757
|
|
|
736
|
-
# Output contours in RA, dec instead of theta, phi
|
|
737
|
-
saving_contours: list = []
|
|
738
|
-
for contours in contours_by_level:
|
|
739
|
-
saving_contours.append([])
|
|
740
|
-
for contour in contours:
|
|
741
|
-
saving_contours[-1].append([])
|
|
742
|
-
theta, phi = contour.T
|
|
743
|
-
ras = phi
|
|
744
|
-
decs = np.pi/2 - theta
|
|
745
|
-
for tmp_ra, tmp_dec in zip(ras, decs):
|
|
746
|
-
saving_contours[-1][-1].append([tmp_ra, tmp_dec])
|
|
747
|
-
|
|
748
|
-
# Save the individual contours, send messages
|
|
749
|
-
for i, val in enumerate(["50", "90"]):
|
|
750
|
-
ras = list(np.asarray(saving_contours[i][0]).T[0])
|
|
751
|
-
decs = list(np.asarray(saving_contours[i][0]).T[1])
|
|
752
|
-
tab = {"ra (rad)": ras, "dec (rad)": decs}
|
|
753
|
-
savename = unique_id + ".contour_" + val + ".txt"
|
|
754
|
-
try:
|
|
755
|
-
LOGGER.info("Dumping to {savename}")
|
|
756
|
-
ascii.write(tab, savename, overwrite=True)
|
|
757
|
-
except OSError:
|
|
758
|
-
LOGGER.error(
|
|
759
|
-
"OS Error prevented contours from being written, "
|
|
760
|
-
"maybe a memory issue. Error is:\n{err}")
|
|
761
|
-
|
|
762
758
|
uncertainty = [(ra_minus, ra_plus), (dec_minus, dec_plus)]
|
|
763
759
|
fits_header = format_fits_header(
|
|
764
760
|
(
|
|
@@ -776,18 +772,15 @@ class SkyScanPlotter:
|
|
|
776
772
|
|
|
777
773
|
# Plot the original online reconstruction location
|
|
778
774
|
if np.sum(np.isnan([extra_ra, extra_dec, extra_radius])) == 0:
|
|
779
|
-
|
|
780
775
|
# dist = angular_distance(minRA, minDec, extra_ra * np.pi/180.,
|
|
781
776
|
# extra_dec * np.pi/180.)
|
|
782
777
|
# print("Millipede best fit is", dist /(np.pi *
|
|
783
778
|
# extra_radius/(1.177 * 180.)), "sigma from reported best fit")
|
|
784
|
-
|
|
785
779
|
extra_ra_rad = np.radians(extra_ra)
|
|
786
780
|
extra_dec_rad = np.radians(extra_dec)
|
|
787
781
|
extra_radius_rad = np.radians(extra_radius)
|
|
788
782
|
extra_lon = extra_ra_rad
|
|
789
783
|
extra_lat = extra_dec_rad
|
|
790
|
-
|
|
791
784
|
healpy.projscatter(
|
|
792
785
|
np.degrees(extra_lon),
|
|
793
786
|
np.degrees(extra_lat),
|
|
@@ -816,52 +809,34 @@ class SkyScanPlotter:
|
|
|
816
809
|
color=cont_col,
|
|
817
810
|
linestyle=cont_sty
|
|
818
811
|
)
|
|
819
|
-
|
|
820
812
|
plt.legend(fontsize=6, loc="lower left")
|
|
821
813
|
|
|
822
|
-
# Dump the whole contour
|
|
823
|
-
path = unique_id + ".contour.pkl"
|
|
824
|
-
print("Saving contour to", path)
|
|
825
|
-
with open(path, "wb") as f:
|
|
826
|
-
pickle.dump(saving_contours, f)
|
|
827
|
-
|
|
828
|
-
# logic for multiordermaps -> for future developements
|
|
829
|
-
|
|
830
|
-
# save multiorder version of the map
|
|
831
|
-
# multiorder_map = mhealpy.HealpixMap(
|
|
832
|
-
# grid_value, uniq_array
|
|
833
|
-
# )
|
|
834
|
-
# multiorder_map.write_map(
|
|
835
|
-
# f"{unique_id}.skymap_nside_{mmap_nside}.multiorder.fits.gz",
|
|
836
|
-
# column_names=["PROBABILITY"],
|
|
837
|
-
# extra_header=fits_header,
|
|
838
|
-
# overwrite=True,
|
|
839
|
-
# )
|
|
840
|
-
|
|
841
|
-
if llh_map:
|
|
842
|
-
column_names = ['2DLLH']
|
|
843
|
-
else:
|
|
844
|
-
# avoid excessively heavy data format for the flattened map
|
|
845
|
-
equatorial_map[
|
|
846
|
-
equatorial_map < 1e-16
|
|
847
|
-
] = np.mean(
|
|
848
|
-
equatorial_map[equatorial_map < 1e-16]
|
|
849
|
-
)
|
|
850
|
-
column_names = ["PROBABILITY"]
|
|
851
|
-
|
|
852
814
|
# save flattened map
|
|
815
|
+
equatorial_map, column_names = prepare_flattened_map(
|
|
816
|
+
equatorial_map, llh_map
|
|
817
|
+
)
|
|
853
818
|
if llh_map:
|
|
854
819
|
type_map = "llh"
|
|
855
820
|
else:
|
|
856
821
|
type_map = "probability"
|
|
822
|
+
filename_main = f"{unique_id}.skymap_nside_{mmap_nside}_{type_map}"
|
|
857
823
|
healpy.write_map(
|
|
858
|
-
f"{
|
|
824
|
+
self.output_dir / f"{filename_main}.fits.gz",
|
|
859
825
|
equatorial_map,
|
|
860
826
|
coord='C',
|
|
861
827
|
column_names=column_names,
|
|
862
828
|
extra_header=fits_header,
|
|
863
829
|
overwrite=True
|
|
864
830
|
)
|
|
831
|
+
multiorder_map, column_names = prepare_multiorder_map(
|
|
832
|
+
grid_value, uniq_array, llh_map, column_names
|
|
833
|
+
)
|
|
834
|
+
multiorder_map.write_map(
|
|
835
|
+
self.output_dir / f"{filename_main}.multiorder.fits.gz",
|
|
836
|
+
column_names=column_names,
|
|
837
|
+
extra_header=fits_header,
|
|
838
|
+
overwrite=True,
|
|
839
|
+
)
|
|
865
840
|
|
|
866
841
|
# Save the figure
|
|
867
842
|
LOGGER.info(f"Saving: {plot_filename}...")
|
|
@@ -873,6 +848,40 @@ class SkyScanPlotter:
|
|
|
873
848
|
bbox_inches='tight'
|
|
874
849
|
)
|
|
875
850
|
|
|
851
|
+
self._save_contours(contours_by_level, unique_id)
|
|
876
852
|
LOGGER.info("done.")
|
|
877
853
|
|
|
878
854
|
plt.close()
|
|
855
|
+
|
|
856
|
+
def _save_contours(self, contours_by_level, unique_id) -> None:
|
|
857
|
+
# Output contours in RA, dec instead of theta, phi
|
|
858
|
+
saving_contours: list = []
|
|
859
|
+
for contours in contours_by_level:
|
|
860
|
+
saving_contours.append([])
|
|
861
|
+
for contour in contours:
|
|
862
|
+
saving_contours[-1].append([])
|
|
863
|
+
theta, phi = contour.T
|
|
864
|
+
ras = phi
|
|
865
|
+
decs = np.pi/2 - theta
|
|
866
|
+
for tmp_ra, tmp_dec in zip(ras, decs):
|
|
867
|
+
saving_contours[-1][-1].append([tmp_ra, tmp_dec])
|
|
868
|
+
|
|
869
|
+
# Save the individual contours
|
|
870
|
+
for i, val in enumerate(["50", "90"]):
|
|
871
|
+
ras = list(np.asarray(saving_contours[i][0]).T[0])
|
|
872
|
+
decs = list(np.asarray(saving_contours[i][0]).T[1])
|
|
873
|
+
tab = {"ra (rad)": ras, "dec (rad)": decs}
|
|
874
|
+
savepath = self.output_dir / f"{unique_id}.contour_{val}.txt"
|
|
875
|
+
try:
|
|
876
|
+
LOGGER.info(f"Dumping to {savepath}")
|
|
877
|
+
ascii.write(tab, savepath, overwrite=True)
|
|
878
|
+
except OSError:
|
|
879
|
+
LOGGER.error(
|
|
880
|
+
"OS Error prevented contours from being written, "
|
|
881
|
+
"maybe a memory issue. Error is:\n{err}")
|
|
882
|
+
|
|
883
|
+
# Dump the whole contour
|
|
884
|
+
path = self.output_dir / f"{unique_id}.contour.pkl"
|
|
885
|
+
print("Saving contour to", path)
|
|
886
|
+
with open(path, "wb") as f:
|
|
887
|
+
pickle.dump(saving_contours, f)
|
|
@@ -16,7 +16,9 @@ from matplotlib.transforms import Affine2D # type: ignore[import]
|
|
|
16
16
|
|
|
17
17
|
matplotlib.use('agg')
|
|
18
18
|
|
|
19
|
-
def format_fits_header(
|
|
19
|
+
def format_fits_header(
|
|
20
|
+
event_id_tuple, mjd, ra, dec, uncertainty, llh_map,
|
|
21
|
+
):
|
|
20
22
|
"""Prepare some of the relevant event information for a fits file
|
|
21
23
|
header."""
|
|
22
24
|
run_id, event_id, event_type = event_id_tuple
|
|
@@ -24,14 +26,14 @@ def format_fits_header(event_id_tuple, mjd, ra, dec, uncertainty, llh_map):
|
|
|
24
26
|
if llh_map:
|
|
25
27
|
uncertainty_comment = 'Change in 2LLH based on Wilks theorem'
|
|
26
28
|
else:
|
|
27
|
-
uncertainty_comment = '
|
|
29
|
+
uncertainty_comment = 'Highest posterior density 90% credible region'
|
|
28
30
|
|
|
29
31
|
header = [
|
|
30
32
|
('RUNID', run_id),
|
|
31
33
|
('EVENTID', event_id),
|
|
32
34
|
('SENDER', 'IceCube Collaboration'),
|
|
33
35
|
('EventMJD', mjd),
|
|
34
|
-
('I3TYPE', '
|
|
36
|
+
('I3TYPE', f'{event_type}','Alert Type'),
|
|
35
37
|
('RA', np.round(ra,2),'Degree'),
|
|
36
38
|
('DEC', np.round(dec,2),'Degree'),
|
|
37
39
|
('RA_ERR_PLUS', np.round(uncertainty[0][1],2),
|
|
@@ -42,7 +44,7 @@ def format_fits_header(event_id_tuple, mjd, ra, dec, uncertainty, llh_map):
|
|
|
42
44
|
'90% containment error high'),
|
|
43
45
|
('DEC_ERR_MINUS', np.round(np.abs(uncertainty[1][0]),2),
|
|
44
46
|
'90% containment error low'),
|
|
45
|
-
('COMMENTS', '
|
|
47
|
+
('COMMENTS', '90% uncertainty location' \
|
|
46
48
|
+ ' => ' + uncertainty_comment),
|
|
47
49
|
('NOTE', 'Please ignore pixels with infinite or NaN values.' \
|
|
48
50
|
+ ' They are rare cases of the minimizer failing to converge')
|
|
@@ -97,12 +99,12 @@ def hp_ticklabels(zoom=False, lonra=None, latra=None, rot=None, bounds=None):
|
|
|
97
99
|
pe = [path_effects.Stroke(linewidth=1.5, foreground='white'),
|
|
98
100
|
path_effects.Normal()]
|
|
99
101
|
for _ in lats:
|
|
100
|
-
healpy.projtext(lon_offset, _, "{:.0f}
|
|
102
|
+
healpy.projtext(lon_offset, _, f"{_:.0f}$^\\circ$",
|
|
101
103
|
lonlat=True, path_effects=pe, fontsize=10)
|
|
102
104
|
if zoom:
|
|
103
105
|
for _ in lons:
|
|
104
106
|
healpy.projtext(_, lat_offset,
|
|
105
|
-
"{:.0f}
|
|
107
|
+
f"{_:.0f}$^\\circ$", lonlat=True,
|
|
106
108
|
path_effects=pe, fontsize=10)
|
|
107
109
|
else:
|
|
108
110
|
ax.annotate(r"$\bf{-180^\circ}$", xy=(1.7, 0.625), size="medium")
|
|
@@ -217,6 +217,10 @@ class SkyScanResult:
|
|
|
217
217
|
else:
|
|
218
218
|
self.logger.warning("Metadata doesn't seem to exist and will not be used for plotting.")
|
|
219
219
|
return EventMetadata(0, 0, '', 0, False)
|
|
220
|
+
|
|
221
|
+
def get_results_per_nside(self, nside: int) -> np.ndarray:
|
|
222
|
+
"get the results for the pixels at a given nside"
|
|
223
|
+
return self.result[f"nside-{nside}"]
|
|
220
224
|
|
|
221
225
|
def isclose_nside(self,
|
|
222
226
|
other: "SkyScanResult",
|
|
@@ -388,7 +392,7 @@ class SkyScanResult:
|
|
|
388
392
|
try:
|
|
389
393
|
first = next(iter(self.result.values()))
|
|
390
394
|
except StopIteration: # no results yet
|
|
391
|
-
np.savez(filename, **self.result)
|
|
395
|
+
np.savez(filename, **self.result) # type: ignore
|
|
392
396
|
return Path(filename)
|
|
393
397
|
|
|
394
398
|
try:
|
|
@@ -405,9 +409,9 @@ class SkyScanResult:
|
|
|
405
409
|
],
|
|
406
410
|
dtype=metadata_dtype,
|
|
407
411
|
)
|
|
408
|
-
np.savez(filename, header=header, **self.result)
|
|
412
|
+
np.savez(filename, header=header, **self.result) # type: ignore
|
|
409
413
|
except (TypeError, AttributeError):
|
|
410
|
-
np.savez(filename, **self.result)
|
|
414
|
+
np.savez(filename, **self.result) # type: ignore
|
|
411
415
|
|
|
412
416
|
return Path(filename)
|
|
413
417
|
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
from typing import Union, Tuple, List
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import healpy # type: ignore[import]
|
|
5
|
+
import mhealpy # type: ignore[import]
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ..result import SkyScanResult
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger("skyreader.extract_map")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_map(
|
|
14
|
+
result: SkyScanResult,
|
|
15
|
+
llh_map: bool = True,
|
|
16
|
+
angular_error_floor: Union[None, float] = None,
|
|
17
|
+
remove_min_val: bool = True,
|
|
18
|
+
) -> Tuple[
|
|
19
|
+
np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray
|
|
20
|
+
]:
|
|
21
|
+
"""
|
|
22
|
+
Extract from the output of skymap_scanner the healpy map
|
|
23
|
+
args:
|
|
24
|
+
- result: SkyScanResult. The output of the Skymap Scanner
|
|
25
|
+
- llh_map: bool = True. If True the likelihood will be plotted,
|
|
26
|
+
otherwise the probability
|
|
27
|
+
- angular_error_floor: Union[None, float] = None. if not None,
|
|
28
|
+
sigma of the gaussian to convolute the map with in deg.
|
|
29
|
+
- remove_min_val: bool = True. Remove minimum value from -llh
|
|
30
|
+
no effect if probability map.
|
|
31
|
+
|
|
32
|
+
returns:
|
|
33
|
+
- grid_value: value-per-scanned-pixel (pixels with
|
|
34
|
+
different nsides)
|
|
35
|
+
- grid_ra: right ascension for each pixel in grid_value
|
|
36
|
+
- grid_dec: declination for each pixel in grid_value
|
|
37
|
+
- equatorial_map: healpix map with maximum nside (all pixels
|
|
38
|
+
with same nside)
|
|
39
|
+
- uniq_array: uniqs for all the pixels of the map
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
grid_map = dict()
|
|
43
|
+
|
|
44
|
+
nsides = result.nsides
|
|
45
|
+
max_nside = max(nsides)
|
|
46
|
+
equatorial_map = np.full(healpy.nside2npix(max_nside), np.nan)
|
|
47
|
+
uniq_list = []
|
|
48
|
+
|
|
49
|
+
for nside in nsides:
|
|
50
|
+
LOGGER.info(f"constructing map for nside {nside}...")
|
|
51
|
+
npix = healpy.nside2npix(nside)
|
|
52
|
+
map_data = result.get_results_per_nside(nside)
|
|
53
|
+
pixels = map_data['index']
|
|
54
|
+
values = map_data['llh']
|
|
55
|
+
this_map = np.full(npix, np.nan)
|
|
56
|
+
this_map[pixels] = values
|
|
57
|
+
if nside < max_nside:
|
|
58
|
+
this_map = healpy.ud_grade(this_map, max_nside)
|
|
59
|
+
mask = np.logical_and(~np.isnan(this_map), np.isfinite(this_map))
|
|
60
|
+
equatorial_map[mask] = this_map[mask]
|
|
61
|
+
|
|
62
|
+
for pixel_data in result.get_results_per_nside(nside):
|
|
63
|
+
pixel = pixel_data['index']
|
|
64
|
+
value = pixel_data['llh']
|
|
65
|
+
nested_pixel = healpy.ring2nest(nside, pixel)
|
|
66
|
+
uniq = 4*nside*nside + nested_pixel
|
|
67
|
+
uniq_list.append(uniq)
|
|
68
|
+
tmp_theta, tmp_phi = healpy.pix2ang(nside, pixel)
|
|
69
|
+
tmp_dec = np.pi/2 - tmp_theta
|
|
70
|
+
tmp_ra = tmp_phi
|
|
71
|
+
grid_map[(tmp_dec, tmp_ra)] = value
|
|
72
|
+
|
|
73
|
+
# In case of pointed scans, it helps filling the first nside
|
|
74
|
+
# with empty pixels (especially for saving the multiorder map)
|
|
75
|
+
if nside == nsides[0]:
|
|
76
|
+
grid_map, uniq_list = _fill_first_nside_empty(
|
|
77
|
+
nside, result, grid_map, uniq_list
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
LOGGER.info(f"done with map for nside {nside}...")
|
|
81
|
+
|
|
82
|
+
grid_dec_list, grid_ra_list, grid_value_list = [], [], []
|
|
83
|
+
|
|
84
|
+
for (dec, ra), value in grid_map.items():
|
|
85
|
+
grid_dec_list.append(dec)
|
|
86
|
+
grid_ra_list.append(ra)
|
|
87
|
+
grid_value_list.append(value)
|
|
88
|
+
grid_dec: np.ndarray = np.asarray(grid_dec_list)
|
|
89
|
+
grid_ra: np.ndarray = np.asarray(grid_ra_list)
|
|
90
|
+
grid_value: np.ndarray = np.asarray(grid_value_list)
|
|
91
|
+
uniq_array: np.ndarray = np.asarray(uniq_list)
|
|
92
|
+
|
|
93
|
+
sorting_indices = np.argsort(grid_value)
|
|
94
|
+
grid_value = grid_value[sorting_indices]
|
|
95
|
+
grid_dec = grid_dec[sorting_indices]
|
|
96
|
+
grid_ra = grid_ra[sorting_indices]
|
|
97
|
+
uniq_array = uniq_array[sorting_indices]
|
|
98
|
+
|
|
99
|
+
min_value = grid_value[0]
|
|
100
|
+
|
|
101
|
+
if remove_min_val or (not llh_map):
|
|
102
|
+
# renormalize
|
|
103
|
+
grid_value = grid_value - min_value
|
|
104
|
+
min_value = 0.
|
|
105
|
+
|
|
106
|
+
# renormalize
|
|
107
|
+
equatorial_map[np.isinf(equatorial_map)] = np.nan
|
|
108
|
+
equatorial_map -= np.nanmin(equatorial_map)
|
|
109
|
+
|
|
110
|
+
if llh_map:
|
|
111
|
+
# show 2 * delta_LLH
|
|
112
|
+
grid_value = grid_value * 2.
|
|
113
|
+
equatorial_map *= 2.
|
|
114
|
+
else:
|
|
115
|
+
# Convert to probability
|
|
116
|
+
equatorial_map = np.exp(-1. * equatorial_map)
|
|
117
|
+
equatorial_map = equatorial_map / np.nansum(equatorial_map)
|
|
118
|
+
min_map = np.nanmin(equatorial_map)
|
|
119
|
+
|
|
120
|
+
if angular_error_floor is not None:
|
|
121
|
+
# convolute with a gaussian. angular_error_floor is the
|
|
122
|
+
# sigma in deg.
|
|
123
|
+
# nan values are a problem for the convolution and the contours
|
|
124
|
+
equatorial_map = healpy.smoothing(
|
|
125
|
+
np.nan_to_num(equatorial_map),
|
|
126
|
+
sigma=np.deg2rad(angular_error_floor),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# normalize map
|
|
130
|
+
min_map = np.nanmin(equatorial_map[equatorial_map >= 0.])
|
|
131
|
+
equatorial_map = equatorial_map.clip(min_map, None)
|
|
132
|
+
normalization = np.nansum(equatorial_map)
|
|
133
|
+
equatorial_map = equatorial_map / normalization
|
|
134
|
+
|
|
135
|
+
# obtain values for grid map
|
|
136
|
+
grid_value = healpy.get_interp_val(
|
|
137
|
+
equatorial_map, np.pi/2 - grid_dec, grid_ra
|
|
138
|
+
)
|
|
139
|
+
grid_value = grid_value.clip(min_map, None)
|
|
140
|
+
|
|
141
|
+
return grid_value, grid_ra, grid_dec, equatorial_map, uniq_array
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _fill_first_nside_empty(
|
|
145
|
+
nside: int,
|
|
146
|
+
result: SkyScanResult,
|
|
147
|
+
grid_map: dict,
|
|
148
|
+
uniq_list: list,
|
|
149
|
+
):
|
|
150
|
+
"""
|
|
151
|
+
Fill the grid_map at the first nside with empty pixels
|
|
152
|
+
"""
|
|
153
|
+
tot_npix = healpy.nside2npix(nside)
|
|
154
|
+
if tot_npix > len(result.get_results_per_nside(nside)):
|
|
155
|
+
ring_pixels = np.arange(tot_npix)
|
|
156
|
+
nest_pixels = healpy.ring2nest(nside, ring_pixels)
|
|
157
|
+
uniq_pixels = mhealpy.nest2uniq(nside, nest_pixels)
|
|
158
|
+
for uni, rin in zip(uniq_pixels, ring_pixels):
|
|
159
|
+
if uni not in uniq_list:
|
|
160
|
+
uniq_list.append(uni)
|
|
161
|
+
tmp_theta, tmp_phi = healpy.pix2ang(nside, rin)
|
|
162
|
+
tmp_dec = np.pi/2 - tmp_theta
|
|
163
|
+
tmp_ra = tmp_phi
|
|
164
|
+
grid_map[(tmp_dec, tmp_ra)] = np.nan
|
|
165
|
+
return grid_map, uniq_list
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_contour_levels(
|
|
169
|
+
equatorial_map: np.ndarray,
|
|
170
|
+
llh_map: bool = True,
|
|
171
|
+
systematics: bool = False,
|
|
172
|
+
):
|
|
173
|
+
"""
|
|
174
|
+
get contour levels for the desired map
|
|
175
|
+
|
|
176
|
+
args:
|
|
177
|
+
- equatorial_map: np.ndarray. Necessary in case of
|
|
178
|
+
probability map.
|
|
179
|
+
- llh_map: bool. If True llh levels, otherwise probability
|
|
180
|
+
levels.
|
|
181
|
+
- systematics: bool. Only for llh maps. If True include
|
|
182
|
+
recalibrated llh values from Pan-Starrs event 127852
|
|
183
|
+
(IC160427A syst.)
|
|
184
|
+
|
|
185
|
+
returns:
|
|
186
|
+
- contour_levels, levels of the contours
|
|
187
|
+
- contour_labels, respective labels for the contours
|
|
188
|
+
- contour_colors, respective colors for the contours
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
# Calculate the contour levels
|
|
192
|
+
if llh_map: # likelihood map
|
|
193
|
+
min_value = np.nanmin(equatorial_map)
|
|
194
|
+
if systematics:
|
|
195
|
+
# from Pan-Starrs event 127852
|
|
196
|
+
# these are values determined from MC by Will on the TS (2*LLH)
|
|
197
|
+
# Not clear yet how to translate this for the probability map
|
|
198
|
+
contour_levels = (np.array([22.2, 64.2])+min_value)
|
|
199
|
+
contour_labels = [
|
|
200
|
+
r'50% (IC160427A syst.)', r'90% (IC160427A syst.)'
|
|
201
|
+
]
|
|
202
|
+
contour_colors = ['k', 'r']
|
|
203
|
+
# Wilks
|
|
204
|
+
else:
|
|
205
|
+
contour_levels = (
|
|
206
|
+
np.array([1.39, 4.61, 11.83, 28.74])+min_value
|
|
207
|
+
)[:3]
|
|
208
|
+
contour_labels = [
|
|
209
|
+
r'50%', r'90%', r'3$\sigma$', r'5$\sigma$'
|
|
210
|
+
][:3]
|
|
211
|
+
contour_colors = ['k', 'r', 'g', 'b'][:3]
|
|
212
|
+
else: # probability map
|
|
213
|
+
if systematics:
|
|
214
|
+
raise AssertionError(
|
|
215
|
+
"No corrected values for contours in probability maps"
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
sorted_values = np.sort(equatorial_map)[::-1]
|
|
219
|
+
probability_levels = (
|
|
220
|
+
np.array([0.5, 0.9, 1-1.35e-3, 1-2.87e-7])
|
|
221
|
+
)[:3]
|
|
222
|
+
contour_levels = []
|
|
223
|
+
for prob in probability_levels:
|
|
224
|
+
level_index = (
|
|
225
|
+
np.nancumsum(sorted_values) >= prob
|
|
226
|
+
).tolist().index(True)
|
|
227
|
+
level = sorted_values[level_index]
|
|
228
|
+
contour_levels.append(level)
|
|
229
|
+
contour_labels = [r'50%', r'90%', r'3$\sigma$', r'5$\sigma$'][:3]
|
|
230
|
+
contour_colors = ['k', 'r', 'g', 'b'][:3]
|
|
231
|
+
|
|
232
|
+
return contour_levels, contour_labels, contour_colors
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def find_pixels_double_nside(
|
|
236
|
+
nside: int,
|
|
237
|
+
indexes: np.ndarray,
|
|
238
|
+
):
|
|
239
|
+
"""
|
|
240
|
+
Given indexes of pixels at a given nside, find which are
|
|
241
|
+
the pixels inside these pixels with a double nside
|
|
242
|
+
|
|
243
|
+
args:
|
|
244
|
+
- nside: int. nside of the pixels to investigate.
|
|
245
|
+
- indexes: np.ndarray. indexes on ring ordering of the pixels
|
|
246
|
+
to investigate.
|
|
247
|
+
|
|
248
|
+
returns:
|
|
249
|
+
- idxs_inside: np.ndarray, array containing an array for each
|
|
250
|
+
initial pixel which tells which are the pixels with double
|
|
251
|
+
nside inside that pixel. The pixels are in ring ordering.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
vecs = healpy.boundaries(nside, indexes)
|
|
255
|
+
transposed_vecs = np.transpose(vecs, axes=(0,2,1))
|
|
256
|
+
idxs_inside = np.array(
|
|
257
|
+
[healpy.query_polygon(nside*2, vs) for vs in transposed_vecs]
|
|
258
|
+
)
|
|
259
|
+
return idxs_inside
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def already_filled_uniqs_for_nside(
|
|
263
|
+
nside: int,
|
|
264
|
+
next_nside: int,
|
|
265
|
+
uniqs: np.ndarray
|
|
266
|
+
):
|
|
267
|
+
"""
|
|
268
|
+
Check if among the input uniqs there are pixels at a finer nside
|
|
269
|
+
which are inside other pixels with a bigger nside
|
|
270
|
+
|
|
271
|
+
args:
|
|
272
|
+
-nside: int. nside of the pixels to see if there are finer pixels
|
|
273
|
+
inside.
|
|
274
|
+
-next_nside: int. nside of the finer pixes which we want to see
|
|
275
|
+
if they are inside the coarser pixels
|
|
276
|
+
-uniqs: np.ndarray. Uniqs for all the pixels in the map
|
|
277
|
+
|
|
278
|
+
returns:
|
|
279
|
+
already_filled_uniqs: np.ndarray. Uniqs of the coarser pixels
|
|
280
|
+
which are already filled.
|
|
281
|
+
"""
|
|
282
|
+
nside_per_pixel = mhealpy.uniq2nside(uniqs)
|
|
283
|
+
uniqs_nside = uniqs[nside_per_pixel == nside]
|
|
284
|
+
uniqs_next_nside = uniqs[nside_per_pixel == next_nside]
|
|
285
|
+
pixels_nside = healpy.nest2ring(
|
|
286
|
+
nside, mhealpy.uniq2nest(uniqs_nside)[1]
|
|
287
|
+
)
|
|
288
|
+
idxs_double_nside = find_pixels_double_nside(nside, pixels_nside)
|
|
289
|
+
already_filled_uniqs = []
|
|
290
|
+
for uniq_original_nside, idxs_pixel in zip(
|
|
291
|
+
uniqs_nside, idxs_double_nside
|
|
292
|
+
):
|
|
293
|
+
progressive_nside = nside*2
|
|
294
|
+
while progressive_nside != next_nside:
|
|
295
|
+
idxs_pixel = np.concatenate(
|
|
296
|
+
find_pixels_double_nside(progressive_nside, idxs_pixel)
|
|
297
|
+
)
|
|
298
|
+
progressive_nside *= 2
|
|
299
|
+
uniqs_pixel = mhealpy.nest2uniq(
|
|
300
|
+
next_nside, healpy.ring2nest(next_nside, idxs_pixel)
|
|
301
|
+
)
|
|
302
|
+
pixel_already_filled = False
|
|
303
|
+
for uniq in uniqs_next_nside:
|
|
304
|
+
if uniq in uniqs_pixel:
|
|
305
|
+
pixel_already_filled = True
|
|
306
|
+
continue
|
|
307
|
+
if pixel_already_filled:
|
|
308
|
+
already_filled_uniqs.append(uniq_original_nside)
|
|
309
|
+
return np.array(already_filled_uniqs)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def find_filled_pixels(uniqs: np.ndarray):
|
|
313
|
+
"""
|
|
314
|
+
given an array of uniqs, finds which pixels already have finer
|
|
315
|
+
pixels inside and returns array with the indeces of these pixels
|
|
316
|
+
|
|
317
|
+
args:
|
|
318
|
+
- uniqs: np.ndarray. Array of uniqs per each pixel of the map
|
|
319
|
+
|
|
320
|
+
returns:
|
|
321
|
+
- already_filled_indeces: np.ndarray. Array with the indeces
|
|
322
|
+
of the already_filled_uniqs in the input array
|
|
323
|
+
"""
|
|
324
|
+
nside_per_pixel = mhealpy.uniq2nside(uniqs)
|
|
325
|
+
nsides = np.unique(nside_per_pixel)
|
|
326
|
+
already_filled_uniqs = []
|
|
327
|
+
for nside_index, nside in enumerate(nsides[:-1]):
|
|
328
|
+
next_nside = nsides[nside_index + 1]
|
|
329
|
+
already_filled_uniqs_nside = already_filled_uniqs_for_nside(
|
|
330
|
+
nside, next_nside, uniqs
|
|
331
|
+
)
|
|
332
|
+
already_filled_uniqs.append(already_filled_uniqs_nside)
|
|
333
|
+
already_filled_uniqs = np.concatenate(already_filled_uniqs)
|
|
334
|
+
already_filled_indeces = np.array(
|
|
335
|
+
[np.where(uniqs == uni)[0][0] for uni in already_filled_uniqs]
|
|
336
|
+
)
|
|
337
|
+
return already_filled_indeces
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def clean_data_multiorder_map(
|
|
341
|
+
grid_value: np.ndarray, uniqs: np.ndarray
|
|
342
|
+
):
|
|
343
|
+
"""
|
|
344
|
+
Clean a map from the pixels which have a finer scan inside
|
|
345
|
+
|
|
346
|
+
args:
|
|
347
|
+
- grid_value: np.ndarray. Value of probability (likelihood)
|
|
348
|
+
per each scanned pixel.
|
|
349
|
+
- uniqs: np.ndarray. Uniqs per each scanned pixel to univocally
|
|
350
|
+
identify them
|
|
351
|
+
|
|
352
|
+
returns:
|
|
353
|
+
- grid_value: np.ndarray. Cleaned array
|
|
354
|
+
- uniqs: np.ndarray. Cleaned array
|
|
355
|
+
"""
|
|
356
|
+
filled_indeces = find_filled_pixels(uniqs)
|
|
357
|
+
grid_value = np.delete(grid_value, filled_indeces)
|
|
358
|
+
uniqs = np.delete(uniqs, filled_indeces)
|
|
359
|
+
return grid_value, uniqs
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def prepare_flattened_map(
|
|
363
|
+
equatorial_map: np.ndarray,
|
|
364
|
+
llh_map: bool,
|
|
365
|
+
) -> Tuple[np.ndarray, List[str]]:
|
|
366
|
+
"""
|
|
367
|
+
Create the healpix map that needs to be saved keeping
|
|
368
|
+
into account if it is a probability or a llh map
|
|
369
|
+
"""
|
|
370
|
+
if llh_map:
|
|
371
|
+
column_names = ['2DLLH']
|
|
372
|
+
else:
|
|
373
|
+
# avoid excessively heavy data format for the flattened map
|
|
374
|
+
equatorial_map[equatorial_map < 1e-16] = np.nanmean(
|
|
375
|
+
equatorial_map[equatorial_map < 1e-16]
|
|
376
|
+
)
|
|
377
|
+
column_names = ["PROBABILITY"]
|
|
378
|
+
return equatorial_map, column_names
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def prepare_multiorder_map(
|
|
382
|
+
grid_value: np.ndarray,
|
|
383
|
+
uniq_array: np.ndarray,
|
|
384
|
+
llh_map: bool,
|
|
385
|
+
column_names: List[str]
|
|
386
|
+
) -> Tuple[mhealpy.HealpixMap, List[str]]:
|
|
387
|
+
"""
|
|
388
|
+
Create the mhealpix map that needs to be saved keeping
|
|
389
|
+
into account if it is a probability or a llh map
|
|
390
|
+
"""
|
|
391
|
+
# clean from redundant pixels
|
|
392
|
+
grid_value, uniq_array = clean_data_multiorder_map(
|
|
393
|
+
grid_value, uniq_array
|
|
394
|
+
)
|
|
395
|
+
# save multiorder version of the map
|
|
396
|
+
if llh_map:
|
|
397
|
+
multiorder_map = mhealpy.HealpixMap(grid_value, uniq_array)
|
|
398
|
+
else:
|
|
399
|
+
all_nsides = mhealpy.uniq2nside(uniq_array)
|
|
400
|
+
max_nside = np.max(all_nsides)
|
|
401
|
+
multiorder_map = mhealpy.HealpixMap(
|
|
402
|
+
grid_value / healpy.nside2pixarea(
|
|
403
|
+
max_nside, degrees=True,
|
|
404
|
+
), uniq_array
|
|
405
|
+
)
|
|
406
|
+
column_names = [f"{column_names[0]} DENSITY [deg-2]"]
|
|
407
|
+
return multiorder_map, column_names
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
import healpy # type: ignore[import]
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
from ..result import SkyScanResult
|
|
8
|
-
|
|
9
|
-
LOGGER = logging.getLogger("skyreader.extract_map")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def extract_map(
|
|
13
|
-
result: SkyScanResult,
|
|
14
|
-
llh_map: bool = True,
|
|
15
|
-
angular_error_floor: Union[None, float] = None,
|
|
16
|
-
remove_min_val: bool = True,
|
|
17
|
-
):
|
|
18
|
-
"""
|
|
19
|
-
Extract from the output of skymap_scanner the healpy map
|
|
20
|
-
args:
|
|
21
|
-
- result: SkyScanResult. The output of the Skymap Scanner
|
|
22
|
-
- llh_map: bool = True. If True the likelihood will be plotted,
|
|
23
|
-
otherwise the probability
|
|
24
|
-
- angular_error_floor: Union[None, float] = None. if not None,
|
|
25
|
-
sigma of the gaussian to convolute the map with in deg.
|
|
26
|
-
- remove_min_val: bool = True. Remove minimum value from -llh
|
|
27
|
-
no effect if probability map.
|
|
28
|
-
|
|
29
|
-
returns:
|
|
30
|
-
- grid_value: value-per-scanned-pixel (pixels with
|
|
31
|
-
different nsides)
|
|
32
|
-
- grid_ra: right ascension for each pixel in grid_value
|
|
33
|
-
- grid_dec: declination for each pixel in grid_value
|
|
34
|
-
- equatorial_map: healpix map with maximum nside (all pixels
|
|
35
|
-
with same nside)
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
grid_map = dict()
|
|
39
|
-
|
|
40
|
-
nsides = result.nsides
|
|
41
|
-
max_nside = max(nsides)
|
|
42
|
-
equatorial_map = np.full(healpy.nside2npix(max_nside), np.nan)
|
|
43
|
-
|
|
44
|
-
for nside in nsides:
|
|
45
|
-
LOGGER.info(f"constructing map for nside {nside}...")
|
|
46
|
-
npix = healpy.nside2npix(nside)
|
|
47
|
-
map_data = result.result[f'nside-{nside}']
|
|
48
|
-
pixels = map_data['index']
|
|
49
|
-
values = map_data['llh']
|
|
50
|
-
this_map = np.full(npix, np.nan)
|
|
51
|
-
this_map[pixels] = values
|
|
52
|
-
if nside < max_nside:
|
|
53
|
-
this_map = healpy.ud_grade(this_map, max_nside)
|
|
54
|
-
mask = np.logical_and(~np.isnan(this_map), np.isfinite(this_map))
|
|
55
|
-
equatorial_map[mask] = this_map[mask]
|
|
56
|
-
|
|
57
|
-
for pixel_data in result.result[f"nside-{nside}"]:
|
|
58
|
-
pixel = pixel_data['index']
|
|
59
|
-
value = pixel_data['llh']
|
|
60
|
-
if np.isfinite(value) and not np.isnan(value):
|
|
61
|
-
tmp_theta, tmp_phi = healpy.pix2ang(nside, pixel)
|
|
62
|
-
tmp_dec = np.pi/2 - tmp_theta
|
|
63
|
-
tmp_ra = tmp_phi
|
|
64
|
-
grid_map[(tmp_dec, tmp_ra)] = value
|
|
65
|
-
# nested_pixel = healpy.ang2pix(
|
|
66
|
-
# nside, tmp_theta, tmp_phi, nest=True
|
|
67
|
-
# )
|
|
68
|
-
# uniq = 4*nside*nside + nested_pixel
|
|
69
|
-
# uniq_list.append(uniq)
|
|
70
|
-
LOGGER.info(f"done with map for nside {nside}...")
|
|
71
|
-
|
|
72
|
-
grid_dec_list, grid_ra_list, grid_value_list = [], [], []
|
|
73
|
-
|
|
74
|
-
for (dec, ra), value in grid_map.items():
|
|
75
|
-
grid_dec_list.append(dec)
|
|
76
|
-
grid_ra_list.append(ra)
|
|
77
|
-
grid_value_list.append(value)
|
|
78
|
-
grid_dec: np.ndarray = np.asarray(grid_dec_list)
|
|
79
|
-
grid_ra: np.ndarray = np.asarray(grid_ra_list)
|
|
80
|
-
grid_value: np.ndarray = np.asarray(grid_value_list)
|
|
81
|
-
# uniq_array: np.ndarray = np.asarray(uniq_list)
|
|
82
|
-
|
|
83
|
-
sorting_indices = np.argsort(grid_value)
|
|
84
|
-
grid_value = grid_value[sorting_indices]
|
|
85
|
-
grid_dec = grid_dec[sorting_indices]
|
|
86
|
-
grid_ra = grid_ra[sorting_indices]
|
|
87
|
-
# uniq_array = uniq_array[sorting_indices]
|
|
88
|
-
|
|
89
|
-
min_value = grid_value[0]
|
|
90
|
-
|
|
91
|
-
if remove_min_val or (not llh_map):
|
|
92
|
-
# renormalize
|
|
93
|
-
grid_value = grid_value - min_value
|
|
94
|
-
min_value = 0.
|
|
95
|
-
|
|
96
|
-
# renormalize
|
|
97
|
-
equatorial_map[np.isinf(equatorial_map)] = np.nan
|
|
98
|
-
equatorial_map -= np.nanmin(equatorial_map)
|
|
99
|
-
|
|
100
|
-
if llh_map:
|
|
101
|
-
# show 2 * delta_LLH
|
|
102
|
-
grid_value = grid_value * 2.
|
|
103
|
-
equatorial_map *= 2.
|
|
104
|
-
else:
|
|
105
|
-
# Convert to probability
|
|
106
|
-
equatorial_map = np.exp(-1. * equatorial_map)
|
|
107
|
-
equatorial_map = equatorial_map / np.nansum(equatorial_map)
|
|
108
|
-
|
|
109
|
-
# nan values are a problem for the convolution and the contours
|
|
110
|
-
min_map = np.nanmin(equatorial_map)
|
|
111
|
-
equatorial_map[np.isnan(equatorial_map)] = min_map
|
|
112
|
-
|
|
113
|
-
if angular_error_floor is not None:
|
|
114
|
-
# convolute with a gaussian. angular_error_floor is the
|
|
115
|
-
# sigma in deg.
|
|
116
|
-
equatorial_map = healpy.smoothing(
|
|
117
|
-
equatorial_map,
|
|
118
|
-
sigma=np.deg2rad(angular_error_floor),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# normalize map
|
|
122
|
-
min_map = np.nanmin(equatorial_map[equatorial_map > 0.0])
|
|
123
|
-
equatorial_map[np.isnan(equatorial_map)] = min_map
|
|
124
|
-
equatorial_map = equatorial_map.clip(min_map, None)
|
|
125
|
-
normalization = np.nansum(equatorial_map)
|
|
126
|
-
equatorial_map = equatorial_map / normalization
|
|
127
|
-
|
|
128
|
-
# obtain values for grid map
|
|
129
|
-
grid_value = healpy.get_interp_val(
|
|
130
|
-
equatorial_map, np.pi/2 - grid_dec, grid_ra
|
|
131
|
-
)
|
|
132
|
-
grid_value[np.isnan(grid_value)] = min_map
|
|
133
|
-
grid_value = grid_value.clip(min_map, None)
|
|
134
|
-
|
|
135
|
-
return grid_value, grid_ra, grid_dec, equatorial_map
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def get_contour_levels(
|
|
139
|
-
equatorial_map: np.ndarray,
|
|
140
|
-
llh_map: bool = True,
|
|
141
|
-
systematics: bool = False,
|
|
142
|
-
):
|
|
143
|
-
"""
|
|
144
|
-
get contour levels for the desired map
|
|
145
|
-
|
|
146
|
-
args:
|
|
147
|
-
- equatorial_map: np.ndarray. Necessary in case of
|
|
148
|
-
probability map.
|
|
149
|
-
- llh_map: bool. If True llh levels, otherwise probability
|
|
150
|
-
levels.
|
|
151
|
-
- systematics: bool. Only for llh maps. If True include
|
|
152
|
-
recalibrated llh values from Pan-Starrs event 127852
|
|
153
|
-
(IC160427A syst.)
|
|
154
|
-
|
|
155
|
-
returns:
|
|
156
|
-
- contour_levels, levels of the contours
|
|
157
|
-
- contour_labels, respective labels for the contours
|
|
158
|
-
- contour_colors, respective colors for the contours
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
# Calculate the contour levels
|
|
162
|
-
if llh_map: # likelihood map
|
|
163
|
-
min_value = np.min(equatorial_map)
|
|
164
|
-
if systematics:
|
|
165
|
-
# from Pan-Starrs event 127852
|
|
166
|
-
# these are values determined from MC by Will on the TS (2*LLH)
|
|
167
|
-
# Not clear yet how to translate this for the probability map
|
|
168
|
-
contour_levels = (np.array([22.2, 64.2])+min_value)
|
|
169
|
-
contour_labels = [
|
|
170
|
-
r'50% (IC160427A syst.)', r'90% (IC160427A syst.)'
|
|
171
|
-
]
|
|
172
|
-
contour_colors = ['k', 'r']
|
|
173
|
-
# Wilks
|
|
174
|
-
else:
|
|
175
|
-
contour_levels = (
|
|
176
|
-
np.array([1.39, 4.61, 11.83, 28.74])+min_value
|
|
177
|
-
)[:3]
|
|
178
|
-
contour_labels = [
|
|
179
|
-
r'50%', r'90%', r'3$\sigma$', r'5$\sigma$'
|
|
180
|
-
][:3]
|
|
181
|
-
contour_colors = ['k', 'r', 'g', 'b'][:3]
|
|
182
|
-
else: # probability map
|
|
183
|
-
if systematics:
|
|
184
|
-
raise AssertionError(
|
|
185
|
-
"No corrected values for contours in probability maps"
|
|
186
|
-
)
|
|
187
|
-
else:
|
|
188
|
-
sorted_values = np.sort(equatorial_map)[::-1]
|
|
189
|
-
probability_levels = (
|
|
190
|
-
np.array([0.5, 0.9, 1-1.35e-3, 1-2.87e-7])
|
|
191
|
-
)[:3]
|
|
192
|
-
contour_levels = list()
|
|
193
|
-
for prob in probability_levels:
|
|
194
|
-
level_index = (
|
|
195
|
-
np.nancumsum(sorted_values) >= prob
|
|
196
|
-
).tolist().index(True)
|
|
197
|
-
level = sorted_values[level_index]
|
|
198
|
-
contour_levels.append(level)
|
|
199
|
-
contour_labels = [r'50%', r'90%', r'3$\sigma$', r'5$\sigma$'][:3]
|
|
200
|
-
contour_colors = ['k', 'r', 'g', 'b'][:3]
|
|
201
|
-
|
|
202
|
-
return contour_levels, contour_labels, contour_colors
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{icecube-skyreader-1.3.0 → icecube-skyreader-1.3.2}/icecube_skyreader.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|