prism-lidarcloud 1.0.0__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.
- prism_lidarcloud-1.0.0/PKG-INFO +108 -0
- prism_lidarcloud-1.0.0/README.md +93 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud/__init__.py +155 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud/cli.py +87 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud.egg-info/PKG-INFO +108 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud.egg-info/SOURCES.txt +9 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud.egg-info/dependency_links.txt +1 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud.egg-info/entry_points.txt +2 -0
- prism_lidarcloud-1.0.0/prism_lidarcloud.egg-info/top_level.txt +1 -0
- prism_lidarcloud-1.0.0/pyproject.toml +29 -0
- prism_lidarcloud-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prism-lidarcloud
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client + CLI for the PRISM LiDAR Cloud REST API (LiDAR → survey-grade DEM/DSM/CHM).
|
|
5
|
+
Author: PRISM LiDAR Cloud
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://lidarcloud.app
|
|
8
|
+
Project-URL: Documentation, https://app.lidarcloud.app/docs/api
|
|
9
|
+
Keywords: lidar,dem,dsm,chm,point-cloud,gis,remote-sensing,geospatial
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# prism-lidarcloud
|
|
17
|
+
|
|
18
|
+
Official Python client + CLI for the [PRISM LiDAR Cloud](https://lidarcloud.app) REST API —
|
|
19
|
+
turn raw airborne/UAV LiDAR (`.las`/`.laz`) into a survey-grade deliverable set (bare-earth DEM,
|
|
20
|
+
DSM, CHM, classified point cloud, per-cell uncertainty, accuracy report, CAD bundle) with one call.
|
|
21
|
+
|
|
22
|
+
Zero dependencies (Python standard library only).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
> **PyPI publish pending.** `pip install prism-lidarcloud` is *coming to PyPI* but is not
|
|
27
|
+
> live yet. Until then, install the wheel we serve from the app (works for everyone — zero
|
|
28
|
+
> dependencies, pure standard library):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# served wheel (recommended — no account/repo access needed)
|
|
32
|
+
pip install https://app.lidarcloud.app/downloads/prism_lidarcloud-1.0.0-py3-none-any.whl
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Have repo access? You can also install straight from GitHub (no clone needed — the package
|
|
36
|
+
lives in a subdirectory):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install "git+https://github.com/ebennb/PRISM_cloud.git#subdirectory=clients/python"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Already have the repo checked out? Install the local package directory instead:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ./clients/python
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Either way you get the `prism` CLI on your `PATH` and the `prism_lidarcloud` Python package.
|
|
49
|
+
(Once published, the install will simply be `pip install prism-lidarcloud`.)
|
|
50
|
+
|
|
51
|
+
## Get an API key
|
|
52
|
+
|
|
53
|
+
Sign in at <https://app.lidarcloud.app>, click your email → **API access** → **Create API key**.
|
|
54
|
+
The key (`prism_live_…`) is shown once — copy it. Keep it secret; it carries your account's quota
|
|
55
|
+
and billing.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export PRISM_API_KEY=prism_live_xxxxxxxxxxxx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# one shot: upload, wait, download the product zip
|
|
65
|
+
prism run scan.las --align 3dep --out products.zip
|
|
66
|
+
|
|
67
|
+
# same, with a real .las path (Windows)
|
|
68
|
+
prism run "Z:\DOKI_github\PRISM_cloud\Sites\Blues_Creek_Subsets\Blues_Creek_Subset_Small\LAS_20260514-191148_LAS_RGB_p051526_v2_alignedSubSmall.las" --align 3dep --out blues_creek_products.zip
|
|
69
|
+
|
|
70
|
+
# step by step
|
|
71
|
+
prism submit scan.las --align 3dep # prints {job_id, status_url, ...}
|
|
72
|
+
prism status <job_id>
|
|
73
|
+
prism download <job_id> -o products.zip
|
|
74
|
+
prism jobs # list your jobs
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Common flags: `--align none|3dep|upload|existing`, `--reference other.las` (paired change
|
|
78
|
+
detection), `--no-change`, `--no-classify`, `--no-report`, `--no-cad`, `--dem-res 25cm`,
|
|
79
|
+
`--vdatum navd88`.
|
|
80
|
+
|
|
81
|
+
## Python
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from prism_lidarcloud import Client
|
|
85
|
+
|
|
86
|
+
c = Client() # reads PRISM_API_KEY
|
|
87
|
+
out = c.run("scan.las", out="products.zip", align="3dep", change=True)
|
|
88
|
+
print("saved", out)
|
|
89
|
+
|
|
90
|
+
# or control each step
|
|
91
|
+
job = c.submit("scan.las", align="3dep")
|
|
92
|
+
final = c.wait(job["job_id"], on_progress=lambda d: print(d["status"], d["pct"]))
|
|
93
|
+
c.download(job["job_id"], "products.zip")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## What you get back
|
|
97
|
+
|
|
98
|
+
A `.zip` containing the bare-earth **DEM**, **DSM**, **CHM**, **DEM uncertainty** rasters
|
|
99
|
+
(GeoTIFF), the classified point cloud, the AOI boundary, and — unless disabled — the **accuracy
|
|
100
|
+
report** (Word) and **CAD bundle** (DXF + LandXML). Paired/3DEP runs add alignment + change-detection
|
|
101
|
+
products.
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
|
|
105
|
+
- Jobs run on ephemeral cloud workers; `wait()` polls until `done`/`error`.
|
|
106
|
+
- API jobs consume the same account quota as the web app and appear in your usage console tagged
|
|
107
|
+
**API** (vs **web**).
|
|
108
|
+
- Full reference: <https://app.lidarcloud.app/docs/api>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# prism-lidarcloud
|
|
2
|
+
|
|
3
|
+
Official Python client + CLI for the [PRISM LiDAR Cloud](https://lidarcloud.app) REST API —
|
|
4
|
+
turn raw airborne/UAV LiDAR (`.las`/`.laz`) into a survey-grade deliverable set (bare-earth DEM,
|
|
5
|
+
DSM, CHM, classified point cloud, per-cell uncertainty, accuracy report, CAD bundle) with one call.
|
|
6
|
+
|
|
7
|
+
Zero dependencies (Python standard library only).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
> **PyPI publish pending.** `pip install prism-lidarcloud` is *coming to PyPI* but is not
|
|
12
|
+
> live yet. Until then, install the wheel we serve from the app (works for everyone — zero
|
|
13
|
+
> dependencies, pure standard library):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# served wheel (recommended — no account/repo access needed)
|
|
17
|
+
pip install https://app.lidarcloud.app/downloads/prism_lidarcloud-1.0.0-py3-none-any.whl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Have repo access? You can also install straight from GitHub (no clone needed — the package
|
|
21
|
+
lives in a subdirectory):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "git+https://github.com/ebennb/PRISM_cloud.git#subdirectory=clients/python"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Already have the repo checked out? Install the local package directory instead:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install ./clients/python
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Either way you get the `prism` CLI on your `PATH` and the `prism_lidarcloud` Python package.
|
|
34
|
+
(Once published, the install will simply be `pip install prism-lidarcloud`.)
|
|
35
|
+
|
|
36
|
+
## Get an API key
|
|
37
|
+
|
|
38
|
+
Sign in at <https://app.lidarcloud.app>, click your email → **API access** → **Create API key**.
|
|
39
|
+
The key (`prism_live_…`) is shown once — copy it. Keep it secret; it carries your account's quota
|
|
40
|
+
and billing.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export PRISM_API_KEY=prism_live_xxxxxxxxxxxx
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## CLI
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# one shot: upload, wait, download the product zip
|
|
50
|
+
prism run scan.las --align 3dep --out products.zip
|
|
51
|
+
|
|
52
|
+
# same, with a real .las path (Windows)
|
|
53
|
+
prism run "Z:\DOKI_github\PRISM_cloud\Sites\Blues_Creek_Subsets\Blues_Creek_Subset_Small\LAS_20260514-191148_LAS_RGB_p051526_v2_alignedSubSmall.las" --align 3dep --out blues_creek_products.zip
|
|
54
|
+
|
|
55
|
+
# step by step
|
|
56
|
+
prism submit scan.las --align 3dep # prints {job_id, status_url, ...}
|
|
57
|
+
prism status <job_id>
|
|
58
|
+
prism download <job_id> -o products.zip
|
|
59
|
+
prism jobs # list your jobs
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Common flags: `--align none|3dep|upload|existing`, `--reference other.las` (paired change
|
|
63
|
+
detection), `--no-change`, `--no-classify`, `--no-report`, `--no-cad`, `--dem-res 25cm`,
|
|
64
|
+
`--vdatum navd88`.
|
|
65
|
+
|
|
66
|
+
## Python
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from prism_lidarcloud import Client
|
|
70
|
+
|
|
71
|
+
c = Client() # reads PRISM_API_KEY
|
|
72
|
+
out = c.run("scan.las", out="products.zip", align="3dep", change=True)
|
|
73
|
+
print("saved", out)
|
|
74
|
+
|
|
75
|
+
# or control each step
|
|
76
|
+
job = c.submit("scan.las", align="3dep")
|
|
77
|
+
final = c.wait(job["job_id"], on_progress=lambda d: print(d["status"], d["pct"]))
|
|
78
|
+
c.download(job["job_id"], "products.zip")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What you get back
|
|
82
|
+
|
|
83
|
+
A `.zip` containing the bare-earth **DEM**, **DSM**, **CHM**, **DEM uncertainty** rasters
|
|
84
|
+
(GeoTIFF), the classified point cloud, the AOI boundary, and — unless disabled — the **accuracy
|
|
85
|
+
report** (Word) and **CAD bundle** (DXF + LandXML). Paired/3DEP runs add alignment + change-detection
|
|
86
|
+
products.
|
|
87
|
+
|
|
88
|
+
## Notes
|
|
89
|
+
|
|
90
|
+
- Jobs run on ephemeral cloud workers; `wait()` polls until `done`/`error`.
|
|
91
|
+
- API jobs consume the same account quota as the web app and appear in your usage console tagged
|
|
92
|
+
**API** (vs **web**).
|
|
93
|
+
- Full reference: <https://app.lidarcloud.app/docs/api>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""prism-lidarcloud — official Python client for the PRISM LiDAR Cloud REST API.
|
|
2
|
+
|
|
3
|
+
Zero dependencies (Python standard library only). Get an API key from your account at
|
|
4
|
+
https://app.lidarcloud.app/account → "API access" → Create API key.
|
|
5
|
+
|
|
6
|
+
from prism_lidarcloud import Client
|
|
7
|
+
c = Client(api_key="prism_live_...") # or set PRISM_API_KEY
|
|
8
|
+
out = c.run("scan.las", out="products.zip", align="3dep", wait=True)
|
|
9
|
+
print("saved", out)
|
|
10
|
+
|
|
11
|
+
Or step by step:
|
|
12
|
+
|
|
13
|
+
job = c.submit("scan.las", align="3dep", change=True)
|
|
14
|
+
c.wait(job["job_id"])
|
|
15
|
+
c.download(job["job_id"], "products.zip")
|
|
16
|
+
"""
|
|
17
|
+
import io, json, mimetypes, os, time, uuid
|
|
18
|
+
import urllib.request, urllib.error
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.0"
|
|
21
|
+
DEFAULT_BASE_URL = os.environ.get("PRISM_BASE_URL", "https://app.lidarcloud.app")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PrismError(RuntimeError):
|
|
25
|
+
"""An API error. `.status` is the HTTP code; `.detail` the server message."""
|
|
26
|
+
def __init__(self, message, status=None, detail=None):
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
self.status = status
|
|
29
|
+
self.detail = detail
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _multipart(fields, files):
|
|
33
|
+
"""Encode a multipart/form-data body. fields: {name: str}. files: {name: (filename, bytes)}.
|
|
34
|
+
Returns (content_type, body_bytes)."""
|
|
35
|
+
boundary = "----prism" + uuid.uuid4().hex
|
|
36
|
+
out = io.BytesIO()
|
|
37
|
+
def w(s): out.write(s.encode() if isinstance(s, str) else s)
|
|
38
|
+
for name, val in (fields or {}).items():
|
|
39
|
+
w(f"--{boundary}\r\n")
|
|
40
|
+
w(f'Content-Disposition: form-data; name="{name}"\r\n\r\n')
|
|
41
|
+
w(f"{val}\r\n")
|
|
42
|
+
for name, (fn, data) in (files or {}).items():
|
|
43
|
+
ctype = mimetypes.guess_type(fn)[0] or "application/octet-stream"
|
|
44
|
+
w(f"--{boundary}\r\n")
|
|
45
|
+
w(f'Content-Disposition: form-data; name="{name}"; filename="{fn}"\r\n')
|
|
46
|
+
w(f"Content-Type: {ctype}\r\n\r\n")
|
|
47
|
+
w(data); w("\r\n")
|
|
48
|
+
w(f"--{boundary}--\r\n")
|
|
49
|
+
return f"multipart/form-data; boundary={boundary}", out.getvalue()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Client:
|
|
53
|
+
"""A PRISM LiDAR Cloud API client.
|
|
54
|
+
|
|
55
|
+
api_key : your prism_live_... key (or set the PRISM_API_KEY env var).
|
|
56
|
+
base_url : the API host (defaults to https://app.lidarcloud.app).
|
|
57
|
+
timeout : per-request socket timeout in seconds (uploads use upload_timeout).
|
|
58
|
+
"""
|
|
59
|
+
def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, timeout=120, upload_timeout=1800):
|
|
60
|
+
self.api_key = api_key or os.environ.get("PRISM_API_KEY")
|
|
61
|
+
if not self.api_key:
|
|
62
|
+
raise PrismError("No API key — pass api_key= or set PRISM_API_KEY.")
|
|
63
|
+
self.base_url = base_url.rstrip("/")
|
|
64
|
+
self.timeout = timeout
|
|
65
|
+
self.upload_timeout = upload_timeout
|
|
66
|
+
|
|
67
|
+
# ── low-level request ──
|
|
68
|
+
def _req(self, method, path, *, body=None, ctype=None, timeout=None, raw=False):
|
|
69
|
+
url = self.base_url + path
|
|
70
|
+
headers = {"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"}
|
|
71
|
+
if ctype:
|
|
72
|
+
headers["Content-Type"] = ctype
|
|
73
|
+
req = urllib.request.Request(url, data=body, method=method, headers=headers)
|
|
74
|
+
try:
|
|
75
|
+
resp = urllib.request.urlopen(req, timeout=timeout or self.timeout)
|
|
76
|
+
except urllib.error.HTTPError as e:
|
|
77
|
+
detail = None
|
|
78
|
+
try:
|
|
79
|
+
detail = json.loads(e.read().decode()).get("detail") or json.loads(e.read().decode()).get("message")
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
raise PrismError(f"{method} {path} failed: HTTP {e.code}{(' — ' + detail) if detail else ''}",
|
|
83
|
+
status=e.code, detail=detail) from None
|
|
84
|
+
except urllib.error.URLError as e:
|
|
85
|
+
raise PrismError(f"{method} {path} failed: {e.reason}") from None
|
|
86
|
+
if raw:
|
|
87
|
+
return resp
|
|
88
|
+
return json.loads(resp.read().decode())
|
|
89
|
+
|
|
90
|
+
# ── high-level operations ──
|
|
91
|
+
def submit(self, path, *, align="3dep", change=True, classify=True, dem_res="auto",
|
|
92
|
+
vdatum="navd88", report=True, cad=True, reference=None, notify=False):
|
|
93
|
+
"""Submit a point cloud (.las/.laz) for processing. Returns {job_id, status, status_url,
|
|
94
|
+
download_url, ...}. `reference` (optional path) enables paired change detection."""
|
|
95
|
+
with open(path, "rb") as f:
|
|
96
|
+
data = f.read()
|
|
97
|
+
files = {"file": (os.path.basename(path), data)}
|
|
98
|
+
if reference:
|
|
99
|
+
with open(reference, "rb") as f:
|
|
100
|
+
files["reference"] = (os.path.basename(reference), f.read())
|
|
101
|
+
align = "upload"
|
|
102
|
+
fields = {"align": align, "change": "1" if change else "0",
|
|
103
|
+
"classify": "1" if classify else "0", "dem_res": dem_res, "vdatum": vdatum,
|
|
104
|
+
"report": "1" if report else "0", "cad": "1" if cad else "0",
|
|
105
|
+
"notify": "1" if notify else "0"}
|
|
106
|
+
ctype, b = _multipart(fields, files)
|
|
107
|
+
return self._req("POST", "/api/v1/jobs", body=b, ctype=ctype, timeout=self.upload_timeout)
|
|
108
|
+
|
|
109
|
+
def status(self, job_id):
|
|
110
|
+
"""Poll one job. Returns {status: queued|running|done|error, pct, stage, ...}."""
|
|
111
|
+
return self._req("GET", f"/api/v1/jobs/{job_id}")
|
|
112
|
+
|
|
113
|
+
def jobs(self):
|
|
114
|
+
"""List your account's jobs visible to the server. Returns {jobs:[...], count}."""
|
|
115
|
+
return self._req("GET", "/api/v1/jobs")
|
|
116
|
+
|
|
117
|
+
def wait(self, job_id, poll=5, timeout=14400, on_progress=None):
|
|
118
|
+
"""Block until the job is done or errors. Returns the final status dict. Raises PrismError
|
|
119
|
+
on a failed job or timeout. `on_progress(status_dict)` is called on each poll if given."""
|
|
120
|
+
t0 = time.time()
|
|
121
|
+
while True:
|
|
122
|
+
d = self.status(job_id)
|
|
123
|
+
if on_progress:
|
|
124
|
+
on_progress(d)
|
|
125
|
+
st = d.get("status")
|
|
126
|
+
if st == "done":
|
|
127
|
+
return d
|
|
128
|
+
if st == "error":
|
|
129
|
+
raise PrismError(f"job {job_id} failed: {d.get('error') or 'unknown error'}", detail=d.get("error"))
|
|
130
|
+
if time.time() - t0 > timeout:
|
|
131
|
+
raise PrismError(f"timed out waiting for job {job_id} after {timeout}s")
|
|
132
|
+
time.sleep(poll)
|
|
133
|
+
|
|
134
|
+
def download(self, job_id, out, *, clouds=True):
|
|
135
|
+
"""Stream the finished product zip to `out` (a path). Returns the path."""
|
|
136
|
+
resp = self._req("GET", f"/api/v1/jobs/{job_id}/download?clouds={1 if clouds else 0}",
|
|
137
|
+
timeout=self.upload_timeout, raw=True)
|
|
138
|
+
with open(out, "wb") as f:
|
|
139
|
+
while True:
|
|
140
|
+
chunk = resp.read(1 << 20)
|
|
141
|
+
if not chunk:
|
|
142
|
+
break
|
|
143
|
+
f.write(chunk)
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
def run(self, path, *, out=None, wait=True, poll=5, on_progress=None, **submit_kwargs):
|
|
147
|
+
"""One call: submit -> (optionally) wait -> download. Returns the output path if downloaded,
|
|
148
|
+
else the submit response dict."""
|
|
149
|
+
job = self.submit(path, **submit_kwargs)
|
|
150
|
+
jid = job["job_id"]
|
|
151
|
+
if not wait:
|
|
152
|
+
return job
|
|
153
|
+
self.wait(jid, poll=poll, on_progress=on_progress)
|
|
154
|
+
out = out or (os.path.splitext(os.path.basename(path))[0] + "_products.zip")
|
|
155
|
+
return self.download(jid, out, clouds=submit_kwargs.get("clouds", True))
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""prism — command-line interface for PRISM LiDAR Cloud.
|
|
2
|
+
|
|
3
|
+
prism run scan.las --align 3dep --wait -o products.zip
|
|
4
|
+
prism submit scan.las --align 3dep
|
|
5
|
+
prism status <job_id>
|
|
6
|
+
prism download <job_id> -o products.zip
|
|
7
|
+
prism jobs
|
|
8
|
+
|
|
9
|
+
Auth: --token prism_live_... or set PRISM_API_KEY. Host override: --base-url / PRISM_BASE_URL.
|
|
10
|
+
"""
|
|
11
|
+
import argparse, json, sys
|
|
12
|
+
from . import Client, PrismError, __version__, DEFAULT_BASE_URL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _client(args):
|
|
16
|
+
return Client(api_key=args.token, base_url=args.base_url)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _progress(d):
|
|
20
|
+
sys.stderr.write(f"\r {d.get('status','?'):9} {d.get('pct',0):5.1f}% {d.get('stage','')[:48]:48}")
|
|
21
|
+
sys.stderr.flush()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main(argv=None):
|
|
25
|
+
p = argparse.ArgumentParser(prog="prism", description="PRISM LiDAR Cloud CLI")
|
|
26
|
+
p.add_argument("--version", action="version", version=f"prism-lidarcloud {__version__}")
|
|
27
|
+
p.add_argument("--token", help="API key (or set PRISM_API_KEY)")
|
|
28
|
+
p.add_argument("--base-url", default=DEFAULT_BASE_URL, help="API host")
|
|
29
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
30
|
+
|
|
31
|
+
def add_opts(sp):
|
|
32
|
+
sp.add_argument("--align", default="3dep", choices=["none", "3dep", "upload", "existing"],
|
|
33
|
+
help="co-registration reference (default 3dep)")
|
|
34
|
+
sp.add_argument("--reference", help="reference .las/.laz for paired change detection")
|
|
35
|
+
sp.add_argument("--no-change", action="store_true", help="disable change detection")
|
|
36
|
+
sp.add_argument("--no-classify", action="store_true", help="disable semantic classification")
|
|
37
|
+
sp.add_argument("--no-report", action="store_true", help="omit the accuracy report")
|
|
38
|
+
sp.add_argument("--no-cad", action="store_true", help="omit the CAD bundle")
|
|
39
|
+
sp.add_argument("--dem-res", default="auto", help="DEM resolution (auto|25cm|50cm|100cm)")
|
|
40
|
+
sp.add_argument("--vdatum", default="navd88", help="navd88 (default, rigorous GEOID18) | ellipsoidal")
|
|
41
|
+
|
|
42
|
+
def opts(args):
|
|
43
|
+
return dict(align=args.align, reference=args.reference, change=not args.no_change,
|
|
44
|
+
classify=not args.no_classify, report=not args.no_report, cad=not args.no_cad,
|
|
45
|
+
dem_res=args.dem_res, vdatum=args.vdatum)
|
|
46
|
+
|
|
47
|
+
sp = sub.add_parser("run", help="submit, wait, and download in one step")
|
|
48
|
+
sp.add_argument("file"); add_opts(sp)
|
|
49
|
+
sp.add_argument("-o", "--out", help="output zip path")
|
|
50
|
+
sp.add_argument("--no-wait", action="store_true", help="just submit; don't wait/download")
|
|
51
|
+
sp.add_argument("--poll", type=int, default=5, help="poll interval seconds")
|
|
52
|
+
|
|
53
|
+
sp = sub.add_parser("submit", help="submit a job and print its id")
|
|
54
|
+
sp.add_argument("file"); add_opts(sp)
|
|
55
|
+
|
|
56
|
+
sp = sub.add_parser("status", help="print a job's status"); sp.add_argument("job_id")
|
|
57
|
+
sp = sub.add_parser("download", help="download a finished job's product zip")
|
|
58
|
+
sp.add_argument("job_id"); sp.add_argument("-o", "--out", required=True)
|
|
59
|
+
sub.add_parser("jobs", help="list your jobs")
|
|
60
|
+
|
|
61
|
+
args = p.parse_args(argv)
|
|
62
|
+
try:
|
|
63
|
+
c = _client(args)
|
|
64
|
+
if args.cmd == "run":
|
|
65
|
+
if args.no_wait:
|
|
66
|
+
print(json.dumps(c.submit(args.file, **opts(args)), indent=2)); return 0
|
|
67
|
+
out = c.run(args.file, out=args.out, wait=True, poll=args.poll,
|
|
68
|
+
on_progress=_progress, **opts(args))
|
|
69
|
+
sys.stderr.write("\n")
|
|
70
|
+
print(out); return 0
|
|
71
|
+
if args.cmd == "submit":
|
|
72
|
+
print(json.dumps(c.submit(args.file, **opts(args)), indent=2)); return 0
|
|
73
|
+
if args.cmd == "status":
|
|
74
|
+
print(json.dumps(c.status(args.job_id), indent=2)); return 0
|
|
75
|
+
if args.cmd == "download":
|
|
76
|
+
print(c.download(args.job_id, args.out)); return 0
|
|
77
|
+
if args.cmd == "jobs":
|
|
78
|
+
print(json.dumps(c.jobs(), indent=2)); return 0
|
|
79
|
+
except PrismError as e:
|
|
80
|
+
sys.stderr.write(f"error: {e}\n"); return 1
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
sys.stderr.write("\naborted\n"); return 130
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
sys.exit(main())
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prism-lidarcloud
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client + CLI for the PRISM LiDAR Cloud REST API (LiDAR → survey-grade DEM/DSM/CHM).
|
|
5
|
+
Author: PRISM LiDAR Cloud
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://lidarcloud.app
|
|
8
|
+
Project-URL: Documentation, https://app.lidarcloud.app/docs/api
|
|
9
|
+
Keywords: lidar,dem,dsm,chm,point-cloud,gis,remote-sensing,geospatial
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# prism-lidarcloud
|
|
17
|
+
|
|
18
|
+
Official Python client + CLI for the [PRISM LiDAR Cloud](https://lidarcloud.app) REST API —
|
|
19
|
+
turn raw airborne/UAV LiDAR (`.las`/`.laz`) into a survey-grade deliverable set (bare-earth DEM,
|
|
20
|
+
DSM, CHM, classified point cloud, per-cell uncertainty, accuracy report, CAD bundle) with one call.
|
|
21
|
+
|
|
22
|
+
Zero dependencies (Python standard library only).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
> **PyPI publish pending.** `pip install prism-lidarcloud` is *coming to PyPI* but is not
|
|
27
|
+
> live yet. Until then, install the wheel we serve from the app (works for everyone — zero
|
|
28
|
+
> dependencies, pure standard library):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# served wheel (recommended — no account/repo access needed)
|
|
32
|
+
pip install https://app.lidarcloud.app/downloads/prism_lidarcloud-1.0.0-py3-none-any.whl
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Have repo access? You can also install straight from GitHub (no clone needed — the package
|
|
36
|
+
lives in a subdirectory):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install "git+https://github.com/ebennb/PRISM_cloud.git#subdirectory=clients/python"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Already have the repo checked out? Install the local package directory instead:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ./clients/python
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Either way you get the `prism` CLI on your `PATH` and the `prism_lidarcloud` Python package.
|
|
49
|
+
(Once published, the install will simply be `pip install prism-lidarcloud`.)
|
|
50
|
+
|
|
51
|
+
## Get an API key
|
|
52
|
+
|
|
53
|
+
Sign in at <https://app.lidarcloud.app>, click your email → **API access** → **Create API key**.
|
|
54
|
+
The key (`prism_live_…`) is shown once — copy it. Keep it secret; it carries your account's quota
|
|
55
|
+
and billing.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export PRISM_API_KEY=prism_live_xxxxxxxxxxxx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# one shot: upload, wait, download the product zip
|
|
65
|
+
prism run scan.las --align 3dep --out products.zip
|
|
66
|
+
|
|
67
|
+
# same, with a real .las path (Windows)
|
|
68
|
+
prism run "Z:\DOKI_github\PRISM_cloud\Sites\Blues_Creek_Subsets\Blues_Creek_Subset_Small\LAS_20260514-191148_LAS_RGB_p051526_v2_alignedSubSmall.las" --align 3dep --out blues_creek_products.zip
|
|
69
|
+
|
|
70
|
+
# step by step
|
|
71
|
+
prism submit scan.las --align 3dep # prints {job_id, status_url, ...}
|
|
72
|
+
prism status <job_id>
|
|
73
|
+
prism download <job_id> -o products.zip
|
|
74
|
+
prism jobs # list your jobs
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Common flags: `--align none|3dep|upload|existing`, `--reference other.las` (paired change
|
|
78
|
+
detection), `--no-change`, `--no-classify`, `--no-report`, `--no-cad`, `--dem-res 25cm`,
|
|
79
|
+
`--vdatum navd88`.
|
|
80
|
+
|
|
81
|
+
## Python
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from prism_lidarcloud import Client
|
|
85
|
+
|
|
86
|
+
c = Client() # reads PRISM_API_KEY
|
|
87
|
+
out = c.run("scan.las", out="products.zip", align="3dep", change=True)
|
|
88
|
+
print("saved", out)
|
|
89
|
+
|
|
90
|
+
# or control each step
|
|
91
|
+
job = c.submit("scan.las", align="3dep")
|
|
92
|
+
final = c.wait(job["job_id"], on_progress=lambda d: print(d["status"], d["pct"]))
|
|
93
|
+
c.download(job["job_id"], "products.zip")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## What you get back
|
|
97
|
+
|
|
98
|
+
A `.zip` containing the bare-earth **DEM**, **DSM**, **CHM**, **DEM uncertainty** rasters
|
|
99
|
+
(GeoTIFF), the classified point cloud, the AOI boundary, and — unless disabled — the **accuracy
|
|
100
|
+
report** (Word) and **CAD bundle** (DXF + LandXML). Paired/3DEP runs add alignment + change-detection
|
|
101
|
+
products.
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
|
|
105
|
+
- Jobs run on ephemeral cloud workers; `wait()` polls until `done`/`error`.
|
|
106
|
+
- API jobs consume the same account quota as the web app and appear in your usage console tagged
|
|
107
|
+
**API** (vs **web**).
|
|
108
|
+
- Full reference: <https://app.lidarcloud.app/docs/api>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
prism_lidarcloud/__init__.py
|
|
4
|
+
prism_lidarcloud/cli.py
|
|
5
|
+
prism_lidarcloud.egg-info/PKG-INFO
|
|
6
|
+
prism_lidarcloud.egg-info/SOURCES.txt
|
|
7
|
+
prism_lidarcloud.egg-info/dependency_links.txt
|
|
8
|
+
prism_lidarcloud.egg-info/entry_points.txt
|
|
9
|
+
prism_lidarcloud.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prism_lidarcloud
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prism-lidarcloud"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Official Python client + CLI for the PRISM LiDAR Cloud REST API (LiDAR → survey-grade DEM/DSM/CHM)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "PRISM LiDAR Cloud" }]
|
|
13
|
+
keywords = ["lidar", "dem", "dsm", "chm", "point-cloud", "gis", "remote-sensing", "geospatial"]
|
|
14
|
+
dependencies = [] # standard library only
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://lidarcloud.app"
|
|
23
|
+
Documentation = "https://app.lidarcloud.app/docs/api"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
prism = "prism_lidarcloud.cli:main"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
packages = ["prism_lidarcloud"]
|