segment-geospatial 1.3.2__tar.gz → 1.3.3__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.
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/docs-build.yml +2 -2
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/macos.yml +1 -1
- segment_geospatial-1.3.3/.github/workflows/publish.yml +54 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/ubuntu.yml +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/windows.yml +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.pre-commit-config.yaml +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/PKG-INFO +1 -1
- segment_geospatial-1.3.3/agent-harness/SAMGEO.md +59 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/README.md +148 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/__init__.py +3 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/__main__.py +6 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/__init__.py +1 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/data.py +229 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/export.py +201 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/model.py +262 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/project.py +266 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/segment.py +369 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/session.py +182 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/core/vector.py +155 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/samgeo_cli.py +748 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/skills/SKILL.md +123 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/tests/TEST.md +198 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/tests/__init__.py +0 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/tests/test_core.py +415 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/tests/test_full_e2e.py +480 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/utils/__init__.py +1 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/utils/repl_skin.py +185 -0
- segment_geospatial-1.3.3/agent-harness/cli_anything/samgeo/utils/samgeo_backend.py +155 -0
- segment_geospatial-1.3.3/agent-harness/setup.py +30 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_automatic.ipynb +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/usage.md +2 -1
- segment_geospatial-1.3.3/package_plugin.py +118 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/pyproject.toml +2 -2
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/samgeo_plugin.py +97 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/requirements_docs.txt +2 -2
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/__init__.py +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/api.py +66 -22
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/common.py +123 -6
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/hq_sam.py +19 -7
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/samgeo.py +19 -7
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/samgeo2.py +24 -11
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/samgeo3.py +14 -51
- segment_geospatial-1.3.3/scripts/upload_to_qgis_plugin_repo.py +74 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/PKG-INFO +1 -1
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/SOURCES.txt +25 -0
- segment_geospatial-1.3.3/tests/test_api.py +274 -0
- segment_geospatial-1.3.3/tests/test_common.py +151 -0
- segment_geospatial-1.3.2/tests/test_api.py +0 -161
- segment_geospatial-1.3.2/tests/test_common.py +0 -62
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.editorconfig +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/FUNDING.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/dependabot.yaml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/docker-image.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/docker-publish.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/docs.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/draft-pdf.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.github/workflows/pypi.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/.gitignore +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/CITATION.cff +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/CODE_OF_CONDUCT.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/Dockerfile +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/LICENSE +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/MANIFEST.in +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/README.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/CNAME +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/api.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/assets/README.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/assets/favicon.png +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/assets/logo.png +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/assets/logo_rect.png +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/caption.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/changelog.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/changelog_update.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/common.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/contributing.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/detectree2.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/arcgis.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/automatic_mask_generator.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/automatic_mask_generator_hq.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/box_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/data/tree_boxes.geojson +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/detectree2.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/fast_sam.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/image_captioning.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/input_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/input_prompts_hq.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/maxar_open_data.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_box_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_point_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_predictor.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_text_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam2_video.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_automated_segmentation.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_batch_segmentation.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_box_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_image_segmentation.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_image_segmentation_jpg.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_interactive.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_object_tracking.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_point_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_point_prompts_batch.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_tiled_segmentation.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_video_masks.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_video_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/sam3_video_segmentation.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/satellite-predictor.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/satellite.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/text_prompts.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/text_prompts_batch.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/text_swimming_pools.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/examples/tree_mapping.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/faq.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/fast_sam.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/hq_sam.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/index.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/installation.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/overrides/main.html +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/samgeo.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/samgeo2.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/samgeo3.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/text_sam.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/workshops/AIforGood_2025.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/workshops/IPPN_2024.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/workshops/cn_workshop.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/workshops/jupytext.toml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/docs/workshops/purdue.ipynb +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/mkdocs.yml +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/paper/10.21105.joss.05663.pdf +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/paper/paper.bib +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/paper/paper.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/LICENSE +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/README.md +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/__init__.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/icons/icon.png +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/install_plugin.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/install_plugin.sh +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/map_tools.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/metadata.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/resources.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/qgis-samgeo-plugin/test_plugin.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/requirements.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/requirements_dev.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/caption.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/detectree2.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/fast_sam.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/fer.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/text_sam.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/samgeo/utmconv.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/dependency_links.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/entry_points.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/requires.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/segment_geospatial.egg-info/top_level.txt +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/setup.cfg +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/tests/__init__.py +0 -0
- {segment_geospatial-1.3.2 → segment_geospatial-1.3.3}/tests/test_samgeo.py +0 -0
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
with:
|
|
15
15
|
fetch-depth: 0
|
|
16
16
|
- name: Setup micromamba
|
|
17
|
-
uses: mamba-org/setup-micromamba@
|
|
17
|
+
uses: mamba-org/setup-micromamba@v3
|
|
18
18
|
with:
|
|
19
19
|
environment-name: docs-env
|
|
20
20
|
create-args: >-
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
pip install .
|
|
35
35
|
- run: mkdocs build
|
|
36
36
|
- name: Deploy to Netlify
|
|
37
|
-
uses: nwtgck/actions-netlify@
|
|
37
|
+
uses: nwtgck/actions-netlify@v4.0
|
|
38
38
|
with:
|
|
39
39
|
publish-dir: "./site"
|
|
40
40
|
production-branch: master
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
tag:
|
|
9
|
+
description: "Release tag to publish (must already exist on GitHub)"
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
publish:
|
|
18
|
+
name: Publish plugin to plugins.qgis.org
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
env:
|
|
21
|
+
TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
22
|
+
PLUGIN_DIR: qgis-samgeo-plugin
|
|
23
|
+
PLUGIN_NAME: samgeo_plugin
|
|
24
|
+
ZIP_PATH: dist/samgeo_plugin.zip
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v6
|
|
27
|
+
|
|
28
|
+
- uses: actions/setup-python@v6
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.13"
|
|
31
|
+
|
|
32
|
+
- name: Build plugin zip
|
|
33
|
+
run: python "package_plugin.py" --source "$PLUGIN_DIR" --name "$PLUGIN_NAME" --output "$ZIP_PATH"
|
|
34
|
+
|
|
35
|
+
- name: Verify metadata version matches release tag
|
|
36
|
+
run: |
|
|
37
|
+
metadata_version=$(grep '^version=' "$PLUGIN_DIR/metadata.txt" | cut -d'=' -f2 | tr -d '[:space:]')
|
|
38
|
+
tag_version="${TAG#v}"
|
|
39
|
+
if [ "$metadata_version" != "$tag_version" ]; then
|
|
40
|
+
echo "::error::metadata.txt version ($metadata_version) does not match release tag ($tag_version)"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
- name: Attach zip to GitHub release
|
|
45
|
+
env:
|
|
46
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
47
|
+
run: |
|
|
48
|
+
gh release upload "$TAG" "$ZIP_PATH" --clobber
|
|
49
|
+
|
|
50
|
+
- name: Upload to plugins.qgis.org
|
|
51
|
+
env:
|
|
52
|
+
QGIS_PLUGIN_REPO_USERNAME: ${{ secrets.QGIS_PLUGIN_REPO_USERNAME }}
|
|
53
|
+
QGIS_PLUGIN_REPO_PASSWORD: ${{ secrets.QGIS_PLUGIN_REPO_PASSWORD }}
|
|
54
|
+
run: python scripts/upload_to_qgis_plugin_repo.py "$ZIP_PATH"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SAMGEO: Agent Harness SOP
|
|
2
|
+
|
|
3
|
+
## Software Overview
|
|
4
|
+
|
|
5
|
+
**segment-geospatial** (samgeo) is a Python package for segmenting geospatial data
|
|
6
|
+
using Meta AI's Segment Anything Model (SAM) family. It wraps SAM v1, SAM 2, SAM 3,
|
|
7
|
+
FastSAM, HQ-SAM, and LangSAM with geospatial-aware I/O (GeoTIFF, GeoPackage, etc.).
|
|
8
|
+
|
|
9
|
+
## Backend
|
|
10
|
+
|
|
11
|
+
The backend is the `samgeo` Python library itself. Unlike GUI applications that need
|
|
12
|
+
a headless CLI invocation, samgeo is already a Python library — the CLI harness imports
|
|
13
|
+
and calls its classes/functions directly.
|
|
14
|
+
|
|
15
|
+
**Key classes:**
|
|
16
|
+
- `SamGeo` (v1) — `samgeo.samgeo.SamGeo`
|
|
17
|
+
- `SamGeo2` (v2) — `samgeo.samgeo2.SamGeo2`
|
|
18
|
+
- `SamGeo3` (v3) — `samgeo.samgeo3.SamGeo3`
|
|
19
|
+
- `LangSAM` — `samgeo.text_sam.LangSAM`
|
|
20
|
+
|
|
21
|
+
**Key utility functions** (from `samgeo.common`):
|
|
22
|
+
- `tms_to_geotiff()` — Download TMS tiles as GeoTIFF
|
|
23
|
+
- `raster_to_vector()` / `raster_to_gpkg()` / `raster_to_shp()` / `raster_to_geojson()`
|
|
24
|
+
- `reproject()`, `split_raster()`, `image_to_cog()`
|
|
25
|
+
- `get_profile()`, `get_basemaps()`
|
|
26
|
+
|
|
27
|
+
## Data Model
|
|
28
|
+
|
|
29
|
+
**Project state** is a JSON file tracking:
|
|
30
|
+
- Source image path, CRS, bounds
|
|
31
|
+
- Active model type and parameters
|
|
32
|
+
- Generated mask paths
|
|
33
|
+
- Vector output paths
|
|
34
|
+
- Operation history (for undo/redo)
|
|
35
|
+
|
|
36
|
+
**File formats:**
|
|
37
|
+
- Input: GeoTIFF, PNG, JPG, NumPy arrays, URLs
|
|
38
|
+
- Mask output: GeoTIFF (raster masks)
|
|
39
|
+
- Vector output: GeoPackage (.gpkg), Shapefile (.shp), GeoJSON (.geojson)
|
|
40
|
+
- Project: JSON (.json)
|
|
41
|
+
|
|
42
|
+
## CLI Command Groups
|
|
43
|
+
|
|
44
|
+
| Group | Purpose |
|
|
45
|
+
|-------|---------|
|
|
46
|
+
| `project` | Create, open, save, inspect projects |
|
|
47
|
+
| `model` | List, download, inspect SAM models |
|
|
48
|
+
| `segment` | Automatic, point, box, and text segmentation |
|
|
49
|
+
| `data` | Download tiles, inspect rasters, reproject, split |
|
|
50
|
+
| `vector` | Convert masks to vectors, inspect, filter |
|
|
51
|
+
| `export` | Export masks and vectors to various formats |
|
|
52
|
+
| `session` | Undo/redo, history, session state |
|
|
53
|
+
|
|
54
|
+
## Dependencies
|
|
55
|
+
|
|
56
|
+
- `segment-geospatial` (the package itself — hard dependency)
|
|
57
|
+
- `click` (CLI framework)
|
|
58
|
+
- `prompt_toolkit` (REPL)
|
|
59
|
+
- PyTorch + SAM model weights (downloaded on first use)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# cli-anything-samgeo
|
|
2
|
+
|
|
3
|
+
CLI harness for [segment-geospatial](https://github.com/opengeos/segment-geospatial) — segment geospatial imagery using SAM models from the command line.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python 3.10+
|
|
8
|
+
- segment-geospatial: `pip install segment-geospatial[all]`
|
|
9
|
+
- PyTorch with CUDA (recommended) or CPU
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd agent-harness
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This installs the `cli-anything-samgeo` command in your PATH.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Create a project
|
|
24
|
+
cli-anything-samgeo project new -n my-seg -o project.json -s image.tif
|
|
25
|
+
|
|
26
|
+
# Run automatic segmentation
|
|
27
|
+
cli-anything-samgeo --project project.json segment automatic -o masks.tif
|
|
28
|
+
|
|
29
|
+
# Convert masks to vectors
|
|
30
|
+
cli-anything-samgeo --project project.json vector convert masks.tif output.gpkg
|
|
31
|
+
|
|
32
|
+
# Export as GeoJSON
|
|
33
|
+
cli-anything-samgeo --project project.json export render output.geojson -f geojson
|
|
34
|
+
|
|
35
|
+
# All commands support --json for machine-readable output
|
|
36
|
+
cli-anything-samgeo --json model list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Command Groups
|
|
40
|
+
|
|
41
|
+
| Command | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| `project` | Create, open, inspect projects |
|
|
44
|
+
| `model` | List, inspect, check SAM models |
|
|
45
|
+
| `segment` | Automatic, point, box, text segmentation |
|
|
46
|
+
| `data` | Download tiles, raster info, reproject, split |
|
|
47
|
+
| `vector` | Convert masks to vectors, inspect, filter |
|
|
48
|
+
| `export` | Export masks to various formats |
|
|
49
|
+
| `session` | Session status and history |
|
|
50
|
+
|
|
51
|
+
## Interactive REPL
|
|
52
|
+
|
|
53
|
+
Run without arguments to enter the interactive REPL:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cli-anything-samgeo
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## JSON Output
|
|
60
|
+
|
|
61
|
+
Add `--json` before any command for machine-readable output:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cli-anything-samgeo --json data info image.tif
|
|
65
|
+
cli-anything-samgeo --json model list
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Using with Claude Code
|
|
69
|
+
|
|
70
|
+
This CLI ships with a `SKILL.md` file that lets Claude Code discover and use all
|
|
71
|
+
commands automatically. There are two ways to enable it.
|
|
72
|
+
|
|
73
|
+
### Option 1: Add SKILL.md to your CLAUDE.md
|
|
74
|
+
|
|
75
|
+
Append a reference to the skill file in your project or user `CLAUDE.md`:
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
# In your CLAUDE.md
|
|
79
|
+
Read the skill file at /path/to/agent-harness/cli_anything/samgeo/skills/SKILL.md
|
|
80
|
+
for the full cli-anything-samgeo command reference. Use `--json` for all
|
|
81
|
+
cli-anything-samgeo commands so output is machine-readable.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Replace `/path/to/` with the actual absolute path. Claude Code reads `CLAUDE.md`
|
|
85
|
+
at the start of every conversation, so it will know the CLI exists and how to
|
|
86
|
+
call it.
|
|
87
|
+
|
|
88
|
+
### Option 2: Point Claude Code at the skill on the fly
|
|
89
|
+
|
|
90
|
+
In any Claude Code conversation, paste:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Read agent-harness/cli_anything/samgeo/skills/SKILL.md and use that CLI
|
|
94
|
+
to segment this satellite image.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Claude Code will read the skill file, learn the command syntax, and start
|
|
98
|
+
using `cli-anything-samgeo` with `--json` output.
|
|
99
|
+
|
|
100
|
+
### Example Claude Code session
|
|
101
|
+
|
|
102
|
+
Once Claude Code knows about the skill, you can give it natural-language tasks:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
> Segment all buildings in satellite.tif and export the results as a GeoPackage.
|
|
106
|
+
|
|
107
|
+
# Claude Code will run:
|
|
108
|
+
cli-anything-samgeo --json project new -n buildings -o project.json -s satellite.tif -t sam2
|
|
109
|
+
cli-anything-samgeo --json --project project.json segment automatic -o masks.tif
|
|
110
|
+
cli-anything-samgeo --json vector convert masks.tif buildings.gpkg
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
> Download OpenStreetMap tiles for downtown Portland and tell me about the image.
|
|
115
|
+
|
|
116
|
+
# Claude Code will run:
|
|
117
|
+
cli-anything-samgeo --json data download-tiles -o portland.tif -b "-122.68,45.51,-122.66,45.53" -z 17
|
|
118
|
+
cli-anything-samgeo --json data info portland.tif
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Tips for Claude Code usage
|
|
122
|
+
|
|
123
|
+
- The `--json` flag is essential — it gives Claude Code structured output it can
|
|
124
|
+
parse and reason about, rather than human-formatted tables.
|
|
125
|
+
- The `--project` flag must appear *before* the command group (e.g.,
|
|
126
|
+
`--project proj.json segment automatic`, not `segment automatic --project proj.json`).
|
|
127
|
+
- Claude Code can chain multiple commands in sequence to build full pipelines
|
|
128
|
+
(download → segment → vectorize → export).
|
|
129
|
+
- Use `model check sam2` to let Claude Code verify a model backend is installed
|
|
130
|
+
before attempting segmentation.
|
|
131
|
+
|
|
132
|
+
## Running Tests
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd agent-harness
|
|
136
|
+
python -m pytest cli_anything/samgeo/tests/ -v -s
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Supported Models
|
|
140
|
+
|
|
141
|
+
| Type | Models | Install |
|
|
142
|
+
|------|--------|---------|
|
|
143
|
+
| SAM v1 | vit_h, vit_l, vit_b | `pip install segment-geospatial` |
|
|
144
|
+
| SAM 2 | hiera-tiny/small/base-plus/large | `pip install segment-geospatial[samgeo2]` |
|
|
145
|
+
| SAM 3 | facebook/sam3 | `pip install segment-geospatial[samgeo3]` |
|
|
146
|
+
| FastSAM | FastSAM-x, FastSAM-s | `pip install segment-geospatial[fast]` |
|
|
147
|
+
| HQ-SAM | vit_h, vit_l, vit_b, vit_tiny | `pip install segment-geospatial[hq]` |
|
|
148
|
+
| LangSAM | text-based (SAM2 backend) | `pip install segment-geospatial[text]` |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core modules for cli-anything-samgeo."""
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Data operations: download tiles, inspect rasters, reproject, split."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from cli_anything.samgeo.utils.samgeo_backend import (
|
|
6
|
+
get_common_module,
|
|
7
|
+
get_rasterio,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def raster_info(path):
|
|
12
|
+
"""Get information about a raster file.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
path: Path to the raster file.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
dict: Raster metadata.
|
|
19
|
+
"""
|
|
20
|
+
rasterio = get_rasterio()
|
|
21
|
+
path = os.path.abspath(path)
|
|
22
|
+
|
|
23
|
+
if not os.path.exists(path):
|
|
24
|
+
raise FileNotFoundError(f"Raster file not found: {path}")
|
|
25
|
+
|
|
26
|
+
with rasterio.open(path) as src:
|
|
27
|
+
info = {
|
|
28
|
+
"path": path,
|
|
29
|
+
"driver": src.driver,
|
|
30
|
+
"crs": str(src.crs) if src.crs else None,
|
|
31
|
+
"width": src.width,
|
|
32
|
+
"height": src.height,
|
|
33
|
+
"bands": src.count,
|
|
34
|
+
"dtype": str(src.dtypes[0]) if src.dtypes else None,
|
|
35
|
+
"bounds": {
|
|
36
|
+
"left": src.bounds.left,
|
|
37
|
+
"bottom": src.bounds.bottom,
|
|
38
|
+
"right": src.bounds.right,
|
|
39
|
+
"top": src.bounds.top,
|
|
40
|
+
},
|
|
41
|
+
"transform": list(src.transform)[:6],
|
|
42
|
+
"nodata": src.nodata,
|
|
43
|
+
"file_size": os.path.getsize(path),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return info
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def download_tiles(
|
|
50
|
+
output,
|
|
51
|
+
bbox,
|
|
52
|
+
zoom=None,
|
|
53
|
+
resolution=None,
|
|
54
|
+
source="OpenStreetMap",
|
|
55
|
+
crs="EPSG:3857",
|
|
56
|
+
to_cog=False,
|
|
57
|
+
**kwargs,
|
|
58
|
+
):
|
|
59
|
+
"""Download TMS tiles as a GeoTIFF.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
output: Output file path.
|
|
63
|
+
bbox: Bounding box as [west, south, east, north].
|
|
64
|
+
zoom: Zoom level. If None, auto-detected from resolution.
|
|
65
|
+
resolution: Resolution in meters. Alternative to zoom.
|
|
66
|
+
source: Tile source name or URL.
|
|
67
|
+
crs: Output CRS.
|
|
68
|
+
to_cog: Whether to convert to Cloud-Optimized GeoTIFF.
|
|
69
|
+
**kwargs: Additional kwargs.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
dict: Result with output path and file size.
|
|
73
|
+
"""
|
|
74
|
+
common = get_common_module()
|
|
75
|
+
output = os.path.abspath(output)
|
|
76
|
+
parent = os.path.dirname(output)
|
|
77
|
+
if parent:
|
|
78
|
+
os.makedirs(parent, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
common.tms_to_geotiff(
|
|
81
|
+
output=output,
|
|
82
|
+
bbox=bbox,
|
|
83
|
+
zoom=zoom,
|
|
84
|
+
resolution=resolution,
|
|
85
|
+
source=source,
|
|
86
|
+
crs=crs,
|
|
87
|
+
to_cog=to_cog,
|
|
88
|
+
**kwargs,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
result = {
|
|
92
|
+
"output": output,
|
|
93
|
+
"file_size": os.path.getsize(output) if os.path.exists(output) else 0,
|
|
94
|
+
"bbox": bbox,
|
|
95
|
+
"zoom": zoom,
|
|
96
|
+
"source": source,
|
|
97
|
+
"crs": crs,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def reproject_raster(
|
|
104
|
+
input_path, output_path, dst_crs="EPSG:4326", resampling="nearest", to_cog=True
|
|
105
|
+
):
|
|
106
|
+
"""Reproject a raster file to a new CRS.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
input_path: Input raster path.
|
|
110
|
+
output_path: Output raster path.
|
|
111
|
+
dst_crs: Target CRS.
|
|
112
|
+
resampling: Resampling method.
|
|
113
|
+
to_cog: Whether to output as COG.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
dict: Result with output path and new CRS.
|
|
117
|
+
"""
|
|
118
|
+
common = get_common_module()
|
|
119
|
+
input_path = os.path.abspath(input_path)
|
|
120
|
+
output_path = os.path.abspath(output_path)
|
|
121
|
+
|
|
122
|
+
if not os.path.exists(input_path):
|
|
123
|
+
raise FileNotFoundError(f"Input raster not found: {input_path}")
|
|
124
|
+
|
|
125
|
+
parent = os.path.dirname(output_path)
|
|
126
|
+
if parent:
|
|
127
|
+
os.makedirs(parent, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
common.reproject(
|
|
130
|
+
image=input_path,
|
|
131
|
+
output=output_path,
|
|
132
|
+
dst_crs=dst_crs,
|
|
133
|
+
resampling=resampling,
|
|
134
|
+
to_cog=to_cog,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
result = {
|
|
138
|
+
"input": input_path,
|
|
139
|
+
"output": output_path,
|
|
140
|
+
"dst_crs": dst_crs,
|
|
141
|
+
"file_size": os.path.getsize(output_path) if os.path.exists(output_path) else 0,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def split_raster_tiles(input_path, out_dir, tile_size=256, overlap=0):
|
|
148
|
+
"""Split a raster into tiles.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
input_path: Input raster path.
|
|
152
|
+
out_dir: Output directory for tiles.
|
|
153
|
+
tile_size: Tile size in pixels (int or [width, height]).
|
|
154
|
+
overlap: Overlap in pixels.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
dict: Result with tile count and output directory.
|
|
158
|
+
"""
|
|
159
|
+
common = get_common_module()
|
|
160
|
+
input_path = os.path.abspath(input_path)
|
|
161
|
+
out_dir = os.path.abspath(out_dir)
|
|
162
|
+
|
|
163
|
+
if not os.path.exists(input_path):
|
|
164
|
+
raise FileNotFoundError(f"Input raster not found: {input_path}")
|
|
165
|
+
|
|
166
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
167
|
+
|
|
168
|
+
common.split_raster(
|
|
169
|
+
filename=input_path,
|
|
170
|
+
out_dir=out_dir,
|
|
171
|
+
tile_size=tile_size,
|
|
172
|
+
overlap=overlap,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
tiles = [f for f in os.listdir(out_dir) if f.endswith((".tif", ".tiff"))]
|
|
176
|
+
|
|
177
|
+
result = {
|
|
178
|
+
"input": input_path,
|
|
179
|
+
"output_dir": out_dir,
|
|
180
|
+
"tile_count": len(tiles),
|
|
181
|
+
"tile_size": tile_size,
|
|
182
|
+
"overlap": overlap,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def image_to_cog(source, output=None, profile="deflate"):
|
|
189
|
+
"""Convert a raster to Cloud-Optimized GeoTIFF.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
source: Input raster path.
|
|
193
|
+
output: Output path. If None, writes to a new *_cog.tif file
|
|
194
|
+
alongside the source.
|
|
195
|
+
profile: COG profile.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
dict: Result with output path.
|
|
199
|
+
"""
|
|
200
|
+
common = get_common_module()
|
|
201
|
+
source = os.path.abspath(source)
|
|
202
|
+
|
|
203
|
+
if not os.path.exists(source):
|
|
204
|
+
raise FileNotFoundError(f"Source raster not found: {source}")
|
|
205
|
+
|
|
206
|
+
common.image_to_cog(source=source, dst_path=output, profile=profile)
|
|
207
|
+
|
|
208
|
+
out = output or source
|
|
209
|
+
result = {
|
|
210
|
+
"output": os.path.abspath(out),
|
|
211
|
+
"file_size": os.path.getsize(out) if os.path.exists(out) else 0,
|
|
212
|
+
"profile": profile,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def list_basemaps(free_only=True):
|
|
219
|
+
"""List available TMS basemap sources.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
free_only: Only list free tile services.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
list: List of basemap names.
|
|
226
|
+
"""
|
|
227
|
+
common = get_common_module()
|
|
228
|
+
basemaps = common.get_basemaps(free_only=free_only)
|
|
229
|
+
return sorted(basemaps.keys()) if isinstance(basemaps, dict) else sorted(basemaps)
|