def calcTemperatureEquivalent(wavelength,sysresp,tmin,tmax): """Calc the interpolation function between temperature and photon rate radiance Args: | wavelength (np.array): wavelength vector | sysresp (np.array): system response spectral vector | tmin (float): minimum temperature in lookup table | tmax (float): maximum temperature in lookup table Returns: | interpolation function Raises: | No exception is raised. Author: CJ Willers """ wavelength = wavelength.reshape(-1, 1) sysresp = sysresp.reshape(-1, 1) temp = np.linspace(0.99*float(tmin), 1.01*float(tmax), 100).reshape(-1,1) # radiance in q/(s.m2) rad = np.trapz(sysresp * ryplanck.planck(wavelength, temp, type='ql'),wavelength, axis=0).reshape(-1,1) / np.pi fintpLE = interpolate.interp1d(rad.reshape(-1,), temp.reshape(-1,)) fintpEL = interpolate.interp1d(temp.reshape(-1,), rad.reshape(-1,)) return fintpLE,fintpEL
def deeStarPeak(wavelength, temperature, eta, halfApexAngle): i = 0 for wlc in wavelength: wl = np.linspace(wlc / 100, wlc, 1000).reshape(-1, 1) LbackLambda = ryplanck.planck(wl, temperature, type='ql') / np.pi Lback = np.trapz(LbackLambda.reshape(-1, 1), wl, axis=0)[0] Eback = Lback * np.pi * (np.sin(halfApexAngle))**2 # funny construct is to prevent divide by zero tempvar = np.sqrt(eta / (Eback + (Eback == 0))) * (Eback != 0) + 0 * (Eback == 0) dstarwlc[i] = 1e-6 * wlc * tempvar / (const.h * const.c * np.sqrt(2)) #print(Eback) i = i + 1 return dstarwlc * 100. # to get cm units
def calcEffective(ilines, alt, dir, specranges, dffilename): dirname = os.path.join('.', dir, 'elev', '{:.0f}'.format(alt)) lfiles = ryfiles.listFiles(dirname, patterns='tape7') # print(dirname) dfCols = [ 'Atmo', 'Altitude', 'Zenith', 'SpecBand', 'ToaWattTot', 'ToaWatt', 'BoaWattTot', 'BoaWatt', 'LpathWatt', 'ToaQTot', 'ToaQ', 'BoaQTot', 'BoaQ', 'LpathQ', 'effTauSun', 'effTau300' ] #open the excel file, read in, append to the dataframe and later save to the same file if os.path.exists(dffilename): df = pd.read_excel(dffilename) else: df = pd.DataFrame(columns=dfCols) for filename in lfiles: print(filename) elev = float(filename.split('\\')[3]) #read transmittance and path radiance from tape7 dataset = rymodtran.loadtape7( filename, ['FREQ', 'TOT_TRANS', 'TOA_SUN', 'TOTAL_RAD']) dataset[:, 2] *= 1e4 # convert from /cm2 to /m2 dataset[:, 3] *= 1e4 # but only do this once for data set for key in specranges: tape7 = dataset.copy() # select only lines in the spectral ranges select = np.all([ tape7[:, 0] >= 1e4 / specranges[key][1], tape7[:, 0] <= 1e4 / specranges[key][0] ], axis=0) # print(tape7[select]) tape7s = tape7[select] #now integrate the path and TOA radiances. now in W/(m2.sr.cm-1) TOAwatttot = np.trapz(tape7[:, 2], tape7[:, 0]) TOAwatt = np.trapz(tape7s[:, 2], tape7s[:, 0]) BOAwatttot = np.trapz(tape7[:, 1] * tape7[:, 2], tape7[:, 0]) BOAwatt = np.trapz(tape7s[:, 1] * tape7s[:, 2], tape7s[:, 0]) Lpathwatt = np.trapz(tape7s[:, 3], tape7s[:, 0]) # convert radiance terms to photon rates, must to this while it is still spectral # then integrate after conversion conv = tape7[:, 0] * const.h * const.c * 1e2 tape7[:, 2] /= conv tape7[:, 3] /= conv tape7s = tape7[select] #now integrate the path and TOA radiances. now in (q/s)/(m2.sr.cm-1) TOAqtot = np.trapz(tape7[:, 2], tape7[:, 0]) TOAq = np.trapz(tape7s[:, 2], tape7s[:, 0]) BOAqtot = np.trapz(tape7[:, 1] * tape7[:, 2], tape7[:, 0]) BOAq = np.trapz(tape7s[:, 1] * tape7s[:, 2], tape7s[:, 0]) Lpathq = np.trapz(tape7s[:, 3], tape7s[:, 0]) LSun = ryplanck.planck(tape7s[:, 0], 6000., 'en') L300 = ryplanck.planck(tape7s[:, 0], 300., 'en') effTauSun = np.trapz(LSun * tape7s[:, 1], tape7s[:, 0]) / np.trapz( LSun, tape7s[:, 0]) effTau300 = np.trapz(L300 * tape7s[:, 1], tape7s[:, 0]) / np.trapz( L300, tape7s[:, 0]) # print(elev,key, BOAwatttot, BOAwatt, TOAwatttot, TOAwatt, Lpathwatt) # print(elev,key, BOAqtot, BOAq, TOAqtot, TOAq, Lpathq) df = df.append( pd.DataFrame([[ dir, alt, elev, key, TOAwatttot, TOAwatt, BOAwatttot, BOAwatt, Lpathwatt, TOAqtot, TOAq, BOAqtot, BOAq, Lpathq, effTauSun, effTau300 ]], columns=dfCols)) ilines += 1 writer = pd.ExcelWriter(dffilename) df.to_excel(writer, 'Sheet1') pd.DataFrame(specranges).to_excel(writer, 'SpecRanges') writer.save() return (ilines)
absorption = Absorption(wavelength / 1e6, Eg, tempDet, a0, a0p) quantumEffic = QuantumEfficiency(absorption, d1, d2, theta1, n1, n2) responsivity = Responsivity(wavelength / 1e6, quantumEffic) print("\nSource temperature = {0} K".format(tempSource)) print("Source emissivity = {0} ".format(emisSource)) print("Source distance = {0} m".format(distance)) print("Source area = {0} m2".format(areaSource)) print("Source solid angle = {0:.6f} sr".format(solidAngSource)) print("\nBackground temperature = {0} K".format(tempBkg)) print("Background emissivity = {0} ".format(emisBkg)) print("Background solid angle = {0:.6f} sr".format(solidAngBkg)) #spectral irradiance for test setup, for both source and background # in units of photon rate q/(s.m2) EsourceQL =(emisSource * ryplanck.planck(wavelength,tempSource,'ql') \ * solidAngSource) / (np.pi ) EbkgQL = (emisBkg * ryplanck.planck(wavelength, tempBkg, 'ql') * \ solidAngBkg ) / (np.pi ) #in radiant units W/m2 EsourceEL = (emisSource * ryplanck.planck(wavelength, tempSource,'el')*\ solidAngSource) / (np.pi ) EbkgEL = (emisBkg * ryplanck.planck(wavelength, tempBkg, 'el') * \ solidAngBkg) / (np.pi ) #photocurrent from both QE&QL and R&EL spectral data should have same values. iSourceE = np.trapz(EsourceEL * areaDet * responsivity, wavelength) iBkgE = np.trapz(EbkgEL * areaDet * responsivity, wavelength) iSourceQ = np.trapz(EsourceQL * areaDet * quantumEffic * const.e, wavelength) iBkgQ = np.trapz(EbkgQL * areaDet * quantumEffic * const.e, wavelength)
def CalculateDataTables(self): """Calculate the mapping functions between sensor signal, radiance and temperature. Using the spectral curves and DL vs. temperature calibration inputs calculate the various mapping functions between digital level, radiance and temperature. Set up the various tables for later conversion. The TableTempRad table has three columns: (1) temperature, (2) radiance with filter present and (3) radiance without filter. Args: | None. Returns: | None. Side effect of all tables calculated. Raises: | No exception is raised. """ if self.spectralsLoaded: # calculate a high resolution lookup table between temperature and radiance # this is independent from calibration data, no keyTInstr dependence tempHiRes = np.linspace(self.tmprLow, self.tmprHi, 1 + \ (self.tmprHi - self.tmprLow )/self.tmprInc) xx = np.ones(tempHiRes.shape) #first calculate for case with filter present # print(xx.shape, self.specEffWiFil.shape) _, spectrlHR = np.meshgrid(xx,self.specEffWiFil) specLHiRes = spectrlHR * ryplanck.planck(self.nu, tempHiRes, type='en') LHiRes = np.trapz(specLHiRes, x=self.nu, axis=0) / np.pi self.TableTempRad = np.hstack((tempHiRes.reshape(-1,1),LHiRes.reshape(-1,1) )) #first calculate for case with filter not present _, spectrlHR = np.meshgrid(xx,self.specEffNoFil) specLHiRes = spectrlHR * ryplanck.planck(self.nu, tempHiRes, type='en') LHiRes = np.trapz(specLHiRes, x=self.nu, axis=0) / np.pi self.TableTempRad = np.hstack((self.TableTempRad, LHiRes.reshape(-1,1) )) self.hiresTablesCalculated = True # this part is only done if there is calibration data if self.dicCaldata is not None: #set up the DL range self.interpDL = np.linspace(self.sigMin, self.sigMax, 1 + \ (self.sigMax - self.sigMin )/self.sigInc ) # print(np.min(self.interpDL)) # self.TableTempRad = None # self.interpDL = None # self.TableTintRad = None self.dicSpecRadiance = None self.dicCalRadiance = None self.DlRadCoeff = None self.dicTableDLRad = None self.dicinterpDL = None #create containers to store self.dicSpecRadiance = collections.defaultdict(float) self.dicCalRadiance = collections.defaultdict(float) self.DlRadCoeff = collections.defaultdict(float) self.dicinterpDL = collections.defaultdict(float) self.dicTableDLRad = collections.defaultdict(float) # self.dicSpecRadiance.clear() # self.dicCalRadiance.clear() # self.DlRadCoeff.clear() # self.dicinterpDL.clear() # self.dicTableDLRad.clear() #step through all instrument temperatures for tmprInstr in self.dicCaldata: #temperature is in 0'th column of dicCaldata[tmprInstr] #digital level is in 1'st column of dicCaldata[tmprInstr] #radiance is in 2'nd column of dicCaldata[tmprInstr] #planck array has shape (nu,temp), now get spectrals to same shape, then #integrate along nu axis=0 self.dicCaldata[tmprInstr] = self.dicCaldata[tmprInstr].astype(np.float64) xx = np.ones(self.dicCaldata[tmprInstr][:,0].shape) _, spectrl = np.meshgrid(xx,self.specEffWiFil) #now spectrl has the same shape as the planck function return self.dicSpecRadiance[tmprInstr] = \ spectrl * ryplanck.planck(self.nu, self.dicCaldata[tmprInstr][:,0], type='en') self.dicCalRadiance[tmprInstr] = \ np.trapz(self.dicSpecRadiance[tmprInstr], x=self.nu, axis=0) / np.pi #if first time, stack horizontally to the array, otherwise overwrite if self.dicCaldata[tmprInstr].shape[1] < 3: self.dicCaldata[tmprInstr] = np.hstack((self.dicCaldata[tmprInstr], self.dicCalRadiance[tmprInstr].reshape(-1,1))) else: self.dicCaldata[tmprInstr][:,2] = self.dicCalRadiance[tmprInstr].reshape(-1,) # now determine the best fit between radiance and DL # the relationship should be linear y = mx + c # x=digital level, y=radiance coeff = np.polyfit(self.dicCaldata[tmprInstr][:,1], self.dicCalRadiance[tmprInstr].reshape(-1,), deg=1) # print('straight line fit DL = {} L + {}'.format(coeff[0],coeff[1])) self.DlRadCoeff[tmprInstr] = coeff self.interpL = np.polyval(coeff, self.interpDL) #.astype(np.float64) # add the DL floor due to instrument & optics temperature pow = self.dicPower[tmprInstr] self.dicinterpDL[tmprInstr] = \ (self.interpDL ** pow + self.dicFloor[tmprInstr] ** pow) ** (1./pow) #now save a lookup table for tmprInstr value self.dicTableDLRad[tmprInstr] = \ np.hstack(([self.dicinterpDL[tmprInstr].reshape(-1,1), self.interpL.reshape(-1,1)])) # for some strange reason this is necessary to force float64 self.dicTableDLRad[tmprInstr] = self.dicTableDLRad[tmprInstr].astype(np.float64) #calculate the radiance in the optics and sensor for later interpolation of Tinternal tempTint = 273.15 + np.linspace(np.min(self.dicCaldata.keys())-20, np.max(self.dicCaldata.keys())+20, 101) xx = np.ones(tempTint.shape) _, spectrlHR = np.meshgrid(xx,self.specEffWiFil) specLTint = spectrlHR * ryplanck.planck(self.nu, tempTint, type='en') LTint = np.trapz(specLTint, x=self.nu, axis=0) / np.pi self.TableTintRad = np.hstack((tempTint.reshape(-1,1),LTint.reshape(-1,1) )) self.calTablesCalculated = True else: ResetAllContainers()
plotdata = numpy.hstack((plotdata, emis)) plotdata = numpy.hstack((plotdata, sfilter)) plotdata = numpy.hstack((plotdata, tauA)) label = ['Detector', 'Emissivity', 'Filter', 'Atmosphere transmittance'] plot1.plot(1, wavel, plotdata, "Spectral", "Wavelength [$\mu$m]", "Relative magnitude", label=label, maxNX=10, maxNY=10) #check path radiance against Planck's Law for atmo temperature LbbTropical = ryplanck.planck(wavel, 273 + 27, type='el').reshape(-1, 1) / numpy.pi plotdata = LbbTropical plotdata = numpy.hstack((plotdata, lpathwl)) label = ['300 K Planck Law', 'Tropical path radiance'] plot1.plot(2, wavel, plotdata, label=label) currentP = plot1.getSubPlot(2) currentP.set_xlabel('Wavelength [$\mu$m]') currentP.set_ylabel('Radiance [W/(m$^2$.sr.$\mu$m)]') currentP.set_title('Path Radiance') ########################################################## # define sensor scalar parameters opticsArea = 7.8e-3 # optical aperture area [m2] opticsFOV = 1.0e-4 # sensor field of view [sr] transZ = 1.0e4 # amplifier transimpedance gain [V/A]
lut1.PlotTempRadiance(withFilter=False) lut1.PlotCalSpecRadiance() lut1.PlotCalDLTemp() lut1.PlotCalDLRadiance() lut1.PlotCalTempRadiance() lut1.PlotCalTintRad() # print('This table shows radiance with and without filter for given temperature') # print(lut1.TableTempRad) tempr1 = [300., 307.5, 505., 792.5, 800.] rad1 = lut1.LookupTempRad(tempr1) tempr1i = lut1.LookupRadTemp(rad1) print('Input temperature values = {}'.format(tempr1)) print('Lut radiance values = {}'.format(rad1)) print('Lut temperature values = {}'.format(tempr1i)) # now calculate from first principles L1stP1 = np.trapz(ryplanck.planck(nu, tempr1, type='en'), x=nu, axis=0) / np.pi print('First principles radiance values = {}'.format(L1stP1)) print('\n-------------------------------------------------------') print('-------------------------------------------------------') print('-------------------------------------------------------') print('-------------------------------------------------------') print('-------------------------------------------------------') # second test has non-unity spectrals, no camera cal data lut2 = RadLookup('lut2', nu, tmprMin,tmprMax,tmprInc, 'data/LWIRsensor.txt', 'data/LW100mmLens.txt', 'data/LWND10.txt', 'data/Unity.txt', 'data/Unity.txt') print(lut2.Info()) if doPlots: lut2.PlotSpectrals() lut2.PlotTempRadiance(withFilter=True)
#plot the data plot1= ryplot.Plotter(1, 2, 2,'Flame sensor',figsize=(24,16)) plotdata = detR plotdata = numpy.hstack((plotdata,emis)) plotdata = numpy.hstack((plotdata,sfilter)) plotdata = numpy.hstack((plotdata,tauA)) label = ['Detector','Emissivity','Filter','Atmosphere transmittance'] plot1.plot(1, wavel, plotdata, "Spectral","Wavelength [$\mu$m]", "Relative magnitude", label = label, maxNX=10, maxNY=10) #check path radiance against Planck's Law for atmo temperature LbbTropical = ryplanck.planck(wavel, 273+27, type='el').reshape(-1, 1)/numpy.pi plotdata = LbbTropical plotdata = numpy.hstack((plotdata,lpathwl)) label=['300 K Planck Law','Tropical path radiance'] plot1.plot(2, wavel, plotdata, label = label) currentP = plot1.getSubPlot(2) currentP.set_xlabel('Wavelength [$\mu$m]') currentP.set_ylabel('Radiance [W/(m$^2$.sr.$\mu$m)]') currentP.set_title('Path Radiance') ########################################################## # define sensor scalar parameters opticsArea=7.8e-3 # optical aperture area [m2] opticsFOV=1.0e-4 # sensor field of view [sr] transZ=1.0e4 # amplifier transimpedance gain [V/A]
def lllPhotonrates(self, specranges=None ): """Calculate the approximate photon rate radiance for low light conditions The colour temperature of various sources are used to predict the photon flux. The calculation uses the colour temperature of the source and the ratio of real low light luminance to the luminance of a Planck radiator at the same temperature as the source colour temperature. This procedure critically depends on the sources' spectral radiance in the various different spectral bands. For this calculation the approach is taken that for natural scenes the spectral shape can be modelled by a Planck curve at the appropriate colour temperature. The steps followed are as follows: 1. Calculate the photon rate for the scene at the appropriate colour temperature, spectrally weighted by the eye's luminous efficiency response. Do this for photopic and scotopic vision. 2. Weigh the photopic and scotopic photon rates according to illumination level 3. Determine the ratio k of low light level scene illumination to photon irradiance. This factor k is calculated in the visual band, but then applied to scale the other spectral bands by the same scale. 4. Use Planck radiation at the appropriate colour temperature to calculate the radiance in any spectral band, but then scale the value with the factor k. The specranges format is as follows:: numpts = 300 specranges = { key: [wavelength vector, response vector ], 'VIS': [np.linspace(0.43,0.69,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'NIR': [np.linspace(0.7, 0.9,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'SWIR': [np.linspace(1.0, 1.7,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'MWIR': [np.linspace(3.6,4.9,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'LWIR': [np.linspace(7.5,10,numpts).reshape(-1,1),np.ones((numpts,1)) ], } If specranges is None, the predefined values are used, as shown above. The function returns scene radiance in a Pandas datatable with the following columns:: u'Irradiance-lm/m2', u'ColourTemp', u'FracPhotop', u'k', u'Radiance-q/(s.m2.sr)-NIR', u'Radiance-q/(s.m2.sr)-VIS', u'Radiance-q/(s.m2.sr)-MWIR', u'Radiance-q/(s.m2.sr)-LWIR', u'Radiance-q/(s.m2.sr)-SWIR' and rows with the following index:: u'Overcast night', u'Star light', u'Quarter moon', u'Full moon', u'Deep twilight', u'Twilight', u'Very dark day', u'Overcast day', u'Full sky light', u'Sun light' Args: | specranges (dictionary): User-supplied dictionary defining the spectral | responses. See the dictionary format above and an example in the code. Returns: | Pandas dataframe with radiance in the specified spectral bands. Raises: | No exception is raised. """ self.dfPhotRates = pd.DataFrame(self.lllux).transpose() self.dfPhotRates.columns = ['Irradiance-lm/m2','ColourTemp','FracPhotop'] self.dfPhotRates.sort_values(by='Irradiance-lm/m2',inplace=True) wl = np.linspace(0.3, 0.8, 100) photLumEff,wl = ryutils.luminousEfficiency(vlamtype='photopic', wavelen=wl) scotLumEff,wl = ryutils.luminousEfficiency(vlamtype='scotopic', wavelen=wl) self.dfPhotRates['k'] = (self.dfPhotRates['Irradiance-lm/m2']) / (\ self.dfPhotRates['FracPhotop'] * 683 * np.trapz(photLumEff.reshape(-1,1) * \ ryplanck.planckel(wl, self.dfPhotRates['ColourTemp']),wl, axis=0)\ + \ (1-self.dfPhotRates['FracPhotop']) * 1700 * np.trapz(scotLumEff.reshape(-1,1) * \ ryplanck.planckel(wl, self.dfPhotRates['ColourTemp']),wl, axis=0)) \ if specranges is None: numpts = 300 specranges = { 'VIS': [np.linspace(0.43,0.69,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'NIR': [np.linspace(0.7, 0.9,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'SWIR': [np.linspace(1.0, 1.7,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'MWIR': [np.linspace(3.6,4.9,numpts).reshape(-1,1),np.ones((numpts,1)) ], 'LWIR': [np.linspace(7.5,10,numpts).reshape(-1,1),np.ones((numpts,1)) ], } for specrange in specranges.keys(): wlsr = specranges[specrange][0] self.dfPhotRates['Radiance-q/(s.m2.sr)-{}'.format(specrange)] = (self.dfPhotRates['k'] /np.pi ) * \ np.trapz(specranges[specrange][1] * ryplanck.planck(wlsr, self.dfPhotRates['ColourTemp'],'ql'),wlsr, axis=0) self.dfPhotRates.sort_values(by='Irradiance-lm/m2',inplace=True) return self.dfPhotRates
# if the spectral domain conversions are correct, all following six statements should print unity vectors print('all following six statements should print unity vectors:') print(convertSpectralDomain(frequenRef, 'fl')/wavelenRef) print(convertSpectralDomain(wavenumRef, 'nl')/wavelenRef) print(convertSpectralDomain(frequenRef, 'fn')/wavenumRef) print(convertSpectralDomain(wavelenRef, 'ln')/wavenumRef) print(convertSpectralDomain(wavelenRef, 'lf')/frequenRef) print(convertSpectralDomain(wavenumRef, 'nf')/frequenRef) print('test illegal input type should have shape (0,0)') print(convertSpectralDomain(wavenumRef, 'ng').shape) print(convertSpectralDomain(wavenumRef, '').shape) print(convertSpectralDomain(wavenumRef).shape) # now test conversion of spectral density quantities #create planck spectral densities at the wavelength interval exitancewRef = ryplanck.planck(wavelenRef, 1000,'el') exitancefRef = ryplanck.planck(frequenRef, 1000,'ef') exitancenRef = ryplanck.planck(wavenumRef, 1000,'en') exitance = exitancewRef.copy() #convert to frequency density print('all following eight statements should print (close to) unity vectors:') (freq, exitance) = convertSpectralDensity(wavelenRef, exitance, 'lf') print('exitance converted: wf against calculation') print(exitancefRef/exitance) #convert to wavenumber density (waven, exitance) = convertSpectralDensity(freq, exitance, 'fn') print('exitance converted: wf->fn against calculation') print(exitancenRef/exitance) #convert to wavelength density (wavel, exitance) = convertSpectralDensity(waven, exitance, 'nl') #now repeat in opposite sense
# if the spectral domain conversions are correct, all following six statements should print unity vectors print('all following six statements should print unity vectors:') print(convertSpectralDomain(frequenRef, 'fl')/wavelenRef) print(convertSpectralDomain(wavenumRef, 'nl')/wavelenRef) print(convertSpectralDomain(frequenRef, 'fn')/wavenumRef) print(convertSpectralDomain(wavelenRef, 'ln')/wavenumRef) print(convertSpectralDomain(wavelenRef, 'lf')/frequenRef) print(convertSpectralDomain(wavenumRef, 'nf')/frequenRef) print('test illegal input type should have shape (0,0)') print(convertSpectralDomain(wavenumRef, 'ng').shape) print(convertSpectralDomain(wavenumRef, '').shape) print(convertSpectralDomain(wavenumRef).shape) # now test conversion of spectral density quantities #create planck spectral densities at the wavelength interval emittancewRef = ryplanck.planck(wavelenRef, 1000,'el') emittancefRef = ryplanck.planck(frequenRef, 1000,'ef') emittancenRef = ryplanck.planck(wavenumRef, 1000,'en') emittance = emittancewRef.copy() #convert to frequency density print('all following eight statements should print (close to) unity vectors:') (freq, emittance) = convertSpectralDensity(wavelenRef, emittance, 'lf') print('emittance converted: wf against calculation') print(emittancefRef/emittance) #convert to wavenumber density (waven, emittance) = convertSpectralDensity(freq, emittance, 'fn') print('emittance converted: wf->fn against calculation') print(emittancenRef/emittance) #convert to wavelength density (wavel, emittance) = convertSpectralDensity(waven, emittance, 'nl') #now repeat in opposite sense