firepype 0.0.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.
firepype/cli.py ADDED
@@ -0,0 +1,296 @@
1
+ # firepype/cli.py
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from .config import PipelineConfig, RunSpec
8
+ from .pipeline import run_ab_pairs
9
+
10
+
11
+ def main():
12
+ """
13
+ Purpose:
14
+ Command-line entry point for the FIRE AB-pair NIR reduction pipeline.
15
+ Parses CLI arguments, constructs PipelineConfig (applying optional
16
+ overrides), and executes AB-pair processing to produce co-added
17
+ spectrum and QA outputs
18
+ Inputs:
19
+ None (arguments are read from sys.argv via argparse)
20
+ Returns:
21
+ None
22
+ Runs the pipeline and writes outputs to disk. Exits with argparse
23
+ error messages on invalid usage
24
+ CLI options:
25
+ --raw-dir Directory with raw FITS files (required)
26
+ --out-dir Output directory for products and QA (required)
27
+ --arc Path to ARC frame FITS (required)
28
+ --ref-list Path to reference line list file (required)
29
+ --spec Frame spec string for AB pairing (required)
30
+ --no-qa Disable QA plots (optional flag)
31
+ --ap-radius Extraction aperture half-width in pixels (optional)
32
+ --bg-in Inner background offset in pixels (optional)
33
+ --bg-out Outer background offset in pixels (optional)
34
+ --footprint-half Half-size (columns) for footprint median combine (optional)
35
+ --wl-min Wavelength range minimum in microns (optional)
36
+ --wl-max Wavelength range maximum in microns (optional)
37
+ --grid-step Wavelength grid step for coadd in microns (optional)
38
+ --verbose Enable verbose diagnostic prints (optional)
39
+ """
40
+
41
+ p = argparse.ArgumentParser(
42
+ prog="firepype",
43
+ description="FIRE AB-pair NIR reduction pipeline",
44
+ )
45
+
46
+ p.add_argument(
47
+ "--raw-dir",
48
+ required=True,
49
+ help="Directory containing raw FITS files (e.g., /data/raw)",
50
+ )
51
+
52
+ p.add_argument(
53
+ "--out-dir",
54
+ required=True,
55
+ help="Output directory for products and QA (e.g., ./out)",
56
+ )
57
+
58
+ p.add_argument(
59
+ "--arc",
60
+ required=True,
61
+ help="Path to the ARC frame FITS (e.g., /data/raw/fire_XXXX.fits)",
62
+ )
63
+
64
+ p.add_argument(
65
+ "--ref-list",
66
+ required=True,
67
+ help="Path to the line list file (e.g., /data/ref/line_list.lst)",
68
+ )
69
+
70
+ p.add_argument(
71
+ "--spec",
72
+ required=True,
73
+ help='Frame spec string for AB pairing, e.g. "XXXX-XXXX" or "XX-XX,XX-XX"',
74
+ )
75
+
76
+ p.add_argument(
77
+ "--no-qa",
78
+ action="store_true",
79
+ help="Disable QA plots",
80
+ )
81
+
82
+ # Optional default overrides
83
+ p.add_argument(
84
+ "--ap-radius", type=int, default=None, help="Extraction aperture half-width (px)"
85
+ )
86
+
87
+ p.add_argument(
88
+ "--bg-in", type=int, default=None, help="Inner background offset (px)"
89
+ )
90
+
91
+ p.add_argument(
92
+ "--bg-out", type=int, default=None, help="Outer background offset (px)"
93
+ )
94
+
95
+ p.add_argument(
96
+ "--footprint-half",
97
+ type=int,
98
+ default=None,
99
+ help="Half-size (in columns) for footprint median combine",
100
+ )
101
+
102
+ p.add_argument(
103
+ "--wl-min", type=float, default=None, help="Wavelength range min (micron)"
104
+ )
105
+ p.add_argument(
106
+ "--wl-max", type=float, default=None, help="Wavelength range max (micron)"
107
+ )
108
+ p.add_argument(
109
+ "--grid-step", type=float, default=None, help="Grid step for coadd (micron)"
110
+ )
111
+ p.add_argument(
112
+ "--verbose", action="store_true", help="Verbose detection/diagnostic prints"
113
+ )
114
+
115
+ args = p.parse_args()
116
+
117
+ # Build config with defaults, then override if provided
118
+ cfg = PipelineConfig(
119
+ run=RunSpec(
120
+ raw_dir=Path(args.raw_dir),
121
+ out_dir=Path(args.out_dir),
122
+ arc_path=Path(args.arc),
123
+ ref_list_path=Path(args.ref_list),
124
+ user_spec=args.spec,
125
+ )
126
+ )
127
+
128
+ cfg.qa.save_figs = not args.no_qa
129
+ cfg.qa.verbose = bool(args.verbose)
130
+ cfg.qa.out_dir = Path(args.out_dir)
131
+ cfg.qa.qa_dir = Path(args.out_dir) / "qa"
132
+
133
+ if args.ap_radius is not None:
134
+ cfg.extraction.ap_radius = int(args.ap_radius)
135
+
136
+ if args.bg_in is not None:
137
+ cfg.extraction.bg_in = int(args.bg_in)
138
+
139
+ if args.bg_out is not None:
140
+ cfg.extraction.bg_out = int(args.bg_out)
141
+
142
+ if args.footprint_half is not None:
143
+ cfg.extraction.footprint_half = int(args.footprint_half)
144
+
145
+ if args.wl_min is not None or args.wl_max is not None:
146
+ wl_lo = args.wl_min if args.wl_min is not None else cfg.wavecal.wl_range[0]
147
+ wl_hi = args.wl_max if args.wl_max is not None else cfg.wavecal.wl_range[1]
148
+ cfg.wavecal.wl_range = (float(wl_lo), float(wl_hi))
149
+
150
+ if args.grid_step is not None:
151
+ cfg.wavecal.grid_step = float(args.grid_step)
152
+
153
+ run_ab_pairs(cfg)
154
+
155
+ def main_telluric() -> None:
156
+ """
157
+ Telluric/response CLI.
158
+
159
+ Minimal workflow:
160
+ - Read a standard-star exposure (or coadd) FITS.
161
+ - Optionally read a reference spectrum (A0V) to derive response.
162
+ - Fit/derive telluric transmission and instrument response.
163
+ - Write out telluric.fits and response.fits (or as configured).
164
+ """
165
+ import argparse
166
+ import sys
167
+ from pathlib import Path
168
+
169
+ parser = argparse.ArgumentParser(
170
+ prog="firepype-telluric",
171
+ description="Telluric + instrument response from a standard star",
172
+ )
173
+
174
+ parser.add_argument(
175
+ "--standard",
176
+ required=True,
177
+ help="Path to standard star FITS (2D or extracted 1D).",
178
+ )
179
+
180
+ parser.add_argument(
181
+ "--out-dir",
182
+ required=True,
183
+ help="Output directory for telluric/response products.",
184
+ )
185
+
186
+ parser.add_argument(
187
+ "--ref-spectrum",
188
+ default=None,
189
+ help="Optional path to reference spectrum for the standard "
190
+ "(e.g., A0V template). If omitted, use built-in or empirical.",
191
+ )
192
+
193
+ parser.add_argument(
194
+ "--stype",
195
+ default="A0V",
196
+ help='Standard star spectral type (default: "A0V").',
197
+ )
198
+
199
+ parser.add_argument(
200
+ "--airmass",
201
+ type=float,
202
+ default=None,
203
+ help="Airmass to use for telluric modeling (override header if given).",
204
+ )
205
+
206
+ parser.add_argument(
207
+ "--wl-min",
208
+ type=float,
209
+ default=None,
210
+ help="Minimum wavelength (micron) to include.",
211
+ )
212
+
213
+ parser.add_argument(
214
+ "--wl-max",
215
+ type=float,
216
+ default=None,
217
+ help="Maximum wavelength (micron) to include.",
218
+ )
219
+
220
+ parser.add_argument(
221
+ "--plot",
222
+ action="store_true",
223
+ help="Save diagnostic plots to out-dir.",
224
+ )
225
+
226
+ parser.add_argument(
227
+ "--overwrite",
228
+ action="store_true",
229
+ help="Overwrite existing outputs.",
230
+ )
231
+
232
+ args = parser.parse_args()
233
+
234
+ std_path = Path(args.standard).expanduser().resolve()
235
+ out_dir = Path(args.out_dir).expanduser().resolve()
236
+ ref_path = (
237
+ None
238
+ if args.ref_spectrum is None
239
+ else Path(args.ref_spectrum).expanduser().resolve()
240
+ )
241
+
242
+ # Basic validations
243
+ if not std_path.exists():
244
+ print(f"error: standard file not found: {std_path}", file=sys.stderr)
245
+ sys.exit(2)
246
+
247
+ if ref_path is not None and not ref_path.exists():
248
+ print(f"error: reference spectrum not found: {ref_path}", file=sys.stderr)
249
+ sys.exit(2)
250
+
251
+ out_dir.mkdir(parents=True, exist_ok=True)
252
+
253
+ # Default output filenames
254
+ telluric_out = out_dir / "telluric.fits"
255
+ response_out = out_dir / "response.fits"
256
+ qa_dir = out_dir / "qa"
257
+
258
+ if not args.overwrite:
259
+ for p in (telluric_out, response_out):
260
+ if p.exists():
261
+ print(f"error: output exists (use --overwrite): {p}", file=sys.stderr)
262
+ sys.exit(2)
263
+
264
+ # Import and run the actual logic
265
+ try:
266
+ from .telluric import run_telluric
267
+ except Exception as e:
268
+ print(f"error: could not import telluric runner: {e}", file=sys.stderr)
269
+ sys.exit(1)
270
+
271
+ try:
272
+ run_telluric(
273
+ standard_fits=std_path,
274
+ stype=args.stype,
275
+ out_telluric=telluric_out,
276
+ out_response=response_out,
277
+ ref_spectrum=ref_path,
278
+ airmass=args.airmass,
279
+ wl_min=args.wl_min,
280
+ wl_max=args.wl_max,
281
+ save_plots=args.plot,
282
+ qa_dir=qa_dir,
283
+ overwrite=args.overwrite,
284
+ )
285
+
286
+ except Exception as e:
287
+ print(f"error: telluric processing failed: {e}", file=sys.stderr)
288
+ sys.exit(1)
289
+
290
+ print(f"wrote: {telluric_out}")
291
+ print(f"wrote: {response_out}")
292
+ if args.plot:
293
+ print(f"qa: {qa_dir}")
294
+
295
+ if __name__ == "__main__":
296
+ main()
firepype/coadd.py ADDED
@@ -0,0 +1,105 @@
1
+ # firepype/coadd.py
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+
6
+
7
+ class CoaddAccumulator:
8
+ """
9
+ Purpose:
10
+ Maintain inverse-variance coaddition accumulator on a fixed wavelength
11
+ grid. Add multiple spectra (already interpolated to this grid) along
12
+ with their per-bin uncertainties and an optional validity mask; to
13
+ accumulate weighted sums and producecco-added spectrum and errors
14
+ Inputs:
15
+ grid_wl (at init): 1D array of target wavelength grid in microns (or
16
+ arbitrary units) onto which all spectra are interpolated
17
+ Returns:
18
+ CoaddAccumulator instance
19
+ """
20
+
21
+ def __init__(self, grid_wl: np.ndarray):
22
+ """
23
+ Purpose:
24
+ Initialise coaddition accumulator for a specified wavelength grid
25
+ Inputs:
26
+ grid_wl: 1D array of wavelengths defining the coadd grid
27
+ Returns:
28
+ None
29
+ """
30
+
31
+ self.grid = np.asarray(grid_wl, float)
32
+ self.flux_sum = np.zeros_like(self.grid, dtype=float)
33
+ self.weight_sum = np.zeros_like(self.grid, dtype=float)
34
+
35
+ def add_spectrum(
36
+ self,
37
+ flux: np.ndarray,
38
+ err: np.ndarray,
39
+ mask: np.ndarray | None = None,
40
+ ) -> None:
41
+ """
42
+ Purpose:
43
+ Add single spectrum to accumulator. Spectrum should already be
44
+ interpolated onto the accumulator's grid (self.grid). Accumulation is
45
+ performed using inverse-variance weights
46
+ Inputs:
47
+ flux: 1D array of flux values on self.grid (same shape as grid)
48
+ err: 1D array of 1-sigma uncertainties on self.grid (same shape)
49
+ mask: Optional boolean mask (same shape) where True indicates a bin
50
+ should be used. If provided, it is combined with finite/positive
51
+ error masking
52
+ Returns:
53
+ None
54
+ Raises:
55
+ ValueError: If flux/err (or mask, when provided) shapes do not match
56
+ the grid shape
57
+ """
58
+
59
+ f = np.asarray(flux, float)
60
+ e = np.asarray(err, float)
61
+
62
+ if f.shape != self.grid.shape or e.shape != self.grid.shape:
63
+ raise ValueError("flux/err must match grid shape")
64
+
65
+ m = np.isfinite(f) & np.isfinite(e) & (e > 0)
66
+
67
+ if mask is not None:
68
+ mask = np.asarray(mask, bool)
69
+
70
+ if mask.shape != self.grid.shape:
71
+ raise ValueError("mask must match grid shape")
72
+
73
+ m &= mask
74
+
75
+ if not np.any(m):
76
+ return
77
+
78
+ w = 1.0 / np.maximum(e[m] ** 2, 1e-20)
79
+ self.flux_sum[m] += w * f[m]
80
+ self.weight_sum[m] += w
81
+
82
+
83
+ def finalize(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
84
+ """
85
+ Purpose:
86
+ Compute final coadded flux and error arrays from accumulated
87
+ weighted sums
88
+ Inputs:
89
+ None
90
+ Returns:
91
+ tuple:
92
+ - flux (np.ndarray): Weighted-mean flux on self.grid; NaN where no
93
+ samples contributed (weight == 0)
94
+ - err (np.ndarray): 1-sigma uncertainty as sqrt(1/weight); NaN where
95
+ weight == 0
96
+ - used (np.ndarray): Boolean mask where weight > 0 (bins used)
97
+ """
98
+
99
+ used = self.weight_sum > 0
100
+ flux = np.full_like(self.grid, np.nan, dtype=float)
101
+ err = np.full_like(self.grid, np.nan, dtype=float)
102
+ flux[used] = self.flux_sum[used] / self.weight_sum[used]
103
+ err[used] = np.sqrt(1.0 / self.weight_sum[used])
104
+
105
+ return flux, err, used
firepype/config.py ADDED
@@ -0,0 +1,55 @@
1
+ # firepype/config.py
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+ from typing import Sequence, Tuple, Optional, List
5
+
6
+ @dataclass
7
+ class QASettings:
8
+ save_figs: bool = True
9
+ verbose: bool = True
10
+ out_dir: Path = Path("./output")
11
+ qa_dir: Path = field(default_factory=lambda: Path("./output/qa"))
12
+
13
+ @dataclass
14
+ class WavecalSettings:
15
+ wl_range: Tuple[float, float] = (0.83, 2.45)
16
+ grid_step: float = 0.0008
17
+ band_anchors_global: Sequence[Tuple[int, float]] = (
18
+ (100, 0.83),
19
+ (1000, 1.15),
20
+ (1600, 1.79),
21
+ (1950, 2.43),
22
+ )
23
+ max_sep: float = 0.012
24
+ deg: int = 3
25
+ force_global_orient: str = "fwd"
26
+
27
+ @dataclass
28
+ class ExtractionSettings:
29
+ ap_radius: int = 5
30
+ bg_in: int = 8
31
+ bg_out: int = 18
32
+ footprint_half: int = 1
33
+ row_fraction: Tuple[float, float] = (0.35, 0.85)
34
+
35
+ @dataclass
36
+ class SlitDetectionSettings:
37
+ slit_x_hint: Tuple[int, int] = (900, 1300)
38
+ slit_hint_expand: int = 150
39
+ row_fraction: Tuple[float, float] = (0.35, 0.85)
40
+
41
+ @dataclass
42
+ class RunSpec:
43
+ raw_dir: Path
44
+ out_dir: Path
45
+ arc_path: Path
46
+ ref_list_path: Path
47
+ user_spec: str # e.g., "XXXX-XXXX"
48
+
49
+ @dataclass
50
+ class PipelineConfig:
51
+ run: RunSpec
52
+ qa: QASettings = field(default_factory=QASettings)
53
+ wavecal: WavecalSettings = field(default_factory=WavecalSettings)
54
+ extraction: ExtractionSettings = field(default_factory=ExtractionSettings)
55
+ slit: SlitDetectionSettings = field(default_factory=SlitDetectionSettings)