pandoraaperture 0.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.
- pandoraaperture-0.1.0/LICENSE +21 -0
- pandoraaperture-0.1.0/PKG-INFO +45 -0
- pandoraaperture-0.1.0/docs/README.md +20 -0
- pandoraaperture-0.1.0/pyproject.toml +58 -0
- pandoraaperture-0.1.0/src/pandoraaperture/__init__.py +149 -0
- pandoraaperture-0.1.0/src/pandoraaperture/data/pandora.mplstyle +71 -0
- pandoraaperture-0.1.0/src/pandoraaperture/docstrings.py +239 -0
- pandoraaperture-0.1.0/src/pandoraaperture/fits.py +207 -0
- pandoraaperture-0.1.0/src/pandoraaperture/prf.py +766 -0
- pandoraaperture-0.1.0/src/pandoraaperture/scene.py +551 -0
- pandoraaperture-0.1.0/src/pandoraaperture/utils.py +24 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pandora Data Processing Center
|
|
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.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pandoraaperture
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Christina Hedges
|
|
7
|
+
Author-email: christina.l.hedges@nasa.gov
|
|
8
|
+
Requires-Python: >=3.9,<3.13
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
|
15
|
+
Requires-Dist: astropy (>=6.0.1)
|
|
16
|
+
Requires-Dist: gaiaoffline (>=1.0.3,<2.0.0)
|
|
17
|
+
Requires-Dist: matplotlib (>=2.0.0)
|
|
18
|
+
Requires-Dist: numpy (>=1.2)
|
|
19
|
+
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
20
|
+
Requires-Dist: pandoraref (>=0.3.0)
|
|
21
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
22
|
+
Requires-Dist: sparse3d (>=1.2.10)
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
<a href="https://github.com/pandoramission/pandora-aperture/actions/workflows/black.yml"><img src="https://github.com/pandoramission/pandora-aperture/workflows/black/badge.svg" alt="black status"/></a> <a href="https://github.com/pandoramission/pandora-aperture/actions/workflows/flake8.yml"><img src="https://github.com/pandoramission/pandora-aperture/workflows/flake8/badge.svg" alt="flake8 status"/></a> [](https://pandoramission.github.io/pandora-aperture/)
|
|
26
|
+
[](https://pypi.org/project/pandoraaperture/)
|
|
27
|
+
[](https://pypi.org/project/pandoraaperture/)
|
|
28
|
+
|
|
29
|
+
# Pandora Aperture
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
To install you can use
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pip install pandoraaperture --upgrade
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You should update your package often, as we frequently put out new versions with updated Current Best Estimates, and some limited new functionality. Check your version number using
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pandoraaperture as pa
|
|
43
|
+
pa.__version__
|
|
44
|
+
```
|
|
45
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<a href="https://github.com/pandoramission/pandora-aperture/actions/workflows/black.yml"><img src="https://github.com/pandoramission/pandora-aperture/workflows/black/badge.svg" alt="black status"/></a> <a href="https://github.com/pandoramission/pandora-aperture/actions/workflows/flake8.yml"><img src="https://github.com/pandoramission/pandora-aperture/workflows/flake8/badge.svg" alt="flake8 status"/></a> [](https://pandoramission.github.io/pandora-aperture/)
|
|
2
|
+
[](https://pypi.org/project/pandoraaperture/)
|
|
3
|
+
[](https://pypi.org/project/pandoraaperture/)
|
|
4
|
+
|
|
5
|
+
# Pandora Aperture
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
To install you can use
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install pandoraaperture --upgrade
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You should update your package often, as we frequently put out new versions with updated Current Best Estimates, and some limited new functionality. Check your version number using
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import pandoraaperture as pa
|
|
19
|
+
pa.__version__
|
|
20
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pandoraaperture"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Christina Hedges",email = "christina.l.hedges@nasa.gov"}
|
|
7
|
+
]
|
|
8
|
+
readme = "docs/README.md"
|
|
9
|
+
requires-python = ">=3.9,<3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"rich (>=13.9.4,<14.0.0)",
|
|
12
|
+
"numpy (>=1.2)",
|
|
13
|
+
"pandas (>=2.2.3,<3.0.0)",
|
|
14
|
+
"appdirs (>=1.4.4,<2.0.0)",
|
|
15
|
+
"sparse3d (>=1.2.10)",
|
|
16
|
+
"matplotlib (>=2.0.0)",
|
|
17
|
+
"astropy (>=6.0.1)",
|
|
18
|
+
"gaiaoffline (>=1.0.3,<2.0.0)",
|
|
19
|
+
"pandoraref (>=0.3.0)"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.poetry]
|
|
23
|
+
packages = [{include = "pandoraaperture", from = "src"}]
|
|
24
|
+
|
|
25
|
+
[tool.poetry.group.dev]
|
|
26
|
+
optional = true
|
|
27
|
+
|
|
28
|
+
[tool.poetry.group.dev.dependencies]
|
|
29
|
+
pytest = "^8.3.4"
|
|
30
|
+
black = "^25.1.0"
|
|
31
|
+
isort = "^6.0.0"
|
|
32
|
+
flake8 = "^7.1.2"
|
|
33
|
+
jupyterlab = "^4.3.5"
|
|
34
|
+
mkdocs = "^1.6.1"
|
|
35
|
+
mkdocs-jupyter = "^0.25.1"
|
|
36
|
+
mkdocs-material = "^9.6.5"
|
|
37
|
+
pytkdocs = {version = "^0.16.2", extras = ["numpy-style"]}
|
|
38
|
+
mkdocs-include-markdown-plugin = "^7.1.4"
|
|
39
|
+
mkdocstrings = {version = "^0.28.2", extras = ["python"]}
|
|
40
|
+
jupyter-contrib-nbextensions = "^0.7.0"
|
|
41
|
+
notebook = ">=6.0.0,<7.0.0"
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
45
|
+
build-backend = "poetry.core.masonry.api"
|
|
46
|
+
|
|
47
|
+
[tool.black]
|
|
48
|
+
line-length = 79
|
|
49
|
+
|
|
50
|
+
[tool.isort]
|
|
51
|
+
import_heading_firstparty = 'First-party/Local'
|
|
52
|
+
import_heading_future = 'Future'
|
|
53
|
+
import_heading_stdlib = 'Standard library'
|
|
54
|
+
import_heading_thirdparty = 'Third-party'
|
|
55
|
+
line_length = 79
|
|
56
|
+
multi_line_output = 3
|
|
57
|
+
no_lines_before = 'LOCALFOLDER'
|
|
58
|
+
profile = 'black'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Standard library
|
|
2
|
+
import logging # noqa: E402
|
|
3
|
+
import os # noqa
|
|
4
|
+
from glob import glob
|
|
5
|
+
|
|
6
|
+
# Third-party
|
|
7
|
+
from rich.console import Console # noqa: E402
|
|
8
|
+
from rich.logging import RichHandler # noqa: E402
|
|
9
|
+
|
|
10
|
+
PACKAGEDIR = os.path.abspath(os.path.dirname(__file__))
|
|
11
|
+
DOCSDIR = "/".join(PACKAGEDIR.split("/")[:-2]) + "/docs/"
|
|
12
|
+
TESTDIR = "/".join(PACKAGEDIR.split("/")[:-2]) + "/tests/"
|
|
13
|
+
PANDORASTYLE = glob(f"{PACKAGEDIR}/data/pandora.mplstyle")
|
|
14
|
+
|
|
15
|
+
# Standard library
|
|
16
|
+
import configparser # noqa: E402
|
|
17
|
+
from importlib.metadata import PackageNotFoundError, version # noqa
|
|
18
|
+
|
|
19
|
+
# Third-party
|
|
20
|
+
import numpy as np # noqa: E402
|
|
21
|
+
import pandas as pd # noqa: E402
|
|
22
|
+
import pandoraref as pr # noqa: E402
|
|
23
|
+
from appdirs import user_config_dir, user_data_dir # noqa: E402
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_version():
|
|
27
|
+
try:
|
|
28
|
+
return version("pandoraaperture")
|
|
29
|
+
except PackageNotFoundError:
|
|
30
|
+
return "unknown"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__version__ = get_version()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Custom Logger with Rich
|
|
37
|
+
class PandoraLogger(logging.Logger):
|
|
38
|
+
def __init__(self, name, level=logging.INFO):
|
|
39
|
+
super().__init__(name, level)
|
|
40
|
+
console = Console()
|
|
41
|
+
self.handler = RichHandler(
|
|
42
|
+
show_time=False, show_level=False, show_path=False, console=console
|
|
43
|
+
)
|
|
44
|
+
self.handler.setFormatter(
|
|
45
|
+
logging.Formatter(
|
|
46
|
+
"%(asctime)s %(levelname)s: %(message)s",
|
|
47
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
self.addHandler(self.handler)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_logger(name="pandoraaperture"):
|
|
54
|
+
"""Configure and return a logger with RichHandler."""
|
|
55
|
+
return PandoraLogger(name)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
CONFIGDIR = user_config_dir("pandoraaperture")
|
|
59
|
+
os.makedirs(CONFIGDIR, exist_ok=True)
|
|
60
|
+
CONFIGPATH = os.path.join(CONFIGDIR, "config.ini")
|
|
61
|
+
|
|
62
|
+
logger = get_logger("pandoraaperture")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def reset_config():
|
|
66
|
+
"""Set the config to defaults."""
|
|
67
|
+
# use this function to set your default configuration parameters.
|
|
68
|
+
config = configparser.ConfigParser()
|
|
69
|
+
config["SETTINGS"] = {
|
|
70
|
+
"log_level": "WARNING",
|
|
71
|
+
"data_dir": user_data_dir("pandoraaperture"),
|
|
72
|
+
"pixel_buffer": 15,
|
|
73
|
+
"catalog_columns": "source_id, phot_g_mean_flux, phot_bp_mean_flux, phot_rp_mean_flux, j_flux, h_flux, k_flux, teff_gspphot",
|
|
74
|
+
}
|
|
75
|
+
with open(CONFIGPATH, "w") as configfile:
|
|
76
|
+
config.write(configfile)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_config() -> configparser.ConfigParser:
|
|
80
|
+
"""
|
|
81
|
+
Loads the configuration file, creating it with defaults if it doesn't exist.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
configparser.ConfigParser
|
|
86
|
+
The loaded configuration.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
config = configparser.ConfigParser()
|
|
90
|
+
|
|
91
|
+
if not os.path.exists(CONFIGPATH):
|
|
92
|
+
# Create default configuration
|
|
93
|
+
reset_config()
|
|
94
|
+
config.read(CONFIGPATH)
|
|
95
|
+
return config
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def save_config(config: configparser.ConfigParser) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Saves the configuration to the file.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
config : configparser.ConfigParser
|
|
105
|
+
The configuration to save.
|
|
106
|
+
app_name : str
|
|
107
|
+
Name of the application.
|
|
108
|
+
"""
|
|
109
|
+
with open(CONFIGPATH, "w") as configfile:
|
|
110
|
+
config.write(configfile)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
config = load_config()
|
|
114
|
+
|
|
115
|
+
# Use this to check that keys you expect are in the config file.
|
|
116
|
+
# If you update the config file and think users may be out of date
|
|
117
|
+
# add the config parameters to this loop to check and reset the config.
|
|
118
|
+
for key in ["data_dir", "log_level"]:
|
|
119
|
+
if key not in config["SETTINGS"]:
|
|
120
|
+
logger.error(
|
|
121
|
+
f"`{key}` missing from the `pandoraaperture` config file. Your configuration is being reset."
|
|
122
|
+
)
|
|
123
|
+
reset_config()
|
|
124
|
+
config = load_config()
|
|
125
|
+
|
|
126
|
+
DATADIR = config["SETTINGS"]["data_dir"]
|
|
127
|
+
logger.setLevel(config["SETTINGS"]["log_level"])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def display_config() -> pd.DataFrame:
|
|
131
|
+
dfs = []
|
|
132
|
+
for section in config.sections():
|
|
133
|
+
df = pd.DataFrame(
|
|
134
|
+
np.asarray(
|
|
135
|
+
[(key, value) for key, value in dict(config[section]).items()]
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
df["section"] = section
|
|
139
|
+
df.columns = ["key", "value", "section"]
|
|
140
|
+
df = df.set_index(["section", "key"])
|
|
141
|
+
dfs.append(df)
|
|
142
|
+
return pd.concat(dfs)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
NIRDAReference = pr.NIRDAReference()
|
|
146
|
+
VISDAReference = pr.VISDAReference()
|
|
147
|
+
|
|
148
|
+
from .prf import PRF, DispersedPRF, SpatialPRF # noqa: E402,F401
|
|
149
|
+
from .scene import DispersedSkyScene, ROISkyScene, SkyScene # noqa: E402,F401
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
lines.color: C0 # has no affect on plot(); see axes.prop_cycle
|
|
2
|
+
lines.linewidth : 0.8
|
|
3
|
+
lines.markersize : 1.5
|
|
4
|
+
figure.facecolor : white
|
|
5
|
+
figure.edgecolor: white # figure edge color
|
|
6
|
+
figure.dpi : 150
|
|
7
|
+
figure.titlesize: x-large # size of the figure title (``Figure.suptitle()``)
|
|
8
|
+
figure.autolayout: True # When True, automatically adjust subplot
|
|
9
|
+
# parameters to make the plot fit the figure
|
|
10
|
+
# using `tight_layout`
|
|
11
|
+
image.origin: lower # {lower, upper}
|
|
12
|
+
text.color : 000000
|
|
13
|
+
axes.spines.top : False
|
|
14
|
+
axes.spines.right : False
|
|
15
|
+
axes.titlepad : 10
|
|
16
|
+
axes.titlesize : x-large
|
|
17
|
+
axes.facecolor : white
|
|
18
|
+
axes.edgecolor : 000000
|
|
19
|
+
axes.labelcolor : 000000
|
|
20
|
+
axes.labelsize : large
|
|
21
|
+
axes.linewidth : 1.25
|
|
22
|
+
axes.prop_cycle : cycler('color', ['000000', '0072B2', '009E73', 'D55E00', 'CC79A7', 'F0E442', '56B4E9'])
|
|
23
|
+
axes.formatter.use_mathtext: True # When True, use mathtext for scientific
|
|
24
|
+
# notation.
|
|
25
|
+
axes.formatter.min_exponent: 1 # minimum exponent to format in scientific notation
|
|
26
|
+
axes.formatter.useoffset: True # If True, the tick label formatter
|
|
27
|
+
# will default to labeling ticks relative
|
|
28
|
+
# to an offset when the data range is
|
|
29
|
+
# small compared to the minimum absolute
|
|
30
|
+
# value of the data.
|
|
31
|
+
axes.formatter.offset_threshold: 4 # When useoffset is True, the offset
|
|
32
|
+
# will be used when it can remove
|
|
33
|
+
# at least this number of significant
|
|
34
|
+
# digits from tick labels.
|
|
35
|
+
|
|
36
|
+
axes.unicode_minus: True # use Unicode for the minus symbol rather than hyphen. See
|
|
37
|
+
# https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes
|
|
38
|
+
axes.labelpad: 7.0
|
|
39
|
+
errorbar.capsize: 3
|
|
40
|
+
legend.fontsize : medium
|
|
41
|
+
legend.frameon : True
|
|
42
|
+
legend.numpoints : 3
|
|
43
|
+
legend.scatterpoints : 3
|
|
44
|
+
legend.facecolor : inherit
|
|
45
|
+
legend.edgecolor : 000000
|
|
46
|
+
legend.framealpha: 0.9
|
|
47
|
+
legend.handlelength: 2.5 # the length of the legend lines
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
savefig.dpi: 150 # figure dots per inch or 'figure'
|
|
51
|
+
savefig.bbox: tight # {tight, standard}
|
|
52
|
+
|
|
53
|
+
grid.linestyle: --
|
|
54
|
+
|
|
55
|
+
xtick.top : False
|
|
56
|
+
xtick.color : 666666
|
|
57
|
+
xtick.labelcolor : 000000
|
|
58
|
+
xtick.direction : in
|
|
59
|
+
xtick.major.size : 8
|
|
60
|
+
xtick.minor.size : 4
|
|
61
|
+
xtick.minor.visible : True
|
|
62
|
+
xtick.labelsize : medium
|
|
63
|
+
|
|
64
|
+
ytick.minor.visible : True
|
|
65
|
+
ytick.right : False
|
|
66
|
+
ytick.color : 666666
|
|
67
|
+
ytick.labelcolor : 000000
|
|
68
|
+
ytick.direction : in
|
|
69
|
+
ytick.major.size : 8
|
|
70
|
+
ytick.minor.size : 4
|
|
71
|
+
ytick.labelsize : medium
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Standard library
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
# Third-party
|
|
5
|
+
import astropy.units as u
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from astropy.coordinates import SkyCoord
|
|
9
|
+
from astropy.time import Time
|
|
10
|
+
from astropy.wcs import WCS
|
|
11
|
+
from scipy.interpolate import RectBivariateSpline
|
|
12
|
+
from sparse3d import Sparse3D
|
|
13
|
+
|
|
14
|
+
DOCSTRINGS = {
|
|
15
|
+
"name": (str, "Name of detector, choose from VISDA or NIRDA."),
|
|
16
|
+
"delta_pos": (
|
|
17
|
+
tuple,
|
|
18
|
+
"Change in position in pixels. Use format (row, column).",
|
|
19
|
+
),
|
|
20
|
+
"file": (
|
|
21
|
+
str,
|
|
22
|
+
"Input file to use. Choose either a string path to the file or an `astropy.fits.HDUList` object",
|
|
23
|
+
),
|
|
24
|
+
"flux": (
|
|
25
|
+
npt.NDArray,
|
|
26
|
+
"Array of the flux of the PRF as a function of position. Usually normalized such that the total flux is 1.",
|
|
27
|
+
),
|
|
28
|
+
"gradients": (
|
|
29
|
+
bool,
|
|
30
|
+
"Whether to return gradients. If True, will return an additional 2 arrays that contain the gradients in each axis.",
|
|
31
|
+
),
|
|
32
|
+
"pixel_size": (
|
|
33
|
+
u.Quantity,
|
|
34
|
+
"True detector pixel size in dimensions of length/pixel",
|
|
35
|
+
),
|
|
36
|
+
"sub_pixel_size": (
|
|
37
|
+
u.Quantity,
|
|
38
|
+
"PSF file pixel size in dimensions of length/pixel",
|
|
39
|
+
),
|
|
40
|
+
"scale": (
|
|
41
|
+
float,
|
|
42
|
+
"How much to scale the PRF by. Scale of 2 makes the PSF 2x broader. Default is 1.",
|
|
43
|
+
),
|
|
44
|
+
"imshape": (
|
|
45
|
+
tuple,
|
|
46
|
+
"Tuple of the shape of the true image. "
|
|
47
|
+
"If using ROIs, use the shape of the image that "
|
|
48
|
+
"each ROI is cut out from. Use format (row, column).",
|
|
49
|
+
),
|
|
50
|
+
"imcorner": (
|
|
51
|
+
tuple,
|
|
52
|
+
"Tuple of the lower left corner of the image, i.e. it's origin. "
|
|
53
|
+
"Use this to move the image around on the grid. If using a window mode,"
|
|
54
|
+
" make sure to set this to the right corner. Use format (row, column).",
|
|
55
|
+
),
|
|
56
|
+
"location": (
|
|
57
|
+
tuple,
|
|
58
|
+
"Location of the source on the detector. Use format (row, column). "
|
|
59
|
+
"If not set will default to `self._default_location` which is in the "
|
|
60
|
+
"middle of the image as set by `imshape` and `imcorner`.",
|
|
61
|
+
),
|
|
62
|
+
"spline": (RectBivariateSpline, "A spline model describing the PRF."),
|
|
63
|
+
"normalize": (
|
|
64
|
+
bool,
|
|
65
|
+
"Whether to normalize the input data so that the total flux is 1.",
|
|
66
|
+
),
|
|
67
|
+
"row_im": (
|
|
68
|
+
npt.NDArray,
|
|
69
|
+
"1D Array of integer row values at which the PRF is evaluated.",
|
|
70
|
+
),
|
|
71
|
+
"column_im": (
|
|
72
|
+
npt.NDArray,
|
|
73
|
+
"1D Array of integer column values at which the PRF is evaluated.",
|
|
74
|
+
),
|
|
75
|
+
"prf_im": (
|
|
76
|
+
npt.NDArray,
|
|
77
|
+
"2D Array of PRF values.",
|
|
78
|
+
),
|
|
79
|
+
"dprf_im": (
|
|
80
|
+
npt.NDArray,
|
|
81
|
+
"2, 2D arrays of the gradient of the PRF values. Only returned if `gradients`=True.",
|
|
82
|
+
),
|
|
83
|
+
"X": (Sparse3D, "Sparse3D object containing the PRF at a given location."),
|
|
84
|
+
"dX0": (
|
|
85
|
+
Sparse3D,
|
|
86
|
+
"Sparse3D object containing the gradient of the PRF in axis 0"
|
|
87
|
+
" at a given location. Only returned if `gradients`=True.",
|
|
88
|
+
),
|
|
89
|
+
"dX1": (
|
|
90
|
+
Sparse3D,
|
|
91
|
+
"Sparse3D object containing the gradient of the PRF in axis 1"
|
|
92
|
+
" at a given location. Only returned if `gradients`=True.",
|
|
93
|
+
),
|
|
94
|
+
"focal_row": (
|
|
95
|
+
u.Quantity,
|
|
96
|
+
"Row position on the focal plane that corresponds to each element in the flux array. Units of pixels.",
|
|
97
|
+
),
|
|
98
|
+
"focal_column": (
|
|
99
|
+
u.Quantity,
|
|
100
|
+
"Column position on the focal plane that corresponds to each element in the flux array. Units of pixels.",
|
|
101
|
+
),
|
|
102
|
+
"trace_row": (
|
|
103
|
+
u.Quantity,
|
|
104
|
+
"Row position within the spectral trace that corresponds to each element in the flux array. Units of pixels.",
|
|
105
|
+
),
|
|
106
|
+
"trace_column": (
|
|
107
|
+
u.Quantity,
|
|
108
|
+
"Column position within the spectral trace that corresponds to each element in the flux array. Units of pixels.",
|
|
109
|
+
),
|
|
110
|
+
"prf": ("PRF", "pandoraaperture PRF class."),
|
|
111
|
+
"wcs": (WCS, "astropy World Coordinate System"),
|
|
112
|
+
"time": (Time, "astropy Time"),
|
|
113
|
+
"user_cat": (
|
|
114
|
+
pd.DataFrame,
|
|
115
|
+
"Optional catalog from the user. Use this to pass a catalog of objects expected in this"
|
|
116
|
+
" data that are not part of the Gaia catalog. You must include all the columns "
|
|
117
|
+
"specified in your config file under `catalog_columns`.",
|
|
118
|
+
),
|
|
119
|
+
"nROIs": (int, "The number of regions of interest in the larger image"),
|
|
120
|
+
"ROI_size": (
|
|
121
|
+
tuple,
|
|
122
|
+
"The size the regions of interest in (row, column) pixels. All ROIs must be the same size.",
|
|
123
|
+
),
|
|
124
|
+
"ROI_corners": (
|
|
125
|
+
list,
|
|
126
|
+
"The origin (lower left) corner positon for each of the ROIs. Must have length nROIs. List of tuples.",
|
|
127
|
+
),
|
|
128
|
+
"cat": (
|
|
129
|
+
pd.DataFrame,
|
|
130
|
+
"Catalog of sources that will land within the image.",
|
|
131
|
+
),
|
|
132
|
+
"coord": (SkyCoord, "Coordinate in the sky."),
|
|
133
|
+
"radius": (u.Quantity, "A radius in degrees for a cone search."),
|
|
134
|
+
"A": (
|
|
135
|
+
Sparse3D,
|
|
136
|
+
"Matrix containing the PRFs of all targets in the scene. ",
|
|
137
|
+
),
|
|
138
|
+
"target": (
|
|
139
|
+
int,
|
|
140
|
+
"Indicates a target in the catalog. Use either in integer to express "
|
|
141
|
+
"an index in the catalog, or a SkyCoord to find the closest target",
|
|
142
|
+
),
|
|
143
|
+
"threshold": (
|
|
144
|
+
float,
|
|
145
|
+
"Threshold to cut aperture off at, in units of electrons/s.",
|
|
146
|
+
),
|
|
147
|
+
"ra": (u.Quantity, "Right Ascention"),
|
|
148
|
+
"dec": (u.Quantity, "Declination"),
|
|
149
|
+
"theta": (u.Quantity, "Roll angle in degrees"),
|
|
150
|
+
"pixel_resolution": (
|
|
151
|
+
float,
|
|
152
|
+
"The separation between different elements in the PRF model in pixels. For example 0.25"
|
|
153
|
+
" means there will be approximately 0.25 pixels between each element.",
|
|
154
|
+
),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def extract_docstring_type(dtype, desc):
|
|
159
|
+
if isinstance(dtype, tuple):
|
|
160
|
+
dtype_str = " or ".join(
|
|
161
|
+
[t._name if hasattr(t, "_name") else t.__name__][0]
|
|
162
|
+
for t in dtype
|
|
163
|
+
if t is not None
|
|
164
|
+
)
|
|
165
|
+
dtype_str += " or None" if None in dtype else ""
|
|
166
|
+
elif isinstance(dtype, str):
|
|
167
|
+
dtype_str = dtype
|
|
168
|
+
else:
|
|
169
|
+
dtype_str = dtype.__name__
|
|
170
|
+
return dtype_str, desc
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def clean_docstring(func, additional_docstring, indent_str, heading):
|
|
174
|
+
existing_docstring = func.__doc__ or ""
|
|
175
|
+
if heading in existing_docstring:
|
|
176
|
+
func.__doc__ = (
|
|
177
|
+
existing_docstring.split("---\n")[0]
|
|
178
|
+
+ "---\n"
|
|
179
|
+
+ additional_docstring
|
|
180
|
+
+ "---\n".join(existing_docstring.split("---\n")[1:])
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
func.__doc__ = (
|
|
184
|
+
existing_docstring
|
|
185
|
+
+ f"\n\n{indent_str}{heading}\n{indent_str}----------\n"
|
|
186
|
+
+ additional_docstring
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Decorator to add common parameters to docstring
|
|
191
|
+
def add_docstring(func=None, *, parameters=None, returns=None):
|
|
192
|
+
def decorator(func, parameters=parameters, returns=returns):
|
|
193
|
+
param_docstring, return_docstring = "", ""
|
|
194
|
+
if func.__doc__:
|
|
195
|
+
# Determine the current indentation level
|
|
196
|
+
lines = func.__doc__.splitlines()
|
|
197
|
+
if len(lines[0]) == 0:
|
|
198
|
+
indent = len(lines[1]) - len(lines[1].lstrip())
|
|
199
|
+
else:
|
|
200
|
+
indent = len(lines[0]) - len(lines[0].lstrip())
|
|
201
|
+
else:
|
|
202
|
+
indent = 0
|
|
203
|
+
indent_str = " " * indent
|
|
204
|
+
if isinstance(parameters, str):
|
|
205
|
+
parameters = [parameters]
|
|
206
|
+
if isinstance(returns, str):
|
|
207
|
+
returns = [returns]
|
|
208
|
+
|
|
209
|
+
if parameters is not None:
|
|
210
|
+
for name in parameters:
|
|
211
|
+
if name in DOCSTRINGS:
|
|
212
|
+
dtype_str, desc = extract_docstring_type(*DOCSTRINGS[name])
|
|
213
|
+
param_docstring += f"{indent_str}{name}: {dtype_str}\n{indent_str} {desc}\n"
|
|
214
|
+
clean_docstring(func, param_docstring, indent_str, "Parameters")
|
|
215
|
+
|
|
216
|
+
if returns is not None:
|
|
217
|
+
for name in returns:
|
|
218
|
+
if name in DOCSTRINGS:
|
|
219
|
+
dtype_str, desc = extract_docstring_type(*DOCSTRINGS[name])
|
|
220
|
+
return_docstring += f"{indent_str}{name}: {dtype_str}\n{indent_str} {desc}\n"
|
|
221
|
+
clean_docstring(func, return_docstring, indent_str, "Returns")
|
|
222
|
+
return func
|
|
223
|
+
|
|
224
|
+
return decorator
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Decorator to inherit docstring from base class
|
|
228
|
+
def inherit_docstring(func):
|
|
229
|
+
@functools.wraps(func)
|
|
230
|
+
def wrapper(*args, **kwargs):
|
|
231
|
+
return func(*args, **kwargs)
|
|
232
|
+
|
|
233
|
+
if func.__doc__ is None:
|
|
234
|
+
for base in func.__qualname__.split(".")[0].__bases__:
|
|
235
|
+
base_func = getattr(base, func.__name__, None)
|
|
236
|
+
if base_func and base_func.__doc__:
|
|
237
|
+
func.__doc__ = base_func.__doc__
|
|
238
|
+
break
|
|
239
|
+
return wrapper
|