lunar-toolkit 0.1.0.dev2__tar.gz → 0.1.0.dev5__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.dev2 → lunar_toolkit-0.1.0.dev5}/PKG-INFO +2 -2
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/asp.py +20 -1
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/sfs.py +2 -2
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/sfs.py +32 -7
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/static/index.html +486 -443
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/PKG-INFO +2 -2
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/requires.txt +1 -1
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/pyproject.toml +2 -2
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_sfs.py +32 -10
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_static.py +2 -2
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_tools.py +21 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_workspace.py +4 -4
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/LICENSE +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/README.md +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/__init__.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/__init__.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/_jobs.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/_status.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/_utils.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/app.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/build_db.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/download.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/kaguya.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/ode_search.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/pairs.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/process.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/query.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/reference.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/search.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/serve.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/sfs_report.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/shadowcam.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/stereo.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/cli/workspace.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/config.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/constants.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/csm.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/downloader.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/geometry.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/isis.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/kaguya/__init__.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/kaguya/mi.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/kaguya/tc.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/lroc/__init__.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/lroc/cumindex.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/lroc/nac.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/observation.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/ode.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/provenance.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/reference.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/selection.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/sfs_products.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/sfs_report.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/shadowcam/__init__.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/shadowcam/sc.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/stereo.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/stereo_dem.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/tools.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit/workspace.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/SOURCES.txt +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/dependency_links.txt +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/entry_points.txt +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/lunar_toolkit.egg-info/top_level.txt +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/setup.cfg +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_aoi.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_app.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_build.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_config.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_csm.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_download.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_footprints.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_integration.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_kaguya.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_kaguya_mi.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_lbl.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_ode.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_reference.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_selection.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_sfs_products.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_sfs_report.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_shadowcam.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_stereo.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_tool_env.py +0 -0
- {lunar_toolkit-0.1.0.dev2 → lunar_toolkit-0.1.0.dev5}/tests/test_update.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.dev5
|
|
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
|
|
@@ -42,7 +42,7 @@ Requires-Dist: types-PyYAML; extra == "dev"
|
|
|
42
42
|
Requires-Dist: pre-commit; extra == "dev"
|
|
43
43
|
Requires-Dist: ruff; extra == "dev"
|
|
44
44
|
Provides-Extra: docs
|
|
45
|
-
Requires-Dist: mkdocs
|
|
45
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
46
46
|
Requires-Dist: mkdocs-gen-files; extra == "docs"
|
|
47
47
|
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
48
48
|
Dynamic: license-file
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
|
+
import time
|
|
7
8
|
from collections.abc import Sequence
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from pathlib import Path
|
|
@@ -11,7 +12,7 @@ from typing import Any
|
|
|
11
12
|
|
|
12
13
|
import numpy as np
|
|
13
14
|
|
|
14
|
-
from .tools import asp_options, gdal_options, is_dry_run, run
|
|
15
|
+
from .tools import ToolError, asp_options, gdal_options, is_dry_run, run
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def _paths(items: Sequence[Any]) -> list[str]:
|
|
@@ -49,12 +50,30 @@ def bundle_adjust(
|
|
|
49
50
|
*mapprojected* (same order as *images*) and the *dem* they were projected
|
|
50
51
|
onto become ``--mapprojected-data``, which finds matches between images of
|
|
51
52
|
very different illumination. Returns *out_prefix*.
|
|
53
|
+
|
|
54
|
+
Raises :class:`~lunar_toolkit.tools.ToolError` when the run writes no fresh
|
|
55
|
+
``.adjust`` file for some image: ASP exits 0 even when it finds nothing to
|
|
56
|
+
optimize ("No points to optimize ... Cannot continue").
|
|
52
57
|
"""
|
|
53
58
|
_parent(out_prefix)
|
|
54
59
|
if mapprojected is not None:
|
|
55
60
|
options["mapprojected_data"] = " ".join(_paths([*mapprojected, *([dem] if dem else [])]))
|
|
56
61
|
tool = "parallel_bundle_adjust" if parallel else "bundle_adjust"
|
|
62
|
+
start = time.time()
|
|
57
63
|
run(tool, [*_paths(images), *_paths(cameras), *asp_options(options), "-o", out_prefix], cwd=cwd)
|
|
64
|
+
if not is_dry_run():
|
|
65
|
+
prefix = Path(cwd or ".") / out_prefix
|
|
66
|
+
cams: Sequence[Path | None] = cameras if len(cameras) == len(images) else [None] * len(images)
|
|
67
|
+
missing = [
|
|
68
|
+
p
|
|
69
|
+
for p in (bundle_adjust_file_name(prefix, im, cam) for im, cam in zip(images, cams))
|
|
70
|
+
if not p.is_file() or p.stat().st_mtime < start - 2
|
|
71
|
+
]
|
|
72
|
+
if missing:
|
|
73
|
+
raise ToolError(
|
|
74
|
+
f"{tool} wrote no adjustment for {len(missing)} of {len(images)} images "
|
|
75
|
+
f"(e.g. {missing[0].name}); see {out_prefix}-log-bundle_adjust-*.txt"
|
|
76
|
+
)
|
|
58
77
|
return Path(out_prefix)
|
|
59
78
|
|
|
60
79
|
|
|
@@ -242,7 +242,7 @@ def _effective(args: argparse.Namespace):
|
|
|
242
242
|
|
|
243
243
|
def run(args: argparse.Namespace) -> int:
|
|
244
244
|
from .. import config as cfgmod
|
|
245
|
-
from ..sfs import SfsPipeline, aoi_projwin, search_images
|
|
245
|
+
from ..sfs import PipelineError, SfsPipeline, aoi_projwin, search_images
|
|
246
246
|
from ..tools import ToolError
|
|
247
247
|
from ._utils import print_dry_run, resolve_images
|
|
248
248
|
|
|
@@ -310,7 +310,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
310
310
|
print_dry_run(lambda: pipeline.run(images))
|
|
311
311
|
return 0
|
|
312
312
|
out = pipeline.run(images)
|
|
313
|
-
except ToolError as exc:
|
|
313
|
+
except (ToolError, PipelineError) as exc:
|
|
314
314
|
print(exc, file=sys.stderr)
|
|
315
315
|
return 1
|
|
316
316
|
print(f"SfS DEM -> {out}")
|
|
@@ -168,11 +168,15 @@ class SfsConfig:
|
|
|
168
168
|
``--heights-from-dem`` (reusing the matches).
|
|
169
169
|
max_offset_p85 : float, optional
|
|
170
170
|
Drop images whose 85th-percentile mapprojected match offset (m) is larger.
|
|
171
|
+
Applied to the DEM-registered, refined cameras (ASP's ``ba_align_ref``
|
|
172
|
+
report); with *refine* off, to the unregistered ``ba/`` cameras.
|
|
171
173
|
max_large_residual_fraction : float, optional
|
|
172
174
|
Drop images with more than this fraction of reprojection errors over
|
|
173
175
|
*large_residual_px* pixels.
|
|
174
176
|
min_matches : int
|
|
175
177
|
Drop images with fewer inlier matches.
|
|
178
|
+
min_images : int
|
|
179
|
+
Stop with :class:`PipelineError` when a quality filter leaves fewer images.
|
|
176
180
|
selection : {"coverage", "image_subset", "all"}
|
|
177
181
|
SfS image selection: greedy per-pixel illumination coverage
|
|
178
182
|
(:func:`select_by_illumination`), ASP ``image_subset`` per Sun-azimuth bin,
|
|
@@ -236,6 +240,7 @@ class SfsConfig:
|
|
|
236
240
|
max_large_residual_fraction: float | None = 0.85
|
|
237
241
|
large_residual_px: float = 1.0
|
|
238
242
|
min_matches: int = 1
|
|
243
|
+
min_images: int = 3
|
|
239
244
|
selection: str = "coverage"
|
|
240
245
|
max_shadow_fraction: float | None = 0.95
|
|
241
246
|
selection_views: int = 3
|
|
@@ -281,6 +286,10 @@ class SfsConfig:
|
|
|
281
286
|
return {**PRESETS[self.preset][step], **extra}
|
|
282
287
|
|
|
283
288
|
|
|
289
|
+
class PipelineError(RuntimeError):
|
|
290
|
+
"""The SfS pipeline cannot continue (e.g. too few images left)."""
|
|
291
|
+
|
|
292
|
+
|
|
284
293
|
@dataclass
|
|
285
294
|
class SfsImage:
|
|
286
295
|
"""One input image: cube, optional CSM camera, Sun angles and derived files."""
|
|
@@ -315,11 +324,11 @@ class SfsPipeline:
|
|
|
315
324
|
3. sort by Sun azimuth (``sfs --query``);
|
|
316
325
|
4. mapproject at native resolution (``prj/``) and bundle-adjust with matches between
|
|
317
326
|
the mapprojected images (``ba/``);
|
|
318
|
-
5. drop images with no matches
|
|
319
|
-
residuals (``selection/removed.csv``);
|
|
327
|
+
5. drop images with no matches or mostly large residuals (``selection/removed.csv``);
|
|
320
328
|
6. register the cameras to the prior DEM, translation only, from stereo DEMs
|
|
321
329
|
(``stereo/``) or the BA points; apply to all cameras (``align/``, ``ba_align/``);
|
|
322
|
-
7. refine with ``--heights-from-dem`` and filter again
|
|
330
|
+
7. refine with ``--heights-from-dem`` and filter again, now also on mapprojected
|
|
331
|
+
match offsets (``ba_refine/``); fewer than ``min_images`` left stops the run;
|
|
323
332
|
8. mapproject at the target resolution (``prj_sfs/``), write max-lit QA mosaics,
|
|
324
333
|
all images and per 30° Sun-azimuth bin (``qa/``);
|
|
325
334
|
9. select an illumination-diverse subset (``selection/``);
|
|
@@ -684,6 +693,11 @@ class SfsPipeline:
|
|
|
684
693
|
(``ba``, ``ba_refine``) and removes the images of its ``image_rejections`` view,
|
|
685
694
|
with this config's thresholds. Removed images are logged and appended to
|
|
686
695
|
``selection/removed.csv``.
|
|
696
|
+
|
|
697
|
+
At ``ba`` the match-offset rule waits for the refinement when that runs: before
|
|
698
|
+
registration the network floats tens of metres off the DEM, so convergent pairs
|
|
699
|
+
mapproject apart by ``dh * tan(convergence)`` however well they are tied (ASP:
|
|
700
|
+
"do not use this option for any initial evaluation of bundle adjustment").
|
|
687
701
|
"""
|
|
688
702
|
cfg = self.config
|
|
689
703
|
files = {
|
|
@@ -695,11 +709,17 @@ class SfsPipeline:
|
|
|
695
709
|
)
|
|
696
710
|
}
|
|
697
711
|
if not any(p.exists() for p in files.values()):
|
|
698
|
-
if
|
|
699
|
-
|
|
700
|
-
|
|
712
|
+
if is_dry_run():
|
|
713
|
+
return []
|
|
714
|
+
raise PipelineError(f"No bundle-adjustment reports under {prefix}")
|
|
715
|
+
overrides: dict[str, Any] = {}
|
|
716
|
+
if stage == "ba" and cfg.max_offset_p85 is not None:
|
|
717
|
+
if cfg.refine:
|
|
718
|
+
overrides["max_offset_p85"] = None
|
|
719
|
+
else:
|
|
720
|
+
log.warning("Match offsets of unregistered cameras overstate misalignment; consider refine")
|
|
701
721
|
with self.workspace() as ws:
|
|
702
|
-
ws.set_rejection_params(cfg)
|
|
722
|
+
ws.set_rejection_params(cfg, **overrides)
|
|
703
723
|
ws.load_bundle_adjust(prefix, run=Path(prefix).parent.name)
|
|
704
724
|
flags = ws.rejections(Path(prefix).parent.name)
|
|
705
725
|
removed = [
|
|
@@ -721,6 +741,11 @@ class SfsPipeline:
|
|
|
721
741
|
["product_id", "stage", "reason"],
|
|
722
742
|
[[r.image.product_id, r.stage, r.reason] for r in self.removed],
|
|
723
743
|
)
|
|
744
|
+
if len(self.images) < self.config.min_images:
|
|
745
|
+
raise PipelineError(
|
|
746
|
+
f"{len(self.images)} images left after the {removed[0].stage} filter, fewer than "
|
|
747
|
+
f"min_images={self.config.min_images}; see {self.work_dir / 'selection' / 'removed.csv'}"
|
|
748
|
+
)
|
|
724
749
|
return removed
|
|
725
750
|
|
|
726
751
|
def register(self) -> Path:
|