pyreduce-astro 0.6.0__cp314-cp314-win_amd64.whl → 0.7a2__cp314-cp314-win_amd64.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.
- pyreduce/__main__.py +194 -94
- pyreduce/cli.py +342 -0
- pyreduce/clib/Release/_slitfunc_2d.obj +0 -0
- pyreduce/clib/Release/_slitfunc_bd.obj +0 -0
- pyreduce/clib/_slitfunc_2d.cp311-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_2d.cp312-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_2d.cp313-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_2d.cp314-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_bd.cp311-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_bd.cp312-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_bd.cp313-win_amd64.pyd +0 -0
- pyreduce/clib/_slitfunc_bd.cp314-win_amd64.pyd +0 -0
- pyreduce/combine_frames.py +46 -40
- pyreduce/configuration.py +6 -1
- pyreduce/continuum_normalization.py +3 -3
- pyreduce/datasets.py +12 -5
- pyreduce/estimate_background_scatter.py +2 -1
- pyreduce/extract.py +2 -1
- pyreduce/instruments/aj.py +9 -0
- pyreduce/instruments/aj.yaml +51 -0
- pyreduce/instruments/andes.py +17 -17
- pyreduce/instruments/andes.yaml +72 -0
- pyreduce/instruments/common.py +141 -113
- pyreduce/instruments/common.yaml +57 -0
- pyreduce/instruments/crires_plus.py +17 -17
- pyreduce/instruments/crires_plus.yaml +101 -0
- pyreduce/instruments/filters.py +1 -1
- pyreduce/instruments/harpn.py +19 -17
- pyreduce/instruments/harpn.yaml +140 -0
- pyreduce/instruments/harps.py +19 -17
- pyreduce/instruments/harps.yaml +144 -0
- pyreduce/instruments/instrument_info.py +14 -14
- pyreduce/instruments/jwst_miri.py +4 -4
- pyreduce/instruments/jwst_miri.yaml +53 -0
- pyreduce/instruments/jwst_niriss.py +8 -8
- pyreduce/instruments/jwst_niriss.yaml +60 -0
- pyreduce/instruments/lick_apf.py +3 -3
- pyreduce/instruments/lick_apf.yaml +60 -0
- pyreduce/instruments/mcdonald.py +8 -8
- pyreduce/instruments/mcdonald.yaml +56 -0
- pyreduce/instruments/metis_ifu.py +7 -7
- pyreduce/instruments/metis_ifu.yaml +62 -0
- pyreduce/instruments/metis_lss.py +7 -7
- pyreduce/instruments/metis_lss.yaml +62 -0
- pyreduce/instruments/micado.py +5 -5
- pyreduce/instruments/micado.yaml +62 -0
- pyreduce/instruments/models.py +257 -0
- pyreduce/instruments/neid.py +19 -17
- pyreduce/instruments/neid.yaml +61 -0
- pyreduce/instruments/nirspec.py +12 -12
- pyreduce/instruments/nirspec.yaml +63 -0
- pyreduce/instruments/nte.py +7 -7
- pyreduce/instruments/nte.yaml +55 -0
- pyreduce/instruments/uves.py +9 -9
- pyreduce/instruments/uves.yaml +65 -0
- pyreduce/instruments/xshooter.py +6 -6
- pyreduce/instruments/xshooter.yaml +63 -0
- pyreduce/make_shear.py +3 -2
- pyreduce/pipeline.py +619 -0
- pyreduce/reduce.py +90 -230
- pyreduce/settings/settings_AJ.json +19 -0
- pyreduce/settings/settings_ANDES.json +1 -1
- pyreduce/settings/settings_CRIRES_PLUS.json +1 -1
- pyreduce/settings/settings_HARPN.json +1 -1
- pyreduce/settings/settings_HARPS.json +1 -1
- pyreduce/settings/settings_JWST_MIRI.json +1 -1
- pyreduce/settings/settings_JWST_NIRISS.json +1 -1
- pyreduce/settings/settings_LICK_APF.json +2 -2
- pyreduce/settings/settings_MCDONALD.json +1 -1
- pyreduce/settings/settings_METIS_IFU.json +1 -1
- pyreduce/settings/settings_METIS_LSS.json +1 -1
- pyreduce/settings/settings_MICADO.json +1 -1
- pyreduce/settings/settings_NEID.json +1 -1
- pyreduce/settings/settings_NIRSPEC.json +2 -2
- pyreduce/settings/settings_NTE.json +1 -1
- pyreduce/settings/settings_UVES.json +1 -1
- pyreduce/settings/settings_XSHOOTER.json +1 -1
- pyreduce/settings/settings_pyreduce.json +8 -2
- pyreduce/settings/settings_schema.json +27 -4
- pyreduce/tools/combine.py +2 -2
- pyreduce/{trace_orders.py → trace.py} +364 -30
- pyreduce/util.py +82 -4
- pyreduce/wavelength_calibration.py +12 -14
- pyreduce_astro-0.7a2.dist-info/METADATA +106 -0
- {pyreduce_astro-0.6.0.dist-info → pyreduce_astro-0.7a2.dist-info}/RECORD +88 -82
- pyreduce_astro-0.7a2.dist-info/entry_points.txt +2 -0
- pyreduce/instruments/andes.json +0 -61
- pyreduce/instruments/common.json +0 -46
- pyreduce/instruments/crires_plus.json +0 -63
- pyreduce/instruments/harpn.json +0 -136
- pyreduce/instruments/harps.json +0 -155
- pyreduce/instruments/instrument_schema.json +0 -318
- pyreduce/instruments/jwst_miri.json +0 -53
- pyreduce/instruments/jwst_niriss.json +0 -52
- pyreduce/instruments/lick_apf.json +0 -53
- pyreduce/instruments/mcdonald.json +0 -59
- pyreduce/instruments/metis_ifu.json +0 -63
- pyreduce/instruments/metis_lss.json +0 -65
- pyreduce/instruments/micado.json +0 -53
- pyreduce/instruments/neid.json +0 -51
- pyreduce/instruments/nirspec.json +0 -56
- pyreduce/instruments/nte.json +0 -47
- pyreduce/instruments/uves.json +0 -59
- pyreduce/instruments/xshooter.json +0 -66
- pyreduce_astro-0.6.0.dist-info/METADATA +0 -114
- {pyreduce_astro-0.6.0.dist-info → pyreduce_astro-0.7a2.dist-info}/WHEEL +0 -0
- {pyreduce_astro-0.6.0.dist-info → pyreduce_astro-0.7a2.dist-info}/licenses/LICENSE +0 -0
pyreduce/__main__.py
CHANGED
|
@@ -1,106 +1,206 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
PyReduce command-line interface.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
uv run reduce --help
|
|
6
|
+
uv run reduce run UVES HD132205 --night 2010-04-01
|
|
7
|
+
uv run reduce run UVES HD132205 --steps bias,flat,orders
|
|
8
|
+
uv run reduce bias UVES HD132205
|
|
9
|
+
uv run reduce combine --output combined.fits *.final.fits
|
|
3
10
|
"""
|
|
4
11
|
|
|
5
|
-
import
|
|
6
|
-
import sys
|
|
12
|
+
import click
|
|
7
13
|
|
|
8
|
-
from .
|
|
14
|
+
from . import datasets
|
|
15
|
+
from .configuration import get_configuration_for_instrument
|
|
16
|
+
from .reduce import main as reduce_main
|
|
9
17
|
from .tools.combine import combine as tools_combine
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
19
|
+
ALL_STEPS = (
|
|
20
|
+
"bias",
|
|
21
|
+
"flat",
|
|
22
|
+
"orders",
|
|
23
|
+
"curvature",
|
|
24
|
+
"scatter",
|
|
25
|
+
"norm_flat",
|
|
26
|
+
"wavecal_master",
|
|
27
|
+
"wavecal_init",
|
|
28
|
+
"wavecal",
|
|
29
|
+
"freq_comb_master",
|
|
30
|
+
"freq_comb",
|
|
31
|
+
"science",
|
|
32
|
+
"continuum",
|
|
33
|
+
"finalize",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group()
|
|
38
|
+
@click.version_option(package_name="pyreduce-astro")
|
|
39
|
+
def cli():
|
|
40
|
+
"""PyReduce - Echelle spectrograph data reduction pipeline."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cli.command()
|
|
45
|
+
@click.argument("instrument")
|
|
46
|
+
@click.argument("target")
|
|
47
|
+
@click.option("--night", "-n", default=None, help="Observation night (YYYY-MM-DD)")
|
|
48
|
+
@click.option("--arm", "-a", default=None, help="Instrument arm/detector")
|
|
49
|
+
@click.option(
|
|
50
|
+
"--steps",
|
|
51
|
+
"-s",
|
|
52
|
+
default=None,
|
|
53
|
+
help="Comma-separated steps to run (default: all)",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--base-dir",
|
|
57
|
+
"-b",
|
|
58
|
+
default=None,
|
|
59
|
+
help="Base directory for data (default: $REDUCE_DATA or ~/REDUCE_DATA)",
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--input-dir", "-i", default="raw", help="Input directory relative to base"
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--output-dir", "-o", default="reduced", help="Output directory relative to base"
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"--plot", "-p", default=0, help="Plot level (0=none, 1=save, 2=interactive)"
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--order-range",
|
|
72
|
+
default=None,
|
|
73
|
+
help="Order range to process (e.g., '1,21')",
|
|
74
|
+
)
|
|
75
|
+
def run(
|
|
76
|
+
instrument,
|
|
77
|
+
target,
|
|
78
|
+
night,
|
|
79
|
+
arm,
|
|
80
|
+
steps,
|
|
81
|
+
base_dir,
|
|
82
|
+
input_dir,
|
|
83
|
+
output_dir,
|
|
84
|
+
plot,
|
|
85
|
+
order_range,
|
|
86
|
+
):
|
|
87
|
+
"""Run the reduction pipeline.
|
|
88
|
+
|
|
89
|
+
INSTRUMENT: Name of the instrument (e.g., UVES, HARPS, XSHOOTER)
|
|
90
|
+
TARGET: Target star name or regex pattern
|
|
91
|
+
"""
|
|
92
|
+
# Parse steps
|
|
93
|
+
if steps:
|
|
94
|
+
steps = tuple(s.strip() for s in steps.split(","))
|
|
95
|
+
else:
|
|
96
|
+
steps = "all"
|
|
97
|
+
|
|
98
|
+
# Parse order range
|
|
99
|
+
if order_range:
|
|
100
|
+
parts = order_range.split(",")
|
|
101
|
+
order_range = (int(parts[0]), int(parts[1]))
|
|
102
|
+
|
|
103
|
+
# Load configuration
|
|
104
|
+
config = get_configuration_for_instrument(instrument)
|
|
105
|
+
|
|
106
|
+
# Run reduction
|
|
107
|
+
reduce_main(
|
|
108
|
+
instrument=instrument,
|
|
109
|
+
target=target,
|
|
110
|
+
night=night,
|
|
111
|
+
arms=arm,
|
|
112
|
+
steps=steps,
|
|
113
|
+
base_dir=base_dir or "",
|
|
114
|
+
input_dir=input_dir,
|
|
115
|
+
output_dir=output_dir,
|
|
116
|
+
configuration=config,
|
|
117
|
+
order_range=order_range,
|
|
118
|
+
plot=plot,
|
|
82
119
|
)
|
|
83
120
|
|
|
84
|
-
args = parser.parse_args()
|
|
85
|
-
|
|
86
|
-
files = args.input
|
|
87
|
-
output = args.output
|
|
88
|
-
plot = args.plot
|
|
89
121
|
|
|
90
|
-
|
|
122
|
+
@cli.command()
|
|
123
|
+
@click.argument("files", nargs=-1, required=True)
|
|
124
|
+
@click.option("--output", "-o", default="combined.fits", help="Output filename")
|
|
125
|
+
@click.option("--plot", "-p", default=None, type=int, help="Plot specific order")
|
|
126
|
+
def combine(files, output, plot):
|
|
127
|
+
"""Combine multiple reduced spectra.
|
|
128
|
+
|
|
129
|
+
FILES: Input .final.fits files to combine
|
|
130
|
+
"""
|
|
131
|
+
tools_combine(list(files), output, plot=plot)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@cli.command()
|
|
135
|
+
@click.argument("instrument")
|
|
136
|
+
def download(instrument):
|
|
137
|
+
"""Download sample dataset for an instrument.
|
|
138
|
+
|
|
139
|
+
INSTRUMENT: Name of the instrument (e.g., UVES, HARPS)
|
|
140
|
+
"""
|
|
141
|
+
instrument = instrument.upper()
|
|
142
|
+
dataset_func = getattr(datasets, instrument, None)
|
|
143
|
+
if dataset_func is None:
|
|
144
|
+
available = [
|
|
145
|
+
name
|
|
146
|
+
for name in dir(datasets)
|
|
147
|
+
if name.isupper() and not name.startswith("_")
|
|
148
|
+
]
|
|
149
|
+
raise click.ClickException(
|
|
150
|
+
f"Unknown instrument '{instrument}'. Available: {', '.join(available)}"
|
|
151
|
+
)
|
|
152
|
+
path = dataset_func()
|
|
153
|
+
click.echo(f"Dataset downloaded to: {path}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@cli.command("list-steps")
|
|
157
|
+
def list_steps():
|
|
158
|
+
"""List all available reduction steps."""
|
|
159
|
+
click.echo("Available reduction steps:")
|
|
160
|
+
for step in ALL_STEPS:
|
|
161
|
+
click.echo(f" - {step}")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def make_step_command(step_name):
|
|
165
|
+
"""Factory to create a command for a single step."""
|
|
166
|
+
|
|
167
|
+
@click.command(name=step_name)
|
|
168
|
+
@click.argument("instrument")
|
|
169
|
+
@click.argument("target")
|
|
170
|
+
@click.option("--night", "-n", default=None, help="Observation night")
|
|
171
|
+
@click.option("--arm", "-a", default=None, help="Instrument arm")
|
|
172
|
+
@click.option("--base-dir", "-b", default=None, help="Base directory")
|
|
173
|
+
@click.option("--input-dir", "-i", default="raw", help="Input directory")
|
|
174
|
+
@click.option("--output-dir", "-o", default="reduced", help="Output directory")
|
|
175
|
+
@click.option("--plot", "-p", default=0, help="Plot level")
|
|
176
|
+
def cmd(instrument, target, night, arm, base_dir, input_dir, output_dir, plot):
|
|
177
|
+
config = get_configuration_for_instrument(instrument)
|
|
178
|
+
reduce_main(
|
|
179
|
+
instrument=instrument,
|
|
180
|
+
target=target,
|
|
181
|
+
night=night,
|
|
182
|
+
arms=arm,
|
|
183
|
+
steps=(step_name,),
|
|
184
|
+
base_dir=base_dir or "",
|
|
185
|
+
input_dir=input_dir,
|
|
186
|
+
output_dir=output_dir,
|
|
187
|
+
configuration=config,
|
|
188
|
+
plot=plot,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
cmd.__doc__ = f"Run the '{step_name}' step."
|
|
192
|
+
return cmd
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Register individual step commands
|
|
196
|
+
for _step in ALL_STEPS:
|
|
197
|
+
cli.add_command(make_step_command(_step))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def main():
|
|
201
|
+
"""Entry point for the CLI."""
|
|
202
|
+
cli()
|
|
91
203
|
|
|
92
204
|
|
|
93
205
|
if __name__ == "__main__":
|
|
94
|
-
|
|
95
|
-
if len(sys.argv) == 1:
|
|
96
|
-
script = "help"
|
|
97
|
-
else:
|
|
98
|
-
script = sys.argv[1]
|
|
99
|
-
|
|
100
|
-
# Run the chosen script
|
|
101
|
-
if script == "reduce":
|
|
102
|
-
reduce()
|
|
103
|
-
elif script == "combine":
|
|
104
|
-
combine()
|
|
105
|
-
else:
|
|
106
|
-
help()
|
|
206
|
+
main()
|
pyreduce/cli.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Click-based CLI for PyReduce.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
uv run reduce --help
|
|
5
|
+
uv run reduce bias INSTRUMENT --files bias/*.fits --output output/
|
|
6
|
+
uv run reduce run reduction.yaml
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from glob import glob
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from . import datasets
|
|
17
|
+
from .instruments.instrument_info import load_instrument
|
|
18
|
+
from .pipeline import Pipeline
|
|
19
|
+
|
|
20
|
+
# Map CLI names to dataset functions
|
|
21
|
+
AVAILABLE_DATASETS = {
|
|
22
|
+
"UVES": datasets.UVES,
|
|
23
|
+
"HARPS": datasets.HARPS,
|
|
24
|
+
"XSHOOTER": datasets.XSHOOTER,
|
|
25
|
+
"NIRSPEC": datasets.KECK_NIRSPEC,
|
|
26
|
+
"JWST_NIRISS": datasets.JWST_NIRISS,
|
|
27
|
+
"JWST_MIRI": datasets.JWST_MIRI,
|
|
28
|
+
"LICK_APF": datasets.LICK_APF,
|
|
29
|
+
"MCDONALD": datasets.MCDONALD,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.group()
|
|
34
|
+
@click.version_option(package_name="pyreduce-astro")
|
|
35
|
+
def cli():
|
|
36
|
+
"""PyReduce echelle spectrograph reduction pipeline."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@cli.command()
|
|
41
|
+
@click.argument("instrument")
|
|
42
|
+
@click.option(
|
|
43
|
+
"--output",
|
|
44
|
+
"-o",
|
|
45
|
+
default=None,
|
|
46
|
+
help="Output directory (default: $REDUCE_DATA or ~/REDUCE_DATA)",
|
|
47
|
+
)
|
|
48
|
+
def download(instrument: str, output: str | None):
|
|
49
|
+
"""Download example dataset for an instrument.
|
|
50
|
+
|
|
51
|
+
Available instruments: UVES, HARPS, XSHOOTER, NIRSPEC, JWST_NIRISS, JWST_MIRI,
|
|
52
|
+
LICK_APF, MCDONALD
|
|
53
|
+
|
|
54
|
+
Data is saved to $REDUCE_DATA if set, otherwise ~/REDUCE_DATA.
|
|
55
|
+
|
|
56
|
+
\b
|
|
57
|
+
Examples:
|
|
58
|
+
uv run reduce download UVES
|
|
59
|
+
uv run reduce download UVES -o ~/data/
|
|
60
|
+
"""
|
|
61
|
+
instrument_upper = instrument.upper()
|
|
62
|
+
if instrument_upper not in AVAILABLE_DATASETS:
|
|
63
|
+
available = ", ".join(sorted(AVAILABLE_DATASETS.keys()))
|
|
64
|
+
raise click.ClickException(
|
|
65
|
+
f"Unknown instrument '{instrument}'. Available: {available}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
click.echo(f"Downloading {instrument_upper} example dataset...")
|
|
69
|
+
data_dir = AVAILABLE_DATASETS[instrument_upper](output)
|
|
70
|
+
click.echo(f"Dataset saved to: {data_dir}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@cli.command("list-datasets")
|
|
74
|
+
def list_datasets():
|
|
75
|
+
"""List available example datasets."""
|
|
76
|
+
click.echo("Available example datasets:")
|
|
77
|
+
click.echo()
|
|
78
|
+
for name in sorted(AVAILABLE_DATASETS.keys()):
|
|
79
|
+
click.echo(f" {name}")
|
|
80
|
+
click.echo()
|
|
81
|
+
click.echo("Download with: uv run reduce download <INSTRUMENT> -o <DIR>")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@cli.command()
|
|
85
|
+
@click.argument("instrument")
|
|
86
|
+
@click.option(
|
|
87
|
+
"--files",
|
|
88
|
+
"-f",
|
|
89
|
+
multiple=True,
|
|
90
|
+
help="Input FITS files (can use glob patterns)",
|
|
91
|
+
)
|
|
92
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
93
|
+
@click.option("--mode", "-m", default="", help="Instrument mode (e.g., RED, BLUE)")
|
|
94
|
+
@click.option(
|
|
95
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
96
|
+
)
|
|
97
|
+
def bias(instrument: str, files: tuple[str, ...], output: str, mode: str, plot: int):
|
|
98
|
+
"""Create master bias from bias frames."""
|
|
99
|
+
inst = load_instrument(instrument)
|
|
100
|
+
file_list = _expand_globs(files)
|
|
101
|
+
|
|
102
|
+
if not file_list:
|
|
103
|
+
raise click.ClickException("No input files specified. Use --files option.")
|
|
104
|
+
|
|
105
|
+
click.echo(f"Creating master bias from {len(file_list)} files...")
|
|
106
|
+
Pipeline(inst, output, mode=mode, plot=plot).bias(file_list).run()
|
|
107
|
+
click.echo(f"Master bias saved to {output}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@cli.command()
|
|
111
|
+
@click.argument("instrument")
|
|
112
|
+
@click.option(
|
|
113
|
+
"--files",
|
|
114
|
+
"-f",
|
|
115
|
+
multiple=True,
|
|
116
|
+
help="Input FITS files (can use glob patterns)",
|
|
117
|
+
)
|
|
118
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
119
|
+
@click.option("--mode", "-m", default="", help="Instrument mode")
|
|
120
|
+
@click.option(
|
|
121
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
122
|
+
)
|
|
123
|
+
def flat(instrument: str, files: tuple[str, ...], output: str, mode: str, plot: int):
|
|
124
|
+
"""Create master flat from flat frames."""
|
|
125
|
+
inst = load_instrument(instrument)
|
|
126
|
+
file_list = _expand_globs(files)
|
|
127
|
+
|
|
128
|
+
if not file_list:
|
|
129
|
+
raise click.ClickException("No input files specified. Use --files option.")
|
|
130
|
+
|
|
131
|
+
click.echo(f"Creating master flat from {len(file_list)} files...")
|
|
132
|
+
Pipeline(inst, output, mode=mode, plot=plot).flat(file_list).run()
|
|
133
|
+
click.echo(f"Master flat saved to {output}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@cli.command()
|
|
137
|
+
@click.argument("instrument")
|
|
138
|
+
@click.option(
|
|
139
|
+
"--files",
|
|
140
|
+
"-f",
|
|
141
|
+
multiple=True,
|
|
142
|
+
help="Flat files for tracing (optional if flat already exists)",
|
|
143
|
+
)
|
|
144
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
145
|
+
@click.option("--mode", "-m", default="", help="Instrument mode")
|
|
146
|
+
@click.option(
|
|
147
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
148
|
+
)
|
|
149
|
+
def trace(instrument: str, files: tuple[str, ...], output: str, mode: str, plot: int):
|
|
150
|
+
"""Trace echelle orders on flat field."""
|
|
151
|
+
inst = load_instrument(instrument)
|
|
152
|
+
file_list = _expand_globs(files) if files else None
|
|
153
|
+
|
|
154
|
+
click.echo("Tracing echelle orders...")
|
|
155
|
+
pipe = Pipeline(inst, output, mode=mode, plot=plot)
|
|
156
|
+
if file_list:
|
|
157
|
+
pipe = pipe.flat(file_list)
|
|
158
|
+
pipe.trace_orders(file_list).run()
|
|
159
|
+
click.echo(f"Order trace saved to {output}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@cli.command()
|
|
163
|
+
@click.argument("instrument")
|
|
164
|
+
@click.option(
|
|
165
|
+
"--files",
|
|
166
|
+
"-f",
|
|
167
|
+
multiple=True,
|
|
168
|
+
required=True,
|
|
169
|
+
help="Wavelength calibration files (ThAr, etc.)",
|
|
170
|
+
)
|
|
171
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
172
|
+
@click.option("--mode", "-m", default="", help="Instrument mode")
|
|
173
|
+
@click.option(
|
|
174
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
175
|
+
)
|
|
176
|
+
def wavecal(instrument: str, files: tuple[str, ...], output: str, mode: str, plot: int):
|
|
177
|
+
"""Perform wavelength calibration."""
|
|
178
|
+
inst = load_instrument(instrument)
|
|
179
|
+
file_list = _expand_globs(files)
|
|
180
|
+
|
|
181
|
+
if not file_list:
|
|
182
|
+
raise click.ClickException("No input files specified. Use --files option.")
|
|
183
|
+
|
|
184
|
+
click.echo(f"Running wavelength calibration with {len(file_list)} files...")
|
|
185
|
+
Pipeline(inst, output, mode=mode, plot=plot).wavelength_calibration(file_list).run()
|
|
186
|
+
click.echo(f"Wavelength calibration saved to {output}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@cli.command()
|
|
190
|
+
@click.argument("instrument")
|
|
191
|
+
@click.option(
|
|
192
|
+
"--files",
|
|
193
|
+
"-f",
|
|
194
|
+
multiple=True,
|
|
195
|
+
required=True,
|
|
196
|
+
help="Science observation files",
|
|
197
|
+
)
|
|
198
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
199
|
+
@click.option("--mode", "-m", default="", help="Instrument mode")
|
|
200
|
+
@click.option("--target", "-t", default="", help="Target name")
|
|
201
|
+
@click.option(
|
|
202
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
203
|
+
)
|
|
204
|
+
def extract(
|
|
205
|
+
instrument: str,
|
|
206
|
+
files: tuple[str, ...],
|
|
207
|
+
output: str,
|
|
208
|
+
mode: str,
|
|
209
|
+
target: str,
|
|
210
|
+
plot: int,
|
|
211
|
+
):
|
|
212
|
+
"""Extract spectra from science frames."""
|
|
213
|
+
inst = load_instrument(instrument)
|
|
214
|
+
file_list = _expand_globs(files)
|
|
215
|
+
|
|
216
|
+
if not file_list:
|
|
217
|
+
raise click.ClickException("No input files specified. Use --files option.")
|
|
218
|
+
|
|
219
|
+
click.echo(f"Extracting spectra from {len(file_list)} files...")
|
|
220
|
+
Pipeline(inst, output, mode=mode, target=target, plot=plot).extract(file_list).run()
|
|
221
|
+
click.echo(f"Extracted spectra saved to {output}")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@cli.command()
|
|
225
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
226
|
+
@click.option(
|
|
227
|
+
"--steps",
|
|
228
|
+
"-s",
|
|
229
|
+
default="all",
|
|
230
|
+
help="Steps to run (comma-separated, or 'all')",
|
|
231
|
+
)
|
|
232
|
+
@click.option("--skip-existing", is_flag=True, help="Skip steps with existing output")
|
|
233
|
+
@click.option(
|
|
234
|
+
"--plot", "-p", default=0, type=int, help="Plot level (0=off, 1=basic, 2=detailed)"
|
|
235
|
+
)
|
|
236
|
+
def run(config_file: str, steps: str, skip_existing: bool, plot: int):
|
|
237
|
+
"""Run full reduction pipeline from config file.
|
|
238
|
+
|
|
239
|
+
CONFIG_FILE should be a YAML file with instrument, files, and output settings.
|
|
240
|
+
|
|
241
|
+
Example config.yaml:
|
|
242
|
+
|
|
243
|
+
\b
|
|
244
|
+
instrument: UVES
|
|
245
|
+
output: /data/reduced/
|
|
246
|
+
mode: RED
|
|
247
|
+
files:
|
|
248
|
+
bias: /data/raw/bias/*.fits
|
|
249
|
+
flat: /data/raw/flat/*.fits
|
|
250
|
+
wavecal: /data/raw/thar/*.fits
|
|
251
|
+
science: /data/raw/science/*.fits
|
|
252
|
+
steps: [bias, flat, trace, wavecal, extract]
|
|
253
|
+
"""
|
|
254
|
+
import yaml
|
|
255
|
+
|
|
256
|
+
with open(config_file) as f:
|
|
257
|
+
config = yaml.safe_load(f)
|
|
258
|
+
|
|
259
|
+
instrument_name = config.get("instrument")
|
|
260
|
+
if not instrument_name:
|
|
261
|
+
raise click.ClickException("Config file must specify 'instrument'")
|
|
262
|
+
|
|
263
|
+
inst = load_instrument(instrument_name)
|
|
264
|
+
output = config.get("output", ".")
|
|
265
|
+
mode = config.get("mode", "")
|
|
266
|
+
target = config.get("target", "")
|
|
267
|
+
files = config.get("files", {})
|
|
268
|
+
config_steps = config.get("steps", [])
|
|
269
|
+
|
|
270
|
+
# Parse steps
|
|
271
|
+
if steps != "all":
|
|
272
|
+
config_steps = [s.strip() for s in steps.split(",")]
|
|
273
|
+
elif not config_steps:
|
|
274
|
+
config_steps = ["bias", "flat", "trace", "wavecal", "extract"]
|
|
275
|
+
|
|
276
|
+
click.echo(f"Running pipeline for {instrument_name}")
|
|
277
|
+
click.echo(f"Steps: {', '.join(config_steps)}")
|
|
278
|
+
click.echo(f"Output: {output}")
|
|
279
|
+
|
|
280
|
+
pipe = Pipeline(inst, output, mode=mode, target=target, plot=plot)
|
|
281
|
+
|
|
282
|
+
# Add steps based on config
|
|
283
|
+
if "bias" in config_steps and files.get("bias"):
|
|
284
|
+
pipe = pipe.bias(_expand_globs(files["bias"]))
|
|
285
|
+
|
|
286
|
+
if "flat" in config_steps and files.get("flat"):
|
|
287
|
+
pipe = pipe.flat(_expand_globs(files["flat"]))
|
|
288
|
+
|
|
289
|
+
if "trace" in config_steps:
|
|
290
|
+
trace_files = files.get("orders") or files.get("flat")
|
|
291
|
+
pipe = pipe.trace_orders(_expand_globs(trace_files) if trace_files else None)
|
|
292
|
+
|
|
293
|
+
if "scatter" in config_steps:
|
|
294
|
+
pipe = pipe.scatter()
|
|
295
|
+
|
|
296
|
+
if "norm_flat" in config_steps:
|
|
297
|
+
pipe = pipe.normalize_flat()
|
|
298
|
+
|
|
299
|
+
if "wavecal" in config_steps and files.get("wavecal"):
|
|
300
|
+
pipe = pipe.wavelength_calibration(_expand_globs(files["wavecal"]))
|
|
301
|
+
|
|
302
|
+
if "curvature" in config_steps:
|
|
303
|
+
curv_files = files.get("curvature") or files.get("wavecal")
|
|
304
|
+
pipe = pipe.curvature(_expand_globs(curv_files) if curv_files else None)
|
|
305
|
+
|
|
306
|
+
if "extract" in config_steps and files.get("science"):
|
|
307
|
+
pipe = pipe.extract(_expand_globs(files["science"]))
|
|
308
|
+
|
|
309
|
+
if "continuum" in config_steps:
|
|
310
|
+
pipe = pipe.continuum()
|
|
311
|
+
|
|
312
|
+
if "finalize" in config_steps:
|
|
313
|
+
pipe = pipe.finalize()
|
|
314
|
+
|
|
315
|
+
pipe.run(skip_existing=skip_existing)
|
|
316
|
+
click.echo("Pipeline complete!")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _expand_globs(patterns) -> list[str]:
|
|
320
|
+
"""Expand glob patterns to file list."""
|
|
321
|
+
if isinstance(patterns, str):
|
|
322
|
+
patterns = [patterns]
|
|
323
|
+
|
|
324
|
+
files = []
|
|
325
|
+
for pattern in patterns:
|
|
326
|
+
expanded = glob(pattern)
|
|
327
|
+
if expanded:
|
|
328
|
+
files.extend(expanded)
|
|
329
|
+
else:
|
|
330
|
+
# If no glob match, treat as literal path
|
|
331
|
+
if Path(pattern).exists():
|
|
332
|
+
files.append(pattern)
|
|
333
|
+
return sorted(set(files))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def main():
|
|
337
|
+
"""Entry point for the CLI."""
|
|
338
|
+
cli()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
if __name__ == "__main__":
|
|
342
|
+
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|