Coverage for lunar_toolkit/cli/stereo.py: 56%
39 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 02:58 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 02:58 +0000
1"""stereo - DEM from one LROC NAC stereo pair (ISIS + ASP)."""
3import argparse
4import sys
5from pathlib import Path
7COMMAND = "stereo"
10def add_parser(sub: argparse._SubParsersAction) -> None:
11 p = sub.add_parser(
12 COMMAND,
13 help="Build a DEM from a NAC stereo pair (bundle_adjust, parallel_stereo, point2dem).",
14 formatter_class=argparse.RawDescriptionHelpFormatter,
15 epilog="""
16Examples
17--------
18 lunar stereo M181058717LE M181073012LE --res 2 --work-dir pair1
19 lunar stereo L.IMG R.IMG --dem ldem.tif --res 2 # mapproject first, align to the DEM
20 lunar stereo L.cub R.cub --dry-run
22Needs ISIS and ASP on PATH (or ASP_ROOT / ISISROOT).
23""",
24 )
25 p.add_argument("left", help="Left image: NAC EDR product ID, .IMG or .cub path.")
26 p.add_argument("right", help="Right image: NAC EDR product ID, .IMG or .cub path.")
27 p.add_argument("--work-dir", default="stereo_work", metavar="DIR")
28 p.add_argument("--res", type=float, default=2.0, metavar="M", help="DEM m/px (default: 2).")
29 p.add_argument("--dem", default=None, metavar="TIF", help="Prior DEM to mapproject onto and align to.")
30 p.add_argument(
31 "--image-res",
32 type=float,
33 default=1.0,
34 metavar="M",
35 help="Mapprojected image m/px when --dem is set (default: 1).",
36 )
37 p.add_argument("--no-align", action="store_true", help="Skip pc_align to --dem.")
38 p.add_argument("--max-displacement", type=float, default=200.0, metavar="M")
39 p.add_argument("--no-csm", action="store_true", help="Use ISIS cameras instead of CSM.")
40 p.add_argument("--local-spice", action="store_true")
41 p.add_argument("--processes", type=int, default=None, metavar="N")
42 p.add_argument("--threads", type=int, default=None, metavar="N")
43 p.add_argument(
44 "--stereo-opt",
45 action="append",
46 metavar="KEY=VALUE",
47 help="Extra / overriding parallel_stereo option (repeatable).",
48 )
49 p.add_argument("--force", action="store_true")
50 p.add_argument("--dry-run", action="store_true", help="Print the commands without running them.")
51 p.add_argument("--data-dir", type=Path, default=None, metavar="DIR")
54def run(args: argparse.Namespace) -> int:
55 from ..stereo_dem import stereo_dem
56 from ..tools import ToolError
57 from ._utils import parse_options, print_dry_run, resolve_images
59 work_dir = Path(args.work_dir)
60 left, right = resolve_images(
61 [args.left, args.right], work_dir / "raw", args.data_dir, download=not args.dry_run
62 )
63 kwargs = dict(
64 resolution=args.res,
65 prior_dem=Path(args.dem) if args.dem else None,
66 image_resolution=args.image_res,
67 align=not args.no_align,
68 max_displacement=args.max_displacement,
69 csm=not args.no_csm,
70 web_spice=not args.local_spice,
71 processes=args.processes,
72 threads=args.threads,
73 stereo_options=parse_options(args.stereo_opt),
74 force=args.force,
75 )
76 try:
77 if args.dry_run:
78 print_dry_run(lambda: stereo_dem(left, right, work_dir, **kwargs))
79 return 0
80 dem = stereo_dem(left, right, work_dir, **kwargs)
81 except ToolError as exc:
82 print(exc, file=sys.stderr)
83 return 1
84 print(f"Stereo DEM -> {dem}")
85 return 0