space-ska 1.0__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.
- space_ska-1.0/LICENSE +21 -0
- space_ska-1.0/PKG-INFO +27 -0
- space_ska-1.0/README.md +6 -0
- space_ska-1.0/pyproject.toml +35 -0
- space_ska-1.0/ska/__init__.py +25 -0
- space_ska-1.0/ska/cache.py +88 -0
- space_ska-1.0/ska/cli.py +214 -0
- space_ska-1.0/ska/filter.py +248 -0
- space_ska-1.0/ska/spectrum.py +243 -0
- space_ska-1.0/ska/svo.py +118 -0
space_ska-1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Benoit Carry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
space_ska-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: space-ska
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Spectral-Kit for Asteroids
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Benoit Carry
|
|
7
|
+
Author-email: benoit.carry@oca.eu
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Provides-Extra: docs
|
|
13
|
+
Requires-Dist: astropy (>=6.0.1,<7.0.0)
|
|
14
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
15
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
16
|
+
Requires-Dist: pandas (>=2.2.1,<3.0.0)
|
|
17
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
18
|
+
Requires-Dist: rich (>=13.7.1,<14.0.0)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ska - Spectral-Kit for Asteroids
|
|
22
|
+
|
|
23
|
+
Suites of tools to compute colors from spectra or reflectance spectra using the
|
|
24
|
+
[SVO Filter Service](http://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice)
|
|
25
|
+
in different photometric system: Vega, AB, ST.
|
|
26
|
+
|
|
27
|
+
|
space_ska-1.0/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "space-ska"
|
|
3
|
+
version = "1.0"
|
|
4
|
+
description = "Spectral-Kit for Asteroids"
|
|
5
|
+
authors = ["Benoit Carry <benoit.carry@oca.eu>", "Max Mahlke"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{'include' = 'ska'}]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.12"
|
|
12
|
+
requests = "^2.31.0"
|
|
13
|
+
pandas = "^2.2.1"
|
|
14
|
+
click = "^8.1.7"
|
|
15
|
+
rich = "^13.7.1"
|
|
16
|
+
astropy = "^6.0.1"
|
|
17
|
+
numpy = "^1.26.4"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
[tool.poetry.extras]
|
|
21
|
+
docs = [
|
|
22
|
+
"furo",
|
|
23
|
+
"sphinx",
|
|
24
|
+
"sphinx-copybutton",
|
|
25
|
+
"sphinx-hoverxref",
|
|
26
|
+
"sphinx-redactor-theme",
|
|
27
|
+
"spinx_design"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.poetry.scripts]
|
|
31
|
+
ska = "ska.cli:cli_ska"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["poetry-core"]
|
|
35
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Spectral-Kit for Asteroids."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from .filter import Filter # noqa
|
|
6
|
+
from .spectrum import Spectrum # noqa
|
|
7
|
+
from . import svo # noqa
|
|
8
|
+
from .cache import download_sun_vega # noqa
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
|
|
12
|
+
# Cache location
|
|
13
|
+
PATH_CACHE = os.path.join(os.path.expanduser("~"), ".cache/ska")
|
|
14
|
+
os.makedirs(PATH_CACHE, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
# SKA Auxiliary data
|
|
17
|
+
PATH_VEGA = os.path.join(PATH_CACHE, "spectrum_vega.csv")
|
|
18
|
+
PATH_SUN = os.path.join(PATH_CACHE, "spectrum_sun.csv")
|
|
19
|
+
PATH_FILTER_LIST = os.path.join(PATH_CACHE, "svo_filters.txt")
|
|
20
|
+
|
|
21
|
+
if not os.path.isfile(PATH_FILTER_LIST):
|
|
22
|
+
svo.download_filter_list()
|
|
23
|
+
|
|
24
|
+
if not os.path.isfile(PATH_VEGA) or not os.path.isfile(PATH_SUN):
|
|
25
|
+
download_sun_vega()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Cache management for ska"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import glob
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
import ska
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ------
|
|
11
|
+
# Functions for cache management
|
|
12
|
+
def clear():
|
|
13
|
+
"""Remove the cached filters and the acceptable list"""
|
|
14
|
+
|
|
15
|
+
filter_ids, filter_files = take_inventory()
|
|
16
|
+
|
|
17
|
+
# Remove cached filters
|
|
18
|
+
for f in filter_files:
|
|
19
|
+
os.unlink(os.path.join(ska.PATH_CACHE, f))
|
|
20
|
+
|
|
21
|
+
# Remove list of SVO Filters
|
|
22
|
+
os.unlink(os.path.join(ska.PATH_CACHE, "svo_filters.txt"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def take_inventory():
|
|
26
|
+
"""Create lists of the cached filter VOTables.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
list of str
|
|
31
|
+
The path to the cached VOTables.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Get all XML in cache
|
|
35
|
+
cached_xmls = set(
|
|
36
|
+
file_ for file_ in glob.glob(os.path.join(ska.PATH_CACHE, "*.xml"))
|
|
37
|
+
)
|
|
38
|
+
cached_ids = set(
|
|
39
|
+
os.path.basename(FILT).replace("_", "/")[:-4] for FILT in cached_xmls
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return cached_ids, cached_xmls
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def update_filter_list():
|
|
46
|
+
# TBD doc / handle issue
|
|
47
|
+
ska.svo.download_filter_list()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def update_filters(ids, force=False):
|
|
51
|
+
"""Update the cached filters (VOTable files).
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
ids : list
|
|
56
|
+
List of SVO IDs corresponding to the filters to update.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# Download filters
|
|
60
|
+
for f in ids:
|
|
61
|
+
ska.svo.download_filter(f, force=force)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Get Vega and Sun
|
|
65
|
+
def download_sun_vega():
|
|
66
|
+
"""Download the spectra of the Sun and Vega"""
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
|
|
70
|
+
# Get Solar Spectrum
|
|
71
|
+
r = requests.get(
|
|
72
|
+
"https://raw.githubusercontent.com/bcarry/ska/main/data/hst_sun.csv"
|
|
73
|
+
)
|
|
74
|
+
with open(ska.PATH_SUN, "w") as file:
|
|
75
|
+
file.write(r.text)
|
|
76
|
+
|
|
77
|
+
# Get Vega Spectrum
|
|
78
|
+
r = requests.get(
|
|
79
|
+
"https://raw.githubusercontent.com/bcarry/ska/main/data/lte096-4.0-0.5a%2B0.0.BT-NextGen.7.dat.csv"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
with open(ska.PATH_VEGA, "w") as file:
|
|
83
|
+
file.write(r.text)
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
except:
|
|
88
|
+
return False
|
space_ska-1.0/ska/cli.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import rich
|
|
6
|
+
|
|
7
|
+
import ska
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
@click.version_option(version=ska.__version__, message="%(version)s")
|
|
12
|
+
def cli_ska():
|
|
13
|
+
"""CLI for Spectral-Kit for Asteroids."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# --------------------------------------------------------------------------------
|
|
18
|
+
# Status
|
|
19
|
+
@cli_ska.command()
|
|
20
|
+
@click.option(
|
|
21
|
+
"--clear",
|
|
22
|
+
"-c",
|
|
23
|
+
help="Clear cached filters and update the filter list.",
|
|
24
|
+
is_flag=True,
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--update", "-u", help="Update cached filters and filter list.", is_flag=True
|
|
28
|
+
)
|
|
29
|
+
def status(clear, update):
|
|
30
|
+
"""Echo the status of the cached filters."""
|
|
31
|
+
from rich import prompt
|
|
32
|
+
|
|
33
|
+
from ska import cache
|
|
34
|
+
|
|
35
|
+
# ------
|
|
36
|
+
# Check filter list
|
|
37
|
+
if not os.path.isfile(ska.PATH_FILTER_LIST):
|
|
38
|
+
ska.svo.download_filter_list()
|
|
39
|
+
|
|
40
|
+
# ------
|
|
41
|
+
# Echo inventory
|
|
42
|
+
cached_ids, cached_xmls = cache.take_inventory()
|
|
43
|
+
|
|
44
|
+
rich.print(
|
|
45
|
+
f"""\nContents of {ska.PATH_CACHE}:
|
|
46
|
+
|
|
47
|
+
{len(cached_xmls)} filters\n"""
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Update or clear
|
|
51
|
+
if cached_xmls:
|
|
52
|
+
if not clear and not update:
|
|
53
|
+
decision = prompt.Prompt.ask(
|
|
54
|
+
"Update or clear the cached filters and filter list?\n"
|
|
55
|
+
"[blue][0][/blue] No "
|
|
56
|
+
"[blue][1][/blue] Clear cache "
|
|
57
|
+
"[blue][2][/blue] Update data ",
|
|
58
|
+
choices=["0", "1", "2"],
|
|
59
|
+
show_choices=False,
|
|
60
|
+
default="0",
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
decision = "none"
|
|
64
|
+
|
|
65
|
+
if clear or decision == "1":
|
|
66
|
+
rich.print("\nClearing the cached filters and filter list..")
|
|
67
|
+
cache.clear()
|
|
68
|
+
|
|
69
|
+
elif update or decision == "2":
|
|
70
|
+
rich.print(cached_ids)
|
|
71
|
+
rich.print("\nDownload filters from SVO Filter Service..")
|
|
72
|
+
cache.update_filters(cached_ids, force=True)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------------------------
|
|
76
|
+
# Fuzzy search among filters ID
|
|
77
|
+
@cli_ska.command()
|
|
78
|
+
def id():
|
|
79
|
+
"""Fuzzy-search SVO filter index."""
|
|
80
|
+
|
|
81
|
+
import shutil
|
|
82
|
+
import subprocess
|
|
83
|
+
|
|
84
|
+
PATH_EXECUTABLE = shutil.which("fzf")
|
|
85
|
+
|
|
86
|
+
if PATH_EXECUTABLE is None:
|
|
87
|
+
rich.print(
|
|
88
|
+
"Interactive selection is not possible as the fzf tool is not installed.\n"
|
|
89
|
+
)
|
|
90
|
+
sys.exit()
|
|
91
|
+
|
|
92
|
+
FZF_OPTIONS = []
|
|
93
|
+
|
|
94
|
+
# Open fzf subprocess
|
|
95
|
+
process = subprocess.Popen(
|
|
96
|
+
[shutil.which("fzf"), *FZF_OPTIONS],
|
|
97
|
+
stdin=subprocess.PIPE,
|
|
98
|
+
stdout=subprocess.PIPE,
|
|
99
|
+
stderr=None,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
FILTERS = ska.svo.load_filter_list()
|
|
103
|
+
for filter in FILTERS:
|
|
104
|
+
line = filter.encode(sys.getdefaultencoding()) + b"\n"
|
|
105
|
+
process.stdin.write(line)
|
|
106
|
+
process.stdin.flush()
|
|
107
|
+
|
|
108
|
+
# Run process and wait for user selection
|
|
109
|
+
process.stdin.close()
|
|
110
|
+
process.wait()
|
|
111
|
+
|
|
112
|
+
# Extract selected line
|
|
113
|
+
try:
|
|
114
|
+
choice = [line for line in process.stdout][0].decode()
|
|
115
|
+
filt = ska.Filter(choice.strip())
|
|
116
|
+
filt.display_summary()
|
|
117
|
+
|
|
118
|
+
except IndexError: # no choice was made, c-c c-c
|
|
119
|
+
sys.exit()
|
|
120
|
+
return choice
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# --------------------------------------------------------------------------------
|
|
124
|
+
# Color computation
|
|
125
|
+
@cli_ska.command()
|
|
126
|
+
@click.argument("file")
|
|
127
|
+
@click.argument("filter1")
|
|
128
|
+
@click.argument("filter2")
|
|
129
|
+
@click.option(
|
|
130
|
+
"--phot_sys", default="Vega", help="Photometric system: Vega (default) | ST | AB"
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--reflectance",
|
|
134
|
+
"-r",
|
|
135
|
+
is_flag=True,
|
|
136
|
+
default=False,
|
|
137
|
+
help="Multiply the input reflectance by Solar spectrum.",
|
|
138
|
+
)
|
|
139
|
+
def color(file, filter1, filter2, phot_sys, reflectance):
|
|
140
|
+
"""Compute the color between two filters"""
|
|
141
|
+
|
|
142
|
+
import pandas as pd
|
|
143
|
+
|
|
144
|
+
# TBD: interactive selection filters with fzf
|
|
145
|
+
|
|
146
|
+
# Load filters
|
|
147
|
+
f_1 = ska.Filter(filter1)
|
|
148
|
+
f_2 = ska.Filter(filter2)
|
|
149
|
+
|
|
150
|
+
# Read spectrum
|
|
151
|
+
spectrum = ska.Spectrum(file)
|
|
152
|
+
|
|
153
|
+
# Compute color
|
|
154
|
+
if reflectance:
|
|
155
|
+
color = spectrum.reflectance_to_color(f_1, f_2, phot_sys=phot_sys)
|
|
156
|
+
# color = skatools.reflectance_to_color(spectrum, f_1, f_2, phot_sys=phot_sys)
|
|
157
|
+
else:
|
|
158
|
+
color = spectrum.compute_color(f_1, f_2, phot_sys=phot_sys)
|
|
159
|
+
# color = skatools.compute_color(spectrum, f_1, f_2, phot_sys=phot_sys)
|
|
160
|
+
click.echo(f"{color:4.2f}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# --------------------------------------------------------------------------------
|
|
164
|
+
# Solar Colors
|
|
165
|
+
@cli_ska.command()
|
|
166
|
+
@click.argument("filter1")
|
|
167
|
+
@click.argument("filter2")
|
|
168
|
+
@click.option(
|
|
169
|
+
"--phot_sys",
|
|
170
|
+
default="Vega",
|
|
171
|
+
help="Photometric system ([green]Vega[/green] | ST | AB)",
|
|
172
|
+
)
|
|
173
|
+
def solarcolor(filter1, filter2, phot_sys):
|
|
174
|
+
"""Compute the color of the Sun between two filters"""
|
|
175
|
+
|
|
176
|
+
# Load filters
|
|
177
|
+
f_1 = ska.Filter(filter1)
|
|
178
|
+
f_2 = ska.Filter(filter2)
|
|
179
|
+
|
|
180
|
+
# Compute color
|
|
181
|
+
color = f_1.solar_color(f_2, phot_sys=phot_sys)
|
|
182
|
+
click.echo(f"{color:4.2f}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --------------------------------------------------------------------------------
|
|
186
|
+
# Filter basic information
|
|
187
|
+
@cli_ska.command()
|
|
188
|
+
@click.argument("filter")
|
|
189
|
+
def filter(filter):
|
|
190
|
+
"""Display the basic properties of the filter"""
|
|
191
|
+
|
|
192
|
+
f = ska.Filter(filter)
|
|
193
|
+
f.display_summary()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# --------------------------------------------------------------------------------
|
|
197
|
+
# Plot filter transmission
|
|
198
|
+
@cli_ska.command()
|
|
199
|
+
@click.argument("filter")
|
|
200
|
+
@click.option("--figure", default=None, help="Name of the figure")
|
|
201
|
+
@click.option(
|
|
202
|
+
"--black", default=False, is_flag=True, help="Figure with a dark background"
|
|
203
|
+
)
|
|
204
|
+
def plot(filter, figure, black):
|
|
205
|
+
"""Display the basic properties of the filter"""
|
|
206
|
+
|
|
207
|
+
f = ska.Filter(filter)
|
|
208
|
+
|
|
209
|
+
import matplotlib.pyplot as plt
|
|
210
|
+
|
|
211
|
+
fig, ax = f.plot_transmission(figure, black=black)
|
|
212
|
+
|
|
213
|
+
if not "figure" in locals():
|
|
214
|
+
plt.show()
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from astropy.io.votable import parse
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
import rich
|
|
8
|
+
|
|
9
|
+
import ska
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Filter:
|
|
13
|
+
# --------------------------------------------------------------------------------
|
|
14
|
+
def __init__(self, id):
|
|
15
|
+
"""Load a filter.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
id : str
|
|
20
|
+
The filter unique ID (see SVO filter service)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Test validity of filters
|
|
24
|
+
FILTERS = ska.svo.load_filter_list()
|
|
25
|
+
if id not in FILTERS:
|
|
26
|
+
rich.print(
|
|
27
|
+
f"[red]Unknown filter ID {id}[/red]. Use [green]ska filter[/green] command to list available filters"
|
|
28
|
+
)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
# raise ValueError(f"Unknown filter ID {id}. Use [green]ska filter[/green] to list available filters")
|
|
31
|
+
|
|
32
|
+
self.id = id
|
|
33
|
+
self.path = os.path.join(ska.PATH_CACHE, f"{self.id.replace('/','_')}.xml")
|
|
34
|
+
|
|
35
|
+
# Download if not cached
|
|
36
|
+
if not os.path.isfile(self.path):
|
|
37
|
+
ska.svo.download_filter(self.id)
|
|
38
|
+
|
|
39
|
+
# Parse filter response
|
|
40
|
+
self.VOFilter = parse(self.path)
|
|
41
|
+
data = pd.DataFrame(data=self.VOFilter.get_first_table().array.data)
|
|
42
|
+
|
|
43
|
+
# Select non-zero transmission and convert to micron
|
|
44
|
+
data = data[data.Transmission >= 1e-5]
|
|
45
|
+
data.Wavelength /= 1e4 # to micron
|
|
46
|
+
|
|
47
|
+
# Store attributes
|
|
48
|
+
self.id = id
|
|
49
|
+
self.wave = data.Wavelength
|
|
50
|
+
self.trans = data.Transmission
|
|
51
|
+
self.central_wavelength = (
|
|
52
|
+
self.VOFilter.get_field_by_id("WavelengthCen").value / 1e4
|
|
53
|
+
)
|
|
54
|
+
self.FWHM = self.VOFilter.get_field_by_id("FWHM").value / 1e4
|
|
55
|
+
self.pivot_wavelength = (
|
|
56
|
+
self.VOFilter.get_field_by_id("WavelengthPivot").value / 1e4
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
self.facility = self.VOFilter.get_field_by_id("Facility").value
|
|
61
|
+
except:
|
|
62
|
+
self.facility = None
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
self.instrument = self.VOFilter.get_field_by_id("Instrument").value
|
|
66
|
+
except:
|
|
67
|
+
self.instrument = None
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
self.band = self.VOFilter.get_field_by_id("Band").value
|
|
71
|
+
except:
|
|
72
|
+
self.band = None
|
|
73
|
+
|
|
74
|
+
# --------------------------------------------------------------------------------
|
|
75
|
+
def display_summary(self):
|
|
76
|
+
import rich
|
|
77
|
+
|
|
78
|
+
rich.print(f"\n[bright_cyan]Filter ID :[/bright_cyan] {self.id}")
|
|
79
|
+
|
|
80
|
+
if self.facility is not None:
|
|
81
|
+
rich.print(f"[bright_cyan]Facility :[/bright_cyan] {self.facility:s}")
|
|
82
|
+
|
|
83
|
+
if self.instrument is not None:
|
|
84
|
+
rich.print(f"[bright_cyan]Instrument:[/bright_cyan] {self.instrument:s}")
|
|
85
|
+
|
|
86
|
+
if self.band is not None:
|
|
87
|
+
rich.print(f"[bright_cyan]Band :[/bright_cyan] {self.band:s}")
|
|
88
|
+
|
|
89
|
+
rich.print(
|
|
90
|
+
f"[bright_cyan]Central λ :[/bright_cyan] [green]{self.central_wavelength:.3f}[/green] [bright_cyan](micron)[/bright_cyan]"
|
|
91
|
+
)
|
|
92
|
+
rich.print(
|
|
93
|
+
f"[bright_cyan]FWHM :[/bright_cyan] [green]{self.FWHM:.3f}[/green] [bright_cyan](micron)[/bright_cyan]"
|
|
94
|
+
)
|
|
95
|
+
rich.print(
|
|
96
|
+
f"[bright_cyan]Pivot λ :[/bright_cyan] [green]{self.pivot_wavelength:.3f}[/green] [bright_cyan](micron)[/bright_cyan]"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# --------------------------------------------------------------------------------
|
|
100
|
+
def compute_flux(self, spectrum):
|
|
101
|
+
"""Computes the flux of a spectrum in a given band.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
spectrum : pd.DataFrame
|
|
106
|
+
Wavelength: in Angstrom
|
|
107
|
+
Flux: Flux density (erg/cm2/s/ang)
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
float
|
|
112
|
+
The computed mean flux density
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# Wavelength range to integrate over
|
|
116
|
+
lambda_int = np.arange(self.wave.min(), self.wave.max(), 0.0005)
|
|
117
|
+
|
|
118
|
+
# Detector type
|
|
119
|
+
# Photon counter
|
|
120
|
+
if self.VOFilter.get_field_by_id("DetectorType") == 1:
|
|
121
|
+
factor = lambda_int
|
|
122
|
+
# Energy counter
|
|
123
|
+
else:
|
|
124
|
+
factor = lambda_int * 0 + 1
|
|
125
|
+
|
|
126
|
+
# Interpolate over the transmission range
|
|
127
|
+
interpol_transmission = np.interp(lambda_int, self.wave, self.trans)
|
|
128
|
+
|
|
129
|
+
interpol_spectrum = np.interp(lambda_int, spectrum.Wavelength, spectrum.Flux)
|
|
130
|
+
|
|
131
|
+
# Compute the flux by integrating over wavelength.
|
|
132
|
+
nom = np.trapz(
|
|
133
|
+
interpol_spectrum * interpol_transmission * factor,
|
|
134
|
+
lambda_int,
|
|
135
|
+
)
|
|
136
|
+
denom = np.trapz(interpol_transmission * factor, lambda_int)
|
|
137
|
+
flux = nom / denom
|
|
138
|
+
return flux
|
|
139
|
+
|
|
140
|
+
# --------------------------------------------------------------------------------
|
|
141
|
+
def solar_color(self, filter, phot_sys="Vega", vega=None):
|
|
142
|
+
"""Compute the color of the Sun between current and provided filter
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
==========
|
|
146
|
+
filter: str
|
|
147
|
+
The filter unique ID (see SVO filter service)
|
|
148
|
+
|
|
149
|
+
phot_sys : str
|
|
150
|
+
Photometric system in which to report the color (default=AB)
|
|
151
|
+
|
|
152
|
+
vega : ska.Spectrum
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
=======
|
|
156
|
+
float
|
|
157
|
+
The solar color
|
|
158
|
+
"""
|
|
159
|
+
if not isinstance(filter, ska.Filter):
|
|
160
|
+
filter = ska.Filter(filter)
|
|
161
|
+
|
|
162
|
+
# Extract Solar Fluxes
|
|
163
|
+
sun_1 = self.VOFilter.get_field_by_id("Fsun").value
|
|
164
|
+
sun_2 = filter.VOFilter.get_field_by_id("Fsun").value
|
|
165
|
+
|
|
166
|
+
# Convert to magnitude
|
|
167
|
+
mag_1 = -2.5 * np.log10(sun_1)
|
|
168
|
+
mag_2 = -2.5 * np.log10(sun_2)
|
|
169
|
+
colorST = mag_1 - mag_2
|
|
170
|
+
|
|
171
|
+
# Solar color in ST photometric system
|
|
172
|
+
if phot_sys == "ST":
|
|
173
|
+
return colorST
|
|
174
|
+
|
|
175
|
+
# Solar color in Vega photometric system
|
|
176
|
+
elif phot_sys == "Vega":
|
|
177
|
+
# Read Vega spectrum if not provided
|
|
178
|
+
if not "vega" in locals():
|
|
179
|
+
vega = ska.Spectrum(ska.PATH_VEGA)
|
|
180
|
+
else:
|
|
181
|
+
if not isinstance(vega, ska.Spectrum):
|
|
182
|
+
vega = ska.Spectrum(ska.PATH_VEGA)
|
|
183
|
+
|
|
184
|
+
# Compute color of Vega
|
|
185
|
+
vega_ST = vega.compute_color(self, filter, phot_sys="ST")
|
|
186
|
+
return colorST - vega_ST
|
|
187
|
+
|
|
188
|
+
# Solar color in ST photometric system
|
|
189
|
+
else:
|
|
190
|
+
pivot_1 = self.VOFilter.get_field_by_id("WavelengthPivot").value
|
|
191
|
+
pivot_2 = filter.VOFilter.get_field_by_id("WavelengthPivot").value
|
|
192
|
+
return colorST - 5 * np.log10(pivot_1 / pivot_2)
|
|
193
|
+
|
|
194
|
+
# --------------------------------------------------------------------------------
|
|
195
|
+
def plot_transmission(self, figure=None, black=False):
|
|
196
|
+
"""Create a plot of the transmission.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
figure : str
|
|
201
|
+
Path to save a figure
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
figure, axe
|
|
206
|
+
Matplotlib figure and axe
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
# Define figure
|
|
210
|
+
import matplotlib.pyplot as plt
|
|
211
|
+
|
|
212
|
+
if black:
|
|
213
|
+
plt.style.use("dark_background")
|
|
214
|
+
else:
|
|
215
|
+
plt.style.use("default")
|
|
216
|
+
fig, ax = plt.subplots()
|
|
217
|
+
|
|
218
|
+
# Plot transmission
|
|
219
|
+
ax.plot(self.wave, self.trans, label=self.id)
|
|
220
|
+
|
|
221
|
+
# Central wavelength and FWHM
|
|
222
|
+
ax.axvline(
|
|
223
|
+
self.central_wavelength,
|
|
224
|
+
color="gray",
|
|
225
|
+
linestyle="--",
|
|
226
|
+
label=r"$\lambda_c$ = {:.2f} $\mu$m".format(self.central_wavelength),
|
|
227
|
+
)
|
|
228
|
+
ax.plot(
|
|
229
|
+
self.central_wavelength + self.FWHM / 2 * np.array([-1, 1]),
|
|
230
|
+
[self.trans.max() / 2, self.trans.max() / 2],
|
|
231
|
+
linestyle="dotted",
|
|
232
|
+
color="gray",
|
|
233
|
+
label=r"FWHM = {:.2f} $\mu$m".format(self.FWHM),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Add labels
|
|
237
|
+
ax.set_xlabel("Wavelength (micron)")
|
|
238
|
+
ax.set_ylabel("Transmission")
|
|
239
|
+
ax.legend(loc="lower right")
|
|
240
|
+
ax.set_ylim(bottom=0)
|
|
241
|
+
fig.tight_layout()
|
|
242
|
+
|
|
243
|
+
# Save to file
|
|
244
|
+
if figure is not None:
|
|
245
|
+
# fig.savefig(figure, dpi=180, facecolor="w", edgecolor="w")
|
|
246
|
+
fig.savefig(figure, dpi=180)
|
|
247
|
+
|
|
248
|
+
return fig, ax
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
import rich
|
|
8
|
+
import ska
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Spectrum:
|
|
12
|
+
# --------------------------------------------------------------------------------
|
|
13
|
+
def __init__(self, input=None):
|
|
14
|
+
""" """
|
|
15
|
+
|
|
16
|
+
# Store attributes
|
|
17
|
+
self.Wavelength = None
|
|
18
|
+
self.Flux = None
|
|
19
|
+
self.Reflectance = False
|
|
20
|
+
|
|
21
|
+
if "input" in locals():
|
|
22
|
+
|
|
23
|
+
# Initialize from a file
|
|
24
|
+
if isinstance(input, str):
|
|
25
|
+
self.from_csv(input)
|
|
26
|
+
|
|
27
|
+
# Initialize from a numpy.ndarray
|
|
28
|
+
if isinstance(input, np.ndarray):
|
|
29
|
+
self.from_numpy(input)
|
|
30
|
+
|
|
31
|
+
# Initialize from a pandas.DataFrame
|
|
32
|
+
if isinstance(input, pd.DataFrame):
|
|
33
|
+
self.from_dataframe(input)
|
|
34
|
+
|
|
35
|
+
# --------------------------------------------------------------------------------
|
|
36
|
+
def copy(self):
|
|
37
|
+
from copy import deepcopy
|
|
38
|
+
|
|
39
|
+
return deepcopy(self)
|
|
40
|
+
|
|
41
|
+
# --------------------------------------------------------------------------------
|
|
42
|
+
def from_csv(self, file):
|
|
43
|
+
if not os.path.isfile(file):
|
|
44
|
+
rich.print(f"[red]Spectrum file {file} not found.[/red].")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
# Read spectrum
|
|
48
|
+
try:
|
|
49
|
+
spectrum = pd.read_csv(file)
|
|
50
|
+
except:
|
|
51
|
+
rich.print(f"[red]Cannot read spectrum file {file}.[/red].")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
self.from_dataframe(spectrum)
|
|
55
|
+
|
|
56
|
+
# --------------------------------------------------------------------------------
|
|
57
|
+
def from_numpy(self, arr, reflectance=False):
|
|
58
|
+
if not isinstance(arr, np.ndarray):
|
|
59
|
+
rich.print(f"[red]Input is not a numpy array.[/red]")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
if arr.shape[1] < 2:
|
|
63
|
+
rich.print(f"[red]Input array has less than 2 columns.[/red]")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
# Store attributes
|
|
67
|
+
order = np.argsort(arr[:, 0])
|
|
68
|
+
self.Wavelength = arr[order, 0]
|
|
69
|
+
self.Flux = arr[order, 1]
|
|
70
|
+
self.Reflectance = reflectance
|
|
71
|
+
|
|
72
|
+
# --------------------------------------------------------------------------------
|
|
73
|
+
def from_dataframe(self, df):
|
|
74
|
+
|
|
75
|
+
# Test Wavelength column
|
|
76
|
+
check_wave = True
|
|
77
|
+
if not "Wavelength" in df.columns:
|
|
78
|
+
check_wave = False
|
|
79
|
+
rich.print(f"[red]Column 'Wavelength' missing from input.[/red]")
|
|
80
|
+
|
|
81
|
+
# Test Flux or Reflectance column
|
|
82
|
+
check_flux = True
|
|
83
|
+
check_refl = True
|
|
84
|
+
if not "Flux" in df.columns:
|
|
85
|
+
check_flux = False
|
|
86
|
+
if not "Reflectance" in df.columns:
|
|
87
|
+
check_refl = False
|
|
88
|
+
|
|
89
|
+
if not (check_flux | check_refl):
|
|
90
|
+
rich.print(f"[red]Column 'Flux' or 'Reflectance' missing from input.[/red]")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
if not (check_wave & (check_flux | check_refl)):
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
# Store attributes
|
|
97
|
+
order = np.argsort(df.Wavelength)
|
|
98
|
+
self.Wavelength = np.array(df.loc[order, "Wavelength"].values)
|
|
99
|
+
if check_flux:
|
|
100
|
+
self.Flux = np.array(df.loc[order, "Flux"].values)
|
|
101
|
+
if check_refl:
|
|
102
|
+
self.Flux = np.array(df.loc[order, "Reflectance"].values)
|
|
103
|
+
self.Reflectance = True
|
|
104
|
+
|
|
105
|
+
# --------------------------------------------------------------------------------
|
|
106
|
+
def compute_color(self, filter1, filter2, phot_sys="Vega", vega=None):
|
|
107
|
+
"""Computes filter_1-filter_2 color of spectrum in the requested system.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
==========
|
|
111
|
+
filter_1: ska.Filter
|
|
112
|
+
|
|
113
|
+
filter_2: ska.Filter
|
|
114
|
+
|
|
115
|
+
phot_sys : str
|
|
116
|
+
Photometric system in which to report the color (default=Vega)
|
|
117
|
+
|
|
118
|
+
vega : ska.Spectrum
|
|
119
|
+
The spectrum of Vega
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
=======
|
|
123
|
+
float
|
|
124
|
+
The requested color
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
# Compute fluxes in each filter
|
|
128
|
+
flux1 = filter1.compute_flux(self)
|
|
129
|
+
flux2 = filter2.compute_flux(self)
|
|
130
|
+
|
|
131
|
+
# Magnitude in AB photometric system
|
|
132
|
+
if phot_sys == "AB":
|
|
133
|
+
# Get Pivot wavelength for both filters
|
|
134
|
+
pivot_1 = filter1.VOFilter.get_field_by_id("WavelengthPivot").value
|
|
135
|
+
pivot_2 = filter2.VOFilter.get_field_by_id("WavelengthPivot").value
|
|
136
|
+
|
|
137
|
+
# Compute and return the color
|
|
138
|
+
return -2.5 * np.log10(flux1 / flux2) - 5 * np.log10(pivot_1 / pivot_2)
|
|
139
|
+
|
|
140
|
+
# Magnitude in Vega photometric system
|
|
141
|
+
elif phot_sys == "Vega":
|
|
142
|
+
# Read Vega spectrum if not provided
|
|
143
|
+
if vega is None:
|
|
144
|
+
vega = ska.Spectrum(ska.PATH_VEGA)
|
|
145
|
+
|
|
146
|
+
# Compute fluxes of Vega in each filter
|
|
147
|
+
flux1_vega = filter1.compute_flux(vega)
|
|
148
|
+
flux2_vega = filter2.compute_flux(vega)
|
|
149
|
+
|
|
150
|
+
# Compute and return the color
|
|
151
|
+
return -2.5 * (np.log10(flux1 / flux1_vega) - np.log10(flux2 / flux2_vega))
|
|
152
|
+
|
|
153
|
+
# Magnitude in ST photometric system
|
|
154
|
+
elif phot_sys == "ST":
|
|
155
|
+
return -2.5 * np.log10(flux1 / flux2)
|
|
156
|
+
|
|
157
|
+
# --------------------------------------------------------------------------------
|
|
158
|
+
def reflectance_to_flux(self, sun=None):
|
|
159
|
+
"""Convert reflectance to flux by multiply by Solar spectrum.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
==========
|
|
163
|
+
sun : ska.Spectrum
|
|
164
|
+
Spectrum of the Sun
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
=======
|
|
168
|
+
ska.Spectrum
|
|
169
|
+
A copy of the input Spectrum, in flux units
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
# Test if the input is a reflectance spectrum
|
|
173
|
+
if not self.Reflectance:
|
|
174
|
+
rich.print(f"[red]Input spectrum is not a reflectance spectrum.[/red]")
|
|
175
|
+
|
|
176
|
+
# Read spectrum of the Sun if not provided
|
|
177
|
+
if not isinstance(sun, ska.Spectrum):
|
|
178
|
+
sun = ska.Spectrum(ska.PATH_SUN)
|
|
179
|
+
|
|
180
|
+
# Interpolate spectrum of the Sun
|
|
181
|
+
interpol_spectrum = np.interp(self.Wavelength, sun.Wavelength, sun.Flux)
|
|
182
|
+
|
|
183
|
+
# Mulitply reflectance by Solar spectrum
|
|
184
|
+
spectrum = self.copy()
|
|
185
|
+
spectrum.Flux = self.Flux * interpol_spectrum
|
|
186
|
+
spectrum.Reflectance = False
|
|
187
|
+
return spectrum
|
|
188
|
+
|
|
189
|
+
# --------------------------------------------------------------------------------
|
|
190
|
+
def reflectance_to_color(
|
|
191
|
+
self, filter1, filter2, phot_sys="Vega", vega=None, sun=None
|
|
192
|
+
):
|
|
193
|
+
"""Computes filter_1-filter_2 color for a reflectance spectrum.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
==========
|
|
197
|
+
filter_1: ska.Filter
|
|
198
|
+
|
|
199
|
+
filter_2: ska.Filter
|
|
200
|
+
|
|
201
|
+
phot_sys : str
|
|
202
|
+
Photometric system in which to report the color (default=Vega)
|
|
203
|
+
|
|
204
|
+
vega : ska.Spectrum
|
|
205
|
+
Spectrum of Vega
|
|
206
|
+
|
|
207
|
+
sun : ska.Spectrum
|
|
208
|
+
Spectrum of the Sun
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
=======
|
|
212
|
+
float
|
|
213
|
+
The requested color
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
# Integration grid is built from the transmission curve
|
|
217
|
+
lambda_min = np.min([filter1.wave.min(), filter2.wave.min()])
|
|
218
|
+
lambda_max = np.max([filter1.wave.max(), filter2.wave.max()])
|
|
219
|
+
|
|
220
|
+
# Wavelength range to integrate over
|
|
221
|
+
lambda_int = np.arange(lambda_min, lambda_max, 0.0005)
|
|
222
|
+
|
|
223
|
+
# Read spectrum of the Sun if not provided
|
|
224
|
+
if not isinstance(sun, ska.Spectrum):
|
|
225
|
+
sun = ska.Spectrum(ska.PATH_SUN)
|
|
226
|
+
|
|
227
|
+
# Interpolate spectrum of the Sun
|
|
228
|
+
interpol_spectrum = np.interp(lambda_int, sun.Wavelength, sun.Flux)
|
|
229
|
+
interp_sun = pd.DataFrame({"Wavelength": lambda_int, "Flux": interpol_spectrum})
|
|
230
|
+
interp_sun = interp_sun.astype("float")
|
|
231
|
+
|
|
232
|
+
# Interpolate reflectance spectrum
|
|
233
|
+
interpol_spectrum = np.interp(lambda_int, self.Wavelength, self.Flux)
|
|
234
|
+
interp_spectrum = ska.Spectrum(
|
|
235
|
+
pd.DataFrame(
|
|
236
|
+
{"Wavelength": lambda_int, "Flux": interpol_spectrum * interp_sun.Flux}
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Compute color of the reflectance*Sun spectrum
|
|
241
|
+
return interp_spectrum.compute_color(
|
|
242
|
+
filter1, filter2, phot_sys=phot_sys, vega=vega
|
|
243
|
+
)
|
space_ska-1.0/ska/svo.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import requests
|
|
5
|
+
from astropy.io.votable import parse
|
|
6
|
+
import rich
|
|
7
|
+
|
|
8
|
+
import ska
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def download_filter_list():
|
|
12
|
+
"""Retrieve the list of filter IDs from SVO Filter Service
|
|
13
|
+
http://svo2.cab.inta-csic.es/theory/fps/
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
=======
|
|
17
|
+
list
|
|
18
|
+
The list of filter IDS
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
|
|
23
|
+
# Main SVO filter list
|
|
24
|
+
r = requests.get(
|
|
25
|
+
"https://svo.cab.inta-csic.es/files/svo/Public/HowTo/FPS/FPS_info.xml"
|
|
26
|
+
)
|
|
27
|
+
SVOFilters = parse(io.BytesIO(r.content))
|
|
28
|
+
main_id = SVOFilters.get_first_table().to_table().to_pandas().filterID.to_list()
|
|
29
|
+
|
|
30
|
+
# Secondary SVO filter list
|
|
31
|
+
r = requests.get(
|
|
32
|
+
"https://svo.cab.inta-csic.es/files/svo/Public/HowTo/FPS/others.xml"
|
|
33
|
+
)
|
|
34
|
+
SVOFilters = parse(io.BytesIO(r.content))
|
|
35
|
+
other_id = SVOFilters.get_first_table().to_table().to_pandas()["__ID"].to_list()
|
|
36
|
+
|
|
37
|
+
# Merge and Write to disk
|
|
38
|
+
filter_id = main_id + other_id
|
|
39
|
+
with open(ska.PATH_FILTER_LIST, "w") as file:
|
|
40
|
+
for f in filter_id:
|
|
41
|
+
file.write(f"{f}\n")
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
except:
|
|
45
|
+
# raise Exception("Error downloading filter list")
|
|
46
|
+
rich.print(f"[red]Error downloading filter {id} VOTable[/red].")
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_filter_list():
|
|
51
|
+
"""Read all filter IDs from a cache list
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
=======
|
|
55
|
+
list
|
|
56
|
+
The list of filter IDS
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
if not os.path.isfile(ska.PATH_FILTER_LIST):
|
|
60
|
+
download_filter_list()
|
|
61
|
+
|
|
62
|
+
with open(ska.PATH_FILTER_LIST, "r") as file:
|
|
63
|
+
FILTERS = [filt.strip() for filt in file]
|
|
64
|
+
return FILTERS
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def download_filter(id, force=False):
|
|
68
|
+
"""Download a filter VOTable from SVO Filter Service
|
|
69
|
+
http://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
==========
|
|
73
|
+
id : str
|
|
74
|
+
The unique SVO filter identifier to be downloaded
|
|
75
|
+
|
|
76
|
+
force : bool
|
|
77
|
+
If True, the filter VOTable will be downloaded even if it is already cached
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
=======
|
|
81
|
+
str
|
|
82
|
+
The path to the filter VOTable file
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
# Test if the filter ID is valid
|
|
86
|
+
FILTERS = load_filter_list()
|
|
87
|
+
if id not in FILTERS:
|
|
88
|
+
rich.print(
|
|
89
|
+
f"[red]Unknown filter ID {id}[/red]. Use [green]ska filter[/green] to list available filters"
|
|
90
|
+
)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
# raise ValueError(f"Unknown filter ID {id}. Use ska filter to list available filters")
|
|
93
|
+
|
|
94
|
+
# SVO Base URL for queries
|
|
95
|
+
url = f"http://svo2.cab.inta-csic.es/theory/fps3/fps.php?"
|
|
96
|
+
|
|
97
|
+
# Output name for the filter VOTable
|
|
98
|
+
out = os.path.join(ska.PATH_CACHE, id.replace("/", "_") + ".xml")
|
|
99
|
+
|
|
100
|
+
# Download VOTable
|
|
101
|
+
if (not os.path.isfile(out)) or force:
|
|
102
|
+
try:
|
|
103
|
+
# Request the filter VOTable
|
|
104
|
+
r = requests.get(url, params={"ID": id})
|
|
105
|
+
SVOFilter = parse(io.BytesIO(r.content))
|
|
106
|
+
filter_info = SVOFilter.get_first_table()
|
|
107
|
+
|
|
108
|
+
# Write it to disk
|
|
109
|
+
# os.makedirs(ska.PATH_CACHE, exist_ok=True)
|
|
110
|
+
SVOFilter.to_xml(out)
|
|
111
|
+
|
|
112
|
+
except:
|
|
113
|
+
rich.print(f"[red]Error downloading filter {id} VOTable[/red].")
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
# raise Exception("Error downloading filter VOTable")
|
|
116
|
+
|
|
117
|
+
# Return path to filter VOTable
|
|
118
|
+
return out
|