def __init__(self): QMainWindow.__init__(self) self.settings = QSettings("greyltc", "batch-iv-analysis") self.rows = 0 #keep track of how many rows there are in the table self.cols = OrderedDict() thisKey = 'plotBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Draw Plot' self.cols[thisKey].tooltip = 'Click this button to draw a plot for that row' thisKey = 'exportBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Export' self.cols[thisKey].tooltip = 'Click this button to export\ninterpolated data points from fits' thisKey = 'file' self.cols[thisKey] = col() self.cols[thisKey].header = 'File' self.cols[thisKey].tooltip = 'File name\nHover to see header from data file' thisKey = 'pce' self.cols[thisKey] = col() self.cols[thisKey].header = 'PCE\n[%]' self.cols[thisKey].tooltip = 'Power conversion efficiency as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'pmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW/cm^2]' self.cols[thisKey].tooltip = 'Maximum power density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'jsc' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_sc\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Short-circuit current density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'voc' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_oc\n[mV]' self.cols[thisKey].tooltip = 'Open-circuit voltage as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'ff' self.cols[thisKey] = col() self.cols[thisKey].header = 'FF' self.cols[thisKey].tooltip = 'Fill factor as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'rs' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'jph' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_ph\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Photogenerated current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'j0' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_0\n[nA/cm^2]' self.cols[thisKey].tooltip = 'Reverse saturation current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'n' self.cols[thisKey] = col() self.cols[thisKey].header = 'n' self.cols[thisKey].tooltip = 'Diode ideality factor as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'Vmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_max\n[mV]' self.cols[thisKey].tooltip = 'Voltage at maximum power point as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'area' self.cols[thisKey] = col() self.cols[thisKey].header = 'Area\n[cm^2]' self.cols[thisKey].tooltip = 'Device area' thisKey = 'pmax2' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW]' self.cols[thisKey].tooltip = 'Maximum power as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'isc' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_sc\n[mA]' self.cols[thisKey].tooltip = 'Short-circuit current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'iph' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_ph\n[mA]' self.cols[thisKey].tooltip = 'Photogenerated current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'i0' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_0\n[nA]' self.cols[thisKey].tooltip = 'Reverse saturation current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rs2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm]' self.cols[thisKey].tooltip = 'Series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm]' self.cols[thisKey].tooltip = 'Shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' #how long status messages show for self.messageDuration = 2500#ms # Set up the user interface from Designer. self.ui = Ui_batch_iv_analysis() self.ui.setupUi(self) #insert cols for item in self.cols: blankItem = QTableWidgetItem() thisCol = self.cols.keys().index(item) self.ui.tableWidget.insertColumn(thisCol) blankItem.setToolTip(self.cols[item].tooltip) blankItem.setText(self.cols[item].header) self.ui.tableWidget.setHorizontalHeaderItem(thisCol,blankItem) #file system watcher self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.handleWatchUpdate) #connect signals generated by gui elements to proper functions self.ui.actionOpen.triggered.connect(self.openCall) self.ui.actionEnable_Watching.triggered.connect(self.watchCall) self.ui.actionSave.triggered.connect(self.handleSave) self.ui.actionWatch_2.triggered.connect(self.handleWatchAction) self.ui.actionClear_Table.triggered.connect(self.clearTableCall)
class MainWindow(QMainWindow): workingDirectory = '' fileNames = [] supportedExtensions = QStringList(('*.txt','*.csv')) def __init__(self): QMainWindow.__init__(self) self.settings = QSettings("greyltc", "batch-iv-analysis") self.rows = 0 #keep track of how many rows there are in the table self.cols = OrderedDict() thisKey = 'plotBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Draw Plot' self.cols[thisKey].tooltip = 'Click this button to draw a plot for that row' thisKey = 'exportBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Export' self.cols[thisKey].tooltip = 'Click this button to export\ninterpolated data points from fits' thisKey = 'file' self.cols[thisKey] = col() self.cols[thisKey].header = 'File' self.cols[thisKey].tooltip = 'File name\nHover to see header from data file' thisKey = 'pce' self.cols[thisKey] = col() self.cols[thisKey].header = 'PCE\n[%]' self.cols[thisKey].tooltip = 'Power conversion efficiency as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'pmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW/cm^2]' self.cols[thisKey].tooltip = 'Maximum power density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'jsc' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_sc\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Short-circuit current density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'voc' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_oc\n[mV]' self.cols[thisKey].tooltip = 'Open-circuit voltage as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'ff' self.cols[thisKey] = col() self.cols[thisKey].header = 'FF' self.cols[thisKey].tooltip = 'Fill factor as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'rs' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'jph' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_ph\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Photogenerated current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'j0' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_0\n[nA/cm^2]' self.cols[thisKey].tooltip = 'Reverse saturation current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'n' self.cols[thisKey] = col() self.cols[thisKey].header = 'n' self.cols[thisKey].tooltip = 'Diode ideality factor as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'Vmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_max\n[mV]' self.cols[thisKey].tooltip = 'Voltage at maximum power point as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'area' self.cols[thisKey] = col() self.cols[thisKey].header = 'Area\n[cm^2]' self.cols[thisKey].tooltip = 'Device area' thisKey = 'pmax2' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW]' self.cols[thisKey].tooltip = 'Maximum power as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'isc' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_sc\n[mA]' self.cols[thisKey].tooltip = 'Short-circuit current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'iph' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_ph\n[mA]' self.cols[thisKey].tooltip = 'Photogenerated current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'i0' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_0\n[nA]' self.cols[thisKey].tooltip = 'Reverse saturation current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rs2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm]' self.cols[thisKey].tooltip = 'Series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm]' self.cols[thisKey].tooltip = 'Shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' #how long status messages show for self.messageDuration = 2500#ms # Set up the user interface from Designer. self.ui = Ui_batch_iv_analysis() self.ui.setupUi(self) #insert cols for item in self.cols: blankItem = QTableWidgetItem() thisCol = self.cols.keys().index(item) self.ui.tableWidget.insertColumn(thisCol) blankItem.setToolTip(self.cols[item].tooltip) blankItem.setText(self.cols[item].header) self.ui.tableWidget.setHorizontalHeaderItem(thisCol,blankItem) #file system watcher self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.handleWatchUpdate) #connect signals generated by gui elements to proper functions self.ui.actionOpen.triggered.connect(self.openCall) self.ui.actionEnable_Watching.triggered.connect(self.watchCall) self.ui.actionSave.triggered.connect(self.handleSave) self.ui.actionWatch_2.triggered.connect(self.handleWatchAction) self.ui.actionClear_Table.triggered.connect(self.clearTableCall) def exportInterp(self,row): thisGraphData = self.ui.tableWidget.item(row,self.cols.keys().index('plotBtn')).data(Qt.UserRole).toPyObject() fitX = thisGraphData[QString(u'fitX')] modelY = thisGraphData[QString(u'modelY')] splineY = thisGraphData[QString(u'splineY')] a = np.asarray([fitX, modelY, splineY]) a = np.transpose(a) destinationFolder = os.path.join(self.workingDirectory,'exports') QDestinationFolder = QDir(destinationFolder) if not QDestinationFolder.exists(): QDir().mkdir(destinationFolder) saveFile = os.path.join(destinationFolder,str(self.ui.tableWidget.item(row,self.cols.keys().index('file')).text())+'.csv') header = 'Voltage [V],CharEqn Current [mA/cm^2],Spline Current [mA/cm^2]' try: np.savetxt(saveFile, a, delimiter=",",header=header) self.ui.statusbar.showMessage("Exported " + saveFile,5000) except: self.ui.statusbar.showMessage("Could not export " + saveFile,self.messageDuration) def handleButton(self): btn = self.sender() #kinda hacky: row = self.ui.tableWidget.indexAt(btn.pos()).row() col = self.ui.tableWidget.indexAt(btn.pos()).column() if col == 0: self.rowGraph(row) if col == 1: self.exportInterp(row) def rowGraph(self,row): thisGraphData = self.ui.tableWidget.item(row,self.cols.keys().index('plotBtn')).data(Qt.UserRole).toPyObject() filename = str(self.ui.tableWidget.item(row,self.cols.keys().index('file')).text()) v = thisGraphData[QString(u'v')] i = thisGraphData[QString(u'i')] if not thisGraphData[QString(u'vsTime')]: plt.plot(v, i, c='b', marker='o', ls="None",label='I-V Data') plt.scatter(thisGraphData[QString(u'Vmax')], thisGraphData[QString(u'Imax')], c='g',marker='x',s=100) plt.scatter(thisGraphData[QString(u'Voc')], 0, c='g',marker='x',s=100) plt.scatter(0, thisGraphData[QString(u'Isc')], c='g',marker='x',s=100) fitX = thisGraphData[QString(u'fitX')] modelY = thisGraphData[QString(u'modelY')] splineY = thisGraphData[QString(u'splineY')] if not np.isnan(modelY[0]): plt.plot(fitX, modelY,c='k', label='CharEqn Best Fit') plt.plot(fitX, splineY,c='g', label='Spline Fit') plt.autoscale(axis='x', tight=True) plt.grid(b=True) ax = plt.gca() handles, labels = ax.get_legend_handles_labels() ax.legend(handles, labels, loc=3) plt.annotate( thisGraphData[QString(u'Voc')].__format__('0.4f')+ ' V', xy = (thisGraphData[QString(u'Voc')], 0), xytext = (40, 20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.annotate( float(thisGraphData[QString(u'Isc')]).__format__('0.4f') + ' mA/cm^2', xy = (0,thisGraphData[QString(u'Isc')]), xytext = (40, 20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.annotate( float(thisGraphData[QString(u'Imax')]*thisGraphData[QString(u'Vmax')]).__format__('0.4f') + '% @(' + float(thisGraphData[QString(u'Vmax')]).__format__('0.4f') + ',' + float(thisGraphData[QString(u'Imax')]).__format__('0.4f') + ')', xy = (thisGraphData[QString(u'Vmax')],thisGraphData[QString(u'Imax')]), xytext = (80, 40), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.ylabel('Current [mA/cm^2]') plt.xlabel('Voltage [V]') else: #vs time time = thisGraphData[QString(u'time')] fig, ax1 = plt.subplots() ax1.plot(time, v, 'b-',label='Voltage [V]') ax1.set_xlabel('Time [s]') # Make the y-axis label and tick labels match the line color. ax1.set_ylabel('Voltage [V]', color='b') for tl in ax1.get_yticklabels(): tl.set_color('b') #fdsf ax2 = ax1.twinx() ax2.plot(time, i, 'r-') ax2.set_ylabel('Current [mA/cm^2]', color='r') for tl in ax2.get_yticklabels(): tl.set_color('r') plt.title(filename) plt.draw() plt.show() def handleSave(self): if self.settings.contains('lastFolder'): saveDir = self.settings.value('lastFolder').toString() else: saveDir = '.' path = QFileDialog.getSaveFileName(self, caption='Save File', directory=saveDir) if not str(path[0]) == '': with open(unicode(path), 'wb') as stream: writer = csv.writer(stream) rowdata = [] for column in range(self.ui.tableWidget.columnCount()): item = self.ui.tableWidget.horizontalHeaderItem(column) if item is not None: rowdata.append(unicode(item.text()).encode('utf8').replace('\n',' ')) else: rowdata.append('') writer.writerow(rowdata) for row in range(self.ui.tableWidget.rowCount()): rowdata = [] for column in range(self.ui.tableWidget.columnCount()): item = self.ui.tableWidget.item(row, column) if item is not None: rowdata.append(unicode(item.text()).encode('utf8')) else: rowdata.append('') writer.writerow(rowdata) stream.close() def clearTableCall(self): for ii in range(self.rows): self.ui.tableWidget.removeRow(0) self.ui.tableWidget.clearContents() self.rows = 0 self.fileNames = [] def processFile(self,fullPath): fileName, fileExtension = os.path.splitext(fullPath) fileName = os.path.basename(fullPath) self.fileNames.append(fileName) if fileExtension == '.csv': delimiter = ',' else: delimiter = None self.ui.statusbar.showMessage("processing: "+ fileName,2500) #wait here for the file to be completely written to disk and closed before trying to read it fi = QFileInfo(fullPath) while (not fi.isWritable()): time.sleep(0.001) fi.refresh() fp = open(fullPath, mode='r') fileBuffer = fp.read() fp.close() first10 = fileBuffer[0:10] nMcHeaderLines = 25 #number of header lines in mcgehee IV file format isMcFile = False #true if this is a McGehee iv file format if (not first10.__contains__('#')) and (first10.__contains__('/')) and (first10.__contains__('\t')):#the first line is not a comment #the first 8 chars do not contain comment symbol and do contain / and a tab, it's safe to assume mcgehee iv file format isMcFile = True #comment out the first 25 rows here fileBuffer = '#'+fileBuffer fileBuffer = fileBuffer.replace('\n', '\n#',nMcHeaderLines-1) splitBuffer = fileBuffer.splitlines(True) area = 1 noArea = True vsTime = False #this is not an i,v vs t data file #extract header lines and search for area header = [] for line in splitBuffer: if line.startswith('#'): header.append(line) if line.__contains__('Area'): area = float(line.split(' ')[3]) noArea = False if line.__contains__('I&V vs t'): if float(line.split(' ')[5]) == 1: vsTime = True else: break outputScaleFactor = np.array(1000/area) #for converstion to [mA/cm^2] tempFile = QTemporaryFile() tempFile.open() tempFile.writeData(fileBuffer) tempFile.flush() #read in data try: data = np.loadtxt(str(tempFile.fileName()),delimiter=delimiter) VV = data[:,0] II = data[:,1] if vsTime: time = data[:,2] except: self.ui.statusbar.showMessage('Could not read' + fileName +'. Prepend # to all non-data lines and try again',2500) return tempFile.close() tempFile.remove() if isMcFile: #convert to amps II = II/1000*area if not vsTime: #sort data by ascending voltage newOrder = VV.argsort() VV=VV[newOrder] II=II[newOrder] #remove duplicate voltage entries VV, indices = np.unique(VV, return_index =True) II = II[indices] else: #sort data by ascending time newOrder = time.argsort() VV=VV[newOrder] II=II[newOrder] time=time[newOrder] time=time-time[0]#start time at t=0 #catch and fix flipped current sign: if II[0] < II[-1]: II = II * -1 indexInQuad1 = np.logical_and(VV>0,II>0) if any(indexInQuad1): #enters statement if there is at least one datapoint in quadrant 1 isDarkCurve = False else: self.ui.statusbar.showMessage("Dark curve detected",500) isDarkCurve = True #put items in table self.ui.tableWidget.insertRow(self.rows) for ii in range(len(self.cols)): self.ui.tableWidget.setItem(self.rows,ii,QTableWidgetItem()) if not vsTime: fitParams, fitCovariance, infodict, errmsg, ier = self.bestEffortFit(VV,II) #print errmsg I0_fit = fitParams[0] Iph_fit = fitParams[1] Rs_fit = fitParams[2] Rsh_fit = fitParams[3] n_fit = fitParams[4] #0 -> LS-straight line #1 -> cubic spline interpolant smoothingParameter = 1-2e-6 iFitSpline = SmoothSpline(VV, II, p=smoothingParameter) def cellModel(voltageIn): #voltageIn = np.array(voltageIn) return vectorizedCurrent(voltageIn, I0_fit, Iph_fit, Rs_fit, Rsh_fit, n_fit) def invCellPowerSpline(voltageIn): if voltageIn < 0: return 0 else: return -1*voltageIn*iFitSpline(voltageIn) def invCellPowerModel(voltageIn): if voltageIn < 0: return 0 else: return -1*voltageIn*cellModel(voltageIn) if not isDarkCurve: VVq1 = VV[indexInQuad1] IIq1 = II[indexInQuad1] vMaxGuess = VVq1[np.array(VVq1*IIq1).argmax()] powerSearchResults = optimize.minimize(invCellPowerSpline,vMaxGuess) #catch a failed max power search: if not powerSearchResults.status == 0: print "power search exit code = " + str(powerSearchResults.status) print powerSearchResults.message vMax = nan iMax = nan pMax = nan else: vMax = powerSearchResults.x[0] iMax = iFitSpline([vMax])[0] pMax = vMax*iMax #only do this stuff if the char eqn fit was good if ier < 5: powerSearchResults_charEqn = optimize.minimize(invCellPowerModel,vMaxGuess) #catch a failed max power search: if not powerSearchResults_charEqn.status == 0: print "power search exit code = " + str(powerSearchResults_charEqn.status) print powerSearchResults_charEqn.message vMax_charEqn = nan else: vMax_charEqn = powerSearchResults_charEqn.x[0] #dude try: Voc_nn_charEqn=optimize.brentq(cellModel, VV[0], VV[-1]) except: Voc_nn_charEqn = nan else: Voc_nn_charEqn = nan vMax_charEqn = nan try: Voc_nn = optimize.brentq(iFitSpline, VV[0], VV[-1]) except: Voc_nn = nan else: Voc_nn = nan vMax = nan iMax = nan pMax = nan Voc_nn_charEqn = nan vMax_charEqn = nan iMax_charEqn = nan pMax_charEqn = nan if ier < 5: dontFindBounds = False iMax_charEqn = cellModel([vMax_charEqn])[0] pMax_charEqn = vMax_charEqn*iMax_charEqn Isc_nn_charEqn = cellModel(0) FF_charEqn = pMax_charEqn/(Voc_nn_charEqn*Isc_nn_charEqn) else: dontFindBounds = True iMax_charEqn = nan pMax_charEqn = nan Isc_nn_charEqn = nan FF_charEqn = nan #there is a maddening bug in SmoothingSpline: it can't evaluate 0 alone, so I have to do this: try: Isc_nn = iFitSpline([0,1e-55])[0] except: Isc_nn = nan FF = pMax/(Voc_nn*Isc_nn) if (ier != 7) and (ier != 6) and (not dontFindBounds) and (type(fitCovariance) is not float): #error estimation: alpha = 0.05 # 95% confidence interval = 100*(1-alpha) nn = len(VV) # number of data points p = len(fitParams) # number of parameters dof = max(0, nn - p) # number of degrees of freedom # student-t value for the dof and confidence level tval = t.ppf(1.0-alpha/2., dof) lowers = [] uppers = [] #calculate 95% confidence interval for a, p,var in zip(range(nn), fitParams, np.diag(fitCovariance)): sigma = var**0.5 lower = p - sigma*tval upper = p + sigma*tval lowers.append(lower) uppers.append(upper) else: uppers = [nan,nan,nan,nan,nan] lowers = [nan,nan,nan,nan,nan] plotPoints = 1000 fitX = np.linspace(VV[0],VV[-1],plotPoints) if ier < 5: modelY = cellModel(fitX)*outputScaleFactor else: modelY = np.empty(plotPoints)*nan splineY = iFitSpline(fitX)*outputScaleFactor graphData = {'vsTime':vsTime,'origRow':self.rows,'fitX':fitX,'modelY':modelY,'splineY':splineY,'i':II*outputScaleFactor,'v':VV,'Voc':Voc_nn,'Isc':Isc_nn*outputScaleFactor,'Vmax':vMax,'Imax':iMax*outputScaleFactor} #export button exportBtn = QPushButton(self.ui.tableWidget) exportBtn.setText('Export') exportBtn.clicked.connect(self.handleButton) self.ui.tableWidget.setCellWidget(self.rows,self.cols.keys().index('exportBtn'), exportBtn) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pce')).setData(Qt.DisplayRole,round(pMax/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pce')).setToolTip(str(round(pMax_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax')).setData(Qt.DisplayRole,round(pMax/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax')).setToolTip(str(round(pMax_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jsc')).setData(Qt.DisplayRole,round(Isc_nn/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jsc')).setToolTip(str(round(Isc_nn_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('voc')).setData(Qt.DisplayRole,round(Voc_nn*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('voc')).setToolTip(str(round(Voc_nn_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('ff')).setData(Qt.DisplayRole,round(FF,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('ff')).setToolTip(str(round(FF_charEqn,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs')).setData(Qt.DisplayRole,round(Rs_fit*area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs')).setToolTip('[{0} {1}]'.format(lowers[2]*area, uppers[2]*area)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh')).setData(Qt.DisplayRole,round(Rsh_fit*area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh')).setToolTip('[{0} {1}]'.format(lowers[3]*area, uppers[3]*area)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jph')).setData(Qt.DisplayRole,round(Iph_fit/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jph')).setToolTip('[{0} {1}]'.format(lowers[1]/area*1e3, uppers[1]/area*1e3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('j0')).setData(Qt.DisplayRole,round(I0_fit/area*1e9,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('j0')).setToolTip('[{0} {1}]'.format(lowers[0]/area*1e9, uppers[0]/area*1e9)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('n')).setData(Qt.DisplayRole,round(n_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('n')).setToolTip('[{0} {1}]'.format(lowers[4], uppers[4])) self.ui.tableWidget.item(self.rows,self.cols.keys().index('Vmax')).setData(Qt.DisplayRole,round(vMax*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('Vmax')).setToolTip(str(round(vMax_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('area')).setData(Qt.DisplayRole,round(area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax2')).setData(Qt.DisplayRole,round(pMax*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax2')).setToolTip(str(round(pMax_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('isc')).setData(Qt.DisplayRole,round(Isc_nn*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('isc')).setToolTip(str(round(Isc_nn_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('iph')).setData(Qt.DisplayRole,round(Iph_fit*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('iph')).setToolTip('[{0} {1}]'.format(lowers[1]*1e3, uppers[1]*1e3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('i0')).setData(Qt.DisplayRole,round(I0_fit*1e9,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('i0')).setToolTip('[{0} {1}]'.format(lowers[0]*1e9, uppers[0]*1e9)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs2')).setData(Qt.DisplayRole,round(Rs_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs2')).setToolTip('[{0} {1}]'.format(lowers[2], uppers[2])) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh2')).setData(Qt.DisplayRole,round(Rsh_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh2')).setToolTip('[{0} {1}]'.format(lowers[3], uppers[3])) else:#vs time graphData = {'vsTime':vsTime,'origRow':self.rows,'time':time,'i':II*outputScaleFactor,'v':VV} #file name self.ui.tableWidget.item(self.rows,self.cols.keys().index('file')).setText(fileName) self.ui.tableWidget.item(self.rows,self.cols.keys().index('file')).setToolTip(''.join(header)) #plot button plotBtn = QPushButton(self.ui.tableWidget) plotBtn.setText('Plot') plotBtn.clicked.connect(self.handleButton) self.ui.tableWidget.setCellWidget(self.rows,self.cols.keys().index('plotBtn'), plotBtn) self.ui.tableWidget.item(self.rows,self.cols.keys().index('plotBtn')).setData(Qt.UserRole,graphData) self.ui.tableWidget.resizeColumnsToContents() self.rows = self.rows + 1 def bestEffortFit(self,VV,II): #splineTestVV=np.linspace(VV[0],VV[-1],1000) #splineTestII=iFitSpline(splineTestVV) #p1, = plt.plot(splineTestVV,splineTestII) #p3, = plt.plot(VV,II,ls='None',marker='o', label='Data') #plt.draw() #plt.show() #data point selection: #lowest voltage (might be same as Isc) V_start_n = VV[0] I_start_n = II[0] #highest voltage V_end_n = VV[-1] I_end_n = II[-1] #Isc iFit = interpolate.interp1d(VV,II) V_sc_n = 0 try: I_sc_n = float(iFit(V_sc_n)) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) #mpp VVcalc = VV-VV[0] IIcalc = II-min(II) Pvirtual= np.array(VVcalc*IIcalc) vMaxIndex = Pvirtual.argmax() V_vmpp_n = VV[vMaxIndex] I_vmpp_n = II[vMaxIndex] #Vp: half way in voltage between vMpp and the start of the dataset: V_vp_n = (V_vmpp_n-V_start_n)/2 +V_start_n try: I_vp_n = float(iFit(V_vp_n)) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) #Ip: half way in current between vMpp and the end of the dataset: I_ip_n = (I_vmpp_n-I_end_n)/2 + I_end_n iFit2 = interpolate.interp1d(VV,II-I_ip_n) try: V_ip_n = optimize.brentq(iFit2, VV[0], VV[-1]) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) diaplayAllGuesses = False def evaluateGuessPlot(dataX, dataY, myguess): myguess = [float(x) for x in myguess] print "myguess:" print myguess vv=np.linspace(min(dataX),max(dataX),1000) ii=vectorizedCurrent(vv,myguess[0],myguess[1],myguess[2],myguess[3],myguess[4]) plt.title('Guess and raw data') plt.plot(vv,ii) plt.scatter(dataX,dataY) plt.grid(b=True) plt.draw() plt.show() #phase 1 guesses: I_L_initial_guess = I_sc_n R_sh_initial_guess = 1e6 #compute intellegent guesses for Iph, Rsh by forcing the curve through several data points and numerically solving the resulting system of eqns newRhs = rhs - I aLine = Rsh*V+Iph-I eqnSys1 = aLine.subs([(V,V_start_n),(I,I_start_n)]) eqnSys2 = aLine.subs([(V,V_vp_n),(I,I_vp_n)]) eqnSys = (eqnSys1,eqnSys2) try: nGuessSln = sympy.nsolve(eqnSys,(Iph,Rsh),(I_L_initial_guess,R_sh_initial_guess),maxsteps=10000) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) I_L_guess = nGuessSln[0] R_sh_guess = -1*1/nGuessSln[1] R_s_guess = -1*(V_end_n-V_ip_n)/(I_end_n-I_ip_n) n_initial_guess = 2 #TODO: maybe a more intelegant guess for n can be found using http://pvcdrom.pveducation.org/CHARACT/IDEALITY.HTM I0_initial_guess = eyeNot[0].evalf(subs={Vth:thermalVoltage,Rs:R_s_guess,Rsh:R_sh_guess,Iph:I_L_guess,n:n_initial_guess,I:I_ip_n,V:V_ip_n}) initial_guess = [I0_initial_guess, I_L_guess, R_s_guess, R_sh_guess, n_initial_guess] if diaplayAllGuesses: evaluateGuessPlot(VV, II, initial_guess) # let's try the fit now, if it works great, we're done, otherwise we can continue #try: #guess = initial_guess #fitParams, fitCovariance, infodict, errmsg, ier = optimize.curve_fit(optimizeThis, VV, II,p0=guess,full_output = True,xtol=1e-13,ftol=1e-15) #return(fitParams, fitCovariance, infodict, errmsg, ier) #except: #pass #refine guesses for I0 and Rs by forcing the curve through several data points and numerically solving the resulting system of eqns eqnSys1 = newRhs.subs([(Vth,thermalVoltage),(Iph,I_L_guess),(V,V_ip_n),(I,I_ip_n),(n,n_initial_guess),(Rsh,R_sh_guess)]) eqnSys2 = newRhs.subs([(Vth,thermalVoltage),(Iph,I_L_guess),(V,V_end_n),(I,I_end_n),(n,n_initial_guess),(Rsh,R_sh_guess)]) eqnSys = (eqnSys1,eqnSys2) try: nGuessSln = sympy.nsolve(eqnSys,(I0,Rs),(I0_initial_guess,R_s_guess),maxsteps=10000) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) I0_guess = nGuessSln[0] R_s_guess = nGuessSln[1] #Rs_initial_guess = RsEqn[0].evalf(subs={I0:I0_initial_guess,Vth:thermalVoltage,Rsh:R_sh_guess,Iph:I_L_guess,n:n_initial_guess,I:I_end_n,V:V_end_n}) #I0_guess = I0_initial_guess #R_s_guess = Rs_initial_guess guess = [I0_guess, I_L_guess, R_s_guess, R_sh_guess, n_initial_guess] if diaplayAllGuesses: evaluateGuessPlot(VV, II, guess) #nidf #give 5x weight to data around mpp #nP = II*VV #maxIndex = np.argmax(nP) #weights = np.ones(len(II)) #halfRange = (V_ip_n-VV[vMaxIndex])/2 #upperTarget = VV[vMaxIndex] + halfRange #lowerTarget = VV[vMaxIndex] - halfRange #lowerTarget = 0 #upperTarget = V_oc_n #lowerI = np.argmin(abs(VV-lowerTarget)) #upperI = np.argmin(abs(VV-upperTarget)) #weights[range(lowerI,upperI)] = 3 #weights[maxnpi] = 10 #todo: play with setting up "key points" guess = [float(x) for x in guess] #odrMod = odr.Model(odrThing) #myData = odr.Data(VV,II) #myodr = odr.ODR(myData, odrMod, beta0=guess,maxit=5000,sstol=1e-20,partol=1e-20)# #myoutput = myodr.run() #myoutput.pprint() #see http://docs.scipy.org/doc/external/odrpack_guide.pdf try: #myoutput = myodr.run() #fitParams = myoutput.beta #print myoutput.stopreason #print myoutput.info #ier = 1 fitParams, fitCovariance, infodict, errmsg, ier = optimize.curve_fit(optimizeThis, VV, II,p0=guess,full_output = True,xtol=1e-13,ftol=1e-15) #fitParams, fitCovariance, infodict, errmsg, ier = optimize.leastsq(func=residual, args=(VV, II, np.ones(len(II))),x0=guess,full_output=1,xtol=1e-12,ftol=1e-14)#,xtol=1e-12,ftol=1e-14,maxfev=12000 #fitParams, fitCovariance, infodict, errmsg, ier = optimize.leastsq(func=residual, args=(VV, II, weights),x0=fitParams,full_output=1,ftol=1e-15,xtol=0)#,xtol=1e-12,ftol=1e-14 alwaysShowRecap = False if alwaysShowRecap: vv=np.linspace(VV[0],VV[-1],1000) print "fit:" print fitParams print "guess:" print guess print ier print errmsg ii=vectorizedCurrent(vv,guess[0],guess[1],guess[2],guess[3],guess[4]) ii2=vectorizedCurrent(vv,fitParams[0],fitParams[1],fitParams[2],fitParams[3],fitParams[4]) plt.title('Fit analysis') p1, = plt.plot(vv,ii, label='Guess',ls='--') p2, = plt.plot(vv,ii2, label='Fit') p3, = plt.plot(VV,II,ls='None',marker='o', label='Data') #p4, = plt.plot(VV[range(lowerI,upperI)],II[range(lowerI,upperI)],ls="None",marker='o', label='5x Weight Data') ax = plt.gca() handles, labels = ax.get_legend_handles_labels() ax.legend(handles, labels, loc=3) plt.grid(b=True) plt.draw() plt.show() return(fitParams, fitCovariance, infodict, errmsg, ier) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) def openCall(self): #remember the last path th user opened if self.settings.contains('lastFolder'): openDir = self.settings.value('lastFolder').toString() else: openDir = '.' fileNames = QFileDialog.getOpenFileNamesAndFilter(directory = openDir, caption="Select one or more files to open", filter = '(*.txt *.csv);;Folders (*)') #fileNames = QFileDialog.getExistingDirectory(directory = openDir, caption="Select one or more files to open") if len(fileNames[0])>0:#check if user clicked cancel self.workingDirectory = os.path.dirname(str(fileNames[0][0])) self.settings.setValue('lastFolder',self.workingDirectory) for fullPath in fileNames[0]: fullPath = str(fullPath) self.processFile(fullPath) if self.ui.actionEnable_Watching.isChecked(): watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) #user chose file --> watch def handleWatchAction(self): #remember the last path th user opened if self.settings.contains('lastFolder'): openDir = self.settings.value('lastFolder').toString() else: openDir = '.' myDir = QFileDialog.getExistingDirectory(directory = openDir, caption="Select folder to watch") if len(myDir)>0:#check if user clicked cancel self.workingDirectory = str(myDir) self.settings.setValue('lastFolder',self.workingDirectory) self.ui.actionEnable_Watching.setChecked(True) watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) #user toggeled Tools --> Enable Watching def watchCall(self): watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) if self.ui.actionEnable_Watching.isChecked(): if (self.workingDirectory != ''): self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) def handleWatchUpdate(self,path): myDir = QDir(path) myDir.setNameFilters(self.supportedExtensions) allFilesNow = myDir.entryList() allFilesNow = list(allFilesNow) allFilesNow = [str(item) for item in allFilesNow] differentFiles = list(set(allFilesNow) ^ set(self.fileNames)) if differentFiles != []: for aFile in differentFiles: if self.fileNames.__contains__(aFile): #TODO: delete the file from the table self.ui.statusbar.showMessage('Removed' + aFile,2500) else: #process the new file self.processFile(os.path.join(self.workingDirectory,aFile))