GRSdriver 1.0.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.
- GRSdriver-1.0.0/GRSdriver/__init__.py +9 -0
- GRSdriver-1.0.0/GRSdriver/driver_S2_SAFE.py +518 -0
- GRSdriver-1.0.0/GRSdriver/driver_landsat_col2.py +202 -0
- GRSdriver-1.0.0/GRSdriver/run.py +72 -0
- GRSdriver-1.0.0/GRSdriver/visual.py +195 -0
- GRSdriver-1.0.0/GRSdriver.egg-info/PKG-INFO +276 -0
- GRSdriver-1.0.0/GRSdriver.egg-info/SOURCES.txt +16 -0
- GRSdriver-1.0.0/GRSdriver.egg-info/dependency_links.txt +1 -0
- GRSdriver-1.0.0/GRSdriver.egg-info/requires.txt +8 -0
- GRSdriver-1.0.0/GRSdriver.egg-info/top_level.txt +2 -0
- GRSdriver-1.0.0/LICENSE +201 -0
- GRSdriver-1.0.0/PKG-INFO +276 -0
- GRSdriver-1.0.0/README.md +51 -0
- GRSdriver-1.0.0/pyproject.toml +31 -0
- GRSdriver-1.0.0/rsr/__init__.py +0 -0
- GRSdriver-1.0.0/rsr/landsat_formatting.py +77 -0
- GRSdriver-1.0.0/setup.cfg +4 -0
- GRSdriver-1.0.0/setup.py +42 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numba import jit
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import geopandas as gpd
|
|
10
|
+
import xarray as xr
|
|
11
|
+
import xmltodict
|
|
12
|
+
|
|
13
|
+
from rasterio.features import rasterize
|
|
14
|
+
import scipy.odr as odr
|
|
15
|
+
from affine import Affine
|
|
16
|
+
from osgeo import gdal, ogr
|
|
17
|
+
#import cartopy.crs as ccrs
|
|
18
|
+
from pyproj import CRS
|
|
19
|
+
import eoreader as eo
|
|
20
|
+
|
|
21
|
+
from eoreader.reader import Reader
|
|
22
|
+
from eoreader.env_vars import USE_DASK
|
|
23
|
+
|
|
24
|
+
# EOReader uses dask if == 1
|
|
25
|
+
os.environ[USE_DASK] = "0"
|
|
26
|
+
|
|
27
|
+
opj = os.path.join
|
|
28
|
+
BAND_NAMES = np.array(['B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B09', 'B10', 'B11', 'B12'])
|
|
29
|
+
BAND_NAMES_EOREADER = np.array(['CA', 'BLUE', 'GREEN', 'RED', 'VRE_1',
|
|
30
|
+
'VRE_2', 'VRE_3', 'NIR', 'NARROW_NIR',
|
|
31
|
+
'WV', 'SWIR_CIRRUS', 'SWIR_1', 'SWIR_2'])
|
|
32
|
+
|
|
33
|
+
BAND_ID = [b.replace('B', '') for b in BAND_NAMES]
|
|
34
|
+
NATIVE_RESOLUTION = [60, 10, 10, 10, 20, 20, 20, 10, 20, 60, 60, 20, 20]
|
|
35
|
+
WAVELENGTH = np.array([443, 490, 560, 665, 705, 740, 783, 842, 865, 945, 1375, 1610, 2190])
|
|
36
|
+
BAND_WIDTH = [20, 65, 35, 30, 15, 15, 20, 115, 20, 20, 30, 90, 180]
|
|
37
|
+
|
|
38
|
+
INFO = pd.DataFrame({'bandId': range(13),
|
|
39
|
+
'ESA': BAND_NAMES,
|
|
40
|
+
'EOREADER': BAND_NAMES_EOREADER,
|
|
41
|
+
'Wavelength (nm)': WAVELENGTH,
|
|
42
|
+
'Band width (nm)': BAND_WIDTH,
|
|
43
|
+
'Resolution (m)': NATIVE_RESOLUTION}).set_index('bandId').T
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Sentinel2Driver():
|
|
47
|
+
def __init__(self, imageSAFE,
|
|
48
|
+
band_idx=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
|
49
|
+
band_tbp_idx=[0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12],
|
|
50
|
+
subset=None,
|
|
51
|
+
resolution=20,
|
|
52
|
+
resolution_angle=60,
|
|
53
|
+
verbose=False,
|
|
54
|
+
**kwargs):
|
|
55
|
+
|
|
56
|
+
abspath = os.path.abspath(imageSAFE)
|
|
57
|
+
dirroot, basename = os.path.split(abspath)
|
|
58
|
+
self.verbose = verbose
|
|
59
|
+
self.band_idx = band_idx
|
|
60
|
+
self.band_tbp_idx = band_tbp_idx
|
|
61
|
+
self.subset = subset
|
|
62
|
+
self.resolution_angle = resolution_angle
|
|
63
|
+
self.resolution = resolution
|
|
64
|
+
self.INFO = INFO[band_idx]
|
|
65
|
+
|
|
66
|
+
# -----------------------------------------------------
|
|
67
|
+
# define prod and geom where data will be loaded
|
|
68
|
+
# -----------------------------------------------------
|
|
69
|
+
self.prod = xr.Dataset()
|
|
70
|
+
self.geom = xr.Dataset()
|
|
71
|
+
|
|
72
|
+
# --------------------------------
|
|
73
|
+
# define interpolation parameters
|
|
74
|
+
# --------------------------------
|
|
75
|
+
# tile for 10m resolution: width,height = 10980,10980
|
|
76
|
+
# tile for 20m resolution: width,height = 5490,5490
|
|
77
|
+
# tile for 60m resolution: width,height = 1830,1830
|
|
78
|
+
if resolution_angle == 10:
|
|
79
|
+
self.width, self.height = 10980, 10980
|
|
80
|
+
elif resolution_angle == 20:
|
|
81
|
+
self.width, self.height = 5490, 5490
|
|
82
|
+
elif resolution_angle == 60:
|
|
83
|
+
self.width, self.height = 1830, 1830
|
|
84
|
+
try:
|
|
85
|
+
self.xml_granule = glob.glob(opj(imageSAFE, 'GRANULE', '*', 'MTD_TL.xml'))[0]
|
|
86
|
+
self.xml_file = glob.glob(opj(imageSAFE, 'MTD*.xml'))[0]
|
|
87
|
+
except:
|
|
88
|
+
print('old or deprecated SAFE format detected')
|
|
89
|
+
self.xml_granule = glob.glob(opj(imageSAFE, 'GRANULE', '*', '*MTD*.xml'))[0]
|
|
90
|
+
self.xml_file = glob.glob(opj(imageSAFE, '*MTD*.xml'))[0]
|
|
91
|
+
|
|
92
|
+
# get metadata and angle file
|
|
93
|
+
ds = gdal.Open(self.xml_file)
|
|
94
|
+
self.metadata = ds.GetMetadata()
|
|
95
|
+
self.metadata2 = xmltodict.parse(ds.GetMetadata('xml:SENTINEL2')[0])
|
|
96
|
+
__ = []
|
|
97
|
+
for _ in self.metadata2['n1:Level-1C_User_Product']['n1:General_Info']['Product_Image_Characteristics'][
|
|
98
|
+
'Reflectance_Conversion']['Solar_Irradiance_List']['SOLAR_IRRADIANCE']:
|
|
99
|
+
__.append([int(_['@bandId']), float(_['#text'])])
|
|
100
|
+
self.solar_irradiance = np.array(__)
|
|
101
|
+
|
|
102
|
+
# Spectral Response Functions
|
|
103
|
+
SRFs = []
|
|
104
|
+
wl_hr = np.arange(400, 2350)
|
|
105
|
+
for _ in self.metadata2['n1:Level-1C_User_Product'][
|
|
106
|
+
'n1:General_Info']['Product_Image_Characteristics']['Spectral_Information_List']['Spectral_Information']:
|
|
107
|
+
|
|
108
|
+
bandID = int(_['@bandId'])
|
|
109
|
+
if not self.band_idx.__contains__(bandID):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
wl_min, wl_max = float(_['Wavelength']['MIN']['#text']), float(_['Wavelength']['MAX']['#text'])
|
|
113
|
+
step = float(_['Spectral_Response']['STEP']['#text'])
|
|
114
|
+
wl = np.arange(wl_min, wl_max + step, step)
|
|
115
|
+
SRF = np.asarray(_['Spectral_Response']['VALUES'].split(), dtype=np.float32)
|
|
116
|
+
SRFs.append(
|
|
117
|
+
xr.DataArray(SRF, coords=dict(wl_hr=wl[:len(SRF)]), name='SRF').interp(wl_hr=wl_hr).assign_coords(
|
|
118
|
+
dict(wl=WAVELENGTH[bandID])))
|
|
119
|
+
self.SRFs = xr.concat(SRFs, dim='wl')
|
|
120
|
+
self.SRFs.attrs['description'] = 'Spectral response function of each band'
|
|
121
|
+
|
|
122
|
+
# Open instance of eoreader
|
|
123
|
+
reader = Reader()
|
|
124
|
+
|
|
125
|
+
# Open the product
|
|
126
|
+
reader = reader.open(imageSAFE, remove_tmp=True, **kwargs)
|
|
127
|
+
self.reader = reader
|
|
128
|
+
self.processing_baseline = reader._processing_baseline
|
|
129
|
+
self.datetime = reader.datetime
|
|
130
|
+
|
|
131
|
+
# save geographic data
|
|
132
|
+
self.extent = reader.extent()
|
|
133
|
+
self.bounds = self.extent.bounds
|
|
134
|
+
minx, miny, maxx, maxy = self.bounds.values[0]
|
|
135
|
+
self.crs = self.reader.crs()
|
|
136
|
+
self.epsg = self.extent.crs.to_epsg()
|
|
137
|
+
# str_epsg = str(self.epsg)
|
|
138
|
+
# zone = str_epsg[-2:]
|
|
139
|
+
# is_south = str_epsg[2] == 7
|
|
140
|
+
# self.proj = ccrs.UTM(zone, is_south)
|
|
141
|
+
# self.proj = CRS.from_dict({'proj': 'utm', 'zone': zone, 'south': is_south})
|
|
142
|
+
self.transform = Affine(resolution, 0., minx, 0., -resolution, maxy)
|
|
143
|
+
|
|
144
|
+
# -------------------------
|
|
145
|
+
# interpolation
|
|
146
|
+
# -------------------------
|
|
147
|
+
# set the indexing of numpy and xarray
|
|
148
|
+
self.indexing = 'xy'
|
|
149
|
+
self.new_x = np.linspace(minx, maxx, self.width)
|
|
150
|
+
self.new_y = np.linspace(miny, maxy, self.height)[::-1]
|
|
151
|
+
|
|
152
|
+
# ---------------------------------
|
|
153
|
+
# getting appropriate version of mask driver
|
|
154
|
+
# ---------------------------------
|
|
155
|
+
# TODO generalize to load all available masks
|
|
156
|
+
if self.processing_baseline < 4:
|
|
157
|
+
self._open_mask = reader._open_mask_lt_4_0
|
|
158
|
+
else:
|
|
159
|
+
self._open_mask = reader._open_mask_gt_4_0
|
|
160
|
+
|
|
161
|
+
def load_product(self,
|
|
162
|
+
add_time=True,
|
|
163
|
+
**kwargs):
|
|
164
|
+
|
|
165
|
+
self.load_bands(subset=self.subset, add_time=add_time, **kwargs)
|
|
166
|
+
|
|
167
|
+
self.load_geom()
|
|
168
|
+
#if self.resolution_angle != self.resolution:
|
|
169
|
+
self.geom=self.geom.interp(x=self.prod.x, y=self.prod.y, method='nearest')
|
|
170
|
+
self.prod = xr.merge([self.prod, self.geom])
|
|
171
|
+
del self.geom
|
|
172
|
+
|
|
173
|
+
# add native metadata
|
|
174
|
+
for item in self.metadata:
|
|
175
|
+
self.prod.attrs[item] = self.metadata[item]
|
|
176
|
+
|
|
177
|
+
self.prod.attrs['satellite'] = self.prod.attrs['PRODUCT_URI'].split('_')[0]
|
|
178
|
+
self.prod.attrs['tile'] = self.prod.attrs['PRODUCT_URI'].split('_')[5][1:]
|
|
179
|
+
self.prod.attrs['solar_irradiance'] = self.solar_irradiance[:, 1]
|
|
180
|
+
self.prod.attrs['solar_irradiance_unit'] = 'W/m²/µm'
|
|
181
|
+
self.prod.attrs['mean_solar_azimuth'] = self.mean_sun_azi
|
|
182
|
+
self.prod.attrs['mean_solar_zenith_angle'] = self.mean_sza
|
|
183
|
+
self.prod.attrs['acquisition_date'] = self.prod.attrs['DATATAKE_1_DATATAKE_SENSING_START']
|
|
184
|
+
|
|
185
|
+
def load_bands(self, subset=None,
|
|
186
|
+
add_time=True,
|
|
187
|
+
**kwargs):
|
|
188
|
+
|
|
189
|
+
# ----------------------------------
|
|
190
|
+
# getting bands
|
|
191
|
+
# ----------------------------------
|
|
192
|
+
window=None
|
|
193
|
+
if subset is not None:
|
|
194
|
+
|
|
195
|
+
if subset.dtype == 'geometry':
|
|
196
|
+
window = subset.to_crs(self.crs).bounds.values[0]
|
|
197
|
+
else:
|
|
198
|
+
window = subset[0]
|
|
199
|
+
bands = self.reader.stack(list(BAND_NAMES_EOREADER[self.band_idx]),
|
|
200
|
+
resolution=self.resolution,
|
|
201
|
+
window=window,
|
|
202
|
+
**kwargs)
|
|
203
|
+
|
|
204
|
+
# fix for naming in differnt EOreader versions
|
|
205
|
+
if 'z' in bands.coords:
|
|
206
|
+
bands = bands.rename({'z': 'bands'})
|
|
207
|
+
|
|
208
|
+
# ----------------------------------
|
|
209
|
+
# setting up coordinates and dimensions
|
|
210
|
+
# ----------------------------------
|
|
211
|
+
self.prod = bands.assign_coords(wl=('bands', self.INFO.loc['Wavelength (nm)'])). \
|
|
212
|
+
swap_dims({'bands': 'wl'}).drop({'band', 'bands', 'variable'})
|
|
213
|
+
self.prod = self.prod.assign_coords(bandID=('wl', self.INFO.loc['ESA'].values))
|
|
214
|
+
self.prod = self.prod.to_dataset(name='bands', promote_attrs=True)
|
|
215
|
+
self.prod.attrs['wl_to_process'] = WAVELENGTH[self.band_tbp_idx]
|
|
216
|
+
|
|
217
|
+
# add spectral response function
|
|
218
|
+
self.prod = xr.merge([self.prod, self.SRFs.sel(wl=self.prod.wl.values)]).drop_vars('bandID')
|
|
219
|
+
|
|
220
|
+
# compute central wavelengths
|
|
221
|
+
wl_true = []
|
|
222
|
+
for wl_, srf in self.prod.SRF.groupby('wl', squeeze=False):
|
|
223
|
+
srf = srf.dropna('wl_hr')
|
|
224
|
+
wl_true.append((srf.wl_hr * srf).integrate('wl_hr') / srf.integrate('wl_hr'))
|
|
225
|
+
wl_true = xr.concat(wl_true, dim='wl')
|
|
226
|
+
wl_true.name = 'wl_true'
|
|
227
|
+
self.prod = xr.merge([self.prod, wl_true])
|
|
228
|
+
|
|
229
|
+
# add time
|
|
230
|
+
if add_time:
|
|
231
|
+
self.prod = self.prod.assign_coords(time=self.datetime) #.expand_dims('time')
|
|
232
|
+
# self.prod.clear()
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def parse_angular_grid_node(node):
|
|
236
|
+
'''
|
|
237
|
+
Internal parsing function for angular grids
|
|
238
|
+
:return:
|
|
239
|
+
'''
|
|
240
|
+
values = []
|
|
241
|
+
for c in node.find('Values_List'):
|
|
242
|
+
values.append(np.array([float(t) for t in c.text.split()]))
|
|
243
|
+
values_array = np.stack(values)
|
|
244
|
+
return values_array
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def set_crs(arr, crs):
|
|
248
|
+
arr.rio.set_crs(crs, inplace=True)
|
|
249
|
+
arr.rio.write_crs(inplace=True)
|
|
250
|
+
|
|
251
|
+
def get_raw_angles(self):
|
|
252
|
+
|
|
253
|
+
minx, miny, maxx, maxy = self.bounds.values[0]
|
|
254
|
+
with open(self.xml_granule) as xml_file:
|
|
255
|
+
tree = ET.parse(xml_file)
|
|
256
|
+
root = tree.getroot()
|
|
257
|
+
|
|
258
|
+
raw_sza = self.parse_angular_grid_node(root.find('.//Tile_Angles/Sun_Angles_Grid/Zenith'))
|
|
259
|
+
raw_sazi = self.parse_angular_grid_node(root.find('.//Tile_Angles/Sun_Angles_Grid/Azimuth'))
|
|
260
|
+
self.mean_sza= np.nanmean(raw_sza)
|
|
261
|
+
self.mean_sun_azi = np.nanmean(raw_sazi)
|
|
262
|
+
|
|
263
|
+
# compute x and y for angle grids
|
|
264
|
+
# check dimension
|
|
265
|
+
Nx, Ny = raw_sza.shape
|
|
266
|
+
|
|
267
|
+
xang = np.linspace(minx, maxx, Nx)
|
|
268
|
+
yang = np.linspace(miny, maxy, Ny)[::-1]
|
|
269
|
+
|
|
270
|
+
raw_sun_ang = xr.Dataset(data_vars=dict(sza=(['y', 'x'], raw_sza),
|
|
271
|
+
sazi=(['y', 'x'], raw_sazi)),
|
|
272
|
+
coords=dict(x=xang, y=yang))
|
|
273
|
+
self.set_crs(raw_sun_ang, self.crs)
|
|
274
|
+
self.raw_sun_ang = raw_sun_ang
|
|
275
|
+
|
|
276
|
+
# ---------------------------------
|
|
277
|
+
# getting viewing geometry datacube
|
|
278
|
+
# ---------------------------------
|
|
279
|
+
bandIds, detectorIds = [], []
|
|
280
|
+
for angleID in root.findall('.//Tile_Angles/Viewing_Incidence_Angles_Grids'):
|
|
281
|
+
bandIds.append(int(angleID.attrib['bandId']))
|
|
282
|
+
detectorIds.append(int(angleID.attrib['detectorId']))
|
|
283
|
+
Nband, Ndetector = np.max(bandIds) + 1, np.max(detectorIds) + 1
|
|
284
|
+
|
|
285
|
+
# allocate/fill rasters
|
|
286
|
+
vza = np.full((Nband, Ndetector, Nx, Ny), np.nan, dtype=float)
|
|
287
|
+
vazi = np.full((Nband, Ndetector, Nx, Ny), np.nan, dtype=float)
|
|
288
|
+
|
|
289
|
+
for angleID in root.findall('.//Tile_Angles/Viewing_Incidence_Angles_Grids'):
|
|
290
|
+
iband = int(angleID.attrib['bandId'])
|
|
291
|
+
idetector = int(angleID.attrib['detectorId'])
|
|
292
|
+
vza[iband, idetector] = self.parse_angular_grid_node(angleID.find('Zenith'))
|
|
293
|
+
vazi[iband, idetector] = self.parse_angular_grid_node(angleID.find('Azimuth'))
|
|
294
|
+
|
|
295
|
+
raw_view_ang = xr.Dataset(data_vars=dict(vza=(['bandId', 'detectorId', 'y', 'x'], vza),
|
|
296
|
+
vazi=(['bandId', 'detectorId', 'y', 'x'], vazi)),
|
|
297
|
+
coords=dict(bandId=range(Nband),
|
|
298
|
+
detectorId=range(Ndetector),
|
|
299
|
+
x=xang, y=yang))
|
|
300
|
+
self.set_crs(raw_view_ang, self.crs)
|
|
301
|
+
|
|
302
|
+
# clean up Dataset (remove empty slices)
|
|
303
|
+
raw_view_ang = raw_view_ang.dropna('detectorId', how='all')
|
|
304
|
+
self.raw_view_ang = raw_view_ang
|
|
305
|
+
|
|
306
|
+
# set number of different detectors
|
|
307
|
+
self.detector_num = len(raw_view_ang.detectorId)
|
|
308
|
+
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
def load_geom(self,
|
|
312
|
+
method='linear'):
|
|
313
|
+
|
|
314
|
+
self.get_raw_angles()
|
|
315
|
+
self.get_all_band_angles( method=method)
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def linfit(beta, x):
|
|
319
|
+
return beta[0] * x[0] + beta[1] * x[1] + beta[2]
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
@jit(nopython=True) # "uint16[:,:](float64[:],float64[:],float64[:,:],float64[:,:],intp,intp)",
|
|
323
|
+
def lin2D(arr, x, y, mask, betas, detector_offset=0, scale_factor=100):
|
|
324
|
+
|
|
325
|
+
Nx, Ny = mask.shape
|
|
326
|
+
|
|
327
|
+
for ii in range(Nx):
|
|
328
|
+
for jj in range(Ny):
|
|
329
|
+
detect = mask[ii, jj]
|
|
330
|
+
if detect >= detector_offset:
|
|
331
|
+
#continue
|
|
332
|
+
beta = betas[detect - detector_offset]
|
|
333
|
+
val = beta[0] * x[jj] + beta[1] * y[ii] + beta[2]
|
|
334
|
+
# compression using simple int8 and scale factor
|
|
335
|
+
arr[ii, jj] = (val * scale_factor)
|
|
336
|
+
|
|
337
|
+
def data_fitting(self, x0, y0, arr):
|
|
338
|
+
# ---------------------------------
|
|
339
|
+
# ODR multilinear regression
|
|
340
|
+
# ---------------------------------
|
|
341
|
+
xgrid, ygrid = np.meshgrid(x0, y0, indexing=self.indexing)
|
|
342
|
+
|
|
343
|
+
# vectorize
|
|
344
|
+
values = arr.values.flatten()
|
|
345
|
+
x_ = xgrid.flatten()
|
|
346
|
+
y_ = ygrid.flatten()
|
|
347
|
+
|
|
348
|
+
# remove NaN
|
|
349
|
+
idx = ~np.isnan(values)
|
|
350
|
+
values = values[idx]
|
|
351
|
+
points = np.empty((2, len(values)))
|
|
352
|
+
points[0] = x_[idx]
|
|
353
|
+
points[1] = y_[idx]
|
|
354
|
+
|
|
355
|
+
# set ODR fitting
|
|
356
|
+
mean = np.nanmean(values)
|
|
357
|
+
linear = odr.Model(self.linfit)
|
|
358
|
+
data = odr.Data(points, values)
|
|
359
|
+
beta0 = [0, 0, mean]
|
|
360
|
+
|
|
361
|
+
# proceed with ODR fitting
|
|
362
|
+
fit = odr.ODR(data, linear, beta0=beta0)
|
|
363
|
+
resfit = fit.run()
|
|
364
|
+
|
|
365
|
+
if self.verbose:
|
|
366
|
+
resfit.pprint()
|
|
367
|
+
|
|
368
|
+
return resfit.beta
|
|
369
|
+
|
|
370
|
+
def get_detector_mask(self, bandId=0,
|
|
371
|
+
resolution=20,
|
|
372
|
+
detector_mask_name='DETFOO'):
|
|
373
|
+
|
|
374
|
+
if self.processing_baseline < 4:
|
|
375
|
+
mask_df = self._open_mask(detector_mask_name, BAND_ID[bandId])
|
|
376
|
+
detector_num = mask_df.gml_id.str.split('-', expand=True).values[:, 2]
|
|
377
|
+
poly_shp = [[geom, int(value)] for geom, value in zip(mask_df.geometry, detector_num)]
|
|
378
|
+
|
|
379
|
+
mask = rasterize(shapes=poly_shp,
|
|
380
|
+
out_shape=(self.height, self.width),
|
|
381
|
+
transform=self.transform)
|
|
382
|
+
else:
|
|
383
|
+
# TODO deprecate 'resolution' argument, 'pixel_size' is used instead since EOReader 0.20
|
|
384
|
+
mask = self._open_mask(detector_mask_name, BAND_ID[bandId], resolution=resolution,
|
|
385
|
+
pixel_size=resolution).astype(np.int8)
|
|
386
|
+
|
|
387
|
+
mask = mask.squeeze()
|
|
388
|
+
return np.array(mask)
|
|
389
|
+
|
|
390
|
+
def get_band_angle_as_numpy(self, xarr,
|
|
391
|
+
bandId=0,
|
|
392
|
+
resolution=20,
|
|
393
|
+
detector_mask_name='DETFOO',
|
|
394
|
+
compress=False,
|
|
395
|
+
):
|
|
396
|
+
'''
|
|
397
|
+
|
|
398
|
+
:param xarr:
|
|
399
|
+
:param bandId:
|
|
400
|
+
:param resolution:
|
|
401
|
+
:param detector_mask_name:
|
|
402
|
+
|
|
403
|
+
:return:
|
|
404
|
+
'''
|
|
405
|
+
|
|
406
|
+
detector_offset = int(xarr.detectorId.min())
|
|
407
|
+
mask = self.get_detector_mask(bandId=bandId, resolution=resolution)
|
|
408
|
+
|
|
409
|
+
# TODO check how to avoid taking the nodata value "0" when coarsening the raster
|
|
410
|
+
# TODO for the moment this induces bad detector number at the edge of the image swath
|
|
411
|
+
# mask = _open_mask(detector_mask_name, BAND_ID[bandId], resolution=NATIVE_RESOLUTION[bandId])
|
|
412
|
+
# # mask nodata value
|
|
413
|
+
# mask = mask.where(mask!=0)
|
|
414
|
+
# # resample mask at the desired resolution
|
|
415
|
+
# if resolution != NATIVE_RESOLUTION[iband]:
|
|
416
|
+
# mask = mask.interp(x=new_x, y=new_y, method='nearest')
|
|
417
|
+
# # compress mask into int8
|
|
418
|
+
# mask = mask.astype(np.int8)
|
|
419
|
+
|
|
420
|
+
x, y = self.new_x, self.new_y
|
|
421
|
+
# self.prod.clear()
|
|
422
|
+
betas = np.full((self.detector_num, 3), np.nan)
|
|
423
|
+
xarr_ = xarr.sel(bandId=bandId)
|
|
424
|
+
for id in range(self.detector_num):
|
|
425
|
+
# --------------------------------------------------------------
|
|
426
|
+
# Linear 2D-fitting to get the function of the regression plane
|
|
427
|
+
# --------------------------------------------------------------
|
|
428
|
+
arr = xarr_.isel(detectorId=id).dropna('y', how='all').dropna('x', how='all')
|
|
429
|
+
x0, y0 = arr.x.values, arr.y.values
|
|
430
|
+
betas[id, :] = self.data_fitting(x0, y0, arr)
|
|
431
|
+
|
|
432
|
+
if compress:
|
|
433
|
+
# TODO experimental, needs to be tested
|
|
434
|
+
# compression in uint16 (NB: range 0-65535)
|
|
435
|
+
new_arr = np.full((self.width, self.height), np.nan, dtype=np.int16)
|
|
436
|
+
# compute angles from betas values for each detector and band
|
|
437
|
+
self.lin2D(new_arr, x, y, mask, betas, detector_offset=detector_offset, scale_factor=100)
|
|
438
|
+
else:
|
|
439
|
+
# compression in uint16 (NB: range 0-65535)
|
|
440
|
+
new_arr = np.full((self.width, self.height), np.nan, dtype=np.float32)
|
|
441
|
+
# compute angles from betas values for each detector and band
|
|
442
|
+
self.lin2D(new_arr, x, y, mask, betas, detector_offset=detector_offset, scale_factor=1)
|
|
443
|
+
|
|
444
|
+
del mask
|
|
445
|
+
return new_arr
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def scat_angle(sza, vza, azi):
|
|
449
|
+
'''
|
|
450
|
+
self.azi: azimuth in rad for convention azi=180 when sun-sensor in opposition
|
|
451
|
+
:return: scattering angle in deg
|
|
452
|
+
'''
|
|
453
|
+
|
|
454
|
+
sza = np.radians(sza)
|
|
455
|
+
vza = np.radians(vza)
|
|
456
|
+
azi = np.radians(azi)
|
|
457
|
+
ang = -np.cos(sza) * np.cos(vza) - np.sin(sza) * np.sin(vza) * np.cos(azi)
|
|
458
|
+
ang = np.arccos(ang)
|
|
459
|
+
return np.degrees(ang)
|
|
460
|
+
|
|
461
|
+
def get_all_band_angles(self, method='linear'):
|
|
462
|
+
|
|
463
|
+
new_x, new_y = self.new_x, self.new_y
|
|
464
|
+
band_idx = self.band_idx
|
|
465
|
+
# -----------------------------------------------------------------
|
|
466
|
+
# Sun angles (easy!) based on standard bidimensional interpolation
|
|
467
|
+
# -----------------------------------------------------------------
|
|
468
|
+
new_sun_ang = self.raw_sun_ang.interp(x=new_x, y=new_y, method=method)
|
|
469
|
+
|
|
470
|
+
# ------------------------------------------------------
|
|
471
|
+
# Viewing angles (not easy!) based on 2D-plane fitting
|
|
472
|
+
# ------------------------------------------------------
|
|
473
|
+
raw_vza = self.raw_view_ang.vza
|
|
474
|
+
raw_vazi = self.raw_view_ang.vazi
|
|
475
|
+
|
|
476
|
+
# ---------------------------------------------------------
|
|
477
|
+
# convert vza, azi angles into cartesian vector components
|
|
478
|
+
# (NOT NEEDED FOR THE MOMENT!!)
|
|
479
|
+
# ---------------------------------------------------------
|
|
480
|
+
# np.tan(np.deg2rad(view_ang.vza)) * np.sin(np.deg2rad(view_ang.vazi))
|
|
481
|
+
# np.tan(np.deg2rad(view_ang.vza)) * np.cos(np.deg2rad(view_ang.vazi))
|
|
482
|
+
# dx.name = 'dx'
|
|
483
|
+
# dy.name = 'dy'
|
|
484
|
+
|
|
485
|
+
new_vza, new_vazi = [], []
|
|
486
|
+
for ibandId, bandId in enumerate(band_idx):
|
|
487
|
+
if self.verbose:
|
|
488
|
+
print('Band number ' + str(bandId) + ' is being loaded')
|
|
489
|
+
new_vza.append(self.get_band_angle_as_numpy(raw_vza, bandId=bandId, resolution=self.resolution_angle))
|
|
490
|
+
new_vazi.append(self.get_band_angle_as_numpy(raw_vazi, bandId=bandId, resolution=self.resolution_angle))
|
|
491
|
+
raa = (np.array(new_vazi) - new_sun_ang.sazi.values) % 360
|
|
492
|
+
|
|
493
|
+
self.geom['vza'] = xr.DataArray(np.array(new_vza), dims=['wl', 'y', 'x'])
|
|
494
|
+
self.geom['raa'] = xr.DataArray(raa, dims=['wl', 'y', 'x'])
|
|
495
|
+
self.geom['sza'] = xr.DataArray(new_sun_ang.sza.values, dims=['y', 'x'])
|
|
496
|
+
#
|
|
497
|
+
# xr.Dataset(data_vars=dict(vza=(['wl', 'y', 'x'], )),
|
|
498
|
+
# coords=dict(wl=self.INFO.loc['Wavelength (nm)'],
|
|
499
|
+
#
|
|
500
|
+
# vazi = xr.Dataset(data_vars=dict(vazi=(['wl', 'y', 'x'], np.array(new_vazi))),
|
|
501
|
+
# coords=dict(wl=self.INFO.loc['Wavelength (nm)'], x=new_x, y=new_y))
|
|
502
|
+
# new_ang = xr.Dataset(data_vars=dict(vza=(['wl', 'y', 'x'], np.array(new_vza))),
|
|
503
|
+
# coords=dict(wl=self.INFO.loc['Wavelength (nm)'], x=new_x, y=new_y))
|
|
504
|
+
#
|
|
505
|
+
# new_ang['sza'] = new_sun_ang.sza
|
|
506
|
+
# new_ang['razi'] = vazi.vazi - new_sun_ang.sazi) % 360
|
|
507
|
+
# # new_ang['scat_ang'] = scat_angle(new_ang.sza, new_ang.vza, new_ang.razi)
|
|
508
|
+
self.geom['x']=self.new_x
|
|
509
|
+
self.geom['y']=self.new_y
|
|
510
|
+
self.set_crs(self.geom, self.crs)
|
|
511
|
+
|
|
512
|
+
del new_vza, new_vazi, raa, new_sun_ang
|
|
513
|
+
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
# ------------------------------------------------------------------------------------------
|
|
517
|
+
# ------------------------------------------------------------------------------------------
|
|
518
|
+
# ------------------------------------------------------------------------------------------
|