def extract_shorelines(metadata, settings): """ Extracts shorelines from satellite images. KV WRL 2018 Arguments: ----------- metadata: dict contains all the information about the satellite images that were downloaded settings: dict contains the following fields: sitename: str String containig the name of the site cloud_mask_issue: boolean True if there is an issue with the cloud mask and sand pixels are being masked on the images buffer_size: int size of the buffer (m) around the sandy beach over which the pixels are considered in the thresholding algorithm min_beach_area: int minimum allowable object area (in metres^2) for the class 'sand' cloud_thresh: float value between 0 and 1 defining the maximum percentage of cloud cover allowed in the images output_epsg: int output spatial reference system as EPSG code check_detection: boolean True to show each invidual detection and let the user validate the mapped shoreline Returns: ----------- output: dict contains the extracted shorelines and corresponding dates. """ sitename = settings['inputs']['sitename'] filepath_data = settings['inputs']['filepath'] # initialise output structure output = dict([]) # create a subfolder to store the .jpg images showing the detection filepath_jpg = os.path.join(filepath_data, sitename, 'jpg_files', 'detection') if not os.path.exists(filepath_jpg): os.makedirs(filepath_jpg) # close all open figures plt.close('all') print('Mapping shorelines:') # loop through satellite list for satname in metadata.keys(): # get images filepath = SDS_tools.get_filepath(settings['inputs'], satname) filenames = metadata[satname]['filenames'] # initialise the output variables output_timestamp = [ ] # datetime at which the image was acquired (UTC time) output_shoreline = [] # vector of shoreline points output_filename = [ ] # filename of the images from which the shorelines where derived output_cloudcover = [] # cloud cover of the images output_geoaccuracy = [] # georeferencing accuracy of the images output_idxkeep = [ ] # index that were kept during the analysis (cloudy images are skipped) # load classifiers and if satname in ['L5', 'L7', 'L8']: pixel_size = 15 if settings['dark_sand']: clf = joblib.load( os.path.join(os.getcwd(), 'classifiers', 'NN_4classes_Landsat_dark.pkl')) else: clf = joblib.load( os.path.join(os.getcwd(), 'classifiers', 'NN_4classes_Landsat.pkl')) elif satname == 'S2': pixel_size = 10 clf = joblib.load( os.path.join(os.getcwd(), 'classifiers', 'NN_4classes_S2.pkl')) # convert settings['min_beach_area'] and settings['buffer_size'] from metres to pixels buffer_size_pixels = np.ceil(settings['buffer_size'] / pixel_size) min_beach_area_pixels = np.ceil(settings['min_beach_area'] / pixel_size**2) # loop through the images for i in range(len(filenames)): print('\r%s: %d%%' % (satname, int(((i + 1) / len(filenames)) * 100)), end='') # get image filename fn = SDS_tools.get_filenames(filenames[i], filepath, satname) # preprocess image (cloud mask + pansharpening/downsampling) im_ms, georef, cloud_mask, im_extra, imQA = SDS_preprocess.preprocess_single( fn, satname, settings['cloud_mask_issue']) # get image spatial reference system (epsg code) from metadata dict image_epsg = metadata[satname]['epsg'][i] # calculate cloud cover cloud_cover = np.divide( sum(sum(cloud_mask.astype(int))), (cloud_mask.shape[0] * cloud_mask.shape[1])) # skip image if cloud cover is above threshold if cloud_cover > settings['cloud_thresh']: continue # classify image in 4 classes (sand, whitewater, water, other) with NN classifier im_classif, im_labels = classify_image_NN(im_ms, im_extra, cloud_mask, min_beach_area_pixels, clf) # calculate a buffer around the reference shoreline (if any has been digitised) im_ref_buffer = create_shoreline_buffer(cloud_mask.shape, georef, image_epsg, pixel_size, settings) # there are two options to extract to map the contours: # if there are pixels in the 'sand' class --> use find_wl_contours2 (enhanced) # otherwise use find_wl_contours2 (traditional) try: # use try/except structure for long runs if sum(sum(im_labels[:, :, 0])) == 0: # compute MNDWI image (SWIR-G) im_mndwi = SDS_tools.nd_index(im_ms[:, :, 4], im_ms[:, :, 1], cloud_mask) # find water contours on MNDWI grayscale image contours_mwi = find_wl_contours1(im_mndwi, cloud_mask, im_ref_buffer) else: # use classification to refine threshold and extract the sand/water interface contours_wi, contours_mwi = find_wl_contours2( im_ms, im_labels, cloud_mask, buffer_size_pixels, im_ref_buffer) except: print('Could not map shoreline for this image: ' + filenames[i]) continue # process water contours into shorelines shoreline = process_shoreline(contours_mwi, georef, image_epsg, settings) # visualise the mapped shorelines, there are two options: # if settings['check_detection'] = True, shows the detection to the user for accept/reject # if settings['save_figure'] = True, saves a figure for each mapped shoreline if settings['check_detection'] or settings['save_figure']: date = filenames[i][:19] skip_image = show_detection(im_ms, cloud_mask, im_labels, shoreline, image_epsg, georef, settings, date, satname) # if the user decides to skip the image, continue and do not save the mapped shoreline if skip_image: continue # append to output variables output_timestamp.append(metadata[satname]['dates'][i]) output_shoreline.append(shoreline) output_filename.append(filenames[i]) output_cloudcover.append(cloud_cover) output_geoaccuracy.append(metadata[satname]['acc_georef'][i]) output_idxkeep.append(i) # create dictionnary of output output[satname] = { 'dates': output_timestamp, 'shorelines': output_shoreline, 'filename': output_filename, 'cloud_cover': output_cloudcover, 'geoaccuracy': output_geoaccuracy, 'idx': output_idxkeep } print('') # Close figure window if still open if plt.get_fignums(): plt.close() # change the format to have one list sorted by date with all the shorelines (easier to use) output = SDS_tools.merge_output(output) # save outputput structure as output.pkl filepath = os.path.join(filepath_data, sitename) with open(os.path.join(filepath, sitename + '_output.pkl'), 'wb') as f: pickle.dump(output, f) # save output into a gdb.GeoDataFrame gdf = SDS_tools.output_to_gdf(output) # set projection gdf.crs = {'init': 'epsg:' + str(settings['output_epsg'])} # save as geojson gdf.to_file(os.path.join(filepath, sitename + '_output.geojson'), driver='GeoJSON', encoding='utf-8') return output
# [OPTIONAL] create a reference shoreline (helps to identify outliers and false detections) settings["reference_shoreline"] = SDS_preprocess.get_reference_sl( metadata, settings) # set the max distance (in meters) allowed from the reference shoreline for a detected shoreline to be valid settings["max_dist_ref"] = 100 # extract shorelines from all images (also saves output.pkl and shorelines.kml) output = SDS_shoreline.extract_shorelines(metadata, settings) output = SDS_tools.remove_duplicates( output ) # removes duplicates (images taken on the same date by the same satellite) output = SDS_tools.remove_inaccurate_georef(output, 10) # for GIS applications, save output into a GEOJSON layer geomtype = "points" # choose 'points' or 'lines' for the layer geometry gdf = SDS_tools.output_to_gdf(output, geomtype) gdf.crs = { "init": "epsg:" + str(settings["output_epsg"]) } # set layer projection # save GEOJSON layer to file gdf.to_file( os.path.join(filepath_data, sitename, "%s_output_%s.geojson" % (sitename, geomtype)), driver="GeoJSON", encoding="utf-8", ) plots.plot_shorelines(filepath_data, sitename, output) if int(o.mode) == 2 or int(o.mode) == 3: