Luecke commited on
Commit
02da240
·
2 Parent(s): 3bb43b3 7cc6647

Merge branch 'main' of https://github.com/jonathanseele/WeCanopy

Browse files
generate_tree_images/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /detected_trees
2
+ /training_images
generate_tree_images/generate_tree_images.ipynb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa9e3f0c51e56d3590954647541e3860ec62d517bed85842d641605276a4dee1
3
+ size 13104
generate_tree_images/generate_tree_images.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import rasterio
3
+ import geopandas as gpd
4
+ from shapely.geometry import box
5
+ from rasterio.mask import mask
6
+ from PIL import Image
7
+ import numpy as np
8
+ import warnings
9
+ from rasterio.errors import NodataShadowWarning
10
+ import sys
11
+
12
+ warnings.filterwarnings("ignore", category=NodataShadowWarning)
13
+
14
+ def cut_trees(output_dir, geojson_path, tif_path):
15
+ # create output directory if it doesnt exist
16
+ if not os.path.exists(output_dir):
17
+ os.makedirs(output_dir)
18
+
19
+ # Load the GeoDataFrame
20
+ gdf = gpd.read_file(geojson_path)
21
+
22
+ # Clear the terminal screen
23
+ os.system('cls' if os.name == 'nt' else 'clear')
24
+
25
+ # Open the .tif file
26
+ with rasterio.open(tif_path) as src:
27
+ # Get the bounds of the .tif image
28
+ tif_bounds = box(*src.bounds)
29
+
30
+ # Get the CRS (Coordinate Reference System) of the .tif image
31
+ tif_crs = src.crs
32
+
33
+ # Reproject the GeoDataFrame to the CRS of the .tif file
34
+ gdf = gdf.to_crs(tif_crs)
35
+
36
+ # Loop through each polygon in the GeoDataFrame
37
+ N = len(gdf)
38
+ n = int(N/10)
39
+ image_counter = 0
40
+ for idx, row in gdf.iterrows():
41
+ if idx % n == 0:
42
+ progress = f"{round(idx/N*100)} % complete --> {idx}/{N}"
43
+ sys.stdout.write('\r' + progress)
44
+ sys.stdout.flush()
45
+
46
+ # Extract the geometry (polygon)
47
+ geom = row['geometry']
48
+ name = row['id']
49
+
50
+ # Check if the polygon intersects the image bounds
51
+ if geom.intersects(tif_bounds):
52
+ # Create a mask for the current polygon
53
+ out_image, out_transform = mask(src, [geom], crop=True)
54
+
55
+ # Convert the masked image to a numpy array
56
+ out_image = out_image.transpose(1, 2, 0) # rearrange dimensions for PIL (H, W, C)
57
+
58
+ # Ensure the array is not empty
59
+ if out_image.size == 0:
60
+ message = f"{round(idx/N*100)} % complete --> {idx}/{N} | Polygon {idx} resulted in an empty image and will be skipped."
61
+ sys.stdout.write('\r' + message)
62
+ sys.stdout.flush()
63
+ continue
64
+
65
+ # Remove the zero-padded areas (optional)
66
+ mask_array = (out_image[:, :, 0] != src.nodata)
67
+ non_zero_rows = np.any(mask_array, axis=1)
68
+ non_zero_cols = np.any(mask_array, axis=0)
69
+
70
+ # Ensure there are non-zero rows and columns
71
+ if not np.any(non_zero_rows) or not np.any(non_zero_cols):
72
+ message = f"{round(idx/N*100)} % complete --> {idx}/{N} | Polygon {idx} resulted in an invalid image area and will be skipped."
73
+ sys.stdout.write('\r' + message)
74
+ sys.stdout.flush()
75
+ continue
76
+
77
+ out_image = out_image[non_zero_rows][:, non_zero_cols]
78
+
79
+ # Convert to a PIL Image and save as PNG
80
+ out_image = Image.fromarray(out_image.astype(np.uint8)) # Ensure correct type for PIL
81
+ output_path = os.path.join(output_dir, f'tree_{name}.png')
82
+ out_image.save(output_path)
83
+ image_counter += 1
84
+ else:
85
+ message = f"{round(idx/N*100)} % complete --> {idx}/{N} | Polygon {idx} is outside the image bounds and will be skipped."
86
+ sys.stdout.write('\r' + message)
87
+ sys.stdout.flush()
88
+
89
+ print(f'\n {image_counter}/{N} Tree images have been successfully saved in the "detected_trees" folder.')
90
+
91
+
92
+ def resize_images(input_folder, output_folder, target_size):
93
+ # Create the output folder if it doesn't exist
94
+ if not os.path.exists(output_folder):
95
+ os.makedirs(output_folder)
96
+
97
+ counter = 0
98
+ # Loop through all files in the input folder
99
+ for filename in os.listdir(input_folder):
100
+ if filename.endswith('.png'): # Check for PNG files
101
+ # Open image
102
+ with Image.open(os.path.join(input_folder, filename)) as img:
103
+ # Resize image while preserving aspect ratio
104
+ img.thumbnail(target_size, Image.LANCZOS)
105
+ # Calculate paste position to center image in canvas
106
+ paste_pos = ((target_size[0] - img.size[0]) // 2, (target_size[1] - img.size[1]) // 2)
107
+ # Create a new blank canvas with the target size and black background
108
+ new_img = Image.new("RGBA", target_size, (0, 0, 0, 255))
109
+ # Paste resized image onto the canvas
110
+ new_img.paste(img, paste_pos, img)
111
+ # Convert to RGB to remove transparency by merging with black background
112
+ new_img = new_img.convert("RGB")
113
+ # Save resized image to output folder
114
+ new_img.save(os.path.join(output_folder, filename))
115
+
116
+ counter += 1
117
+ # Display the counter
118
+ if counter % 50 == 0:
119
+ message = f"Processed {counter} images"
120
+ print(message, end='\r')
121
+
122
+ # Final message after processing all images
123
+ print(f"Processed a total of {counter} images.")
124
+
125
+
126
+ # THIS IS THE FUNCTION TO IMPORT
127
+ def generate_tree_images(geojson_path, tif_path, target_size = (224, 224)):
128
+ """
129
+ INPUT: geojson path, tif_path that contain the trees, optional target_size of the resulting images
130
+
131
+ RETURNS: nothing
132
+
133
+ Action: It creates two folders: + "detected trees" --> the cut tree images
134
+ + "tree_images" --> the processed cut tree images, ready to use for species recognition
135
+ """
136
+
137
+
138
+ # Set input and output folders
139
+ folder_cut_trees = "detected_trees"
140
+ folder_finished_images = "tree_images"
141
+ # Set target size (width, height)
142
+ cut_trees(geojson_path = geojson_path, tif_path = tif_path, output_dir = folder_cut_trees)
143
+ resize_images(input_folder = folder_cut_trees, output_folder = folder_finished_images, target_size = target_size)
144
+
145
+
generate_tree_images/readme.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ## Use the generate_tree_images.py for the pipeline
2
+
3
+ --> use the function *generate_tree_images*