Coverage for lunar_toolkit/cli/process.py: 25%
55 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"""process - download and run the ISIS + GDAL pipeline on LROC NAC images."""
3import argparse
4import sys
5from pathlib import Path
7from ..constants import DEFAULT_PROJECTION
9COMMAND = "process"
12def add_parser(sub: argparse._SubParsersAction) -> None:
13 p = sub.add_parser(
14 COMMAND,
15 help="Run the ISIS + GDAL pipeline on LROC NAC images.",
16 formatter_class=argparse.RawDescriptionHelpFormatter,
17 epilog="""
18Examples
19--------
20 lunar process M1096757719LE
21 lunar process M1096757719LE M1096757719RE --workers 2
22 lunar process --from-file ids.txt --output-dir ./output
23 lunar process /data/M1096757719LE.IMG --keep --resolution 1
24 lunar process https://pds.lroc.im-ldi.com/data/.../M1096757719LE.IMG
26Everything is written to --output-dir: the GeoTIFF <id>.tif (map-projected I/F,
27DEFLATE-compressed) plus intermediates (<id>.IMG, <id>.cal.echo.cub, <id>.map.cub),
28which are removed after success unless --keep. Steps whose output exists are
29skipped, so re-running resumes (use --force to redo them).
30""",
31 )
32 p.add_argument(
33 "sources",
34 nargs="*",
35 metavar="SOURCE",
36 help="Product IDs (e.g. M1096757719LE), local .IMG paths, or PDS URLs.",
37 )
38 p.add_argument(
39 "--from-file",
40 metavar="FILE",
41 help="Text file of sources, one per line (#comments ignored).",
42 )
43 p.add_argument(
44 "--output-dir",
45 default=None,
46 metavar="DIR",
47 help="Output directory (default: current working directory).",
48 )
49 p.add_argument(
50 "--keep",
51 action="store_true",
52 help="Keep the downloaded EDR and the intermediate ISIS cubes.",
53 )
54 p.add_argument("--force", action="store_true", help="Redo steps whose output exists.")
55 p.add_argument(
56 "--workers",
57 type=int,
58 default=1,
59 metavar="N",
60 help="Parallel workers for batch processing (default: 1).",
61 )
62 p.add_argument(
63 "--projection",
64 default=DEFAULT_PROJECTION,
65 metavar="PROJ",
66 help=f"ISIS map projection for mosrange (default: {DEFAULT_PROJECTION}).",
67 )
68 p.add_argument("--resolution", type=float, default=None, metavar="M", help="Map resolution in m/pixel.")
69 p.add_argument(
70 "--no-web",
71 dest="web",
72 action="store_false",
73 help="Use local SPICE kernels in spiceinit instead of the USGS web service.",
74 )
75 p.add_argument(
76 "--data-dir",
77 type=Path,
78 default=None,
79 metavar="DIR",
80 help="Override Parquet data directory.",
81 )
84def run(args: argparse.Namespace) -> int:
85 from ..lroc.nac import NacImageCollection, NacProcessor
86 from ._utils import load_sources
88 sources = load_sources(args)
89 if not sources:
90 print("No sources provided. Pass product IDs, paths, URLs, or --from-file.", file=sys.stderr)
91 return 1
93 output_dir = Path(args.output_dir) if args.output_dir else Path.cwd()
94 output_dir.mkdir(parents=True, exist_ok=True)
96 # Route: product IDs -> NacImageCollection; existing paths / URLs -> processor directly
97 direct_sources: list[str] = []
98 id_sources: list[str] = []
99 for s in sources:
100 s = s.strip()
101 if s.startswith(("http://", "https://")) or Path(s).exists():
102 direct_sources.append(s)
103 else:
104 id_sources.append(s)
106 opts = {
107 "projection": args.projection,
108 "resolution": args.resolution,
109 "web": args.web,
110 "keep": args.keep,
111 "force": args.force,
112 }
113 ok = total = 0
115 if id_sources:
116 collection = NacImageCollection.from_sources(id_sources, data_dir=args.data_dir)
117 results = collection.process(output_dir, workers=args.workers, **opts)
118 for pid, tif in results.items():
119 total += 1
120 if tif:
121 print(f" {pid} -> {tif}")
122 ok += 1
123 else:
124 print(f" {pid} - FAILED", file=sys.stderr)
126 if direct_sources:
127 proc = NacProcessor(output_dir, **opts)
128 tifs = proc.process_batch(direct_sources, max_workers=args.workers)
129 for src, tif in zip(direct_sources, tifs):
130 total += 1
131 label = Path(src).name if not src.startswith("http") else src
132 if tif:
133 print(f" {label} -> {tif}")
134 ok += 1
135 else:
136 print(f" {label} - FAILED", file=sys.stderr)
138 return 0 if ok == total else 1