senpai 2.9.1__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.
- senpai/__init__.py +36 -0
- senpai/api/main.py +165 -0
- senpai/api/models/examples.py +51 -0
- senpai/api/models/returns.py +592 -0
- senpai/api/routes/astrometry.py +136 -0
- senpai/api/routes/senpai.py +159 -0
- senpai/api/tests/__init__.py +0 -0
- senpai/api/tests/conftest.py +73 -0
- senpai/api/tests/test_api.py +293 -0
- senpai/astrometry.py +256 -0
- senpai/catalog/README.md +142 -0
- senpai/catalog/__init__.py +0 -0
- senpai/catalog/constants.py +76 -0
- senpai/catalog/gaia.py +208 -0
- senpai/catalog/gaia_local.py +101 -0
- senpai/catalog/gaia_mirror.py +232 -0
- senpai/catalog/gaia_transforms.py +64 -0
- senpai/catalog/runner.py +1142 -0
- senpai/catalog/sdss.py +390 -0
- senpai/catalog/sstrc7_source.py +187 -0
- senpai/cli/batch.py +438 -0
- senpai/cli/burr.py +1203 -0
- senpai/cli/calibrate.py +77 -0
- senpai/cli/catalog_magnitude_analysis.py +337 -0
- senpai/cli/common.py +122 -0
- senpai/cli/coverage.py +3107 -0
- senpai/cli/detect.py +154 -0
- senpai/cli/measure_gain.py +165 -0
- senpai/cli/photometry.py +114 -0
- senpai/cli/plotting.py +81 -0
- senpai/cli/rate.py +117 -0
- senpai/cli/rate_sidereal.py +155 -0
- senpai/cli/sidereal.py +122 -0
- senpai/core/__init__.py +0 -0
- senpai/core/config.py +702 -0
- senpai/core/constants.py +54 -0
- senpai/core/logging.py +160 -0
- senpai/engine/__init__.py +24 -0
- senpai/engine/constants.py +3 -0
- senpai/engine/detection/jacobian.py +313 -0
- senpai/engine/detection/kernels.py +245 -0
- senpai/engine/detection/point/fwhm.py +339 -0
- senpai/engine/detection/point/satellite.py +980 -0
- senpai/engine/detection/point/sidereal.py +670 -0
- senpai/engine/detection/streak/extraction.py +2628 -0
- senpai/engine/detection/streak/frame_shift.py +165 -0
- senpai/engine/detection/streak/masking.py +776 -0
- senpai/engine/detection/streak/rate_extraction.py +222 -0
- senpai/engine/detection/streak/rate_rate.py +872 -0
- senpai/engine/detection/streak/rate_sidereal.py +525 -0
- senpai/engine/detection/streak/sidereal_sidereal.py +56 -0
- senpai/engine/detection/streak/sidereal_streak.py +1597 -0
- senpai/engine/detection/streak/validation.py +988 -0
- senpai/engine/detection/track_mode.py +315 -0
- senpai/engine/models/astrometry.py +517 -0
- senpai/engine/models/images.py +224 -0
- senpai/engine/models/metadata.py +234 -0
- senpai/engine/models/senpai.py +938 -0
- senpai/engine/models/starfield.py +246 -0
- senpai/engine/models/streak_measurement.py +307 -0
- senpai/engine/observability/__init__.py +27 -0
- senpai/engine/observability/calibration.py +3298 -0
- senpai/engine/observability/detector_gain.py +239 -0
- senpai/engine/photometry/color_terms.py +310 -0
- senpai/engine/photometry/photometry.md +210 -0
- senpai/engine/photometry/utils.py +2831 -0
- senpai/engine/plotting/axes.py +18 -0
- senpai/engine/plotting/images.py +1449 -0
- senpai/engine/plotting/normalization.py +66 -0
- senpai/engine/plotting/photometry.py +868 -0
- senpai/engine/plotting/psf.py +574 -0
- senpai/engine/plotting/replot.py +384 -0
- senpai/engine/plotting/wcs_diagnostics.py +143 -0
- senpai/engine/processing/__init__.py +0 -0
- senpai/engine/processing/collect.py +937 -0
- senpai/engine/processing/photometry_pipeline.py +254 -0
- senpai/engine/processing/rate.py +323 -0
- senpai/engine/processing/rate_scan_confirmation.py +851 -0
- senpai/engine/processing/sidereal.py +188 -0
- senpai/engine/processing/stamp_confirmation.py +1408 -0
- senpai/engine/processing/streak_correlation.py +743 -0
- senpai/engine/tests/__init__.py +0 -0
- senpai/engine/tests/test_aperture_sums.py +93 -0
- senpai/engine/tests/test_burr_adapter.py +610 -0
- senpai/engine/tests/test_burr_replot.py +158 -0
- senpai/engine/tests/test_calibration.py +452 -0
- senpai/engine/tests/test_calibration_frames.py +326 -0
- senpai/engine/tests/test_catalog_logic.py +391 -0
- senpai/engine/tests/test_color_terms.py +261 -0
- senpai/engine/tests/test_config.py +210 -0
- senpai/engine/tests/test_detection_only_mode.py +499 -0
- senpai/engine/tests/test_detector_gain.py +97 -0
- senpai/engine/tests/test_export_coco.py +473 -0
- senpai/engine/tests/test_fits_io.py +367 -0
- senpai/engine/tests/test_frame_organization.py +188 -0
- senpai/engine/tests/test_gaia_local.py +73 -0
- senpai/engine/tests/test_gaia_sky_cache.py +88 -0
- senpai/engine/tests/test_half_flux_radius.py +71 -0
- senpai/engine/tests/test_masking.py +69 -0
- senpai/engine/tests/test_models.py +361 -0
- senpai/engine/tests/test_photometry_synthetic.py +514 -0
- senpai/engine/tests/test_photometry_zp.py +92 -0
- senpai/engine/tests/test_point_detection.py +610 -0
- senpai/engine/tests/test_preprocessing.py +411 -0
- senpai/engine/tests/test_sstrc7_source.py +113 -0
- senpai/engine/tests/test_stats.py +57 -0
- senpai/engine/tests/test_streak_extraction.py +184 -0
- senpai/engine/tests/test_streak_geometry.py +320 -0
- senpai/engine/tests/test_streak_validation.py +256 -0
- senpai/engine/tests/test_track_rate_units.py +48 -0
- senpai/engine/tests/test_wcs_helpers.py +79 -0
- senpai/engine/utils/astrometry_diagnostics.py +98 -0
- senpai/engine/utils/darks.py +945 -0
- senpai/engine/utils/file_io.py +156 -0
- senpai/engine/utils/fits_io.py +515 -0
- senpai/engine/utils/flats.py +746 -0
- senpai/engine/utils/frame_organization.py +174 -0
- senpai/engine/utils/memory.py +25 -0
- senpai/engine/utils/preprocessing.py +1548 -0
- senpai/engine/utils/propagate_wcs.py +21 -0
- senpai/engine/utils/serialization.py +52 -0
- senpai/engine/utils/simulation.py +206 -0
- senpai/engine/utils/stats.py +43 -0
- senpai/engine/utils/streak_chain.py +161 -0
- senpai/engine/utils/wcs_helpers.py +736 -0
- senpai/engine/utils/wcs_ops.py +329 -0
- senpai/engine/utils/wcs_refinement.py +1091 -0
- senpai/engine/utils/wcs_validation.py +191 -0
- senpai/export/README.md +176 -0
- senpai/export/__init__.py +11 -0
- senpai/export/cli.py +447 -0
- senpai/export/coco.py +1162 -0
- senpai/export/dataset_split.py +424 -0
- senpai/export/example_folder.py +132 -0
- senpai/integrations/__init__.py +0 -0
- senpai/integrations/burr/__init__.py +26 -0
- senpai/integrations/burr/filenames.py +96 -0
- senpai/integrations/burr/night.py +679 -0
- senpai/integrations/burr/replot.py +12 -0
- senpai/integrations/burr/run_state.py +134 -0
- senpai/resources/config/burr.yaml +115 -0
- senpai/resources/config/containerized.yaml +28 -0
- senpai/resources/config/dao.yaml +156 -0
- senpai/resources/config/local-localastrometry.yaml +27 -0
- senpai/resources/config/local.yaml +109 -0
- senpai/resources/config/local2.yaml +105 -0
- senpai-2.9.1.dist-info/METADATA +211 -0
- senpai-2.9.1.dist-info/RECORD +150 -0
- senpai-2.9.1.dist-info/WHEEL +4 -0
- senpai-2.9.1.dist-info/licenses/LICENSE +661 -0
senpai/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
+
|
|
4
|
+
from senpai.core.logging import setup_logging
|
|
5
|
+
|
|
6
|
+
setup_logging()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("senpai")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
try:
|
|
13
|
+
# astro-senpai <= 2.9.0 shipped the code under its own name
|
|
14
|
+
__version__ = version("astro-senpai")
|
|
15
|
+
except PackageNotFoundError:
|
|
16
|
+
__version__ = "unknown" # Fallback if the package is not installed
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _warn_if_installed_via_astro_senpai():
|
|
20
|
+
# astro-senpai is now an empty package that depends on senpai. Wheels cannot
|
|
21
|
+
# print at install time, so tell people at import to depend on senpai.
|
|
22
|
+
try:
|
|
23
|
+
version("senpai")
|
|
24
|
+
version("astro-senpai")
|
|
25
|
+
except PackageNotFoundError:
|
|
26
|
+
return
|
|
27
|
+
warnings.warn(
|
|
28
|
+
"senpai was installed through astro-senpai, its old PyPI name. Depend on "
|
|
29
|
+
"senpai instead: pip install senpai, or uv add senpai && uv remove "
|
|
30
|
+
"astro-senpai.",
|
|
31
|
+
FutureWarning,
|
|
32
|
+
stacklevel=3,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_warn_if_installed_via_astro_senpai()
|
senpai/api/main.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main entry point for API server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import concurrent.futures
|
|
6
|
+
import contextlib
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
14
|
+
|
|
15
|
+
from senpai.astrometry import examine_indices, test_astrometry_install
|
|
16
|
+
from senpai.core.config import AppConfig, initialize_config
|
|
17
|
+
from senpai.core.constants import LOCAL_APP_CONFIG_OVERRIDE
|
|
18
|
+
from senpai.core.logging import get_local_config, setup_logging
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
DEFAULT_EXECUTOR_MAX_WORKERS = max(1, (os.cpu_count() or 2) - 1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def setup_routes(app: FastAPI) -> None:
|
|
26
|
+
"""Configure application routes"""
|
|
27
|
+
logger.info("Setting up routes...")
|
|
28
|
+
# Import routes here to avoid circular imports
|
|
29
|
+
from senpai.api.routes import astrometry, senpai
|
|
30
|
+
|
|
31
|
+
app.include_router(senpai.router, tags=["SENPAI"], prefix="/senpai")
|
|
32
|
+
app.include_router(astrometry.router, tags=["Astrometry"], prefix="/astrometry")
|
|
33
|
+
|
|
34
|
+
logger.info("Routes setup completed")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextlib.asynccontextmanager
|
|
38
|
+
async def lifespan(app: FastAPI):
|
|
39
|
+
max_workers_env = os.getenv("SENPAI_EXECUTOR_WORKERS")
|
|
40
|
+
max_workers = int(max_workers_env) if max_workers_env else DEFAULT_EXECUTOR_MAX_WORKERS
|
|
41
|
+
executor = concurrent.futures.ProcessPoolExecutor(max_workers=max_workers, initializer=_init_pool_worker)
|
|
42
|
+
logger.info(f"Using ProcessPoolExecutor with max_workers={max_workers}")
|
|
43
|
+
|
|
44
|
+
app.state.executor = executor
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
test_astrometry_install()
|
|
48
|
+
examine_indices()
|
|
49
|
+
yield
|
|
50
|
+
finally:
|
|
51
|
+
executor.shutdown(wait=False, cancel_futures=True)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _init_pool_worker():
|
|
55
|
+
from senpai.core.config import initialize_config
|
|
56
|
+
|
|
57
|
+
cfg_path = os.getenv("SENPAI_CONFIG")
|
|
58
|
+
if cfg_path:
|
|
59
|
+
initialize_config(Path(cfg_path))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_app(config: AppConfig | str | Path | None = None) -> FastAPI:
|
|
63
|
+
"""Application factory supporting both local and Lambda environments"""
|
|
64
|
+
# Suppress specific warnings
|
|
65
|
+
# Setup logging, will check if already configured
|
|
66
|
+
# Example usage
|
|
67
|
+
setup_logging(
|
|
68
|
+
level="INFO",
|
|
69
|
+
disabled_loggers=["matplotlib", "astropy.io.fits", "scipy.optimize"],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if config is None:
|
|
73
|
+
config = Path(os.environ.get("SENPAI_CONFIG", LOCAL_APP_CONFIG_OVERRIDE))
|
|
74
|
+
|
|
75
|
+
if isinstance(config, (str, Path)):
|
|
76
|
+
logger.info(f"Loading config from path: {config}")
|
|
77
|
+
config = initialize_config(config)
|
|
78
|
+
|
|
79
|
+
# Create FastAPI app with conditional docs
|
|
80
|
+
logger.info(f"Creating app with config:\n{config.model_dump_json(indent=2)}")
|
|
81
|
+
app = FastAPI(
|
|
82
|
+
title="SENPAI API",
|
|
83
|
+
version=config.version,
|
|
84
|
+
debug=config.debug,
|
|
85
|
+
docs_url="/docs",
|
|
86
|
+
redoc_url="/redoc",
|
|
87
|
+
openapi_url="/openapi.json",
|
|
88
|
+
lifespan=lifespan,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Store uvicorn logging config
|
|
92
|
+
app.state.log_config = get_local_config()
|
|
93
|
+
app.state.log_level = config.logging.level.lower()
|
|
94
|
+
|
|
95
|
+
# Add CORS middleware configuration
|
|
96
|
+
app.add_middleware(
|
|
97
|
+
CORSMiddleware,
|
|
98
|
+
allow_origins=["*"],
|
|
99
|
+
allow_credentials=True,
|
|
100
|
+
allow_methods=["*"],
|
|
101
|
+
allow_headers=["*"],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Setup routes
|
|
105
|
+
setup_routes(app)
|
|
106
|
+
|
|
107
|
+
logger.info("App created successfully")
|
|
108
|
+
|
|
109
|
+
return app
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@click.command()
|
|
113
|
+
@click.option("--host", default="0.0.0.0", show_default=True)
|
|
114
|
+
@click.option("--port", default=8000, show_default=True)
|
|
115
|
+
@click.option("--workers", default=1, show_default=True, envvar="WORKERS")
|
|
116
|
+
@click.option(
|
|
117
|
+
"--config",
|
|
118
|
+
type=click.Path(exists=True, path_type=Path),
|
|
119
|
+
default=LOCAL_APP_CONFIG_OVERRIDE, # CLI use defaults to local config. Lambda does not use this.
|
|
120
|
+
show_default=True,
|
|
121
|
+
)
|
|
122
|
+
def run_server(host: str, port: int, workers: int | None, config: Path | None = None) -> None:
|
|
123
|
+
"""Run the API server locally"""
|
|
124
|
+
import uvicorn
|
|
125
|
+
|
|
126
|
+
# Load config first
|
|
127
|
+
config_path = config or LOCAL_APP_CONFIG_OVERRIDE
|
|
128
|
+
app_config = initialize_config(config_path)
|
|
129
|
+
|
|
130
|
+
logger.info("Starting uvicorn server...")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
workers = int(workers)
|
|
134
|
+
except ValueError:
|
|
135
|
+
workers = 1
|
|
136
|
+
|
|
137
|
+
if workers > 1:
|
|
138
|
+
# Set config path in the env so zero-arg factory can pick it up
|
|
139
|
+
os.environ.setdefault("SENPAI_CONFIG", str(config_path))
|
|
140
|
+
|
|
141
|
+
# Using zero-arg factory so workers can spawn
|
|
142
|
+
uvicorn.run(
|
|
143
|
+
"senpai.api.main:create_app",
|
|
144
|
+
factory=True,
|
|
145
|
+
host=host,
|
|
146
|
+
port=port,
|
|
147
|
+
workers=workers,
|
|
148
|
+
log_config=get_local_config(),
|
|
149
|
+
log_level=app_config.logging.level.lower(),
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
# Create and run app
|
|
153
|
+
app = create_app(app_config)
|
|
154
|
+
# Single process - use in-memory app
|
|
155
|
+
uvicorn.run(
|
|
156
|
+
app,
|
|
157
|
+
host=host,
|
|
158
|
+
port=port,
|
|
159
|
+
log_config=app.state.log_config,
|
|
160
|
+
log_level=app.state.log_level,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
run_server()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from senpai.engine.models.starfield import ImageMetadata, StarInImage, StarListImage
|
|
6
|
+
|
|
7
|
+
# A small (x, y, counts) sample of a real 1024x1024 detection list, spanning the
|
|
8
|
+
# brightness range of the field. This is an illustrative payload for /docs, not
|
|
9
|
+
# a solvable star field -- a full field would swamp the Swagger UI.
|
|
10
|
+
#
|
|
11
|
+
# It is a literal on purpose. FastAPI evaluates ``Body(examples=...)`` at import
|
|
12
|
+
# time, so anything read from disk here runs on every ``create_app()``; reading
|
|
13
|
+
# a repo test fixture there broke non-editable installs entirely (issue #6).
|
|
14
|
+
_EXAMPLE_DETECTIONS: list[tuple[float, float, float]] = [
|
|
15
|
+
(125.432, 568.183, 917909.8),
|
|
16
|
+
(291.171, 210.435, 10242.4),
|
|
17
|
+
(611.514, 645.360, 6810.8),
|
|
18
|
+
(501.782, 543.489, 4385.3),
|
|
19
|
+
(545.078, 117.519, 3354.3),
|
|
20
|
+
(158.725, 198.313, 2544.5),
|
|
21
|
+
(205.254, 510.905, 1744.2),
|
|
22
|
+
(372.452, 621.724, 1586.4),
|
|
23
|
+
(415.934, 294.103, 1235.9),
|
|
24
|
+
(78.394, 195.271, 1004.6),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StarListImageExample(BaseModel):
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__()
|
|
31
|
+
self._value: StarListImage | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def summary(self) -> str:
|
|
35
|
+
return "A list of stars in an image with image metadata"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def value(self) -> StarListImage:
|
|
39
|
+
"""Get example StarListImage value"""
|
|
40
|
+
if self._value is None:
|
|
41
|
+
self._value = StarListImage(
|
|
42
|
+
detections=[StarInImage(x=x, y=y, counts=counts) for x, y, counts in _EXAMPLE_DETECTIONS],
|
|
43
|
+
image_metadata=ImageMetadata(
|
|
44
|
+
image_id="x_y_counts", width=1024, height=1024, boresight_ra=245.45, boresight_dec=41.8
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
return self._value
|
|
48
|
+
|
|
49
|
+
def get_openapi_examples(self) -> Dict[str, Dict[str, Any]]:
|
|
50
|
+
"""Convert to OpenAPI examples format"""
|
|
51
|
+
return [self.value]
|