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/__init__.py +27 -0
- firepype/calibration.py +520 -0
- firepype/cli.py +296 -0
- firepype/coadd.py +105 -0
- firepype/config.py +55 -0
- firepype/detection.py +517 -0
- firepype/extraction.py +198 -0
- firepype/io.py +248 -0
- firepype/pipeline.py +339 -0
- firepype/plotting.py +234 -0
- firepype/telluric.py +1401 -0
- firepype/utils.py +344 -0
- firepype-0.0.1.dist-info/METADATA +153 -0
- firepype-0.0.1.dist-info/RECORD +18 -0
- firepype-0.0.1.dist-info/WHEEL +5 -0
- firepype-0.0.1.dist-info/entry_points.txt +3 -0
- firepype-0.0.1.dist-info/licenses/LICENSE +21 -0
- firepype-0.0.1.dist-info/top_level.txt +1 -0
firepype/plotting.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# firepype/plotting.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Sequence, Tuple
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
from scipy.ndimage import gaussian_filter1d
|
|
10
|
+
|
|
11
|
+
from .calibration import (
|
|
12
|
+
find_arc_peaks_1d,
|
|
13
|
+
solve_dispersion_from_arc1d,
|
|
14
|
+
)
|
|
15
|
+
from .utils import ensure_dir
|
|
16
|
+
|
|
17
|
+
def plot_arc_trace_on_raw(
|
|
18
|
+
arc_img: np.ndarray,
|
|
19
|
+
center_col: int,
|
|
20
|
+
*,
|
|
21
|
+
ap: int = 5,
|
|
22
|
+
bg_in: int = 8,
|
|
23
|
+
bg_out: int = 18,
|
|
24
|
+
half: int = 1,
|
|
25
|
+
row_frac: Tuple[float, float] = (0.35, 0.85),
|
|
26
|
+
title: str | None = None,
|
|
27
|
+
save_path: str | Path | None = None,
|
|
28
|
+
show: bool = False,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Purpose:
|
|
32
|
+
Overlay the extraction geometry on raw arc image:
|
|
33
|
+
- Display selected central column and its neighbours
|
|
34
|
+
- Shade extraction aperture and background side-bands
|
|
35
|
+
- Mark row band for median spatial profiles
|
|
36
|
+
Inputs:
|
|
37
|
+
arc_img: 2D array (rows x cols) of arc image
|
|
38
|
+
center_col: Central column index to draw aperture/background
|
|
39
|
+
ap: Half-width of extraction aperture in columns (default 5)
|
|
40
|
+
bg_in: Inner offset (columns) from center to background band (default 8)
|
|
41
|
+
bg_out: Outer offset (columns) from center to background band (default 18)
|
|
42
|
+
half: Include columns in [center_col - half, center_col + half] (default 1)
|
|
43
|
+
row_frac: Fractional row range for band visualisation (default (0.35, 0.85))
|
|
44
|
+
title: Optional plot title (default None -> auto-generated)
|
|
45
|
+
save_path: Optional path to save plot (default None: no save)
|
|
46
|
+
show: Display figure (default False)
|
|
47
|
+
Returns:
|
|
48
|
+
None
|
|
49
|
+
Saves and/or shows figure of extraction geometry
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
img = np.asarray(arc_img, float)
|
|
53
|
+
nrows, ncols = img.shape
|
|
54
|
+
r0 = int(min(row_frac) * nrows)
|
|
55
|
+
r1 = int(max(row_frac) * nrows)
|
|
56
|
+
|
|
57
|
+
cols = [center_col + dc for dc in range(-half, half + 1)]
|
|
58
|
+
cols = [c for c in cols if 0 <= c < ncols] or [min(max(center_col, 0), ncols - 1)]
|
|
59
|
+
|
|
60
|
+
fig, ax = plt.subplots(figsize=(7, 6))
|
|
61
|
+
vmin, vmax = np.nanpercentile(img, [5, 99])
|
|
62
|
+
ax.imshow(img, origin="lower", cmap="gray", vmin=vmin, vmax=vmax, aspect="auto")
|
|
63
|
+
|
|
64
|
+
# Row band
|
|
65
|
+
ax.hlines([r0, r1 - 1], 0, ncols - 1, colors="cyan", linestyles="--", lw=1.1,
|
|
66
|
+
alpha=0.7)
|
|
67
|
+
|
|
68
|
+
# Aperture blocks
|
|
69
|
+
for c in cols:
|
|
70
|
+
lo = max(0, c - ap)
|
|
71
|
+
hi = min(ncols - 1, c + ap)
|
|
72
|
+
ax.axvspan(lo, hi, color="lime", alpha=0.18)
|
|
73
|
+
for c in cols:
|
|
74
|
+
ax.axvline(c, color="lime", lw=0.9, alpha=0.75)
|
|
75
|
+
|
|
76
|
+
# Background bands around the central column
|
|
77
|
+
c0 = min(max(center_col, 0), ncols - 1)
|
|
78
|
+
ax.axvspan(max(0, c0 - bg_out), max(0, c0 - bg_in), color="orange", alpha=0.18)
|
|
79
|
+
ax.axvspan(min(ncols - 1, c0 + bg_in), min(ncols - 1, c0 + bg_out),
|
|
80
|
+
color="orange", alpha=0.18)
|
|
81
|
+
|
|
82
|
+
ax.set_xlim(0, ncols - 1)
|
|
83
|
+
ax.set_ylim(0, nrows - 1)
|
|
84
|
+
ax.set_xlabel("Column (dispersion)")
|
|
85
|
+
ax.set_ylabel("Row (spatial)")
|
|
86
|
+
ax.set_title(title or f"ARC trace overlay (center_col={center_col}, half={half}, ap={ap})")
|
|
87
|
+
fig.tight_layout()
|
|
88
|
+
|
|
89
|
+
if save_path:
|
|
90
|
+
ensure_dir(Path(save_path).parent.as_posix())
|
|
91
|
+
fig.savefig(save_path, dpi=140)
|
|
92
|
+
if show:
|
|
93
|
+
plt.show()
|
|
94
|
+
plt.close(fig)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def plot_arc_1d_with_line_labels(
|
|
98
|
+
arc1d: np.ndarray,
|
|
99
|
+
wl_range: Tuple[float, float],
|
|
100
|
+
ref_lines_um: np.ndarray,
|
|
101
|
+
*,
|
|
102
|
+
anchors: Sequence[tuple[int, float]] | None = None,
|
|
103
|
+
solver_deg: int = 3,
|
|
104
|
+
solver_max_sep: float = 0.012,
|
|
105
|
+
arc_col: int | None = None,
|
|
106
|
+
title_tag: str = "",
|
|
107
|
+
save_path: str | Path | None = None,
|
|
108
|
+
show: bool = False,
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Purpose:
|
|
112
|
+
Plot high-pass filtered 1D arc profile with labeled wavelengths of
|
|
113
|
+
detected arc features. Dispersion solution is solved using the provided
|
|
114
|
+
line list and optional anchors, then used to annotate peaks
|
|
115
|
+
Inputs:
|
|
116
|
+
arc1d: 1D arc signal (array-like)
|
|
117
|
+
wl_range: Target wavelength span for solution in microns (wl_lo, wl_hi)
|
|
118
|
+
ref_lines_um: Reference line wavelengths in microns
|
|
119
|
+
anchors: Optional list of (pixel_index, wavelength_um) anchor pairs to guide
|
|
120
|
+
dispersion solution (default None)
|
|
121
|
+
solver_deg: Polynomial degree for dispersion fit (default 3)
|
|
122
|
+
solver_max_sep: Maximum separation (microns) for matching lines (default 0.012)
|
|
123
|
+
arc_col: Optional column index associated with 1D extraction, for title
|
|
124
|
+
title_tag: Extra text appended to the title (default "")
|
|
125
|
+
save_path: Optional path to save the plot (default None: no save)
|
|
126
|
+
show: Display figure (default False)
|
|
127
|
+
Returns:
|
|
128
|
+
None
|
|
129
|
+
Saves and/or shows figure with 1D arc profile and labeled peaks
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
y = np.asarray(arc1d, float)
|
|
133
|
+
n = y.size
|
|
134
|
+
|
|
135
|
+
base = gaussian_filter1d(y, sigma=15, mode="nearest")
|
|
136
|
+
sm = gaussian_filter1d(y - base, sigma=0.8, mode="nearest")
|
|
137
|
+
xpix = np.arange(n)
|
|
138
|
+
|
|
139
|
+
# Peaks for visualisation
|
|
140
|
+
pk, _ = find_arc_peaks_1d(y, sigma_lo=15, sigma_hi=0.8)
|
|
141
|
+
|
|
142
|
+
# Solve dispersion for labelling
|
|
143
|
+
wl_sol = solve_dispersion_from_arc1d(
|
|
144
|
+
y,
|
|
145
|
+
wl_range=wl_range,
|
|
146
|
+
ref_lines_um=np.asarray(ref_lines_um, float),
|
|
147
|
+
deg=solver_deg,
|
|
148
|
+
anchors=anchors,
|
|
149
|
+
max_sep=solver_max_sep,
|
|
150
|
+
verbose=False,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Map matched peaks into wavelengths for labels
|
|
154
|
+
yvals = np.interp(pk, xpix, sm) if pk.size > 0 else np.array([], float)
|
|
155
|
+
wl_m = wl_sol[pk] if pk.size > 0 else np.array([], float)
|
|
156
|
+
|
|
157
|
+
fig, ax = plt.subplots(figsize=(7, 4))
|
|
158
|
+
ax.plot(xpix, sm, color="0.2", lw=0.9, label="Arc 1D (smoothed)")
|
|
159
|
+
|
|
160
|
+
if pk.size > 0:
|
|
161
|
+
ax.vlines(pk, ymin=np.nanmin(sm), ymax=np.nanmax(sm) * 0.85, colors="C3",
|
|
162
|
+
linestyles=":", alpha=0.6)
|
|
163
|
+
ax.scatter(pk, yvals, s=20, color="C1", zorder=3, label="Peaks")
|
|
164
|
+
dy = 0.05 * (np.nanmax(sm) - np.nanmin(sm))
|
|
165
|
+
for p, w, yy in zip(pk, wl_m, yvals):
|
|
166
|
+
ax.text(p, yy + dy, f"{w:.4f}", color="C1", fontsize=8, rotation=90,
|
|
167
|
+
ha="center", va="bottom")
|
|
168
|
+
|
|
169
|
+
ttl = f"Extracted ARC 1D (col={arc_col if arc_col is not None else 'n/a'})"
|
|
170
|
+
if title_tag:
|
|
171
|
+
ttl += f" — {title_tag}"
|
|
172
|
+
ax.set_title(ttl)
|
|
173
|
+
ax.set_xlabel("Row (pixel)")
|
|
174
|
+
ax.set_ylabel("Arc counts (arb.)")
|
|
175
|
+
ax.grid(alpha=0.25)
|
|
176
|
+
ax.legend(loc="upper right")
|
|
177
|
+
fig.tight_layout()
|
|
178
|
+
if save_path:
|
|
179
|
+
ensure_dir(Path(save_path).parent.as_posix())
|
|
180
|
+
fig.savefig(save_path, dpi=140)
|
|
181
|
+
if show:
|
|
182
|
+
plt.show()
|
|
183
|
+
plt.close(fig)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def plot_1d_spectrum(
|
|
187
|
+
wl_um: np.ndarray,
|
|
188
|
+
flux: np.ndarray,
|
|
189
|
+
title: str,
|
|
190
|
+
save_path: str | Path | None,
|
|
191
|
+
*,
|
|
192
|
+
xlabel: str = "Wavelength (um)",
|
|
193
|
+
ylabel: str = "Flux (arb)",
|
|
194
|
+
show: bool = False,
|
|
195
|
+
):
|
|
196
|
+
"""
|
|
197
|
+
Purpose:
|
|
198
|
+
Plot simple 1D spectrum, with optional save/show
|
|
199
|
+
Inputs:
|
|
200
|
+
wl_um: 1D wavelengths in microns
|
|
201
|
+
flux: 1D flux values aligned with wl_um
|
|
202
|
+
title: Plot title string
|
|
203
|
+
save_path: Optional path to save plot (default None: no save)
|
|
204
|
+
xlabel: X-axis label (default "Wavelength (um)")
|
|
205
|
+
ylabel: Y-axis label (default "Flux (arb)")
|
|
206
|
+
show: Display figure (default False)
|
|
207
|
+
Returns:
|
|
208
|
+
None
|
|
209
|
+
Saves and/or shows the spectrum plot
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
wl = np.asarray(wl_um, float)
|
|
213
|
+
fx = np.asarray(flux, float)
|
|
214
|
+
m = np.isfinite(wl) & np.isfinite(fx)
|
|
215
|
+
if np.count_nonzero(m) < 5:
|
|
216
|
+
return
|
|
217
|
+
wl = wl[m]
|
|
218
|
+
fx = fx[m]
|
|
219
|
+
idx = np.argsort(wl)
|
|
220
|
+
wl = wl[idx]
|
|
221
|
+
fx = fx[idx]
|
|
222
|
+
plt.figure(figsize=(7, 5))
|
|
223
|
+
plt.plot(wl, fx, lw=1.0, color="C3")
|
|
224
|
+
plt.xlabel(xlabel)
|
|
225
|
+
plt.ylabel(ylabel)
|
|
226
|
+
plt.title(title)
|
|
227
|
+
plt.grid(alpha=0.3)
|
|
228
|
+
plt.tight_layout()
|
|
229
|
+
if save_path:
|
|
230
|
+
ensure_dir(Path(save_path).parent.as_posix())
|
|
231
|
+
plt.savefig(save_path, dpi=140)
|
|
232
|
+
if show:
|
|
233
|
+
plt.show()
|
|
234
|
+
plt.close()
|