def arc_model_to_rgb(thickness, porosity, aoi=8): # Wavelength axis wavelength = np.arange(360, 801, 5).astype('float') # Choice of illuminant makes a very small change to the calculated RGB color. illuminant = 'LED-B3' # # Scan thickness and porosity. # thickness = np.arange(0, 196, 5).astype('float') # porosity = np.arange(0, 0.51, 0.1).astype('float') # col = np.zeros((3, len(thickness), len(porosity))) # col_hex = np.empty((len(thickness), len(porosity)), dtype='object') # xyY = np.zeros((3, len(thickness), len(porosity))) index_film = refractive_index_porous_silica(wavelength, porosity) index_substrate = refractive_index_glass(wavelength) # Calculate reflectance reflectance = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, aoi=aoi, wavelength=wavelength) reflectance_ref = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate, film_thickness=0, aoi=aoi, wavelength=wavelength) rgb = spectrum_to_rgb(wavelength, reflectance, illuminant=illuminant) rgb_ref = spectrum_to_rgb(wavelength, reflectance_ref, illuminant=illuminant) # White balance rgb_wb = rgb / rgb_ref # Clamp rgb_wb[rgb_wb >= 1] = 1 rgb_wb[rgb_wb < 0] = 0 return rgb_wb
def arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity): index_film = refractive_index_porous_silica(wavelength=wavelength, porosity=porosity) index_substrate = refractive_index_glass(wavelength=wavelength) thin_film_R = thin_film_reflectance( index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, wavelength=wavelength, aoi=aoi ) # # thin_film_R = thin_film_reflection_fast( # wavelength=wavelength, # thickness=thickness, # aoi=aoi, # porosity=porosity) # # index_film = index_porous_silica(wavelength=wavelength, # porosity=porosity) # thin_film_reflectance = thin_film_reflection( # polarization='mixed', # wavelength=wavelength, # d_list=[np.inf, thickness, np.inf], # index_film=index_film, # index_substrate=index_glass, # aoi=aoi) glass_reflectance = np.interp(wavelength, wavelength_calc, glass_reflectance_calc) reflectance = (1 - fraction_dust) * ( fraction_abraded * glass_reflectance + ( 1 - fraction_abraded) * thin_film_R) + fraction_dust return 100 * reflectance
"""Example test comparing fast explicit method and tmm method for thin film reflectance. toddkarin 09/10/2020 """ import numpy as np from time import time from pvarc.materials import refractive_index_glass, refractive_index_porous_silica from pvarc.tmm import thin_film_reflectance_tmm from pvarc import thin_film_reflectance wavelength = np.linspace(200, 1250, 2000) index_substrate = refractive_index_glass(wavelength) index_film = refractive_index_porous_silica(wavelength) aoi = 8 film_thickness = 120 polarization = 'mixed' start_time = time() R_tmm = thin_film_reflectance_tmm(index_film=index_film, index_substrate=index_substrate, film_thickness=film_thickness, aoi=aoi, wavelength=wavelength, polarization=polarization) time_tmm = time() - start_time print('Elapsed time for TMM method: {:.5f} s'.format(time_tmm)) start_time = time()
def arc_reflection_model(wavelength, thickness=125, fraction_abraded=0, porosity=0.3, fraction_dust=0, aoi=8, n0=1.0003): """ Return the reflection values for a model of an aged ARC. The reflection is a linear combination of reflection from a thin film of a variable thickness and the reflection from BK7 glass. Parameters ---------- wavelength : ndarray wavelength in nm thickness thickness of ARC in nm. fraction_abraded fraction of coating loss. 1 corresponds to 100% of the coating area removed and only underlying glass is present, 0 corresponds to the coating covering the entire sample. fraction_dust fraction of module area covered by dust with reflectivity of 1. A value of 0 corresponds to no dust (clean sample), 1 to full dust coverage (reflectivity of 1). aoi angle of incidence in degrees. Returns ------- reflectance : ndarray Reflectance of sample at the values of wavelength specified. """ index_substrate = refractive_index_glass(wavelength) index_film = refractive_index_porous_silica(wavelength, porosity=porosity) glass_reflectance = single_interface_reflectance( n0=n0, n1=index_substrate, polarization='mixed', aoi=aoi) thin_film_R = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, aoi=aoi, wavelength=wavelength) reflectance = (1 - fraction_dust) * ( fraction_abraded * glass_reflectance + ( 1 - fraction_abraded) * thin_film_R) + fraction_dust return reflectance
def fit_arc_reflection_spectrum(wavelength, reflectance, x0=None, aoi=8, model='d', fixed=None, verbose=False, method='basinhopping', niter=20, wavelength_min=450, wavelength_max=1000): """ This function fits an ARC model to the reflection spectrum. The ARC model is described in the function arc_reflection_model. Parameters ---------- wavelength : ndarray Wavelength in nm reflectance : ndarray Fractional reflection between 0 and 1, unitless. x0 : dict Startpoint for fitting algorithm. aoi : float Angle of incidence of light model : str Different variation of the same model are available. The model is described under the function `arc_reflection_model`. The fixed values are specified in the input 'fixed'. 'TP' - thickness and poristy are fit, fraction_abraded and fraction_dust set to fixed values. 'TPA' - thickness, porosity and fraction_abraded are fit, fraction_dust is fixed. 'TPAD' - thickness, porosity, fraction_abraded and fraction_dust are fit. 'TAD' - thickness, fraction_abraded, fraction_dust are fit, porosity is a fixed value. fixed : dict Dictionary of parameters to be fixed. Default is: fixed = {'thickness': 125, 'fraction_abraded': 0, 'fraction_dust': 0, 'porosity': 0.3} verbose : bool Whether to print output at each iteration. method : str Optimization method, can be 'minimize' or 'basinhopping' niter : int Number of basinhopping steps if method == 'basinhopping' wavelength_min : float Lower bound for wavelength values used in fit. wavelength_max : float Upper bound for wavelength values used in fit. Returns ------- result : dict Dictionary of best fit values. ret Output of optimizer. """ if np.mean(reflectance) > 1: print( 'Warning: check that reflectance is a fractional value between 0 and 1.') if x0 == None: x0 = estimate_arc_reflection_model_params(wavelength, reflectance) fixed_default = {'thickness': 125, 'fraction_abraded': 0, 'fraction_dust': 0, 'porosity': 0.3} if fixed == None: fixed = fixed_default else: for p in fixed_default: if p not in fixed: fixed[p] = fixed_default[p] # print('x0: ', x0) scale = {'thickness': 0.01, 'fraction_abraded': 1, 'fraction_dust': 1000, 'porosity': 1} if model == 'TPA': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['porosity'] * scale['porosity'] ] elif model == 'TAD': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['fraction_dust'] * scale['fraction_dust'] ] elif model == 'TPAD': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['fraction_dust'] * scale['fraction_dust'], x0['porosity'] * scale['porosity'], ] elif model == 'TP': x0_list = [x0['thickness'] * scale['thickness'], x0['porosity'] * scale['porosity'], ] else: raise Exception('model options are "TP", "TPA", "TPAD" or "TAD"') # Increase by a factor of 100 to improve numeric accuracy. reflectance = reflectance * 100 reflectance[reflectance < 0] = 0 reflectance[reflectance > 100] = 100 cax = np.logical_and(wavelength > wavelength_min, wavelength < wavelength_max) wavelength_calc = wavelength.copy() index_substrate = refractive_index_glass(wavelength) glass_reflectance_calc = single_interface_reflectance(n0=1.0003, n1=index_substrate, aoi=aoi, polarization='mixed', ) # # Get interpolator for correct # if not aoi == 8: # raise Exception('aoi must be 8 degrees.') thickness_min = 50 thickness_max = 200 porosity_max = 0.499 if model == 'TPA': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, porosity_max * scale['porosity']), ] elif model == 'TAD': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, 1 * scale['fraction_dust']), ] elif model == 'TPAD': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, 1 * scale['fraction_dust']), (0, porosity_max * scale['porosity']), ] elif model == 'TP': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, porosity_max * scale['porosity']), ] def arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity): index_film = refractive_index_porous_silica(wavelength=wavelength, porosity=porosity) index_substrate = refractive_index_glass(wavelength=wavelength) thin_film_R = thin_film_reflectance( index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, wavelength=wavelength, aoi=aoi ) # # thin_film_R = thin_film_reflection_fast( # wavelength=wavelength, # thickness=thickness, # aoi=aoi, # porosity=porosity) # # index_film = index_porous_silica(wavelength=wavelength, # porosity=porosity) # thin_film_reflectance = thin_film_reflection( # polarization='mixed', # wavelength=wavelength, # d_list=[np.inf, thickness, np.inf], # index_film=index_film, # index_substrate=index_glass, # aoi=aoi) glass_reflectance = np.interp(wavelength, wavelength_calc, glass_reflectance_calc) reflectance = (1 - fraction_dust) * ( fraction_abraded * glass_reflectance + ( 1 - fraction_abraded) * thin_film_R) + fraction_dust return 100 * reflectance def arc_coating_error_function(x): # print('x: ',x) if model == 'TPA': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] porosity = x[2] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded=fraction_abraded, fraction_dust=fixed[ 'fraction_dust'], porosity=porosity) elif model == 'TAD': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] fraction_dust = x[2] / scale['fraction_dust'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity=fixed['porosity']) # if verbose: # print( # 'Thickness: {:03.2f}, Fraction Abraded: {:.1%}, Fraction dust: {:.1%}'.format( # thickness, # fraction_abraded, # fraction_dust)) elif model == 'TPAD': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] fraction_dust = x[2] / scale['fraction_dust'] porosity = x[3] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity) elif model == 'TP': thickness = x[0] / scale['thickness'] porosity = x[1] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded=fixed[ 'fraction_abraded'], fraction_dust=fixed[ 'fraction_dust'], porosity=porosity) else: raise Exception('model type unknown') residual = np.mean( np.sqrt(np.abs(reflectance_model - reflectance) ** 2)) return residual if method == 'minimize': res = minimize(arc_coating_error_function, x0=x0_list, options=dict( # maxiter=100, disp=verbose ), bounds=bounds ) elif method == 'basinhopping': def basinhopping_callback(x, f, accept): if model == 'TPA' and verbose: # print('x:', x) print( '--\nThickness: {:03.2f}, Fraction Abraded: {:.1%}, Porosity: {:.1%}'.format( x[0] / scale['thickness'], x[1] / scale['fraction_abraded'], x[2] / scale['porosity'] ) ) res = basinhopping(arc_coating_error_function, x0=x0_list, niter=niter, minimizer_kwargs={'bounds': bounds}, disp=verbose, callback=basinhopping_callback ) if model == 'TPA': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': fixed['fraction_dust'], 'porosity': res['x'][2] / scale['porosity']} elif model == 'TAD': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': res['x'][2] / scale['fraction_dust'], 'porosity': fixed['porosity']} elif model == 'TPAD': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': res['x'][2] / scale['fraction_dust'], 'porosity': res['x'][3] / scale['porosity'], } elif model == 'TP': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': fixed['fraction_abraded'], 'fraction_dust': fixed['fraction_dust'], 'porosity': res['x'][1] / scale['porosity'], } return result, res
x, ret = fit_arc_reflection_spectrum(wavelength, reflection, model='TPA', aoi=8, wavelength_min=450, wavelength_max=1000, method='basinhopping', verbose=True) wavelength_extend = np.linspace(300, 1250, 1000) reflection_fit = arc_reflection_model(wavelength_extend, **x) # Calculate solar weighted photon reflection (SWPR) using fit swpr = solar_weighted_photon_reflectance(wavelength_extend, reflection_fit) # Calculate SWPR for glass reference index_glass = refractive_index_glass(wavelength_extend) reflection_BK7 = single_interface_reflectance(n0=1.0003, n1=index_glass, aoi=8) swpr_bk7 = solar_weighted_photon_reflectance(wavelength_extend, reflection_BK7) # Calculate power enhancement due to coating. power_enchancement = swpr_bk7 - swpr # Compare fit vs simulated value. print('--\nComparison of true values vs. best fit') for p in ['thickness', 'porosity', 'fraction_abraded']: print('{}.\t True: {:.2f}, Fit: {:.2f}, '.format(p, param_true[p], x[p])) # Plot theory. plt.plot(wavelength_extend,
def calculate_rgb_vs_thickness_porosity( camera='Ximea-MC050cg_combined_labeled.csv', light_source='LEDW7E_A01_intensity.csv', optical_system='MVL23M23.csv', thickness_max=186, thickness_step=0.4, porosity_max=0.5, porosity_step=0.01, aoi=0): # Wavelength axis wavelength = np.arange(300, 755, 5).astype('float') dwavelength = wavelength[1] - wavelength[0] # Scan thickness and porosity. thickness = np.arange(0, thickness_max, thickness_step).astype('float') porosity = np.arange(0, porosity_max, porosity_step).astype('float') # Initialize arrays. swpr = np.zeros((len(thickness), len(porosity))) rgb_wb = np.zeros((len(thickness), len(porosity), 3)) # Load Camera QE qe_fpath = os.path.join(os.path.dirname(__file__), 'cameras', camera) df = pd.read_csv(qe_fpath, skiprows=2) dfi = pd.DataFrame({'Wavelength': wavelength}) for k in ['Red', 'Green', 'Blue']: dfi[k] = np.interp(wavelength, df['Wavelength'], df[k], left=0, right=0) # Load Illuminant spectrum illum_fpath = os.path.join(os.path.dirname(__file__), 'sources', light_source) df_illum = pd.read_csv(illum_fpath, skiprows=2) # illuminant_sd = ILLUMINANTS_SDS[sources[s]] # illuminant_conv = ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][illuminant_name] illuminant_spectrum = np.interp(wavelength, df_illum['Wavelength'], df_illum['Intensity'], left=0, right=0) illuminant_spectrum_photon = illuminant_spectrum * wavelength # Load optical system optical_system_fpath = os.path.join(os.path.dirname(__file__), 'cameras', optical_system) df_optical_system = pd.read_csv(optical_system_fpath, skiprows=2) optical_system_transmission = 0.01 * np.interp( wavelength, df_optical_system['Wavelength'], df_optical_system['Transmission'], left=0, right=0) # Calculate RGB colors for k in tqdm(range(len(porosity))): index_film = refractive_index_porous_silica(wavelength, porosity[k]) index_substrate = refractive_index_glass(wavelength) for j in range(len(thickness)): # Calculate reflectance reflectance = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate, film_thickness=thickness[j], aoi=aoi, wavelength=wavelength) # for c in ['Blue', 'Green', 'Red']: # np.sum(reflectance * illuminant_spectrum_photon * dfi[c] / 100) * dwavelength rgb = np.sum( reflectance[:, np.newaxis] * \ optical_system_transmission[:, np.newaxis] * \ illuminant_spectrum_photon[:, np.newaxis] * \ np.array(dfi.iloc[:, 1:]) / 100, axis=0) * dwavelength swpr[j, k] = solar_weighted_photon_reflectance(wavelength, reflectance) # Use first run through, with 0 nm thickness, (i.e. low-iron glass) # as a reference for white balance. if thickness[j] == 0: rgb_ref = rgb.copy() # White balance rgb_wb[j, k, :] = rgb / rgb_ref # x = rgb_wb[0, :, :] / np.sum(rgb_wb, axis=0) # y = rgb_wb[1, :, :] / np.sum(rgb_wb, axis=0) # z = rgb_wb[2, :, :] / np.sum(rgb_wb, axis=0) # t_grid, P_grid = np.meshgrid(thickness, porosity, indexing='ij') return thickness, porosity, rgb_wb, swpr
"""Example for printing a list of the refractive index of BK7 glass, useful for performing a light reference in the spectrometer software. """ import numpy as np from pvarc.materials import refractive_index_glass from pvarc import single_interface_reflectance wavelength = np.arange(190, 1125, 10) index_glass = refractive_index_glass(wavelength, type='BK7') reflectance = single_interface_reflectance(n0=1.0003, n1=index_glass, aoi=8.0, polarization='mixed') print('Glass reflectance') for k in range(len(wavelength)): print('{}\t{:.5f}'.format(wavelength[k], reflectance[k]))
# aoi_scan = np.arange(70,-1,-10) # aoi_scan = np.array([0,10,20,30,40,50,60,70]) for porosity in [0.15, 0.3]: plt.figure(0, figsize=(3.7, 3)) plt.clf() ax = plt.axes() rect = ax.patch rect.set_facecolor('k') Rmat = np.zeros((len(wavelength), len(thickness_scan))) index_film = refractive_index_porous_silica(wavelength, porosity) index_film_smooth = refractive_index_porous_silica(wavelength_smooth, porosity) index_substrate = refractive_index_glass(wavelength) index_substrate_smooth = refractive_index_glass(wavelength_smooth) def smoothit(y, N=10): return np.convolve(y, np.ones((N, )) / N, mode='valid') plt.plot(smoothit(source['wavelength'][1:-200]), smoothit(source['value'][1:-200]) / source['value'][1:-5].max() * 5, 'w--', label='LED') for j in range(len(thickness_scan)): # Calculate reflectance at rough. Rmat[:, j] = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate,