segment-geospatial 1.1.0__tar.gz → 1.2.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.
Files changed (124) hide show
  1. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/draft-pdf.yml +1 -1
  2. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.pre-commit-config.yaml +1 -1
  3. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/PKG-INFO +2 -1
  4. segment_geospatial-1.2.0/docs/examples/sam3_tiled_segmentation.ipynb +274 -0
  5. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/mkdocs.yml +1 -0
  6. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/pyproject.toml +2 -2
  7. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/requirements.txt +1 -0
  8. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/__init__.py +1 -1
  9. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/common.py +218 -1
  10. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/samgeo3.py +436 -6
  11. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/segment_geospatial.egg-info/PKG-INFO +2 -1
  12. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/segment_geospatial.egg-info/SOURCES.txt +1 -0
  13. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/segment_geospatial.egg-info/requires.txt +1 -0
  14. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.editorconfig +0 -0
  15. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/FUNDING.yml +0 -0
  16. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  19. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/dependabot.yaml +0 -0
  20. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/docker-image.yml +0 -0
  21. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/docker-publish.yml +0 -0
  22. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/docs-build.yml +0 -0
  23. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/docs.yml +0 -0
  24. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/macos.yml +0 -0
  25. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/pypi.yml +0 -0
  26. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/ubuntu.yml +0 -0
  27. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.github/workflows/windows.yml +0 -0
  28. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/.gitignore +0 -0
  29. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/CITATION.cff +0 -0
  30. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  31. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/Dockerfile +0 -0
  32. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/LICENSE +0 -0
  33. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/MANIFEST.in +0 -0
  34. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/README.md +0 -0
  35. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/CNAME +0 -0
  36. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/assets/README.md +0 -0
  37. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/assets/favicon.png +0 -0
  38. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/assets/logo.png +0 -0
  39. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/assets/logo_rect.png +0 -0
  40. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/caption.md +0 -0
  41. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/changelog.md +0 -0
  42. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/changelog_update.py +0 -0
  43. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/common.md +0 -0
  44. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/contributing.md +0 -0
  45. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/arcgis.ipynb +0 -0
  46. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/automatic_mask_generator.ipynb +0 -0
  47. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/automatic_mask_generator_hq.ipynb +0 -0
  48. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/box_prompts.ipynb +0 -0
  49. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/data/tree_boxes.geojson +0 -0
  50. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/fast_sam.ipynb +0 -0
  51. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/image_captioning.ipynb +0 -0
  52. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/input_prompts.ipynb +0 -0
  53. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/input_prompts_hq.ipynb +0 -0
  54. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/maxar_open_data.ipynb +0 -0
  55. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_automatic.ipynb +0 -0
  56. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_box_prompts.ipynb +0 -0
  57. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_point_prompts.ipynb +0 -0
  58. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_predictor.ipynb +0 -0
  59. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_text_prompts.ipynb +0 -0
  60. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam2_video.ipynb +0 -0
  61. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_automated_segmentation.ipynb +0 -0
  62. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_batch_segmentation.ipynb +0 -0
  63. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_box_prompts.ipynb +0 -0
  64. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_image_segmentation.ipynb +0 -0
  65. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_image_segmentation_jpg.ipynb +0 -0
  66. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_interactive.ipynb +0 -0
  67. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_object_tracking.ipynb +0 -0
  68. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_point_prompts.ipynb +0 -0
  69. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_point_prompts_batch.ipynb +0 -0
  70. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_video_masks.ipynb +0 -0
  71. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_video_prompts.ipynb +0 -0
  72. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/sam3_video_segmentation.ipynb +0 -0
  73. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/satellite-predictor.ipynb +0 -0
  74. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/satellite.ipynb +0 -0
  75. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/text_prompts.ipynb +0 -0
  76. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/text_prompts_batch.ipynb +0 -0
  77. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/text_swimming_pools.ipynb +0 -0
  78. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/examples/tree_mapping.ipynb +0 -0
  79. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/faq.md +0 -0
  80. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/fast_sam.md +0 -0
  81. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/hq_sam.md +0 -0
  82. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/index.md +0 -0
  83. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/installation.md +0 -0
  84. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/overrides/main.html +0 -0
  85. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/samgeo.md +0 -0
  86. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/samgeo2.md +0 -0
  87. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/samgeo3.md +0 -0
  88. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/text_sam.md +0 -0
  89. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/usage.md +0 -0
  90. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/workshops/AIforGood_2025.ipynb +0 -0
  91. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/workshops/IPPN_2024.ipynb +0 -0
  92. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/workshops/cn_workshop.ipynb +0 -0
  93. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/workshops/jupytext.toml +0 -0
  94. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/docs/workshops/purdue.ipynb +0 -0
  95. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/paper/10.21105.joss.05663.pdf +0 -0
  96. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/paper/paper.bib +0 -0
  97. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/paper/paper.md +0 -0
  98. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/LICENSE +0 -0
  99. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/README.md +0 -0
  100. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/__init__.py +0 -0
  101. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/icons/icon.png +0 -0
  102. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/install_plugin.py +0 -0
  103. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/install_plugin.sh +0 -0
  104. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/map_tools.py +0 -0
  105. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/metadata.txt +0 -0
  106. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/resources.py +0 -0
  107. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/samgeo_plugin.py +0 -0
  108. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/qgis-samgeo-plugin/test_plugin.py +0 -0
  109. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/requirements_dev.txt +0 -0
  110. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/requirements_docs.txt +0 -0
  111. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/caption.py +0 -0
  112. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/fast_sam.py +0 -0
  113. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/fer.py +0 -0
  114. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/hq_sam.py +0 -0
  115. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/samgeo.py +0 -0
  116. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/samgeo2.py +0 -0
  117. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/text_sam.py +0 -0
  118. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/samgeo/utmconv.py +0 -0
  119. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/segment_geospatial.egg-info/dependency_links.txt +0 -0
  120. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/segment_geospatial.egg-info/top_level.txt +0 -0
  121. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/setup.cfg +0 -0
  122. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/tests/__init__.py +0 -0
  123. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/tests/test_common.py +0 -0
  124. {segment_geospatial-1.1.0 → segment_geospatial-1.2.0}/tests/test_samgeo.py +0 -0
@@ -14,7 +14,7 @@ jobs:
14
14
  # This should be the path to the paper within your repo.
15
15
  paper-path: paper/paper.md
16
16
  - name: Upload
17
- uses: actions/upload-artifact@v5
17
+ uses: actions/upload-artifact@v6
18
18
  with:
19
19
  name: paper
20
20
  # This is the output path where Pandoc will write the compiled
@@ -12,7 +12,7 @@ repos:
12
12
  args: ["--maxkb=500"]
13
13
 
14
14
  - repo: https://github.com/astral-sh/ruff-pre-commit
15
- rev: v0.7.4
15
+ rev: v0.14.9
16
16
  hooks:
17
17
  - id: ruff
18
18
  types_or: [pyi, jupyter]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: segment-geospatial
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Meta AI' Segment Anything Model (SAM) for Geospatial Data.
5
5
  Author-email: Qiusheng Wu <giswqs@gmail.com>
6
6
  License: MIT license
@@ -29,6 +29,7 @@ Requires-Dist: pyproj
29
29
  Requires-Dist: rasterio
30
30
  Requires-Dist: segment_anything
31
31
  Requires-Dist: shapely
32
+ Requires-Dist: smoothify
32
33
  Requires-Dist: torch
33
34
  Requires-Dist: torchvision
34
35
  Requires-Dist: tqdm
@@ -0,0 +1,274 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Tiled Segmentation for Large GeoTIFF Images with SAM 3\n",
8
+ "\n",
9
+ "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/segment-geospatial/blob/main/docs/examples/sam3_tiled_segmentation.ipynb)\n",
10
+ "\n",
11
+ "This notebook demonstrates how to use the sliding window (tiled) approach to segment large GeoTIFF images that would otherwise exceed GPU memory limits.\n",
12
+ "\n",
13
+ "## Overview\n",
14
+ "\n",
15
+ "When working with large satellite or aerial imagery, loading the entire image into GPU memory for SAM 3 inference is often not feasible. The `generate_masks_tiled()` method solves this by:\n",
16
+ "\n",
17
+ "1. **Dividing** the large image into smaller, overlapping tiles\n",
18
+ "2. **Processing** each tile independently with SAM 3\n",
19
+ "3. **Merging** the results back into a seamless output mask\n",
20
+ "4. **Preserving** georeferencing information from the original GeoTIFF\n",
21
+ "\n",
22
+ "## Key Parameters\n",
23
+ "\n",
24
+ "- **tile_size**: Size of each processing tile (default: 1024 pixels)\n",
25
+ "- **overlap**: Overlap between adjacent tiles (default: 128 pixels) - helps prevent edge artifacts\n",
26
+ "- **prompt**: Text description of objects to segment\n",
27
+ "- **min_size/max_size**: Filter objects by pixel area"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "markdown",
32
+ "metadata": {},
33
+ "source": [
34
+ "## Installation\n",
35
+ "\n",
36
+ "Uncomment and run the following cell to install the required packages:"
37
+ ]
38
+ },
39
+ {
40
+ "cell_type": "code",
41
+ "execution_count": null,
42
+ "metadata": {},
43
+ "outputs": [],
44
+ "source": [
45
+ "# %pip install \"segment-geospatial[samgeo3]\""
46
+ ]
47
+ },
48
+ {
49
+ "cell_type": "markdown",
50
+ "metadata": {},
51
+ "source": [
52
+ "## Import Libraries"
53
+ ]
54
+ },
55
+ {
56
+ "cell_type": "code",
57
+ "execution_count": null,
58
+ "metadata": {},
59
+ "outputs": [],
60
+ "source": [
61
+ "import os\n",
62
+ "import leafmap\n",
63
+ "from samgeo import SamGeo3, common"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "markdown",
68
+ "metadata": {},
69
+ "source": [
70
+ "## Download Sample Data\n",
71
+ "\n",
72
+ "We'll use a sample satellite image for this demonstration. You can replace this with your own large GeoTIFF."
73
+ ]
74
+ },
75
+ {
76
+ "cell_type": "code",
77
+ "execution_count": null,
78
+ "metadata": {},
79
+ "outputs": [],
80
+ "source": [
81
+ "# Download a sample satellite image\n",
82
+ "url = \"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/naip_water_train.tif\"\n",
83
+ "image_path = \"naip_water_train.tif\"\n",
84
+ "\n",
85
+ "if not os.path.exists(image_path):\n",
86
+ " common.download_file(url, image_path)"
87
+ ]
88
+ },
89
+ {
90
+ "cell_type": "markdown",
91
+ "metadata": {},
92
+ "source": [
93
+ "## Check Raster Info\n",
94
+ "\n",
95
+ "Let's check the dimensions of our image to understand why tiling might be necessary."
96
+ ]
97
+ },
98
+ {
99
+ "cell_type": "code",
100
+ "execution_count": null,
101
+ "metadata": {},
102
+ "outputs": [],
103
+ "source": [
104
+ "common.print_raster_info(image_path)"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "markdown",
109
+ "metadata": {},
110
+ "source": [
111
+ "## Initialize SamGeo3"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "metadata": {},
118
+ "outputs": [],
119
+ "source": [
120
+ "sam = SamGeo3(backend=\"meta\")"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "markdown",
125
+ "metadata": {},
126
+ "source": [
127
+ "## Run Tiled Segmentation\n",
128
+ "\n",
129
+ "Now we'll use the `generate_masks_tiled()` method to process the image. This method:\n",
130
+ "\n",
131
+ "1. Reads the image tile by tile\n",
132
+ "2. Processes each tile with SAM3\n",
133
+ "3. Merges overlapping regions intelligently\n",
134
+ "4. Saves the result as a georeferenced GeoTIFF"
135
+ ]
136
+ },
137
+ {
138
+ "cell_type": "code",
139
+ "execution_count": null,
140
+ "metadata": {},
141
+ "outputs": [],
142
+ "source": [
143
+ "# Output path for the mask\n",
144
+ "output_path = \"segmentation_mask.tif\"\n",
145
+ "\n",
146
+ "# Run tiled segmentation\n",
147
+ "sam.generate_masks_tiled(\n",
148
+ " source=image_path,\n",
149
+ " prompt=\"water\", # Change prompt based on what you want to segment\n",
150
+ " output=output_path,\n",
151
+ " tile_size=1024, # Size of each tile (adjust based on GPU memory)\n",
152
+ " overlap=128, # Overlap between tiles\n",
153
+ " min_size=100, # Minimum object size in pixels\n",
154
+ " unique=False, # Create binary mask (0 or 1)\n",
155
+ " dtype=\"int32\", # Data type for output\n",
156
+ " verbose=True, # Show progress\n",
157
+ ")"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": [
164
+ "## Visualize Results"
165
+ ]
166
+ },
167
+ {
168
+ "cell_type": "code",
169
+ "execution_count": null,
170
+ "metadata": {},
171
+ "outputs": [],
172
+ "source": [
173
+ "m = leafmap.Map()\n",
174
+ "m.add_raster(image_path, layer_name=\"Original Image\")\n",
175
+ "m.add_raster(\n",
176
+ " output_path, nodata=0, opacity=0.8, cmap=\"Blues\", layer_name=\"Segmentation Mask\"\n",
177
+ ")\n",
178
+ "m"
179
+ ]
180
+ },
181
+ {
182
+ "cell_type": "markdown",
183
+ "metadata": {},
184
+ "source": [
185
+ "## Convert Mask to Vector\n",
186
+ "\n",
187
+ "You can convert the raster mask to vector format (GeoPackage, Shapefile, etc.) for further analysis in GIS software."
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "code",
192
+ "execution_count": null,
193
+ "metadata": {},
194
+ "outputs": [],
195
+ "source": [
196
+ "# Convert mask to vector\n",
197
+ "vector_path = \"segmentation_mask.gpkg\"\n",
198
+ "common.raster_to_vector(output_path, vector_path)"
199
+ ]
200
+ },
201
+ {
202
+ "cell_type": "markdown",
203
+ "metadata": {},
204
+ "source": [
205
+ "## Smooth Vector"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": null,
211
+ "metadata": {},
212
+ "outputs": [],
213
+ "source": [
214
+ "smooth_vector_path = \"segmentation_mask_smooth.gpkg\"\n",
215
+ "gdf = common.smooth_vector(vector_path, smooth_vector_path)"
216
+ ]
217
+ },
218
+ {
219
+ "cell_type": "code",
220
+ "execution_count": null,
221
+ "metadata": {},
222
+ "outputs": [],
223
+ "source": [
224
+ "m.add_gdf(gdf, layer_name=\"Smoothed Vector\", info_mode=None)\n",
225
+ "m"
226
+ ]
227
+ },
228
+ {
229
+ "cell_type": "code",
230
+ "execution_count": null,
231
+ "metadata": {},
232
+ "outputs": [],
233
+ "source": []
234
+ },
235
+ {
236
+ "cell_type": "markdown",
237
+ "metadata": {},
238
+ "source": [
239
+ "## Tips for Processing Large Images\n",
240
+ "\n",
241
+ "1. **Tile Size**: Larger tiles capture more context but require more GPU memory. Start with 512 or 1024 and increase if you have sufficient GPU memory.\n",
242
+ "\n",
243
+ "2. **Overlap**: Higher overlap (e.g., 128-256) helps prevent artifacts at tile boundaries but increases processing time. Lower overlap (e.g., 64) is faster but may have more edge effects.\n",
244
+ "\n",
245
+ "3. **Memory Management**: The method automatically clears GPU memory after each tile. If you still encounter memory issues, try reducing the tile_size.\n",
246
+ "\n",
247
+ "4. **Data Type**: Use `int32` for images with many objects, `int16` for up to 65535 objects, or `int8` for up to 255 objects.\n",
248
+ "\n",
249
+ "5. **Filtering**: Use `min_size` and `max_size` to filter out noise (small objects) or irrelevant large regions."
250
+ ]
251
+ }
252
+ ],
253
+ "metadata": {
254
+ "kernelspec": {
255
+ "display_name": "geo",
256
+ "language": "python",
257
+ "name": "python3"
258
+ },
259
+ "language_info": {
260
+ "codemirror_mode": {
261
+ "name": "ipython",
262
+ "version": 3
263
+ },
264
+ "file_extension": ".py",
265
+ "mimetype": "text/x-python",
266
+ "name": "python",
267
+ "nbconvert_exporter": "python",
268
+ "pygments_lexer": "ipython3",
269
+ "version": "3.12.2"
270
+ }
271
+ },
272
+ "nbformat": 4,
273
+ "nbformat_minor": 4
274
+ }
@@ -81,6 +81,7 @@ nav:
81
81
  - examples/sam3_point_prompts.ipynb
82
82
  - examples/sam3_point_prompts_batch.ipynb
83
83
  - examples/sam3_box_prompts.ipynb
84
+ - examples/sam3_tiled_segmentation.ipynb
84
85
  - Workshops:
85
86
  - workshops/purdue.ipynb
86
87
  - workshops/cn_workshop.ipynb
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "segment-geospatial"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  dynamic = [
9
9
  "dependencies",
10
10
  ]
@@ -111,7 +111,7 @@ dependencies = {file = ["requirements.txt"]}
111
111
  universal = true
112
112
 
113
113
  [tool.bumpversion]
114
- current_version = "1.1.0"
114
+ current_version = "1.2.0"
115
115
  commit = true
116
116
  tag = true
117
117
 
@@ -10,6 +10,7 @@ pyproj
10
10
  rasterio
11
11
  segment_anything
12
12
  shapely
13
+ smoothify
13
14
  torch
14
15
  torchvision
15
16
  tqdm
@@ -2,7 +2,7 @@
2
2
 
3
3
  __author__ = """Qiusheng Wu"""
4
4
  __email__ = "giswqs@gmail.com"
5
- __version__ = "1.1.0"
5
+ __version__ = "1.2.0"
6
6
 
7
7
 
8
8
  from .samgeo import * # noqa: F403
@@ -4,7 +4,7 @@ The source code is adapted from https://github.com/aliaksandr960/segment-anythin
4
4
 
5
5
  import os
6
6
  import tempfile
7
- from typing import Any, List, Optional, Tuple, Union
7
+ from typing import Any, Dict, List, Optional, Tuple, Union
8
8
 
9
9
  import cv2
10
10
  import geopandas as gpd
@@ -4055,3 +4055,220 @@ def get_device() -> torch.device:
4055
4055
  return torch.device("mps")
4056
4056
  else:
4057
4057
  return torch.device("cpu")
4058
+
4059
+
4060
+ def get_raster_info(raster_path: str) -> Dict[str, Any]:
4061
+ """Display basic information about a raster dataset.
4062
+
4063
+ Args:
4064
+ raster_path (str): Path to the raster file
4065
+
4066
+ Returns:
4067
+ dict: Dictionary containing the basic information about the raster
4068
+ """
4069
+ # Open the raster dataset
4070
+ with rasterio.open(raster_path) as src:
4071
+ # Get basic metadata
4072
+ info = {
4073
+ "driver": src.driver,
4074
+ "width": src.width,
4075
+ "height": src.height,
4076
+ "count": src.count,
4077
+ "dtype": src.dtypes[0],
4078
+ "crs": src.crs.to_string() if src.crs else "No CRS defined",
4079
+ "transform": src.transform,
4080
+ "bounds": src.bounds,
4081
+ "resolution": (src.transform[0], -src.transform[4]),
4082
+ "nodata": src.nodata,
4083
+ }
4084
+
4085
+ # Calculate statistics for each band
4086
+ stats = []
4087
+ for i in range(1, src.count + 1):
4088
+ band = src.read(i, masked=True)
4089
+ band_stats = {
4090
+ "band": i,
4091
+ "min": float(band.min()),
4092
+ "max": float(band.max()),
4093
+ "mean": float(band.mean()),
4094
+ "std": float(band.std()),
4095
+ }
4096
+ stats.append(band_stats)
4097
+
4098
+ info["band_stats"] = stats
4099
+
4100
+ return info
4101
+
4102
+
4103
+ def get_raster_stats(raster_path: str, divide_by: float = 1.0) -> Dict[str, Any]:
4104
+ """Calculate statistics for each band in a raster dataset.
4105
+
4106
+ This function computes min, max, mean, and standard deviation values
4107
+ for each band in the provided raster, returning results in a dictionary
4108
+ with lists for each statistic type.
4109
+
4110
+ Args:
4111
+ raster_path (str): Path to the raster file
4112
+ divide_by (float, optional): Value to divide pixel values by.
4113
+ Defaults to 1.0, which keeps the original pixel values unchanged.
4114
+
4115
+ Returns:
4116
+ dict: Dictionary containing lists of statistics with keys:
4117
+ - 'min': List of minimum values for each band
4118
+ - 'max': List of maximum values for each band
4119
+ - 'mean': List of mean values for each band
4120
+ - 'std': List of standard deviation values for each band
4121
+ """
4122
+ # Initialize the results dictionary with empty lists
4123
+ stats = {"min": [], "max": [], "mean": [], "std": []}
4124
+
4125
+ # Open the raster dataset
4126
+ with rasterio.open(raster_path) as src:
4127
+ # Calculate statistics for each band
4128
+ for i in range(1, src.count + 1):
4129
+ band = src.read(i, masked=True)
4130
+
4131
+ # Append statistics for this band to each list
4132
+ stats["min"].append(float(band.min()) / divide_by)
4133
+ stats["max"].append(float(band.max()) / divide_by)
4134
+ stats["mean"].append(float(band.mean()) / divide_by)
4135
+ stats["std"].append(float(band.std()) / divide_by)
4136
+
4137
+ return stats
4138
+
4139
+
4140
+ def print_raster_info(
4141
+ raster_path: str, show_preview: bool = True, figsize: Tuple[int, int] = (10, 8)
4142
+ ) -> Optional[Dict[str, Any]]:
4143
+ """Print formatted information about a raster dataset and optionally show a preview.
4144
+
4145
+ Args:
4146
+ raster_path (str): Path to the raster file
4147
+ show_preview (bool, optional): Whether to display a visual preview of the raster.
4148
+ Defaults to True.
4149
+ figsize (tuple, optional): Figure size as (width, height). Defaults to (10, 8).
4150
+
4151
+ Returns:
4152
+ dict: Dictionary containing raster information if successful, None otherwise
4153
+ """
4154
+ import matplotlib.pyplot as plt
4155
+ from rasterio.plot import show
4156
+
4157
+ try:
4158
+ info = get_raster_info(raster_path)
4159
+
4160
+ # Print basic information
4161
+ print(f"===== RASTER INFORMATION: {raster_path} =====")
4162
+ print(f"Driver: {info['driver']}")
4163
+ print(f"Dimensions: {info['width']} x {info['height']} pixels")
4164
+ print(f"Number of bands: {info['count']}")
4165
+ print(f"Data type: {info['dtype']}")
4166
+ print(f"Coordinate Reference System: {info['crs']}")
4167
+ print(f"Georeferenced Bounds: {info['bounds']}")
4168
+ print(f"Pixel Resolution: {info['resolution'][0]}, {info['resolution'][1]}")
4169
+ print(f"NoData Value: {info['nodata']}")
4170
+
4171
+ # Print band statistics
4172
+ print("\n----- Band Statistics -----")
4173
+ for band_stat in info["band_stats"]:
4174
+ print(f"Band {band_stat['band']}:")
4175
+ print(f" Min: {band_stat['min']:.2f}")
4176
+ print(f" Max: {band_stat['max']:.2f}")
4177
+ print(f" Mean: {band_stat['mean']:.2f}")
4178
+ print(f" Std Dev: {band_stat['std']:.2f}")
4179
+
4180
+ # Show a preview if requested
4181
+ if show_preview:
4182
+ with rasterio.open(raster_path) as src:
4183
+ # For multi-band images, show RGB composite or first band
4184
+ if src.count >= 3:
4185
+ # Try to show RGB composite
4186
+ rgb = np.dstack([src.read(i) for i in range(1, 4)])
4187
+ plt.figure(figsize=figsize)
4188
+ plt.imshow(rgb)
4189
+ plt.title(f"RGB Preview: {raster_path}")
4190
+ else:
4191
+ # Show first band for single-band images
4192
+ plt.figure(figsize=figsize)
4193
+ show(
4194
+ src.read(1),
4195
+ cmap="viridis",
4196
+ title=f"Band 1 Preview: {raster_path}",
4197
+ )
4198
+ plt.colorbar(label="Pixel Value")
4199
+ plt.show()
4200
+
4201
+ return info
4202
+
4203
+ except Exception as e:
4204
+ print(f"Error reading raster: {str(e)}")
4205
+ return None
4206
+
4207
+
4208
+ def smooth_vector(
4209
+ vector_data: Union[str, gpd.GeoDataFrame],
4210
+ output_path: str = None,
4211
+ segment_length: float = None,
4212
+ smooth_iterations: int = 3,
4213
+ num_cores: int = 0,
4214
+ merge_collection: bool = True,
4215
+ merge_field: str = None,
4216
+ merge_multipolygons: bool = True,
4217
+ preserve_area: bool = True,
4218
+ area_tolerance: float = 0.01,
4219
+ **kwargs: Any,
4220
+ ) -> gpd.GeoDataFrame:
4221
+ """Smooth a vector data using the smoothify library.
4222
+ See https://github.com/DPIRD-DMA/Smoothify for more details.
4223
+
4224
+ Args:
4225
+ vector_data: The vector data to smooth.
4226
+ output_path: The path to save the smoothed vector data. If None, returns the smoothed vector data.
4227
+ segment_length: Resolution of the original raster data in map units. If None (default), automatically
4228
+ detects by finding the minimum segment length (from a data sample). Recommended to specify explicitly when known.
4229
+ smooth_iterations: The number of iterations to smooth the vector data.
4230
+ num_cores: Number of cores to use for parallel processing. If 0 (default), uses all available cores.
4231
+ merge_collection: Whether to merge/dissolve adjacent geometries in collections before smoothing.
4232
+ merge_field: Column name to use for dissolving geometries. Only valid when merge_collection=True.
4233
+ If None, dissolves all geometries together. If specified, dissolves geometries grouped by the column values.
4234
+ merge_multipolygons: Whether to merge adjacent polygons within MultiPolygons before smoothing
4235
+ preserve_area: Whether to restore original area after smoothing via buffering (applies to Polygons only)
4236
+ area_tolerance: Percentage of original area allowed as error (e.g., 0.01 = 0.01% error = 99.99% preservation).
4237
+ Only affects Polygons when preserve_area=True
4238
+
4239
+ Returns:
4240
+ gpd.GeoDataFrame: The smoothed vector data.
4241
+
4242
+ Examples:
4243
+ >>> from samgeo import common
4244
+ >>> gdf = common.read_vector("path/to/vector.geojson")
4245
+ >>> smoothed_gdf = common.smooth_vector(gdf, smooth_iterations=3, output_path="path/to/smoothed_vector.geojson")
4246
+ >>> smoothed_gdf.head()
4247
+ >>> smoothed_gdf.explore()
4248
+ """
4249
+ import leafmap
4250
+
4251
+ try:
4252
+ from smoothify import smoothify
4253
+ except ImportError:
4254
+ install_package("smoothify")
4255
+ from smoothify import smoothify
4256
+
4257
+ if isinstance(vector_data, str):
4258
+ vector_data = leafmap.read_vector(vector_data)
4259
+
4260
+ smoothed_vector_data = smoothify(
4261
+ geom=vector_data,
4262
+ segment_length=segment_length,
4263
+ smooth_iterations=smooth_iterations,
4264
+ num_cores=num_cores,
4265
+ merge_collection=merge_collection,
4266
+ merge_field=merge_field,
4267
+ merge_multipolygons=merge_multipolygons,
4268
+ preserve_area=preserve_area,
4269
+ area_tolerance=area_tolerance,
4270
+ **kwargs,
4271
+ )
4272
+ if output_path is not None:
4273
+ smoothed_vector_data.to_file(output_path)
4274
+ return smoothed_vector_data