class Watcher( QObject ): " Filesystem watcher implementation " fsChanged = pyqtSignal( list ) def __init__( self, excludeFilters, dirToWatch ): QObject.__init__( self ) self.__dirWatcher = QFileSystemWatcher( self ) # data members self.__excludeFilter = [] # Files exclude filter self.__srcDirsToWatch = set() # Came from the user self.__fsTopLevelSnapshot = {} # Current snapshot self.__fsSnapshot = {} # Current snapshot # Sets of dirs which are currently watched self.__dirsToWatch = set() self.__topLevelDirsToWatch = set() # Generated till root # precompile filters for flt in excludeFilters: self.__excludeFilter.append( re.compile( flt ) ) # Initialise the list of dirs to watch self.__srcDirsToWatch.add( dirToWatch ) self.__topLevelDirsToWatch = self.__buildTopDirsList( self.__srcDirsToWatch ) self.__fsTopLevelSnapshot = self.__buildTopLevelSnapshot( self.__topLevelDirsToWatch, self.__srcDirsToWatch ) self.__dirsToWatch = self.__buildSnapshot() # Here __dirsToWatch and __topLevelDirsToWatch have a complete # set of what should be watched # Add the dirs to the watcher dirs = [] for path in self.__dirsToWatch | self.__topLevelDirsToWatch: dirs.append( path ) self.__dirWatcher.addPaths( dirs ) self.__dirWatcher.directoryChanged.connect( self.__onDirChanged ) # self.debug() return @staticmethod def __buildTopDirsList( srcDirs ): " Takes a list of dirs to be watched and builds top dirs set " topDirsList = set() for path in srcDirs: parts = path.split( os.path.sep ) for index in xrange( 1, len( parts ) - 1 ): candidate = os.path.sep.join( parts[ 0:index ] ) + os.path.sep if os.path.exists( candidate ): if os.access( candidate, os.R_OK ): topDirsList.add( candidate ) return topDirsList @staticmethod def __buildTopLevelSnapshot( topLevelDirs, srcDirs ): " Takes top level dirs and builds their snapshot " snapshot = {} for path in topLevelDirs: itemsSet = set() # search for all the dirs to be watched for candidate in topLevelDirs | srcDirs: if len( candidate ) <= len( path ): continue if candidate.startswith( path ): candidate = candidate[ len( path ) : ] slashIndex = candidate.find( os.path.sep ) + 1 item = candidate[ : slashIndex ] if os.path.exists( path + item ): itemsSet.add( item ) snapshot[ path ] = itemsSet return snapshot def __buildSnapshot( self ): " Builds the filesystem snapshot " snapshotDirs = set() for path in self.__srcDirsToWatch: self.__addSnapshotPath( path, snapshotDirs ) return snapshotDirs def __addSnapshotPath( self, path, snapshotDirs, itemsToReport = None ): " Adds one path to the FS snapshot " if not os.path.exists( path ): return snapshotDirs.add( path ) dirItems = set() for item in os.listdir( path ): if self.__shouldExclude( item ): continue if os.path.isdir( path + item ): dirName = path + item + os.path.sep dirItems.add( item + os.path.sep ) if itemsToReport is not None: itemsToReport.append( "+" + dirName ) self.__addSnapshotPath( dirName, snapshotDirs, itemsToReport ) continue dirItems.add( item ) if itemsToReport is not None: itemsToReport.append( "+" + path + item ) self.__fsSnapshot[ path ] = dirItems return def __onDirChanged( self, path ): " Triggered when the dir is changed " path = str( path ) if not path.endswith( os.path.sep ): path = path + os.path.sep # Check if it is a top level dir try: oldSet = self.__fsTopLevelSnapshot[ path ] # Build a new set of what is in that top level dir newSet = set() for item in os.listdir( path ): if not os.path.isdir( path + item ): continue # Only dirs are of interest for the top level item = item + os.path.sep if item in oldSet: newSet.add( item ) # Now we have an old set and a new one with those from the old # which actually exist diff = oldSet - newSet # diff are those which disappeared. We need to do the following: # - build a list of all the items in the fs snapshot which start # from this dir # - build a list of dirs which should be deregistered from the # watcher. This list includes both top level and project level # - deregister dirs from the watcher # - emit a signal of what disappeared if not diff: return # no changes self.__fsTopLevelSnapshot[ path ] = newSet dirsToBeRemoved = [] itemsToReport = [] for item in diff: self.__processRemoveTopDir( path + item, dirsToBeRemoved, itemsToReport ) # Here: it is possible that the last dir to watch disappeared if not newSet: # There is nothing to watch here anymore dirsToBeRemoved.append( path ) del self.__fsTopLevelSnapshot[ path ] parts = path[ 1:-1 ].split( os.path.sep ) for index in xrange( len( parts ) - 2, 0, -1 ): candidate = os.path.sep + \ os.path.sep.join( parts[ 0 : index ] ) + \ os.path.sep dirSet = self.__fsTopLevelSnapshot[ candidate ] dirSet.remove( parts[ index + 1 ] + os.path.sep ) if not dirSet: dirsToBeRemoved.append( candidate ) del self.__fsTopLevelSnapshot[ candidate ] continue break # it is not the last item in the set # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths( dirsToBeRemoved ) # Report if itemsToReport: self.fsChanged.emit( itemsToReport ) return except: # it is not a top level dir - no key pass # Here: the change is in the project level dir try: oldSet = self.__fsSnapshot[ path ] # Build a new set of what is in that top level dir newSet = set() for item in os.listdir( path ): if self.__shouldExclude( item ): continue if os.path.isdir( path + item ): newSet.add( item + os.path.sep ) else: newSet.add( item ) # Here: we have a new and old snapshots # Lets calculate the difference deletedItems = oldSet - newSet addedItems = newSet - oldSet if not deletedItems and not addedItems: return # No changes # Update the changed dir set self.__fsSnapshot[ path ] = newSet # We need to build some lists: # - list of files which were added # - list of dirs which were added # - list of files which were deleted # - list of dirs which were deleted # The deleted dirs must be unregistered in the watcher # The added dirs must be registered itemsToReport = [] dirsToBeAdded = [] dirsToBeRemoved = [] for item in addedItems: if item.endswith( os.path.sep ): # directory was added self.__processAddedDir( path + item, dirsToBeAdded, itemsToReport ) else: itemsToReport.append( "+" + path + item ) for item in deletedItems: if item.endswith( os.path.sep ): # directory was deleted self.__processRemovedDir( path + item, dirsToBeRemoved, itemsToReport ) else: itemsToReport.append( "-" + path + item ) # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths( dirsToBeRemoved ) if dirsToBeAdded: self.__dirWatcher.addPaths( dirsToBeAdded ) # Report self.fsChanged.emit( itemsToReport ) except: # It could be a queued signal about what was already reported pass # self.debug() return def __shouldExclude( self, name ): " Tests if a file must be excluded " for excl in self.__excludeFilter: if excl.match( name ): return True return False def __processAddedDir( self, path, dirsToBeAdded, itemsToReport ): " called for an appeared dir in the project tree " dirsToBeAdded.append( path ) itemsToReport.append( "+" + path ) # it should add dirs recursively into the snapshot and care # of the items to report dirItems = set() for item in os.listdir( path ): if self.__shouldExclude( item ): continue if os.path.isdir( path + item ): dirName = path + item + os.path.sep dirItems.add( item + os.path.sep ) self.__processAddedDir( dirName, dirsToBeAdded, itemsToReport ) continue itemsToReport.append( "+" + path + item ) dirItems.add( item ) self.__fsSnapshot[ path ] = dirItems return def __processRemovedDir( self, path, dirsToBeRemoved, itemsToReport ): " called for a disappeared dir in the project tree " # it should remove the dirs recursively from the fs snapshot # and care of items to report dirsToBeRemoved.append( path ) itemsToReport.append( "-" + path ) oldSet = self.__fsSnapshot[ path ] for item in oldSet: if item.endswith( os.path.sep ): # Nested dir self.__processRemovedDir( path + item, dirsToBeRemoved, itemsToReport ) else: # a file itemsToReport.append( "-" + path + item ) del self.__fsSnapshot[ path ] return def __processRemoveTopDir( self, path, dirsToBeRemoved, itemsToReport ): " Called for a disappeared top level dir " if path in self.__fsTopLevelSnapshot: # It is still a top level dir dirsToBeRemoved.append( path ) for item in self.__fsTopLevelSnapshot[ path ]: self.__processRemoveTopDir( path + item, dirsToBeRemoved, itemsToReport ) del self.__fsTopLevelSnapshot[ path ] else: # This is a project level dir self.__processRemovedDir( path, dirsToBeRemoved, itemsToReport ) return def reset( self ): " Resets the watcher (it does not report any changes) " self.__dirWatcher.removePaths( self.__dirWatcher.directories() ) self.__srcDirsToWatch = set() self.__fsTopLevelSnapshot = {} self.__fsSnapshot = {} self.__dirsToWatch = set() self.__topLevelDirsToWatch = set() return def registerDir( self, path ): " Adds a directory to the list of watched ones " if not path.endswith( os.path.sep ): path = path + os.path.sep if path in self.__srcDirsToWatch: return # It is there already # It is necessary to do the following: # - add the dir to the fs snapshot # - collect dirs to add to the watcher # - collect items to report self.__srcDirsToWatch.add( path ) dirsToWatch = set() itemsToReport = [] self.__registerDir( path, dirsToWatch, itemsToReport ) # It might be that top level dirs should be updated too newTopLevelDirsToWatch = self.__buildTopDirsList(self.__srcDirsToWatch) addedDirs = newTopLevelDirsToWatch - self.__topLevelDirsToWatch for item in addedDirs: dirsToWatch.add( item ) # Identify items to be watched by this dir dirItems = set() for candidate in newTopLevelDirsToWatch | self.__srcDirsToWatch: if len( candidate ) <= len( item ): continue if candidate.startswith( item ): candidate = candidate[ len( item ) : ] slashIndex = candidate.find( os.path.sep ) + 1 dirName = candidate[ : slashIndex ] if os.path.exists( item + dirName ): dirItems.add( dirName ) # Update the top level dirs snapshot self.__fsTopLevelSnapshot[ item ] = dirItems # Update the top level snapshot with the added dir upperDir = os.path.dirname( path[ :-1 ] ) + os.path.sep dirName = path.replace( upperDir, '' ) self.__fsTopLevelSnapshot[ upperDir ].add( dirName ) # Update the list of top level dirs to watch self.__topLevelDirsToWatch = newTopLevelDirsToWatch # Update the watcher if dirsToWatch: dirs = [] for item in dirsToWatch: dirs.append( item ) self.__dirWatcher.addPaths( dirs ) # Report the changes if itemsToReport: self.fsChanged.emit( itemsToReport ) # self.debug() return def __registerDir( self, path, dirsToWatch, itemsToReport ): " Adds one path to the FS snapshot " if not os.path.exists( path ): return dirsToWatch.add( path ) itemsToReport.append( "+" + path ) dirItems = set() for item in os.listdir( path ): if self.__shouldExclude( item ): continue if os.path.isdir( path + item ): dirName = path + item + os.path.sep dirItems.add( item + os.path.sep ) itemsToReport.append( "+" + path + item + os.path.sep ) self.__addSnapshotPath( dirName, dirsToWatch, itemsToReport ) continue dirItems.add( item ) itemsToReport.append( "+" + path + item ) self.__fsSnapshot[ path ] = dirItems return def deregisterDir( self, path ): " Removes the directory from the list of the watched ones " if not path.endswith( os.path.sep ): path = path + os.path.sep if path not in self.__srcDirsToWatch: return # It is not there already self.__srcDirsToWatch.remove( path ) # It is necessary to do the following: # - remove the dir from the fs snapshot # - collect the dirs to be removed from watching # - collect item to report itemsToReport = [] dirsToBeRemoved = [] self.__deregisterDir( path, dirsToBeRemoved, itemsToReport ) # It is possible that some of the top level watched dirs should be # removed as well newTopLevelDirsToWatch = self.__buildTopDirsList(self.__srcDirsToWatch) deletedDirs = self.__topLevelDirsToWatch - newTopLevelDirsToWatch for item in deletedDirs: dirsToBeRemoved.append( item ) del self.__fsTopLevelSnapshot[ item ] # It might be the case that some of the items should be deleted in the # top level dirs sets for dirName in self.__fsTopLevelSnapshot: itemsSet = self.__fsTopLevelSnapshot[ dirName ] for item in itemsSet: candidate = dirName + item if candidate == path or candidate in deletedDirs: itemsSet.remove( item ) self.__fsTopLevelSnapshot[ dirName ] = itemsSet break # Update the list of dirs to be watched self.__topLevelDirsToWatch = newTopLevelDirsToWatch # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths( dirsToBeRemoved ) # Report the changes if itemsToReport: self.fsChanged.emit( itemsToReport ) # self.debug() return def __deregisterDir( self, path, dirsToBeRemoved, itemsToReport ): " Deregisters a directory recursively " dirsToBeRemoved.append( path ) itemsToReport.append( "-" + path ) if path in self.__fsTopLevelSnapshot: # This is a top level dir for item in self.__fsTopLevelSnapshot[ path ]: if item.endswith( os.path.sep ): # It's a dir self.__deregisterDir( path + item, dirsToBeRemoved, itemsToReport ) else: # It's a file itemsToReport.append( "-" + path + item ) del self.__fsTopLevelSnapshot[ path ] return # It is from an a project level snapshot if path in self.__fsSnapshot: for item in self.__fsSnapshot[ path ]: if item.endswith( os.path.sep ): # It's a dir self.__deregisterDir( path + item, dirsToBeRemoved, itemsToReport ) else: # It's a file itemsToReport.append( "-" + path + item ) del self.__fsSnapshot[ path ] return def debug( self ): print "Top level dirs to watch: " + str( self.__topLevelDirsToWatch ) print "Project dirs to watch: " + str( self.__dirsToWatch ) print "Top level snapshot: " + str( self.__fsTopLevelSnapshot ) print "Project snapshot: " + str( self.__fsSnapshot )
class TreeProjectsWidget(QTreeWidget): ############################################################################### # TreeProjectsWidget SIGNALS ############################################################################### """ runProject() closeProject(QString) closeFilesFromProjectClosed(QString) addProjectToConsole(QString) removeProjectFromConsole(QString) """ ############################################################################### #Extra context menu 'all' indicate a menu for ALL LANGUAGES! EXTRA_MENUS = {'all': []} images = { 'py': resources.IMAGES['tree-python'], 'java': resources.IMAGES['tree-java'], 'fn': resources.IMAGES['tree-code'], 'c': resources.IMAGES['tree-code'], 'cs': resources.IMAGES['tree-code'], 'jpg': resources.IMAGES['tree-image'], 'png': resources.IMAGES['tree-image'], 'html': resources.IMAGES['tree-html'], 'css': resources.IMAGES['tree-css'], 'ui': resources.IMAGES['designer']} def __init__(self): QTreeWidget.__init__(self) self.header().setHidden(True) self.setSelectionMode(QTreeWidget.SingleSelection) self.setAnimated(True) self._actualProject = None #self._projects -> key: [Item, folderStructure] self._projects = {} self.__enableCloseNotification = True self._fileWatcher = QFileSystemWatcher() self.header().setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.header().setResizeMode(0, QHeaderView.ResizeToContents) self.header().setStretchLastSection(False) self.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self, SIGNAL( "customContextMenuRequested(const QPoint &)"), self._menu_context_tree) self.connect(self, SIGNAL("itemClicked(QTreeWidgetItem *, int)"), self._open_file) self.connect(self._fileWatcher, SIGNAL("directoryChanged(QString)"), self._refresh_project_by_path) def add_extra_menu(self, menu, lang='all'): ''' Add an extra menu for the given language @lang: string with the form 'py', 'php', 'json', etc ''' #remove blanks and replace dots Example(.py => py) lang = lang.strip().replace('.', '') self.EXTRA_MENUS.setdefault(lang, []) self.EXTRA_MENUS[lang].append(menu) def _menu_context_tree(self, point): index = self.indexAt(point) if not index.isValid(): return item = self.itemAt(point) handler = None menu = QMenu(self) if item.isFolder or item.parent() is None: action_add_file = menu.addAction(QIcon(resources.IMAGES['new']), self.tr("Add New File")) self.connect(action_add_file, SIGNAL("triggered()"), self._add_new_file) action_add_folder = menu.addAction(QIcon( resources.IMAGES['openProj']), self.tr("Add New Folder")) self.connect(action_add_folder, SIGNAL("triggered()"), self._add_new_folder) action_create_init = menu.addAction( self.tr("Create '__init__' Complete")) self.connect(action_create_init, SIGNAL("triggered()"), self._create_init) if item.isFolder and (item.parent() != None): action_remove_folder = menu.addAction(self.tr("Remove Folder")) self.connect(action_remove_folder, SIGNAL("triggered()"), self._delete_folder) elif not item.isFolder: action_rename_file = menu.addAction(self.tr("Rename File")) action_move_file = menu.addAction(self.tr("Move File")) action_copy_file = menu.addAction(self.tr("Copy File")) action_remove_file = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Delete File")) self.connect(action_remove_file, SIGNAL("triggered()"), self._delete_file) self.connect(action_rename_file, SIGNAL("triggered()"), self._rename_file) self.connect(action_copy_file, SIGNAL("triggered()"), self._copy_file) self.connect(action_move_file, SIGNAL("triggered()"), self._move_file) #Allow to edit Qt UI files with the appropiate program if item.lang() == 'ui': action_edit_ui_file = menu.addAction(self.tr("Edit UI File")) self.connect(action_edit_ui_file, SIGNAL("triggered()"), self._edit_ui_file) #menu per file language! for m in self.EXTRA_MENUS.get(item.lang(), ()): menu.addSeparator() menu.addMenu(m) if item.parent() is None: menu.addSeparator() actionRunProject = menu.addAction(QIcon( resources.IMAGES['play']), self.tr("Run Project")) self.connect(actionRunProject, SIGNAL("triggered()"), SIGNAL("runProject()")) actionMainProject = menu.addAction(self.tr("Set as Main Project")) self.connect(actionMainProject, SIGNAL("triggered()"), lambda: self.set_default_project(item)) if item.addedToConsole: actionRemoveFromConsole = menu.addAction( self.tr("Remove this Project from the Python Console")) self.connect(actionRemoveFromConsole, SIGNAL("triggered()"), self._remove_project_from_console) else: actionAdd2Console = menu.addAction( self.tr("Add this Project to the Python Console")) self.connect(actionAdd2Console, SIGNAL("triggered()"), self._add_project_to_console) actionProperties = menu.addAction(QIcon(resources.IMAGES['pref']), self.tr("Project Properties")) self.connect(actionProperties, SIGNAL("triggered()"), self.open_project_properties) #get the extra context menu for this projectType handler = settings.get_project_type_handler(item.projectType) menu.addSeparator() action_refresh = menu.addAction( self.style().standardIcon(QStyle.SP_BrowserReload), self.tr("Refresh Project")) self.connect(action_refresh, SIGNAL("triggered()"), self._refresh_project) action_close = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Close Project")) self.connect(action_close, SIGNAL("triggered()"), self._close_project) #menu for all items! for m in self.EXTRA_MENUS.get('all', ()): menu.addSeparator() menu.addMenu(m) #menu for the Project Type(if present) if handler: for m in handler.get_context_menus(): menu.addSeparator() menu.addMenu(m) #show the menu! menu.exec_(QCursor.pos()) def _add_project_to_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("addProjectToConsole(QString)"), item.path) item.addedToConsole = True def _remove_project_from_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("removeProjectFromConsole(QString)"), item.path) item.addedToConsole = False def _open_file(self, item, column): if item.childCount() == 0 and not item.isFolder: fileName = os.path.join(item.path, unicode(item.text(column))) main_container.MainContainer().open_file(fileName) def _get_project_root(self, item=None): if item is None: item = self.currentItem() while item is not None and item.parent() is not None: item = item.parent() return item def set_default_project(self, item): item.setForeground(0, QBrush(QColor(0, 204, 82))) if self._actualProject: self._actualProject.setForeground(0, QBrush(Qt.darkGray)) self._actualProject = item def open_project_properties(self): item = self._get_project_root() proj = project_properties_widget.ProjectProperties(item, self) proj.show() def _refresh_project_by_path(self, folder): item = self.get_item_for_path(unicode(folder)) self._refresh_project(item) def _refresh_project(self, item=None): if item is None: item = self.currentItem() parentItem = self._get_project_root(item) if parentItem is None: return if item.parent() is None: path = item.path else: path = file_manager.create_path(item.path, unicode(item.text(0))) if parentItem.extensions != settings.SUPPORTED_EXTENSIONS: folderStructure = file_manager.open_project_with_extensions( path, parentItem.extensions) else: folderStructure = file_manager.open_project(path) if folderStructure.get(path, [None, None])[1] is not None: folderStructure[path][1].sort() else: return item.takeChildren() self._load_folder(folderStructure, path, item) item.setExpanded(True) def _close_project(self): item = self.currentItem() index = self.indexOfTopLevelItem(item) pathKey = item.path for directory in self._fileWatcher.directories(): directory = unicode(directory) if file_manager.belongs_to_folder(pathKey, directory): self._fileWatcher.removePath(directory) self._fileWatcher.removePath(pathKey) self.takeTopLevelItem(index) self._projects.pop(pathKey) if self.__enableCloseNotification: self.emit(SIGNAL("closeProject(QString)"), pathKey) self.emit(SIGNAL("closeFilesFromProjectClosed(QString)"), pathKey) item = self.currentItem() if item: self.set_default_project(item) def _create_init(self): item = self.currentItem() if item.parent() is None: pathFolder = item.path else: pathFolder = os.path.join(item.path, str(item.text(0))) try: file_manager.create_init_file_complete(pathFolder) except file_manager.NinjaFileExistsException, ex: QMessageBox.information(self, self.tr("Create INIT fail"), ex.message) self._refresh_project(item)
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))
class Watcher(QObject): " Filesystem watcher implementation " fsChanged = pyqtSignal(list) def __init__(self, excludeFilters, dirToWatch): QObject.__init__(self) self.__dirWatcher = QFileSystemWatcher(self) # data members self.__excludeFilter = [] # Files exclude filter self.__srcDirsToWatch = set() # Came from the user self.__fsTopLevelSnapshot = {} # Current snapshot self.__fsSnapshot = {} # Current snapshot # Sets of dirs which are currently watched self.__dirsToWatch = set() self.__topLevelDirsToWatch = set() # Generated till root # precompile filters for flt in excludeFilters: self.__excludeFilter.append(re.compile(flt)) # Initialise the list of dirs to watch self.__srcDirsToWatch.add(dirToWatch) self.__topLevelDirsToWatch = self.__buildTopDirsList( self.__srcDirsToWatch) self.__fsTopLevelSnapshot = self.__buildTopLevelSnapshot( self.__topLevelDirsToWatch, self.__srcDirsToWatch) self.__dirsToWatch = self.__buildSnapshot() # Here __dirsToWatch and __topLevelDirsToWatch have a complete # set of what should be watched # Add the dirs to the watcher dirs = [] for path in self.__dirsToWatch | self.__topLevelDirsToWatch: dirs.append(path) self.__dirWatcher.addPaths(dirs) self.__dirWatcher.directoryChanged.connect(self.__onDirChanged) # self.debug() return @staticmethod def __buildTopDirsList(srcDirs): " Takes a list of dirs to be watched and builds top dirs set " topDirsList = set() for path in srcDirs: parts = path.split(os.path.sep) for index in xrange(1, len(parts) - 1): candidate = os.path.sep.join(parts[0:index]) + os.path.sep if os.path.exists(candidate): if os.access(candidate, os.R_OK): topDirsList.add(candidate) return topDirsList @staticmethod def __buildTopLevelSnapshot(topLevelDirs, srcDirs): " Takes top level dirs and builds their snapshot " snapshot = {} for path in topLevelDirs: itemsSet = set() # search for all the dirs to be watched for candidate in topLevelDirs | srcDirs: if len(candidate) <= len(path): continue if candidate.startswith(path): candidate = candidate[len(path):] slashIndex = candidate.find(os.path.sep) + 1 item = candidate[:slashIndex] if os.path.exists(path + item): itemsSet.add(item) snapshot[path] = itemsSet return snapshot def __buildSnapshot(self): " Builds the filesystem snapshot " snapshotDirs = set() for path in self.__srcDirsToWatch: self.__addSnapshotPath(path, snapshotDirs) return snapshotDirs def __addSnapshotPath(self, path, snapshotDirs, itemsToReport=None): " Adds one path to the FS snapshot " if not os.path.exists(path): return snapshotDirs.add(path) dirItems = set() for item in os.listdir(path): if self.__shouldExclude(item): continue if os.path.isdir(path + item): dirName = path + item + os.path.sep dirItems.add(item + os.path.sep) if itemsToReport is not None: itemsToReport.append("+" + dirName) self.__addSnapshotPath(dirName, snapshotDirs, itemsToReport) continue dirItems.add(item) if itemsToReport is not None: itemsToReport.append("+" + path + item) self.__fsSnapshot[path] = dirItems return def __onDirChanged(self, path): " Triggered when the dir is changed " path = str(path) if not path.endswith(os.path.sep): path = path + os.path.sep # Check if it is a top level dir try: oldSet = self.__fsTopLevelSnapshot[path] # Build a new set of what is in that top level dir newSet = set() for item in os.listdir(path): if not os.path.isdir(path + item): continue # Only dirs are of interest for the top level item = item + os.path.sep if item in oldSet: newSet.add(item) # Now we have an old set and a new one with those from the old # which actually exist diff = oldSet - newSet # diff are those which disappeared. We need to do the following: # - build a list of all the items in the fs snapshot which start # from this dir # - build a list of dirs which should be deregistered from the # watcher. This list includes both top level and project level # - deregister dirs from the watcher # - emit a signal of what disappeared if not diff: return # no changes self.__fsTopLevelSnapshot[path] = newSet dirsToBeRemoved = [] itemsToReport = [] for item in diff: self.__processRemoveTopDir(path + item, dirsToBeRemoved, itemsToReport) # Here: it is possible that the last dir to watch disappeared if not newSet: # There is nothing to watch here anymore dirsToBeRemoved.append(path) del self.__fsTopLevelSnapshot[path] parts = path[1:-1].split(os.path.sep) for index in xrange(len(parts) - 2, 0, -1): candidate = os.path.sep + \ os.path.sep.join( parts[ 0 : index ] ) + \ os.path.sep dirSet = self.__fsTopLevelSnapshot[candidate] dirSet.remove(parts[index + 1] + os.path.sep) if not dirSet: dirsToBeRemoved.append(candidate) del self.__fsTopLevelSnapshot[candidate] continue break # it is not the last item in the set # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths(dirsToBeRemoved) # Report if itemsToReport: self.fsChanged.emit(itemsToReport) return except: # it is not a top level dir - no key pass # Here: the change is in the project level dir try: oldSet = self.__fsSnapshot[path] # Build a new set of what is in that top level dir newSet = set() for item in os.listdir(path): if self.__shouldExclude(item): continue if os.path.isdir(path + item): newSet.add(item + os.path.sep) else: newSet.add(item) # Here: we have a new and old snapshots # Lets calculate the difference deletedItems = oldSet - newSet addedItems = newSet - oldSet if not deletedItems and not addedItems: return # No changes # Update the changed dir set self.__fsSnapshot[path] = newSet # We need to build some lists: # - list of files which were added # - list of dirs which were added # - list of files which were deleted # - list of dirs which were deleted # The deleted dirs must be unregistered in the watcher # The added dirs must be registered itemsToReport = [] dirsToBeAdded = [] dirsToBeRemoved = [] for item in addedItems: if item.endswith(os.path.sep): # directory was added self.__processAddedDir(path + item, dirsToBeAdded, itemsToReport) else: itemsToReport.append("+" + path + item) for item in deletedItems: if item.endswith(os.path.sep): # directory was deleted self.__processRemovedDir(path + item, dirsToBeRemoved, itemsToReport) else: itemsToReport.append("-" + path + item) # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths(dirsToBeRemoved) if dirsToBeAdded: self.__dirWatcher.addPaths(dirsToBeAdded) # Report self.fsChanged.emit(itemsToReport) except: # It could be a queued signal about what was already reported pass # self.debug() return def __shouldExclude(self, name): " Tests if a file must be excluded " for excl in self.__excludeFilter: if excl.match(name): return True return False def __processAddedDir(self, path, dirsToBeAdded, itemsToReport): " called for an appeared dir in the project tree " dirsToBeAdded.append(path) itemsToReport.append("+" + path) # it should add dirs recursively into the snapshot and care # of the items to report dirItems = set() for item in os.listdir(path): if self.__shouldExclude(item): continue if os.path.isdir(path + item): dirName = path + item + os.path.sep dirItems.add(item + os.path.sep) self.__processAddedDir(dirName, dirsToBeAdded, itemsToReport) continue itemsToReport.append("+" + path + item) dirItems.add(item) self.__fsSnapshot[path] = dirItems return def __processRemovedDir(self, path, dirsToBeRemoved, itemsToReport): " called for a disappeared dir in the project tree " # it should remove the dirs recursively from the fs snapshot # and care of items to report dirsToBeRemoved.append(path) itemsToReport.append("-" + path) oldSet = self.__fsSnapshot[path] for item in oldSet: if item.endswith(os.path.sep): # Nested dir self.__processRemovedDir(path + item, dirsToBeRemoved, itemsToReport) else: # a file itemsToReport.append("-" + path + item) del self.__fsSnapshot[path] return def __processRemoveTopDir(self, path, dirsToBeRemoved, itemsToReport): " Called for a disappeared top level dir " if path in self.__fsTopLevelSnapshot: # It is still a top level dir dirsToBeRemoved.append(path) for item in self.__fsTopLevelSnapshot[path]: self.__processRemoveTopDir(path + item, dirsToBeRemoved, itemsToReport) del self.__fsTopLevelSnapshot[path] else: # This is a project level dir self.__processRemovedDir(path, dirsToBeRemoved, itemsToReport) return def reset(self): " Resets the watcher (it does not report any changes) " self.__dirWatcher.removePaths(self.__dirWatcher.directories()) self.__srcDirsToWatch = set() self.__fsTopLevelSnapshot = {} self.__fsSnapshot = {} self.__dirsToWatch = set() self.__topLevelDirsToWatch = set() return def registerDir(self, path): " Adds a directory to the list of watched ones " if not path.endswith(os.path.sep): path = path + os.path.sep if path in self.__srcDirsToWatch: return # It is there already # It is necessary to do the following: # - add the dir to the fs snapshot # - collect dirs to add to the watcher # - collect items to report self.__srcDirsToWatch.add(path) dirsToWatch = set() itemsToReport = [] self.__registerDir(path, dirsToWatch, itemsToReport) # It might be that top level dirs should be updated too newTopLevelDirsToWatch = self.__buildTopDirsList(self.__srcDirsToWatch) addedDirs = newTopLevelDirsToWatch - self.__topLevelDirsToWatch for item in addedDirs: dirsToWatch.add(item) # Identify items to be watched by this dir dirItems = set() for candidate in newTopLevelDirsToWatch | self.__srcDirsToWatch: if len(candidate) <= len(item): continue if candidate.startswith(item): candidate = candidate[len(item):] slashIndex = candidate.find(os.path.sep) + 1 dirName = candidate[:slashIndex] if os.path.exists(item + dirName): dirItems.add(dirName) # Update the top level dirs snapshot self.__fsTopLevelSnapshot[item] = dirItems # Update the top level snapshot with the added dir upperDir = os.path.dirname(path[:-1]) + os.path.sep dirName = path.replace(upperDir, '') self.__fsTopLevelSnapshot[upperDir].add(dirName) # Update the list of top level dirs to watch self.__topLevelDirsToWatch = newTopLevelDirsToWatch # Update the watcher if dirsToWatch: dirs = [] for item in dirsToWatch: dirs.append(item) self.__dirWatcher.addPaths(dirs) # Report the changes if itemsToReport: self.fsChanged.emit(itemsToReport) # self.debug() return def __registerDir(self, path, dirsToWatch, itemsToReport): " Adds one path to the FS snapshot " if not os.path.exists(path): return dirsToWatch.add(path) itemsToReport.append("+" + path) dirItems = set() for item in os.listdir(path): if self.__shouldExclude(item): continue if os.path.isdir(path + item): dirName = path + item + os.path.sep dirItems.add(item + os.path.sep) itemsToReport.append("+" + path + item + os.path.sep) self.__addSnapshotPath(dirName, dirsToWatch, itemsToReport) continue dirItems.add(item) itemsToReport.append("+" + path + item) self.__fsSnapshot[path] = dirItems return def deregisterDir(self, path): " Removes the directory from the list of the watched ones " if not path.endswith(os.path.sep): path = path + os.path.sep if path not in self.__srcDirsToWatch: return # It is not there already self.__srcDirsToWatch.remove(path) # It is necessary to do the following: # - remove the dir from the fs snapshot # - collect the dirs to be removed from watching # - collect item to report itemsToReport = [] dirsToBeRemoved = [] self.__deregisterDir(path, dirsToBeRemoved, itemsToReport) # It is possible that some of the top level watched dirs should be # removed as well newTopLevelDirsToWatch = self.__buildTopDirsList(self.__srcDirsToWatch) deletedDirs = self.__topLevelDirsToWatch - newTopLevelDirsToWatch for item in deletedDirs: dirsToBeRemoved.append(item) del self.__fsTopLevelSnapshot[item] # It might be the case that some of the items should be deleted in the # top level dirs sets for dirName in self.__fsTopLevelSnapshot: itemsSet = self.__fsTopLevelSnapshot[dirName] for item in itemsSet: candidate = dirName + item if candidate == path or candidate in deletedDirs: itemsSet.remove(item) self.__fsTopLevelSnapshot[dirName] = itemsSet break # Update the list of dirs to be watched self.__topLevelDirsToWatch = newTopLevelDirsToWatch # Update the watcher if dirsToBeRemoved: self.__dirWatcher.removePaths(dirsToBeRemoved) # Report the changes if itemsToReport: self.fsChanged.emit(itemsToReport) # self.debug() return def __deregisterDir(self, path, dirsToBeRemoved, itemsToReport): " Deregisters a directory recursively " dirsToBeRemoved.append(path) itemsToReport.append("-" + path) if path in self.__fsTopLevelSnapshot: # This is a top level dir for item in self.__fsTopLevelSnapshot[path]: if item.endswith(os.path.sep): # It's a dir self.__deregisterDir(path + item, dirsToBeRemoved, itemsToReport) else: # It's a file itemsToReport.append("-" + path + item) del self.__fsTopLevelSnapshot[path] return # It is from an a project level snapshot if path in self.__fsSnapshot: for item in self.__fsSnapshot[path]: if item.endswith(os.path.sep): # It's a dir self.__deregisterDir(path + item, dirsToBeRemoved, itemsToReport) else: # It's a file itemsToReport.append("-" + path + item) del self.__fsSnapshot[path] return def debug(self): print "Top level dirs to watch: " + str(self.__topLevelDirsToWatch) print "Project dirs to watch: " + str(self.__dirsToWatch) print "Top level snapshot: " + str(self.__fsTopLevelSnapshot) print "Project snapshot: " + str(self.__fsSnapshot)