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

1"""process - download and run the ISIS + GDAL pipeline on LROC NAC images.""" 

2 

3import argparse 

4import sys 

5from pathlib import Path 

6 

7from ..constants import DEFAULT_PROJECTION 

8 

9COMMAND = "process" 

10 

11 

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 

25 

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 ) 

82 

83 

84def run(args: argparse.Namespace) -> int: 

85 from ..lroc.nac import NacImageCollection, NacProcessor 

86 from ._utils import load_sources 

87 

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 

92 

93 output_dir = Path(args.output_dir) if args.output_dir else Path.cwd() 

94 output_dir.mkdir(parents=True, exist_ok=True) 

95 

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) 

105 

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 

114 

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) 

125 

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) 

137 

138 return 0 if ok == total else 1