opticallyshallowdeep 1.1.3__tar.gz → 1.2.0__tar.gz
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.
- {opticallyshallowdeep-1.1.3/opticallyshallowdeep.egg-info → opticallyshallowdeep-1.2.0}/PKG-INFO +33 -8
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/README.md +31 -7
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/check_transpose.py +2 -2
- opticallyshallowdeep-1.2.0/opticallyshallowdeep/cloud_mask.py +103 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/make_multiband_image.py +2 -2
- opticallyshallowdeep-1.2.0/opticallyshallowdeep/make_vertical_strips.py +34 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/netcdf_to_multiband_geotiff.py +2 -2
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/process_as_strips.py +29 -32
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/run.py +32 -14
- opticallyshallowdeep-1.2.0/opticallyshallowdeep/write_georef_image.py +27 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0/opticallyshallowdeep.egg-info}/PKG-INFO +33 -8
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep.egg-info/SOURCES.txt +2 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep.egg-info/requires.txt +1 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/setup.py +2 -2
- opticallyshallowdeep-1.1.3/opticallyshallowdeep/write_georef_image.py +0 -21
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/LICENSE +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/MANIFEST.in +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/__init__.py +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/find_epsg.py +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/models/SR.h5 +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/models/TOA.h5 +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/parse_string.py +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep.egg-info/dependency_links.txt +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep.egg-info/top_level.txt +0 -0
- {opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/setup.cfg +0 -0
{opticallyshallowdeep-1.1.3/opticallyshallowdeep.egg-info → opticallyshallowdeep-1.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opticallyshallowdeep
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Identify optically shallow and deep waters in satellite imagery
|
|
5
5
|
Author: Yulun Wu
|
|
6
6
|
Author-email: yulunwu8@gmail.com
|
|
@@ -16,11 +16,12 @@ Requires-Dist: pyproj
|
|
|
16
16
|
Requires-Dist: joblib
|
|
17
17
|
Requires-Dist: scipy
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
|
+
Requires-Dist: imagecodecs
|
|
19
20
|
Requires-Dist: tensorflow
|
|
20
21
|
|
|
21
22
|
# Optically-Shallow-Deep
|
|
22
23
|
|
|
23
|
-
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network that was trained on a diverse set of global images.
|
|
24
|
+
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network (DNN) that was trained on a diverse set of global images.
|
|
24
25
|
|
|
25
26
|
Supported input includes L1C SAFE files and ACOLITE-processed L2R netCDF files. The output geotiff contains probabilities of water pixels being optically shallow and deep.
|
|
26
27
|
|
|
@@ -77,24 +78,48 @@ pip3 install opticallyshallowdeep
|
|
|
77
78
|
|
|
78
79
|
## Quick Start
|
|
79
80
|
|
|
81
|
+
For L1C files:
|
|
82
|
+
|
|
80
83
|
```python
|
|
81
84
|
import opticallyshallowdeep as osd
|
|
82
85
|
|
|
83
86
|
# Input file
|
|
84
|
-
|
|
87
|
+
file_L1C = 'folder/S2.SAFE'
|
|
88
|
+
|
|
89
|
+
# Output folder
|
|
90
|
+
folder_out = 'folder/test_folder_out'
|
|
91
|
+
|
|
92
|
+
# Run the OSW/ODW classifier
|
|
93
|
+
osd.run(file_L1C, folder_out)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For ACOLITE L2R files:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import opticallyshallowdeep as osd
|
|
100
|
+
|
|
101
|
+
# Input files
|
|
102
|
+
file_L1C = 'test_folder_in/S2.SAFE'
|
|
103
|
+
file_L2R = 'test_folder_in/L2R.nc'
|
|
85
104
|
|
|
86
105
|
# Output folder
|
|
87
106
|
folder_out = 'folder/test_folder_out'
|
|
88
107
|
|
|
89
108
|
# Run the OSW/ODW classifier
|
|
90
|
-
osd.run(file_in, folder_out)
|
|
109
|
+
osd.run(file_in, folder_out, file_L2R=file_L2R)
|
|
91
110
|
```
|
|
92
111
|
|
|
112
|
+
The L1C file is always required as it contains a cloud mask. Pixels within 8 pixels of the cloud mask are masked to reduce the impact of clouds.
|
|
93
113
|
|
|
94
|
-
Output is a 3-band geotiff:
|
|
95
114
|
|
|
96
|
-
-
|
|
97
|
-
- B2: Prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW)
|
|
98
|
-
- B3: pixels that are masked out
|
|
115
|
+
Output is a 1-band geotiff, with values of prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW). Non-water pixels are masked. It is recommended to use pixels between 0 and 40 as ODW, and pixels between 60 and 100 as OSW (publication in review).
|
|
99
116
|
|
|
100
117
|
A log file, an intermediate multi-band geotiff, and a preview PNG are also generated in the output folder. They can be deleted after the processing.
|
|
118
|
+
|
|
119
|
+
## Training, test, and validation data
|
|
120
|
+
|
|
121
|
+
All annotated shapefiles used in training, testing, and validating the DNN model are in the annotated_shapefiles folder, grouped by Sentinel-2 Scene ID.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Optically-Shallow-Deep
|
|
2
2
|
|
|
3
|
-
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network that was trained on a diverse set of global images.
|
|
3
|
+
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network (DNN) that was trained on a diverse set of global images.
|
|
4
4
|
|
|
5
5
|
Supported input includes L1C SAFE files and ACOLITE-processed L2R netCDF files. The output geotiff contains probabilities of water pixels being optically shallow and deep.
|
|
6
6
|
|
|
@@ -57,24 +57,48 @@ pip3 install opticallyshallowdeep
|
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
59
59
|
|
|
60
|
+
For L1C files:
|
|
61
|
+
|
|
60
62
|
```python
|
|
61
63
|
import opticallyshallowdeep as osd
|
|
62
64
|
|
|
63
65
|
# Input file
|
|
64
|
-
|
|
66
|
+
file_L1C = 'folder/S2.SAFE'
|
|
67
|
+
|
|
68
|
+
# Output folder
|
|
69
|
+
folder_out = 'folder/test_folder_out'
|
|
70
|
+
|
|
71
|
+
# Run the OSW/ODW classifier
|
|
72
|
+
osd.run(file_L1C, folder_out)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For ACOLITE L2R files:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import opticallyshallowdeep as osd
|
|
79
|
+
|
|
80
|
+
# Input files
|
|
81
|
+
file_L1C = 'test_folder_in/S2.SAFE'
|
|
82
|
+
file_L2R = 'test_folder_in/L2R.nc'
|
|
65
83
|
|
|
66
84
|
# Output folder
|
|
67
85
|
folder_out = 'folder/test_folder_out'
|
|
68
86
|
|
|
69
87
|
# Run the OSW/ODW classifier
|
|
70
|
-
osd.run(file_in, folder_out)
|
|
88
|
+
osd.run(file_in, folder_out, file_L2R=file_L2R)
|
|
71
89
|
```
|
|
72
90
|
|
|
91
|
+
The L1C file is always required as it contains a cloud mask. Pixels within 8 pixels of the cloud mask are masked to reduce the impact of clouds.
|
|
73
92
|
|
|
74
|
-
Output is a 3-band geotiff:
|
|
75
93
|
|
|
76
|
-
-
|
|
77
|
-
- B2: Prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW)
|
|
78
|
-
- B3: pixels that are masked out
|
|
94
|
+
Output is a 1-band geotiff, with values of prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW). Non-water pixels are masked. It is recommended to use pixels between 0 and 40 as ODW, and pixels between 60 and 100 as OSW (publication in review).
|
|
79
95
|
|
|
80
96
|
A log file, an intermediate multi-band geotiff, and a preview PNG are also generated in the output folder. They can be deleted after the processing.
|
|
97
|
+
|
|
98
|
+
## Training, test, and validation data
|
|
99
|
+
|
|
100
|
+
All annotated shapefiles used in training, testing, and validating the DNN model are in the annotated_shapefiles folder, grouped by Sentinel-2 Scene ID.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import os, sys
|
|
6
|
+
import rasterio
|
|
7
|
+
import numpy as np
|
|
8
|
+
from scipy import ndimage
|
|
9
|
+
|
|
10
|
+
def cloud_mask(file_L1C, buffer_size = 8):
|
|
11
|
+
|
|
12
|
+
print('Making cloud mask...')
|
|
13
|
+
|
|
14
|
+
files = os.listdir(file_L1C)
|
|
15
|
+
metadata = {}
|
|
16
|
+
metadata['file_L1C'] = file_L1C
|
|
17
|
+
|
|
18
|
+
# Identify paths
|
|
19
|
+
for i, fname in enumerate(files):
|
|
20
|
+
tmp = fname.split('.')
|
|
21
|
+
path = '{}/{}'.format(file_L1C,fname)
|
|
22
|
+
|
|
23
|
+
# Granules
|
|
24
|
+
if (fname == 'GRANULE'):
|
|
25
|
+
granules = os.listdir(path)
|
|
26
|
+
|
|
27
|
+
# Check if there is only one granule file
|
|
28
|
+
n_granule = 0
|
|
29
|
+
|
|
30
|
+
for granule in granules:
|
|
31
|
+
if granule[0]=='.':continue
|
|
32
|
+
|
|
33
|
+
n_granule += 1
|
|
34
|
+
if n_granule>1: sys.exit('Warning: more than 1 granule')
|
|
35
|
+
|
|
36
|
+
metadata['granule'] = '{}/{}/{}/IMG_DATA/'.format(file_L1C,fname,granule)
|
|
37
|
+
metadata['MGRS_tile'] = granule.split('_')[1][1:]
|
|
38
|
+
metadata['QI_DATA'] = '{}/{}/{}/QI_DATA'.format(file_L1C,fname,granule)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# # MGRS
|
|
42
|
+
# tile = metadata['MGRS_tile'] + '54905490'
|
|
43
|
+
# d = m.toLatLon(tile)
|
|
44
|
+
# metadata['lat'] = d[0]
|
|
45
|
+
# metadata['lon'] = d[1]
|
|
46
|
+
|
|
47
|
+
# Band files
|
|
48
|
+
image_files = os.listdir(metadata['granule'])
|
|
49
|
+
for image in image_files:
|
|
50
|
+
if image[0]=='.':continue
|
|
51
|
+
if image[-4:]=='.xml':continue
|
|
52
|
+
tmp = image.split('_')
|
|
53
|
+
metadata[tmp[-1][0:3]] = '{}/{}/{}/IMG_DATA/{}'.format(file_L1C,fname,granule,image)
|
|
54
|
+
|
|
55
|
+
### Load built-in mask
|
|
56
|
+
|
|
57
|
+
gml_file = "{}/MSK_CLOUDS_B00.gml".format(metadata['QI_DATA'])
|
|
58
|
+
jp2_file = "{}/MSK_CLASSI_B00.jp2".format(metadata['QI_DATA'])
|
|
59
|
+
|
|
60
|
+
# For imagery before processing baseline 4: Jan 25, 2022
|
|
61
|
+
if os.path.exists(gml_file):
|
|
62
|
+
|
|
63
|
+
# Built-in cloud mask
|
|
64
|
+
import geopandas as gpd
|
|
65
|
+
from rasterio.features import geometry_mask
|
|
66
|
+
|
|
67
|
+
# Load a raster as the base of the mask
|
|
68
|
+
image = metadata['B02']
|
|
69
|
+
|
|
70
|
+
with rasterio.open(image) as src:
|
|
71
|
+
# Read the raster data and transform
|
|
72
|
+
raster_data = src.read(1)
|
|
73
|
+
transform = src.transform
|
|
74
|
+
crs = src.crs
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# Read GML file
|
|
78
|
+
gdf = gpd.read_file(gml_file)
|
|
79
|
+
|
|
80
|
+
# Create a mask using the GML polygons and the GeoTIFF metadata
|
|
81
|
+
mask_cloud = geometry_mask(gdf['geometry'], transform=transform, out_shape=raster_data.shape, invert=True)
|
|
82
|
+
|
|
83
|
+
# Sometimes the GML file contains no information, assume no clouds in such case
|
|
84
|
+
except:
|
|
85
|
+
mask_cloud = np.zeros_like(raster_data)
|
|
86
|
+
|
|
87
|
+
# For imagery processing baseline 4
|
|
88
|
+
elif os.path.exists(jp2_file):
|
|
89
|
+
band_ds = rasterio.open(jp2_file)
|
|
90
|
+
band_array = band_ds.read(1)
|
|
91
|
+
mask_cloud = band_array == 1
|
|
92
|
+
mask_cloud = np.repeat(np.repeat(mask_cloud, 6, axis=0), 6, axis=1)
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
sys.exit('Warning: cloud mask missing in {}.'.format(metadata['QI_DATA']))
|
|
96
|
+
|
|
97
|
+
# To buffer: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.binary_dilation.html
|
|
98
|
+
struct1 = ndimage.generate_binary_structure(2, 1)
|
|
99
|
+
mask_cloud_buffered = ndimage.binary_dilation(mask_cloud, structure=struct1,iterations=buffer_size).astype(mask_cloud.dtype)
|
|
100
|
+
|
|
101
|
+
print('Done')
|
|
102
|
+
return mask_cloud_buffered
|
|
103
|
+
|
|
@@ -13,7 +13,7 @@ def make_multiband_image(file_in,folder_out):
|
|
|
13
13
|
imageFile = os.path.join(folder_out,basename) + '.tif'
|
|
14
14
|
|
|
15
15
|
if os.path.exists(imageFile):
|
|
16
|
-
print('
|
|
16
|
+
print('Multi-band geotiff exists: ' + str(imageFile))
|
|
17
17
|
else:
|
|
18
18
|
print('Making multi-band geotiff: ' + str(imageFile))
|
|
19
19
|
|
|
@@ -26,7 +26,7 @@ def make_multiband_image(file_in,folder_out):
|
|
|
26
26
|
res = int(band2.transform[0])
|
|
27
27
|
arrayList = []
|
|
28
28
|
for bandFile in S2Files:
|
|
29
|
-
print("Reading band: {}".format(bandFile))
|
|
29
|
+
# print("Reading band: {}".format(bandFile))
|
|
30
30
|
band = rasterio.open(bandFile)
|
|
31
31
|
ar = band.read(1)
|
|
32
32
|
bandRes = int(band.transform[0])
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def make_vertical_strips(full_img):
|
|
7
|
+
'''use to save ram, process bigger images faster, and it overlaps so middle image is not
|
|
8
|
+
distorted from how edge pixels are handled'''
|
|
9
|
+
|
|
10
|
+
# number of dimensions
|
|
11
|
+
n_dim = full_img.ndim
|
|
12
|
+
|
|
13
|
+
if n_dim == 2:
|
|
14
|
+
height, width = full_img.shape #this is done so strips do not have artifacts from kernals
|
|
15
|
+
overlap_size = 16 #size of overlap, max tile size is 15, so there is a 1px buffer
|
|
16
|
+
strip1 = full_img[:, :width//5 + overlap_size]#left overlap
|
|
17
|
+
strip2 = full_img[:, width//5: 2*width//5+ overlap_size]# left half overlap
|
|
18
|
+
strip3 = full_img[:, 2*width//5:3*width//5 + overlap_size]#left overlap
|
|
19
|
+
strip4 = full_img[:, 3*width//5:4*width//5+ overlap_size]#left overlap
|
|
20
|
+
strip5 = full_img[:, 4*width//5:width]# no overlap
|
|
21
|
+
elif n_dim == 3:
|
|
22
|
+
height, width, _ = full_img.shape #this is done so strips do not have artifacts from kernals
|
|
23
|
+
overlap_size = 16 #size of overlap, max tile size is 15, so there is a 1px buffer
|
|
24
|
+
strip1 = full_img[:, :width//5 + overlap_size, :]#left overlap
|
|
25
|
+
strip2 = full_img[:, width//5: 2*width//5+ overlap_size, :]# left half overlap
|
|
26
|
+
strip3 = full_img[:, 2*width//5:3*width//5 + overlap_size, :]#left overlap
|
|
27
|
+
strip4 = full_img[:, 3*width//5:4*width//5+ overlap_size, :]#left overlap
|
|
28
|
+
strip5 = full_img[:, 4*width//5:width, :]# no overlap
|
|
29
|
+
else:
|
|
30
|
+
import sys
|
|
31
|
+
sys.exit('Unknown dimension(s) of input imagery to be splited into strips')
|
|
32
|
+
|
|
33
|
+
return [strip1,strip2,strip3,strip4,strip5]
|
|
34
|
+
|
|
@@ -18,7 +18,7 @@ def netcdf_to_multiband_geotiff(netcdf_file, folder_out):
|
|
|
18
18
|
output_geotiff_file = os.path.join(folder_out, tif_base)
|
|
19
19
|
|
|
20
20
|
if os.path.exists(output_geotiff_file):
|
|
21
|
-
print('
|
|
21
|
+
print('Multi-band geotiff exists: ' + str(output_geotiff_file))
|
|
22
22
|
|
|
23
23
|
else:
|
|
24
24
|
|
|
@@ -44,7 +44,7 @@ def netcdf_to_multiband_geotiff(netcdf_file, folder_out):
|
|
|
44
44
|
for i, band_name in enumerate(band_names):
|
|
45
45
|
ar = nc.variables[band_name][:,:] * 10_000
|
|
46
46
|
ar[np.isnan(ar)] = value_for_nodata
|
|
47
|
-
data_array[i] = ar
|
|
47
|
+
data_array[i] = ar.astype('int16')
|
|
48
48
|
|
|
49
49
|
lat = nc.variables['lat'][:,:]
|
|
50
50
|
lon = nc.variables['lon'][:,:]
|
{opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/process_as_strips.py
RENAMED
|
@@ -16,41 +16,32 @@ from scipy.ndimage import binary_dilation
|
|
|
16
16
|
import tensorflow as tf
|
|
17
17
|
from tensorflow.keras.layers import Input, Dense
|
|
18
18
|
from tensorflow.keras.models import Model,load_model
|
|
19
|
+
tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
from .make_vertical_strips import make_vertical_strips
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def process_as_strips (full_img, image_path, if_SR, model_path, selected_columns, model_columns, file_in, cloud_list):
|
|
25
|
+
striplist=make_vertical_strips(full_img) #create a list of strips with overlap
|
|
23
26
|
RGBlist=[]
|
|
27
|
+
|
|
24
28
|
for n in range(len(striplist)):
|
|
25
|
-
print("
|
|
26
|
-
strip_p=process_img_to_rgb(striplist[n],image_path, if_SR, model_path, selected_columns, model_columns, file_in) #output is RGB of image
|
|
29
|
+
print("Strip {}/5".format(n+1))
|
|
30
|
+
strip_p=process_img_to_rgb(striplist[n],image_path, if_SR, model_path, selected_columns, model_columns, file_in, cloud_list[n]) #output is RGB of image
|
|
27
31
|
RGBlist.append(strip_p) #append processed strip to RGB list
|
|
28
32
|
RGB_img=join_vertical_strips(RGBlist[0], RGBlist[1], RGBlist[2], RGBlist[3],RGBlist[4])
|
|
29
33
|
plot_RGB_img(RGB_img, image_path) #save the final image
|
|
30
34
|
return RGB_img
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
def make_vertical_strips(full_img):
|
|
34
|
-
'''use to save ram, process bigger images faster, and it overlaps so middle image is not
|
|
35
|
-
distorted from how edge pixels are handled'''
|
|
36
|
-
height, width, _ = full_img.shape #this is done so strips do not have artifacts from kernals
|
|
37
|
-
overlap_size = 16 #size of overlap, max tile size is 15, so there is a 1px buffer
|
|
38
|
-
strip1 = full_img[:, :width//5 + overlap_size, :]#left overlap
|
|
39
|
-
strip2 = full_img[:, width//5: 2*width//5+ overlap_size, :]# left half overlap
|
|
40
|
-
strip3 = full_img[:, 2*width//5:3*width//5 + overlap_size, :]#left overlap
|
|
41
|
-
strip4 = full_img[:, 3*width//5:4*width//5+ overlap_size, :]#left overlap
|
|
42
|
-
strip5 = full_img[:, 4*width//5:width, :]# no overlap
|
|
43
|
-
return strip1,strip2,strip3,strip4,strip5
|
|
44
|
-
|
|
45
|
-
def process_img_to_rgb(img,file_path, if_SR, model_path, selected_columns, model_columns, file_in):
|
|
36
|
+
def process_img_to_rgb(img, file_path, if_SR, model_path, selected_columns, model_columns, file_in, img_cloud):
|
|
46
37
|
img,img_name,correction=correct_baseline(img,file_path, if_SR, file_in)#used on slices or whole images
|
|
47
|
-
final_cord=get_water_pix_coord(img,correction, if_SR) #getting coordinates of water pixels
|
|
38
|
+
final_cord=get_water_pix_coord(img,correction, if_SR, img_cloud) #getting coordinates of water pixels
|
|
48
39
|
if len(final_cord)==0:
|
|
49
40
|
RGB_img=make_blank_img(img)
|
|
50
41
|
return RGB_img
|
|
51
42
|
else:
|
|
52
43
|
# print(" {} {} Coordinates of non-glinty water pixels".format(time_tracker(start_time),len(final_cord)))
|
|
53
|
-
print(" Processing {}
|
|
44
|
+
print(" Processing {} water pixels".format(len(final_cord)))
|
|
54
45
|
|
|
55
46
|
filter_image = process_image_with_filters(img, selected_columns) #creating a filter image to extract values from
|
|
56
47
|
edge_nodata_list = select_edge_and_buffer_no_data_pixels (img,correction, if_SR) #selecting pixels for slow processing
|
|
@@ -60,7 +51,7 @@ def process_img_to_rgb(img,file_path, if_SR, model_path, selected_columns, model
|
|
|
60
51
|
cord_list, pred_results, con_1=load_model_and_predict_pixels(value_list,model_path,cord_list, if_SR)
|
|
61
52
|
RGB_img=make_output_images_fast(cord_list, pred_results, con_1,img)#make RBG image
|
|
62
53
|
# print(" {} Finished model predictions".format(time_tracker(start_time)))
|
|
63
|
-
print("
|
|
54
|
+
print(" Complete")
|
|
64
55
|
|
|
65
56
|
del cord_list, pred_results, con_1,img
|
|
66
57
|
gc.collect()
|
|
@@ -88,12 +79,10 @@ def correct_baseline(img,file_path, if_SR, file_in):
|
|
|
88
79
|
xml = minidom.parse(xml_path)#look at xml for correction first
|
|
89
80
|
tdom = xml.getElementsByTagName('RADIO_ADD_OFFSET')#if this tag exists it is after baseline 4
|
|
90
81
|
|
|
91
|
-
|
|
92
82
|
tdom_URI = xml.getElementsByTagName('PRODUCT_URI')
|
|
93
83
|
S2_URI = tdom_URI[0].firstChild.nodeValue
|
|
94
84
|
img_name = S2_URI[39:44]
|
|
95
85
|
|
|
96
|
-
|
|
97
86
|
# If no RADIO_ADD_OFFSET
|
|
98
87
|
if len(tdom) == 0:
|
|
99
88
|
|
|
@@ -108,7 +97,7 @@ def correct_baseline(img,file_path, if_SR, file_in):
|
|
|
108
97
|
'''Correction is a very important variable, since in some of the images we need to add 1000 in order to
|
|
109
98
|
correct for baseline 4. In these instances, 0 becomes 1000. There are times where we need to mask out 0 pixels
|
|
110
99
|
or avoid 0, so we use correction as a variable for pixels that are originally 0'''
|
|
111
|
-
print(' Adjusted pixel value for before Baseline 4 processing')
|
|
100
|
+
# print(' Adjusted pixel value for before Baseline 4 processing')
|
|
112
101
|
del chunks
|
|
113
102
|
|
|
114
103
|
# If there is RADIO_ADD_OFFSET
|
|
@@ -119,7 +108,7 @@ def correct_baseline(img,file_path, if_SR, file_in):
|
|
|
119
108
|
del img
|
|
120
109
|
return imgf, img_name, correction
|
|
121
110
|
|
|
122
|
-
def get_water_pix_coord(img,correction, if_SR):
|
|
111
|
+
def get_water_pix_coord(img,correction, if_SR, img_cloud):
|
|
123
112
|
#creates the mask of what is water by using Glint threshold, NDWI, NDSI...
|
|
124
113
|
if if_SR == False:
|
|
125
114
|
glint_t= 1500#this glint thresholds were used when training the model.
|
|
@@ -129,6 +118,7 @@ def get_water_pix_coord(img,correction, if_SR):
|
|
|
129
118
|
glr, glc = glint_coordinates
|
|
130
119
|
glint_coordinates_list = list(zip(glr, glc))#where not glint
|
|
131
120
|
del glr, glc,glint_coordinates
|
|
121
|
+
|
|
132
122
|
b3,b8,b11 = img[:, :, 2].astype(np.float32),img[:, :, 7].astype(np.float32),img[:, :, 9].astype(np.float32)
|
|
133
123
|
NDWI = (b3 - b8) / (b3 + b8 +1e-8) #NDWI with avoiding div 0
|
|
134
124
|
coordinates_NDWI = np.where(NDWI > 0)#where water (used to be 0)
|
|
@@ -136,6 +126,7 @@ def get_water_pix_coord(img,correction, if_SR):
|
|
|
136
126
|
coordinate_list_NDWI = list(zip(ndwir,ndwic))
|
|
137
127
|
del b8,NDWI, coordinates_NDWI,ndwir, ndwic
|
|
138
128
|
gc.collect()
|
|
129
|
+
|
|
139
130
|
NDSI = (b3 - b11) / (b3 + b11 +1e-8) #NDSI with avoiding div 0
|
|
140
131
|
coordinates_NDSI = np.where(NDSI < .42)#where not snow
|
|
141
132
|
ndsir, ndsic = coordinates_NDSI
|
|
@@ -143,11 +134,17 @@ def get_water_pix_coord(img,correction, if_SR):
|
|
|
143
134
|
del b3,b11,NDSI,coordinates_NDSI,ndsir,ndsic
|
|
144
135
|
gc.collect()
|
|
145
136
|
|
|
137
|
+
coordinates_cloud = np.where(np.invert(img_cloud))
|
|
138
|
+
cloudr, cloudc = coordinates_cloud
|
|
139
|
+
coordinate_list_cloud = list(zip(cloudr, cloudc))#where not glint
|
|
140
|
+
del cloudr, cloudc,coordinates_cloud
|
|
141
|
+
gc.collect()
|
|
142
|
+
|
|
146
143
|
# L1C
|
|
147
144
|
if if_SR == False:
|
|
148
145
|
ND_coordinates = np.column_stack(np.where(np.all((img > correction) & (img < 30000), axis=-1)))#where not no data (in any band)
|
|
149
146
|
ND_coordinates_list = list(map(tuple, ND_coordinates))
|
|
150
|
-
common_coordinates_set = set(glint_coordinates_list) & set(ND_coordinates_list)& set(coordinate_list_NDWI)& set(coordinate_list_NDSI)
|
|
147
|
+
common_coordinates_set = set(glint_coordinates_list) & set(ND_coordinates_list)& set(coordinate_list_NDWI)& set(coordinate_list_NDSI)& set(coordinate_list_cloud)
|
|
151
148
|
common_coordinates_list = list(common_coordinates_set) # Convert set to list
|
|
152
149
|
del ND_coordinates,ND_coordinates_list,common_coordinates_set,glint_coordinates_list
|
|
153
150
|
|
|
@@ -157,7 +154,7 @@ def get_water_pix_coord(img,correction, if_SR):
|
|
|
157
154
|
ND_coordinates_list = list(map(tuple, ND_coordinates))
|
|
158
155
|
Acolite_pix=np.column_stack(np.where(np.all((img <= 3000), axis=-1)))#threshold from ACOLITE
|
|
159
156
|
Acolite_pix_list = list(map(tuple, Acolite_pix))
|
|
160
|
-
common_coordinates_set = set(glint_coordinates_list)&set(Acolite_pix_list)&set(ND_coordinates_list)&set(coordinate_list_NDWI)
|
|
157
|
+
common_coordinates_set = set(glint_coordinates_list)&set(Acolite_pix_list)&set(ND_coordinates_list)&set(coordinate_list_NDWI)& set(coordinate_list_cloud)
|
|
161
158
|
common_coordinates_list = list(common_coordinates_set)
|
|
162
159
|
del common_coordinates_set,Acolite_pix,Acolite_pix_list,ND_coordinates,ND_coordinates_list,glint_coordinates_list
|
|
163
160
|
gc.collect()
|
|
@@ -168,7 +165,7 @@ def make_blank_img(img):
|
|
|
168
165
|
Y_b, X_b, b = img.shape #sometimes the image is all no data or the correction value, in this instance, we make a blank image
|
|
169
166
|
RGB_img = np.zeros((Y_b, X_b, 3), dtype=np.uint8)
|
|
170
167
|
# print(' {} Blank strip added. No valid water pixels'.format(time_tracker(start_time)))
|
|
171
|
-
print('
|
|
168
|
+
print(' No valid water pixels')
|
|
172
169
|
return RGB_img
|
|
173
170
|
|
|
174
171
|
def time_tracker(start_time):
|
|
@@ -415,11 +412,11 @@ def plot_RGB_img(RGB_img, image_path):
|
|
|
415
412
|
import matplotlib.pyplot as plt
|
|
416
413
|
fig, ax = plt.subplots(1, 3, figsize=(10, 10), sharex=True, sharey=True)
|
|
417
414
|
ax[0].imshow(RGB_img[:,:,0])#plotting to see what OSW/ODW looks like
|
|
418
|
-
ax[0].set_title('Prediction
|
|
415
|
+
ax[0].set_title('Prediction based on 0.5 threshold')
|
|
419
416
|
ax[1].imshow(RGB_img[:,:,1])
|
|
420
|
-
ax[1].set_title('Prediction
|
|
417
|
+
ax[1].set_title('Prediction probability')
|
|
421
418
|
ax[2].imshow(RGB_img[:,:,2])
|
|
422
|
-
ax[2].set_title('
|
|
419
|
+
ax[2].set_title('Non-water mask')
|
|
423
420
|
# plt.show()
|
|
424
421
|
|
|
425
422
|
out_path = image_path.replace('.tif','.png')
|
|
@@ -11,13 +11,17 @@ from .parse_string import parse_string
|
|
|
11
11
|
|
|
12
12
|
from .write_georef_image import write_georef_image
|
|
13
13
|
from .netcdf_to_multiband_geotiff import netcdf_to_multiband_geotiff
|
|
14
|
+
from .make_vertical_strips import make_vertical_strips
|
|
15
|
+
from .cloud_mask import cloud_mask
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run(file_L1C, folder_out, file_L2R = None, to_log=True):
|
|
17
21
|
|
|
18
22
|
### Check the two
|
|
19
|
-
if not os.path.exists(
|
|
20
|
-
sys.exit('
|
|
23
|
+
if not os.path.exists(file_L1C):
|
|
24
|
+
sys.exit('file_L1C does not exist: ' + str(file_L1C))
|
|
21
25
|
|
|
22
26
|
# folder_out: if not exist -> create it
|
|
23
27
|
if not os.path.exists(folder_out):
|
|
@@ -28,7 +32,7 @@ def run(file_in,folder_out, to_log=True):
|
|
|
28
32
|
# Start logging in txt file
|
|
29
33
|
orig_stdout = sys.stdout
|
|
30
34
|
|
|
31
|
-
log_base = os.path.basename(
|
|
35
|
+
log_base = os.path.basename(file_L1C).replace('.safe','.txt').replace('.SAFE','.txt')
|
|
32
36
|
log_base = 'OSD_log_'+ log_base
|
|
33
37
|
log_file = os.path.join(folder_out,log_base)
|
|
34
38
|
|
|
@@ -47,10 +51,11 @@ def run(file_in,folder_out, to_log=True):
|
|
|
47
51
|
sys.stdout = Logger(log_file)
|
|
48
52
|
|
|
49
53
|
# Metadata
|
|
50
|
-
print('=== ENVIRONMENT ===')
|
|
54
|
+
print('\n=== ENVIRONMENT ===')
|
|
51
55
|
print('OSD version: ' + str(version('opticallyshallowdeep')))
|
|
52
56
|
print('Start time: ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
|
|
53
|
-
print('
|
|
57
|
+
print('file_L1C: ' + str(file_L1C))
|
|
58
|
+
print('file_L2R: ' + str(file_L2R))
|
|
54
59
|
print('folder_out: ' + str(folder_out))
|
|
55
60
|
print('\n=== PRE-PROCESSING ===')
|
|
56
61
|
|
|
@@ -61,23 +66,30 @@ def run(file_in,folder_out, to_log=True):
|
|
|
61
66
|
|
|
62
67
|
### Take input path and identify model path
|
|
63
68
|
|
|
64
|
-
#
|
|
65
|
-
if
|
|
69
|
+
# If ACOLITE L2R is not provided
|
|
70
|
+
if file_L2R is None:
|
|
71
|
+
|
|
66
72
|
if_SR = False
|
|
67
73
|
model = 'models/TOA.h5'
|
|
68
74
|
model_columns = GTOA_model_columns
|
|
75
|
+
file_in = file_L1C
|
|
69
76
|
|
|
70
77
|
# make multiband_image
|
|
71
|
-
image_path = make_multiband_image(
|
|
78
|
+
image_path = make_multiband_image(file_L1C,folder_out)
|
|
79
|
+
|
|
80
|
+
# If ACOLITE L2R is provided
|
|
81
|
+
else:
|
|
82
|
+
|
|
83
|
+
if not os.path.exists(file_L2R):
|
|
84
|
+
sys.exit('file_L2R does not exist: ' + str(file_L2R))
|
|
72
85
|
|
|
73
|
-
# SR
|
|
74
|
-
elif file_in.endswith('.nc') or file_in.endswith('.NC'):
|
|
75
86
|
if_SR = True
|
|
76
87
|
model = 'models/SR.h5'
|
|
77
88
|
model_columns = GACOLITE_model_columns
|
|
89
|
+
file_in = file_L2R
|
|
78
90
|
|
|
79
91
|
# make multiband_image
|
|
80
|
-
image_path = netcdf_to_multiband_geotiff(
|
|
92
|
+
image_path = netcdf_to_multiband_geotiff(file_L2R, folder_out)
|
|
81
93
|
|
|
82
94
|
# make it a list of lists
|
|
83
95
|
selected_columns = [parse_string(s) for s in model_columns]
|
|
@@ -90,15 +102,21 @@ def run(file_in,folder_out, to_log=True):
|
|
|
90
102
|
|
|
91
103
|
# check
|
|
92
104
|
image=check_transpose(image)
|
|
105
|
+
|
|
106
|
+
# make cloud mask
|
|
107
|
+
img_cloud = cloud_mask(file_L1C)
|
|
108
|
+
cloud_list = make_vertical_strips(img_cloud)
|
|
93
109
|
|
|
94
110
|
print('\n=== PREDICTING OSW/ODW ===')
|
|
95
111
|
|
|
96
112
|
# create strips and process them -- make big RGB image
|
|
97
|
-
RGB_img=process_as_strips(image, image_path, if_SR, model_path, selected_columns, model_columns, file_in)
|
|
113
|
+
RGB_img=process_as_strips(image, image_path, if_SR, model_path, selected_columns, model_columns, file_in, cloud_list)
|
|
98
114
|
|
|
99
115
|
# write as geotiff
|
|
100
116
|
write_georef_image(image_path,RGB_img)
|
|
101
|
-
print("Image OSW/ODW completed {}".format(RGB_img.shape))
|
|
117
|
+
print("Image OSW/ODW completed, dimension: {}".format(RGB_img.shape))
|
|
118
|
+
|
|
119
|
+
print('Finish time: ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
|
|
102
120
|
|
|
103
121
|
del RGB_img
|
|
104
122
|
gc.collect()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import rasterio, gc
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
def write_georef_image(image_path,RGB_img):
|
|
6
|
+
|
|
7
|
+
output_name = image_path.replace('.tif','_OSW_ODW.tif')
|
|
8
|
+
raster_with_ref = rasterio.open(image_path) # Open the raster with geospatial information
|
|
9
|
+
crs = raster_with_ref.crs # Get the CRS (Coordinate Reference System) from the raster
|
|
10
|
+
epsg_from_raster = crs.to_epsg() # Use the EPSG code from the CRS
|
|
11
|
+
height, width, _ = RGB_img.shape
|
|
12
|
+
|
|
13
|
+
count = 1
|
|
14
|
+
dtype = RGB_img.dtype
|
|
15
|
+
transform = raster_with_ref.transform# Use the same transform as the reference raster
|
|
16
|
+
|
|
17
|
+
output_band = RGB_img[:,:,1]
|
|
18
|
+
mask_band = RGB_img[:,:,2]
|
|
19
|
+
output_band[mask_band == 0] = 255
|
|
20
|
+
|
|
21
|
+
with rasterio.open(output_name, "w",driver="GTiff", height=height, width=width, nodata=255,
|
|
22
|
+
count=count, dtype=dtype, crs=crs, transform=transform) as dst:
|
|
23
|
+
dst.write(output_band, 1)
|
|
24
|
+
|
|
25
|
+
del raster_with_ref
|
|
26
|
+
gc.collect()
|
|
27
|
+
|
{opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0/opticallyshallowdeep.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opticallyshallowdeep
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Identify optically shallow and deep waters in satellite imagery
|
|
5
5
|
Author: Yulun Wu
|
|
6
6
|
Author-email: yulunwu8@gmail.com
|
|
@@ -16,11 +16,12 @@ Requires-Dist: pyproj
|
|
|
16
16
|
Requires-Dist: joblib
|
|
17
17
|
Requires-Dist: scipy
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
|
+
Requires-Dist: imagecodecs
|
|
19
20
|
Requires-Dist: tensorflow
|
|
20
21
|
|
|
21
22
|
# Optically-Shallow-Deep
|
|
22
23
|
|
|
23
|
-
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network that was trained on a diverse set of global images.
|
|
24
|
+
This python tool delineates optically shallow and deep waters in Sentinel-2 imagery. The tool uses a deep neural network (DNN) that was trained on a diverse set of global images.
|
|
24
25
|
|
|
25
26
|
Supported input includes L1C SAFE files and ACOLITE-processed L2R netCDF files. The output geotiff contains probabilities of water pixels being optically shallow and deep.
|
|
26
27
|
|
|
@@ -77,24 +78,48 @@ pip3 install opticallyshallowdeep
|
|
|
77
78
|
|
|
78
79
|
## Quick Start
|
|
79
80
|
|
|
81
|
+
For L1C files:
|
|
82
|
+
|
|
80
83
|
```python
|
|
81
84
|
import opticallyshallowdeep as osd
|
|
82
85
|
|
|
83
86
|
# Input file
|
|
84
|
-
|
|
87
|
+
file_L1C = 'folder/S2.SAFE'
|
|
88
|
+
|
|
89
|
+
# Output folder
|
|
90
|
+
folder_out = 'folder/test_folder_out'
|
|
91
|
+
|
|
92
|
+
# Run the OSW/ODW classifier
|
|
93
|
+
osd.run(file_L1C, folder_out)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For ACOLITE L2R files:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import opticallyshallowdeep as osd
|
|
100
|
+
|
|
101
|
+
# Input files
|
|
102
|
+
file_L1C = 'test_folder_in/S2.SAFE'
|
|
103
|
+
file_L2R = 'test_folder_in/L2R.nc'
|
|
85
104
|
|
|
86
105
|
# Output folder
|
|
87
106
|
folder_out = 'folder/test_folder_out'
|
|
88
107
|
|
|
89
108
|
# Run the OSW/ODW classifier
|
|
90
|
-
osd.run(file_in, folder_out)
|
|
109
|
+
osd.run(file_in, folder_out, file_L2R=file_L2R)
|
|
91
110
|
```
|
|
92
111
|
|
|
112
|
+
The L1C file is always required as it contains a cloud mask. Pixels within 8 pixels of the cloud mask are masked to reduce the impact of clouds.
|
|
93
113
|
|
|
94
|
-
Output is a 3-band geotiff:
|
|
95
114
|
|
|
96
|
-
-
|
|
97
|
-
- B2: Prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW)
|
|
98
|
-
- B3: pixels that are masked out
|
|
115
|
+
Output is a 1-band geotiff, with values of prediction probability of OSW (100 means most likely OSW, 0 means most likely ODW). Non-water pixels are masked. It is recommended to use pixels between 0 and 40 as ODW, and pixels between 60 and 100 as OSW (publication in review).
|
|
99
116
|
|
|
100
117
|
A log file, an intermediate multi-band geotiff, and a preview PNG are also generated in the output folder. They can be deleted after the processing.
|
|
118
|
+
|
|
119
|
+
## Training, test, and validation data
|
|
120
|
+
|
|
121
|
+
All annotated shapefiles used in training, testing, and validating the DNN model are in the annotated_shapefiles folder, grouped by Sentinel-2 Scene ID.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
{opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep.egg-info/SOURCES.txt
RENAMED
|
@@ -4,8 +4,10 @@ README.md
|
|
|
4
4
|
setup.py
|
|
5
5
|
opticallyshallowdeep/__init__.py
|
|
6
6
|
opticallyshallowdeep/check_transpose.py
|
|
7
|
+
opticallyshallowdeep/cloud_mask.py
|
|
7
8
|
opticallyshallowdeep/find_epsg.py
|
|
8
9
|
opticallyshallowdeep/make_multiband_image.py
|
|
10
|
+
opticallyshallowdeep/make_vertical_strips.py
|
|
9
11
|
opticallyshallowdeep/netcdf_to_multiband_geotiff.py
|
|
10
12
|
opticallyshallowdeep/parse_string.py
|
|
11
13
|
opticallyshallowdeep/process_as_strips.py
|
|
@@ -5,7 +5,7 @@ with open("readme.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='opticallyshallowdeep',
|
|
8
|
-
version='1.
|
|
8
|
+
version='1.2.0',
|
|
9
9
|
author='Yulun Wu',
|
|
10
10
|
author_email='yulunwu8@gmail.com',
|
|
11
11
|
description='Identify optically shallow and deep waters in satellite imagery',
|
|
@@ -19,7 +19,7 @@ setup(
|
|
|
19
19
|
],
|
|
20
20
|
python_requires='>=3.8',
|
|
21
21
|
install_requires=['geopandas','rasterio','tifffile','netCDF4','pyproj',
|
|
22
|
-
'joblib','scipy','matplotlib','tensorflow']
|
|
22
|
+
'joblib','scipy','matplotlib','imagecodecs','tensorflow']
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import rasterio, gc
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
def write_georef_image(image_path,RGB_img):
|
|
6
|
-
|
|
7
|
-
output_name = image_path.replace('.tif','_OSW_ODW.tif')
|
|
8
|
-
raster_with_ref = rasterio.open(image_path) # Open the raster with geospatial information
|
|
9
|
-
crs = raster_with_ref.crs#Get the CRS (Coordinate Reference System) from the raster
|
|
10
|
-
epsg_from_raster = crs.to_epsg()#Use the EPSG code from the CRS
|
|
11
|
-
height, width, _ = RGB_img.shape
|
|
12
|
-
count = 3 #3 bands for all the
|
|
13
|
-
dtype = RGB_img.dtype
|
|
14
|
-
transform = raster_with_ref.transform# Use the same transform as the reference raster
|
|
15
|
-
with rasterio.open(output_name, "w",driver="GTiff",height=height,width=width,
|
|
16
|
-
count=count,dtype=dtype,crs=crs,transform=transform) as dst:
|
|
17
|
-
dst.write(np.moveaxis(RGB_img, -1, 0))
|
|
18
|
-
del raster_with_ref
|
|
19
|
-
gc.collect()
|
|
20
|
-
|
|
21
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/models/TOA.h5
RENAMED
|
File without changes
|
{opticallyshallowdeep-1.1.3 → opticallyshallowdeep-1.2.0}/opticallyshallowdeep/parse_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|