geoai-py 0.3.6__py2.py3-none-any.whl → 0.4.1__py2.py3-none-any.whl
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.
- geoai/__init__.py +76 -14
- geoai/download.py +9 -8
- geoai/extract.py +65 -24
- geoai/geoai.py +3 -1
- geoai/hf.py +447 -0
- geoai/segment.py +4 -3
- geoai/segmentation.py +8 -7
- geoai/train.py +1039 -0
- geoai/utils.py +32 -28
- {geoai_py-0.3.6.dist-info → geoai_py-0.4.1.dist-info}/METADATA +3 -8
- geoai_py-0.4.1.dist-info/RECORD +15 -0
- geoai_py-0.3.6.dist-info/RECORD +0 -13
- {geoai_py-0.3.6.dist-info → geoai_py-0.4.1.dist-info}/LICENSE +0 -0
- {geoai_py-0.3.6.dist-info → geoai_py-0.4.1.dist-info}/WHEEL +0 -0
- {geoai_py-0.3.6.dist-info → geoai_py-0.4.1.dist-info}/entry_points.txt +0 -0
- {geoai_py-0.3.6.dist-info → geoai_py-0.4.1.dist-info}/top_level.txt +0 -0
geoai/__init__.py
CHANGED
|
@@ -2,34 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
__author__ = """Qiusheng Wu"""
|
|
4
4
|
__email__ = "giswqs@gmail.com"
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.4.1"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def set_proj_lib_path():
|
|
13
|
-
"""
|
|
12
|
+
def set_proj_lib_path(verbose=False):
|
|
13
|
+
"""
|
|
14
|
+
Set the PROJ_LIB and GDAL_DATA environment variables based on the current conda environment.
|
|
15
|
+
|
|
16
|
+
This function attempts to locate and set the correct paths for PROJ_LIB and GDAL_DATA
|
|
17
|
+
by checking multiple possible locations within the conda environment structure.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
verbose (bool): If True, print additional information during the process.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
bool: True if both paths were set successfully, False otherwise.
|
|
24
|
+
"""
|
|
14
25
|
try:
|
|
15
26
|
# Get conda environment path
|
|
16
27
|
conda_env_path = os.environ.get("CONDA_PREFIX") or sys.prefix
|
|
17
28
|
|
|
29
|
+
# Define possible paths for PROJ_LIB
|
|
30
|
+
possible_proj_paths = [
|
|
31
|
+
os.path.join(conda_env_path, "share", "proj"),
|
|
32
|
+
os.path.join(conda_env_path, "Library", "share", "proj"),
|
|
33
|
+
os.path.join(conda_env_path, "Library", "share"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Define possible paths for GDAL_DATA
|
|
37
|
+
possible_gdal_paths = [
|
|
38
|
+
os.path.join(conda_env_path, "share", "gdal"),
|
|
39
|
+
os.path.join(conda_env_path, "Library", "share", "gdal"),
|
|
40
|
+
os.path.join(conda_env_path, "Library", "data", "gdal"),
|
|
41
|
+
os.path.join(conda_env_path, "Library", "share"),
|
|
42
|
+
]
|
|
43
|
+
|
|
18
44
|
# Set PROJ_LIB environment variable
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
proj_set = False
|
|
46
|
+
for proj_path in possible_proj_paths:
|
|
47
|
+
if os.path.exists(proj_path) and os.path.isdir(proj_path):
|
|
48
|
+
# Verify it contains projection data
|
|
49
|
+
if os.path.exists(os.path.join(proj_path, "proj.db")):
|
|
50
|
+
os.environ["PROJ_LIB"] = proj_path
|
|
51
|
+
if verbose:
|
|
52
|
+
print(f"PROJ_LIB set to: {proj_path}")
|
|
53
|
+
proj_set = True
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
# Set GDAL_DATA environment variable
|
|
57
|
+
gdal_set = False
|
|
58
|
+
for gdal_path in possible_gdal_paths:
|
|
59
|
+
if os.path.exists(gdal_path) and os.path.isdir(gdal_path):
|
|
60
|
+
# Verify it contains the header.dxf file or other critical GDAL files
|
|
61
|
+
if os.path.exists(
|
|
62
|
+
os.path.join(gdal_path, "header.dxf")
|
|
63
|
+
) or os.path.exists(os.path.join(gdal_path, "gcs.csv")):
|
|
64
|
+
os.environ["GDAL_DATA"] = gdal_path
|
|
65
|
+
if verbose:
|
|
66
|
+
print(f"GDAL_DATA set to: {gdal_path}")
|
|
67
|
+
gdal_set = True
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
# If paths still not found, try a last-resort approach
|
|
71
|
+
if not proj_set or not gdal_set:
|
|
72
|
+
# Try a deep search in the conda environment
|
|
73
|
+
for root, dirs, files in os.walk(conda_env_path):
|
|
74
|
+
if not gdal_set and "header.dxf" in files:
|
|
75
|
+
os.environ["GDAL_DATA"] = root
|
|
76
|
+
if verbose:
|
|
77
|
+
print(f"GDAL_DATA set to: {root} (deep search)")
|
|
78
|
+
gdal_set = True
|
|
79
|
+
|
|
80
|
+
if not proj_set and "proj.db" in files:
|
|
81
|
+
os.environ["PROJ_LIB"] = root
|
|
82
|
+
if verbose:
|
|
83
|
+
print(f"PROJ_LIB set to: {root} (deep search)")
|
|
84
|
+
proj_set = True
|
|
85
|
+
|
|
86
|
+
if proj_set and gdal_set:
|
|
87
|
+
break
|
|
88
|
+
|
|
27
89
|
except Exception as e:
|
|
28
|
-
print(e)
|
|
90
|
+
print(f"Error setting projection library paths: {e}")
|
|
29
91
|
return
|
|
30
92
|
|
|
31
93
|
|
|
32
|
-
if "google.colab" not in sys.modules:
|
|
33
|
-
|
|
94
|
+
# if ("google.colab" not in sys.modules) and (sys.platform != "windows"):
|
|
95
|
+
# set_proj_lib_path()
|
|
34
96
|
|
|
35
97
|
from .geoai import *
|
geoai/download.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"""This module provides functions to download data, including NAIP imagery and building data from Overture Maps."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import geopandas as gpd
|
|
7
9
|
import matplotlib.pyplot as plt
|
|
8
|
-
|
|
10
|
+
import numpy as np
|
|
9
11
|
import planetary_computer as pc
|
|
10
|
-
import
|
|
12
|
+
import requests
|
|
13
|
+
import rioxarray
|
|
14
|
+
from pystac_client import Client
|
|
11
15
|
from shapely.geometry import box
|
|
12
16
|
from tqdm import tqdm
|
|
13
|
-
import requests
|
|
14
|
-
import subprocess
|
|
15
|
-
import logging
|
|
16
17
|
|
|
17
18
|
# Configure logging
|
|
18
19
|
logging.basicConfig(
|
geoai/extract.py
CHANGED
|
@@ -14,16 +14,15 @@ import torch
|
|
|
14
14
|
from huggingface_hub import hf_hub_download
|
|
15
15
|
from rasterio.windows import Window
|
|
16
16
|
from shapely.geometry import Polygon, box
|
|
17
|
-
from tqdm import tqdm
|
|
18
17
|
from torchvision.models.detection import (
|
|
19
|
-
maskrcnn_resnet50_fpn,
|
|
20
18
|
fasterrcnn_resnet50_fpn_v2,
|
|
19
|
+
maskrcnn_resnet50_fpn,
|
|
21
20
|
)
|
|
21
|
+
from tqdm import tqdm
|
|
22
22
|
|
|
23
23
|
# Local Imports
|
|
24
24
|
from .utils import get_raster_stats
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
try:
|
|
28
27
|
from torchgeo.datasets import NonGeoDataset
|
|
29
28
|
except ImportError as e:
|
|
@@ -270,7 +269,9 @@ class ObjectDetector:
|
|
|
270
269
|
Object extraction using Mask R-CNN with TorchGeo.
|
|
271
270
|
"""
|
|
272
271
|
|
|
273
|
-
def __init__(
|
|
272
|
+
def __init__(
|
|
273
|
+
self, model_path=None, repo_id=None, model=None, num_classes=2, device=None
|
|
274
|
+
):
|
|
274
275
|
"""
|
|
275
276
|
Initialize the object extractor.
|
|
276
277
|
|
|
@@ -278,6 +279,7 @@ class ObjectDetector:
|
|
|
278
279
|
model_path: Path to the .pth model file.
|
|
279
280
|
repo_id: Hugging Face repository ID for model download.
|
|
280
281
|
model: Pre-initialized model object (optional).
|
|
282
|
+
num_classes: Number of classes for detection (default: 2).
|
|
281
283
|
device: Device to use for inference ('cuda:0', 'cpu', etc.).
|
|
282
284
|
"""
|
|
283
285
|
# Set device
|
|
@@ -297,7 +299,7 @@ class ObjectDetector:
|
|
|
297
299
|
self.simplify_tolerance = 1.0 # Tolerance for polygon simplification
|
|
298
300
|
|
|
299
301
|
# Initialize model
|
|
300
|
-
self.model = self.initialize_model(model)
|
|
302
|
+
self.model = self.initialize_model(model, num_classes=num_classes)
|
|
301
303
|
|
|
302
304
|
# Download model if needed
|
|
303
305
|
if model_path is None or (not os.path.exists(model_path)):
|
|
@@ -342,11 +344,12 @@ class ObjectDetector:
|
|
|
342
344
|
print("Please specify a local model path or ensure internet connectivity.")
|
|
343
345
|
raise
|
|
344
346
|
|
|
345
|
-
def initialize_model(self, model):
|
|
347
|
+
def initialize_model(self, model, num_classes=2):
|
|
346
348
|
"""Initialize a deep learning model for object detection.
|
|
347
349
|
|
|
348
350
|
Args:
|
|
349
351
|
model (torch.nn.Module): A pre-initialized model object.
|
|
352
|
+
num_classes (int): Number of classes for detection.
|
|
350
353
|
|
|
351
354
|
Returns:
|
|
352
355
|
torch.nn.Module: A deep learning model for object detection.
|
|
@@ -361,7 +364,7 @@ class ObjectDetector:
|
|
|
361
364
|
model = maskrcnn_resnet50_fpn(
|
|
362
365
|
weights=None,
|
|
363
366
|
progress=False,
|
|
364
|
-
num_classes=
|
|
367
|
+
num_classes=num_classes, # Background + object
|
|
365
368
|
weights_backbone=None,
|
|
366
369
|
# These parameters ensure consistent normalization
|
|
367
370
|
image_mean=image_mean,
|
|
@@ -1306,13 +1309,14 @@ class ObjectDetector:
|
|
|
1306
1309
|
Returns:
|
|
1307
1310
|
GeoDataFrame with regularized objects
|
|
1308
1311
|
"""
|
|
1312
|
+
import math
|
|
1313
|
+
|
|
1314
|
+
import cv2
|
|
1315
|
+
import geopandas as gpd
|
|
1309
1316
|
import numpy as np
|
|
1310
|
-
from shapely.geometry import Polygon, MultiPolygon, box
|
|
1311
1317
|
from shapely.affinity import rotate, translate
|
|
1312
|
-
import
|
|
1313
|
-
import math
|
|
1318
|
+
from shapely.geometry import MultiPolygon, Polygon, box
|
|
1314
1319
|
from tqdm import tqdm
|
|
1315
|
-
import cv2
|
|
1316
1320
|
|
|
1317
1321
|
def get_angle(p1, p2, p3):
|
|
1318
1322
|
"""Calculate angle between three points in degrees (0-180)"""
|
|
@@ -2112,7 +2116,7 @@ class ObjectDetector:
|
|
|
2112
2116
|
output_path=None,
|
|
2113
2117
|
confidence_threshold=0.5,
|
|
2114
2118
|
min_object_area=100,
|
|
2115
|
-
|
|
2119
|
+
max_object_area=None,
|
|
2116
2120
|
**kwargs,
|
|
2117
2121
|
):
|
|
2118
2122
|
"""
|
|
@@ -2123,7 +2127,7 @@ class ObjectDetector:
|
|
|
2123
2127
|
output_path: Path for output GeoJSON.
|
|
2124
2128
|
confidence_threshold: Minimum confidence score (0.0-1.0). Default: 0.5
|
|
2125
2129
|
min_object_area: Minimum area in pixels to keep an object. Default: 100
|
|
2126
|
-
|
|
2130
|
+
max_object_area: Maximum area in pixels to keep an object. Default: None
|
|
2127
2131
|
**kwargs: Additional parameters
|
|
2128
2132
|
|
|
2129
2133
|
Returns:
|
|
@@ -2147,8 +2151,9 @@ class ObjectDetector:
|
|
|
2147
2151
|
print(f"Found {num_features} connected components")
|
|
2148
2152
|
|
|
2149
2153
|
# Process each component
|
|
2150
|
-
|
|
2151
|
-
|
|
2154
|
+
polygons = []
|
|
2155
|
+
confidences = []
|
|
2156
|
+
pixels = []
|
|
2152
2157
|
|
|
2153
2158
|
# Add progress bar
|
|
2154
2159
|
for label in tqdm(range(1, num_features + 1), desc="Processing components"):
|
|
@@ -2179,8 +2184,8 @@ class ObjectDetector:
|
|
|
2179
2184
|
if area < min_object_area:
|
|
2180
2185
|
continue
|
|
2181
2186
|
|
|
2182
|
-
if
|
|
2183
|
-
if area >
|
|
2187
|
+
if max_object_area is not None:
|
|
2188
|
+
if area > max_object_area:
|
|
2184
2189
|
continue
|
|
2185
2190
|
|
|
2186
2191
|
# Get minimum area rectangle
|
|
@@ -2197,16 +2202,18 @@ class ObjectDetector:
|
|
|
2197
2202
|
poly = Polygon(geo_points)
|
|
2198
2203
|
|
|
2199
2204
|
# Add to lists
|
|
2200
|
-
|
|
2201
|
-
|
|
2205
|
+
polygons.append(poly)
|
|
2206
|
+
confidences.append(confidence)
|
|
2207
|
+
pixels.append(area)
|
|
2202
2208
|
|
|
2203
2209
|
# Create GeoDataFrame
|
|
2204
|
-
if
|
|
2210
|
+
if polygons:
|
|
2205
2211
|
gdf = gpd.GeoDataFrame(
|
|
2206
2212
|
{
|
|
2207
|
-
"geometry":
|
|
2208
|
-
"confidence":
|
|
2209
|
-
"class": [1] * len(
|
|
2213
|
+
"geometry": polygons,
|
|
2214
|
+
"confidence": confidences,
|
|
2215
|
+
"class": [1] * len(polygons),
|
|
2216
|
+
"pixels": pixels,
|
|
2210
2217
|
},
|
|
2211
2218
|
crs=crs,
|
|
2212
2219
|
)
|
|
@@ -2218,7 +2225,7 @@ class ObjectDetector:
|
|
|
2218
2225
|
|
|
2219
2226
|
return gdf
|
|
2220
2227
|
else:
|
|
2221
|
-
print("No valid
|
|
2228
|
+
print("No valid polygons found")
|
|
2222
2229
|
return None
|
|
2223
2230
|
|
|
2224
2231
|
|
|
@@ -2356,3 +2363,37 @@ class SolarPanelDetector(ObjectDetector):
|
|
|
2356
2363
|
super().__init__(
|
|
2357
2364
|
model_path=model_path, repo_id=repo_id, model=model, device=device
|
|
2358
2365
|
)
|
|
2366
|
+
|
|
2367
|
+
|
|
2368
|
+
class ParkingSplotDetector(ObjectDetector):
|
|
2369
|
+
"""
|
|
2370
|
+
Car detection using a pre-trained Mask R-CNN model.
|
|
2371
|
+
|
|
2372
|
+
This class extends the `ObjectDetector` class with additional methods for car detection.
|
|
2373
|
+
"""
|
|
2374
|
+
|
|
2375
|
+
def __init__(
|
|
2376
|
+
self,
|
|
2377
|
+
model_path="parking_spot_detection.pth",
|
|
2378
|
+
repo_id=None,
|
|
2379
|
+
model=None,
|
|
2380
|
+
num_classes=3,
|
|
2381
|
+
device=None,
|
|
2382
|
+
):
|
|
2383
|
+
"""
|
|
2384
|
+
Initialize the object extractor.
|
|
2385
|
+
|
|
2386
|
+
Args:
|
|
2387
|
+
model_path: Path to the .pth model file.
|
|
2388
|
+
repo_id: Repo ID for loading models from the Hub.
|
|
2389
|
+
model: Custom model to use for inference.
|
|
2390
|
+
num_classes: Number of classes for the model. Default: 3
|
|
2391
|
+
device: Device to use for inference ('cuda:0', 'cpu', etc.).
|
|
2392
|
+
"""
|
|
2393
|
+
super().__init__(
|
|
2394
|
+
model_path=model_path,
|
|
2395
|
+
repo_id=repo_id,
|
|
2396
|
+
model=model,
|
|
2397
|
+
num_classes=num_classes,
|
|
2398
|
+
device=device,
|
|
2399
|
+
)
|