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.
Files changed (150) hide show
  1. senpai/__init__.py +36 -0
  2. senpai/api/main.py +165 -0
  3. senpai/api/models/examples.py +51 -0
  4. senpai/api/models/returns.py +592 -0
  5. senpai/api/routes/astrometry.py +136 -0
  6. senpai/api/routes/senpai.py +159 -0
  7. senpai/api/tests/__init__.py +0 -0
  8. senpai/api/tests/conftest.py +73 -0
  9. senpai/api/tests/test_api.py +293 -0
  10. senpai/astrometry.py +256 -0
  11. senpai/catalog/README.md +142 -0
  12. senpai/catalog/__init__.py +0 -0
  13. senpai/catalog/constants.py +76 -0
  14. senpai/catalog/gaia.py +208 -0
  15. senpai/catalog/gaia_local.py +101 -0
  16. senpai/catalog/gaia_mirror.py +232 -0
  17. senpai/catalog/gaia_transforms.py +64 -0
  18. senpai/catalog/runner.py +1142 -0
  19. senpai/catalog/sdss.py +390 -0
  20. senpai/catalog/sstrc7_source.py +187 -0
  21. senpai/cli/batch.py +438 -0
  22. senpai/cli/burr.py +1203 -0
  23. senpai/cli/calibrate.py +77 -0
  24. senpai/cli/catalog_magnitude_analysis.py +337 -0
  25. senpai/cli/common.py +122 -0
  26. senpai/cli/coverage.py +3107 -0
  27. senpai/cli/detect.py +154 -0
  28. senpai/cli/measure_gain.py +165 -0
  29. senpai/cli/photometry.py +114 -0
  30. senpai/cli/plotting.py +81 -0
  31. senpai/cli/rate.py +117 -0
  32. senpai/cli/rate_sidereal.py +155 -0
  33. senpai/cli/sidereal.py +122 -0
  34. senpai/core/__init__.py +0 -0
  35. senpai/core/config.py +702 -0
  36. senpai/core/constants.py +54 -0
  37. senpai/core/logging.py +160 -0
  38. senpai/engine/__init__.py +24 -0
  39. senpai/engine/constants.py +3 -0
  40. senpai/engine/detection/jacobian.py +313 -0
  41. senpai/engine/detection/kernels.py +245 -0
  42. senpai/engine/detection/point/fwhm.py +339 -0
  43. senpai/engine/detection/point/satellite.py +980 -0
  44. senpai/engine/detection/point/sidereal.py +670 -0
  45. senpai/engine/detection/streak/extraction.py +2628 -0
  46. senpai/engine/detection/streak/frame_shift.py +165 -0
  47. senpai/engine/detection/streak/masking.py +776 -0
  48. senpai/engine/detection/streak/rate_extraction.py +222 -0
  49. senpai/engine/detection/streak/rate_rate.py +872 -0
  50. senpai/engine/detection/streak/rate_sidereal.py +525 -0
  51. senpai/engine/detection/streak/sidereal_sidereal.py +56 -0
  52. senpai/engine/detection/streak/sidereal_streak.py +1597 -0
  53. senpai/engine/detection/streak/validation.py +988 -0
  54. senpai/engine/detection/track_mode.py +315 -0
  55. senpai/engine/models/astrometry.py +517 -0
  56. senpai/engine/models/images.py +224 -0
  57. senpai/engine/models/metadata.py +234 -0
  58. senpai/engine/models/senpai.py +938 -0
  59. senpai/engine/models/starfield.py +246 -0
  60. senpai/engine/models/streak_measurement.py +307 -0
  61. senpai/engine/observability/__init__.py +27 -0
  62. senpai/engine/observability/calibration.py +3298 -0
  63. senpai/engine/observability/detector_gain.py +239 -0
  64. senpai/engine/photometry/color_terms.py +310 -0
  65. senpai/engine/photometry/photometry.md +210 -0
  66. senpai/engine/photometry/utils.py +2831 -0
  67. senpai/engine/plotting/axes.py +18 -0
  68. senpai/engine/plotting/images.py +1449 -0
  69. senpai/engine/plotting/normalization.py +66 -0
  70. senpai/engine/plotting/photometry.py +868 -0
  71. senpai/engine/plotting/psf.py +574 -0
  72. senpai/engine/plotting/replot.py +384 -0
  73. senpai/engine/plotting/wcs_diagnostics.py +143 -0
  74. senpai/engine/processing/__init__.py +0 -0
  75. senpai/engine/processing/collect.py +937 -0
  76. senpai/engine/processing/photometry_pipeline.py +254 -0
  77. senpai/engine/processing/rate.py +323 -0
  78. senpai/engine/processing/rate_scan_confirmation.py +851 -0
  79. senpai/engine/processing/sidereal.py +188 -0
  80. senpai/engine/processing/stamp_confirmation.py +1408 -0
  81. senpai/engine/processing/streak_correlation.py +743 -0
  82. senpai/engine/tests/__init__.py +0 -0
  83. senpai/engine/tests/test_aperture_sums.py +93 -0
  84. senpai/engine/tests/test_burr_adapter.py +610 -0
  85. senpai/engine/tests/test_burr_replot.py +158 -0
  86. senpai/engine/tests/test_calibration.py +452 -0
  87. senpai/engine/tests/test_calibration_frames.py +326 -0
  88. senpai/engine/tests/test_catalog_logic.py +391 -0
  89. senpai/engine/tests/test_color_terms.py +261 -0
  90. senpai/engine/tests/test_config.py +210 -0
  91. senpai/engine/tests/test_detection_only_mode.py +499 -0
  92. senpai/engine/tests/test_detector_gain.py +97 -0
  93. senpai/engine/tests/test_export_coco.py +473 -0
  94. senpai/engine/tests/test_fits_io.py +367 -0
  95. senpai/engine/tests/test_frame_organization.py +188 -0
  96. senpai/engine/tests/test_gaia_local.py +73 -0
  97. senpai/engine/tests/test_gaia_sky_cache.py +88 -0
  98. senpai/engine/tests/test_half_flux_radius.py +71 -0
  99. senpai/engine/tests/test_masking.py +69 -0
  100. senpai/engine/tests/test_models.py +361 -0
  101. senpai/engine/tests/test_photometry_synthetic.py +514 -0
  102. senpai/engine/tests/test_photometry_zp.py +92 -0
  103. senpai/engine/tests/test_point_detection.py +610 -0
  104. senpai/engine/tests/test_preprocessing.py +411 -0
  105. senpai/engine/tests/test_sstrc7_source.py +113 -0
  106. senpai/engine/tests/test_stats.py +57 -0
  107. senpai/engine/tests/test_streak_extraction.py +184 -0
  108. senpai/engine/tests/test_streak_geometry.py +320 -0
  109. senpai/engine/tests/test_streak_validation.py +256 -0
  110. senpai/engine/tests/test_track_rate_units.py +48 -0
  111. senpai/engine/tests/test_wcs_helpers.py +79 -0
  112. senpai/engine/utils/astrometry_diagnostics.py +98 -0
  113. senpai/engine/utils/darks.py +945 -0
  114. senpai/engine/utils/file_io.py +156 -0
  115. senpai/engine/utils/fits_io.py +515 -0
  116. senpai/engine/utils/flats.py +746 -0
  117. senpai/engine/utils/frame_organization.py +174 -0
  118. senpai/engine/utils/memory.py +25 -0
  119. senpai/engine/utils/preprocessing.py +1548 -0
  120. senpai/engine/utils/propagate_wcs.py +21 -0
  121. senpai/engine/utils/serialization.py +52 -0
  122. senpai/engine/utils/simulation.py +206 -0
  123. senpai/engine/utils/stats.py +43 -0
  124. senpai/engine/utils/streak_chain.py +161 -0
  125. senpai/engine/utils/wcs_helpers.py +736 -0
  126. senpai/engine/utils/wcs_ops.py +329 -0
  127. senpai/engine/utils/wcs_refinement.py +1091 -0
  128. senpai/engine/utils/wcs_validation.py +191 -0
  129. senpai/export/README.md +176 -0
  130. senpai/export/__init__.py +11 -0
  131. senpai/export/cli.py +447 -0
  132. senpai/export/coco.py +1162 -0
  133. senpai/export/dataset_split.py +424 -0
  134. senpai/export/example_folder.py +132 -0
  135. senpai/integrations/__init__.py +0 -0
  136. senpai/integrations/burr/__init__.py +26 -0
  137. senpai/integrations/burr/filenames.py +96 -0
  138. senpai/integrations/burr/night.py +679 -0
  139. senpai/integrations/burr/replot.py +12 -0
  140. senpai/integrations/burr/run_state.py +134 -0
  141. senpai/resources/config/burr.yaml +115 -0
  142. senpai/resources/config/containerized.yaml +28 -0
  143. senpai/resources/config/dao.yaml +156 -0
  144. senpai/resources/config/local-localastrometry.yaml +27 -0
  145. senpai/resources/config/local.yaml +109 -0
  146. senpai/resources/config/local2.yaml +105 -0
  147. senpai-2.9.1.dist-info/METADATA +211 -0
  148. senpai-2.9.1.dist-info/RECORD +150 -0
  149. senpai-2.9.1.dist-info/WHEEL +4 -0
  150. 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]