[docs]classBrdfMeasurementPoint:""" class of a single brdf point measure: brdf = f(wavelength, incidence, theta) no phi dependency for measured brdf """def__init__(self,input_incidence,input_wavelength,input_theta,input_brdf):self.incidence=input_incidenceself.wavelength=input_wavelengthself.theta=input_thetaself.brdf=input_brdf
[docs]classBrdfStructure:""" class of BRDF contains method to host and convert 2d reflectance brdf values into 3d brdf file. """def__init__(self,wavelength_list):wavelength_list=sorted(set(wavelength_list))ifnotall(360<item<830foriteminwavelength_list):raiseValueError("Please provide correct wavelengths in range between 360 and 830nm ")self.__wavelengths=wavelength_listself.__incident_angles=[]self.__theta_1d_ressampled=Noneself.__phi_1d_ressampled=Noneself.measurement_2d_brdf=[]self.brdf=[]self.reflectance=[]self.file_name="export_brdfstruct"def__valid_dir(self,str_path):""" Check if a folder is present and, if not, create it. Parameters ---------- str_path : str Path for the folder to validate or create. For example, ``r"C:\\temp\"``. Returns ------- None """ifnotos.path.isdir(str_path):os.makedirs(str_path)
[docs]defbrdf_1d_function(self,wavelength,incident):""" to provide a 1d linear fitting function for 2d measurement brdf points. Parameters ---------- wavelength : float wavelength used to find the target brdf values. incident : float incident used to find the target brdf values. Returns ------- """theta_brdf=[[MeasurePoint.theta,MeasurePoint.brdf]forMeasurePointinself.measurement_2d_brdfif(MeasurePoint.incidence==incidentandMeasurePoint.wavelength==wavelength)]theta_brdf=np.array(theta_brdf).Treturninterpolate.interp1d(theta_brdf[0],theta_brdf[1],fill_value="extrapolate"),np.max(theta_brdf[0])
def__brdf_reflectance(self,theta,phi,brdf):""" function to calculate the reflectance of the brdf at one incident and wavelength Parameters ---------- theta : np.array phi : np.array brdf : 2d brdf function Returns ------- float brdf reflectance value. """theta_rad,phi_rad=np.radians(theta),np.radians(phi)# samples on which integrande is knownintegrande=(1/math.pi)*brdf*np.sin(theta_rad)# *theta for polar integrationf=interpolate.interp2d(theta_rad,phi_rad,integrande,kind="linear",bounds_error=False,fill_value=0)# calculation of integrande as from from samplesr=nquad(f,[[0,math.pi/2],[0,2*math.pi]],opts=[{"epsabs":0.1},{"epsabs":0.1}])# reflectance calculaiton thanks to nquad libreturnmin(r[0]*100,100)# return reflectance as percentage
[docs]defconvert(self,sampling=1):""" convert the provided 2d measurement brdf data. Parameters ---------- sampling : int sampling used for exported 4d brdf structure. Returns ------- None """defbrdf_2d_function(theta,phi):""" an internal method to calculate the 2d brdf based on location theta and phi. Parameters ---------- theta : float target point with theta value phi : float target point with phi value Returns ------- float value of brdf value """# function that calculated 2d brdf from 1d using revolution assumption.angular_distance=np.sqrt((incidence-theta*np.cos(np.radians(phi)))**2+(theta*np.sin(np.radians(phi)))**2)# +4l for interpolation between measurement value on both side from specular directionangular_distance=np.where(angular_distance<EPSILON,1,angular_distance)# special case for specular point (no interpolation due to null distance)weight=(incidence+angular_distance-theta*np.cos(np.radians(phi)))/(2*angular_distance)weight=np.where(incidence+angular_distance>theta_max,1,weight)# theta max : maximal reflected angle that is measured. for angle > theta_max we do not have valuesreturnweight*(brdf_1d(incidence-angular_distance))+(1-weight)*brdf_1d(incidence+angular_distance)iflen(self.__incident_angles)==0:formeasurementinself.measurement_2d_brdf:ifmeasurement.incidencenotinself.__incident_angles:self.__incident_angles.append(measurement.incidence)# mesh grid for direct 2d matrix calculationforincidenceinself.__incident_angles:forwavelengthinself.__wavelengths:brdf_1d,theta_max=self.brdf_1d_function(wavelength,incidence)self.__theta_1d_ressampled=np.linspace(0,90,int(90/sampling+1))self.__phi_1d_ressampled=np.linspace(0,360,int(360/sampling+1))theta_2d_ressampled,phi_2d_ressampled=np.meshgrid(self.__theta_1d_ressampled,self.__phi_1d_ressampled)self.brdf.append(brdf_2d_function(theta_2d_ressampled,phi_2d_ressampled))self.reflectance.append(self.__brdf_reflectance(self.__theta_1d_ressampled,self.__phi_1d_ressampled,self.brdf[-1]))self.brdf=np.reshape(self.brdf,(len(self.__incident_angles),len(self.__wavelengths),2*int(180/sampling+1)-1,int(90/sampling+1),),)ifnp.all(self.brdf==0):msg="All NULL values at brdf structure, please provide valid inputs"raiseValueError(msg)self.brdf=np.moveaxis(self.brdf,2,3)self.reflectance=np.reshape(self.reflectance,(len(self.__incident_angles),len(self.__wavelengths)))
[docs]defexport_to_speos(self,export_dir):""" save the 4d brdf structure into a brdf file. Parameters ---------- export_dir : str directory to be exported Returns ------- """self.__valid_dir(export_dir)file_name=os.path.join(export_dir,self.file_name+".brdf")export=open(file_name,"w")export.write("OPTIS - brdf surface file v9.0\n")# Header.export.write("0\n")# Defines the mode: 0 for text mode, 1 for binary mode.export.write("Scattering surface\n")# Comment lineexport.write("0\n\n")# Number of characters to read for the measurement description.export.write("1\t0\n")# Contains two boolean values (0=false and 1=true)# The first bReflection tells whether the surface has reflection data or not.# The second bTransmission tells whether the surface has transmission data or not.export.write("1\n")# Contains a boolean value describing the type of value stored in the file:# 1 means the data is proportional to the BSDF.# 0 means the data is proportional to the measured intensity or to the probability density function.# Write BRDF sampling dataexport.write("%d\t%d\n"%(self.brdf.shape[0],self.brdf.shape[1]))# Number of incident angles and Number of wavelength samples (in nanometer)np.savetxt(export,[self.__incident_angles],format("%.3f"),footer="\n",newline="",delimiter="\t",comments="")# List of the incident angles.np.savetxt(export,[self.__wavelengths],format("%.1f"),footer="\n",newline="",delimiter="\t",comments="")# List of wavelength# Write BRDF tablesforincidenceinrange(len(self.__incident_angles)):forwavelengthinrange(len(self.__wavelengths)):export.write("%f\n"%self.reflectance[incidence,wavelength])# reflectanceexport.write("%d\t%d\n"%np.shape(self.brdf)[2:])np.savetxt(export,[self.__phi_1d_ressampled],format("%.3f"),footer="\n",newline="",delimiter="\t",comments="",header="\t",)# phi headerdata_with_theta=np.concatenate((np.array([self.__theta_1d_ressampled]).T,self.brdf[incidence,wavelength,:,:]),axis=1)# add theta headernp.savetxt(export,data_with_theta,format("%.3e"),delimiter="\t")# 2d matrix is directly save thnaks to np.savetxt libexport.close()