geoai-py 0.18.0__tar.gz → 0.18.2__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.
- {geoai_py-0.18.0 → geoai_py-0.18.2}/.gitignore +2 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/.pre-commit-config.yaml +1 -1
- {geoai_py-0.18.0 → geoai_py-0.18.2}/PKG-INFO +2 -2
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/__init__.py +1 -1
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/train.py +22 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/utils.py +302 -86
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/PKG-INFO +2 -2
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/requires.txt +1 -1
- {geoai_py-0.18.0 → geoai_py-0.18.2}/mkdocs.yml +3 -1
- {geoai_py-0.18.0 → geoai_py-0.18.2}/pyproject.toml +2 -2
- {geoai_py-0.18.0 → geoai_py-0.18.2}/requirements.txt +1 -1
- {geoai_py-0.18.0 → geoai_py-0.18.2}/.dockerignore +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/.editorconfig +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/CITATION.cff +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/Dockerfile +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/LICENSE +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/MANIFEST.in +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/README.md +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/__init__.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/catalog_models.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/catalog_tools.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/geo_agents.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/map_tools.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/stac_models.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/agents/stac_tools.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/change_detection.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/classify.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/detectron2.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/dinov3.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/download.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/extract.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/geoai.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/hf.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/map_widgets.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/sam.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/segment.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/segmentation.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/timm_segment.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/timm_train.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/tools/__init__.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/tools/cloudmask.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai/tools/multiclean.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/SOURCES.txt +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/dependency_links.txt +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/entry_points.txt +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/geoai_py.egg-info/top_level.txt +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/pytest.ini +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/requirements_docs.txt +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/setup.cfg +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/__init__.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/create_test_data.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_classify.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_download.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_extract.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_fixtures.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_geoai.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_segment.py +0 -0
- {geoai_py-0.18.0 → geoai_py-0.18.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geoai-py
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.2
|
|
4
4
|
Summary: A Python package for using Artificial Intelligence (AI) with geospatial data
|
|
5
5
|
Author-email: Qiusheng Wu <giswqs@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -24,7 +24,7 @@ Requires-Dist: ever-beta
|
|
|
24
24
|
Requires-Dist: geopandas
|
|
25
25
|
Requires-Dist: huggingface_hub
|
|
26
26
|
Requires-Dist: jupyter-server-proxy
|
|
27
|
-
Requires-Dist: leafmap
|
|
27
|
+
Requires-Dist: leafmap>=0.57.1
|
|
28
28
|
Requires-Dist: localtileserver
|
|
29
29
|
Requires-Dist: mapclassify
|
|
30
30
|
Requires-Dist: maplibre
|
|
@@ -2015,17 +2015,39 @@ def get_semantic_transform(train: bool) -> Any:
|
|
|
2015
2015
|
"""
|
|
2016
2016
|
Get transforms for semantic segmentation data augmentation.
|
|
2017
2017
|
|
|
2018
|
+
This function returns default data augmentation transforms for training
|
|
2019
|
+
semantic segmentation models. The transforms include geometric transformations
|
|
2020
|
+
(horizontal/vertical flips, rotations) and photometric adjustments (brightness,
|
|
2021
|
+
contrast) that are commonly used in remote sensing tasks.
|
|
2022
|
+
|
|
2018
2023
|
Args:
|
|
2019
2024
|
train (bool): Whether to include training-specific transforms.
|
|
2025
|
+
If True, applies augmentations (flips, rotations, brightness/contrast adjustments).
|
|
2026
|
+
If False, only converts to tensor (for validation).
|
|
2020
2027
|
|
|
2021
2028
|
Returns:
|
|
2022
2029
|
SemanticTransforms: Composed transforms.
|
|
2030
|
+
|
|
2031
|
+
Example:
|
|
2032
|
+
>>> train_transform = get_semantic_transform(train=True)
|
|
2033
|
+
>>> val_transform = get_semantic_transform(train=False)
|
|
2023
2034
|
"""
|
|
2024
2035
|
transforms = []
|
|
2025
2036
|
transforms.append(SemanticToTensor())
|
|
2026
2037
|
|
|
2027
2038
|
if train:
|
|
2039
|
+
# Geometric transforms - preserve spatial structure
|
|
2028
2040
|
transforms.append(SemanticRandomHorizontalFlip(0.5))
|
|
2041
|
+
transforms.append(SemanticRandomVerticalFlip(0.5))
|
|
2042
|
+
transforms.append(SemanticRandomRotation90(0.5))
|
|
2043
|
+
|
|
2044
|
+
# Photometric transforms - improve model robustness
|
|
2045
|
+
transforms.append(
|
|
2046
|
+
SemanticBrightnessAdjustment(brightness_range=(0.8, 1.2), prob=0.5)
|
|
2047
|
+
)
|
|
2048
|
+
transforms.append(
|
|
2049
|
+
SemanticContrastAdjustment(contrast_range=(0.8, 1.2), prob=0.5)
|
|
2050
|
+
)
|
|
2029
2051
|
|
|
2030
2052
|
return SemanticTransforms(transforms)
|
|
2031
2053
|
|
|
@@ -64,7 +64,7 @@ def view_raster(
|
|
|
64
64
|
client_args: Optional[Dict] = {"cors_all": False},
|
|
65
65
|
basemap: Optional[str] = "OpenStreetMap",
|
|
66
66
|
basemap_args: Optional[Dict] = None,
|
|
67
|
-
backend: Optional[str] = "
|
|
67
|
+
backend: Optional[str] = "folium",
|
|
68
68
|
**kwargs: Any,
|
|
69
69
|
) -> Any:
|
|
70
70
|
"""
|
|
@@ -87,7 +87,7 @@ def view_raster(
|
|
|
87
87
|
client_args (Optional[Dict], optional): Additional arguments for the client. Defaults to {"cors_all": False}.
|
|
88
88
|
basemap (Optional[str], optional): The basemap to use. Defaults to "OpenStreetMap".
|
|
89
89
|
basemap_args (Optional[Dict], optional): Additional arguments for the basemap. Defaults to None.
|
|
90
|
-
backend (Optional[str], optional): The backend to use. Defaults to "
|
|
90
|
+
backend (Optional[str], optional): The backend to use. Defaults to "folium".
|
|
91
91
|
**kwargs (Any): Additional keyword arguments.
|
|
92
92
|
|
|
93
93
|
Returns:
|
|
@@ -123,26 +123,39 @@ def view_raster(
|
|
|
123
123
|
if isinstance(source, dict):
|
|
124
124
|
source = dict_to_image(source)
|
|
125
125
|
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
126
|
+
if isinstance(source, str) and source.startswith("http"):
|
|
127
|
+
if backend == "folium":
|
|
128
|
+
|
|
129
|
+
m.add_geotiff(
|
|
130
|
+
url=source,
|
|
131
|
+
name=layer_name,
|
|
132
|
+
opacity=opacity,
|
|
133
|
+
attribution=attribution,
|
|
134
|
+
fit_bounds=zoom_to_layer,
|
|
135
|
+
palette=colormap,
|
|
136
|
+
vmin=vmin,
|
|
137
|
+
vmax=vmax,
|
|
138
|
+
**kwargs,
|
|
139
|
+
)
|
|
140
|
+
m.add_layer_control()
|
|
141
|
+
m.add_opacity_control()
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
if indexes is not None:
|
|
145
|
+
kwargs["bidx"] = indexes
|
|
146
|
+
if colormap is not None:
|
|
147
|
+
kwargs["colormap_name"] = colormap
|
|
148
|
+
if attribution is None:
|
|
149
|
+
attribution = "TiTiler"
|
|
150
|
+
|
|
151
|
+
m.add_cog_layer(
|
|
152
|
+
source,
|
|
153
|
+
name=layer_name,
|
|
154
|
+
opacity=opacity,
|
|
155
|
+
attribution=attribution,
|
|
156
|
+
zoom_to_layer=zoom_to_layer,
|
|
157
|
+
**kwargs,
|
|
158
|
+
)
|
|
146
159
|
else:
|
|
147
160
|
m.add_raster(
|
|
148
161
|
source=source,
|
|
@@ -1081,8 +1094,9 @@ def view_vector(
|
|
|
1081
1094
|
|
|
1082
1095
|
def view_vector_interactive(
|
|
1083
1096
|
vector_data: Union[str, gpd.GeoDataFrame],
|
|
1084
|
-
layer_name: str = "Vector
|
|
1097
|
+
layer_name: str = "Vector",
|
|
1085
1098
|
tiles_args: Optional[Dict] = None,
|
|
1099
|
+
opacity: float = 0.7,
|
|
1086
1100
|
**kwargs: Any,
|
|
1087
1101
|
) -> Any:
|
|
1088
1102
|
"""
|
|
@@ -1097,6 +1111,7 @@ def view_vector_interactive(
|
|
|
1097
1111
|
layer_name (str, optional): The name of the layer. Defaults to "Vector Layer".
|
|
1098
1112
|
tiles_args (dict, optional): Additional arguments for the localtileserver client.
|
|
1099
1113
|
get_folium_tile_layer function. Defaults to None.
|
|
1114
|
+
opacity (float, optional): The opacity of the layer. Defaults to 0.7.
|
|
1100
1115
|
**kwargs: Additional keyword arguments to pass to GeoDataFrame.explore() function.
|
|
1101
1116
|
See https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.explore.html
|
|
1102
1117
|
|
|
@@ -1111,9 +1126,8 @@ def view_vector_interactive(
|
|
|
1111
1126
|
>>> roads = gpd.read_file("roads.shp")
|
|
1112
1127
|
>>> view_vector_interactive(roads, figsize=(12, 8))
|
|
1113
1128
|
"""
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
from leafmap import cog_tile
|
|
1129
|
+
|
|
1130
|
+
from leafmap.foliumap import Map
|
|
1117
1131
|
from localtileserver import TileClient, get_folium_tile_layer
|
|
1118
1132
|
|
|
1119
1133
|
google_tiles = {
|
|
@@ -1148,6 +1162,8 @@ def view_vector_interactive(
|
|
|
1148
1162
|
basemap_layer_name = None
|
|
1149
1163
|
raster_layer = None
|
|
1150
1164
|
|
|
1165
|
+
m = Map()
|
|
1166
|
+
|
|
1151
1167
|
if "tiles" in kwargs and isinstance(kwargs["tiles"], str):
|
|
1152
1168
|
if kwargs["tiles"].title() in google_tiles:
|
|
1153
1169
|
basemap_layer_name = google_tiles[kwargs["tiles"].title()]["name"]
|
|
@@ -1158,14 +1174,17 @@ def view_vector_interactive(
|
|
|
1158
1174
|
tiles_args = {}
|
|
1159
1175
|
if kwargs["tiles"].lower().startswith("http"):
|
|
1160
1176
|
basemap_layer_name = "Remote Raster"
|
|
1161
|
-
kwargs["tiles"] =
|
|
1162
|
-
kwargs["attr"] = "TiTiler"
|
|
1177
|
+
m.add_geotiff(kwargs["tiles"], name=basemap_layer_name, **tiles_args)
|
|
1163
1178
|
else:
|
|
1164
1179
|
basemap_layer_name = "Local Raster"
|
|
1165
1180
|
client = TileClient(kwargs["tiles"])
|
|
1166
1181
|
raster_layer = get_folium_tile_layer(client, **tiles_args)
|
|
1167
|
-
|
|
1168
|
-
|
|
1182
|
+
m.add_tile_layer(
|
|
1183
|
+
raster_layer.tiles,
|
|
1184
|
+
name=basemap_layer_name,
|
|
1185
|
+
attribution="localtileserver",
|
|
1186
|
+
**tiles_args,
|
|
1187
|
+
)
|
|
1169
1188
|
|
|
1170
1189
|
if "max_zoom" not in kwargs:
|
|
1171
1190
|
kwargs["max_zoom"] = 30
|
|
@@ -1180,23 +1199,18 @@ def view_vector_interactive(
|
|
|
1180
1199
|
if not isinstance(vector_data, gpd.GeoDataFrame):
|
|
1181
1200
|
raise TypeError("Input data must be a GeoDataFrame")
|
|
1182
1201
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1202
|
+
if "column" in kwargs:
|
|
1203
|
+
if "legend_position" not in kwargs:
|
|
1204
|
+
kwargs["legend_position"] = "bottomleft"
|
|
1205
|
+
if "cmap" not in kwargs:
|
|
1206
|
+
kwargs["cmap"] = "viridis"
|
|
1207
|
+
m.add_data(vector_data, layer_name=layer_name, opacity=opacity, **kwargs)
|
|
1187
1208
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
if isinstance(layer, folium.GeoJson):
|
|
1191
|
-
layer.layer_name = layer_name
|
|
1192
|
-
if isinstance(layer, folium.TileLayer) and basemap_layer_name:
|
|
1193
|
-
layer.layer_name = basemap_layer_name
|
|
1194
|
-
|
|
1195
|
-
if layer_control:
|
|
1196
|
-
m.add_child(folium.LayerControl())
|
|
1209
|
+
else:
|
|
1210
|
+
m.add_gdf(vector_data, layer_name=layer_name, opacity=opacity, **kwargs)
|
|
1197
1211
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1212
|
+
m.add_layer_control()
|
|
1213
|
+
m.add_opacity_control()
|
|
1200
1214
|
|
|
1201
1215
|
return m
|
|
1202
1216
|
|
|
@@ -2990,6 +3004,82 @@ def batch_vector_to_raster(
|
|
|
2990
3004
|
return output_files
|
|
2991
3005
|
|
|
2992
3006
|
|
|
3007
|
+
def get_default_augmentation_transforms(
|
|
3008
|
+
tile_size: int = 256,
|
|
3009
|
+
include_normalize: bool = False,
|
|
3010
|
+
mean: Tuple[float, float, float] = (0.485, 0.456, 0.406),
|
|
3011
|
+
std: Tuple[float, float, float] = (0.229, 0.224, 0.225),
|
|
3012
|
+
) -> Any:
|
|
3013
|
+
"""
|
|
3014
|
+
Get default data augmentation transforms for geospatial imagery using albumentations.
|
|
3015
|
+
|
|
3016
|
+
This function returns a composition of augmentation transforms commonly used
|
|
3017
|
+
for remote sensing and geospatial data. The transforms include geometric
|
|
3018
|
+
transformations (flips, rotations) and photometric adjustments (brightness,
|
|
3019
|
+
contrast, saturation).
|
|
3020
|
+
|
|
3021
|
+
Args:
|
|
3022
|
+
tile_size (int): Target size for tiles. Defaults to 256.
|
|
3023
|
+
include_normalize (bool): Whether to include normalization transform.
|
|
3024
|
+
Defaults to False. Set to True if using for training with pretrained models.
|
|
3025
|
+
mean (tuple): Mean values for normalization (RGB). Defaults to ImageNet values.
|
|
3026
|
+
std (tuple): Standard deviation for normalization (RGB). Defaults to ImageNet values.
|
|
3027
|
+
|
|
3028
|
+
Returns:
|
|
3029
|
+
albumentations.Compose: A composition of augmentation transforms.
|
|
3030
|
+
|
|
3031
|
+
Example:
|
|
3032
|
+
>>> import albumentations as A
|
|
3033
|
+
>>> # Get default transforms
|
|
3034
|
+
>>> transform = get_default_augmentation_transforms()
|
|
3035
|
+
>>> # Apply to image and mask
|
|
3036
|
+
>>> augmented = transform(image=image, mask=mask)
|
|
3037
|
+
>>> aug_image = augmented['image']
|
|
3038
|
+
>>> aug_mask = augmented['mask']
|
|
3039
|
+
"""
|
|
3040
|
+
try:
|
|
3041
|
+
import albumentations as A
|
|
3042
|
+
except ImportError:
|
|
3043
|
+
raise ImportError(
|
|
3044
|
+
"albumentations is required for data augmentation. "
|
|
3045
|
+
"Install it with: pip install albumentations"
|
|
3046
|
+
)
|
|
3047
|
+
|
|
3048
|
+
transforms_list = [
|
|
3049
|
+
# Geometric transforms
|
|
3050
|
+
A.HorizontalFlip(p=0.5),
|
|
3051
|
+
A.VerticalFlip(p=0.5),
|
|
3052
|
+
A.RandomRotate90(p=0.5),
|
|
3053
|
+
A.ShiftScaleRotate(
|
|
3054
|
+
shift_limit=0.1,
|
|
3055
|
+
scale_limit=0.1,
|
|
3056
|
+
rotate_limit=45,
|
|
3057
|
+
border_mode=0,
|
|
3058
|
+
p=0.5,
|
|
3059
|
+
),
|
|
3060
|
+
# Photometric transforms
|
|
3061
|
+
A.RandomBrightnessContrast(
|
|
3062
|
+
brightness_limit=0.2,
|
|
3063
|
+
contrast_limit=0.2,
|
|
3064
|
+
p=0.5,
|
|
3065
|
+
),
|
|
3066
|
+
A.HueSaturationValue(
|
|
3067
|
+
hue_shift_limit=10,
|
|
3068
|
+
sat_shift_limit=20,
|
|
3069
|
+
val_shift_limit=10,
|
|
3070
|
+
p=0.3,
|
|
3071
|
+
),
|
|
3072
|
+
A.GaussNoise(var_limit=(10.0, 50.0), p=0.2),
|
|
3073
|
+
A.GaussianBlur(blur_limit=(3, 5), p=0.2),
|
|
3074
|
+
]
|
|
3075
|
+
|
|
3076
|
+
# Add normalization if requested
|
|
3077
|
+
if include_normalize:
|
|
3078
|
+
transforms_list.append(A.Normalize(mean=mean, std=std))
|
|
3079
|
+
|
|
3080
|
+
return A.Compose(transforms_list)
|
|
3081
|
+
|
|
3082
|
+
|
|
2993
3083
|
def export_geotiff_tiles(
|
|
2994
3084
|
in_raster,
|
|
2995
3085
|
out_folder,
|
|
@@ -3004,6 +3094,9 @@ def export_geotiff_tiles(
|
|
|
3004
3094
|
create_overview=False,
|
|
3005
3095
|
skip_empty_tiles=False,
|
|
3006
3096
|
metadata_format="PASCAL_VOC",
|
|
3097
|
+
apply_augmentation=False,
|
|
3098
|
+
augmentation_count=3,
|
|
3099
|
+
augmentation_transforms=None,
|
|
3007
3100
|
):
|
|
3008
3101
|
"""
|
|
3009
3102
|
Export georeferenced GeoTIFF tiles and labels from raster and classification data.
|
|
@@ -3023,12 +3116,54 @@ def export_geotiff_tiles(
|
|
|
3023
3116
|
create_overview (bool): Whether to create an overview image of all tiles
|
|
3024
3117
|
skip_empty_tiles (bool): If True, skip tiles with no features
|
|
3025
3118
|
metadata_format (str): Output metadata format (PASCAL_VOC, COCO, YOLO). Default: PASCAL_VOC
|
|
3119
|
+
apply_augmentation (bool): If True, generate augmented versions of each tile.
|
|
3120
|
+
This will create multiple variants of each tile using data augmentation techniques.
|
|
3121
|
+
Defaults to False.
|
|
3122
|
+
augmentation_count (int): Number of augmented versions to generate per tile
|
|
3123
|
+
(only used if apply_augmentation=True). Defaults to 3.
|
|
3124
|
+
augmentation_transforms (albumentations.Compose, optional): Custom augmentation transforms.
|
|
3125
|
+
If None and apply_augmentation=True, uses default transforms from
|
|
3126
|
+
get_default_augmentation_transforms(). Should be an albumentations.Compose object.
|
|
3127
|
+
Defaults to None.
|
|
3128
|
+
|
|
3129
|
+
Returns:
|
|
3130
|
+
None: Tiles and labels are saved to out_folder.
|
|
3131
|
+
|
|
3132
|
+
Example:
|
|
3133
|
+
>>> # Export tiles without augmentation
|
|
3134
|
+
>>> export_geotiff_tiles('image.tif', 'output/', 'labels.tif')
|
|
3135
|
+
>>>
|
|
3136
|
+
>>> # Export tiles with default augmentation (3 augmented versions per tile)
|
|
3137
|
+
>>> export_geotiff_tiles('image.tif', 'output/', 'labels.tif',
|
|
3138
|
+
... apply_augmentation=True)
|
|
3139
|
+
>>>
|
|
3140
|
+
>>> # Export with custom augmentation
|
|
3141
|
+
>>> import albumentations as A
|
|
3142
|
+
>>> custom_transform = A.Compose([
|
|
3143
|
+
... A.HorizontalFlip(p=0.5),
|
|
3144
|
+
... A.RandomBrightnessContrast(p=0.5),
|
|
3145
|
+
... ])
|
|
3146
|
+
>>> export_geotiff_tiles('image.tif', 'output/', 'labels.tif',
|
|
3147
|
+
... apply_augmentation=True,
|
|
3148
|
+
... augmentation_count=5,
|
|
3149
|
+
... augmentation_transforms=custom_transform)
|
|
3026
3150
|
"""
|
|
3027
3151
|
|
|
3028
3152
|
import logging
|
|
3029
3153
|
|
|
3030
3154
|
logging.getLogger("rasterio").setLevel(logging.ERROR)
|
|
3031
3155
|
|
|
3156
|
+
# Initialize augmentation transforms if needed
|
|
3157
|
+
if apply_augmentation:
|
|
3158
|
+
if augmentation_transforms is None:
|
|
3159
|
+
augmentation_transforms = get_default_augmentation_transforms(
|
|
3160
|
+
tile_size=tile_size
|
|
3161
|
+
)
|
|
3162
|
+
if not quiet:
|
|
3163
|
+
print(
|
|
3164
|
+
f"Data augmentation enabled: generating {augmentation_count} augmented versions per tile"
|
|
3165
|
+
)
|
|
3166
|
+
|
|
3032
3167
|
# Create output directories
|
|
3033
3168
|
os.makedirs(out_folder, exist_ok=True)
|
|
3034
3169
|
image_dir = os.path.join(out_folder, "images")
|
|
@@ -3360,53 +3495,134 @@ def export_geotiff_tiles(
|
|
|
3360
3495
|
# Read image data
|
|
3361
3496
|
image_data = src.read(window=window)
|
|
3362
3497
|
|
|
3363
|
-
#
|
|
3364
|
-
|
|
3498
|
+
# Helper function to save a single tile (original or augmented)
|
|
3499
|
+
def save_tile(
|
|
3500
|
+
img_data,
|
|
3501
|
+
lbl_mask,
|
|
3502
|
+
tile_id,
|
|
3503
|
+
img_profile,
|
|
3504
|
+
window_trans,
|
|
3505
|
+
is_augmented=False,
|
|
3506
|
+
):
|
|
3507
|
+
"""Save a single image and label tile."""
|
|
3508
|
+
# Export image as GeoTIFF
|
|
3509
|
+
image_path = os.path.join(image_dir, f"tile_{tile_id:06d}.tif")
|
|
3365
3510
|
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3511
|
+
# Update profile
|
|
3512
|
+
img_profile_copy = img_profile.copy()
|
|
3513
|
+
img_profile_copy.update(
|
|
3514
|
+
{
|
|
3515
|
+
"height": tile_size,
|
|
3516
|
+
"width": tile_size,
|
|
3517
|
+
"count": img_data.shape[0],
|
|
3518
|
+
"transform": window_trans,
|
|
3519
|
+
}
|
|
3520
|
+
)
|
|
3521
|
+
|
|
3522
|
+
# Save image as GeoTIFF
|
|
3523
|
+
try:
|
|
3524
|
+
with rasterio.open(image_path, "w", **img_profile_copy) as dst:
|
|
3525
|
+
dst.write(img_data)
|
|
3526
|
+
stats["total_tiles"] += 1
|
|
3527
|
+
except Exception as e:
|
|
3528
|
+
pbar.write(f"ERROR saving image GeoTIFF: {e}")
|
|
3529
|
+
stats["errors"] += 1
|
|
3530
|
+
return
|
|
3531
|
+
|
|
3532
|
+
# Export label as GeoTIFF (only if class data provided)
|
|
3533
|
+
if in_class_data is not None:
|
|
3534
|
+
# Create profile for label GeoTIFF
|
|
3535
|
+
label_profile = {
|
|
3536
|
+
"driver": "GTiff",
|
|
3537
|
+
"height": tile_size,
|
|
3538
|
+
"width": tile_size,
|
|
3539
|
+
"count": 1,
|
|
3540
|
+
"dtype": "uint8",
|
|
3541
|
+
"crs": src.crs,
|
|
3542
|
+
"transform": window_trans,
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
label_path = os.path.join(label_dir, f"tile_{tile_id:06d}.tif")
|
|
3546
|
+
try:
|
|
3547
|
+
with rasterio.open(label_path, "w", **label_profile) as dst:
|
|
3548
|
+
dst.write(lbl_mask.astype(np.uint8), 1)
|
|
3549
|
+
|
|
3550
|
+
if not is_augmented and np.any(lbl_mask > 0):
|
|
3551
|
+
stats["tiles_with_features"] += 1
|
|
3552
|
+
stats["feature_pixels"] += np.count_nonzero(lbl_mask)
|
|
3553
|
+
except Exception as e:
|
|
3554
|
+
pbar.write(f"ERROR saving label GeoTIFF: {e}")
|
|
3555
|
+
stats["errors"] += 1
|
|
3556
|
+
|
|
3557
|
+
# Save original tile
|
|
3558
|
+
save_tile(
|
|
3559
|
+
image_data,
|
|
3560
|
+
label_mask,
|
|
3561
|
+
tile_index,
|
|
3562
|
+
src.profile,
|
|
3563
|
+
window_transform,
|
|
3564
|
+
is_augmented=False,
|
|
3375
3565
|
)
|
|
3376
3566
|
|
|
3377
|
-
#
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3567
|
+
# Generate and save augmented tiles if enabled
|
|
3568
|
+
if apply_augmentation:
|
|
3569
|
+
for aug_idx in range(augmentation_count):
|
|
3570
|
+
# Prepare image for augmentation (convert from CHW to HWC)
|
|
3571
|
+
img_for_aug = np.transpose(image_data, (1, 2, 0))
|
|
3572
|
+
|
|
3573
|
+
# Ensure uint8 data type for albumentations
|
|
3574
|
+
# Albumentations expects uint8 for most transforms
|
|
3575
|
+
if not np.issubdtype(img_for_aug.dtype, np.uint8):
|
|
3576
|
+
# If image is float, scale to 0-255 and convert to uint8
|
|
3577
|
+
if np.issubdtype(img_for_aug.dtype, np.floating):
|
|
3578
|
+
img_for_aug = (
|
|
3579
|
+
(img_for_aug * 255).clip(0, 255).astype(np.uint8)
|
|
3580
|
+
)
|
|
3581
|
+
else:
|
|
3582
|
+
img_for_aug = img_for_aug.astype(np.uint8)
|
|
3385
3583
|
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3584
|
+
# Apply augmentation
|
|
3585
|
+
try:
|
|
3586
|
+
if in_class_data is not None:
|
|
3587
|
+
# Augment both image and mask
|
|
3588
|
+
augmented = augmentation_transforms(
|
|
3589
|
+
image=img_for_aug, mask=label_mask
|
|
3590
|
+
)
|
|
3591
|
+
aug_image = augmented["image"]
|
|
3592
|
+
aug_mask = augmented["mask"]
|
|
3593
|
+
else:
|
|
3594
|
+
# Augment only image
|
|
3595
|
+
augmented = augmentation_transforms(image=img_for_aug)
|
|
3596
|
+
aug_image = augmented["image"]
|
|
3597
|
+
aug_mask = label_mask
|
|
3398
3598
|
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
with rasterio.open(label_path, "w", **label_profile) as dst:
|
|
3402
|
-
dst.write(label_mask.astype(np.uint8), 1)
|
|
3599
|
+
# Convert back from HWC to CHW
|
|
3600
|
+
aug_image = np.transpose(aug_image, (2, 0, 1))
|
|
3403
3601
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3602
|
+
# Ensure correct dtype for saving
|
|
3603
|
+
aug_image = aug_image.astype(image_data.dtype)
|
|
3604
|
+
|
|
3605
|
+
# Generate unique tile ID for augmented version
|
|
3606
|
+
# Use a collision-free numbering scheme: (tile_index * (augmentation_count + 1)) + aug_idx + 1
|
|
3607
|
+
aug_tile_id = (
|
|
3608
|
+
(tile_index * (augmentation_count + 1)) + aug_idx + 1
|
|
3609
|
+
)
|
|
3610
|
+
|
|
3611
|
+
# Save augmented tile
|
|
3612
|
+
save_tile(
|
|
3613
|
+
aug_image,
|
|
3614
|
+
aug_mask,
|
|
3615
|
+
aug_tile_id,
|
|
3616
|
+
src.profile,
|
|
3617
|
+
window_transform,
|
|
3618
|
+
is_augmented=True,
|
|
3619
|
+
)
|
|
3620
|
+
|
|
3621
|
+
except Exception as e:
|
|
3622
|
+
pbar.write(
|
|
3623
|
+
f"ERROR applying augmentation {aug_idx} to tile {tile_index}: {e}"
|
|
3624
|
+
)
|
|
3625
|
+
stats["errors"] += 1
|
|
3410
3626
|
|
|
3411
3627
|
# Create annotations for object detection if using vector class data
|
|
3412
3628
|
if (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geoai-py
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.2
|
|
4
4
|
Summary: A Python package for using Artificial Intelligence (AI) with geospatial data
|
|
5
5
|
Author-email: Qiusheng Wu <giswqs@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -24,7 +24,7 @@ Requires-Dist: ever-beta
|
|
|
24
24
|
Requires-Dist: geopandas
|
|
25
25
|
Requires-Dist: huggingface_hub
|
|
26
26
|
Requires-Dist: jupyter-server-proxy
|
|
27
|
-
Requires-Dist: leafmap
|
|
27
|
+
Requires-Dist: leafmap>=0.57.1
|
|
28
28
|
Requires-Dist: localtileserver
|
|
29
29
|
Requires-Dist: mapclassify
|
|
30
30
|
Requires-Dist: maplibre
|
|
@@ -60,6 +60,7 @@ plugins:
|
|
|
60
60
|
"examples/*_detection.ipynb",
|
|
61
61
|
"examples/building_footprints_*.ipynb",
|
|
62
62
|
"examples/data_visualization.ipynb",
|
|
63
|
+
"examples/data_augmentation.ipynb",
|
|
63
64
|
"examples/train_*.ipynb",
|
|
64
65
|
"examples/water_dynamics.ipynb",
|
|
65
66
|
"examples/wetland_mapping.ipynb",
|
|
@@ -110,6 +111,7 @@ nav:
|
|
|
110
111
|
- examples/image_tiling.ipynb
|
|
111
112
|
- examples/create_training_data.ipynb
|
|
112
113
|
- examples/export_training_data_formats.ipynb
|
|
114
|
+
- examples/data_augmentation.ipynb
|
|
113
115
|
- examples/building_footprints_usa.ipynb
|
|
114
116
|
- examples/building_footprints_africa.ipynb
|
|
115
117
|
- examples/building_footprints_china.ipynb
|
|
@@ -161,7 +163,7 @@ nav:
|
|
|
161
163
|
- workshops/GeoAI_Workshop_2025.ipynb
|
|
162
164
|
- workshops/AWS_2025.ipynb
|
|
163
165
|
- workshops/TNView_2025.ipynb
|
|
164
|
-
- workshops/
|
|
166
|
+
- workshops/CANVAS_2025.ipynb
|
|
165
167
|
- API Reference:
|
|
166
168
|
- change_detection module: change_detection.md
|
|
167
169
|
- classify module: classify.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "geoai-py"
|
|
3
|
-
version = "0.18.
|
|
3
|
+
version = "0.18.2"
|
|
4
4
|
dynamic = [
|
|
5
5
|
"dependencies",
|
|
6
6
|
]
|
|
@@ -44,7 +44,7 @@ universal = true
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
[tool.bumpversion]
|
|
47
|
-
current_version = "0.18.
|
|
47
|
+
current_version = "0.18.2"
|
|
48
48
|
commit = true
|
|
49
49
|
tag = true
|
|
50
50
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|