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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ lightstack