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/utils.py ADDED
@@ -0,0 +1,344 @@
1
+ # firepype/utils.py
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Iterable, Tuple
6
+
7
+ import numpy as np
8
+ from scipy.ndimage import gaussian_filter1d
9
+
10
+
11
+ def ensure_dir(p: str | Path):
12
+ """
13
+ Purpose:
14
+ Create a directory (and all missing parents) if it does not already exist
15
+ Inputs:
16
+ p: Path-like string or Path to the directory to create
17
+ Returns:
18
+ None
19
+ """
20
+
21
+ Path(p).mkdir(parents=True, exist_ok=True)
22
+
23
+
24
+ def sg_window_len(n_points: int, preferred: int, min_odd: int = 5) -> int:
25
+ """
26
+ Purpose:
27
+ Compute odd window length for filtering/smoothing that:
28
+ - Does not exceed n_points
29
+ - Is at least min_odd
30
+ - Is as close as possible to preferred
31
+ - Is odd (decremented by 1 if even)
32
+ Inputs:
33
+ n_points: Total number of points available (upper bound for window)
34
+ preferred: Preferred window length
35
+ min_odd: Minimum allowed odd window length (default 5)
36
+ Returns:
37
+ int:
38
+ An odd window length satisfying the constraints (>=3 if possible)
39
+ """
40
+
41
+ if n_points <= 0:
42
+ return 0
43
+
44
+ w = min(preferred, n_points)
45
+
46
+ if w % 2 == 0:
47
+ w -= 1
48
+
49
+ w = max(w, min_odd)
50
+
51
+ if w > n_points:
52
+ w = n_points if (n_points % 2 == 1) else (n_points - 1)
53
+
54
+ return max(w, 3)
55
+
56
+
57
+ def cheb_design_matrix(x: np.ndarray, deg: int) -> np.ndarray:
58
+ """
59
+ Purpose:
60
+ Build Chebyshev T_k(x) design matrix up to degree for inputs x in [-1, 1]
61
+ Inputs:
62
+ x: 1D array of input values (ideally scaled to [-1, 1])
63
+ deg: Non-negative integer degree of polynomial basis
64
+ Returns:
65
+ np.ndarray:
66
+ Matrix of shape (len(x), deg+1) with columns [T0(x), T1(x), ..., T_deg(x)]
67
+ """
68
+
69
+ x = np.asarray(x, float)
70
+ X = np.ones((x.size, deg + 1), float)
71
+
72
+ if deg >= 1:
73
+ X[:, 1] = x
74
+
75
+ for k in range(2, deg + 1):
76
+ X[:, k] = 2.0 * x * X[:, k - 1] - X[:, k - 2]
77
+
78
+ return X
79
+
80
+
81
+ def robust_weights(residuals: np.ndarray, c: float = 4.685) -> np.ndarray:
82
+ """
83
+ Purpose:
84
+ Compute Tukey's biweight (squared) robust regression weights from residuals.
85
+ Points with |u| >= 1 (u = r/(c*s)) receive zero weight
86
+ Inputs:
87
+ residuals: 1D array of residual values
88
+ c: Tuning constant controlling downweighting (default 4.685)
89
+ Returns:
90
+ np.ndarray:
91
+ Weights in [0, 1], same shape as residuals
92
+ """
93
+
94
+ r = np.asarray(residuals, float)
95
+ s = np.nanmedian(np.abs(r - np.nanmedian(r))) * 1.4826 + 1e-12
96
+ u = r / (c * s)
97
+ w = (1 - u**2)
98
+ w[(np.abs(u) >= 1) | ~np.isfinite(w)] = 0.0
99
+
100
+ return w**2
101
+
102
+
103
+ def orient_to_increasing(wl: np.ndarray, fx: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
104
+ """
105
+ Purpose:
106
+ Ensure wavelength array increases with index. If decreasing, reverse both
107
+ wavelength and flux arrays to maintain alignment
108
+ Inputs:
109
+ wl: 1D array of wavelengths
110
+ fx: 1D array of flux values aligned with wl
111
+ Returns:
112
+ tuple:
113
+ - wl_out (np.ndarray): Wavelengths in increasing order
114
+ - fx_out (np.ndarray): Flux values reoriented to match wl_out
115
+ """
116
+
117
+ wl = np.asarray(wl, float)
118
+ fx = np.asarray(fx, float)
119
+
120
+ if wl.size >= 2 and wl[0] > wl[-1]:
121
+ return wl[::-1], fx[::-1]
122
+
123
+ return wl, fx
124
+
125
+
126
+ def assert_monotonic_and_align(
127
+ wl: np.ndarray,
128
+ fx: np.ndarray,
129
+ name: str = "",
130
+ ) -> tuple[np.ndarray, np.ndarray]:
131
+ """
132
+ Purpose:
133
+ Clean and align wavelength and flux arrays by:
134
+ - Dropping non-finite entries
135
+ - Sorting by wavelength
136
+ - Enforcing strictly increasing wavelengths (removing non-increasing steps)
137
+ Raises if monotonicity cannot be achieved
138
+ Inputs:
139
+ wl: 1D array of wavelengths
140
+ fx: 1D array of flux values aligned with wl
141
+ name: Optional label used in error messages
142
+ Returns:
143
+ tuple:
144
+ - wl_out (np.ndarray): Strictly increasing wavelengths
145
+ - fx_out (np.ndarray): Flux aligned to wl_out
146
+ Raises:
147
+ RuntimeError: If wavelengths are not strictly increasing after alignment
148
+ """
149
+
150
+ wl = np.asarray(wl, float)
151
+ fx = np.asarray(fx, float)
152
+ m = np.isfinite(wl) & np.isfinite(fx)
153
+ wl = wl[m]
154
+ fx = fx[m]
155
+
156
+ if wl.size == 0:
157
+ return wl, fx
158
+
159
+ idx = np.argsort(wl)
160
+ wl = wl[idx]
161
+ fx = fx[idx]
162
+
163
+ if wl.size >= 2:
164
+ good = np.concatenate(([True], np.diff(wl) > 0))
165
+ wl = wl[good]
166
+ fx = fx[good]
167
+
168
+ if wl.size >= 2 and not np.all(np.diff(wl) > 0):
169
+ raise RuntimeError(f"{name}: wl not strictly increasing after alignment")
170
+
171
+ return wl, fx
172
+
173
+
174
+ # -------------------- Interpolation and mask helpers --------------------
175
+ def mask_interp_edge_artifacts(
176
+ grid_wl: np.ndarray,
177
+ wl_native: np.ndarray,
178
+ f_native: np.ndarray | None,
179
+ err_native: np.ndarray | None,
180
+ *,
181
+ min_span_px: int = 10,
182
+ pad_bins: int = 8,
183
+ min_keep_bins: int = 24,
184
+ ) -> np.ndarray:
185
+ """
186
+ Purpose:
187
+ Build a boolean mask (True=keep) on the coadd grid that retains only bins
188
+ well-supported by native data
189
+ Rules:
190
+ 1) Keep bins strictly inside native wavelength span
191
+ 2) Trim pad_bins bins from both ends of each inside segment
192
+ 3) Drop any inside segment shorter than min_keep_bins
193
+ 4) If native span < min_span_px, keep nothing
194
+ Inputs:
195
+ grid_wl: 1D coadd wavelength grid
196
+ wl_native: 1D native wavelengths where data exist.
197
+ f_native: Native flux (unused; present for API symmetry)
198
+ err_native: Native errors (unused; present for API symmetry)
199
+ min_span_px: Minimum contiguous native length (pixels) to consider (default 10)
200
+ pad_bins: Padding to exclude at both ends of each inside segment (default 8)
201
+ min_keep_bins: Minimum interior length to keep after padding (default 24)
202
+ Returns:
203
+ np.ndarray:
204
+ Boolean mask on grid_wl where True indicates bins to keep
205
+ """
206
+ grid_wl = np.asarray(grid_wl, float)
207
+ wl_native = np.asarray(wl_native, float)
208
+ if wl_native.size < max(3, min_span_px) or not np.any(np.isfinite(wl_native)):
209
+ return np.zeros_like(grid_wl, dtype=bool)
210
+ wmin = np.nanmin(wl_native)
211
+ wmax = np.nanmax(wl_native)
212
+ inside = np.isfinite(grid_wl) & (grid_wl > wmin) & (grid_wl < wmax)
213
+
214
+ keep = np.zeros_like(inside, dtype=bool)
215
+ n = inside.size
216
+ i = 0
217
+ while i < n:
218
+ if not inside[i]:
219
+ i += 1
220
+ continue
221
+ j = i
222
+ while j < n and inside[j]:
223
+ j += 1
224
+ ii = i + pad_bins
225
+ jj = j - pad_bins
226
+ if jj - ii >= min_keep_bins:
227
+ keep[ii:jj] = True
228
+ i = j
229
+ return keep
230
+
231
+
232
+ def clean_bool_runs(mask: np.ndarray, min_run: int = 24) -> np.ndarray:
233
+ """
234
+ Purpose:
235
+ Remove short True segments from a boolean mask. Any contiguous run of True
236
+ values shorter than min_run is set to False
237
+ Inputs:
238
+ mask: Boolean array
239
+ min_run: Minimum run-length of True values to retain (default 24)
240
+ Returns:
241
+ np.ndarray:
242
+ Cleaned boolean mask
243
+ """
244
+ m = np.asarray(mask, bool).copy()
245
+ n = m.size
246
+ i = 0
247
+ while i < n:
248
+ if not m[i]:
249
+ i += 1
250
+ continue
251
+ j = i
252
+ while j < n and m[j]:
253
+ j += 1
254
+ if (j - i) < min_run:
255
+ m[i:j] = False
256
+ i = j
257
+ return m
258
+
259
+
260
+ # -------------------- Sky-line helper --------------------
261
+ def skyline_mask_from_1d(y: np.ndarray, sigma_hi: float = 3.0, win: int = 51) -> np.ndarray:
262
+ """
263
+ Purpose:
264
+ Identify strong skyline-like deviations in a 1D vector using high-pass
265
+ filtering and a robust MAD-based threshold
266
+ Inputs:
267
+ y: 1D array of values
268
+ sigma_hi: Threshold in Gaussian sigmas for detection (default 3.0)
269
+ win: Window parameter guiding the smoothing scale (default 51)
270
+ Returns:
271
+ np.ndarray:
272
+ Boolean mask of same length as y where True indicates a skyline-like outlier
273
+ """
274
+ y = np.asarray(y, float)
275
+ if y.size < 20:
276
+ return np.zeros_like(y, dtype=bool)
277
+ base = gaussian_filter1d(y, max(5, win // 4), mode="nearest")
278
+ hp = y - base
279
+ mad = np.nanmedian(np.abs(hp - np.nanmedian(hp))) + 1e-12
280
+ thr = sigma_hi * 1.4826 * mad
281
+ return np.abs(hp) > thr
282
+
283
+
284
+ # -------------------- Line list loader --------------------
285
+ def load_line_list_to_microns(path: str | Path) -> np.ndarray:
286
+ """
287
+ Purpose:
288
+ Load a line list file of wavelengths with optional units and convert to microns.
289
+ Supported units: um/µm/micron(s), nm, A/Angstrom(s). Bare numbers are
290
+ heuristically interpreted based on magnitude
291
+ Inputs:
292
+ path: Path to the text file containing wavelengths, one or more tokens per line
293
+ Lines starting with '#' are ignored; commas are allowed
294
+ Returns:
295
+ np.ndarray:
296
+ Sorted 1D float array of wavelengths in microns
297
+ """
298
+ waves: list[float] = []
299
+ with open(path, "r") as f:
300
+ for raw in f:
301
+ s = raw.strip()
302
+ if not s or s.startswith("#"):
303
+ continue
304
+ parts = s.replace(",", " ").split()
305
+ val = None
306
+ unit = None
307
+ for tok in parts:
308
+ t = tok.strip()
309
+ tl = t.lower()
310
+ try:
311
+ if tl.endswith(("um", "µm", "micron", "microns")):
312
+ unit = "um"
313
+ num = "".join(ch for ch in t if (ch.isdigit() or ch in ".-+eE"))
314
+ val = float(num)
315
+ break
316
+ if tl.endswith("nm"):
317
+ unit = "nm"
318
+ val = float(t[:-2])
319
+ break
320
+ if tl.endswith(("a", "ang", "angs", "angstrom", "angstroms")):
321
+ unit = "A"
322
+ num = "".join(ch for ch in t if (ch.isdigit() or ch in ".-+eE"))
323
+ val = float(num)
324
+ break
325
+ # bare number
326
+ val = float(t)
327
+ unit = None
328
+ break
329
+ except Exception:
330
+ continue
331
+ if val is None:
332
+ continue
333
+ if unit is None:
334
+ # heuristic unit inference
335
+ if val > 1000:
336
+ unit = "A"
337
+ elif 400 <= val <= 5000:
338
+ unit = "nm"
339
+ else:
340
+ unit = "um"
341
+ waves.append(val if unit == "um" else val / 1000.0 if unit == "nm" else val / 1e4)
342
+ arr = np.array(waves, dtype=float)
343
+ arr = arr[np.isfinite(arr)]
344
+ return np.sort(arr)
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: firepype
3
+ Version: 0.0.1
4
+ Summary: FIRE AB-pair NIR reduction pipeline
5
+ Author: Gemma Cheng
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/gemma-cheng/firepype
8
+ Project-URL: Repository, https://github.com/gemma-cheng/firepype.git
9
+ Project-URL: Issues, https://github.com/gemma-cheng/firepype/issues
10
+ Keywords: astronomy,spectroscopy,FIRE,infrared,data-reduction
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy>=1.22
20
+ Requires-Dist: scipy>=1.9
21
+ Requires-Dist: astropy>=5.2
22
+ Requires-Dist: matplotlib>=3.6
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-cov; extra == "dev"
26
+ Requires-Dist: ruff; extra == "dev"
27
+ Requires-Dist: black; extra == "dev"
28
+ Requires-Dist: isort; extra == "dev"
29
+ Requires-Dist: mypy; extra == "dev"
30
+ Requires-Dist: build; extra == "dev"
31
+ Requires-Dist: twine; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # firepype
35
+
36
+ Python pipeline for Magellan/FIRE Prism-mode data. Experimental; validated on a limited dataset. Verify outputs against established pipelines (e.g. [FireHose_v2](https://github.com/jgagneastro/FireHose_v2/)).
37
+
38
+ ## Features
39
+
40
+ - Slit edge and object detection with robust heuristics
41
+ - Arc 1D extraction and line matching
42
+ - Robust Chebyshev dispersion solution with optional anchors
43
+ - Parity-aware A–B/B–A differencing and robust negative-beam scaling
44
+ - Footprint median extraction with error estimates
45
+ - Interpolation-edge masking and inverse-variance coaddition
46
+ - Telluric correction:
47
+ - POS-only standard extraction
48
+ - Band-wise scaling with deep-gap masking
49
+ - Vega model broadened to the instrument resolution
50
+ - Transmission (T) smoothing only inside dense contiguous regions
51
+ - Optional QA plots: arc overlays, labeled arc 1D, final coadd, telluric T(λ), corrected spectra
52
+
53
+ ## Installation
54
+
55
+ Installation (PyPi):
56
+
57
+ ```python -m venv .venv
58
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
59
+ python -m pip install -U pip
60
+ pip install firepype
61
+ ```
62
+
63
+ Alternatively, install from source:
64
+
65
+ ```python -m venv .venv
66
+ . .venv/bin/activate
67
+ python -m pip install -U pip
68
+ git clone https://github.com/gemma-cheng/firepype
69
+ cd firepype
70
+ pip install -e .[dev]
71
+ ```
72
+
73
+ Requirements:
74
+ - Python 3.10–3.12
75
+ - numpy ≥ 1.22, scipy ≥ 1.9, astropy ≥ 5.2, matplotlib ≥ 3.6
76
+
77
+
78
+ ## Quickstart
79
+
80
+ Minimal end-to-end reduction:
81
+
82
+ ```
83
+ firepype \
84
+ --raw-dir /path/to/raw \
85
+ --out-dir ./out \
86
+ --arc /path/to/raw/fire_0123.fits \
87
+ --ref-list /path/to/ref/line_list.lst \
88
+ --spec "1-4, 10-7"
89
+ ```
90
+
91
+ Telluric/response only:
92
+ ```
93
+ firepype-telluric \
94
+ --standard ./out/standard_extracted.fits \
95
+ --out-dir ./out/telluric \
96
+ --stype A0V --plot
97
+ ```
98
+
99
+ Outputs
100
+ - ./output/qa/ … if QA enabled
101
+ - Coadded spectrum FITS: wavelength_um, flux
102
+ - Telluric and response FITS in out/telluric/
103
+
104
+
105
+ ## Basic Tutorials
106
+
107
+ Basic usage tutorials can be found in the [`tutorials`](./tutorials/) directory
108
+
109
+
110
+ ## Notes on telluric method
111
+
112
+ - Standard extraction: POS-only column, tuned aperture/background; alternative beam used if POS median is non-positive
113
+ - Wavecal: average across a small footprint around the chosen standard column, using ARC + line list
114
+ - Vega model: broadened to instrument resolution (R ~ 6000 by default) in log-λ space
115
+ - Continuum: robust Chebyshev fit to the standard/model ratio within each band (J/H/K), excluding deep telluric gaps and known A0V intrinsic lines; fit is used to normalize before deriving T
116
+ - Transmission T(λ): computed and lightly smoothed only within dense contiguous support (prevents spreading across gaps)
117
+ - Application: science flux and errors are divided by T within overlap where T_min ≤ T ≤ T_max; elsewhere, values are left as NaN to avoid artifacts
118
+
119
+
120
+ ## Limitations and validation
121
+
122
+ - Tested on a limited set of FIRE Prism-mode observations; results are not guaranteed.
123
+ - Validate outputs against established pipelines (e.g. [FireHose_v2](https://github.com/jgagneastro/FireHose_v2/)):
124
+ - Wavelength RMS per region
125
+ - Sky residuals around OH lines
126
+ - Merged-order continuity
127
+ - S/N consistency
128
+
129
+
130
+ ## License
131
+
132
+ MIT (see [`LICENSE`](./LICENSE)).
133
+
134
+
135
+ ## File Structure
136
+
137
+ - `firepype/`
138
+ - `__init__.py` — package API (exposes `run_ab_pairs`, `apply_telluric_correction`)
139
+ - `config.py` — dataclasses for configuration
140
+ - `io.py` — FITS I/O, path builders, ID pairing
141
+ - `utils.py` — math helpers, masks, line-list loader
142
+ - `calibration.py` — peak detection, line matching, dispersion solver
143
+ - `detection.py` — slit/object detection, parity, negative scaling
144
+ - `extraction.py` — 1D extraction routines
145
+ - `coadd.py` — coaddition accumulator
146
+ - `plotting.py` — optional QA plotting
147
+ - `pipeline.py` — high-level AB-pair orchestration
148
+ - `telluric.py` — telluric correction API
149
+ - `cli.py` — command-line interface (pipeline + telluric subcommand if enabled)
150
+ - `tests/` — minimal tests for core functionality
151
+ - `pyproject.toml` — packaging configuration
152
+ - `README.md` — this file
153
+ - `LICENSE`
@@ -0,0 +1,18 @@
1
+ firepype/__init__.py,sha256=xLNpzFiR9836YIZRlyRDazMzAvs26EfsKyCk2FOulNo,530
2
+ firepype/calibration.py,sha256=fnqjlQ5gX0gc_UfS0NreppdE4Fpo0FOfH9bzUdid80E,17745
3
+ firepype/cli.py,sha256=GR-Q6HwQ92ERiUQGtbAEiOEV8vXlcFoltBaofgpjOyw,8833
4
+ firepype/coadd.py,sha256=GkXAYSp7ta_XuJ2RKjKxICKDa3a-_PT9-Vk-dD48YBA,3572
5
+ firepype/config.py,sha256=5LZ1jWKFKLtty6kFjTE8-KfzMkyo9ZloaQBp7ByRmtY,1507
6
+ firepype/detection.py,sha256=mjWL88YcxzOEE1apT6WGm_C5PYwfIyiUQ-iuI5xqCoE,17643
7
+ firepype/extraction.py,sha256=szuWuVy41NeKMTTJkP1qb9OWBYQPItW-vDvcda59fGM,6731
8
+ firepype/io.py,sha256=1PcAsonkyiyNj6Z6y9e0i_QM5qehCr83nuuzOhgoIf0,8016
9
+ firepype/pipeline.py,sha256=y6Fxo8WtkA_xrtAonrMwkbtQowkPAMtBim5yX_BNA3U,11838
10
+ firepype/plotting.py,sha256=QA5zMSn1K4FsaDSyKtGOTqFKJXlsu8Xzc7JU5ZmYk7k,7809
11
+ firepype/telluric.py,sha256=2BZbM2D0kpOaSnK5iWeakvFMIvezryl0GkSlOhZCRgo,45641
12
+ firepype/utils.py,sha256=zaKRnjiX4u34F18ARjOC0BH6b5U446jQqRjFqt6cRQM,10815
13
+ firepype-0.0.1.dist-info/licenses/LICENSE,sha256=x48N5ZHPdntCIe7-ar6ihNwS14EP6Qrh3lomJszfqOg,1068
14
+ firepype-0.0.1.dist-info/METADATA,sha256=7mazulby_bozdt0nEDOr2qMMfqH7mPOlLvfctu5u-LI,5247
15
+ firepype-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ firepype-0.0.1.dist-info/entry_points.txt,sha256=kDNIahVdISpH9Sp9XOigY5L0CgIc_jbUVV0Qh3_cPmY,94
17
+ firepype-0.0.1.dist-info/top_level.txt,sha256=zoxsw_Fp0xGN4gIuhxSMwaVVkYvhliujeehZ4TnK_Lo,9
18
+ firepype-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ firepype = firepype.cli:main
3
+ firepype-telluric = firepype.cli:main_telluric
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gemma Cheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ firepype