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/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()