geoai-py 0.13.1__tar.gz → 0.13.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.
Files changed (48) hide show
  1. {geoai_py-0.13.1 → geoai_py-0.13.2}/PKG-INFO +1 -1
  2. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/__init__.py +1 -1
  3. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/train.py +118 -8
  4. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/PKG-INFO +1 -1
  5. {geoai_py-0.13.1 → geoai_py-0.13.2}/pyproject.toml +2 -2
  6. {geoai_py-0.13.1 → geoai_py-0.13.2}/.dockerignore +0 -0
  7. {geoai_py-0.13.1 → geoai_py-0.13.2}/.editorconfig +0 -0
  8. {geoai_py-0.13.1 → geoai_py-0.13.2}/.gitignore +0 -0
  9. {geoai_py-0.13.1 → geoai_py-0.13.2}/.pre-commit-config.yaml +0 -0
  10. {geoai_py-0.13.1 → geoai_py-0.13.2}/Dockerfile +0 -0
  11. {geoai_py-0.13.1 → geoai_py-0.13.2}/LICENSE +0 -0
  12. {geoai_py-0.13.1 → geoai_py-0.13.2}/MANIFEST.in +0 -0
  13. {geoai_py-0.13.1 → geoai_py-0.13.2}/README.md +0 -0
  14. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/agents/__init__.py +0 -0
  15. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/agents/geo_agents.py +0 -0
  16. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/agents/map_tools.py +0 -0
  17. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/change_detection.py +0 -0
  18. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/classify.py +0 -0
  19. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/detectron2.py +0 -0
  20. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/dinov3.py +0 -0
  21. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/download.py +0 -0
  22. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/extract.py +0 -0
  23. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/geoai.py +0 -0
  24. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/hf.py +0 -0
  25. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/map_widgets.py +0 -0
  26. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/sam.py +0 -0
  27. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/segment.py +0 -0
  28. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/segmentation.py +0 -0
  29. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai/utils.py +0 -0
  30. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/SOURCES.txt +0 -0
  31. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/dependency_links.txt +0 -0
  32. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/entry_points.txt +0 -0
  33. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/requires.txt +0 -0
  34. {geoai_py-0.13.1 → geoai_py-0.13.2}/geoai_py.egg-info/top_level.txt +0 -0
  35. {geoai_py-0.13.1 → geoai_py-0.13.2}/mkdocs.yml +0 -0
  36. {geoai_py-0.13.1 → geoai_py-0.13.2}/pytest.ini +0 -0
  37. {geoai_py-0.13.1 → geoai_py-0.13.2}/requirements.txt +0 -0
  38. {geoai_py-0.13.1 → geoai_py-0.13.2}/requirements_docs.txt +0 -0
  39. {geoai_py-0.13.1 → geoai_py-0.13.2}/setup.cfg +0 -0
  40. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/__init__.py +0 -0
  41. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/create_test_data.py +0 -0
  42. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_classify.py +0 -0
  43. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_download.py +0 -0
  44. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_extract.py +0 -0
  45. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_fixtures.py +0 -0
  46. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_geoai.py +0 -0
  47. {geoai_py-0.13.1 → geoai_py-0.13.2}/tests/test_segment.py +0 -0
  48. {geoai_py-0.13.1 → geoai_py-0.13.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.13.1
3
+ Version: 0.13.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  __author__ = """Qiusheng Wu"""
4
4
  __email__ = "giswqs@gmail.com"
5
- __version__ = "0.13.1"
5
+ __version__ = "0.13.2"
6
6
 
7
7
 
8
8
  import os
@@ -2625,6 +2625,8 @@ def semantic_inference_on_geotiff(
2625
2625
  num_channels: int = 3,
2626
2626
  num_classes: int = 2,
2627
2627
  device: Optional[torch.device] = None,
2628
+ probability_path: Optional[str] = None,
2629
+ probability_threshold: Optional[float] = None,
2628
2630
  quiet: bool = False,
2629
2631
  **kwargs: Any,
2630
2632
  ) -> Tuple[str, float]:
@@ -2641,6 +2643,11 @@ def semantic_inference_on_geotiff(
2641
2643
  num_channels (int): Number of channels to use from the input image.
2642
2644
  num_classes (int): Number of classes in the model output.
2643
2645
  device (torch.device, optional): Device to run inference on.
2646
+ probability_path (str, optional): Path to save probability map. If provided,
2647
+ the normalized class probabilities will be saved as a multi-band raster.
2648
+ probability_threshold (float, optional): Probability threshold for binary classification.
2649
+ Only used when num_classes=2. If provided, pixels with class 1 probability >= threshold
2650
+ are classified as class 1, otherwise class 0. If None (default), uses argmax.
2644
2651
  quiet (bool): If True, suppress progress bar. Defaults to False.
2645
2652
  **kwargs: Additional arguments.
2646
2653
 
@@ -2811,10 +2818,19 @@ def semantic_inference_on_geotiff(
2811
2818
  / count_accumulator[valid_pixels]
2812
2819
  )
2813
2820
 
2814
- # Take argmax to get final class predictions
2815
- mask[valid_pixels] = np.argmax(
2816
- normalized_probs[:, valid_pixels], axis=0
2817
- ).astype(np.uint8)
2821
+ # Apply threshold for binary classification or use argmax
2822
+ if probability_threshold is not None and num_classes == 2:
2823
+ # Use threshold: classify as class 1 if probability >= threshold
2824
+ mask[valid_pixels] = (
2825
+ normalized_probs[1, valid_pixels] >= probability_threshold
2826
+ ).astype(np.uint8)
2827
+ if not quiet:
2828
+ print(f"Using probability threshold: {probability_threshold}")
2829
+ else:
2830
+ # Take argmax to get final class predictions
2831
+ mask[valid_pixels] = np.argmax(
2832
+ normalized_probs[:, valid_pixels], axis=0
2833
+ ).astype(np.uint8)
2818
2834
 
2819
2835
  # Check class distribution in predictions (summary only)
2820
2836
  unique_classes, class_counts = np.unique(
@@ -2839,6 +2855,29 @@ def semantic_inference_on_geotiff(
2839
2855
  if not quiet:
2840
2856
  print(f"Saved prediction to {output_path}")
2841
2857
 
2858
+ # Save probability map if requested
2859
+ if probability_path is not None:
2860
+ prob_dir = os.path.abspath(os.path.dirname(probability_path))
2861
+ os.makedirs(prob_dir, exist_ok=True)
2862
+
2863
+ # Prepare probability output metadata
2864
+ prob_meta = meta.copy()
2865
+ prob_meta.update({"count": num_classes, "dtype": "float32"})
2866
+
2867
+ # Save normalized probabilities
2868
+ with rasterio.open(probability_path, "w", **prob_meta) as dst:
2869
+ for class_idx in range(num_classes):
2870
+ # Normalize probabilities
2871
+ prob_band = np.zeros((height, width), dtype=np.float32)
2872
+ prob_band[valid_pixels] = (
2873
+ prob_accumulator[class_idx, valid_pixels]
2874
+ / count_accumulator[valid_pixels]
2875
+ )
2876
+ dst.write(prob_band, class_idx + 1)
2877
+
2878
+ if not quiet:
2879
+ print(f"Saved probability map to {probability_path}")
2880
+
2842
2881
  return output_path, inference_time
2843
2882
 
2844
2883
 
@@ -2853,6 +2892,8 @@ def semantic_inference_on_image(
2853
2892
  num_classes: int = 2,
2854
2893
  device: Optional[torch.device] = None,
2855
2894
  binary_output: bool = True,
2895
+ probability_path: Optional[str] = None,
2896
+ probability_threshold: Optional[float] = None,
2856
2897
  quiet: bool = False,
2857
2898
  **kwargs: Any,
2858
2899
  ) -> Tuple[str, float]:
@@ -2870,6 +2911,11 @@ def semantic_inference_on_image(
2870
2911
  num_classes (int): Number of classes in the model output.
2871
2912
  device (torch.device, optional): Device to run inference on.
2872
2913
  binary_output (bool): If True, convert multi-class output to binary (class > 0).
2914
+ probability_path (str, optional): Path to save probability map. If provided,
2915
+ the normalized class probabilities will be saved as a multi-band raster.
2916
+ probability_threshold (float, optional): Probability threshold for binary classification.
2917
+ Only used when num_classes=2. If provided, pixels with class 1 probability >= threshold
2918
+ are classified as class 1, otherwise class 0. If None (default), uses argmax.
2873
2919
  quiet (bool): If True, suppress progress bar. Defaults to False.
2874
2920
  **kwargs: Additional arguments.
2875
2921
 
@@ -3056,10 +3102,19 @@ def semantic_inference_on_image(
3056
3102
  / count_accumulator[valid_pixels]
3057
3103
  )
3058
3104
 
3059
- # Take argmax to get final class predictions
3060
- mask[valid_pixels] = np.argmax(
3061
- normalized_probs[:, valid_pixels], axis=0
3062
- ).astype(np.uint8)
3105
+ # Apply threshold for binary classification or use argmax
3106
+ if probability_threshold is not None and num_classes == 2:
3107
+ # Use threshold: classify as class 1 if probability >= threshold
3108
+ mask[valid_pixels] = (
3109
+ normalized_probs[1, valid_pixels] >= probability_threshold
3110
+ ).astype(np.uint8)
3111
+ if not quiet:
3112
+ print(f"Using probability threshold: {probability_threshold}")
3113
+ else:
3114
+ # Take argmax to get final class predictions
3115
+ mask[valid_pixels] = np.argmax(
3116
+ normalized_probs[:, valid_pixels], axis=0
3117
+ ).astype(np.uint8)
3063
3118
 
3064
3119
  # Check class distribution in predictions before binary conversion
3065
3120
  unique_classes, class_counts = np.unique(mask, return_counts=True)
@@ -3116,6 +3171,40 @@ def semantic_inference_on_image(
3116
3171
  if not quiet:
3117
3172
  print(f"Saved prediction to {output_path}")
3118
3173
 
3174
+ # Save probability map if requested
3175
+ if probability_path is not None:
3176
+ prob_dir = os.path.abspath(os.path.dirname(probability_path))
3177
+ os.makedirs(prob_dir, exist_ok=True)
3178
+
3179
+ # For regular images, we'll save as a multi-channel TIFF
3180
+ # since we need to preserve floating point values
3181
+ import rasterio
3182
+ from rasterio.transform import from_bounds
3183
+
3184
+ # Create a simple affine transform (identity transform for pixel coordinates)
3185
+ transform = from_bounds(0, 0, width, height, width, height)
3186
+
3187
+ # Prepare probability output metadata
3188
+ prob_meta = {
3189
+ "driver": "GTiff",
3190
+ "height": height,
3191
+ "width": width,
3192
+ "count": num_classes,
3193
+ "dtype": "float32",
3194
+ "transform": transform,
3195
+ }
3196
+
3197
+ # Save normalized probabilities
3198
+ with rasterio.open(probability_path, "w", **prob_meta) as dst:
3199
+ for class_idx in range(num_classes):
3200
+ # Normalize probabilities
3201
+ prob_band = np.zeros((height, width), dtype=np.float32)
3202
+ prob_band[valid_pixels] = normalized_probs[class_idx, valid_pixels]
3203
+ dst.write(prob_band, class_idx + 1)
3204
+
3205
+ if not quiet:
3206
+ print(f"Saved probability map to {probability_path}")
3207
+
3119
3208
  return output_path, inference_time
3120
3209
 
3121
3210
 
@@ -3131,6 +3220,8 @@ def semantic_segmentation(
3131
3220
  overlap: int = 256,
3132
3221
  batch_size: int = 4,
3133
3222
  device: Optional[torch.device] = None,
3223
+ probability_path: Optional[str] = None,
3224
+ probability_threshold: Optional[float] = None,
3134
3225
  quiet: bool = False,
3135
3226
  **kwargs: Any,
3136
3227
  ) -> None:
@@ -3152,6 +3243,12 @@ def semantic_segmentation(
3152
3243
  overlap (int): Overlap between adjacent windows.
3153
3244
  batch_size (int): Batch size for inference.
3154
3245
  device (torch.device, optional): Device to run inference on.
3246
+ probability_path (str, optional): Path to save probability map. If provided,
3247
+ the normalized class probabilities will be saved as a multi-band raster.
3248
+ probability_threshold (float, optional): Probability threshold for binary classification.
3249
+ Only used when num_classes=2. If provided, pixels with class 1 probability >= threshold
3250
+ are classified as class 1, otherwise class 0. If None (default), uses argmax.
3251
+ Must be between 0 and 1.
3155
3252
  quiet (bool): If True, suppress progress bar. Defaults to False.
3156
3253
  **kwargs: Additional arguments.
3157
3254
 
@@ -3205,6 +3302,15 @@ def semantic_segmentation(
3205
3302
  model.to(device)
3206
3303
  model.eval()
3207
3304
 
3305
+ # Validate probability_threshold
3306
+ if probability_threshold is not None:
3307
+ if not (0 <= probability_threshold <= 1):
3308
+ raise ValueError("probability_threshold must be between 0 and 1")
3309
+ if num_classes != 2:
3310
+ raise ValueError(
3311
+ "probability_threshold is only supported for binary classification (num_classes=2)"
3312
+ )
3313
+
3208
3314
  # Use appropriate inference function based on file format
3209
3315
  if is_geotiff:
3210
3316
  semantic_inference_on_geotiff(
@@ -3217,6 +3323,8 @@ def semantic_segmentation(
3217
3323
  num_channels=num_channels,
3218
3324
  num_classes=num_classes,
3219
3325
  device=device,
3326
+ probability_path=probability_path,
3327
+ probability_threshold=probability_threshold,
3220
3328
  quiet=quiet,
3221
3329
  **kwargs,
3222
3330
  )
@@ -3235,6 +3343,8 @@ def semantic_segmentation(
3235
3343
  num_classes=num_classes,
3236
3344
  device=device,
3237
3345
  binary_output=True, # Convert to binary output for better visualization
3346
+ probability_path=probability_path,
3347
+ probability_threshold=probability_threshold,
3238
3348
  quiet=quiet,
3239
3349
  **kwargs,
3240
3350
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geoai-py
3
- Version: 0.13.1
3
+ Version: 0.13.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "geoai-py"
3
- version = "0.13.1"
3
+ version = "0.13.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.13.1"
47
+ current_version = "0.13.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