-
Notifications
You must be signed in to change notification settings - Fork 1
/
maple_workflow.py
340 lines (277 loc) · 10.3 KB
/
maple_workflow.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
"""
MAPLE Workflow
Main Script that runs the inference workflow pipeline.
Pre Process
1. Create water mask
2. Image Tiling
Classification / Inference
3. Infer "ground truth" from images based on the trained model
Post processing
4. Stich back to the original image dims from the tiles (2.)
5. Clean the output based on known ground truth
Project: Permafrost Discovery Gateway: Mapping Application for Arctic Permafrost Land Environment(MAPLE)
PI : Chandi Witharana
Author : Rajitha Udwalpola
"""
import argparse
import mpl_clean_inference as inf_clean
import mpl_divideimg_234_water_new as divide
import mpl_infer_tiles_GPU_new as inference
import mpl_process_shapefile as process
import mpl_stitchshpfile_new as stich
import numpy as np
import os
import sys
import shutil
from pathlib import Path
from mpl_config import MPL_Config
from osgeo import gdal
from skimage import color, filters, io
from skimage.morphology import disk
import tensorflow as tf
# work tag
WORKTAG = 1
DIETAG = 0
def tile_image(config: MPL_Config, input_img_name: str):
"""
Tile the image into multiple pre-deifined sized parts so that the processing can be done on smaller parts due to
processing limitations
Parameters
----------
config : Contains static configuration information regarding the workflow.
input_img_name : Name of the input image
"""
sys.path.append(config.ROOT_DIR)
crop_size = config.CROP_SIZE
# worker roots
worker_img_root = config.INPUT_IMAGE_DIR
worker_divided_img_root = config.DIVIDED_IMAGE_DIR
# input image path
input_img_path = os.path.join(worker_img_root, input_img_name)
# Create subfolder for each image
new_file_name = input_img_name.split(".tif")[0]
worker_divided_img_subroot = os.path.join(worker_divided_img_root, new_file_name)
print(worker_divided_img_subroot)
try:
shutil.rmtree(worker_divided_img_subroot)
except:
print("directory deletion failed")
pass
os.mkdir(worker_divided_img_subroot)
file1 = os.path.join(worker_divided_img_subroot, "image_data.h5")
file2 = os.path.join(worker_divided_img_subroot, "image_param.h5")
# ----------------------------------------------------------------------------------------------------
# Call divide image <mpl_divided_img_water> to put the water mask and also to tile and store the data
# Multiple image overlaps are NOT taken into account in called code.
#
divide.divide_image(config, input_img_path, crop_size, file1, file2)
print("finished tiling")
def cal_water_mask(config: MPL_Config, input_img_name: str):
"""
This will calculate the water mask to avoid (inference) processing of the masked areas with water
Uses gdal to transform the image into the required format.
Parameters
----------
config : Contains static configuration information regarding the workflow.
input_img_name : Name of the input image
"""
image_file_name = (input_img_name).split(".tif")[0]
worker_water_root = config.WATER_MASK_DIR # os.path.join(worker_root, "water_shp")
temp_water_root = (
config.TEMP_W_IMG_DIR
) # os.path.join(worker_root, "temp_8bitmask")
worker_water_subroot = os.path.join(worker_water_root, image_file_name)
temp_water_subroot = os.path.join(temp_water_root, image_file_name)
# Prepare to make directories to create the files
try:
shutil.rmtree(worker_water_subroot)
except:
print("directory deletion failed")
pass
try:
shutil.rmtree(temp_water_subroot)
except:
print("directory deletion failed")
pass
# check local storage for temporary storage
Path(worker_water_subroot).mkdir(parents=True, exist_ok=True)
Path(temp_water_subroot).mkdir(parents=True, exist_ok=True)
output_watermask = os.path.join(
worker_water_subroot, r"%s_watermask.tif" % image_file_name
)
output_tif_8b_file = os.path.join(
temp_water_subroot, r"%s_8bit.tif" % image_file_name
)
nir_band = 3 # set number of NIR band
input_image = os.path.join(config.INPUT_IMAGE_DIR, input_img_name)
# %% Median and Otsu
value = 5
### UPDATED CODE - amal 01/11/2023
# cmd line execution thrown exceptions unable to capture
# Using gdal to execute the gdal_Translate
# output file checked against the cmd line gdal_translate
gdal.UseExceptions() # Enable errors
try:
gdal.Translate(
destName=output_tif_8b_file,
srcDS=input_image,
format="GTiff",
outputType=gdal.GDT_Byte,
)
except RuntimeError:
print("gdal Translate failed with", gdal.GetLastErrorMsg())
pass
image = io.imread(
output_tif_8b_file
) # image[rows, columns, dimensions]-> image[:,:,3] is near Infrared
nir = image[:, :, nir_band]
bilat_img = filters.rank.median(nir, disk(value))
gtif = gdal.Open(input_image)
geotransform = gtif.GetGeoTransform()
sourceSR = gtif.GetProjection()
x = np.shape(image)[1]
y = np.shape(image)[0]
# Normalize and blur before thresholding. Usually instead of normalizing
# a rgb to greyscale transformation is applied. In this case, we are already
# dealing with a single channel so we divide all the pixels in the image by
# 255 to get a value between [0, 1].
normalized_bilat_img = bilat_img / 255
normalized_blurred_bilat_img = filters.gaussian(normalized_bilat_img, sigma=2.0)
# find the threshold to filter if all values are same otsu cannot find value
# hence t is made to 0.0
try:
t = filters.threshold_otsu(normalized_blurred_bilat_img)
except:
t = 0.0
# perform inverse binary thresholding
mask = normalized_blurred_bilat_img > t
# output np array as GeoTiff
dst_ds = gdal.GetDriverByName("GTiff").Create(
output_watermask, x, y, 1, gdal.GDT_Byte, ["NBITS=1"]
)
dst_ds.GetRasterBand(1).WriteArray(mask)
dst_ds.SetGeoTransform(geotransform)
dst_ds.SetProjection(sourceSR)
dst_ds.FlushCache()
dst_ds = None
def infer_image(config: MPL_Config, input_img_name: str):
"""
Inference based on the trained model reperesented by the saved weights
Parameters
----------
config : Contains static configuration information regarding the workflow.
input_img_name : Name of the input image file
"""
sys.path.append(config.ROOT_DIR)
# worker roots
worker_divided_img_root = config.DIVIDED_IMAGE_DIR
# Create subfolder for each image
new_file_name = input_img_name.split(".tif")[0]
worker_divided_img_subroot = os.path.join(worker_divided_img_root, new_file_name)
print(worker_divided_img_subroot)
file1 = os.path.join(worker_divided_img_subroot, "image_data.h5")
file2 = os.path.join(worker_divided_img_subroot, "image_param.h5")
worker_output_shp_root = config.OUTPUT_SHP_DIR
worker_output_shp_subroot = os.path.join(worker_output_shp_root, new_file_name)
try:
shutil.rmtree(worker_output_shp_subroot)
except:
print("directory deletion failed")
pass
inference.inference_image(
config,
worker_output_shp_subroot,
file1,
file2,
new_file_name,
)
print("done")
def stich_shapefile(config: MPL_Config, input_img_name: str):
"""
Put (stich) the image tiles back to the original
Parameters
----------
config : Contains static configuration information regarding the workflow.
input_img_name : Name of the input image file
Returns
-------
"""
sys.path.append(config.ROOT_DIR)
worker_finaloutput_root = config.FINAL_SHP_DIR
worker_output_shp_root = config.OUTPUT_SHP_DIR
# Create subfolder for each image within the worker img root
new_file_name = input_img_name.split(".tif")[0]
worker_finaloutput_subroot = os.path.join(worker_finaloutput_root, new_file_name)
worker_output_shp_subroot = os.path.join(worker_output_shp_root, new_file_name)
try:
shutil.rmtree(worker_finaloutput_subroot)
except:
print("directory deletion failed")
pass
os.mkdir(worker_finaloutput_subroot)
stich.stitch_shapefile(
config,
worker_output_shp_subroot,
worker_finaloutput_subroot,
new_file_name,
new_file_name,
)
return "done Divide"
if __name__ == "__main__":
tf.compat.v1.disable_eager_execution()
parser = argparse.ArgumentParser(
description="Extract IWPs from satellite image scenes using MAPLE."
)
# Optional Arguments
parser.add_argument(
"--image",
required=False,
default="test_image_01.tif",
metavar="<command>",
help="Image name",
)
parser.add_argument(
"--root_dir",
required=False,
default="",
help="The directory path from where the workflow is running. If none is "
"provided, the current working directory will be used by the workflow.",
)
parser.add_argument(
"--weight_file",
required=False,
default="hyp_best_train_weights_final.h5",
help="The file path to where the model weights can be found. Should be "
"relative to the root directory.",
)
parser.add_argument(
"--gpus_per_core",
required=False,
default=1,
help="Number of GPUs available per core. Used to determine how many "
"inference processes to spin up. Set this to 0 if you want to run the "
"workflow on a CPU.",
type=int
)
args = parser.parse_args()
image_name = args.image
config = MPL_Config(
args.root_dir, args.weight_file, num_gpus_per_core=args.gpus_per_core
)
print("1.start caculating wartermask")
cal_water_mask(config, image_name)
print("2. start tiling image")
tile_image(config, image_name)
print("3. start inferencing")
infer_image(config, image_name)
print("4. start stiching")
stich_shapefile(config, image_name)
process.process_shapefile(config, image_name)
print("5. start cleaning")
inf_clean.clean_inference_shapes(
config.CLEAN_DATA_DIR,
config.PROJECTED_SHP_DIR,
"./data/input_bound/sample2_out_boundry.shp",
)
# Once you are done you can check the output on ArcGIS (win) or else you can check in QGIS (nx) Add the image and the
# shp, shx, dbf as layers.