2020-03-05 21:34:16 +00:00
from opendm import dls
import math
2020-03-06 18:04:34 +00:00
import numpy as np
from opendm import log
2020-03-05 15:39:16 +00:00
# Loosely based on https://github.com/micasense/imageprocessing/blob/master/micasense/utils.py
2020-02-26 22:33:08 +00:00
def dn_to_radiance ( photo , image ) :
2020-02-26 21:06:39 +00:00
"""
Convert Digital Number values to Radiance values
: param photo ODM_Photo
2020-02-26 22:33:08 +00:00
: param image numpy array containing image data
2020-02-26 21:06:39 +00:00
: return numpy array with radiance image values
"""
2020-03-06 18:04:34 +00:00
2020-03-08 15:33:27 +00:00
image = image . astype ( " float32 " )
2020-03-27 14:05:42 +00:00
if len ( image . shape ) != 3 :
raise ValueError ( " Image should have shape length of 3 (got: %s ) " % len ( image . shape ) )
2020-03-06 18:04:34 +00:00
2020-03-05 21:34:16 +00:00
# Handle thermal bands (experimental)
if photo . band_name == ' LWIR ' :
image - = ( 273.15 * 100.0 ) # Convert Kelvin to Celsius
2020-03-06 18:04:34 +00:00
image * = 0.01
2020-03-05 21:34:16 +00:00
return image
# All others
2020-03-05 15:39:16 +00:00
a1 , a2 , a3 = photo . get_radiometric_calibration ( )
dark_level = photo . get_dark_level ( )
exposure_time = photo . exposure_time
gain = photo . get_gain ( )
2020-03-06 18:04:34 +00:00
photometric_exp = photo . get_photometric_exposure ( )
if a1 is None and photometric_exp is None :
log . ODM_WARNING ( " Cannot perform radiometric calibration, no FNumber/Exposure Time or Radiometric Calibration EXIF tags found in %s . Using Digital Number. " % photo . filename )
return image
if a1 is None and photometric_exp is not None :
a1 = photometric_exp
2020-03-05 15:39:16 +00:00
V , x , y = vignette_map ( photo )
if x is None :
x , y = np . meshgrid ( np . arange ( photo . width ) , np . arange ( photo . height ) )
if dark_level is not None :
image - = dark_level
2020-03-06 18:04:34 +00:00
# Normalize DN to 0 - 1.0
2020-03-09 19:57:44 +00:00
bit_depth_max = photo . get_bit_depth_max ( )
if bit_depth_max :
image / = bit_depth_max
2020-03-05 15:39:16 +00:00
if V is not None :
# vignette correction
2020-03-27 14:05:42 +00:00
V = np . repeat ( V [ : , : , np . newaxis ] , image . shape [ 2 ] , axis = 2 )
2020-03-05 15:39:16 +00:00
image * = V
if exposure_time and a2 is not None and a3 is not None :
# row gradient correction
R = 1.0 / ( 1.0 + a2 * y / exposure_time - a3 * y )
2020-03-27 14:05:42 +00:00
R = np . repeat ( R [ : , : , np . newaxis ] , image . shape [ 2 ] , axis = 2 )
2020-03-05 15:39:16 +00:00
image * = R
# Floor any negative radiances to zero (can happend due to noise around blackLevel)
if dark_level is not None :
image [ image < 0 ] = 0
# apply the radiometric calibration - i.e. scale by the gain-exposure product and
# multiply with the radiometric calibration coefficient
if gain is not None and exposure_time is not None :
image / = ( gain * exposure_time )
2020-03-06 18:04:34 +00:00
image * = a1
2020-03-05 21:34:16 +00:00
2020-03-05 15:39:16 +00:00
return image
def vignette_map ( photo ) :
x_vc , y_vc = photo . get_vignetting_center ( )
polynomial = photo . get_vignetting_polynomial ( )
if x_vc and polynomial :
2020-03-06 18:04:34 +00:00
# append 1., so that we can call with numpy polyval
2020-03-05 15:39:16 +00:00
polynomial . append ( 1.0 )
vignette_poly = np . array ( polynomial )
# perform vignette correction
# get coordinate grid across image
x , y = np . meshgrid ( np . arange ( photo . width ) , np . arange ( photo . height ) )
# meshgrid returns transposed arrays
2020-03-06 18:04:34 +00:00
# x = x.T
# y = y.T
2020-03-05 15:39:16 +00:00
# compute matrix of distances from image center
r = np . hypot ( ( x - x_vc ) , ( y - y_vc ) )
# compute the vignette polynomial for each distance - we divide by the polynomial so that the
# corrected image is image_corrected = image_original * vignetteCorrection
vignette = 1.0 / np . polyval ( vignette_poly , r )
return vignette , x , y
return None , None , None
2020-03-05 21:34:16 +00:00
def dn_to_reflectance ( photo , image , use_sun_sensor = True ) :
radiance = dn_to_radiance ( photo , image )
2020-03-06 22:20:44 +00:00
irradiance = compute_irradiance ( photo , use_sun_sensor = use_sun_sensor )
return radiance * math . pi / irradiance
def compute_irradiance ( photo , use_sun_sensor = True ) :
2020-03-05 21:34:16 +00:00
# Thermal?
if photo . band_name == " LWIR " :
return 1.0
2020-03-06 22:20:44 +00:00
# Some cameras (Micasense) store the value (nice! just return)
hirradiance = photo . get_horizontal_irradiance ( )
if hirradiance is not None :
return hirradiance
# TODO: support for calibration panels
2020-03-08 15:33:27 +00:00
if use_sun_sensor and photo . get_sun_sensor ( ) :
2020-03-05 21:34:16 +00:00
# Estimate it
dls_orientation_vector = np . array ( [ 0 , 0 , - 1 ] )
sun_vector_ned , sensor_vector_ned , sun_sensor_angle , \
solar_elevation , solar_azimuth = dls . compute_sun_angle ( [ photo . latitude , photo . longitude ] ,
2020-03-08 15:33:27 +00:00
photo . get_dls_pose ( ) ,
2020-03-06 18:04:34 +00:00
photo . get_utc_time ( ) ,
2020-03-05 21:34:16 +00:00
dls_orientation_vector )
angular_correction = dls . fresnel ( sun_sensor_angle )
# TODO: support for direct and scattered irradiance
2020-03-08 15:33:27 +00:00
direct_to_diffuse_ratio = 6.0 # Assumption, clear skies
spectral_irradiance = photo . get_sun_sensor ( )
2020-03-05 21:34:16 +00:00
percent_diffuse = 1.0 / direct_to_diffuse_ratio
sensor_irradiance = spectral_irradiance / angular_correction
2020-03-06 22:20:44 +00:00
# Find direct irradiance in the plane normal to the sun
2020-03-05 21:34:16 +00:00
untilted_direct_irr = sensor_irradiance / ( percent_diffuse + np . cos ( sun_sensor_angle ) )
direct_irradiance = untilted_direct_irr
2020-03-06 22:20:44 +00:00
scattered_irradiance = untilted_direct_irr * percent_diffuse
2020-03-05 21:34:16 +00:00
# compute irradiance on the ground using the solar altitude angle
horizontal_irradiance = direct_irradiance * np . sin ( solar_elevation ) + scattered_irradiance
2020-03-06 22:20:44 +00:00
return horizontal_irradiance
elif use_sun_sensor :
log . ODM_WARNING ( " No sun sensor values found for %s " % photo . filename )
2020-03-05 21:34:16 +00:00
return 1.0