geoai-py 0.3.3__py2.py3-none-any.whl → 0.3.5__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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  __author__ = """Qiusheng Wu"""
4
4
  __email__ = "giswqs@gmail.com"
5
- __version__ = "0.3.3"
5
+ __version__ = "0.3.5"
6
6
 
7
7
 
8
8
  import os
geoai/extract.py CHANGED
@@ -13,6 +13,7 @@ import rasterio
13
13
  from rasterio.windows import Window
14
14
  from rasterio.features import shapes
15
15
  from huggingface_hub import hf_hub_download
16
+ import scipy.ndimage as ndimage
16
17
  from .utils import get_raster_stats
17
18
 
18
19
  try:
@@ -25,34 +26,74 @@ except ImportError as e:
25
26
 
26
27
  class CustomDataset(NonGeoDataset):
27
28
  """
28
- A TorchGeo dataset for object extraction.
29
- Using NonGeoDataset to avoid spatial indexing issues.
29
+ A TorchGeo dataset for object extraction with overlapping tiles support.
30
+
31
+ This dataset class creates overlapping image tiles for object detection,
32
+ ensuring complete coverage of the input raster including right and bottom edges.
33
+ It inherits from NonGeoDataset to avoid spatial indexing issues.
34
+
35
+ Attributes:
36
+ raster_path: Path to the input raster file.
37
+ chip_size: Size of image chips to extract (height, width).
38
+ overlap: Amount of overlap between adjacent tiles (0.0-1.0).
39
+ transforms: Transforms to apply to the image.
40
+ verbose: Whether to print detailed processing information.
41
+ stride_x: Horizontal stride between tiles based on overlap.
42
+ stride_y: Vertical stride between tiles based on overlap.
43
+ row_starts: Starting Y positions for each row of tiles.
44
+ col_starts: Starting X positions for each column of tiles.
45
+ crs: Coordinate reference system of the raster.
46
+ transform: Affine transform of the raster.
47
+ height: Height of the raster in pixels.
48
+ width: Width of the raster in pixels.
49
+ count: Number of bands in the raster.
50
+ bounds: Geographic bounds of the raster (west, south, east, north).
51
+ roi: Shapely box representing the region of interest.
52
+ rows: Number of rows of tiles.
53
+ cols: Number of columns of tiles.
54
+ raster_stats: Statistics of the raster.
30
55
  """
31
56
 
32
57
  def __init__(
33
- self, raster_path, chip_size=(512, 512), transforms=None, verbose=False
58
+ self,
59
+ raster_path,
60
+ chip_size=(512, 512),
61
+ overlap=0.5,
62
+ transforms=None,
63
+ verbose=False,
34
64
  ):
35
65
  """
36
- Initialize the dataset.
66
+ Initialize the dataset with overlapping tiles.
37
67
 
38
68
  Args:
39
- raster_path: Path to the input raster file
40
- chip_size: Size of image chips to extract (height, width)
41
- transforms: Transforms to apply to the image
42
- verbose: Whether to print detailed processing information
69
+ raster_path: Path to the input raster file.
70
+ chip_size: Size of image chips to extract (height, width). Default is (512, 512).
71
+ overlap: Amount of overlap between adjacent tiles (0.0-1.0). Default is 0.5 (50%).
72
+ transforms: Transforms to apply to the image. Default is None.
73
+ verbose: Whether to print detailed processing information. Default is False.
74
+
75
+ Raises:
76
+ ValueError: If overlap is too high resulting in non-positive stride.
43
77
  """
44
78
  super().__init__()
45
79
 
46
80
  # Initialize parameters
47
81
  self.raster_path = raster_path
48
82
  self.chip_size = chip_size
83
+ self.overlap = overlap
49
84
  self.transforms = transforms
50
85
  self.verbose = verbose
51
-
52
- # For tracking warnings about multi-band images
53
86
  self.warned_about_bands = False
54
87
 
55
- # Open raster and get metadata
88
+ # Calculate stride based on overlap
89
+ self.stride_x = int(chip_size[1] * (1 - overlap))
90
+ self.stride_y = int(chip_size[0] * (1 - overlap))
91
+
92
+ if self.stride_x <= 0 or self.stride_y <= 0:
93
+ raise ValueError(
94
+ f"Overlap {overlap} is too high, resulting in non-positive stride"
95
+ )
96
+
56
97
  with rasterio.open(self.raster_path) as src:
57
98
  self.crs = src.crs
58
99
  self.transform = src.transform
@@ -63,43 +104,78 @@ class CustomDataset(NonGeoDataset):
63
104
  # Define the bounds of the dataset
64
105
  west, south, east, north = src.bounds
65
106
  self.bounds = (west, south, east, north)
66
-
67
- # Define the ROI for the dataset
68
107
  self.roi = box(*self.bounds)
69
108
 
70
- # Calculate number of chips in each dimension
71
- # Use ceil division to ensure we cover the entire image
72
- self.rows = (self.height + self.chip_size[0] - 1) // self.chip_size[0]
73
- self.cols = (self.width + self.chip_size[1] - 1) // self.chip_size[1]
109
+ # Calculate starting positions for each tile
110
+ self.row_starts = []
111
+ self.col_starts = []
112
+
113
+ # Normal row starts using stride
114
+ for r in range((self.height - 1) // self.stride_y):
115
+ self.row_starts.append(r * self.stride_y)
116
+
117
+ # Add a special last row that ensures we reach the bottom edge
118
+ if self.height > self.chip_size[0]:
119
+ self.row_starts.append(max(0, self.height - self.chip_size[0]))
120
+ else:
121
+ # If the image is smaller than chip size, just start at 0
122
+ if not self.row_starts:
123
+ self.row_starts.append(0)
124
+
125
+ # Normal column starts using stride
126
+ for c in range((self.width - 1) // self.stride_x):
127
+ self.col_starts.append(c * self.stride_x)
128
+
129
+ # Add a special last column that ensures we reach the right edge
130
+ if self.width > self.chip_size[1]:
131
+ self.col_starts.append(max(0, self.width - self.chip_size[1]))
132
+ else:
133
+ # If the image is smaller than chip size, just start at 0
134
+ if not self.col_starts:
135
+ self.col_starts.append(0)
136
+
137
+ # Update rows and cols based on actual starting positions
138
+ self.rows = len(self.row_starts)
139
+ self.cols = len(self.col_starts)
74
140
 
75
141
  print(
76
142
  f"Dataset initialized with {self.rows} rows and {self.cols} columns of chips"
77
143
  )
78
144
  print(f"Image dimensions: {self.width} x {self.height} pixels")
79
145
  print(f"Chip size: {self.chip_size[1]} x {self.chip_size[0]} pixels")
146
+ print(
147
+ f"Overlap: {overlap*100}% (stride_x={self.stride_x}, stride_y={self.stride_y})"
148
+ )
80
149
  if src.crs:
81
150
  print(f"CRS: {src.crs}")
82
151
 
83
- # get raster stats
152
+ # Get raster stats
84
153
  self.raster_stats = get_raster_stats(raster_path, divide_by=255)
85
154
 
86
155
  def __getitem__(self, idx):
87
156
  """
88
157
  Get an image chip from the dataset by index.
89
158
 
159
+ Retrieves an image tile with the specified overlap pattern, ensuring
160
+ proper coverage of the entire raster including edges.
161
+
90
162
  Args:
91
- idx: Index of the chip
163
+ idx: Index of the chip to retrieve.
92
164
 
93
165
  Returns:
94
- Dict containing image tensor
166
+ dict: Dictionary containing:
167
+ - image: Image tensor.
168
+ - bbox: Geographic bounding box for the window.
169
+ - coords: Pixel coordinates as tensor [i, j].
170
+ - window_size: Window size as tensor [width, height].
95
171
  """
96
172
  # Convert flat index to grid position
97
173
  row = idx // self.cols
98
174
  col = idx % self.cols
99
175
 
100
- # Calculate pixel coordinates
101
- i = col * self.chip_size[1]
102
- j = row * self.chip_size[0]
176
+ # Get pre-calculated starting positions
177
+ j = self.row_starts[row]
178
+ i = self.col_starts[col]
103
179
 
104
180
  # Read window from raster
105
181
  with rasterio.open(self.raster_path) as src:
@@ -166,7 +242,12 @@ class CustomDataset(NonGeoDataset):
166
242
  }
167
243
 
168
244
  def __len__(self):
169
- """Return the number of samples in the dataset."""
245
+ """
246
+ Return the number of samples in the dataset.
247
+
248
+ Returns:
249
+ int: Total number of tiles in the dataset.
250
+ """
170
251
  return self.rows * self.cols
171
252
 
172
253
 
@@ -761,7 +842,9 @@ class ObjectDetector:
761
842
  print(f"- Edge buffer size: {edge_buffer} pixels")
762
843
 
763
844
  # Create dataset
764
- dataset = CustomDataset(raster_path=raster_path, chip_size=chip_size)
845
+ dataset = CustomDataset(
846
+ raster_path=raster_path, chip_size=chip_size, overlap=overlap
847
+ )
765
848
  self.raster_stats = dataset.raster_stats
766
849
 
767
850
  # Custom collate function to handle Shapely objects
@@ -969,6 +1052,7 @@ class ObjectDetector:
969
1052
  )
970
1053
  chip_size = kwargs.get("chip_size", self.chip_size)
971
1054
  mask_threshold = kwargs.get("mask_threshold", self.mask_threshold)
1055
+ overlap = kwargs.get("overlap", self.overlap)
972
1056
 
973
1057
  # Set default output path if not provided
974
1058
  if output_path is None:
@@ -982,7 +1066,10 @@ class ObjectDetector:
982
1066
 
983
1067
  # Create dataset
984
1068
  dataset = CustomDataset(
985
- raster_path=raster_path, chip_size=chip_size, verbose=verbose
1069
+ raster_path=raster_path,
1070
+ chip_size=chip_size,
1071
+ overlap=overlap,
1072
+ verbose=verbose,
986
1073
  )
987
1074
 
988
1075
  # Store a flag to avoid repetitive messages
@@ -1794,6 +1881,296 @@ class ObjectDetector:
1794
1881
  plt.savefig(sample_output, dpi=300, bbox_inches="tight")
1795
1882
  print(f"Sample visualization saved to {sample_output}")
1796
1883
 
1884
+ def generate_masks(
1885
+ self,
1886
+ raster_path,
1887
+ output_path=None,
1888
+ confidence_threshold=None,
1889
+ mask_threshold=None,
1890
+ overlap=0.25,
1891
+ batch_size=4,
1892
+ verbose=False,
1893
+ **kwargs,
1894
+ ):
1895
+ """
1896
+ Save masks with confidence values as a multi-band GeoTIFF.
1897
+
1898
+ Args:
1899
+ raster_path: Path to input raster
1900
+ output_path: Path for output GeoTIFF
1901
+ confidence_threshold: Minimum confidence score (0.0-1.0)
1902
+ mask_threshold: Threshold for mask binarization (0.0-1.0)
1903
+ overlap: Overlap between tiles (0.0-1.0)
1904
+ batch_size: Batch size for processing
1905
+ verbose: Whether to print detailed processing information
1906
+
1907
+ Returns:
1908
+ Path to the saved GeoTIFF
1909
+ """
1910
+ # Use provided thresholds or fall back to instance defaults
1911
+ if confidence_threshold is None:
1912
+ confidence_threshold = self.confidence_threshold
1913
+ if mask_threshold is None:
1914
+ mask_threshold = self.mask_threshold
1915
+
1916
+ chip_size = kwargs.get("chip_size", self.chip_size)
1917
+
1918
+ # Default output path
1919
+ if output_path is None:
1920
+ output_path = os.path.splitext(raster_path)[0] + "_masks_conf.tif"
1921
+
1922
+ # Process the raster to get individual masks with confidence
1923
+ with rasterio.open(raster_path) as src:
1924
+ # Create dataset with the specified overlap
1925
+ dataset = CustomDataset(
1926
+ raster_path=raster_path,
1927
+ chip_size=chip_size,
1928
+ overlap=overlap,
1929
+ verbose=verbose,
1930
+ )
1931
+
1932
+ # Create output profile
1933
+ output_profile = src.profile.copy()
1934
+ output_profile.update(
1935
+ dtype=rasterio.uint8,
1936
+ count=2, # Two bands: mask and confidence
1937
+ compress="lzw",
1938
+ nodata=0,
1939
+ )
1940
+
1941
+ # Initialize mask and confidence arrays
1942
+ mask_array = np.zeros((src.height, src.width), dtype=np.uint8)
1943
+ conf_array = np.zeros((src.height, src.width), dtype=np.uint8)
1944
+
1945
+ # Define custom collate function to handle Shapely objects
1946
+ def custom_collate(batch):
1947
+ """
1948
+ Custom collate function that handles Shapely geometries
1949
+ by keeping them as Python objects rather than trying to collate them.
1950
+ """
1951
+ elem = batch[0]
1952
+ if isinstance(elem, dict):
1953
+ result = {}
1954
+ for key in elem:
1955
+ if key == "bbox":
1956
+ # Don't collate shapely objects, keep as list
1957
+ result[key] = [d[key] for d in batch]
1958
+ else:
1959
+ # For tensors and other collatable types
1960
+ try:
1961
+ result[key] = (
1962
+ torch.utils.data._utils.collate.default_collate(
1963
+ [d[key] for d in batch]
1964
+ )
1965
+ )
1966
+ except TypeError:
1967
+ # Fall back to list for non-collatable types
1968
+ result[key] = [d[key] for d in batch]
1969
+ return result
1970
+ else:
1971
+ # Default collate for non-dict types
1972
+ return torch.utils.data._utils.collate.default_collate(batch)
1973
+
1974
+ # Create dataloader with custom collate function
1975
+ dataloader = torch.utils.data.DataLoader(
1976
+ dataset,
1977
+ batch_size=batch_size,
1978
+ shuffle=False,
1979
+ num_workers=0,
1980
+ collate_fn=custom_collate,
1981
+ )
1982
+
1983
+ # Process batches
1984
+ print(f"Processing raster with {len(dataloader)} batches")
1985
+ for batch in tqdm(dataloader):
1986
+ # Move images to device
1987
+ images = batch["image"].to(self.device)
1988
+ coords = batch["coords"] # Tensor of shape [batch_size, 2]
1989
+
1990
+ # Run inference
1991
+ with torch.no_grad():
1992
+ predictions = self.model(images)
1993
+
1994
+ # Process predictions
1995
+ for idx, prediction in enumerate(predictions):
1996
+ masks = prediction["masks"].cpu().numpy()
1997
+ scores = prediction["scores"].cpu().numpy()
1998
+
1999
+ # Filter by confidence threshold
2000
+ valid_indices = scores >= confidence_threshold
2001
+ masks = masks[valid_indices]
2002
+ scores = scores[valid_indices]
2003
+
2004
+ # Skip if no valid predictions
2005
+ if len(masks) == 0:
2006
+ continue
2007
+
2008
+ # Get window coordinates
2009
+ i, j = coords[idx].cpu().numpy()
2010
+
2011
+ # Process each mask
2012
+ for mask_idx, mask in enumerate(masks):
2013
+ # Convert to binary mask
2014
+ binary_mask = (mask[0] > mask_threshold).astype(np.uint8) * 255
2015
+ conf_value = int(scores[mask_idx] * 255) # Scale to 0-255
2016
+
2017
+ # Update the mask and confidence arrays
2018
+ h, w = binary_mask.shape
2019
+ valid_h = min(h, src.height - j)
2020
+ valid_w = min(w, src.width - i)
2021
+
2022
+ if valid_h > 0 and valid_w > 0:
2023
+ # Use maximum for overlapping regions in the mask
2024
+ mask_array[j : j + valid_h, i : i + valid_w] = np.maximum(
2025
+ mask_array[j : j + valid_h, i : i + valid_w],
2026
+ binary_mask[:valid_h, :valid_w],
2027
+ )
2028
+
2029
+ # For confidence, only update where mask is positive
2030
+ # and confidence is higher than existing
2031
+ mask_region = binary_mask[:valid_h, :valid_w] > 0
2032
+ if np.any(mask_region):
2033
+ # Only update where mask is positive and new confidence is higher
2034
+ current_conf = conf_array[
2035
+ j : j + valid_h, i : i + valid_w
2036
+ ]
2037
+
2038
+ # Where to update confidence (mask positive & higher confidence)
2039
+ update_mask = np.logical_and(
2040
+ mask_region,
2041
+ np.logical_or(
2042
+ current_conf == 0, current_conf < conf_value
2043
+ ),
2044
+ )
2045
+
2046
+ if np.any(update_mask):
2047
+ conf_array[j : j + valid_h, i : i + valid_w][
2048
+ update_mask
2049
+ ] = conf_value
2050
+
2051
+ # Write to GeoTIFF
2052
+ with rasterio.open(output_path, "w", **output_profile) as dst:
2053
+ dst.write(mask_array, 1)
2054
+ dst.write(conf_array, 2)
2055
+
2056
+ print(f"Masks with confidence values saved to {output_path}")
2057
+ return output_path
2058
+
2059
+ def vectorize_masks(
2060
+ self,
2061
+ masks_path,
2062
+ output_path=None,
2063
+ confidence_threshold=0.5,
2064
+ min_object_area=100,
2065
+ max_object_size=None,
2066
+ **kwargs,
2067
+ ):
2068
+ """
2069
+ Convert masks with confidence to vector polygons.
2070
+
2071
+ Args:
2072
+ masks_path: Path to masks GeoTIFF with confidence band.
2073
+ output_path: Path for output GeoJSON.
2074
+ confidence_threshold: Minimum confidence score (0.0-1.0). Default: 0.5
2075
+ min_object_area: Minimum area in pixels to keep an object. Default: 100
2076
+ max_object_size: Maximum area in pixels to keep an object. Default: None
2077
+ **kwargs: Additional parameters
2078
+
2079
+ Returns:
2080
+ GeoDataFrame with car detections and confidence values
2081
+ """
2082
+
2083
+ print(f"Processing masks from: {masks_path}")
2084
+
2085
+ with rasterio.open(masks_path) as src:
2086
+ # Read mask and confidence bands
2087
+ mask_data = src.read(1)
2088
+ conf_data = src.read(2)
2089
+ transform = src.transform
2090
+ crs = src.crs
2091
+
2092
+ # Convert to binary mask
2093
+ binary_mask = mask_data > 0
2094
+
2095
+ # Find connected components
2096
+ labeled_mask, num_features = ndimage.label(binary_mask)
2097
+ print(f"Found {num_features} connected components")
2098
+
2099
+ # Process each component
2100
+ car_polygons = []
2101
+ car_confidences = []
2102
+
2103
+ # Add progress bar
2104
+ for label in tqdm(range(1, num_features + 1), desc="Processing components"):
2105
+ # Create mask for this component
2106
+ component_mask = (labeled_mask == label).astype(np.uint8)
2107
+
2108
+ # Get confidence value (mean of non-zero values in this region)
2109
+ conf_region = conf_data[component_mask > 0]
2110
+ if len(conf_region) > 0:
2111
+ confidence = (
2112
+ np.mean(conf_region) / 255.0
2113
+ ) # Convert back to 0-1 range
2114
+ else:
2115
+ confidence = 0.0
2116
+
2117
+ # Skip if confidence is below threshold
2118
+ if confidence < confidence_threshold:
2119
+ continue
2120
+
2121
+ # Find contours
2122
+ contours, _ = cv2.findContours(
2123
+ component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
2124
+ )
2125
+
2126
+ for contour in contours:
2127
+ # Filter by size
2128
+ area = cv2.contourArea(contour)
2129
+ if area < min_object_area:
2130
+ continue
2131
+
2132
+ if max_object_size is not None:
2133
+ if area > max_object_size:
2134
+ continue
2135
+
2136
+ # Get minimum area rectangle
2137
+ rect = cv2.minAreaRect(contour)
2138
+ box_points = cv2.boxPoints(rect)
2139
+
2140
+ # Convert to geographic coordinates
2141
+ geo_points = []
2142
+ for x, y in box_points:
2143
+ gx, gy = transform * (x, y)
2144
+ geo_points.append((gx, gy))
2145
+
2146
+ # Create polygon
2147
+ poly = Polygon(geo_points)
2148
+
2149
+ # Add to lists
2150
+ car_polygons.append(poly)
2151
+ car_confidences.append(confidence)
2152
+
2153
+ # Create GeoDataFrame
2154
+ if car_polygons:
2155
+ gdf = gpd.GeoDataFrame(
2156
+ {
2157
+ "geometry": car_polygons,
2158
+ "confidence": car_confidences,
2159
+ "class": [1] * len(car_polygons),
2160
+ },
2161
+ crs=crs,
2162
+ )
2163
+
2164
+ # Save to file if requested
2165
+ if output_path:
2166
+ gdf.to_file(output_path, driver="GeoJSON")
2167
+ print(f"Saved {len(gdf)} cars with confidence to {output_path}")
2168
+
2169
+ return gdf
2170
+ else:
2171
+ print("No valid car polygons found")
2172
+ return None
2173
+
1797
2174
 
1798
2175
  class BuildingFootprintExtractor(ObjectDetector):
1799
2176
  """
@@ -1857,8 +2234,7 @@ class CarDetector(ObjectDetector):
1857
2234
  """
1858
2235
  Car detection using a pre-trained Mask R-CNN model.
1859
2236
 
1860
- This class extends the
1861
- `ObjectDetector` class with additional methods for car detection."
2237
+ This class extends the `ObjectDetector` class with additional methods for car detection.
1862
2238
  """
1863
2239
 
1864
2240
  def __init__(
geoai/utils.py CHANGED
@@ -54,6 +54,7 @@ def view_raster(
54
54
  array_args: Optional[Dict] = {},
55
55
  client_args: Optional[Dict] = {"cors_all": False},
56
56
  basemap: Optional[str] = "OpenStreetMap",
57
+ backend: Optional[str] = "folium",
57
58
  **kwargs,
58
59
  ):
59
60
  """
@@ -81,28 +82,54 @@ def view_raster(
81
82
  leafmap.Map: The map object with the raster layer added.
82
83
  """
83
84
 
85
+ if backend == "folium":
86
+ import leafmap.foliumap as leafmap
87
+ else:
88
+ import leafmap.leafmap as leafmap
89
+
84
90
  m = leafmap.Map(basemap=basemap)
85
91
 
86
92
  if isinstance(source, dict):
87
93
  source = dict_to_image(source)
88
94
 
89
- m.add_raster(
90
- source=source,
91
- indexes=indexes,
92
- colormap=colormap,
93
- vmin=vmin,
94
- vmax=vmax,
95
- nodata=nodata,
96
- attribution=attribution,
97
- layer_name=layer_name,
98
- layer_index=layer_index,
99
- zoom_to_layer=zoom_to_layer,
100
- visible=visible,
101
- opacity=opacity,
102
- array_args=array_args,
103
- client_args=client_args,
104
- **kwargs,
105
- )
95
+ if (
96
+ isinstance(source, str)
97
+ and source.lower().endswith(".tif")
98
+ and source.startswith("http")
99
+ ):
100
+ if indexes is not None:
101
+ kwargs["bidx"] = indexes
102
+ if colormap is not None:
103
+ kwargs["colormap_name"] = colormap
104
+ if attribution is None:
105
+ attribution = "TiTiler"
106
+
107
+ m.add_cog_layer(
108
+ source,
109
+ name=layer_name,
110
+ opacity=opacity,
111
+ attribution=attribution,
112
+ zoom_to_layer=zoom_to_layer,
113
+ **kwargs,
114
+ )
115
+ else:
116
+ m.add_raster(
117
+ source=source,
118
+ indexes=indexes,
119
+ colormap=colormap,
120
+ vmin=vmin,
121
+ vmax=vmax,
122
+ nodata=nodata,
123
+ attribution=attribution,
124
+ layer_name=layer_name,
125
+ layer_index=layer_index,
126
+ zoom_to_layer=zoom_to_layer,
127
+ visible=visible,
128
+ opacity=opacity,
129
+ array_args=array_args,
130
+ client_args=client_args,
131
+ **kwargs,
132
+ )
106
133
  return m
107
134
 
108
135
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: geoai-py
3
- Version: 0.3.3
3
+ Version: 0.3.5
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
@@ -0,0 +1,13 @@
1
+ geoai/__init__.py,sha256=hZW6Fvb463-wq7iWzdssC41ErmU2G7st8ObOjmJfPd8,923
2
+ geoai/download.py,sha256=4GiDmLrp2wKslgfm507WeZrwOdYcMekgQXxWGbl5cBw,13094
3
+ geoai/extract.py,sha256=fyueuGWqL8vd9NqNyIWtxSFhbBSMUGCCkxySTR4xjy4,91467
4
+ geoai/geoai.py,sha256=7sbZ2LCZXaO0Io4Y7UH5tcQMFZH-sjYu_NENRKfyL5o,64
5
+ geoai/preprocess.py,sha256=ddUZUZ2fLNjzSIpXby7-MszM16GZt_gE9BMX4jdUZMw,119217
6
+ geoai/segmentation.py,sha256=Vcymnhwl_xikt4v9x8CYJq_vId9R1gB7-YzLfwg-F9M,11372
7
+ geoai/utils.py,sha256=cLBKrpXiYxFZWVR73ql0gHnt-78vSCvX7NPetvnTU4U,191690
8
+ geoai_py-0.3.5.dist-info/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
9
+ geoai_py-0.3.5.dist-info/METADATA,sha256=HYWbzmYKYhO0FoIsrY0HGWRYGgpUKKL46Yixh_h0b2c,6059
10
+ geoai_py-0.3.5.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
11
+ geoai_py-0.3.5.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
12
+ geoai_py-0.3.5.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
13
+ geoai_py-0.3.5.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- geoai/__init__.py,sha256=WBRcLvq3h_J4SoGXJN2Nx99ENQkPAHq3PtGMW-eQrGw,923
2
- geoai/download.py,sha256=4GiDmLrp2wKslgfm507WeZrwOdYcMekgQXxWGbl5cBw,13094
3
- geoai/extract.py,sha256=hcGFKwEn5a82sLM0C_nzBdJjV2hauv51saILaTT_EN0,76249
4
- geoai/geoai.py,sha256=7sbZ2LCZXaO0Io4Y7UH5tcQMFZH-sjYu_NENRKfyL5o,64
5
- geoai/preprocess.py,sha256=ddUZUZ2fLNjzSIpXby7-MszM16GZt_gE9BMX4jdUZMw,119217
6
- geoai/segmentation.py,sha256=Vcymnhwl_xikt4v9x8CYJq_vId9R1gB7-YzLfwg-F9M,11372
7
- geoai/utils.py,sha256=f4uib-ZiJfQyJyVHvcZRWl0_HISISTkSiRTj2nJbS4I,190888
8
- geoai_py-0.3.3.dist-info/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
9
- geoai_py-0.3.3.dist-info/METADATA,sha256=ihYj9AVDicaea_G_yqsiRZW3lblmgBQ4QO1Mh5DVDDk,6059
10
- geoai_py-0.3.3.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
11
- geoai_py-0.3.3.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
12
- geoai_py-0.3.3.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
13
- geoai_py-0.3.3.dist-info/RECORD,,