Skip to content

Commit 85246c2

Browse files
author
damien turpin
committed
mask filters related to observability, source brightness, galactic latitude implemented
1 parent d93152a commit 85246c2

File tree

2 files changed

+427
-0
lines changed

2 files changed

+427
-0
lines changed
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Created on Mon Nov 21 14:37:49 2022
5+
6+
@author: dt270490
7+
"""
8+
9+
import numpy as np
10+
import healpy as hp
11+
from astroplan import Observer, FixedTarget
12+
from astroplan import is_observable, is_always_observable, months_observable
13+
from astroplan import AltitudeConstraint, AirmassConstraint, AtNightConstraint,\
14+
MoonSeparationConstraint
15+
from astropy.time import Time
16+
from astropy.coordinates import SkyCoord
17+
import astropy.units as u
18+
from astropy.table import Table
19+
import numpy as np
20+
import matplotlib.pyplot as plt
21+
from mhealpy import HealpixMap
22+
import mhealpy as hmap
23+
import pandas as pd
24+
import json
25+
import requests
26+
import io
27+
28+
def get_test_alert():
29+
30+
# Get latests 5 Early SN Ia candidates
31+
r = requests.post(
32+
'https://fink-portal.org/api/v1/latests',
33+
json={
34+
'class': 'Early SN Ia candidate',
35+
'n': '2'
36+
}
37+
)
38+
39+
# Format output in a DataFrame
40+
pdf = pd.read_json(io.BytesIO(r.content))
41+
return pdf
42+
43+
def load_observatories(obs_path_file: str):
44+
"""
45+
Load the observatories for which we want to crossmacth the observable
46+
skies
47+
48+
Parameters
49+
----------
50+
obs_path_file : str
51+
Path to the files where the observatory settings are stored.
52+
53+
Returns
54+
-------
55+
observatories : Dataframes
56+
pandas Dataframe of the observatory configurations
57+
to feed the astroplan.Observer package.
58+
59+
"""
60+
observatories = pd.read_csv(obs_path_file, delimiter=";")
61+
return observatories
62+
63+
def simu_night_time_interval(ref_obs, ref_date: str, n_days: int, day_bin: int):
64+
"""
65+
Compute the list of the date interval during which the observable skies
66+
will be crossmatched
67+
68+
Parameters
69+
----------
70+
ref_obs : astroplan.observer.Observer
71+
astroplan.observer.Observer object for the Observatory chosen as the
72+
reference observatory.
73+
ref_date : str
74+
Date of reference to start the simulation.
75+
n_days : int
76+
Number of simulated days.
77+
day_bin : int
78+
Day interval between two simulated nights.
79+
80+
Returns
81+
-------
82+
None.
83+
84+
"""
85+
# compute the start time of the nearest (compared to the detection time)
86+
# night
87+
date_start_night = ref_obs.twilight_evening_astronomical(ref_date,
88+
which='nearest')
89+
# compute the morning time of the nearest (compared to the detection time)
90+
# day
91+
date_end_night = ref_obs.twilight_morning_astronomical(date_start_night,
92+
which='nearest')
93+
# If the detection time is within the current night, use the detection time
94+
# as a starting date and the end of the night as an ending date
95+
if date_start_night < ref_date and date_end_night>ref_date:
96+
time_ranges = Time([ref_date.iso, date_end_night.iso])
97+
# If the detection time is during day time, use the night starting date
98+
# as a starting date and the end of the night as an ending date
99+
elif date_start_night > ref_date and date_end_night>ref_date:
100+
time_ranges = Time([date_start_night.iso, date_end_night.iso])
101+
# If the detection time is after the nearest night, use the next night
102+
# starting date as a starting date and the end of the night as an
103+
# ending date
104+
elif date_start_night < ref_date and date_end_night<ref_date:
105+
date_start_night = ref_obs.twilight_evening_astronomical(ref_date,
106+
which='next')
107+
date_end_night = ref_obs.twilight_morning_astronomical(date_start_night,
108+
which='next')
109+
time_ranges = Time([date_start_night.iso, date_end_night.iso])
110+
return time_ranges
111+
112+
113+
def build_targets(ras: np.array, decs: np.array):
114+
"""
115+
Make the target objects to be ingested by the astroplan is_observable
116+
functions
117+
118+
Parameters
119+
----------
120+
ras : np.array
121+
Numpy arrays of right ascencion in units of degrees.
122+
decs : np.array
123+
Numpy arrays of declinations in units of degrees.
124+
125+
Returns
126+
-------
127+
targets :
128+
List of targets for which we want to estimate the observability
129+
130+
"""
131+
ras_grid, decs_grid = np.meshgrid(ras, decs)
132+
133+
target_table = Table()
134+
target_table["ra"] = np.reshape(ras_grid, ras_grid.shape[0] * ras_grid.shape[1])
135+
target_table["dec"] = np.reshape(decs_grid, decs_grid.shape[0] * decs_grid.shape[1])
136+
137+
targets = FixedTarget(
138+
coord=SkyCoord(ra=target_table["ra"] * u.deg, dec=target_table["dec"] * u.deg)
139+
)
140+
141+
return targets, target_table
142+
143+
144+
def make_visibility_masks(constraints, observatory, targets, time_range: list):
145+
"""
146+
Build the skymap mask regarding the obsverational constraints:
147+
True = the position is visible
148+
False = the position is not visible
149+
150+
Parameters
151+
----------
152+
constraints : TYPE
153+
DESCRIPTION.
154+
observatory : TYPE
155+
DESCRIPTION.
156+
targets : TYPE
157+
DESCRIPTION.
158+
time_range : list
159+
time range during which the observability is estimated
160+
161+
Returns
162+
-------
163+
mask_visibility : np.array
164+
Numpy arrays of booleans
165+
166+
"""
167+
168+
mask_visibility = is_observable(
169+
constraints, observatory, targets, time_range=time_range
170+
)
171+
172+
return mask_visibility[0]
173+
174+
def obs_visibility_pdf(ras:list,decs:list,det_time:list,
175+
observatories:pd.DataFrame,constraints:list):
176+
"""
177+
This function builds a pandas DataFrame that collect all the visibility
178+
flags of the observatories in the network related to the positions of
179+
optical transients and based on pre-defined observational constraints
180+
181+
Parameters
182+
----------
183+
ras : list
184+
list of right ascencion given in degrees.
185+
decs : list
186+
list of declination given in degrees.
187+
det_time : list
188+
list of first detection times expressed in Julian Date.
189+
observatories : pd.DataFrame
190+
Dataframe gathering the observatories informations.
191+
constraints : list
192+
list of astronomical constraints to be satisfied by each transient
193+
candidates.
194+
195+
Returns
196+
-------
197+
df : pd.DataFrame
198+
The Dataframe related to the observatories visibility for each candid.
199+
200+
"""
201+
mask_visibilities = []
202+
for i in range(len(observatories)):
203+
try:
204+
observatory = Observer.at_site(observatories.obs_name[i])
205+
except:
206+
observatory = Observer(
207+
longitude=observatories.longitude[i] * u.deg,
208+
latitude=observatories.latitude[i] * u.deg,
209+
elevation=observatories.elevation[i] * u.m,
210+
name=observatories.obs_name[i],
211+
)
212+
mask_visibility = []
213+
for k in range(len(pdf_test)):
214+
targets = FixedTarget(
215+
coord=SkyCoord(ra=pdf_test['i:ra'][k] * u.deg,
216+
dec=pdf_test['i:dec'][k]* u.deg)
217+
)
218+
time_ranges = simu_night_time_interval(
219+
observatory, Time(pdf_test['i:jd'][k],format='jd'), 1, 1)
220+
221+
mask_visibility.append(make_visibility_masks(
222+
constraints, observatory, targets, time_ranges
223+
))
224+
mask_visibilities.append(mask_visibility)
225+
# Build the DataFrame
226+
col_name = ['objectId','candid']+observatories.obs_name.values.tolist()
227+
df = pd.DataFrame(columns=col_name)
228+
df.objectId = pdf_test['i:objectId']
229+
df.candid = pdf_test['i:candid']
230+
231+
for i in range(len(observatories.obs_name)):
232+
df[observatories.obs_name[i]] = mask_visibilities[i]
233+
return df
234+
235+
def brightness_mask(mag:list,mag_err:list,fid:list,mag_threshold:int):
236+
"""
237+
Create a mask related to the brightness of the optical transient
238+
candidates
239+
240+
Parameters
241+
----------
242+
mag : list
243+
list of the transient magnitudes.
244+
mag_err : list
245+
list of the transient magnitude errors.
246+
fid : list
247+
list of the filters used for the alert observation.
248+
mag_threshold : int
249+
threshold on the magnitude to keep a transient for follow-up.
250+
251+
Returns
252+
-------
253+
mask : pd.DataFrame
254+
DataFrame masking the too faint candidates.
255+
256+
"""
257+
mask = pdf_test['i:magpsf']<mag_threshold
258+
return mask.tolist()
259+
260+
def extragal_mask(ras:list,decs:list,gal_lat_threshold:float):
261+
"""
262+
Create a mask related to the galactic latitude of the optical transient
263+
candidates
264+
265+
Parameters
266+
----------
267+
ras : list
268+
list of right ascencion given in degrees.
269+
decs : list
270+
list of declination given in degrees.
271+
gal_lat_threshold : float
272+
threshold on the galactic latitude to keep a transient for follow-up.
273+
274+
Returns
275+
-------
276+
mask : list
277+
DataFrame masking the candidates too close to the galactic plane.
278+
279+
"""
280+
coord=SkyCoord(ra=ras * u.deg, dec=decs * u.deg)
281+
mask = (coord.galactic.b.degree > gal_lat_threshold) |\
282+
(coord.galactic.b.degree < -1*gal_lat_threshold)
283+
return mask.tolist()
284+
285+
def net_visibity_mask(pdf_cand_visibility:pd.DataFrame,
286+
mask_brightness:list,
287+
mask_extragal:list):
288+
"""
289+
Build the final mask to filter out the transient candidates
290+
291+
Parameters
292+
----------
293+
pdf_cand_visibility : pd.DataFrame
294+
The Dataframe related to the observatories visibility for each candid.
295+
mask_brightness : list
296+
DataFrame masking the too faint candidates.
297+
mask_extragal : list
298+
DataFrame masking the candidates too close to the galactic plane.
299+
300+
Returns
301+
-------
302+
mask_final : list
303+
the final mask to filter out the transient candidates
304+
305+
"""
306+
# The source has to visible at San Pedro Martir and Xinglong and NOT
307+
mask_net_visibility = (pdf_cand_visibility['OAN-SPM'] == True) & \
308+
(pdf_cand_visibility['Xinglong'] == True) &\
309+
(pdf_cand_visibility['ORM'] == True)
310+
311+
mask_final = mask_net_visibility & mask_brightness & mask_extragal
312+
return mask_final.tolist()
313+
314+
if __name__ == "__main__":
315+
# Load the observatories info
316+
obs_path_file = "../../conf/"
317+
obs_filename = "svom_network.csv"
318+
observatories = load_observatories(obs_path_file + obs_filename)
319+
# Define the observationnal constraints
320+
constraints = [
321+
AltitudeConstraint(40 * u.deg, 80 * u.deg),
322+
AirmassConstraint(2), MoonSeparationConstraint(min=20* u.deg)]#, AtNightConstraint.twilight_astronomical()]
323+
#Load a sample of Fink alert tests
324+
pdf_test = get_test_alert()
325+
# Build Dataframe related to the observatories visibility for each candid
326+
pdf_cand_visibility = obs_visibility_pdf(pdf_test['i:ra'],
327+
pdf_test['i:dec'],
328+
pdf_test['i:jd'],
329+
observatories,
330+
constraints)
331+
# Add a column in the pdf_cand_visibility DataFrame to flag if
332+
# the source is bright enough for Colibri and NOT
333+
mask_brightness = brightness_mask(pdf_test['i:magpsf'],
334+
pdf_test['i:sigmapsf'],pdf_test['i:fid'],
335+
18)
336+
pdf_cand_visibility['mag_mask'] = mask_brightness
337+
# Add a column in the pdf_cand_visibility DataFrame to flag if
338+
# the source is at high galactic latitutes
339+
mask_extragal = extragal_mask(pdf_test['i:ra'],pdf_test['i:dec'],10)
340+
pdf_cand_visibility['extragal_coord_mask'] = mask_extragal
341+
# Make the final mask on every ZTF candidates
342+
mask_final = net_visibity_mask(pdf_cand_visibility,mask_brightness,
343+
mask_extragal)
344+
#Add the final mask to the initial alert DataFrame
345+
pdf_test['svom_mask'] = mask_final

0 commit comments

Comments
 (0)