lunar-toolkit 0.1.0__tar.gz → 0.1.0.dev1__tar.gz
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.
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/PKG-INFO +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/asp.py +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_jobs.py +4 -3
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_status.py +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_utils.py +2 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/serve.py +2 -2
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/config.py +8 -7
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/constants.py +2 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/downloader.py +10 -8
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/geometry.py +14 -10
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/isis.py +4 -3
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/mi.py +9 -7
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/tc.py +9 -7
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/cumindex.py +5 -4
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/nac.py +6 -5
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/observation.py +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/ode.py +8 -7
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/provenance.py +2 -2
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/reference.py +34 -30
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/selection.py +22 -17
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs.py +56 -42
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs_products.py +10 -9
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs_report.py +10 -7
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/shadowcam/sc.py +9 -6
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/stereo.py +11 -9
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/tools.py +3 -2
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/workspace.py +31 -24
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/PKG-INFO +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/pyproject.toml +1 -1
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/LICENSE +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/README.md +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/__init__.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/__init__.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/app.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/build_db.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/download.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/kaguya.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/ode_search.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/pairs.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/process.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/query.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/reference.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/search.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/sfs.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/sfs_report.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/shadowcam.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/stereo.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/workspace.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/csm.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/__init__.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/__init__.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/shadowcam/__init__.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_global.tif +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_north.tif +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_south.tif +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_global.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_north.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_south.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/manifest.json +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_global.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_north.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_south.jpg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/index.html +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/stereo_dem.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/SOURCES.txt +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/dependency_links.txt +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/entry_points.txt +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/requires.txt +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/top_level.txt +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/setup.cfg +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_aoi.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_app.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_build.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_config.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_csm.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_download.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_footprints.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_integration.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_kaguya.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_kaguya_mi.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_lbl.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_ode.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_reference.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_selection.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs_products.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs_report.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_shadowcam.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_static.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_stereo.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_tool_env.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_tools.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_update.py +0 -0
- {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lunar-toolkit
|
|
3
|
-
Version: 0.1.0
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
4
|
Summary: Find, download and process lunar orbital imagery (LROC, ShadowCam, Kaguya TC) and build stereo and shape-from-shading DEMs with ISIS + ASP
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
Keywords: moon,lroc,shadowcam,kaguya,isis,ames-stereo-pipeline,shape-from-shading,dem
|
|
@@ -51,7 +51,7 @@ def bundle_adjust(
|
|
|
51
51
|
onto become ``--mapprojected-data``, which finds matches between images of
|
|
52
52
|
very different illumination. Returns *out_prefix*.
|
|
53
53
|
|
|
54
|
-
Raises
|
|
54
|
+
Raises [`ToolError`][lunar_toolkit.tools.ToolError] when the run writes no fresh
|
|
55
55
|
``.adjust`` file for some image: ASP exits 0 even when it finds nothing to
|
|
56
56
|
optimize ("No points to optimize ... Cannot continue").
|
|
57
57
|
"""
|
|
@@ -66,7 +66,7 @@ class Job:
|
|
|
66
66
|
return self._cancel.is_set()
|
|
67
67
|
|
|
68
68
|
def check(self) -> None:
|
|
69
|
-
"""Raise
|
|
69
|
+
"""Raise `Cancelled` if the job was cancelled."""
|
|
70
70
|
if self._cancel.is_set():
|
|
71
71
|
raise Cancelled()
|
|
72
72
|
|
|
@@ -101,7 +101,8 @@ class Job:
|
|
|
101
101
|
) # fmt: skip
|
|
102
102
|
|
|
103
103
|
def on_bytes(self, name: str, done: int, total: int | None) -> None:
|
|
104
|
-
"""Download progress callback
|
|
104
|
+
"""Download progress callback
|
|
105
|
+
([`lunar_toolkit.downloader.Progress`][lunar_toolkit.downloader.Progress])."""
|
|
105
106
|
self.check()
|
|
106
107
|
if done < 0:
|
|
107
108
|
self.update(stage="Waiting: the host asked us to slow down", rate=None, eta=None)
|
|
@@ -213,7 +214,7 @@ class JobManager:
|
|
|
213
214
|
return job
|
|
214
215
|
|
|
215
216
|
def wait(self, version: int, timeout: float) -> int:
|
|
216
|
-
"""Block until
|
|
217
|
+
"""Block until `version` differs from *version* (or *timeout* / close); the new version."""
|
|
217
218
|
with self._cond:
|
|
218
219
|
self._cond.wait_for(lambda: self.version != version or self.closed, timeout)
|
|
219
220
|
return self.version
|
|
@@ -330,7 +330,7 @@ def redact(obj: Any) -> Any:
|
|
|
330
330
|
|
|
331
331
|
|
|
332
332
|
class StatusCache:
|
|
333
|
-
"""``/api/status`` documents, rebuilt at most every
|
|
333
|
+
"""``/api/status`` documents, rebuilt at most every `CACHE_SECONDS` (or on refresh)."""
|
|
334
334
|
|
|
335
335
|
def __init__(self, dirs: StatusDirs) -> None:
|
|
336
336
|
self.dirs = dirs
|
|
@@ -82,7 +82,8 @@ def region_args(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def ode_filters(args: argparse.Namespace) -> dict[str, Any]:
|
|
85
|
-
"""
|
|
85
|
+
"""[`OdeClient.search`][lunar_toolkit.ode.OdeClient.search] filters from ``--inc`` / ``--emi`` /
|
|
86
|
+
``--phase`` / ``--time``."""
|
|
86
87
|
return {
|
|
87
88
|
"incidence": tuple(args.inc) if args.inc else None,
|
|
88
89
|
"emission": tuple(args.emi) if args.emi else None,
|
|
@@ -238,7 +238,7 @@ def _local_host_header(host: str) -> bool:
|
|
|
238
238
|
|
|
239
239
|
@dataclass
|
|
240
240
|
class ServerConfig:
|
|
241
|
-
"""What a
|
|
241
|
+
"""What a `LunarServer` serves and where its jobs write."""
|
|
242
242
|
|
|
243
243
|
data_dir: Path
|
|
244
244
|
explicit_dir: Path | None = None
|
|
@@ -651,7 +651,7 @@ def _regions() -> dict[str, Any]:
|
|
|
651
651
|
|
|
652
652
|
|
|
653
653
|
def make_server(args: argparse.Namespace) -> tuple[LunarServer, ServerConfig]:
|
|
654
|
-
"""Bind a
|
|
654
|
+
"""Bind a `LunarServer` from the shared options (ValueError for a bad option)."""
|
|
655
655
|
import os
|
|
656
656
|
|
|
657
657
|
from ..config import RUNS_DIR_ENV
|
|
@@ -35,9 +35,10 @@ class ConfigError(ValueError):
|
|
|
35
35
|
|
|
36
36
|
@dataclass
|
|
37
37
|
class RunConfig:
|
|
38
|
-
"""Everything a ``lunar sfs`` run needs: inputs, region and the
|
|
38
|
+
"""Everything a ``lunar sfs`` run needs: inputs, region and the
|
|
39
|
+
[`SfsConfig`][lunar_toolkit.sfs.SfsConfig].
|
|
39
40
|
|
|
40
|
-
In YAML the run keys below and the
|
|
41
|
+
In YAML the run keys below and the [`SfsConfig`][lunar_toolkit.sfs.SfsConfig] fields share one flat
|
|
41
42
|
mapping. Relative paths are relative to the current directory.
|
|
42
43
|
|
|
43
44
|
Parameters
|
|
@@ -52,11 +53,11 @@ class RunConfig:
|
|
|
52
53
|
Prior DEM path, or ``"pgda"`` for the region's PGDA LOLA 5 m DEM (the
|
|
53
54
|
default with a *region*).
|
|
54
55
|
region : str or int, optional
|
|
55
|
-
Artemis III region (
|
|
56
|
+
Artemis III region ([`lunar_toolkit.reference.get_region`][lunar_toolkit.reference.get_region]): sets
|
|
56
57
|
the ``projwin`` (its ROI, or a *crop_km* window), ``region_id`` and the
|
|
57
58
|
image search AOI.
|
|
58
59
|
crop_km : float, optional
|
|
59
|
-
Side of a square window at the region centre (
|
|
60
|
+
Side of a square window at the region centre ([`crop_window`][lunar_toolkit.reference.crop_window]).
|
|
60
61
|
crop_center : (lat, lon), optional
|
|
61
62
|
Centre of that window (default: the ROI centre).
|
|
62
63
|
aoi : str, optional
|
|
@@ -71,7 +72,7 @@ class RunConfig:
|
|
|
71
72
|
stereo convergence for registration, and ``max_sfs_slew`` keeps them out of SfS.
|
|
72
73
|
max_images : int, optional
|
|
73
74
|
Cap on the searched images before download (stereo pairs + illumination coverage;
|
|
74
|
-
|
|
75
|
+
[`lunar_toolkit.sfs.cap_candidates`][lunar_toolkit.sfs.cap_candidates]).
|
|
75
76
|
pgda_dir : Path, optional
|
|
76
77
|
Where PGDA files are downloaded (default: the reference cache).
|
|
77
78
|
data_dir : Path, optional
|
|
@@ -157,7 +158,7 @@ def _coerce(key: str, value: Any, hint: Any) -> Any:
|
|
|
157
158
|
|
|
158
159
|
|
|
159
160
|
def from_dict(data: Mapping[str, Any], source: str = "config") -> RunConfig:
|
|
160
|
-
"""A validated
|
|
161
|
+
"""A validated [`RunConfig`][lunar_toolkit.config.RunConfig] from a flat mapping (keys as in `KEYS`).
|
|
161
162
|
|
|
162
163
|
Raises
|
|
163
164
|
------
|
|
@@ -245,7 +246,7 @@ def dump_config(cfg: RunConfig, path: Path | None = None) -> str:
|
|
|
245
246
|
|
|
246
247
|
|
|
247
248
|
def config_hash(cfg: RunConfig) -> str:
|
|
248
|
-
"""SHA-256 of the settings (canonical JSON of
|
|
249
|
+
"""SHA-256 of the settings (canonical JSON of [`to_dict`][lunar_toolkit.config.to_dict])."""
|
|
249
250
|
return hashlib.sha256(json.dumps(to_dict(cfg), sort_keys=True).encode()).hexdigest()
|
|
250
251
|
|
|
251
252
|
|
|
@@ -145,7 +145,8 @@ def get_data_dir() -> Path:
|
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
def find_parquet(name: str, data_dir: Path | None = None) -> Path:
|
|
148
|
-
"""Path of Parquet database *name* in *data_dir* (default:
|
|
148
|
+
"""Path of Parquet database *name* in *data_dir* (default:
|
|
149
|
+
[`get_data_dir`][lunar_toolkit.constants.get_data_dir])."""
|
|
149
150
|
return Path(data_dir or get_data_dir()) / name
|
|
150
151
|
|
|
151
152
|
|
|
@@ -37,7 +37,8 @@ _PROGRESS: contextvars.ContextVar[Progress | None] = contextvars.ContextVar("dow
|
|
|
37
37
|
|
|
38
38
|
@contextlib.contextmanager
|
|
39
39
|
def report_progress(callback: Progress) -> Iterator[None]:
|
|
40
|
-
"""Send the progress of every
|
|
40
|
+
"""Send the progress of every [`download_file`][lunar_toolkit.downloader.download_file] in this thread /
|
|
41
|
+
context to *callback*."""
|
|
41
42
|
token = _PROGRESS.set(callback)
|
|
42
43
|
try:
|
|
43
44
|
yield
|
|
@@ -127,8 +128,9 @@ class PdsDownloader:
|
|
|
127
128
|
) -> dict[str, Path | None]:
|
|
128
129
|
"""Download a plain list of URLs in parallel.
|
|
129
130
|
|
|
130
|
-
Existing files are reused unless *force* (see
|
|
131
|
-
Returns a dict mapping each URL to its
|
|
131
|
+
Existing files are reused unless *force* (see
|
|
132
|
+
[`download`][lunar_toolkit.downloader.PdsDownloader.download]). Returns a dict mapping each URL to its
|
|
133
|
+
local path (or None on error).
|
|
132
134
|
"""
|
|
133
135
|
path_map: dict[str, Path | None] = {}
|
|
134
136
|
for url, (path, err) in self._download_all(urls, output_dir, show_progress, force).items():
|
|
@@ -189,7 +191,7 @@ class _Host:
|
|
|
189
191
|
"""One host's download slots, request pacing and throttling cooldown.
|
|
190
192
|
|
|
191
193
|
The cooldown is wall-clock time shared by every worker in this process and saved
|
|
192
|
-
to
|
|
194
|
+
to `COOLDOWN_FILE`, so a restarted run honours a server's earlier
|
|
193
195
|
``Retry-After`` instead of tripping its rate limit again.
|
|
194
196
|
"""
|
|
195
197
|
|
|
@@ -286,10 +288,10 @@ def download_file(
|
|
|
286
288
|
count as failures; they give up only after *max_wait* seconds in total (default
|
|
287
289
|
``$LUNAR_MAX_WAIT``, else 6 h). Other *retry_codes* (5xx), connection errors and
|
|
288
290
|
interrupted transfers are retried *retries* times with back-off. Each retry resumes
|
|
289
|
-
the ``.part`` file; checksum mismatches are not retried. See
|
|
291
|
+
the ``.part`` file; checksum mismatches are not retried. See `_download_once`.
|
|
290
292
|
|
|
291
|
-
*progress* (default: the
|
|
292
|
-
done after each chunk; an exception it raises stops the download and keeps the
|
|
293
|
+
*progress* (default: the [`report_progress`][lunar_toolkit.downloader.report_progress] callback, if any)
|
|
294
|
+
gets the bytes done after each chunk; an exception it raises stops the download and keeps the
|
|
293
295
|
``.part`` file for a later resume.
|
|
294
296
|
"""
|
|
295
297
|
progress = progress or _PROGRESS.get()
|
|
@@ -365,7 +367,7 @@ def _download_once(
|
|
|
365
367
|
is already complete.
|
|
366
368
|
* When *expected_md5* is given the finished file is verified before the
|
|
367
369
|
atomic rename; on mismatch the part file is deleted and
|
|
368
|
-
|
|
370
|
+
[`ChecksumError`][lunar_toolkit.downloader.ChecksumError] is raised.
|
|
369
371
|
* On network errors the ``.part`` file is kept so the next call resumes.
|
|
370
372
|
"""
|
|
371
373
|
from tqdm import tqdm
|
|
@@ -103,7 +103,8 @@ def _ps_bbox_of_box(
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def region_bbox(region: dict) -> tuple[float, float, float, float]:
|
|
106
|
-
"""``(west, south, east, north)`` of a
|
|
106
|
+
"""``(west, south, east, north)`` of a [`query_region`][lunar_toolkit.geometry.query_region] (east may be
|
|
107
|
+
< west)."""
|
|
107
108
|
s, n = region["lat"]
|
|
108
109
|
if region["span"] >= 360.0:
|
|
109
110
|
return 0.0, s, 360.0, n
|
|
@@ -173,7 +174,7 @@ def load_aoi(source: AoiLike, densify_deg: float | None = 1.0) -> Aoi:
|
|
|
173
174
|
mapping, a region dict ``{"point": (lat, lon), "radius_km": r}``
|
|
174
175
|
(spherical cap) or ``{"lat": (south, north), "lon": (west, east)}``
|
|
175
176
|
(``west > east`` crosses 0/360; omit ``lon`` for all longitudes), or
|
|
176
|
-
an existing
|
|
177
|
+
an existing [`Aoi`][lunar_toolkit.geometry.Aoi] (returned unchanged). Only
|
|
177
178
|
Polygon / MultiPolygon parts are used (also inside
|
|
178
179
|
GeometryCollections); coordinates are lon/lat degrees, positive east,
|
|
179
180
|
in either −180..180 or 0..360. Polygons may cross the 0/360 (or
|
|
@@ -363,11 +364,11 @@ def _covering_arc(arcs: list[tuple[float, float]]) -> tuple[float, float]:
|
|
|
363
364
|
|
|
364
365
|
|
|
365
366
|
def aoi_region(aoi: AoiLike) -> dict:
|
|
366
|
-
"""Bounding region of an AOI, in
|
|
367
|
+
"""Bounding region of an AOI, in [`query_region`][lunar_toolkit.geometry.query_region] form.
|
|
367
368
|
|
|
368
369
|
Returns ``{"lat": (s, n), "west", "span", "ps"}`` so the bbox prefilter
|
|
369
370
|
used for boxes and circles applies unchanged; refine the prefiltered
|
|
370
|
-
rows with
|
|
371
|
+
rows with [`filter_aoi`][lunar_toolkit.geometry.filter_aoi].
|
|
371
372
|
"""
|
|
372
373
|
aoi = load_aoi(aoi)
|
|
373
374
|
lats = [la for poly in aoi.polygons for la in (p[1] for p in poly[0])]
|
|
@@ -409,7 +410,8 @@ def _polar_hemi(region: dict) -> str | None:
|
|
|
409
410
|
|
|
410
411
|
|
|
411
412
|
def ps_projection(hemi: str) -> Projection:
|
|
412
|
-
"""Ring projection to polar stereographic (
|
|
413
|
+
"""Ring projection to polar stereographic ([`ps_xy`][lunar_toolkit.geometry.ps_xy]) for hemisphere
|
|
414
|
+
*hemi*."""
|
|
413
415
|
# Clamp away from the opposite pole, where the projection diverges
|
|
414
416
|
lim = 89.0
|
|
415
417
|
return lambda ring: [ps_xy(min(la, lim) if hemi == "S" else max(la, -lim), lo, hemi) for lo, la in ring]
|
|
@@ -449,7 +451,7 @@ def aoi_geometry(aoi: AoiLike, proj: Projection | None = None):
|
|
|
449
451
|
def footprint_geometry(rings_list: list[list[Ring]], proj: Projection | None):
|
|
450
452
|
"""Footprint polygons (per polygon: lon/lat exterior, *holes) as shapely geometry.
|
|
451
453
|
|
|
452
|
-
Rings are projected by *proj* (e.g.
|
|
454
|
+
Rings are projected by *proj* (e.g. [`ps_projection`][lunar_toolkit.geometry.ps_projection]), else kept in
|
|
453
455
|
lon/lat (pole-enclosing rings closed via the pole) starting in 0..360.
|
|
454
456
|
"""
|
|
455
457
|
parts = []
|
|
@@ -515,7 +517,8 @@ def footprint_rings(row: dict, wkt_column: str | None = None) -> list[list[Ring]
|
|
|
515
517
|
|
|
516
518
|
|
|
517
519
|
def row_footprint(row: dict, wkt_column: str | None = None, proj: Projection | None = None):
|
|
518
|
-
"""Shapely footprint (
|
|
520
|
+
"""Shapely footprint ([`footprint_geometry`][lunar_toolkit.geometry.footprint_geometry]) of a result row,
|
|
521
|
+
or ``None``."""
|
|
519
522
|
rings = footprint_rings(row, wkt_column)
|
|
520
523
|
return None if rings is None else footprint_geometry(rings, proj)
|
|
521
524
|
|
|
@@ -528,9 +531,9 @@ def aoi_mask(df: pd.DataFrame, aoi: AoiLike, wkt_column: str | None = None) -> l
|
|
|
528
531
|
corner lat/lon columns, else the ``FP_*`` / ``min_lat``…``lon_east``
|
|
529
532
|
bounding box. Rows with no geometry at all are kept.
|
|
530
533
|
|
|
531
|
-
*aoi* is anything
|
|
534
|
+
*aoi* is anything [`load_aoi`][lunar_toolkit.geometry.load_aoi] accepts. Regions entirely poleward
|
|
532
535
|
of ±60° (``POLAR_LAT_THRESHOLD``) are intersected in polar
|
|
533
|
-
stereographic (
|
|
536
|
+
stereographic ([`ps_xy`][lunar_toolkit.geometry.ps_xy], footprint edges straight in the
|
|
534
537
|
projection); others in lon/lat with the AOI replicated at ±360° so
|
|
535
538
|
footprints across the 0/360 seam match.
|
|
536
539
|
"""
|
|
@@ -550,7 +553,8 @@ def aoi_mask(df: pd.DataFrame, aoi: AoiLike, wkt_column: str | None = None) -> l
|
|
|
550
553
|
|
|
551
554
|
|
|
552
555
|
def filter_aoi(df: pd.DataFrame, aoi: AoiLike, wkt_column: str | None = None) -> pd.DataFrame:
|
|
553
|
-
"""Keep only rows whose footprint intersects the AOI (see
|
|
556
|
+
"""Keep only rows whose footprint intersects the AOI (see
|
|
557
|
+
[`aoi_mask`][lunar_toolkit.geometry.aoi_mask])."""
|
|
554
558
|
if df is None or len(df) == 0:
|
|
555
559
|
return df
|
|
556
560
|
return df[aoi_mask(df, aoi, wkt_column=wkt_column)].reset_index(drop=True)
|
|
@@ -48,7 +48,7 @@ def spiceinit(from_cub: Path, spksmithed: bool = True, web: bool = True, **param
|
|
|
48
48
|
def system_shape_available() -> bool:
|
|
49
49
|
"""Whether ``$ISISDATA`` (as the ISIS tools see it) has the default lunar shape model.
|
|
50
50
|
|
|
51
|
-
True when ``spiceinit`` cannot be found or under
|
|
51
|
+
True when ``spiceinit`` cannot be found or under [`dry_run`][lunar_toolkit.tools.dry_run]
|
|
52
52
|
(nothing to decide then).
|
|
53
53
|
"""
|
|
54
54
|
tool = find_tool("spiceinit")
|
|
@@ -202,8 +202,9 @@ def prepare_lronac(
|
|
|
202
202
|
"""Calibrated cube and optional CSM camera for one NAC image.
|
|
203
203
|
|
|
204
204
|
A ``.cub`` *src* is used as is; anything else (an EDR ``.IMG``) goes through
|
|
205
|
-
|
|
206
|
-
in *out_dir* by
|
|
205
|
+
[`calibrate_lronac`][lunar_toolkit.isis.calibrate_lronac] into *out_dir*. With *csm*, ``<id>.json`` is
|
|
206
|
+
written in *out_dir* by [`isd_generate`][lunar_toolkit.isis.isd_generate]. Existing outputs are reused
|
|
207
|
+
unless *force*.
|
|
207
208
|
|
|
208
209
|
Returns
|
|
209
210
|
-------
|
|
@@ -79,7 +79,7 @@ def select_mi_files(files: Iterable[dict], product_id: str | None = None) -> MiP
|
|
|
79
79
|
"""Pick the data and label files of an MI product from its ODE ``files`` list.
|
|
80
80
|
|
|
81
81
|
MI products have the TC layout (detached ``.lbl`` + ``.img.gz``), so
|
|
82
|
-
this follows
|
|
82
|
+
this follows [`select_tc_files`][lunar_toolkit.kaguya.tc.select_tc_files].
|
|
83
83
|
|
|
84
84
|
Returns
|
|
85
85
|
-------
|
|
@@ -112,7 +112,8 @@ def mi_products(df: pd.DataFrame) -> list[MiProduct]:
|
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def lookup_mi_products(product_ids: Iterable[str], client: OdeClient | None = None) -> pd.DataFrame:
|
|
115
|
-
"""Query ODE by product ID and return the observation DataFrame (see
|
|
115
|
+
"""Query ODE by product ID and return the observation DataFrame (see
|
|
116
|
+
[`OdeClient.lookup`][lunar_toolkit.ode.OdeClient.lookup])."""
|
|
116
117
|
from ..ode import OdeClient
|
|
117
118
|
|
|
118
119
|
return (client or OdeClient()).lookup(product_ids, instrument_label="Kaguya MI")
|
|
@@ -127,7 +128,7 @@ def download_mi_product(
|
|
|
127
128
|
) -> Path:
|
|
128
129
|
"""Download (and gunzip) one product's label and image; returns the label path.
|
|
129
130
|
|
|
130
|
-
See
|
|
131
|
+
See [`download_product`][lunar_toolkit.kaguya.tc.download_product].
|
|
131
132
|
"""
|
|
132
133
|
tc = TcProduct(product.product_id, product.image_url, product.label_url, product.image_kbytes)
|
|
133
134
|
return download_product(tc, output_dir, force=force, keep_gz=keep_gz, show_progress=show_progress)
|
|
@@ -162,8 +163,8 @@ class MiProcessor:
|
|
|
162
163
|
force : bool
|
|
163
164
|
Redo every step even when its output exists.
|
|
164
165
|
kaguyami2isis_params, spiceinit_params : dict, optional
|
|
165
|
-
Overrides merged into
|
|
166
|
-
|
|
166
|
+
Overrides merged into `KAGUYAMI2ISIS_PARAMS` /
|
|
167
|
+
`SPICEINIT_PARAMS`.
|
|
167
168
|
"""
|
|
168
169
|
|
|
169
170
|
def __init__(
|
|
@@ -194,7 +195,7 @@ class MiProcessor:
|
|
|
194
195
|
def process(self, source: MiProduct | str | Path) -> MiOutputs:
|
|
195
196
|
"""Download (if needed) and import one product.
|
|
196
197
|
|
|
197
|
-
*source* is a
|
|
198
|
+
*source* is a [`MiProduct`][lunar_toolkit.kaguya.mi.MiProduct], a local ``.lbl`` path or a product
|
|
198
199
|
ID (looked up on ODE).
|
|
199
200
|
"""
|
|
200
201
|
if isinstance(source, MiProduct):
|
|
@@ -269,6 +270,7 @@ class MiProcessor:
|
|
|
269
270
|
|
|
270
271
|
|
|
271
272
|
def import_mi(label: Path, output_dir: Path | None = None, **kwargs: Any) -> MiOutputs:
|
|
272
|
-
"""Import one downloaded MI product; see
|
|
273
|
+
"""Import one downloaded MI product; see [`MiProcessor`][lunar_toolkit.kaguya.mi.MiProcessor] for
|
|
274
|
+
*kwargs*."""
|
|
273
275
|
label = Path(label)
|
|
274
276
|
return MiProcessor(output_dir or label.parent, **kwargs).import_label(label)
|
|
@@ -150,7 +150,8 @@ def lookup_products(
|
|
|
150
150
|
product_ids: Iterable[str],
|
|
151
151
|
client: OdeClient | None = None,
|
|
152
152
|
) -> pd.DataFrame:
|
|
153
|
-
"""Query ODE by product ID and return the observation DataFrame (see
|
|
153
|
+
"""Query ODE by product ID and return the observation DataFrame (see
|
|
154
|
+
[`OdeClient.lookup`][lunar_toolkit.ode.OdeClient.lookup])."""
|
|
154
155
|
from ..ode import OdeClient
|
|
155
156
|
|
|
156
157
|
return (client or OdeClient()).lookup(product_ids, instrument_label="Kaguya TC")
|
|
@@ -212,9 +213,9 @@ class TcProcessor:
|
|
|
212
213
|
|
|
213
214
|
Each product ends up in *output_dir* as ``<id>.cub`` (spiceinit'ed),
|
|
214
215
|
``<id>.json`` (CSM camera, when *csm*) and, when *mapproject*,
|
|
215
|
-
``<id>.tif`` (
|
|
216
|
+
``<id>.tif`` (`GEOTIFF_OPTIONS`), the
|
|
216
217
|
same flat layout and resume / *keep* / *force* behaviour as
|
|
217
|
-
|
|
218
|
+
[`NacProcessor`][lunar_toolkit.lroc.nac.NacProcessor]. Steps whose output
|
|
218
219
|
exists are skipped unless *force*.
|
|
219
220
|
|
|
220
221
|
Parameters
|
|
@@ -240,8 +241,8 @@ class TcProcessor:
|
|
|
240
241
|
force : bool
|
|
241
242
|
Redo every step even when its output exists.
|
|
242
243
|
kaguyatc2isis_params, spiceinit_params : dict, optional
|
|
243
|
-
Overrides merged into
|
|
244
|
-
|
|
244
|
+
Overrides merged into `KAGUYATC2ISIS_PARAMS` /
|
|
245
|
+
`SPICEINIT_PARAMS`.
|
|
245
246
|
"""
|
|
246
247
|
|
|
247
248
|
def __init__(
|
|
@@ -274,7 +275,7 @@ class TcProcessor:
|
|
|
274
275
|
def process(self, source: TcProduct | str | Path) -> TcOutputs:
|
|
275
276
|
"""Download (if needed) and import one product.
|
|
276
277
|
|
|
277
|
-
*source* is a
|
|
278
|
+
*source* is a [`TcProduct`][lunar_toolkit.kaguya.tc.TcProduct], a local ``.lbl`` path or a product
|
|
278
279
|
ID (looked up on ODE).
|
|
279
280
|
"""
|
|
280
281
|
if isinstance(source, TcProduct):
|
|
@@ -361,7 +362,8 @@ class TcProcessor:
|
|
|
361
362
|
|
|
362
363
|
|
|
363
364
|
def import_tc(label: Path, output_dir: Path | None = None, **kwargs: Any) -> TcOutputs:
|
|
364
|
-
"""Import one downloaded TC product; see
|
|
365
|
+
"""Import one downloaded TC product; see [`TcProcessor`][lunar_toolkit.kaguya.tc.TcProcessor] for
|
|
366
|
+
*kwargs*."""
|
|
365
367
|
label = Path(label)
|
|
366
368
|
return TcProcessor(output_dir or label.parent, **kwargs).import_label(label)
|
|
367
369
|
|
|
@@ -322,7 +322,7 @@ class CumIndex:
|
|
|
322
322
|
than the sidecar's ``release`` (or ``max(VOLUME_ID)`` in the
|
|
323
323
|
Parquet when no sidecar exists).
|
|
324
324
|
on_stage : callable, optional
|
|
325
|
-
As in
|
|
325
|
+
As in [`build_db`][lunar_toolkit.lroc.cumindex.CumIndex.build_db].
|
|
326
326
|
|
|
327
327
|
Returns
|
|
328
328
|
-------
|
|
@@ -476,7 +476,7 @@ class CumIndex:
|
|
|
476
476
|
−180..180 or 0..360, omitted ``lon`` = all longitudes), a circle
|
|
477
477
|
(``point=(lat, lon)``, ``radius_km``) or an area of interest
|
|
478
478
|
(``aoi=``: GeoJSON / WKT path, WKT string or
|
|
479
|
-
|
|
479
|
+
[`Aoi`][lunar_toolkit.geometry.Aoi]).
|
|
480
480
|
|
|
481
481
|
Matching is bounding-box based and conservative (may include a few
|
|
482
482
|
false positives, never drops a true hit for convex-ish footprints):
|
|
@@ -647,7 +647,7 @@ def build_parquet(
|
|
|
647
647
|
|
|
648
648
|
Parameters
|
|
649
649
|
----------
|
|
650
|
-
sources : list of
|
|
650
|
+
sources : list of (tab_path, lbl_columns)
|
|
651
651
|
Each TAB is parsed with its own label layout (record lengths differ
|
|
652
652
|
between volumes) and the results are combined ``UNION ALL BY NAME``.
|
|
653
653
|
base_parquet : Path, optional
|
|
@@ -960,7 +960,8 @@ def footprint_wkt_sql(names: Iterable[str], west_expr: str = "FP_LON_WEST") -> s
|
|
|
960
960
|
|
|
961
961
|
# Search regions
|
|
962
962
|
def region_sql(region: dict) -> str:
|
|
963
|
-
"""WHERE clause matching ``FP_*`` columns against a
|
|
963
|
+
"""WHERE clause matching ``FP_*`` columns against a
|
|
964
|
+
[`query_region`][lunar_toolkit.geometry.query_region]."""
|
|
964
965
|
s, n = region["lat"]
|
|
965
966
|
conds = [f"FP_MAX_LAT >= {s!r}", f"FP_MIN_LAT <= {n!r}"]
|
|
966
967
|
if region["span"] < 360.0:
|
|
@@ -104,7 +104,7 @@ def _raw(value: Any) -> Any:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
class IndexColumn(Generic[_V]):
|
|
107
|
-
"""Read-only
|
|
107
|
+
"""Read-only [`NacImage`][lunar_toolkit.lroc.nac.NacImage] attribute holding one CUMINDEX column.
|
|
108
108
|
|
|
109
109
|
The value is converted by *cast*; ``None`` when the column is missing
|
|
110
110
|
or null.
|
|
@@ -408,7 +408,7 @@ class NacImage:
|
|
|
408
408
|
def process(self, output_dir: Path, **kwargs: Any) -> Path:
|
|
409
409
|
"""Run the ISIS + GDAL pipeline on the EDR, returning the GeoTIFF.
|
|
410
410
|
|
|
411
|
-
*kwargs* are
|
|
411
|
+
*kwargs* are [`NacProcessor`][lunar_toolkit.lroc.nac.NacProcessor] options (``projection``,
|
|
412
412
|
``resolution``, ``web``, ``keep``, ``force``).
|
|
413
413
|
"""
|
|
414
414
|
url = self.edr_url
|
|
@@ -418,7 +418,8 @@ class NacImage:
|
|
|
418
418
|
|
|
419
419
|
# Quick view
|
|
420
420
|
def to_png(self, output_dir: Path, input_path: Path) -> Path:
|
|
421
|
-
"""Convert an image (e.g. a GeoTIFF from
|
|
421
|
+
"""Convert an image (e.g. a GeoTIFF from [`process`][lunar_toolkit.lroc.nac.NacImage.process]) to
|
|
422
|
+
``<output_dir>/<id>.png``."""
|
|
422
423
|
return to_png(input_path, Path(output_dir) / f"{self._product_id}.png")
|
|
423
424
|
|
|
424
425
|
def show(self, input_path: Path) -> None:
|
|
@@ -556,7 +557,7 @@ class NacImageCollection:
|
|
|
556
557
|
def process(self, output_dir: Path, workers: int = 4, **kwargs: Any) -> dict[str, Path | None]:
|
|
557
558
|
"""Run the ISIS + GDAL pipeline on all EDRs in parallel.
|
|
558
559
|
|
|
559
|
-
*kwargs* are
|
|
560
|
+
*kwargs* are [`NacProcessor`][lunar_toolkit.lroc.nac.NacProcessor] options. Returns a dict mapping
|
|
560
561
|
product_id → GeoTIFF Path (or None on failure).
|
|
561
562
|
"""
|
|
562
563
|
urls = {img.product_id: img.edr_url for img in self._images}
|
|
@@ -598,7 +599,7 @@ class NacProcessor:
|
|
|
598
599
|
"""Download, calibrate and map-project LROC NAC EDRs into GeoTIFFs.
|
|
599
600
|
|
|
600
601
|
Each image ends up in *output_dir* as ``<id>.tif`` (map-projected I/F,
|
|
601
|
-
|
|
602
|
+
`GEOTIFF_OPTIONS`). Intermediates
|
|
602
603
|
(a downloaded ``<id>.IMG``, ``<id>.cal.echo.cub``, ``<id>.map.cub``) are
|
|
603
604
|
written beside it and removed after success unless *keep*; after a
|
|
604
605
|
failure they stay, so a re-run resumes. Steps whose output exists are
|
|
@@ -80,7 +80,7 @@ def normalize_observations(records: list[dict]) -> pd.DataFrame:
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def observations_from_cumindex(df: pd.DataFrame, dataset: str | None = "edr") -> pd.DataFrame:
|
|
83
|
-
"""Map enriched ``CumIndex`` query results to
|
|
83
|
+
"""Map enriched ``CumIndex`` query results to `OBSERVATION_COLUMNS`."""
|
|
84
84
|
import pandas as pd
|
|
85
85
|
|
|
86
86
|
if df is None or df.empty:
|
|
@@ -106,7 +106,7 @@ class OdeError(RuntimeError):
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class OdeClient:
|
|
109
|
-
"""Query ODE REST and return
|
|
109
|
+
"""Query ODE REST and return `OBSERVATION_COLUMNS` DataFrames.
|
|
110
110
|
|
|
111
111
|
Parameters
|
|
112
112
|
----------
|
|
@@ -151,8 +151,8 @@ class OdeClient:
|
|
|
151
151
|
) -> dict[str, str]:
|
|
152
152
|
"""Translate search arguments into ODE query parameters.
|
|
153
153
|
|
|
154
|
-
An ``aoi`` (see
|
|
155
|
-
its bounding box;
|
|
154
|
+
An ``aoi`` (see [`lunar_toolkit.geometry.load_aoi`][lunar_toolkit.geometry.load_aoi]) is sent as
|
|
155
|
+
its bounding box; [`search`][lunar_toolkit.ode.OdeClient.search] refines the results exactly.
|
|
156
156
|
"""
|
|
157
157
|
_, entry, pt = resolve_instrument(instrument, product_type)
|
|
158
158
|
params: dict[str, str] = {
|
|
@@ -232,13 +232,13 @@ class OdeClient:
|
|
|
232
232
|
) -> pd.DataFrame:
|
|
233
233
|
"""Search ODE and return the common observation schema.
|
|
234
234
|
|
|
235
|
-
Region arguments follow
|
|
236
|
-
and ``lon=(west, east)`` (``west > east`` crosses 0/360), or
|
|
235
|
+
Region arguments follow [`CumIndex.search`][lunar_toolkit.lroc.cumindex.CumIndex.search]:
|
|
236
|
+
``lat=(min, max)`` and ``lon=(west, east)`` (``west > east`` crosses 0/360), or
|
|
237
237
|
``point=(lat, lon)`` with ``radius_km`` (converted to a bounding box;
|
|
238
238
|
circles reaching a pole become a full-longitude cap). ODE matches the
|
|
239
239
|
box against product footprints (``loc=f``, its default). With
|
|
240
240
|
``aoi`` (GeoJSON / WKT path, WKT string or
|
|
241
|
-
|
|
241
|
+
[`Aoi`][lunar_toolkit.geometry.Aoi]) the AOI's
|
|
242
242
|
bounding box is queried and each ``footprint_wkt`` is then
|
|
243
243
|
intersected with the polygon; paging continues until
|
|
244
244
|
``max_results`` products survive that refinement.
|
|
@@ -458,7 +458,8 @@ def to_dataframe(records: list[dict]) -> pd.DataFrame:
|
|
|
458
458
|
|
|
459
459
|
|
|
460
460
|
def select_file_urls(df: pd.DataFrame, kind: str = "product", match: str | None = None) -> list[str]:
|
|
461
|
-
"""Pick file URLs from the ``files`` column (see
|
|
461
|
+
"""Pick file URLs from the ``files`` column (see
|
|
462
|
+
[`OdeClient.download`][lunar_toolkit.ode.OdeClient.download])."""
|
|
462
463
|
kind = kind.lower()
|
|
463
464
|
urls: list[str] = []
|
|
464
465
|
for files in df.get("files", []):
|
|
@@ -62,7 +62,7 @@ def tool_versions() -> dict[str, str | None]:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def isis_paths() -> dict[str, str | None]:
|
|
65
|
-
"""``ISISROOT`` / ``ISISDATA`` as the tools see them (
|
|
65
|
+
"""``ISISROOT`` / ``ISISDATA`` as the tools see them ([`tool_env`][lunar_toolkit.tools.tool_env])."""
|
|
66
66
|
tool = find_tool("spiceinit") or find_tool("sfs")
|
|
67
67
|
env = tool_env(tool) if tool else dict(os.environ)
|
|
68
68
|
return {k: env.get(k) for k in ("ISISROOT", "ISISDATA")}
|
|
@@ -188,7 +188,7 @@ class Provenance:
|
|
|
188
188
|
|
|
189
189
|
@contextlib.contextmanager
|
|
190
190
|
def step(self, name: str) -> Iterator[dict[str, Any]]:
|
|
191
|
-
"""Record a step: times, the commands
|
|
191
|
+
"""Record a step: times, the commands [`run`][lunar_toolkit.tools.run] executes and status.
|
|
192
192
|
|
|
193
193
|
Set ``entry["result"]`` inside the block to list its existing output files.
|
|
194
194
|
"""
|