Initial commit
This commit is contained in:
27
Flexbrdf/hytools/topo/__init__.py
Normal file
27
Flexbrdf/hytools/topo/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
The :mod:`hytools.correction` module include functions image correction.
|
||||
"""
|
||||
from .topo import *
|
||||
from .scsc import *
|
||||
from .c import *
|
||||
from .cosine import *
|
||||
from .scs import *
|
||||
from .modminn import *
|
||||
222
Flexbrdf/hytools/topo/c.py
Normal file
222
Flexbrdf/hytools/topo/c.py
Normal file
@ -0,0 +1,222 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This module contains functions to apply a topographic correction (SCS+C)
|
||||
described in the following papers:
|
||||
|
||||
Scott A. Soenen, Derek R. Peddle, & Craig A. Coburn (2005).
|
||||
SCS+C: A Modified Sun-Canopy-Sensor Topographic Correction in Forested Terrain.
|
||||
IEEE Transactions on Geoscience and Remote Sensing, 43(9), 2148-2159.
|
||||
https://doi.org/10.1109/TGRS.2005.852480
|
||||
|
||||
Topographic correction consists of the following steps:
|
||||
|
||||
1. calculate incidence angle if it is not provided
|
||||
2. estimate C-Correction value
|
||||
3. apply C-Correction value to the image data
|
||||
|
||||
TODO: Rationale/ examples for using different fitting algorithms
|
||||
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from scipy.optimize import nnls
|
||||
from ..io.envi import WriteENVI
|
||||
|
||||
def calc_c(data,cosine_i,fit_type = 'ols'):
|
||||
"""Calculate the topographic correction coefficient (c) for the input data.
|
||||
Used for both the cosine and SCS+S topographic corrections.
|
||||
|
||||
Args:
|
||||
band (numpy.ndarray): Image array.
|
||||
cosine_i (numpy.ndarray): Cosine i array.
|
||||
fit_type (str): Linear model fitting type.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: Topographic correction coefficient.
|
||||
|
||||
"""
|
||||
|
||||
# Reshape for regression
|
||||
cosine_i = np.expand_dims(cosine_i,axis=1)
|
||||
|
||||
if cosine_i.shape[0]==0:
|
||||
return 100000.0
|
||||
|
||||
X = np.concatenate([cosine_i,np.ones(cosine_i.shape)],axis=1)
|
||||
|
||||
# Eq 7. Soenen et al. 2005
|
||||
if fit_type == 'ols':
|
||||
slope, intercept = np.linalg.lstsq(X, data,rcond=-1)[0].flatten()
|
||||
elif fit_type == 'nnls':
|
||||
slope, intercept = nnls(X, data)[0].flatten()
|
||||
|
||||
# Eq 8. Soenen et al. 2005
|
||||
c= intercept/slope
|
||||
|
||||
# Set a large number if slope is zero
|
||||
if not np.isfinite(c):
|
||||
c = 100000.0
|
||||
return c
|
||||
|
||||
def calc_c_coeffs(hy_obj,topo_dict):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
|
||||
'''
|
||||
|
||||
topo_dict['coeffs'] = {}
|
||||
cosine_i = hy_obj.cosine_i()
|
||||
|
||||
for band_num,band in enumerate(hy_obj.bad_bands):
|
||||
if ~band:
|
||||
band = hy_obj.get_band(band_num,mask='calc_topo')
|
||||
topo_dict['coeffs'][band_num] = calc_c(band,cosine_i[hy_obj.mask['calc_topo']],
|
||||
fit_type=topo_dict['c_fit_type'])
|
||||
hy_obj.topo = topo_dict
|
||||
|
||||
def get_band_samples(hy_obj,args):
|
||||
band = hy_obj.get_band(args['band_num'],
|
||||
corrections = hy_obj.corrections)
|
||||
return band[hy_obj.ancillary['sample_mask'] !=0]
|
||||
|
||||
def get_cosine_i_samples(hy_obj):
|
||||
'''Calculate and sample cosine_i
|
||||
'''
|
||||
cosine_i=hy_obj.cosine_i()
|
||||
cosine_i = cosine_i[hy_obj.ancillary['sample_mask'] !=0]
|
||||
|
||||
return cosine_i
|
||||
|
||||
def calc_c_coeffs_group(actors,topo_dict,group_tag):
|
||||
|
||||
cosine_i_samples = ray.get([a.do.remote(get_cosine_i_samples) for a in actors])
|
||||
cosine_i_samples = np.concatenate(cosine_i_samples)
|
||||
|
||||
print(f'Topo Subgroup {group_tag}')
|
||||
|
||||
bad_bands = ray.get(actors[0].do.remote(lambda x: x.bad_bands))
|
||||
coeffs = {}
|
||||
|
||||
for band_num,band in enumerate(bad_bands):
|
||||
if ~band:
|
||||
coeffs[band_num] = {}
|
||||
band_samples = ray.get([a.do.remote(get_band_samples,
|
||||
{'band_num':band_num}) for a in actors])
|
||||
band_samples = np.concatenate(band_samples)
|
||||
|
||||
coeffs[band_num] = calc_c(band_samples,cosine_i_samples,fit_type=topo_dict['c_fit_type'])
|
||||
progbar(np.sum(~bad_bands[:band_num+1]),np.sum(~bad_bands))
|
||||
|
||||
print('\n')
|
||||
|
||||
#Update TOPO coeffs
|
||||
_ = ray.get([a.do.remote(update_topo,{'key':'coeffs',
|
||||
'value': coeffs}) for a in actors])
|
||||
_ = ray.get([a.do.remote(update_topo,{'key':'subgroup',
|
||||
'value': group_tag}) for a in actors])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def apply_c(hy_obj,data,dimension,index):
|
||||
''' Apply SCSS correction to a slice of the data
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if 'cos_sz' not in hy_obj.ancillary.keys():
|
||||
cos_sz = np.cos(hy_obj.get_anc('solar_zn'))
|
||||
hy_obj.ancillary['cos_sz'] = cos_sz
|
||||
if 'cosine_i' not in hy_obj.ancillary.keys():
|
||||
cosine_i = hy_obj.cosine_i()
|
||||
hy_obj.ancillary['cosine_i'] = cosine_i
|
||||
|
||||
C_bands = list(hy_obj.topo['coeffs'].keys())
|
||||
C = np.array(list(hy_obj.topo['coeffs'].values()))
|
||||
|
||||
#Convert to float
|
||||
data = data.astype(np.float32)
|
||||
|
||||
if dimension == 'line':
|
||||
#index= 3000
|
||||
#data = hy_obj.get_line(3000)
|
||||
data = data[:,C_bands]
|
||||
mask = hy_obj.mask['apply_topo'][index,:]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][[index],:].T
|
||||
cos_sz = hy_obj.ancillary['cos_sz'][[index],:].T
|
||||
correction_factor = (cos_sz + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
elif dimension == 'column':
|
||||
# index= 300
|
||||
# data = hy_obj.get_column(index)
|
||||
data = data[:,C_bands]
|
||||
mask = hy_obj.mask['apply_topo'][:,index]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][:,[index]]
|
||||
cos_sz = hy_obj.ancillary['cos_sz'][:,[index]]
|
||||
correction_factor = (cos_sz + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
elif dimension == 'band':
|
||||
#index= 8
|
||||
#data = hy_obj.get_band(index)
|
||||
C = hy_obj.topo['coeffs'][index]
|
||||
correction_factor = (hy_obj.ancillary['cos_sz'] + C)/(hy_obj.ancillary['cosine_i'] + C)
|
||||
data[hy_obj.mask['apply_topo']] = data[hy_obj.mask['apply_topo']] * correction_factor[hy_obj.mask['apply_topo']]
|
||||
|
||||
elif dimension == 'chunk':
|
||||
# index = 200,501,3000,3501
|
||||
x1,x2,y1,y2 = index
|
||||
# data = hy_obj.get_chunk(x1,x2,y1,y2)
|
||||
data = data[:,:,C_bands]
|
||||
mask = hy_obj.mask['apply_topo'][y1:y2,x1:x2]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
cos_sz = hy_obj.ancillary['cos_sz'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
correction_factor = (cos_sz + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
elif dimension == 'pixels':
|
||||
# index = [[2000,2001],[200,501]]
|
||||
y,x = index
|
||||
# data = hy_obj.get_pixels(y,x)
|
||||
data = data[:,C_bands]
|
||||
mask = hy_obj.mask['apply_topo'][y,x]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][[y],[x]].T
|
||||
cos_sz = hy_obj.ancillary['cos_sz'][[y],[x]].T
|
||||
correction_factor = (cos_sz + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
99
Flexbrdf/hytools/topo/cosine.py
Normal file
99
Flexbrdf/hytools/topo/cosine.py
Normal file
@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This module contains functions to apply the Modified topographic correction (SCS+C)
|
||||
described in the following paper:
|
||||
|
||||
Richter, R., Kellenberger, T., & Kaufmann, H. (2009).
|
||||
Comparison of topographic correction methods.
|
||||
Remote Sensing, 1(3), 184-196.
|
||||
https://doi.org/10.3390/rs1030184
|
||||
|
||||
Topographic correction consists of the following steps:
|
||||
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
def calc_cosine_coeffs(hy_obj,topo_dict):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
|
||||
'''
|
||||
hy_obj.topo = topo_dict
|
||||
hy_obj.anc_data = {}
|
||||
|
||||
cos_i = hy_obj.cosine_i()
|
||||
cos_solar_zn = np.cos(hy_obj.get_anc('solar_zn'))
|
||||
|
||||
c_factor = cos_solar_zn/cos_i
|
||||
c_factor[~hy_obj.mask['no_data']] = 1.
|
||||
hy_obj.ancillary['cosine_factor'] =c_factor
|
||||
|
||||
def apply_cosine(hy_obj,data,dimension,index):
|
||||
''' Apply cosine correction to a slice of the data
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if 'cosine_factor' not in hy_obj.ancillary.keys():
|
||||
calc_cosine_coeffs(hy_obj)
|
||||
|
||||
#Convert to float
|
||||
data = data.astype(np.float32)
|
||||
|
||||
if dimension == 'line':
|
||||
#index= 3000
|
||||
#data = hy_obj.get_line(3000)
|
||||
data = data*hy_obj.ancillary['cosine_factor'][np.newaxis,index,:]
|
||||
|
||||
elif dimension == 'column':
|
||||
#index= 300
|
||||
#data = hy_obj.get_column(index)
|
||||
data = hy_obj.ancillary['cosine_factor'][:,index,np.newaxis]
|
||||
|
||||
elif dimension == 'band':
|
||||
#index= 8
|
||||
#data = hy_obj.get_band(index)
|
||||
data = data * hy_obj.ancillary['cosine_factor']
|
||||
|
||||
elif dimension == 'chunk':
|
||||
#index = 200,501,3000,3501
|
||||
x1,x2,y1,y2 = index
|
||||
#data = hy_obj.get_chunk(x1,x2,y1,y2)
|
||||
data = data*hy_obj.ancillary['cosine_factor'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
|
||||
elif dimension == 'pixels':
|
||||
#index = [[2000,2001],[200,501]]
|
||||
y,x = index
|
||||
#data = hy_obj.get_pixels(y,x)
|
||||
data = data*hy_obj.ancillary['cosine_factor'][y,x][:, np.newaxis]
|
||||
return data
|
||||
140
Flexbrdf/hytools/topo/modminn.py
Normal file
140
Flexbrdf/hytools/topo/modminn.py
Normal file
@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This module contains functions to apply the Modified topographic correction (SCS+C)
|
||||
described in the following paper:
|
||||
|
||||
Richter, R., Kellenberger, T., & Kaufmann, H. (2009).
|
||||
Comparison of topographic correction methods.
|
||||
Remote Sensing, 1(3), 184-196.
|
||||
https://doi.org/10.3390/rs1030184
|
||||
|
||||
Topographic correction consists of the following steps:
|
||||
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
def calc_modminn_coeffs(hy_obj,topo_dict):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
|
||||
'''
|
||||
hy_obj.topo =topo_dict
|
||||
|
||||
cos_i = hy_obj.cosine_i()
|
||||
i = np.rad2deg(np.arccos(cos_i))
|
||||
solar_zn = hy_obj.get_anc('solar_zn',radians=False)
|
||||
|
||||
solar_zn_t = np.zeros(solar_zn.shape)
|
||||
solar_zn_t[solar_zn < 45] = solar_zn[solar_zn < 45] +20
|
||||
solar_zn_t[(solar_zn >= 45) & (solar_zn <= 55)] = solar_zn[(solar_zn >= 45) & (solar_zn <= 55)] +15
|
||||
solar_zn_t[solar_zn > 55] = solar_zn[solar_zn > 55] +10
|
||||
|
||||
#Create NDVI mask to seperate vegetation
|
||||
ir = hy_obj.get_wave(850)
|
||||
red = hy_obj.get_wave(660)
|
||||
ndvi = (ir-red)/(ir+red)
|
||||
veg_mask = ndvi > 0.2
|
||||
|
||||
c_factors = np.ones((2,hy_obj.lines,hy_obj.columns))
|
||||
c_factors[:] = cos_i/np.cos(np.radians(solar_zn_t))
|
||||
|
||||
# Non vegetation correction factor
|
||||
c_factors[0][~veg_mask] = c_factors[0][~veg_mask]**(1/2)
|
||||
c_factors[1][~veg_mask] = c_factors[1][~veg_mask]**(1/2)
|
||||
|
||||
# Vegetation correction factor
|
||||
c_factors[0][veg_mask] = c_factors[0][veg_mask]**(3/4)
|
||||
c_factors[1][veg_mask] = c_factors[1][veg_mask]**(1/3)
|
||||
|
||||
#Adjust correction factors to prevent too strong correction
|
||||
c_factors[c_factors <.25] = .25
|
||||
c_factors[c_factors > 1] = 1
|
||||
|
||||
#Correct pixels only where i > threshold
|
||||
c_factors[0][i < solar_zn_t] = 1
|
||||
c_factors[1][i < solar_zn_t] = 1
|
||||
|
||||
c_factors[0][ir == hy_obj.no_data] = 1
|
||||
c_factors[1][ir == hy_obj.no_data] = 1
|
||||
|
||||
hy_obj.ancillary['mm_c_factor'] = c_factors
|
||||
|
||||
def apply_modminn(hy_obj,data,dimension,index):
|
||||
''' Apply SCSS correction to a slice of the data
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if 'mm_c_factor' not in hy_obj.ancillary.keys():
|
||||
calc_modminn_coeffs(hy_obj)
|
||||
|
||||
#Convert to float
|
||||
data = data.astype(np.float32)
|
||||
|
||||
wave_mask =hy_obj.wavelengths >=720
|
||||
|
||||
if dimension == 'line':
|
||||
#index= 3000
|
||||
#data = hy_obj.get_line(3000)
|
||||
data[:,wave_mask] = data[:,wave_mask]*hy_obj.ancillary['mm_c_factor'][1,index,:][:,np.newaxis]
|
||||
data[:,~wave_mask] = data[:,~wave_mask]*hy_obj.ancillary['mm_c_factor'][0,index,:][:,np.newaxis]
|
||||
|
||||
elif dimension == 'column':
|
||||
#index= 300
|
||||
#data = hy_obj.get_column(index)
|
||||
data[:,wave_mask] = data[:,wave_mask]*hy_obj.ancillary['mm_c_factor'][1,:,index][:,np.newaxis]
|
||||
data[:,~wave_mask] = data[:,~wave_mask]*hy_obj.ancillary['mm_c_factor'][0,:,index][:,np.newaxis]
|
||||
|
||||
elif dimension == 'band':
|
||||
#index= 50
|
||||
#data = hy_obj.get_band(index)
|
||||
if hy_obj.wavelengths[index] >=720:
|
||||
cf_index = 1
|
||||
else:
|
||||
cf_index = 0
|
||||
data = data * hy_obj.ancillary['mm_c_factor'][cf_index]
|
||||
|
||||
elif dimension == 'chunk':
|
||||
#index = 200,501,3000,3501
|
||||
x1,x2,y1,y2 = index
|
||||
#data = hy_obj.get_chunk(x1,x2,y1,y2)
|
||||
data[:,:,wave_mask] = data[:,:,wave_mask]*hy_obj.ancillary['mm_c_factor'][1,y1:y2,x1:x2][:,:,np.newaxis]
|
||||
data[:,:,~wave_mask] = data[:,:,~wave_mask]*hy_obj.ancillary['mm_c_factor'][0,y1:y2,x1:x2][:,:,np.newaxis]
|
||||
|
||||
elif dimension == 'pixels':
|
||||
#index = [[2000,2001],[200,501]]
|
||||
y,x = index
|
||||
#data = hy_obj.get_pixels(y,x)
|
||||
data[:,wave_mask] = data[:,wave_mask]*hy_obj.ancillary['mm_c_factor'][1,y,x][:, np.newaxis]
|
||||
data[:,~wave_mask] = data[:,~wave_mask]*hy_obj.ancillary['mm_c_factor'][0,y,x][:, np.newaxis]
|
||||
return data
|
||||
100
Flexbrdf/hytools/topo/scs.py
Normal file
100
Flexbrdf/hytools/topo/scs.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This module contains functions to apply the Modified topographic correction (SCS+C)
|
||||
described in the following paper:
|
||||
|
||||
Richter, R., Kellenberger, T., & Kaufmann, H. (2009).
|
||||
Comparison of topographic correction methods.
|
||||
Remote Sensing, 1(3), 184-196.
|
||||
https://doi.org/10.3390/rs1030184
|
||||
|
||||
Topographic correction consists of the following steps:
|
||||
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
def calc_scs_coeffs(hy_obj,topo_dict):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
|
||||
'''
|
||||
hy_obj.topo = topo_dict
|
||||
hy_obj.anc_data = {}
|
||||
|
||||
cos_i = hy_obj.cosine_i()
|
||||
cos_solar_zn = np.cos(hy_obj.get_anc('solar_zn'))
|
||||
cos_slope = np.cos(hy_obj.get_anc('slope'))
|
||||
|
||||
c_factor = (cos_slope *cos_solar_zn)/cos_i
|
||||
c_factor[~hy_obj.mask['no_data']] = 1.
|
||||
hy_obj.ancillary['scs_factor'] =c_factor
|
||||
|
||||
def apply_scs(hy_obj,data,dimension,index):
|
||||
''' Apply SCSS correction to a slice of the data
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if 'scs_factor' not in hy_obj.ancillary.keys():
|
||||
calc_scs_coeffs(hy_obj)
|
||||
|
||||
#Convert to float
|
||||
data = data.astype(np.float32)
|
||||
|
||||
if dimension == 'line':
|
||||
#index= 3000
|
||||
#data = hy_obj.get_line(3000)
|
||||
data = data*hy_obj.ancillary['scs_factor'][np.newaxis,index,:]
|
||||
|
||||
elif dimension == 'column':
|
||||
#index= 300
|
||||
#data = hy_obj.get_column(index)
|
||||
data = hy_obj.ancillary['scs_factor'][:,index,np.newaxis]
|
||||
|
||||
elif dimension == 'band':
|
||||
#index= 8
|
||||
#data = hy_obj.get_band(index)
|
||||
data = data * hy_obj.ancillary['scs_factor']
|
||||
|
||||
elif dimension == 'chunk':
|
||||
#index = 200,501,3000,3501
|
||||
x1,x2,y1,y2 = index
|
||||
#data = hy_obj.get_chunk(x1,x2,y1,y2)
|
||||
data = data*hy_obj.ancillary['scs_factor'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
|
||||
elif dimension == 'pixels':
|
||||
#index = [[2000,2001],[200,501]]
|
||||
y,x = index
|
||||
#data = hy_obj.get_pixels(y,x)
|
||||
data = data*hy_obj.ancillary['scs_factor'][y,x][:, np.newaxis]
|
||||
return data
|
||||
204
Flexbrdf/hytools/topo/scsc.py
Normal file
204
Flexbrdf/hytools/topo/scsc.py
Normal file
@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This module contains functions to apply a topographic correction (SCS+C)
|
||||
described in the following papers:
|
||||
|
||||
Scott A. Soenen, Derek R. Peddle, & Craig A. Coburn (2005).
|
||||
SCS+C: A Modified Sun-Canopy-Sensor Topographic Correction in Forested Terrain.
|
||||
IEEE Transactions on Geoscience and Remote Sensing, 43(9), 2148-2159.
|
||||
https://doi.org/10.1109/TGRS.2005.852480
|
||||
|
||||
Topographic correction consists of the following steps:
|
||||
|
||||
1. calculate incidence angle if it is not provided
|
||||
2. estimate C-Correction value
|
||||
3. apply C-Correction value to the image data
|
||||
|
||||
TODO: Rationale/ examples for using different fitting algorithms
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
from .c import calc_c, get_band_samples, get_cosine_i_samples
|
||||
import ray
|
||||
from ..misc import update_topo
|
||||
from ..misc import progbar
|
||||
|
||||
def calc_scsc_c1(solar_zn,slope):
|
||||
""" Calculate c1
|
||||
All input geometry units must be in radians.
|
||||
|
||||
Args:
|
||||
solar_zn (numpy.ndarray): Solar zenith angle.
|
||||
slope (numpy.ndarray): Ground slope.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: C1.
|
||||
|
||||
"""
|
||||
|
||||
# Eq 11. Soenen et al. 2005
|
||||
scsc_c1 = np.cos(solar_zn) * np.cos(slope)
|
||||
return scsc_c1
|
||||
|
||||
def calc_scsc_coeffs(hy_obj,topo_dict):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
|
||||
'''
|
||||
|
||||
topo_dict['coeffs'] = {}
|
||||
cosine_i = hy_obj.cosine_i()
|
||||
|
||||
for band_num,band in enumerate(hy_obj.bad_bands):
|
||||
if ~band:
|
||||
band = hy_obj.get_band(band_num,mask='calc_topo')
|
||||
topo_dict['coeffs'][band_num] = calc_c(band,cosine_i[hy_obj.mask['calc_topo']],
|
||||
fit_type=topo_dict['c_fit_type'])
|
||||
hy_obj.topo = topo_dict
|
||||
|
||||
def calc_scsc_coeffs_group(actors,topo_dict,group_tag):
|
||||
|
||||
cosine_i_samples = ray.get([a.do.remote(get_cosine_i_samples) for a in actors])
|
||||
cosine_i_samples = np.concatenate(cosine_i_samples)
|
||||
|
||||
print(f'Topo Subgroup {group_tag}')
|
||||
|
||||
bad_bands = ray.get(actors[0].do.remote(lambda x: x.bad_bands))
|
||||
coeffs = {}
|
||||
|
||||
for band_num,band in enumerate(bad_bands):
|
||||
if ~band:
|
||||
coeffs[band_num] = {}
|
||||
band_samples = ray.get([a.do.remote(get_band_samples,
|
||||
{'band_num':band_num}) for a in actors])
|
||||
band_samples = np.concatenate(band_samples)
|
||||
|
||||
coeffs[band_num] = calc_c(band_samples,cosine_i_samples,fit_type=topo_dict['c_fit_type'])
|
||||
progbar(np.sum(~bad_bands[:band_num+1]),np.sum(~bad_bands))
|
||||
|
||||
print('\n')
|
||||
|
||||
#Update TOPO coeffs
|
||||
_ = ray.get([a.do.remote(update_topo,{'key':'coeffs',
|
||||
'value': coeffs}) for a in actors])
|
||||
_ = ray.get([a.do.remote(update_topo,{'key':'subgroup',
|
||||
'value': group_tag}) for a in actors])
|
||||
|
||||
def apply_scsc_band(hy_obj,band,index):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
c1 = np.cos(hy_obj.get_anc('slope')) * np.cos(hy_obj.get_anc('solar_zn'))
|
||||
cosine_i = hy_obj.cosine_i()
|
||||
|
||||
C = hy_obj.topo['coeffs'][index]
|
||||
correction_factor = (c1 + C)/(cosine_i + C)
|
||||
band[hy_obj.mask['calc_topo']] = band[hy_obj.mask['calc_topo']] * correction_factor[hy_obj.mask['calc_topo']]
|
||||
band[~hy_obj.mask['no_data']] = hy_obj.no_data
|
||||
|
||||
return band
|
||||
|
||||
def apply_scsc(hy_obj,data,dimension,index):
|
||||
''' Apply SCSS correction to a slice of the data
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if 'c1' not in hy_obj.ancillary.keys():
|
||||
c1 = np.cos(hy_obj.get_anc('slope')) * np.cos(hy_obj.get_anc('solar_zn'))
|
||||
hy_obj.ancillary['c1'] = c1
|
||||
if 'cosine_i' not in hy_obj.ancillary.keys():
|
||||
cosine_i = hy_obj.cosine_i()
|
||||
hy_obj.ancillary['cosine_i'] = cosine_i
|
||||
|
||||
C_bands = list([int(x) for x in hy_obj.topo['coeffs'].keys()])
|
||||
C = np.array(list(hy_obj.topo['coeffs'].values()))
|
||||
|
||||
#Convert to float
|
||||
data = data.astype(np.float32)
|
||||
hy_obj.topo['coeffs'] = {int(k): hy_obj.topo['coeffs'][k] for k in hy_obj.topo['coeffs']}
|
||||
|
||||
if (dimension != 'band') & (dimension != 'chunk'):
|
||||
if dimension == 'line':
|
||||
#index= 3000
|
||||
#data = hy_obj.get_line(3000)
|
||||
mask = hy_obj.mask['apply_topo'][index,:]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][[index],:].T
|
||||
c1 = hy_obj.ancillary['c1'][[index],:].T
|
||||
|
||||
elif dimension == 'column':
|
||||
#index= 300
|
||||
#data = hy_obj.get_column(index)
|
||||
mask = hy_obj.mask['apply_topo'][:,index]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][:,[index]]
|
||||
c1 = hy_obj.ancillary['c1'][:,[index]]
|
||||
|
||||
elif dimension == 'pixels':
|
||||
#index = [[2000,2001],[200,501]]
|
||||
y,x = index
|
||||
#data = hy_obj.get_pixels(y,x)
|
||||
mask = hy_obj.mask['apply_topo'][y,x]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][[y],[x]].T
|
||||
c1 = hy_obj.ancillary['c1'][[y],[x]].T
|
||||
|
||||
correction_factor = np.ones(data.shape)
|
||||
correction_factor[:,C_bands] = (c1 + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
elif dimension == 'chunk':
|
||||
#index = 200,501,3000,3501
|
||||
x1,x2,y1,y2 = index
|
||||
#data = hy_obj.get_chunk(x1,x2,y1,y2)
|
||||
mask = hy_obj.mask['apply_topo'][y1:y2,x1:x2]
|
||||
cosine_i = hy_obj.ancillary['cosine_i'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
c1 = hy_obj.ancillary['c1'][y1:y2,x1:x2][:,:,np.newaxis]
|
||||
|
||||
correction_factor = np.ones(data.shape)
|
||||
correction_factor[:,:,C_bands] = (c1 + C)/(cosine_i + C)
|
||||
data[mask,:] = data[mask,:]*correction_factor[mask,:]
|
||||
|
||||
elif (dimension == 'band') and (index in hy_obj.topo['coeffs']):
|
||||
#index= 8
|
||||
#data = hy_obj.get_band(index)
|
||||
C = hy_obj.topo['coeffs'][index]
|
||||
correction_factor = (hy_obj.ancillary['c1'] + C)/(hy_obj.ancillary['cosine_i'] + C)
|
||||
data[hy_obj.mask['apply_topo']] = data[hy_obj.mask['apply_topo']] * correction_factor[hy_obj.mask['apply_topo']]
|
||||
return data
|
||||
183
Flexbrdf/hytools/topo/topo.py
Normal file
183
Flexbrdf/hytools/topo/topo.py
Normal file
@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
HyTools: Hyperspectral image processing library
|
||||
Copyright (C) 2021 University of Wisconsin
|
||||
|
||||
Authors: Adam Chlus, Zhiwei Ye, Philip Townsend.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Topographic correction
|
||||
|
||||
"""
|
||||
import json
|
||||
import numpy as np
|
||||
import ray
|
||||
from .modminn import apply_modminn,calc_modminn_coeffs
|
||||
from .scsc import apply_scsc,calc_scsc_coeffs, calc_scsc_coeffs_group
|
||||
from .cosine import apply_cosine,calc_cosine_coeffs
|
||||
from .c import apply_c,calc_c_coeffs, calc_c_coeffs_group
|
||||
from .scs import apply_scs,calc_scs_coeffs
|
||||
from ..masks import mask_create
|
||||
from ..misc import set_topo
|
||||
|
||||
def calc_cosine_i(solar_zn, solar_az, aspect ,slope):
|
||||
"""Generate cosine i image. The cosine of the incidence angle (i) is
|
||||
defined as the angle between the normal to the pixel surface
|
||||
and the solar zenith direction.
|
||||
All input geometry units must be in radians.
|
||||
|
||||
Args:
|
||||
solar_az (numpy.ndarray): Solar azimuth angle.
|
||||
solar_zn (numpy.ndarray): Solar zenith angle.
|
||||
aspect (numpy.ndarray): Ground aspect.
|
||||
slope (numpy.ndarray): Ground slope.
|
||||
|
||||
Returns:
|
||||
cnumpy.ndarray: Cosine i image.
|
||||
|
||||
"""
|
||||
|
||||
relative_az = aspect - solar_az
|
||||
cosine_i = np.cos(solar_zn)*np.cos(slope) + np.sin(solar_zn)*np.sin(slope)* np.cos(relative_az)
|
||||
return cosine_i
|
||||
|
||||
def apply_topo_correct(hy_obj,data,dimension,index):
|
||||
'''
|
||||
|
||||
Args:
|
||||
hy_obj (TYPE): DESCRIPTION.
|
||||
band (TYPE): DESCRIPTION.
|
||||
index (TYPE): DESCRIPTION.
|
||||
|
||||
Returns:
|
||||
band (TYPE): DESCRIPTION.
|
||||
|
||||
'''
|
||||
|
||||
if ('apply_topo' not in hy_obj.mask) & ('apply_mask' in hy_obj.topo):
|
||||
hy_obj.gen_mask(mask_create,'apply_topo',hy_obj.topo['apply_mask'])
|
||||
|
||||
if hy_obj.topo['type'] == 'mod_minneart':
|
||||
data = apply_modminn(hy_obj,data,dimension,index)
|
||||
elif hy_obj.topo['type'] == 'scs+c':
|
||||
data = apply_scsc(hy_obj,data,dimension,index)
|
||||
elif hy_obj.topo['type'] == 'cosine':
|
||||
data = apply_cosine(hy_obj,data,dimension,index)
|
||||
elif hy_obj.topo['type'] == 'c':
|
||||
data = apply_c(hy_obj,data,dimension,index)
|
||||
elif hy_obj.topo['type'] == 'scs':
|
||||
data = apply_scs(hy_obj,data,dimension,index)
|
||||
return data
|
||||
|
||||
def load_topo_precomputed(hy_obj,topo_dict):
|
||||
with open(topo_dict['coeff_files'][hy_obj.file_name], 'r') as outfile:
|
||||
hy_obj.topo = json.load(outfile)
|
||||
|
||||
def get_topo_sample_mask(hy_obj,topo_dict):
|
||||
|
||||
sample_ratio = float(topo_dict["sample_perc"])
|
||||
|
||||
subsample_mask = np.copy(hy_obj.mask['calc_topo'])
|
||||
|
||||
idx = np.array(np.where(subsample_mask!=0)).T
|
||||
|
||||
if idx.shape[0]>5:
|
||||
idxRand= idx[np.random.choice(range(len(idx)),int(len(idx)*(1-sample_ratio)), replace = False)].T
|
||||
subsample_mask[idxRand[0],idxRand[1]] = 0
|
||||
|
||||
subsample_mask = subsample_mask.astype(np.int8)
|
||||
|
||||
hy_obj.ancillary['sample_mask']=subsample_mask
|
||||
|
||||
def calc_topo_coeffs(actors,topo_dict,actor_group_list=None,group_tag_list=None):
|
||||
#def calc_topo_coeffs(actors,actor_group_list,topo_dict,group_tag_list):
|
||||
if topo_dict['type'] == 'precomputed':
|
||||
print("Using precomputed topographic coefficients.")
|
||||
_ = ray.get([a.do.remote(load_topo_precomputed,topo_dict) for a in actors]) # actors
|
||||
|
||||
#_ = ray.get([a.do.remote(lambda x: x.corrections.append('topo')) for a in actors])
|
||||
|
||||
else:
|
||||
print("Calculating topographic coefficients.")
|
||||
|
||||
_ = ray.get([a.do.remote(set_topo,topo_dict) for a in actors])
|
||||
|
||||
_ = ray.get([a.gen_mask.remote(mask_create,'calc_topo',
|
||||
topo_dict['calc_mask']) for a in actors])
|
||||
|
||||
if (actor_group_list is None) or (topo_dict['type'] in ['scs','mod_minneart','cosine']):
|
||||
# no grouping
|
||||
if topo_dict['type'] == 'scs+c':
|
||||
_ = ray.get([a.do.remote(calc_scsc_coeffs,topo_dict) for a in actors])
|
||||
|
||||
elif topo_dict['type'] == 'scs':
|
||||
_ = ray.get([a.do.remote(calc_scs_coeffs,topo_dict) for a in actors])
|
||||
|
||||
elif topo_dict['type'] == 'mod_minneart':
|
||||
_ = ray.get([a.do.remote(calc_modminn_coeffs,topo_dict) for a in actors])
|
||||
|
||||
elif topo_dict['type'] == 'cosine':
|
||||
_ = ray.get([a.do.remote(calc_cosine_coeffs,topo_dict) for a in actors])
|
||||
|
||||
elif topo_dict['type'] == 'c':
|
||||
_ = ray.get([a.do.remote(calc_c_coeffs,topo_dict) for a in actors])
|
||||
|
||||
#_ = ray.get([a.do.remote(lambda x: x.corrections.append('topo')) for a in actors])
|
||||
|
||||
else:
|
||||
|
||||
_ = ray.get([a.do.remote(get_topo_sample_mask,topo_dict) for a in actors])
|
||||
|
||||
for group_order, sub_actors in enumerate(actor_group_list):
|
||||
|
||||
#return 0
|
||||
|
||||
if topo_dict['type'] == 'scs+c':
|
||||
calc_scsc_coeffs_group(sub_actors,topo_dict,group_tag_list[group_order])
|
||||
|
||||
elif topo_dict['type'] == 'c':
|
||||
calc_c_coeffs_group(sub_actors,topo_dict,group_tag_list[group_order])
|
||||
|
||||
_ = ray.get([a.do.remote(lambda x: x.corrections.append('topo')) for a in actors])
|
||||
|
||||
|
||||
def calc_topo_coeffs_single(hy_obj,topo_dict):
|
||||
|
||||
if topo_dict['type'] == 'precomputed':
|
||||
print("Using precomputed topographic coefficients.")
|
||||
load_topo_precomputed(hy_obj,topo_dict)
|
||||
|
||||
else:
|
||||
print("Calculating topographic coefficients.")
|
||||
|
||||
hy_obj.gen_mask(mask_create,'calc_topo',topo_dict['calc_mask'])
|
||||
|
||||
|
||||
if topo_dict['type'] == 'scs+c':
|
||||
calc_scsc_coeffs(hy_obj,topo_dict)
|
||||
|
||||
elif topo_dict['type'] == 'scs':
|
||||
calc_scs_coeffs(hy_obj,topo_dict)
|
||||
|
||||
elif topo_dict['type'] == 'mod_minneart':
|
||||
calc_modminn_coeffs(hy_obj,topo_dict)
|
||||
|
||||
elif topo_dict['type'] == 'cosine':
|
||||
calc_cosine_coeffs(hy_obj,topo_dict)
|
||||
|
||||
elif topo_dict['type'] == 'c':
|
||||
calc_c_coeffs(hy_obj,topo_dict)
|
||||
|
||||
hy_obj.corrections.append('topo')
|
||||
Reference in New Issue
Block a user