sedlib 1.0.0__py3-none-any.whl
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.
- sedlib/__init__.py +47 -0
- sedlib/bol2rad.py +552 -0
- sedlib/catalog.py +1141 -0
- sedlib/core.py +6059 -0
- sedlib/data/__init__.py +2 -0
- sedlib/data/temp_to_bc_coefficients.yaml +62 -0
- sedlib/filter/__init__.py +5 -0
- sedlib/filter/core.py +1064 -0
- sedlib/filter/data/__init__.py +2 -0
- sedlib/filter/data/svo_all_filter_database.pickle +0 -0
- sedlib/filter/data/svo_filter_catalog.pickle +0 -0
- sedlib/filter/data/svo_meta_data.xml +1282 -0
- sedlib/filter/utils.py +71 -0
- sedlib/helper.py +361 -0
- sedlib/utils.py +789 -0
- sedlib/version.py +12 -0
- sedlib-1.0.0.dist-info/METADATA +611 -0
- sedlib-1.0.0.dist-info/RECORD +21 -0
- sedlib-1.0.0.dist-info/WHEEL +5 -0
- sedlib-1.0.0.dist-info/licenses/LICENSE +201 -0
- sedlib-1.0.0.dist-info/top_level.txt +1 -0
sedlib/filter/utils.py
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
"""Utility functions for the filter module."""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import List, Optional
|
7
|
+
|
8
|
+
class InMemoryHandler(logging.Handler):
|
9
|
+
"""A logging handler that stores log records in memory."""
|
10
|
+
|
11
|
+
def __init__(self, capacity: Optional[int] = None):
|
12
|
+
"""Initialize the handler with optional capacity limit.
|
13
|
+
|
14
|
+
Parameters
|
15
|
+
----------
|
16
|
+
capacity : Optional[int]
|
17
|
+
Maximum number of log records to store. If None, no limit is applied.
|
18
|
+
"""
|
19
|
+
super().__init__()
|
20
|
+
self.capacity = capacity
|
21
|
+
self.logs: List[str] = []
|
22
|
+
|
23
|
+
def emit(self, record: logging.LogRecord) -> None:
|
24
|
+
"""Store the log record in memory.
|
25
|
+
|
26
|
+
Parameters
|
27
|
+
----------
|
28
|
+
record : logging.LogRecord
|
29
|
+
The log record to store
|
30
|
+
"""
|
31
|
+
log_entry = self.format(record)
|
32
|
+
self.logs.append(log_entry)
|
33
|
+
if self.capacity and len(self.logs) > self.capacity:
|
34
|
+
self.logs.pop(0)
|
35
|
+
|
36
|
+
def get_logs(self, log_type: str = 'all') -> List[str]:
|
37
|
+
"""Get all stored log records.
|
38
|
+
|
39
|
+
Parameters
|
40
|
+
----------
|
41
|
+
log_type : str
|
42
|
+
log type to get
|
43
|
+
possible values are 'all', 'info', 'debug', 'warning', 'error'
|
44
|
+
(default: 'all')
|
45
|
+
|
46
|
+
Returns
|
47
|
+
-------
|
48
|
+
List[str]
|
49
|
+
List of formatted log records
|
50
|
+
"""
|
51
|
+
if log_type == 'all':
|
52
|
+
return self.logs.copy()
|
53
|
+
elif log_type == 'info':
|
54
|
+
return [log for log in self.logs if log[22:].startswith('INFO')]
|
55
|
+
elif log_type == 'debug':
|
56
|
+
return [log for log in self.logs if log[22:].startswith('DEBUG')]
|
57
|
+
elif log_type == 'warning':
|
58
|
+
return [log for log in self.logs if log[22:].startswith('WARNING')]
|
59
|
+
elif log_type == 'error':
|
60
|
+
return [log for log in self.logs if log[22:].startswith('ERROR')]
|
61
|
+
else:
|
62
|
+
raise ValueError(f"Invalid log type: {log_type}")
|
63
|
+
|
64
|
+
def clear(self) -> None:
|
65
|
+
"""Clear all stored log records."""
|
66
|
+
self.logs.clear()
|
67
|
+
|
68
|
+
SVO_BASE_URL = 'http://svo2.cab.inta-csic.es/theory/fps/fps.php?'
|
69
|
+
|
70
|
+
SVO_FILTER_URL = SVO_BASE_URL + 'ID='
|
71
|
+
SVO_META_URL = SVO_BASE_URL + 'FORMAT=metadata'
|
sedlib/helper.py
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
"""
|
4
|
+
Helper functions for sedlib
|
5
|
+
"""
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
'get_pattern', '_init_simbad', 'query_gaia_parameters',
|
9
|
+
'find_nearest', 'dumps_quantities', 'get_tmag', 'select_preferred_filters'
|
10
|
+
]
|
11
|
+
|
12
|
+
import json
|
13
|
+
from typing import Any
|
14
|
+
|
15
|
+
import numpy as np
|
16
|
+
|
17
|
+
from astropy import units as u
|
18
|
+
from astropy.units import Quantity
|
19
|
+
from astroquery.simbad import Simbad
|
20
|
+
from astroquery.gaia import Gaia
|
21
|
+
from astroquery.vizier import Vizier
|
22
|
+
|
23
|
+
|
24
|
+
VIZIER_PHOTOMETRY_API_URL = 'https://vizier.cds.unistra.fr/viz-bin/sed'
|
25
|
+
|
26
|
+
FILTER_SYSTEM_MAPPINGS = {
|
27
|
+
'HIP': {
|
28
|
+
'Hp': 'Hipparcos/Hipparcos.Hp',
|
29
|
+
'BT': 'TYCHO/TYCHO.B',
|
30
|
+
'VT': 'TYCHO/TYCHO.B'
|
31
|
+
},
|
32
|
+
'SDSS': lambda fn: f'SLOAN/SDSS.{fn[0]}prime_filter' if fn[-1] == "\'" else f'SLOAN/SDSS.{fn}',
|
33
|
+
'TYCHO': lambda fn: f'TYCHO/{fn}',
|
34
|
+
'Gaia': lambda fn: f'GAIA/GAIA3.{fn}',
|
35
|
+
'GAIA/GAIA2': lambda fn: f'GAIA/GAIA2.{fn}',
|
36
|
+
'GAIA/GAIA3': lambda fn: f'GAIA/GAIA3.{fn}',
|
37
|
+
'IRAS': lambda fn: f'IRAS/IRAS.{fn}mu',
|
38
|
+
'DIRBE': lambda fn: f'COBE/DIRBE.{fn.replace(".", "p")}m',
|
39
|
+
'Cousins': lambda fn: f'Generic/Cousins.{fn}' if fn in ['R', 'I'] else f'Generic/Johnson_UBVRIJHKL.{fn}',
|
40
|
+
# 'Johnson': lambda fn: f'Generic/Johnson_UBVRIJHKL.{fn if fn not in ("L\'", "L", "L\'\'") else "LI" if fn in ("L\'", "L") else "LII"}'
|
41
|
+
# 'Johnson': lambda fn: f'Generic/Johnson_UBVRIJHKL.{fn}' if fn not in ("L'", "L", "L''") else f'Generic/Johnson_UBVRIJHKL.{"LI" if fn in ("L'", "L") else "LII"}'
|
42
|
+
'Johnson': lambda fn: f'Generic/Johnson_UBVRIJHKL.{fn}' if fn not in ("L'", "L", "L''") else (
|
43
|
+
f'Generic/Johnson_UBVRIJHKL.LI' if fn in ("L'", "L") else f'Generic/Johnson_UBVRIJHKL.LII'
|
44
|
+
)
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
def get_pattern(system, filter_name):
|
49
|
+
if system in FILTER_SYSTEM_MAPPINGS:
|
50
|
+
mapping = FILTER_SYSTEM_MAPPINGS[system]
|
51
|
+
if callable(mapping):
|
52
|
+
return mapping(filter_name)
|
53
|
+
elif filter_name in mapping:
|
54
|
+
return mapping[filter_name]
|
55
|
+
return f'*{system.strip().lower()}*{filter_name.strip().lower()}*'
|
56
|
+
|
57
|
+
|
58
|
+
def find_nearest(array, target):
|
59
|
+
"""
|
60
|
+
Find the nearest value in a NumPy array to a given target.
|
61
|
+
|
62
|
+
Parameters
|
63
|
+
----------
|
64
|
+
array : numpy.ndarray
|
65
|
+
Input array to search within.
|
66
|
+
target : int or float
|
67
|
+
The target value to find the nearest to.
|
68
|
+
|
69
|
+
Returns
|
70
|
+
-------
|
71
|
+
nearest_value : float
|
72
|
+
The value in the array closest to the target.
|
73
|
+
nearest_index : tuple
|
74
|
+
The index of the nearest value (supports multi-dimensional arrays).
|
75
|
+
|
76
|
+
Raises
|
77
|
+
------
|
78
|
+
TypeError
|
79
|
+
If `array` is not a numpy.ndarray or if `target` is not int or float.
|
80
|
+
|
81
|
+
Examples
|
82
|
+
--------
|
83
|
+
>>> array = np.array([10, 15, 20, 25, 30])
|
84
|
+
>>> find_nearest(array, 18)
|
85
|
+
(20, 2)
|
86
|
+
|
87
|
+
>>> array_2d = np.array([[10, 15], [20, 25]])
|
88
|
+
>>> find_nearest(array_2d, 18)
|
89
|
+
(20, (1, 0))
|
90
|
+
"""
|
91
|
+
# Check if input is a NumPy array
|
92
|
+
if not isinstance(array, np.ndarray):
|
93
|
+
raise TypeError("Input array must be a NumPy ndarray.")
|
94
|
+
|
95
|
+
# Check if target is of type int or float
|
96
|
+
if not isinstance(target, (int, float)):
|
97
|
+
raise TypeError("Target value must be of type int or float.")
|
98
|
+
|
99
|
+
# Flatten the array to handle both 1D and multi-dimensional cases
|
100
|
+
flat_index = np.abs(array - target).ravel().argmin()
|
101
|
+
|
102
|
+
# Get the nearest value
|
103
|
+
nearest_value = array.ravel()[flat_index]
|
104
|
+
|
105
|
+
# Convert flat index to multi-dimensional index if necessary
|
106
|
+
nearest_index = np.unravel_index(flat_index, array.shape)
|
107
|
+
|
108
|
+
return nearest_value, nearest_index
|
109
|
+
|
110
|
+
|
111
|
+
def _init_simbad():
|
112
|
+
# Simbad.reset_votable_fields()
|
113
|
+
# Simbad.remove_votable_fields('coordinates')
|
114
|
+
# Simbad.add_votable_fields(
|
115
|
+
# 'ra', 'dec', 'parallax', 'otype', 'otypes', 'sptype', 'distance'
|
116
|
+
# )
|
117
|
+
Simbad.add_votable_fields(
|
118
|
+
'parallax',
|
119
|
+
)
|
120
|
+
|
121
|
+
|
122
|
+
def query_gaia_parameters(name, search_simbad=True):
|
123
|
+
"""Query Gaia DR3 to get stellar parameters for an object.
|
124
|
+
|
125
|
+
This function first searches for the object's Gaia DR3 source ID using SIMBAD,
|
126
|
+
then queries the Gaia archive for parallax, distance, temperature and radius information.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
name : str
|
131
|
+
Name of the astronomical object to query
|
132
|
+
|
133
|
+
search_simbad : bool, optional
|
134
|
+
If True, search for the object's Gaia DR3 source ID using SIMBAD.
|
135
|
+
If False, use the provided name as the Gaia DR3 source ID.
|
136
|
+
|
137
|
+
Returns
|
138
|
+
-------
|
139
|
+
dict or None
|
140
|
+
Dictionary containing source_id, parallax, parallax_error, distance_pc,
|
141
|
+
distance_error_pc, teff (effective temperature) and radius. Returns None
|
142
|
+
if no SIMBAD match is found.
|
143
|
+
"""
|
144
|
+
gaia_id = name
|
145
|
+
|
146
|
+
# Query SIMBAD for object IDs
|
147
|
+
if search_simbad:
|
148
|
+
simbad_results = Simbad.query_objectids(name)
|
149
|
+
if len(simbad_results) == 0:
|
150
|
+
return None
|
151
|
+
|
152
|
+
# Find Gaia DR3 ID
|
153
|
+
gaia_id = None
|
154
|
+
for id_entry in simbad_results['id']:
|
155
|
+
if 'Gaia DR3' in id_entry:
|
156
|
+
# gaia_id = id_entry.strip().split()[-1]
|
157
|
+
gaia_id = id_entry
|
158
|
+
break
|
159
|
+
|
160
|
+
if not gaia_id:
|
161
|
+
return None
|
162
|
+
|
163
|
+
gaia_id = gaia_id.strip().split()[-1]
|
164
|
+
|
165
|
+
# Construct and execute Gaia query
|
166
|
+
query = f"""
|
167
|
+
SELECT
|
168
|
+
dr3.source_id,
|
169
|
+
dr3.ra,
|
170
|
+
dr3.dec,
|
171
|
+
dr3.parallax,
|
172
|
+
dr3.parallax_error,
|
173
|
+
1000.0 / dr3.parallax AS distance_pc,
|
174
|
+
(1000.0 / dr3.parallax) * (dr3.parallax_error / dr3.parallax) AS distance_error_pc,
|
175
|
+
dr3.teff_gspphot AS teff,
|
176
|
+
dr3.teff_gspphot_lower AS teff_lower,
|
177
|
+
dr3.teff_gspphot_upper AS teff_upper,
|
178
|
+
ap.radius_gspphot AS radius,
|
179
|
+
ap.radius_gspphot_lower AS radius_lower,
|
180
|
+
ap.radius_gspphot_upper AS radius_upper
|
181
|
+
FROM gaiadr3.gaia_source AS dr3
|
182
|
+
INNER JOIN gaiadr3.astrophysical_parameters AS ap
|
183
|
+
ON dr3.source_id = ap.source_id
|
184
|
+
WHERE dr3.source_id = {gaia_id}
|
185
|
+
"""
|
186
|
+
|
187
|
+
job = Gaia.launch_job(query)
|
188
|
+
t = job.get_results()
|
189
|
+
|
190
|
+
# Convert table row to dictionary
|
191
|
+
result = {
|
192
|
+
'source_id': t['source_id'][0],
|
193
|
+
'ra': t['ra'][0],
|
194
|
+
'dec': t['dec'][0],
|
195
|
+
'parallax': t['parallax'][0],
|
196
|
+
'parallax_error': t['parallax_error'][0],
|
197
|
+
'distance_pc': t['distance_pc'][0],
|
198
|
+
'distance_error_pc': t['distance_error_pc'][0],
|
199
|
+
'teff': t['teff'][0],
|
200
|
+
'teff_lower': t['teff_lower'][0],
|
201
|
+
'teff_upper': t['teff_upper'][0],
|
202
|
+
'radius': t['radius'][0],
|
203
|
+
'radius_lower': t['radius_lower'][0],
|
204
|
+
'radius_upper': t['radius_upper'][0]
|
205
|
+
}
|
206
|
+
|
207
|
+
return result
|
208
|
+
|
209
|
+
|
210
|
+
def dumps_quantities(obj: Any, **json_kwargs) -> str:
|
211
|
+
"""
|
212
|
+
Serialize `obj` to JSON, converting any astropy.units.Quantity into its raw value.
|
213
|
+
|
214
|
+
Parameters
|
215
|
+
----------
|
216
|
+
obj
|
217
|
+
Any Python object (e.g. dict, list, nested structures) possibly
|
218
|
+
containing Quantity instances.
|
219
|
+
json_kwargs
|
220
|
+
Extra keyword arguments to pass through to json.dumps()
|
221
|
+
(e.g. indent=2, sort_keys=True).
|
222
|
+
|
223
|
+
Returns
|
224
|
+
-------
|
225
|
+
str
|
226
|
+
The JSON string with all Quantity instances replaced by their .value.
|
227
|
+
"""
|
228
|
+
class _QuantityEncoder(json.JSONEncoder):
|
229
|
+
def default(self, o: Any) -> Any:
|
230
|
+
if isinstance(o, Quantity):
|
231
|
+
return o.value
|
232
|
+
if isinstance(o, np.ndarray):
|
233
|
+
return o.tolist()
|
234
|
+
# Handle LbfgsInvHessProduct type from scipy.optimize
|
235
|
+
if o.__class__.__name__ == 'LbfgsInvHessProduct':
|
236
|
+
return "<LbfgsInvHessProduct object>"
|
237
|
+
# Try to convert other numpy types
|
238
|
+
if isinstance(o, (np.integer, np.floating, np.bool_)):
|
239
|
+
return o.item()
|
240
|
+
return super().default(o)
|
241
|
+
|
242
|
+
return json.dumps(obj, cls=_QuantityEncoder, **json_kwargs)
|
243
|
+
|
244
|
+
|
245
|
+
def get_tmag(source_id, release="dr3"):
|
246
|
+
"""
|
247
|
+
Retrieve Tmag and its uncertainty (e_Tmag) from the TESS Input Catalog (TIC 8.2, IV/39/tic82),
|
248
|
+
given either a Gaia DR2 or DR3 source_id.
|
249
|
+
|
250
|
+
Parameters
|
251
|
+
----------
|
252
|
+
source_id : int or str
|
253
|
+
The Gaia source identifier (DR2 or DR3), depending on `release`.
|
254
|
+
release : str, optional
|
255
|
+
Indicates which Gaia release `source_id` refers to: "dr2" or "dr3".
|
256
|
+
Default is "dr3".
|
257
|
+
|
258
|
+
Returns
|
259
|
+
-------
|
260
|
+
tuple (Tmag, e_Tmag) as floats, or None if no match is found.
|
261
|
+
"""
|
262
|
+
# Vizier.ROW_LIMIT = 1
|
263
|
+
|
264
|
+
catalog_id = "IV/39/tic82"
|
265
|
+
source_id = str(source_id).strip()
|
266
|
+
|
267
|
+
def _query_tic_by_dr2(dr2_id):
|
268
|
+
"""Query TIC for Tmag given a DR2 source_id string."""
|
269
|
+
# 1) Try a direct GAIA‐constraint on TIC
|
270
|
+
viz = Vizier(columns=["Tmag", "e_Tmag", "GAIA"], catalog=catalog_id)
|
271
|
+
result = viz.query_constraints(GAIA=dr2_id)
|
272
|
+
|
273
|
+
if result:
|
274
|
+
t = result[0]
|
275
|
+
return float(t['Tmag'][0]), float(t['e_Tmag'][0])
|
276
|
+
|
277
|
+
# 2) Fallback: cone‐search around "Gaia DR2 <dr2_id>"
|
278
|
+
viz_fallback = Vizier(columns=["Tmag", "e_Tmag"], catalog=catalog_id)
|
279
|
+
name = f"Gaia DR2 {dr2_id}"
|
280
|
+
fallback = viz_fallback.query_object(name, radius=2 * u.arcsec)
|
281
|
+
if fallback and catalog_id in fallback and len(fallback[catalog_id]) > 0:
|
282
|
+
row = fallback[catalog_id][0]
|
283
|
+
return float(row["Tmag"]), float(row["e_Tmag"])
|
284
|
+
|
285
|
+
return None
|
286
|
+
|
287
|
+
if release.lower() == "dr2":
|
288
|
+
return _query_tic_by_dr2(source_id)
|
289
|
+
|
290
|
+
adql = f"""
|
291
|
+
SELECT TOP 1 x.dr2_source_id
|
292
|
+
FROM gaiadr3.gaia_source AS dr3
|
293
|
+
JOIN gaiadr3.dr2_neighbourhood AS x
|
294
|
+
ON dr3.source_id = x.dr3_source_id
|
295
|
+
WHERE dr3.source_id = {source_id}
|
296
|
+
"""
|
297
|
+
job = Gaia.launch_job(adql)
|
298
|
+
rows = job.get_results()
|
299
|
+
|
300
|
+
if len(rows) == 0:
|
301
|
+
return None
|
302
|
+
|
303
|
+
dr2_id = str(rows["dr2_source_id"][0])
|
304
|
+
return _query_tic_by_dr2(dr2_id)
|
305
|
+
|
306
|
+
|
307
|
+
def select_preferred_filters(sed, column, column_error) -> dict:
|
308
|
+
"""
|
309
|
+
The allowed filters and their priority are defined as follows:
|
310
|
+
|
311
|
+
- Johnson:B
|
312
|
+
- Johnson:V
|
313
|
+
- GAIA/GAIA3:G
|
314
|
+
- GAIA/GAIA3:Gbp
|
315
|
+
- GAIA/GAIA3:Grp
|
316
|
+
- GAIA/GAIA2:G
|
317
|
+
- GAIA/GAIA2:Gbp
|
318
|
+
- GAIA/GAIA2:Grp
|
319
|
+
- Gaia:G
|
320
|
+
- TESS/TESS:Red
|
321
|
+
|
322
|
+
Returns
|
323
|
+
-------
|
324
|
+
dict
|
325
|
+
Dictionary where keys are the selected filter names and values are
|
326
|
+
tuples (abs_mag, abs_mag_err).
|
327
|
+
"""
|
328
|
+
# allowed filters in order
|
329
|
+
allowed_filters = [
|
330
|
+
'Johnson:B',
|
331
|
+
'Johnson:V',
|
332
|
+
'GAIA/GAIA3:G',
|
333
|
+
'GAIA/GAIA3:Grp',
|
334
|
+
'GAIA/GAIA3:Gbp',
|
335
|
+
'GAIA/GAIA2:G',
|
336
|
+
'GAIA/GAIA2:Grp',
|
337
|
+
'GAIA/GAIA2:Gbp',
|
338
|
+
'Gaia:G',
|
339
|
+
'TESS/TESS:Red'
|
340
|
+
]
|
341
|
+
|
342
|
+
result = {}
|
343
|
+
chosen_bands = {}
|
344
|
+
|
345
|
+
for filt in allowed_filters:
|
346
|
+
mask = sed.catalog.table['vizier_filter'] == filt
|
347
|
+
if not mask.any():
|
348
|
+
continue
|
349
|
+
|
350
|
+
row = sed.catalog.table[mask][0]
|
351
|
+
mag = row[column]
|
352
|
+
mag_err = row[column_error]
|
353
|
+
|
354
|
+
band = filt.split(":")[-1]
|
355
|
+
if band not in chosen_bands:
|
356
|
+
if band == 'Red':
|
357
|
+
band = 'TESS'
|
358
|
+
result[band.upper()] = (mag, mag_err)
|
359
|
+
chosen_bands[band] = filt
|
360
|
+
|
361
|
+
return result
|