def __init__(self, config): self.wl, self.fwhm = load_wavelen(config['wavelength_file']) self.n_chan = len(self.wl) if 'auto_rebuild' in config: self.auto_rebuild = config['auto_rebuild'] else: self.auto_rebuild = True self.lut_grid = config['lut_grid'] self.lut_dir = config['lut_path'] self.statevec = list(config['statevector'].keys()) self.bvec = list(config['unknowns'].keys()) self.n_point = len(self.lut_grid) self.n_state = len(self.statevec) # Retrieved variables. We establish scaling, bounds, and # initial guesses for each state vector element. The state # vector elements are all free parameters in the RT lookup table, # and they all have associated dimensions in the LUT grid. self.bounds, self.scale, self.init = [], [], [] self.prior_mean, self.prior_sigma = [], [] for key in self.statevec: element = config['statevector'][key] self.bounds.append(element['bounds']) self.scale.append(element['scale']) self.init.append(element['init']) self.prior_sigma.append(element['prior_sigma']) self.prior_mean.append(element['prior_mean']) self.bounds = s.array(self.bounds) self.scale = s.array(self.scale) self.init = s.array(self.init) self.prior_mean = s.array(self.prior_mean) self.prior_sigma = s.array(self.prior_sigma) self.bval = s.array([config['unknowns'][k] for k in self.bvec])
def __init__(self, config): Surface.__init__(self, config) self.wl, fwhm = load_wavelen(config['wavelength_file']) self.statevec, self.bounds, self.scale, self.init = [], [], [], [] # Each channel maps to a nonnegative absorption residual if 'absorption_resid_file' in config: abs_file = config['absorption_resid_file'] self.C = loadmat(abs_file)['C'] self.abs_inds = s.arange(len(self.wl)) amin, amax = -1.0, 1.0 self.statevec.extend(['ABS_%04i' % int(w) for w in self.wl]) self.bounds.extend([[amin, amax] for w in self.wl]) self.scale.extend([0.01 for w in self.wl]) self.init.extend([0 for v in self.wl]) ind_start = len(self.statevec) else: self.abs_inds = [] self.C = s.array([[]], dtype=float) ind_start = 0 # Other retrieved variables nonabs_sv = ['X', 'G', 'P', 'Y', 'GLINT', 'FLH'] self.statevec.extend(nonabs_sv) self.nonabs_inds = ind_start + s.arange(len(nonabs_sv), dtype=int) self.X_ind = ind_start self.G_ind = ind_start+1 self.P_ind = ind_start+2 self.Y_ind = ind_start+3 self.glint_ind = ind_start+4 self.flh_ind = ind_start+5 self.scale.extend([0.1, 1.0, 1.0, 1.0, 1.0, 1.0]) self.init.extend([0.1, 0.1, 0.1, 0.1, 0.1, 0.01]) self.bounds.extend( [[0, 1.0], [0, 1.0], [0, 10], [0, 2.5], [0, 1], [0, 10]]) aw_wl, aw, q, q2 = s.loadtxt(config['h2oabs_file'], comments='#').T self.aw = interp1d(aw_wl, aw, fill_value='extrapolate')(self.wl) bbw_wl, bbw, q = s.loadtxt(config['h2oscatter_file'], comments='#').T self.bbw = interp1d(bbw_wl, bbw, fill_value='extrapolate')(self.wl) ap_wl, ap1, ap2 = s.loadtxt(config['pigments_file'], comments='#').T aphi = [interp1d(ap_wl, ap1, fill_value='extrapolate')(self.wl), interp1d(ap_wl, ap2, fill_value='extrapolate')(self.wl)] self.aphi_coeffs = s.array(aphi).T self.g0 = config.get('G0', 0.0895) # Lee's first constant self.g1 = config.get('G1', 0.1247) # Lee's second constant self.b1000 = s.argmin(abs(self.wl-1000)) self.b900 = s.argmin(abs(900-self.wl)) self.b440 = s.argmin(abs(self.wl-440)) self.b490 = s.argmin(abs(self.wl-490)) self.b550 = s.argmin(abs(self.wl-550)) self.b640 = s.argmin(abs(self.wl-640)) # Phytoplankton fluorescence peak center and width self.fl_mu = config.get('fl_mu', 683.0) self.fl_sigma = config.get('fl_fwhm', 25.0)/2.355
def __init__(self, config): TabularRT.__init__(self, config) self.sixs_dir = self.find_basedir(config) self.wl, self.fwhm = load_wavelen(config['wavelength_file']) self.sixs_grid_init = s.arange(self.wl[0], self.wl[-1] + 2.5, 2.5) self.sixs_ngrid_init = len(self.sixs_grid_init) self.sixs_dir = self.find_basedir(config) self.params = { 'aermodel': 1, 'AOT550': 0.01, 'H2OSTR': 0, 'O3': 0.40, 'day': config['day'], 'month': config['month'], 'elev': config['elev'], 'alt': config['alt'], 'atm_file': None, 'abscf_data_directory': None, 'wlinf': self.sixs_grid_init[0] / 1000.0, # convert to nm 'wlsup': self.sixs_grid_init[-1] / 1000.0 } if 'obs_file' in config: # A special case where we load the observation geometry # from a custom-crafted text file g = Geometry(obs=config['obs_file']) self.params['solzen'] = g.solar_zenith self.params['solaz'] = g.solar_azimuth self.params['viewzen'] = g.observer_zenith self.params['viewaz'] = g.observer_azimuth else: # We have to get geometry from somewhere, so we presume it is # in the configuration file. for f in ['solzen', 'viewzen', 'solaz', 'viewaz']: self.params[f] = config[f] self.esd = s.loadtxt(config['earth_sun_distance_file']) dt = datetime(2000, self.params['month'], self.params['day']) self.day_of_year = dt.timetuple().tm_yday self.irr_factor = self.esd[self.day_of_year - 1, 1] irr = s.loadtxt(config['irradiance_file'], comments='#') self.iwl, self.irr = irr.T self.irr = self.irr / 10.0 # convert, uW/nm/cm2 self.irr = self.irr / self.irr_factor**2 # consider solar distance self.build_lut()
def __init__(self, config): self.statevec = [] self.bounds = s.array([]) self.scale = s.array([]) self.init = s.array([]) self.bvec = [] self.bval = s.array([]) self.emissive = False self.reconfigure(config) if 'reflectance_file' in config: self.rfl, self.wl = load_spectrum(config['reflectance_file']) self.n_wl = len(self.wl) elif 'wavelength_file' in config: self.wl, self.fwhm = load_wavelen(config['wavelength_file']) self.n_wl = len(self.wl)
def __init__(self, config): """A model of the spectrometer instrument, including spectral response and noise covariance matrices. Noise is typically calculated from a parametric model, fit for the specific instrument. It is a function of the radiance level.""" # If needed, skip first index column and/or convert to nanometers self.wl_init, self.fwhm_init = load_wavelen(config['wavelength_file']) self.n_chan = len(self.wl_init) self.bounds = [] self.scale = [] self.statevec = [] self.init = [] self.prior_sigma = [] self.prior_mean = [] self.fast_resample = True # The "fast resample" option approximates a complete resampling by a # convolution with a uniform FWHM. if 'fast_resample' in config: self.fast_resample = config['fast_resample'] # Are there free parameters? if 'statevector' in config: for key in config['statevector']: self.statevec.append(key) for attr in config['statevector'][key]: getattr(self, attr).append(config['statevector'][key][attr]) self.prior_sigma = s.array(self.prior_sigma) self.prior_mean = s.array(self.prior_mean) self.n_state = len(self.statevec) # Number of integrations comprising the measurement. Noise diminishes # with the square root of this number. self.integrations = config['integrations'] if 'SNR' in config: # We have several ways to define the instrument noise. The # simplest model is based on a single uniform SNR number that # is signal-independnet and applied uniformly to all wavelengths self.model_type = 'SNR' self.snr = float(config['SNR']) elif 'parametric_noise_file' in config: # The second option is a parametric, signal- and wavelength- # dependent noise function. This is given by a four-column # ASCII Text file. Rows represent, respectively, the reference # wavelength, and coefficients A, B, and C that define the # noise-equivalent radiance via NeDL = A * sqrt(B+L) + C # For the actual radiance L. self.noise_file = config['parametric_noise_file'] self.model_type = 'parametric' coeffs = s.loadtxt(self.noise_file, delimiter=' ', comments='#') p_a, p_b, p_c = [ interp1d(coeffs[:, 0], coeffs[:, col], fill_value='extrapolate') for col in (1, 2, 3) ] self.noise = s.array([[p_a(w), p_b(w), p_c(w)] for w in self.wl_init]) elif 'pushbroom_noise_file' in config: # The third option is a full pushbroom noise model that # specifies noise columns and covariances independently for # each cross-track location via an ENVI-format binary data file. self.model_type = 'pushbroom' self.noise_file = config['pushbroom_noise_file'] D = loadmat(self.noise_file) self.ncols = D['columns'][0, 0] if self.n_chan != s.sqrt(D['bands'][0, 0]): logging.error('Noise model mismatches wavelength # bands') raise ValueError('Noise model mismatches wavelength # bands') cshape = ((self.ncols, self.n_chan, self.n_chan)) self.covs = D['covariances'].reshape(cshape) else: logging.error('Instrument noise not defined.') raise IndexError('Please define the instrument noise.') # We track several unretrieved free variables, that are specified # in a fixed order (always start with relative radiometric # calibration) self.bvec = ['Cal_Relative_%04i' % int(w) for w in self.wl_init] + \ ['Cal_Spectral', 'Cal_Stray_SRF'] self.bval = s.zeros(self.n_chan + 2) if 'unknowns' in config: # First we take care of radiometric uncertainties, which add # in quadrature. We sum their squared values. Systematic # radiometric uncertainties account for differences in sampling # and radiative transfer that manifest predictably as a function # of wavelength. unknowns = config['unknowns'] if 'channelized_radiometric_uncertainty_file' in unknowns: f = unknowns['channelized_radiometric_uncertainty_file'] u = s.loadtxt(f, comments='#') if (len(u.shape) > 0 and u.shape[1] > 1): u = u[:, 1] self.bval[:self.n_chan] = self.bval[:self.n_chan] + pow(u, 2) # Uncorrelated radiometric uncertainties are consistent and # independent in all channels. if 'uncorrelated_radiometric_uncertainty' in unknowns: u = unknowns['uncorrelated_radiometric_uncertainty'] self.bval[:self.n_chan] = self.bval[:self.n_chan] + \ pow(s.ones(self.n_chan) * u, 2) # Radiometric uncertainties combine via Root Sum Square... # Be careful to avoid square roots of zero! small = s.ones(self.n_chan) * eps self.bval[:self.n_chan] = s.maximum(self.bval[:self.n_chan], small) self.bval[:self.n_chan] = s.sqrt(self.bval[:self.n_chan]) # Now handle spectral calibration uncertainties if 'wavelength_calibration_uncertainty' in unknowns: self.bval[-2] = unknowns['wavelength_calibration_uncertainty'] if 'stray_srf_uncertainty' in unknowns: self.bval[-1] = unknowns['stray_srf_uncertainty'] # Determine whether the calibration is fixed. If it is fixed, # and the wavelengths of radiative transfer modeling and instrument # are the same, then we can bypass compputationally expensive sampling # operations later. self.calibration_fixed = (not ('FWHM_SCL' in self.statevec)) and \ (not ('WL_SHIFT' in self.statevec)) and \ (not ('WL_SPACE' in self.statevec))