pyreduce-astro 0.7a4__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/__init__.py +67 -0
- pyreduce/__main__.py +322 -0
- pyreduce/cli.py +342 -0
- pyreduce/clib/Release/_slitfunc_2d.cp311-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp311-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp312-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp312-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp313-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp313-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp314-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_2d.cp314-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_2d.obj +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp311-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp311-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp312-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp312-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp313-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp313-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp314-win_amd64.exp +0 -0
- pyreduce/clib/Release/_slitfunc_bd.cp314-win_amd64.lib +0 -0
- pyreduce/clib/Release/_slitfunc_bd.obj +0 -0
- pyreduce/clib/__init__.py +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/clib/build_extract.py +75 -0
- pyreduce/clib/slit_func_2d_xi_zeta_bd.c +1313 -0
- pyreduce/clib/slit_func_2d_xi_zeta_bd.h +55 -0
- pyreduce/clib/slit_func_bd.c +362 -0
- pyreduce/clib/slit_func_bd.h +17 -0
- pyreduce/clipnflip.py +147 -0
- pyreduce/combine_frames.py +861 -0
- pyreduce/configuration.py +191 -0
- pyreduce/continuum_normalization.py +329 -0
- pyreduce/cwrappers.py +404 -0
- pyreduce/datasets.py +238 -0
- pyreduce/echelle.py +413 -0
- pyreduce/estimate_background_scatter.py +130 -0
- pyreduce/extract.py +1362 -0
- pyreduce/extraction_width.py +77 -0
- pyreduce/instruments/__init__.py +0 -0
- pyreduce/instruments/aj.py +9 -0
- pyreduce/instruments/aj.yaml +51 -0
- pyreduce/instruments/andes.py +102 -0
- pyreduce/instruments/andes.yaml +72 -0
- pyreduce/instruments/common.py +711 -0
- pyreduce/instruments/common.yaml +57 -0
- pyreduce/instruments/crires_plus.py +103 -0
- pyreduce/instruments/crires_plus.yaml +101 -0
- pyreduce/instruments/filters.py +195 -0
- pyreduce/instruments/harpn.py +203 -0
- pyreduce/instruments/harpn.yaml +140 -0
- pyreduce/instruments/harps.py +312 -0
- pyreduce/instruments/harps.yaml +144 -0
- pyreduce/instruments/instrument_info.py +140 -0
- pyreduce/instruments/jwst_miri.py +29 -0
- pyreduce/instruments/jwst_miri.yaml +53 -0
- pyreduce/instruments/jwst_niriss.py +98 -0
- pyreduce/instruments/jwst_niriss.yaml +60 -0
- pyreduce/instruments/lick_apf.py +35 -0
- pyreduce/instruments/lick_apf.yaml +60 -0
- pyreduce/instruments/mcdonald.py +123 -0
- pyreduce/instruments/mcdonald.yaml +56 -0
- pyreduce/instruments/metis_ifu.py +45 -0
- pyreduce/instruments/metis_ifu.yaml +62 -0
- pyreduce/instruments/metis_lss.py +45 -0
- pyreduce/instruments/metis_lss.yaml +62 -0
- pyreduce/instruments/micado.py +45 -0
- pyreduce/instruments/micado.yaml +62 -0
- pyreduce/instruments/models.py +257 -0
- pyreduce/instruments/neid.py +156 -0
- pyreduce/instruments/neid.yaml +61 -0
- pyreduce/instruments/nirspec.py +215 -0
- pyreduce/instruments/nirspec.yaml +63 -0
- pyreduce/instruments/nte.py +42 -0
- pyreduce/instruments/nte.yaml +55 -0
- pyreduce/instruments/uves.py +46 -0
- pyreduce/instruments/uves.yaml +65 -0
- pyreduce/instruments/xshooter.py +39 -0
- pyreduce/instruments/xshooter.yaml +63 -0
- pyreduce/make_shear.py +607 -0
- pyreduce/masks/mask_crires_plus_det1.fits.gz +0 -0
- pyreduce/masks/mask_crires_plus_det2.fits.gz +0 -0
- pyreduce/masks/mask_crires_plus_det3.fits.gz +0 -0
- pyreduce/masks/mask_ctio_chiron.fits.gz +0 -0
- pyreduce/masks/mask_elodie.fits.gz +0 -0
- pyreduce/masks/mask_feros3.fits.gz +0 -0
- pyreduce/masks/mask_flames_giraffe.fits.gz +0 -0
- pyreduce/masks/mask_harps_blue.fits.gz +0 -0
- pyreduce/masks/mask_harps_red.fits.gz +0 -0
- pyreduce/masks/mask_hds_blue.fits.gz +0 -0
- pyreduce/masks/mask_hds_red.fits.gz +0 -0
- pyreduce/masks/mask_het_hrs_2x5.fits.gz +0 -0
- pyreduce/masks/mask_jwst_miri_lrs_slitless.fits.gz +0 -0
- pyreduce/masks/mask_jwst_niriss_gr700xd.fits.gz +0 -0
- pyreduce/masks/mask_lick_apf_.fits.gz +0 -0
- pyreduce/masks/mask_mcdonald.fits.gz +0 -0
- pyreduce/masks/mask_nes.fits.gz +0 -0
- pyreduce/masks/mask_nirspec_nirspec.fits.gz +0 -0
- pyreduce/masks/mask_sarg.fits.gz +0 -0
- pyreduce/masks/mask_sarg_2x2a.fits.gz +0 -0
- pyreduce/masks/mask_sarg_2x2b.fits.gz +0 -0
- pyreduce/masks/mask_subaru_hds_red.fits.gz +0 -0
- pyreduce/masks/mask_uves_blue.fits.gz +0 -0
- pyreduce/masks/mask_uves_blue_binned_2_2.fits.gz +0 -0
- pyreduce/masks/mask_uves_middle.fits.gz +0 -0
- pyreduce/masks/mask_uves_middle_2x2_split.fits.gz +0 -0
- pyreduce/masks/mask_uves_middle_binned_2_2.fits.gz +0 -0
- pyreduce/masks/mask_uves_red.fits.gz +0 -0
- pyreduce/masks/mask_uves_red_2x2.fits.gz +0 -0
- pyreduce/masks/mask_uves_red_2x2_split.fits.gz +0 -0
- pyreduce/masks/mask_uves_red_binned_2_2.fits.gz +0 -0
- pyreduce/masks/mask_xshooter_nir.fits.gz +0 -0
- pyreduce/pipeline.py +619 -0
- pyreduce/rectify.py +138 -0
- pyreduce/reduce.py +2065 -0
- pyreduce/settings/settings_AJ.json +19 -0
- pyreduce/settings/settings_ANDES.json +89 -0
- pyreduce/settings/settings_CRIRES_PLUS.json +89 -0
- pyreduce/settings/settings_HARPN.json +73 -0
- pyreduce/settings/settings_HARPS.json +69 -0
- pyreduce/settings/settings_JWST_MIRI.json +55 -0
- pyreduce/settings/settings_JWST_NIRISS.json +55 -0
- pyreduce/settings/settings_LICK_APF.json +62 -0
- pyreduce/settings/settings_MCDONALD.json +58 -0
- pyreduce/settings/settings_METIS_IFU.json +77 -0
- pyreduce/settings/settings_METIS_LSS.json +77 -0
- pyreduce/settings/settings_MICADO.json +78 -0
- pyreduce/settings/settings_NEID.json +73 -0
- pyreduce/settings/settings_NIRSPEC.json +58 -0
- pyreduce/settings/settings_NTE.json +60 -0
- pyreduce/settings/settings_UVES.json +54 -0
- pyreduce/settings/settings_XSHOOTER.json +78 -0
- pyreduce/settings/settings_pyreduce.json +184 -0
- pyreduce/settings/settings_schema.json +850 -0
- pyreduce/tools/__init__.py +0 -0
- pyreduce/tools/combine.py +117 -0
- pyreduce/trace.py +979 -0
- pyreduce/util.py +1366 -0
- pyreduce/wavecal/MICADO_HK_3arcsec_chip5.npz +0 -0
- pyreduce/wavecal/atlas/thar.fits +4946 -13
- pyreduce/wavecal/atlas/thar_list.txt +4172 -0
- pyreduce/wavecal/atlas/une.fits +0 -0
- pyreduce/wavecal/convert.py +38 -0
- pyreduce/wavecal/crires_plus_J1228_Open_det1.npz +0 -0
- pyreduce/wavecal/crires_plus_J1228_Open_det2.npz +0 -0
- pyreduce/wavecal/crires_plus_J1228_Open_det3.npz +0 -0
- pyreduce/wavecal/harpn_harpn_2D.npz +0 -0
- pyreduce/wavecal/harps_blue_2D.npz +0 -0
- pyreduce/wavecal/harps_blue_pol_2D.npz +0 -0
- pyreduce/wavecal/harps_red_2D.npz +0 -0
- pyreduce/wavecal/harps_red_pol_2D.npz +0 -0
- pyreduce/wavecal/mcdonald.npz +0 -0
- pyreduce/wavecal/metis_lss_l_2D.npz +0 -0
- pyreduce/wavecal/metis_lss_m_2D.npz +0 -0
- pyreduce/wavecal/nirspec_K2.npz +0 -0
- pyreduce/wavecal/uves_blue_360nm_2D.npz +0 -0
- pyreduce/wavecal/uves_blue_390nm_2D.npz +0 -0
- pyreduce/wavecal/uves_blue_437nm_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_2x2_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_565nm_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_580nm_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_600nm_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_665nm_2D.npz +0 -0
- pyreduce/wavecal/uves_middle_860nm_2D.npz +0 -0
- pyreduce/wavecal/uves_red_580nm_2D.npz +0 -0
- pyreduce/wavecal/uves_red_600nm_2D.npz +0 -0
- pyreduce/wavecal/uves_red_665nm_2D.npz +0 -0
- pyreduce/wavecal/uves_red_760nm_2D.npz +0 -0
- pyreduce/wavecal/uves_red_860nm_2D.npz +0 -0
- pyreduce/wavecal/xshooter_nir.npz +0 -0
- pyreduce/wavelength_calibration.py +1871 -0
- pyreduce_astro-0.7a4.dist-info/METADATA +106 -0
- pyreduce_astro-0.7a4.dist-info/RECORD +182 -0
- pyreduce_astro-0.7a4.dist-info/WHEEL +4 -0
- pyreduce_astro-0.7a4.dist-info/entry_points.txt +2 -0
- pyreduce_astro-0.7a4.dist-info/licenses/LICENSE +674 -0
pyreduce/__init__.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
+
except ImportError: # for Python<3.8
|
|
4
|
+
from importlib_metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
__version__ = version("pyreduce-astro")
|
|
8
|
+
except PackageNotFoundError:
|
|
9
|
+
__version__ = "unknown"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# add logger to console
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
import tqdm
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# We need to use this to have logging messages handle properly with the progressbar
|
|
19
|
+
class TqdmLoggingHandler(logging.Handler):
|
|
20
|
+
def __init__(self, level=logging.NOTSET):
|
|
21
|
+
super().__init__(level)
|
|
22
|
+
|
|
23
|
+
def emit(self, record):
|
|
24
|
+
try:
|
|
25
|
+
msg = self.format(record)
|
|
26
|
+
tqdm.tqdm.write(msg)
|
|
27
|
+
self.flush()
|
|
28
|
+
except (KeyboardInterrupt, SystemExit):
|
|
29
|
+
raise
|
|
30
|
+
except:
|
|
31
|
+
self.handleError(record)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
logger.setLevel(logging.DEBUG)
|
|
36
|
+
logging.captureWarnings(True)
|
|
37
|
+
|
|
38
|
+
console = TqdmLoggingHandler()
|
|
39
|
+
console.setLevel(logging.INFO)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
import colorlog
|
|
43
|
+
|
|
44
|
+
console.setFormatter(
|
|
45
|
+
colorlog.ColoredFormatter("%(log_color)s%(levelname)s - %(message)s")
|
|
46
|
+
)
|
|
47
|
+
del colorlog
|
|
48
|
+
except ImportError:
|
|
49
|
+
console.setFormatter("%(levelname)s - %(message)s")
|
|
50
|
+
print("Install colorlog for colored logging output")
|
|
51
|
+
|
|
52
|
+
logger.addHandler(console)
|
|
53
|
+
|
|
54
|
+
del logging
|
|
55
|
+
# do not del tqdm, it is needed in the Log Handler
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Lazy loading for faster imports
|
|
59
|
+
def __getattr__(name):
|
|
60
|
+
"""Lazy load submodules on first access."""
|
|
61
|
+
if name in ("configuration", "datasets", "reduce", "util"):
|
|
62
|
+
import importlib
|
|
63
|
+
|
|
64
|
+
module = importlib.import_module(f".{name}", __name__)
|
|
65
|
+
globals()[name] = module
|
|
66
|
+
return module
|
|
67
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
pyreduce/__main__.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
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
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
ALL_STEPS = (
|
|
15
|
+
"bias",
|
|
16
|
+
"flat",
|
|
17
|
+
"orders",
|
|
18
|
+
"curvature",
|
|
19
|
+
"scatter",
|
|
20
|
+
"norm_flat",
|
|
21
|
+
"wavecal_master",
|
|
22
|
+
"wavecal_init",
|
|
23
|
+
"wavecal",
|
|
24
|
+
"freq_comb_master",
|
|
25
|
+
"freq_comb",
|
|
26
|
+
"science",
|
|
27
|
+
"continuum",
|
|
28
|
+
"finalize",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@click.group()
|
|
33
|
+
@click.version_option(package_name="pyreduce-astro")
|
|
34
|
+
def cli():
|
|
35
|
+
"""PyReduce - Echelle spectrograph data reduction pipeline."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@cli.command()
|
|
40
|
+
@click.argument("instrument")
|
|
41
|
+
@click.argument("target")
|
|
42
|
+
@click.option("--night", "-n", default=None, help="Observation night (YYYY-MM-DD)")
|
|
43
|
+
@click.option("--arm", "-a", default=None, help="Instrument arm/detector")
|
|
44
|
+
@click.option(
|
|
45
|
+
"--steps",
|
|
46
|
+
"-s",
|
|
47
|
+
default=None,
|
|
48
|
+
help="Comma-separated steps to run (default: all)",
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--base-dir",
|
|
52
|
+
"-b",
|
|
53
|
+
default=None,
|
|
54
|
+
help="Base directory for data (default: $REDUCE_DATA or ~/REDUCE_DATA)",
|
|
55
|
+
)
|
|
56
|
+
@click.option(
|
|
57
|
+
"--input-dir", "-i", default="raw", help="Input directory relative to base"
|
|
58
|
+
)
|
|
59
|
+
@click.option(
|
|
60
|
+
"--output-dir", "-o", default="reduced", help="Output directory relative to base"
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
"--plot", "-p", default=0, help="Plot level (0=none, 1=save, 2=interactive)"
|
|
64
|
+
)
|
|
65
|
+
@click.option(
|
|
66
|
+
"--order-range",
|
|
67
|
+
default=None,
|
|
68
|
+
help="Order range to process (e.g., '1,21')",
|
|
69
|
+
)
|
|
70
|
+
def run(
|
|
71
|
+
instrument,
|
|
72
|
+
target,
|
|
73
|
+
night,
|
|
74
|
+
arm,
|
|
75
|
+
steps,
|
|
76
|
+
base_dir,
|
|
77
|
+
input_dir,
|
|
78
|
+
output_dir,
|
|
79
|
+
plot,
|
|
80
|
+
order_range,
|
|
81
|
+
):
|
|
82
|
+
"""Run the reduction pipeline.
|
|
83
|
+
|
|
84
|
+
INSTRUMENT: Name of the instrument (e.g., UVES, HARPS, XSHOOTER)
|
|
85
|
+
TARGET: Target star name or regex pattern
|
|
86
|
+
"""
|
|
87
|
+
from .configuration import get_configuration_for_instrument
|
|
88
|
+
from .reduce import main as reduce_main
|
|
89
|
+
|
|
90
|
+
# Parse steps
|
|
91
|
+
if steps:
|
|
92
|
+
steps = tuple(s.strip() for s in steps.split(","))
|
|
93
|
+
else:
|
|
94
|
+
steps = "all"
|
|
95
|
+
|
|
96
|
+
# Parse order range
|
|
97
|
+
if order_range:
|
|
98
|
+
parts = order_range.split(",")
|
|
99
|
+
order_range = (int(parts[0]), int(parts[1]))
|
|
100
|
+
|
|
101
|
+
# Load configuration
|
|
102
|
+
config = get_configuration_for_instrument(instrument)
|
|
103
|
+
|
|
104
|
+
# Run reduction
|
|
105
|
+
reduce_main(
|
|
106
|
+
instrument=instrument,
|
|
107
|
+
target=target,
|
|
108
|
+
night=night,
|
|
109
|
+
arms=arm,
|
|
110
|
+
steps=steps,
|
|
111
|
+
base_dir=base_dir or "",
|
|
112
|
+
input_dir=input_dir,
|
|
113
|
+
output_dir=output_dir,
|
|
114
|
+
configuration=config,
|
|
115
|
+
order_range=order_range,
|
|
116
|
+
plot=plot,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@cli.command()
|
|
121
|
+
@click.argument("files", nargs=-1, required=True)
|
|
122
|
+
@click.option("--output", "-o", default="combined.fits", help="Output filename")
|
|
123
|
+
@click.option("--plot", "-p", default=None, type=int, help="Plot specific order")
|
|
124
|
+
def combine(files, output, plot):
|
|
125
|
+
"""Combine multiple reduced spectra.
|
|
126
|
+
|
|
127
|
+
FILES: Input .final.fits files to combine
|
|
128
|
+
"""
|
|
129
|
+
from .tools.combine import combine as tools_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
|
+
from . import datasets
|
|
142
|
+
|
|
143
|
+
instrument = instrument.upper()
|
|
144
|
+
dataset_func = getattr(datasets, instrument, None)
|
|
145
|
+
if dataset_func is None:
|
|
146
|
+
available = [
|
|
147
|
+
name
|
|
148
|
+
for name in dir(datasets)
|
|
149
|
+
if name.isupper() and not name.startswith("_")
|
|
150
|
+
]
|
|
151
|
+
raise click.ClickException(
|
|
152
|
+
f"Unknown instrument '{instrument}'. Available: {', '.join(available)}"
|
|
153
|
+
)
|
|
154
|
+
path = dataset_func()
|
|
155
|
+
click.echo(f"Dataset downloaded to: {path}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@cli.command()
|
|
159
|
+
@click.argument("filename", required=False)
|
|
160
|
+
@click.option(
|
|
161
|
+
"--list", "-l", "list_examples", is_flag=True, help="List available examples"
|
|
162
|
+
)
|
|
163
|
+
@click.option("--all", "-a", "download_all", is_flag=True, help="Download all examples")
|
|
164
|
+
@click.option("--run", "-r", is_flag=True, help="Run the example after downloading")
|
|
165
|
+
@click.option("--output", "-o", default=".", help="Output directory")
|
|
166
|
+
def examples(filename, list_examples, download_all, run, output):
|
|
167
|
+
"""List, download, or run example scripts from GitHub.
|
|
168
|
+
|
|
169
|
+
Downloads examples matching your installed PyReduce version.
|
|
170
|
+
|
|
171
|
+
\b
|
|
172
|
+
Examples:
|
|
173
|
+
reduce examples # List available examples
|
|
174
|
+
reduce examples uves_example.py # Download to current dir
|
|
175
|
+
reduce examples -r uves_example.py # Download and run
|
|
176
|
+
reduce examples --all -o ~/scripts # Download all to ~/scripts
|
|
177
|
+
"""
|
|
178
|
+
import json
|
|
179
|
+
import os
|
|
180
|
+
import subprocess
|
|
181
|
+
import sys
|
|
182
|
+
import tempfile
|
|
183
|
+
import urllib.request
|
|
184
|
+
from urllib.error import HTTPError
|
|
185
|
+
|
|
186
|
+
from pyreduce import __version__
|
|
187
|
+
|
|
188
|
+
version = __version__.split("+")[0]
|
|
189
|
+
if version == "unknown":
|
|
190
|
+
raise click.ClickException(
|
|
191
|
+
"Cannot determine package version. Install from PyPI or a tagged release."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
github_api = (
|
|
195
|
+
f"https://api.github.com/repos/ivh/PyReduce/contents/examples?ref=v{version}"
|
|
196
|
+
)
|
|
197
|
+
github_raw = f"https://raw.githubusercontent.com/ivh/PyReduce/v{version}/examples"
|
|
198
|
+
|
|
199
|
+
# Fetch list of examples from GitHub API
|
|
200
|
+
try:
|
|
201
|
+
with urllib.request.urlopen(github_api) as resp:
|
|
202
|
+
contents = json.loads(resp.read().decode())
|
|
203
|
+
except HTTPError as e:
|
|
204
|
+
if e.code == 404:
|
|
205
|
+
raise click.ClickException(
|
|
206
|
+
f"Tag v{version} not found on GitHub. "
|
|
207
|
+
"Try installing a released version."
|
|
208
|
+
) from None
|
|
209
|
+
raise click.ClickException(f"GitHub API error: {e}") from None
|
|
210
|
+
|
|
211
|
+
example_files = sorted(f["name"] for f in contents if f["name"].endswith(".py"))
|
|
212
|
+
|
|
213
|
+
# List mode
|
|
214
|
+
if list_examples or (not filename and not download_all):
|
|
215
|
+
click.echo(f"Available examples for v{version}:")
|
|
216
|
+
for name in example_files:
|
|
217
|
+
click.echo(f" {name}")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
if run and download_all:
|
|
221
|
+
raise click.ClickException("Cannot use --run with --all")
|
|
222
|
+
|
|
223
|
+
# Run mode: download to temp and execute
|
|
224
|
+
if run:
|
|
225
|
+
if filename not in example_files:
|
|
226
|
+
raise click.ClickException(
|
|
227
|
+
f"Unknown example '{filename}'. Use 'reduce examples --list' to see available."
|
|
228
|
+
)
|
|
229
|
+
url = f"{github_raw}/{filename}"
|
|
230
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
231
|
+
try:
|
|
232
|
+
with urllib.request.urlopen(url) as resp:
|
|
233
|
+
f.write(resp.read().decode())
|
|
234
|
+
temp_path = f.name
|
|
235
|
+
except HTTPError as e:
|
|
236
|
+
raise click.ClickException(
|
|
237
|
+
f"Failed to download {filename}: {e}"
|
|
238
|
+
) from None
|
|
239
|
+
try:
|
|
240
|
+
click.echo(f"Running {filename}...")
|
|
241
|
+
result = subprocess.run([sys.executable, temp_path], check=False)
|
|
242
|
+
sys.exit(result.returncode)
|
|
243
|
+
finally:
|
|
244
|
+
os.unlink(temp_path)
|
|
245
|
+
|
|
246
|
+
# Ensure output directory exists
|
|
247
|
+
os.makedirs(output, exist_ok=True)
|
|
248
|
+
|
|
249
|
+
def download_file(name):
|
|
250
|
+
url = f"{github_raw}/{name}"
|
|
251
|
+
dest = os.path.join(output, name)
|
|
252
|
+
try:
|
|
253
|
+
urllib.request.urlretrieve(url, dest)
|
|
254
|
+
click.echo(f"Downloaded: {dest}")
|
|
255
|
+
except HTTPError as e:
|
|
256
|
+
click.echo(f"Failed to download {name}: {e}", err=True)
|
|
257
|
+
|
|
258
|
+
if download_all:
|
|
259
|
+
for name in example_files:
|
|
260
|
+
download_file(name)
|
|
261
|
+
else:
|
|
262
|
+
if filename not in example_files:
|
|
263
|
+
raise click.ClickException(
|
|
264
|
+
f"Unknown example '{filename}'. Use 'reduce examples --list' to see available."
|
|
265
|
+
)
|
|
266
|
+
download_file(filename)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@cli.command("list-steps")
|
|
270
|
+
def list_steps():
|
|
271
|
+
"""List all available reduction steps."""
|
|
272
|
+
click.echo("Available reduction steps:")
|
|
273
|
+
for step in ALL_STEPS:
|
|
274
|
+
click.echo(f" - {step}")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def make_step_command(step_name):
|
|
278
|
+
"""Factory to create a command for a single step."""
|
|
279
|
+
|
|
280
|
+
@click.command(name=step_name)
|
|
281
|
+
@click.argument("instrument")
|
|
282
|
+
@click.argument("target")
|
|
283
|
+
@click.option("--night", "-n", default=None, help="Observation night")
|
|
284
|
+
@click.option("--arm", "-a", default=None, help="Instrument arm")
|
|
285
|
+
@click.option("--base-dir", "-b", default=None, help="Base directory")
|
|
286
|
+
@click.option("--input-dir", "-i", default="raw", help="Input directory")
|
|
287
|
+
@click.option("--output-dir", "-o", default="reduced", help="Output directory")
|
|
288
|
+
@click.option("--plot", "-p", default=0, help="Plot level")
|
|
289
|
+
def cmd(instrument, target, night, arm, base_dir, input_dir, output_dir, plot):
|
|
290
|
+
from .configuration import get_configuration_for_instrument
|
|
291
|
+
from .reduce import main as reduce_main
|
|
292
|
+
|
|
293
|
+
config = get_configuration_for_instrument(instrument)
|
|
294
|
+
reduce_main(
|
|
295
|
+
instrument=instrument,
|
|
296
|
+
target=target,
|
|
297
|
+
night=night,
|
|
298
|
+
arms=arm,
|
|
299
|
+
steps=(step_name,),
|
|
300
|
+
base_dir=base_dir or "",
|
|
301
|
+
input_dir=input_dir,
|
|
302
|
+
output_dir=output_dir,
|
|
303
|
+
configuration=config,
|
|
304
|
+
plot=plot,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
cmd.__doc__ = f"Run the '{step_name}' step."
|
|
308
|
+
return cmd
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# Register individual step commands
|
|
312
|
+
for _step in ALL_STEPS:
|
|
313
|
+
cli.add_command(make_step_command(_step))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def main():
|
|
317
|
+
"""Entry point for the CLI."""
|
|
318
|
+
cli()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
main()
|