makewfs 1.0.0__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.
- makewfs/__about__.py +3 -0
- makewfs/__init__.py +15 -0
- makewfs/api.py +210 -0
- makewfs/backend.py +396 -0
- makewfs/cli.py +99 -0
- makewfs/config.py +830 -0
- makewfs/detector.py +116 -0
- makewfs/provenance.py +109 -0
- makewfs/pupil.py +112 -0
- makewfs/py.typed +0 -0
- makewfs/radiometry.py +34 -0
- makewfs/sampling.py +164 -0
- makewfs/sensors/__init__.py +7 -0
- makewfs/sensors/base.py +46 -0
- makewfs/sensors/pyramid.py +215 -0
- makewfs/sensors/shack_hartmann.py +315 -0
- makewfs/source.py +169 -0
- makewfs/wavefront.py +204 -0
- makewfs-1.0.0.dist-info/METADATA +133 -0
- makewfs-1.0.0.dist-info/RECORD +23 -0
- makewfs-1.0.0.dist-info/WHEEL +4 -0
- makewfs-1.0.0.dist-info/entry_points.txt +2 -0
- makewfs-1.0.0.dist-info/licenses/LICENSE +21 -0
makewfs/cli.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Command-line interface for config validation and phase rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from numpy.typing import NDArray
|
|
12
|
+
|
|
13
|
+
from .__about__ import __version__
|
|
14
|
+
from .api import WavefrontSensor
|
|
15
|
+
from .config import load_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_array(path: str | Path) -> NDArray[Any]:
|
|
19
|
+
source = Path(path)
|
|
20
|
+
if source.suffix.lower() == ".npy":
|
|
21
|
+
return np.asarray(np.load(source))
|
|
22
|
+
if source.suffix.lower() == ".npz":
|
|
23
|
+
archive = np.load(source)
|
|
24
|
+
if not archive.files:
|
|
25
|
+
raise ValueError(f"phase archive {source} contains no arrays")
|
|
26
|
+
return np.asarray(archive[archive.files[0]])
|
|
27
|
+
if source.suffix.lower() in {".fits", ".fit"}:
|
|
28
|
+
try:
|
|
29
|
+
from astropy.io import fits # type: ignore[import-untyped]
|
|
30
|
+
except ImportError as exc: # pragma: no cover
|
|
31
|
+
raise ImportError("FITS input requires astropy") from exc
|
|
32
|
+
return np.asarray(fits.getdata(source))
|
|
33
|
+
raise ValueError(f"unsupported array format {source.suffix!r}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _write_array(path: str | Path, array: NDArray[Any]) -> None:
|
|
37
|
+
destination = Path(path)
|
|
38
|
+
if destination.suffix.lower() == ".npy":
|
|
39
|
+
np.save(destination, array)
|
|
40
|
+
elif destination.suffix.lower() == ".npz":
|
|
41
|
+
np.savez_compressed(destination, data=array)
|
|
42
|
+
elif destination.suffix.lower() in {".fits", ".fit"}:
|
|
43
|
+
try:
|
|
44
|
+
from astropy.io import fits
|
|
45
|
+
except ImportError as exc: # pragma: no cover
|
|
46
|
+
raise ImportError("FITS output requires astropy") from exc
|
|
47
|
+
fits.writeto(destination, array, overwrite=True)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(f"unsupported output format {destination.suffix!r}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
53
|
+
parser = argparse.ArgumentParser(
|
|
54
|
+
prog="makewfs", description="Render configured AO WFS images from phase/OPD arrays."
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument("--version", action="version", version=f"makewfs {__version__}")
|
|
57
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
58
|
+
validate = commands.add_parser("validate-config", help="validate a TOML configuration")
|
|
59
|
+
validate.add_argument("config")
|
|
60
|
+
render = commands.add_parser("render", help="render one detector frame")
|
|
61
|
+
render.add_argument("config")
|
|
62
|
+
render.add_argument("wavefront")
|
|
63
|
+
render.add_argument("--output", required=True)
|
|
64
|
+
render.add_argument("--seed", type=int, default=None)
|
|
65
|
+
ideal = commands.add_parser("ideal", help="write an ideal photon-rate map")
|
|
66
|
+
ideal.add_argument("config")
|
|
67
|
+
ideal.add_argument("wavefront")
|
|
68
|
+
ideal.add_argument("--output", required=True)
|
|
69
|
+
return parser
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main(argv: list[str] | None = None) -> int:
|
|
73
|
+
parser = build_parser()
|
|
74
|
+
args = parser.parse_args(argv)
|
|
75
|
+
try:
|
|
76
|
+
if args.command == "validate-config":
|
|
77
|
+
config = load_config(args.config)
|
|
78
|
+
print(
|
|
79
|
+
json.dumps({"valid": True, "digest": config.digest, "sensor": config.sensor.kind})
|
|
80
|
+
)
|
|
81
|
+
return 0
|
|
82
|
+
sensor = WavefrontSensor.from_toml(args.config)
|
|
83
|
+
wavefront = _load_array(args.wavefront)
|
|
84
|
+
if args.command == "ideal":
|
|
85
|
+
_write_array(args.output, sensor.photon_rate(wavefront))
|
|
86
|
+
else:
|
|
87
|
+
frame = sensor.expose(wavefront, seed=args.seed)
|
|
88
|
+
frame.to_fits(args.output, overwrite=True) if Path(args.output).suffix.lower() in {
|
|
89
|
+
".fits",
|
|
90
|
+
".fit",
|
|
91
|
+
} else _write_array(args.output, np.asarray(frame))
|
|
92
|
+
return 0
|
|
93
|
+
except (ValueError, KeyError, FileNotFoundError, ImportError) as exc:
|
|
94
|
+
parser.error(str(exc))
|
|
95
|
+
return 2
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__": # pragma: no cover
|
|
99
|
+
raise SystemExit(main())
|