Spaces:
Sleeping
Sleeping
DynamicPacific
commited on
Commit
·
824dd08
1
Parent(s):
67cd09b
add area detection
Browse files- app.py +9 -1
- utils/advanced_extraction.py +26 -5
app.py
CHANGED
|
@@ -28,7 +28,7 @@ logger = logging.getLogger('forestai')
|
|
| 28 |
|
| 29 |
# Feature styles for trees only
|
| 30 |
FEATURE_STYLES = {
|
| 31 |
-
'trees': {"color": "
|
| 32 |
}
|
| 33 |
|
| 34 |
# Example file path
|
|
@@ -134,6 +134,14 @@ def create_split_view_map(geojson_data, bounds):
|
|
| 134 |
)
|
| 135 |
geojson_layer.add_to(m)
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
# Add split view plugin
|
| 138 |
plugins.SideBySideLayers(
|
| 139 |
layer_left=left_layer,
|
|
|
|
| 28 |
|
| 29 |
# Feature styles for trees only
|
| 30 |
FEATURE_STYLES = {
|
| 31 |
+
'trees': {"color": "yellow", "fillColor": "yellow", "fillOpacity": 0.3, "weight": 2}
|
| 32 |
}
|
| 33 |
|
| 34 |
# Example file path
|
|
|
|
| 134 |
)
|
| 135 |
geojson_layer.add_to(m)
|
| 136 |
|
| 137 |
+
# Add red outline for the region bounding box
|
| 138 |
+
folium.Rectangle(
|
| 139 |
+
bounds=[[south, west], [north, east]],
|
| 140 |
+
color='red',
|
| 141 |
+
weight=3,
|
| 142 |
+
fill=False
|
| 143 |
+
).add_to(m)
|
| 144 |
+
|
| 145 |
# Add split view plugin
|
| 146 |
plugins.SideBySideLayers(
|
| 147 |
layer_left=left_layer,
|
utils/advanced_extraction.py
CHANGED
|
@@ -12,26 +12,38 @@ def extract_features_from_geotiff(image_path, output_folder, feature_type="trees
|
|
| 12 |
logging.info(f"Extracting {feature_type} from {image_path}")
|
| 13 |
|
| 14 |
with rasterio.open(image_path) as src:
|
|
|
|
|
|
|
| 15 |
# Enhanced NDVI calculation
|
| 16 |
if src.count >= 3:
|
| 17 |
red = src.read(1).astype(float)
|
| 18 |
green = src.read(2).astype(float)
|
| 19 |
nir = src.read(4).astype(float) if src.count >= 4 else green
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# Improved NDVI calculation
|
| 22 |
ndvi = np.divide(nir - red, nir + red + 1e-10)
|
| 23 |
|
| 24 |
# Adaptive thresholding based on image statistics
|
| 25 |
ndvi_mean = np.mean(ndvi)
|
| 26 |
ndvi_std = np.std(ndvi)
|
| 27 |
-
threshold = max(0.
|
|
|
|
|
|
|
| 28 |
|
| 29 |
mask = ndvi > threshold
|
|
|
|
| 30 |
else:
|
| 31 |
band = src.read(1)
|
|
|
|
| 32 |
# Adaptive threshold for single-band images
|
| 33 |
-
threshold = np.percentile(band,
|
|
|
|
| 34 |
mask = band > threshold
|
|
|
|
| 35 |
|
| 36 |
# Get bounds
|
| 37 |
bounds = src.bounds
|
|
@@ -45,30 +57,38 @@ def extract_features_from_geotiff(image_path, output_folder, feature_type="trees
|
|
| 45 |
|
| 46 |
# Convert to uint8 for OpenCV processing
|
| 47 |
mask_uint8 = (mask * 255).astype(np.uint8)
|
|
|
|
| 48 |
|
| 49 |
# Morphological operations to clean up the mask
|
| 50 |
kernel = np.ones((3, 3), np.uint8)
|
| 51 |
mask_cleaned = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, kernel)
|
| 52 |
mask_cleaned = cv2.morphologyEx(mask_cleaned, cv2.MORPH_OPEN, kernel)
|
| 53 |
|
|
|
|
|
|
|
| 54 |
# Find contours instead of using grid
|
| 55 |
contours, _ = cv2.findContours(mask_cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
| 56 |
|
| 57 |
# Filter contours by area and create features
|
| 58 |
features = []
|
| 59 |
height, width = mask.shape
|
| 60 |
-
min_area = (height * width) * 0.
|
| 61 |
-
max_area = (height * width) * 0.
|
|
|
|
|
|
|
| 62 |
|
| 63 |
for i, contour in enumerate(contours):
|
| 64 |
area = cv2.contourArea(contour)
|
|
|
|
| 65 |
|
| 66 |
# Filter by area
|
| 67 |
if area < min_area or area > max_area:
|
|
|
|
| 68 |
continue
|
| 69 |
|
| 70 |
# Simplify contour for better polygon shape
|
| 71 |
-
epsilon = 0.
|
| 72 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
| 73 |
|
| 74 |
# Convert contour points to geographic coordinates
|
|
@@ -109,6 +129,7 @@ def extract_features_from_geotiff(image_path, output_folder, feature_type="trees
|
|
| 109 |
}
|
| 110 |
|
| 111 |
features.append(feature)
|
|
|
|
| 112 |
|
| 113 |
logging.info(f"Extracted {len(features)} tree features")
|
| 114 |
|
|
|
|
| 12 |
logging.info(f"Extracting {feature_type} from {image_path}")
|
| 13 |
|
| 14 |
with rasterio.open(image_path) as src:
|
| 15 |
+
logging.info(f"Image shape: {src.shape}, bands: {src.count}")
|
| 16 |
+
|
| 17 |
# Enhanced NDVI calculation
|
| 18 |
if src.count >= 3:
|
| 19 |
red = src.read(1).astype(float)
|
| 20 |
green = src.read(2).astype(float)
|
| 21 |
nir = src.read(4).astype(float) if src.count >= 4 else green
|
| 22 |
|
| 23 |
+
logging.info(f"Red band range: {red.min():.3f} to {red.max():.3f}")
|
| 24 |
+
logging.info(f"Green band range: {green.min():.3f} to {green.max():.3f}")
|
| 25 |
+
logging.info(f"NIR band range: {nir.min():.3f} to {nir.max():.3f}")
|
| 26 |
+
|
| 27 |
# Improved NDVI calculation
|
| 28 |
ndvi = np.divide(nir - red, nir + red + 1e-10)
|
| 29 |
|
| 30 |
# Adaptive thresholding based on image statistics
|
| 31 |
ndvi_mean = np.mean(ndvi)
|
| 32 |
ndvi_std = np.std(ndvi)
|
| 33 |
+
threshold = max(0.05, ndvi_mean + 0.1 * ndvi_std) # More lenient threshold
|
| 34 |
+
|
| 35 |
+
logging.info(f"NDVI mean: {ndvi_mean:.3f}, std: {ndvi_std:.3f}, threshold: {threshold:.3f}")
|
| 36 |
|
| 37 |
mask = ndvi > threshold
|
| 38 |
+
logging.info(f"Mask pixels above threshold: {np.sum(mask)} out of {mask.size}")
|
| 39 |
else:
|
| 40 |
band = src.read(1)
|
| 41 |
+
logging.info(f"Single band range: {band.min():.3f} to {band.max():.3f}")
|
| 42 |
# Adaptive threshold for single-band images
|
| 43 |
+
threshold = np.percentile(band, 40) # Reduced from 70 to be more lenient
|
| 44 |
+
logging.info(f"Single band threshold: {threshold:.3f}")
|
| 45 |
mask = band > threshold
|
| 46 |
+
logging.info(f"Mask pixels above threshold: {np.sum(mask)} out of {mask.size}")
|
| 47 |
|
| 48 |
# Get bounds
|
| 49 |
bounds = src.bounds
|
|
|
|
| 57 |
|
| 58 |
# Convert to uint8 for OpenCV processing
|
| 59 |
mask_uint8 = (mask * 255).astype(np.uint8)
|
| 60 |
+
logging.info(f"Mask uint8 range: {mask_uint8.min()} to {mask_uint8.max()}")
|
| 61 |
|
| 62 |
# Morphological operations to clean up the mask
|
| 63 |
kernel = np.ones((3, 3), np.uint8)
|
| 64 |
mask_cleaned = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, kernel)
|
| 65 |
mask_cleaned = cv2.morphologyEx(mask_cleaned, cv2.MORPH_OPEN, kernel)
|
| 66 |
|
| 67 |
+
logging.info(f"After morphological ops: {np.sum(mask_cleaned > 0)} pixels")
|
| 68 |
+
|
| 69 |
# Find contours instead of using grid
|
| 70 |
contours, _ = cv2.findContours(mask_cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 71 |
+
logging.info(f"Found {len(contours)} contours")
|
| 72 |
|
| 73 |
# Filter contours by area and create features
|
| 74 |
features = []
|
| 75 |
height, width = mask.shape
|
| 76 |
+
min_area = (height * width) * 0.0001 # Minimum 0.01% of image area (reduced from 0.1%)
|
| 77 |
+
max_area = (height * width) * 0.2 # Maximum 20% of image area (increased from 10%)
|
| 78 |
+
|
| 79 |
+
logging.info(f"Area filter: min={min_area:.0f}, max={max_area:.0f}")
|
| 80 |
|
| 81 |
for i, contour in enumerate(contours):
|
| 82 |
area = cv2.contourArea(contour)
|
| 83 |
+
logging.info(f"Contour {i}: area={area:.0f}")
|
| 84 |
|
| 85 |
# Filter by area
|
| 86 |
if area < min_area or area > max_area:
|
| 87 |
+
logging.info(f"Contour {i}: filtered out (area {area:.0f} not in range)")
|
| 88 |
continue
|
| 89 |
|
| 90 |
# Simplify contour for better polygon shape
|
| 91 |
+
epsilon = 0.005 * cv2.arcLength(contour, True)
|
| 92 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
| 93 |
|
| 94 |
# Convert contour points to geographic coordinates
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
features.append(feature)
|
| 132 |
+
logging.info(f"Contour {i}: added as feature with {len(polygon_coords)} points")
|
| 133 |
|
| 134 |
logging.info(f"Extracted {len(features)} tree features")
|
| 135 |
|