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.
Files changed (94) hide show
  1. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/PKG-INFO +1 -1
  2. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/asp.py +1 -1
  3. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_jobs.py +4 -3
  4. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_status.py +1 -1
  5. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/_utils.py +2 -1
  6. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/serve.py +2 -2
  7. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/config.py +8 -7
  8. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/constants.py +2 -1
  9. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/downloader.py +10 -8
  10. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/geometry.py +14 -10
  11. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/isis.py +4 -3
  12. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/mi.py +9 -7
  13. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/tc.py +9 -7
  14. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/cumindex.py +5 -4
  15. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/nac.py +6 -5
  16. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/observation.py +1 -1
  17. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/ode.py +8 -7
  18. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/provenance.py +2 -2
  19. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/reference.py +34 -30
  20. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/selection.py +22 -17
  21. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs.py +56 -42
  22. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs_products.py +10 -9
  23. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/sfs_report.py +10 -7
  24. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/shadowcam/sc.py +9 -6
  25. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/stereo.py +11 -9
  26. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/tools.py +3 -2
  27. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/workspace.py +31 -24
  28. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/PKG-INFO +1 -1
  29. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/pyproject.toml +1 -1
  30. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/LICENSE +0 -0
  31. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/README.md +0 -0
  32. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/__init__.py +0 -0
  33. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/__init__.py +0 -0
  34. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/app.py +0 -0
  35. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/build_db.py +0 -0
  36. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/download.py +0 -0
  37. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/kaguya.py +0 -0
  38. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/ode_search.py +0 -0
  39. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/pairs.py +0 -0
  40. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/process.py +0 -0
  41. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/query.py +0 -0
  42. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/reference.py +0 -0
  43. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/search.py +0 -0
  44. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/sfs.py +0 -0
  45. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/sfs_report.py +0 -0
  46. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/shadowcam.py +0 -0
  47. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/stereo.py +0 -0
  48. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/cli/workspace.py +0 -0
  49. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/csm.py +0 -0
  50. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/kaguya/__init__.py +0 -0
  51. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/lroc/__init__.py +0 -0
  52. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/shadowcam/__init__.py +0 -0
  53. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_global.tif +0 -0
  54. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_north.tif +0 -0
  55. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_dem_south.tif +0 -0
  56. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_global.jpg +0 -0
  57. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_north.jpg +0 -0
  58. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/lola_shade_south.jpg +0 -0
  59. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/manifest.json +0 -0
  60. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_global.jpg +0 -0
  61. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_north.jpg +0 -0
  62. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/basemaps/wac_south.jpg +0 -0
  63. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/static/index.html +0 -0
  64. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit/stereo_dem.py +0 -0
  65. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/SOURCES.txt +0 -0
  66. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/dependency_links.txt +0 -0
  67. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/entry_points.txt +0 -0
  68. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/requires.txt +0 -0
  69. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/lunar_toolkit.egg-info/top_level.txt +0 -0
  70. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/setup.cfg +0 -0
  71. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_aoi.py +0 -0
  72. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_app.py +0 -0
  73. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_build.py +0 -0
  74. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_config.py +0 -0
  75. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_csm.py +0 -0
  76. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_download.py +0 -0
  77. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_footprints.py +0 -0
  78. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_integration.py +0 -0
  79. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_kaguya.py +0 -0
  80. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_kaguya_mi.py +0 -0
  81. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_lbl.py +0 -0
  82. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_ode.py +0 -0
  83. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_reference.py +0 -0
  84. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_selection.py +0 -0
  85. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs.py +0 -0
  86. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs_products.py +0 -0
  87. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_sfs_report.py +0 -0
  88. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_shadowcam.py +0 -0
  89. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_static.py +0 -0
  90. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_stereo.py +0 -0
  91. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_tool_env.py +0 -0
  92. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_tools.py +0 -0
  93. {lunar_toolkit-0.1.0 → lunar_toolkit-0.1.0.dev1}/tests/test_update.py +0 -0
  94. {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 :class:`~lunar_toolkit.tools.ToolError` when the run writes no fresh
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 :class:`Cancelled` if the job was cancelled."""
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 (:data:`lunar_toolkit.downloader.Progress`)."""
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 :attr:`version` differs from *version* (or *timeout* / close); the new version."""
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 :data:`CACHE_SECONDS` (or on refresh)."""
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
- """:meth:`OdeClient.search` filters from ``--inc`` / ``--emi`` / ``--phase`` / ``--time``."""
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 :class:`LunarServer` serves and where its jobs write."""
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 :class:`LunarServer` from the shared options (ValueError for a bad option)."""
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 :class:`SfsConfig`.
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 :class:`SfsConfig` fields share one flat
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 (:func:`lunar_toolkit.reference.get_region`): sets
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 (:func:`~lunar_toolkit.reference.crop_window`).
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
- :func:`lunar_toolkit.sfs.cap_candidates`).
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 :class:`RunConfig` from a flat mapping (keys as in :data:`KEYS`).
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 :func:`to_dict`)."""
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: :func:`get_data_dir`)."""
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 :func:`download_file` in this thread / context to *callback*."""
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 :meth:`download`).
131
- Returns a dict mapping each URL to its local path (or None on error).
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 :data:`COOLDOWN_FILE`, so a restarted run honours a server's earlier
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 :func:`_download_once`.
291
+ the ``.part`` file; checksum mismatches are not retried. See `_download_once`.
290
292
 
291
- *progress* (default: the :func:`report_progress` callback, if any) gets the bytes
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
- :class:`ChecksumError` is raised.
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 :func:`query_region` (east may be < west)."""
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 :class:`Aoi` (returned unchanged). Only
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 :func:`query_region` form.
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 :func:`filter_aoi`.
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 (:func:`ps_xy`) for hemisphere *hemi*."""
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. :func:`ps_projection`), else kept in
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 (:func:`footprint_geometry`) of a result row, or ``None``."""
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 :func:`load_aoi` accepts. Regions entirely poleward
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 (:func:`ps_xy`, footprint edges straight in the
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 :func:`aoi_mask`)."""
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 :func:`~lunar_toolkit.tools.dry_run`
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
- :func:`calibrate_lronac` into *out_dir*. With *csm*, ``<id>.json`` is written
206
- in *out_dir* by :func:`isd_generate`. Existing outputs are reused unless *force*.
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 :func:`~lunar_toolkit.kaguya.tc.select_tc_files`.
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 :meth:`OdeClient.lookup`)."""
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 :func:`~lunar_toolkit.kaguya.tc.download_product`.
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 :data:`KAGUYAMI2ISIS_PARAMS` /
166
- :data:`SPICEINIT_PARAMS`.
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 :class:`MiProduct`, a local ``.lbl`` path or a product
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 :class:`MiProcessor` for *kwargs*."""
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 :meth:`OdeClient.lookup`)."""
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`` (:data:`~lunar_toolkit.constants.GEOTIFF_OPTIONS`), the
216
+ ``<id>.tif`` (`GEOTIFF_OPTIONS`), the
216
217
  same flat layout and resume / *keep* / *force* behaviour as
217
- :class:`~lunar_toolkit.lroc.nac.NacProcessor`. Steps whose output
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 :data:`KAGUYATC2ISIS_PARAMS` /
244
- :data:`SPICEINIT_PARAMS`.
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 :class:`TcProduct`, a local ``.lbl`` path or a product
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 :class:`TcProcessor` for *kwargs*."""
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 :meth:`build_db`.
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
- :class:`~lunar_toolkit.geometry.Aoi`).
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 ``(tab_path, lbl_columns)``
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 :func:`query_region`."""
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 :class:`NacImage` attribute holding one CUMINDEX column.
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 :class:`NacProcessor` options (``projection``,
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 :meth:`process`) to ``<output_dir>/<id>.png``."""
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 :class:`NacProcessor` options. Returns a dict mapping
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
- :data:`~lunar_toolkit.constants.GEOTIFF_OPTIONS`). Intermediates
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 :data:`OBSERVATION_COLUMNS`."""
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 :data:`OBSERVATION_COLUMNS` DataFrames.
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 :func:`lunar_toolkit.geometry.load_aoi`) is sent as
155
- its bounding box; :meth:`search` refines the results exactly.
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 :meth:`CumIndex.search`: ``lat=(min, max)``
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
- :class:`~lunar_toolkit.geometry.Aoi`) the AOI's
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 :meth:`OdeClient.download`)."""
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 (:func:`~lunar_toolkit.tools.tool_env`)."""
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 :func:`~lunar_toolkit.tools.run` executes and status.
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
  """