lightstack 0.1.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.
- lightstack/__init__.py +104 -0
- lightstack/crop.py +310 -0
- lightstack/datacube.py +493 -0
- lightstack/plot.py +310 -0
- lightstack/psf.py +336 -0
- lightstack/utils.py +223 -0
- lightstack-0.1.0.dist-info/METADATA +13 -0
- lightstack-0.1.0.dist-info/RECORD +10 -0
- lightstack-0.1.0.dist-info/WHEEL +5 -0
- lightstack-0.1.0.dist-info/top_level.txt +1 -0
lightstack/utils.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import glob
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from astropy.io import fits
|
|
7
|
+
from astropy.wcs import WCS
|
|
8
|
+
from astropy.wcs.utils import proj_plane_pixel_scales
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Filter sets (JWST and HST)
|
|
12
|
+
NIRCAM = {"F070W","F090W","F115W","F140M","F150W","F162M","F164N","F187N","F182M","F200W","F210M",
|
|
13
|
+
"F250M","F277W","F300M","F335M","F356W","F360M","F410M","F430M","F444W","F460M","F480M"}
|
|
14
|
+
ACS = {"F435W","F475W","F555W","F606W", "F625W","F775W","F814W","F850LP"}
|
|
15
|
+
WFC3_IR = {"F098M","F105W","F110W","F125W","F140W","F160W","F127M","F139M","F153M"}
|
|
16
|
+
WFC3_UV = {"F225W","F275W","F336W","F390W"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def pick_folder(filt):
|
|
20
|
+
"""
|
|
21
|
+
Returns the instrument folder name based on the filter.
|
|
22
|
+
"""
|
|
23
|
+
if filt in NIRCAM:
|
|
24
|
+
return "NIRCam"
|
|
25
|
+
if filt in ACS:
|
|
26
|
+
return "ACS"
|
|
27
|
+
if filt in WFC3_IR:
|
|
28
|
+
return "WFC3-IR"
|
|
29
|
+
if filt in WFC3_UV:
|
|
30
|
+
return "WFC3-UVIS"
|
|
31
|
+
return "OTHERS"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_filter(fname):
|
|
35
|
+
"""
|
|
36
|
+
Extracts the filter name from a FITS filename using a regular expression.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
fname : str
|
|
41
|
+
FITS filename.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
filt : str
|
|
46
|
+
Filter identifier (e.g., 'F150W', 'F410M').
|
|
47
|
+
|
|
48
|
+
Raises
|
|
49
|
+
------
|
|
50
|
+
ValueError
|
|
51
|
+
If no filter name is found in the filename.
|
|
52
|
+
"""
|
|
53
|
+
m = re.search(r'F\d{3}[WNM]', fname.upper())
|
|
54
|
+
if m:
|
|
55
|
+
return m.group(0)
|
|
56
|
+
raise ValueError(f"Filter not found in filename: {fname}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def infer_filter(fname):
|
|
60
|
+
"""
|
|
61
|
+
Infers the filter name from a FITS filename.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
fname : str
|
|
66
|
+
FITS filename.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
filt : str
|
|
71
|
+
Inferred filter name.
|
|
72
|
+
"""
|
|
73
|
+
base = os.path.basename(fname).upper().replace('.', '_')
|
|
74
|
+
for part in base.split('_'):
|
|
75
|
+
if part.startswith('F') and any(c.isdigit() for c in part):
|
|
76
|
+
return part
|
|
77
|
+
return os.path.splitext(os.path.basename(fname))[0]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def filter_id(filt):
|
|
81
|
+
"""
|
|
82
|
+
Extracts the numeric part of a filter name for sorting.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
filt : str
|
|
87
|
+
Filter name.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
num : int
|
|
92
|
+
Numeric wavelength identifier. If not found, returns a large value
|
|
93
|
+
so the filter is sorted last.
|
|
94
|
+
"""
|
|
95
|
+
m = re.search(r'F(\d+)', filt)
|
|
96
|
+
return int(m.group(1)) if m else 9999
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def find_ext(hdul):
|
|
101
|
+
"""
|
|
102
|
+
Finds the first FITS extension containing valid image data.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
hdul : astropy.io.fits.HDUList
|
|
107
|
+
Opened FITS file.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
ext : int or None
|
|
112
|
+
Index of the HDU containing image data. Returns None if not found.
|
|
113
|
+
"""
|
|
114
|
+
for i, hdu in enumerate(hdul):
|
|
115
|
+
if hdu.data is not None and isinstance(hdu.data, np.ndarray):
|
|
116
|
+
return i
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def save_fits(data, header, path):
|
|
121
|
+
"""
|
|
122
|
+
Saves a FITS file to disk.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
data : numpy.ndarray
|
|
127
|
+
Image data.
|
|
128
|
+
header : astropy.io.fits.Header
|
|
129
|
+
FITS header.
|
|
130
|
+
path : str
|
|
131
|
+
Output FITS path.
|
|
132
|
+
"""
|
|
133
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
134
|
+
fits.PrimaryHDU(data=data, header=header).writeto(path, overwrite=True)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def sort_fits(folder):
|
|
138
|
+
"""
|
|
139
|
+
Reads all FITS files in a folder and sorts them by filter wavelength.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
folder : str
|
|
144
|
+
Path to the folder containing FITS files.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
fits_sorted : list of tuples
|
|
149
|
+
List in the form [(fits_path, filter_name), ...], sorted by filter id.
|
|
150
|
+
"""
|
|
151
|
+
fits_files = glob.glob(os.path.join(folder, '*.fits'))
|
|
152
|
+
fits_list = [(f, infer_filter(f)) for f in fits_files]
|
|
153
|
+
fits_sorted = sorted(fits_list, key=lambda x: filter_id(x[1]))
|
|
154
|
+
return fits_sorted
|
|
155
|
+
|
|
156
|
+
def MJy_sr_to_jy(aligned_list):
|
|
157
|
+
"""
|
|
158
|
+
Convert FITS images from MJy/sr to Jy/pixel using the PIXAR_SR keyword: Jy/pixel = (MJy/sr) * 1e6 * PIXAR_SR
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
aligned_list : list of tuples
|
|
163
|
+
List in the form [(fits_path, filter_name), ...].
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
new_list : list of tuples
|
|
168
|
+
List in the form [(new_fits_path, filter_name), ...] for the converted files.
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
new_list = []
|
|
172
|
+
|
|
173
|
+
for fpath, filt in aligned_list:
|
|
174
|
+
|
|
175
|
+
with fits.open(fpath) as hdul:
|
|
176
|
+
ext = find_ext(hdul)
|
|
177
|
+
if ext is None:
|
|
178
|
+
print(f"No data extension found in '{fpath}'. Skipping.")
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
data = hdul[ext].data.astype(np.float64)
|
|
182
|
+
header = hdul[ext].header.copy()
|
|
183
|
+
|
|
184
|
+
# Pixel area in steradians
|
|
185
|
+
pixar_sr = header.get('PIXAR_SR')
|
|
186
|
+
if pixar_sr is None:
|
|
187
|
+
print(f"PIXAR_SR not found in '{fpath}'. Skipping.")
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
# Conversion factor: MJy to Jy and multiply by pixel area
|
|
191
|
+
factor = 1e6 * pixar_sr
|
|
192
|
+
data_jy = data * factor
|
|
193
|
+
|
|
194
|
+
# Update header
|
|
195
|
+
header['BUNIT'] = 'Jy'
|
|
196
|
+
header.add_history(
|
|
197
|
+
f"Converted from MJy/sr to Jy/pixel using PIXAR_SR = {pixar_sr}"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Output file name
|
|
201
|
+
out_name = os.path.splitext(fpath)[0] + "_Jy.fits"
|
|
202
|
+
|
|
203
|
+
# Save
|
|
204
|
+
fits.PrimaryHDU(data_jy, header=header).writeto(out_name, overwrite=True)
|
|
205
|
+
print(f"Saved: {out_name}")
|
|
206
|
+
|
|
207
|
+
new_list.append((out_name, filt))
|
|
208
|
+
|
|
209
|
+
return new_list
|
|
210
|
+
|
|
211
|
+
def get_pixel_scale(fits_path):
|
|
212
|
+
"""
|
|
213
|
+
Compute pixel scale in arcsec/pixel using WCS.
|
|
214
|
+
"""
|
|
215
|
+
with fits.open(fits_path) as hdul:
|
|
216
|
+
ext = find_ext(hdul)
|
|
217
|
+
if ext is None:
|
|
218
|
+
raise ValueError(f"No valid image data in '{fits_path}'.")
|
|
219
|
+
|
|
220
|
+
wcs = WCS(hdul[ext].header)
|
|
221
|
+
|
|
222
|
+
pixscale = proj_plane_pixel_scales(wcs)[0] * 3600.0
|
|
223
|
+
return pixscale
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lightstack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools for building and processing multi-filter astrophysical datacubes
|
|
5
|
+
Author: Andressa Wille, Thallis Pessi
|
|
6
|
+
Requires-Dist: numpy
|
|
7
|
+
Requires-Dist: astropy
|
|
8
|
+
Requires-Dist: matplotlib
|
|
9
|
+
Requires-Dist: reproject
|
|
10
|
+
Requires-Dist: regions
|
|
11
|
+
Requires-Dist: scipy
|
|
12
|
+
Requires-Dist: photutils
|
|
13
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
lightstack/__init__.py,sha256=6uDB2J1BBA-0VdHDJj0ycAjDeeF-WcJHtNr-4dLJxoY,1995
|
|
2
|
+
lightstack/crop.py,sha256=4N9OmSuAoCj84WzhzpUdZZbW30Uyd7_yb8vSP-Ab-64,8049
|
|
3
|
+
lightstack/datacube.py,sha256=Pe9wgn3I60lkie4LMwuIqvtZbjKopVmXAdEMliq30ng,14861
|
|
4
|
+
lightstack/plot.py,sha256=tH8kxZynUuZ3ekK6a078IvT1VOq0NnNeM64vPHJHH7U,7662
|
|
5
|
+
lightstack/psf.py,sha256=NrDCcUWCw-gCUtrerzbb5cDIpmySyjSKTL4wLdN7tYY,8442
|
|
6
|
+
lightstack/utils.py,sha256=UIOrH6_487dh151jEErx9V0KZ221I-94ZFQeZBwukqM,5718
|
|
7
|
+
lightstack-0.1.0.dist-info/METADATA,sha256=SlPbjgJZwLXO0kdku6yRlepmNsftvaXMaL1PSpKmnSQ,337
|
|
8
|
+
lightstack-0.1.0.dist-info/WHEEL,sha256=BNRMDyzLkkcmlv0J8ppDQkk2VED33SesJDynr9ED1gc,91
|
|
9
|
+
lightstack-0.1.0.dist-info/top_level.txt,sha256=DY6G7KIYRN3Tc97LHnFOPi8baJBN9tp4Hzczpre6ki0,11
|
|
10
|
+
lightstack-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lightstack
|