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