splusdata 5.2__tar.gz → 5.3__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.
Files changed (40) hide show
  1. {splusdata-5.2/splusdata.egg-info → splusdata-5.3}/PKG-INFO +1 -1
  2. {splusdata-5.2 → splusdata-5.3}/pyproject.toml +3 -2
  3. {splusdata-5.2 → splusdata-5.3}/splusdata/core.py +33 -2
  4. splusdata-5.3/splusdata/features/find_pointings.py +51 -0
  5. splusdata-5.3/splusdata/scubes/core.py +250 -0
  6. splusdata-5.3/splusdata/scubes/read.py +476 -0
  7. splusdata-5.3/splusdata/scubes/scripts.py +248 -0
  8. {splusdata-5.2 → splusdata-5.3}/splusdata/vars.py +24 -0
  9. {splusdata-5.2 → splusdata-5.3/splusdata.egg-info}/PKG-INFO +1 -1
  10. {splusdata-5.2 → splusdata-5.3}/splusdata.egg-info/SOURCES.txt +3 -1
  11. splusdata-5.3/splusdata.egg-info/entry_points.txt +4 -0
  12. splusdata-5.2/splusdata/scubes/constants.py +0 -8
  13. splusdata-5.2/splusdata/scubes/core.py +0 -327
  14. splusdata-5.2/splusdata.egg-info/entry_points.txt +0 -3
  15. {splusdata-5.2 → splusdata-5.3}/LICENSE +0 -0
  16. {splusdata-5.2 → splusdata-5.3}/README.md +0 -0
  17. {splusdata-5.2 → splusdata-5.3}/setup.cfg +0 -0
  18. {splusdata-5.2 → splusdata-5.3}/splusdata/__init__.py +0 -0
  19. {splusdata-5.2 → splusdata-5.3}/splusdata/connect.py +0 -0
  20. {splusdata-5.2 → splusdata-5.3}/splusdata/features/__init__.py +0 -0
  21. {splusdata-5.2 → splusdata-5.3}/splusdata/features/extinction.py +0 -0
  22. {splusdata-5.2 → splusdata-5.3}/splusdata/features/filterbw.py +0 -0
  23. {splusdata-5.2 → splusdata-5.3}/splusdata/features/hipscat.py +0 -0
  24. {splusdata-5.2 → splusdata-5.3}/splusdata/features/io.py +0 -0
  25. {splusdata-5.2 → splusdata-5.3}/splusdata/features/zeropoints/__init__.py +0 -0
  26. {splusdata-5.2 → splusdata-5.3}/splusdata/features/zeropoints/zp_image.py +0 -0
  27. {splusdata-5.2 → splusdata-5.3}/splusdata/features/zeropoints/zp_map.py +0 -0
  28. {splusdata-5.2 → splusdata-5.3}/splusdata/features/zeropointsdr4.py +0 -0
  29. {splusdata-5.2 → splusdata-5.3}/splusdata/models/__init__.py +0 -0
  30. {splusdata-5.2 → splusdata-5.3}/splusdata/models/star_gal_quasar.py +0 -0
  31. {splusdata-5.2 → splusdata-5.3}/splusdata/readconf.py +0 -0
  32. {splusdata-5.2 → splusdata-5.3}/splusdata/scripts/args.py +0 -0
  33. {splusdata-5.2 → splusdata-5.3}/splusdata/scubes/__init__.py +0 -0
  34. {splusdata-5.2 → splusdata-5.3}/splusdata/vacs/__init__.py +0 -0
  35. {splusdata-5.2 → splusdata-5.3}/splusdata/vacs/pdfs.py +0 -0
  36. {splusdata-5.2 → splusdata-5.3}/splusdata/vacs/sqg.py +0 -0
  37. {splusdata-5.2 → splusdata-5.3}/splusdata/variability/__init__.py +0 -0
  38. {splusdata-5.2 → splusdata-5.3}/splusdata.egg-info/dependency_links.txt +0 -0
  39. {splusdata-5.2 → splusdata-5.3}/splusdata.egg-info/requires.txt +0 -0
  40. {splusdata-5.2 → splusdata-5.3}/splusdata.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splusdata
3
- Version: 5.2
3
+ Version: 5.3
4
4
  Summary: Download SPLUS catalogs, FITS and more
5
5
  Author-email: Gustavo Schwarz <gustavo.b.schwarz@gmail.com>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splusdata"
7
- version = "5.2"
7
+ version = "5.3"
8
8
  description = "Download SPLUS catalogs, FITS and more"
9
9
  authors = [
10
10
  { name = "Gustavo Schwarz", email = "gustavo.b.schwarz@gmail.com" }
@@ -32,7 +32,8 @@ dependencies = [
32
32
 
33
33
  [project.scripts]
34
34
  splusdata = "splusdata.readconf:main"
35
- scubes_dr6 = "splusdata.scubes.core:scubes"
35
+ spd_scubes = "splusdata.scubes.scripts:scubes"
36
+ spd_scubesml = "splusdata.scubes.scripts:scubesml"
36
37
 
37
38
  [tool.setuptools.packages.find]
38
39
  where = ["."]
@@ -3,9 +3,11 @@ import getpass
3
3
 
4
4
  from PIL import Image
5
5
  from astropy.io import fits
6
+ import astropy.units as u
6
7
  import io
7
8
 
8
9
  from splusdata.features.io import print_level
10
+ from splusdata.features.find_pointings import find_pointing
9
11
 
10
12
  class SplusdataError(Exception):
11
13
  """Custom exception type for S-PLUS data errors raised by this helper module.
@@ -645,13 +647,42 @@ class Core:
645
647
  """
646
648
  stamp = self.stamp(ra, dec, size, band, weight=weight, field_name=field_name, size_unit=size_unit, data_release=data_release)
647
649
 
650
+ if weight:
651
+ return stamp
652
+
648
653
  from splusdata.features.zeropoints.zp_image import calibrate_hdu_with_zpmodel
649
654
  zp_model = self.get_zp_file(stamp[1].header["FIELD"], stamp[1].header["FILTER"], data_release=data_release)
650
655
 
651
656
  calibrated_hdu, factor_map = calibrate_hdu_with_zpmodel(
652
657
  stamp[1], zp_model, in_place=False, return_factor=True
653
658
  )
659
+
660
+ stamp[1] = calibrated_hdu
661
+ stamp.append(fits.ImageHDU(factor_map, name="ZP_FACTOR"))
654
662
 
655
663
  if outfile:
656
- calibrated_hdu.writeto(outfile, overwrite=True)
657
- return calibrated_hdu
664
+ stamp.writeto(outfile, overwrite=True)
665
+ return stamp
666
+
667
+ def check_coords(self, ra, dec, radius=1 * u.degree):
668
+ """Check which DR contains a pointing within `radius` of (ra, dec).
669
+
670
+ Parameters
671
+ ----------
672
+ ra, dec : float
673
+ Coordinates in degrees.
674
+ radius : Astropy unit, optional
675
+ Search radius in degrees (default 1 deg).
676
+
677
+ Returns
678
+ -------
679
+ dict or None
680
+ If found, a dict with keys 'dr', 'field', and 'distance' (an astropy
681
+ Quantity in degrees). If not found in any DR, returns None.
682
+
683
+ Raises
684
+ ------
685
+ Exception
686
+ Propagates any errors from `find_pointing`.
687
+ """
688
+ return find_pointing(ra, dec, radius=radius)
@@ -0,0 +1,51 @@
1
+ import pandas as pd
2
+ from functools import lru_cache
3
+ from astropy.coordinates import SkyCoord
4
+ import astropy.units as u
5
+
6
+ from splusdata.vars import DR_POINTINGS
7
+ # uses your DR_POINTINGS exactly as given
8
+
9
+ @lru_cache(maxsize=None)
10
+ def _load_dr(dr: str):
11
+ """Load and normalize a DR table once; results are cached."""
12
+ info = DR_POINTINGS[dr]
13
+ df = pd.read_csv(info["link"])
14
+ # keep only needed cols with consistent names
15
+ df = df.rename(columns={
16
+ info["ra_col"]: "ra",
17
+ info["dec_col"]: "dec",
18
+ info["field_col"]: "field",
19
+ })[["ra", "dec", "field"]].copy()
20
+ # make sure ra/dec are numeric; drop bad rows
21
+ df["ra"] = pd.to_numeric(df["ra"], errors="coerce")
22
+ df["dec"] = pd.to_numeric(df["dec"], errors="coerce")
23
+ df = df.dropna(subset=["ra", "dec"])
24
+ return df.reset_index(drop=True)
25
+
26
+ def find_pointing(ra, dec, radius=1*u.degree):
27
+ """
28
+ Return the first DR (by dr_order) whose field center lies within `radius`.
29
+ Output: {'dr': 'dr4', 'field': '...', 'distance': <Quantity ...>}
30
+ """
31
+ target = SkyCoord(ra=ra*u.deg, dec=dec*u.deg, frame="icrs")
32
+ radius = radius if isinstance(radius, u.Quantity) else radius * u.arcmin
33
+
34
+ for dr in DR_POINTINGS.keys():
35
+ df = _load_dr(dr)
36
+ coords = SkyCoord(df["ra"].values * u.deg, df["dec"].values * u.deg)
37
+ sep = target.separation(coords)
38
+ idx = int(sep.argmin())
39
+ if sep[idx] <= radius:
40
+ return {"dr": dr, "field": df["field"].iloc[idx], "distance": sep[idx]}
41
+ return None
42
+
43
+ # optional helpers:
44
+ def warm_cache(dr_order=("dr4","dr5","dr6")):
45
+ """Preload all DR tables into cache (optional)."""
46
+ for dr in dr_order:
47
+ _ = _load_dr(dr)
48
+
49
+ def clear_cache():
50
+ """Clear cached tables if you need to refresh."""
51
+ _load_dr.cache_clear()
@@ -0,0 +1,250 @@
1
+ import warnings
2
+ import numpy as np
3
+ from os import makedirs
4
+ import astropy.units as u
5
+ from tqdm.auto import tqdm
6
+ from astropy.io import fits
7
+ from astropy.wcs import WCS
8
+ from astropy.table import Table
9
+ from splusdata.core import Core
10
+ import astropy.constants as const
11
+ from os.path import join, exists
12
+ from astropy.wcs import FITSFixedWarning
13
+ from astropy.io.fits.verify import VerifyWarning
14
+
15
+ from splusdata.scubes.read import read_scube
16
+ from splusdata.vars import BANDS, BANDWAVEINFO, get_band_info
17
+ from splusdata.features.io import print_level, convert_coord_to_degrees
18
+
19
+ __scubes_author__ = 'Eduardo A. D. Lacerda <dhubax@gmail.com>'
20
+ __scubes_version__ = '0.1.idr6-beta'
21
+
22
+ def _getval_array(pathlist, key, ext):
23
+ return np.array([fits.getval(img, key, ext) for img in pathlist])
24
+
25
+ def _getdata_array(pathlist, ext):
26
+ return np.array([fits.getdata(img, ext=ext) for img in pathlist])
27
+
28
+ def _getheader_array(pathlist, ext):
29
+ return np.array([fits.getheader(img, ext=ext) for img in pathlist])
30
+
31
+ def _getval_array_mem(hdull, key, ext):
32
+ return np.array([hdul[ext].header.get(key) for hdul in hdull])
33
+
34
+ def _getdata_array_mem(hdull, ext):
35
+ return np.array([hdul[ext].data for hdul in hdull])
36
+
37
+ def _getheader_array_mem(hdull, ext):
38
+ return np.array([hdul[ext].header for hdul in hdull])
39
+
40
+ def _get_band_info_array(prop):
41
+ return np.array([get_band_info(band)[prop] for band in BANDS])
42
+
43
+ class SCubes:
44
+ def __init__(self, ra, dec, field, size=None, username=None, password=None, verbose=1):
45
+ self.conn = Core(username, password, verbose=verbose)
46
+ self.field = field
47
+ self.verbose = verbose
48
+ self.mem = False
49
+
50
+ if verbose == 0:
51
+ # dactivate warnings without verbosity
52
+ warnings.simplefilter('ignore', category=VerifyWarning)
53
+ warnings.simplefilter('ignore', category=FITSFixedWarning)
54
+
55
+ self.cubepath = None
56
+ self._stamp_config(ra, dec, size)
57
+
58
+ def _stamp_config(self, ra, dec, size):
59
+ self.ra, self.dec = convert_coord_to_degrees(ra, dec)
60
+ self.size = size
61
+
62
+ def _getval(self, obj, key, ext):
63
+ return _getval_array_mem(obj, key, ext) if self.mem else _getval_array(obj, key, ext)
64
+
65
+ def _getdata(self, obj, ext):
66
+ return _getdata_array_mem(obj, ext) if self.mem else _getdata_array(obj, ext)
67
+
68
+ def _getheader(self, obj, ext):
69
+ return _getheader_array_mem(obj, ext) if self.mem else _getheader_array(obj, ext)
70
+
71
+ def _download_calibrated_stamps(self, objname, outpath=None, force=False):
72
+ images = []
73
+ wimages = []
74
+ _kw_args = dict(ra=self.ra, dec=self.dec, size=self.size, field_name=self.field)
75
+ for b in tqdm(BANDS, desc=f'{objname} @ {self.field} - Downloading', leave=True, position=0):
76
+ b = b.upper().replace('J0', 'F')
77
+ kw_args = _kw_args.copy()
78
+ kw_args.update(band=b, weight=False)
79
+ if self.mem:
80
+ x = self.conn.calibrated_stamp(**kw_args)
81
+ images.append(x)
82
+ else:
83
+ filename = f'{objname}_{self.field}_{b}_{self.size}x{self.size}_swp.fits.fz'
84
+ kw_args.update(outfile=join(outpath, filename))
85
+ _ = self.conn.calibrated_stamp(**kw_args)
86
+ images.append(kw_args['outfile'])
87
+ # wei
88
+ kw_args['weight'] = True
89
+ if self.mem:
90
+ wimages.append(self.conn.calibrated_stamp(**kw_args))
91
+ else:
92
+ kw_args['outfile'] = join(outpath, filename.replace('swp', 'swpweight'))
93
+ _ = self.conn.calibrated_stamp(**kw_args)
94
+ wimages.append(kw_args['outfile'])
95
+ self.images = images
96
+ self.wimages = wimages
97
+
98
+ def _photospectra(self, flam_scale=None, ext=1):
99
+ flam_scale = 1e-19 if flam_scale is None else flam_scale
100
+ _c = const.c
101
+ scale = (1/flam_scale)
102
+ Jy2fnu = 3631e-23
103
+
104
+ self.wl__b = _get_band_info_array('pivot_wave')*u.Angstrom
105
+
106
+ self.flam_unit = u.erg / u.s / u.cm / u.cm / u.AA
107
+ self.fnu_unit = u.erg / u.s / u.cm / u.cm / u.Hz
108
+
109
+ # flux
110
+ calib_data__byx = self._getdata(self.images, ext)
111
+ fnu__byx = calib_data__byx*Jy2fnu*self.fnu_unit
112
+ flam__byx = scale*(fnu__byx*_c/self.wl__b[:, None, None]**2).to(self.flam_unit)
113
+
114
+ # error in flux
115
+ zp_factor__byx = self._getdata(self.images, 2)
116
+ absdata__byx = np.abs(calib_data__byx/zp_factor__byx)
117
+ gain__b = self._getval(self.images, 'GAIN', ext)
118
+ gain__byx = gain__b[:, None, None]
119
+ weidata__byx = self._getdata(self.wimages, ext)
120
+ absweidata__byx = np.abs(self._getdata(self.wimages, ext))
121
+ dataerr__byx = np.sqrt(1/absweidata__byx + absdata__byx/gain__byx)
122
+ f0__byx = zp_factor__byx*Jy2fnu
123
+ efnu__byx = dataerr__byx*f0__byx*self.fnu_unit
124
+ eflam__byx = scale*(efnu__byx*_c/self.wl__b[:, None, None]**2).to(self.flam_unit)
125
+
126
+ self.flam__byx = flam__byx
127
+ self.eflam__byx = eflam__byx
128
+ self.weidata__byx = weidata__byx
129
+ self.absweidata__byx = absweidata__byx
130
+
131
+ def _stamp_WCS_to_cube_header(self, header):
132
+ '''
133
+ Convert WCS information from stamp to cube header.
134
+
135
+ Parameters
136
+ ----------
137
+ header : :class:`~astropy.io.fits.Header`
138
+ FITS header containing WCS information.
139
+
140
+ Returns
141
+ -------
142
+ :class:`~astropy.io.fits.Header`
143
+ Cube header with updated WCS information.
144
+ '''
145
+ w = WCS(header)
146
+ nw = WCS(naxis=3)
147
+ nw.wcs.cdelt = [w.wcs.cdelt[0], w.wcs.cdelt[1], 1]
148
+ nw.wcs.crval = [w.wcs.crval[0], w.wcs.crval[1], 0]
149
+ nw.wcs.crpix = [w.wcs.crpix[0], w.wcs.crpix[1], 0]
150
+ nw.wcs.ctype = [w.wcs.ctype[0], w.wcs.ctype[1], '']
151
+ try:
152
+ nw.wcs.pc[:2, :2] = w.wcs.get_pc()
153
+ except:
154
+ pass
155
+ return nw.to_header()
156
+
157
+ def _weights_mask_hdu(self):
158
+ # WEIGHTS MASK HDU
159
+ w__byx = self.weidata__byx
160
+ wmask__byx = np.where(w__byx < 0, 1, 0)
161
+ wmask__yx = wmask__byx.sum(axis=0)
162
+ wmask_hdu = fits.ImageHDU(wmask__yx)
163
+ wmask_hdu.header['EXTNAME'] = ('WEIMASK', 'Sum of negative weight pixels (from 1 to 12)')
164
+ return wmask_hdu
165
+
166
+ def _metadata_hdu(self, ext=1):
167
+ # METADATA HDU
168
+ tab = [BANDS]
169
+ tab.append(_get_band_info_array('central_wave'))
170
+ tab.append(_get_band_info_array('pivot_wave'))
171
+ # PSFFWHM
172
+ psffwhm__b = []
173
+ for img in self.images:
174
+ if self.mem:
175
+ hdr = img[ext].header
176
+ else:
177
+ hdr = fits.getheader(img, ext=ext)
178
+ key = [k for k in hdr.keys() if 'FWHMMEAN' in k]
179
+ if len(key) == 1:
180
+ psffwhm__b.append(hdr.get(key[0]))
181
+ tab.append(psffwhm__b)
182
+ tab = Table(tab, names=['FILTER', 'CENTWAVE', 'PIVOTWAVE', 'PSFFWHM'])
183
+ return fits.BinTableHDU(tab, name='METADATA')
184
+
185
+ def _create_cube_hdulist(self, objname, ext=1):
186
+ cube_prim_hdu = fits.PrimaryHDU()
187
+ cube_prim_hdu.header['TILE'] = self.field
188
+ cube_prim_hdu.header['OBJECT'] = objname
189
+ cube_prim_hdu.header['SIZE'] = (self.size, 'Side of the stamp in pixels')
190
+ cube_prim_hdu.header['RA'] = self.ra
191
+ cube_prim_hdu.header['DEC'] = self.dec
192
+ hdr = self.images[0][ext].header if self.mem else fits.getheader(self.images[0], ext=ext)
193
+ cube_prim_hdu.header.update(self._stamp_WCS_to_cube_header(hdr))
194
+ for key in ['X0TILE', 'X01TILE', 'Y0TILE', 'Y01TILE']:
195
+ cube_prim_hdu.header[key] = hdr.get(key)
196
+ # DATA HDU
197
+ flam_hdu = fits.ImageHDU(self.flam__byx.value, cube_prim_hdu.header)
198
+ flam_hdu.header['EXTNAME'] = ('DATA', 'Name of the extension')
199
+ # ERRORS HDU
200
+ eflam_hdu = fits.ImageHDU(self.eflam__byx.value, cube_prim_hdu.header)
201
+ eflam_hdu.header['EXTNAME'] = ('ERRORS', 'Name of the extension')
202
+ hdul = [cube_prim_hdu, flam_hdu, eflam_hdu]
203
+ # INFO TO HEADERS
204
+ for hdu in hdul[1:]:
205
+ hdu.header['BSCALE'] = (self.flam_scale, 'Linear factor in scaling equation')
206
+ hdu.header['BZERO'] = (0, 'Zero point in scaling equation')
207
+ hdu.header['BUNIT'] = (f'{self.flam_unit}', 'Physical units of the array values')
208
+ hdul.append(self._weights_mask_hdu())
209
+ hdul.append(self._metadata_hdu(ext))
210
+ return fits.HDUList(hdul)
211
+
212
+ def write(self, cubepath, overwrite=False):
213
+ print_level(f'writting cube {cubepath}', 1, self.verbose)
214
+ self.cube.writeto(cubepath, overwrite=overwrite)
215
+ print_level(f'Cube successfully created!', 1, self.verbose)
216
+
217
+ def create_cube(self, flam_scale=None, objname=None, outpath=None, force=False, data_ext=1, write_fits=False, return_scube=False, force_mem=False):
218
+ self.flam_scale = 1e-19 if flam_scale is None else flam_scale
219
+ self.objname = 'myobj' if objname is None else objname
220
+ self.outpath = '.' if outpath is None else outpath
221
+ mkcube = True
222
+
223
+ if outpath is not None:
224
+ try:
225
+ makedirs(self.outpath)
226
+ except FileExistsError:
227
+ print_level(f'{self.outpath}: directory already exists', 1, self.verbose)
228
+
229
+ self.cubepath = join(self.outpath, f'{self.objname}_cube.fits')
230
+
231
+ if exists(self.cubepath) and not force:
232
+ mkcube = False
233
+ print_level(f'{self.cubepath}: cube already exists', 1, self.verbose)
234
+ else:
235
+ self.mem = True
236
+
237
+ if not self.mem and force_mem:
238
+ self.mem = True
239
+
240
+ if mkcube:
241
+ self._download_calibrated_stamps(objname, outpath, force=force)
242
+ self._photospectra(flam_scale, ext=data_ext)
243
+
244
+ self.cube = self._create_cube_hdulist(objname, ext=data_ext)
245
+
246
+ if (self.cubepath is not None) and write_fits:
247
+ self.write(self.cubepath, force)
248
+
249
+ if return_scube:
250
+ return read_scube(self.cube)