def getExistingDirectory(self, **kwargs): kwargs = self._processOpenKwargs(kwargs) fname = QtWidgets.QFileDialog.getExistingDirectory(**kwargs) if fname: p = PathStr(fname) self.opts['open'] = p.dirname() return p
def getSaveFileName(self, *args, **kwargs): """ analogue to QtWidgets.QFileDialog.getSaveFileNameAndFilter but returns the filename + chosen file ending even if not typed in gui """ if 'directory' not in kwargs: if self.opts['save']: if self.opts['save']: kwargs['directory'] = self.opts['save'] fname = QtWidgets.QFileDialog.getSaveFileName(**kwargs) if fname: if type(fname) == tuple: # only happened since qt5 # getSaveFileName returns (path, ftype) if not fname[0]: return p = PathStr(fname[0]) if not p.filetype(): ftyp = self._extractFtype(fname[1]) p = p.setFiletype(ftyp) else: p = PathStr(fname) self.opts['save'] = p.dirname() if self.opts['open'] is None: self.opts['open'] = self.opts['save'] return p
def getSaveFileName(self, *args, **kwargs): """ analogue to QtWidgets.QFileDialog.getSaveFileNameAndFilter but returns the filename + chosen file ending even if not typed in gui """ if 'directory' not in kwargs: if self.opts['save']: if self.opts['save']: kwargs['directory'] = self.opts['save'] fname = QtWidgets.QFileDialog.getSaveFileName(**kwargs) if fname: if type(fname) == tuple: #only happened since qt5 #getSaveFileName returns (path, ftype) if not fname[0]: return p = PathStr(fname[0]) if not p.filetype(): ftyp = self._extractFtype(fname[1]) p = p.setFiletype(ftyp) else: p = PathStr(fname) self.opts['save'] = p.dirname() if self.opts['open'] is None: self.opts['open'] = self.opts['save'] return p
def getOpenFileName(self, **kwargs): kwargs = self._processOpenKwargs(kwargs) fname = QtWidgets.QFileDialog.getOpenFileName(**kwargs) if isinstance(fname, tuple): fname = fname[0] if fname: p = PathStr(fname) self.opts['open'] = p.dirname() return p
def equalizeImage(img, save_path=None, name_additive='_eqHist'): ''' Equalize the histogram (contrast) of an image works with RGB/multi-channel images and flat-arrays @param img - image_path or np.array @param save_path if given output images will be saved there @param name_additive if given this additive will be appended to output images @return output images if input images are numpy.arrays and no save_path is given @return None elsewise ''' if isinstance(img, basestring): img = PathStr(img) if not img.exists(): raise Exception("image path doesn't exist") img_name = img.basename().replace('.','%s.' %name_additive) if save_path is None: save_path = img.dirname() img = cv2.imread(img) if img.dtype != np.dtype('uint8'): #openCV cannot work with float arrays or uint > 8bit eqFn = _equalizeHistogram else: eqFn = cv2.equalizeHist if len(img.shape) == 3:#multi channel img like rgb for i in range(img.shape[2]): img[:, :, i] = eqFn(img[:, :, i]) else: # grey scale image img = eqFn(img) if save_path: img_name = PathStr(save_path).join(img_name) cv2.imwrite(img_name, img) return img
def equalizeImage(img, save_path=None, name_additive='_eqHist'): ''' Equalize the histogram (contrast) of an image works with RGB/multi-channel images and flat-arrays @param img - image_path or np.array @param save_path if given output images will be saved there @param name_additive if given this additive will be appended to output images @return output images if input images are numpy.arrays and no save_path is given @return None elsewise ''' if isinstance(img, string_types): img = PathStr(img) if not img.exists(): raise Exception("image path doesn't exist") img_name = img.basename().replace('.', '%s.' % name_additive) if save_path is None: save_path = img.dirname() img = cv2.imread(img) if img.dtype != np.dtype('uint8'): # openCV cannot work with float arrays or uint > 8bit eqFn = _equalizeHistogram else: eqFn = cv2.equalizeHist if len(img.shape) == 3: # multi channel img like rgb for i in range(img.shape[2]): img[:, :, i] = eqFn(img[:, :, i]) else: # grey scale image img = eqFn(img) if save_path: img_name = PathStr(save_path).join(img_name) cv2.imwrite(img_name, img) return img
class Launcher(QtWidgets.QMainWindow): """ A graphical starter for *.pyz files created by the save-method from appbase.MainWindow NEEDS AN OVERHAUL ... after that's done it will be able to: * show all *.pyz-files in a filetree * show the session specific ... * icon * description * author etc. * start, remove, rename, modify a session * modify, start a certain state of a session """ def __init__(self, title='PYZ-Launcher', icon=None, start_script=None, left_header=None, right_header=None, file_type='pyz' ): self.dialogs = Dialogs() _path = PathStr.getcwd() _default_text_color = '#3c3c3c' if icon is None: icon = os.path.join(_path, 'media', 'launcher_logo.svg') if start_script is None: start_script = os.path.join(_path, 'test_session.py') if left_header is None: _description = "<a href=%s style='color: %s'>%s</a>" % ( appbase.__url__, _default_text_color, appbase.__description__) left_header = """<b>%s</b><br> version <a href=%s style='color: %s'>%s</a><br> autor <a href=mailto:%s style='color: %s'>%s</a> """ % ( # text-decoration:underline _description, os.path.join(_path, 'media', 'recent_changes.txt'), _default_text_color, appbase.__version__, appbase.__email__, _default_text_color, appbase.__author__ ) if right_header is None: # if no header is given, list all pdfs in folder media as link d = _path right_header = '' for f in os.listdir(os.path.join(d, 'media')): if f.endswith('.pdf'): _guidePath = os.path.join(d, 'media', f) right_header += "<a href=%s style='color: %s'>%s</a><br>" % ( _guidePath, _default_text_color, f[:-4]) right_header = right_header[:-4] QtWidgets.QMainWindow.__init__(self) self._start_script = start_script self.setWindowTitle(title) self.setWindowIcon(QtGui.QIcon(icon)) self.resize(900, 500) # BASE STRUTURE area = QtWidgets.QWidget() self.setCentralWidget(area) layout = QtWidgets.QVBoxLayout() area.setLayout(layout) #header = QtWidgets.QHBoxLayout() # layout.addLayout(header) # grab the default text color of a qlabel to color all links from blue to it: # LEFT TEXT info = QtWidgets.QLabel(left_header) info.setOpenExternalLinks(True) # LOGO header = QtWidgets.QWidget() header.setFixedHeight(70) headerlayout = QtWidgets.QHBoxLayout() header.setLayout(headerlayout) logo = QtSvg.QSvgWidget(icon) logo.setFixedWidth(50) logo.setFixedHeight(50) headerlayout.addWidget(logo) headerlayout.addWidget(info) layout.addWidget(header) # RIGHT_HEADER userGuide = QtWidgets.QLabel(right_header) userGuide.setOpenExternalLinks(True) userGuide.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight) headerlayout.addWidget(userGuide) # ROOT-PATH OF THE SESSIONS rootLayout = QtWidgets.QHBoxLayout() rootFrame = QtWidgets.QFrame() rootFrame.setFrameStyle( QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Plain) rootFrame.setFixedHeight(45) rootFrame.setLineWidth(0) rootFrame.setLayout(rootLayout) layout.addWidget(rootFrame) self.rootDir = QtWidgets.QLabel() self.rootDir.setAutoFillBackground(True) self.rootDir.setStyleSheet("QLabel { background-color: white; }") # FILE-BROWSER self.treeView = _TreeView() self.fileSystemModel = _FileSystemModel(self.treeView, file_type) self.fileSystemModel.setNameFilters(['*.%s' % file_type]) self.fileSystemModel.setNameFilterDisables(False) self.treeView.setModel(self.fileSystemModel) treelayout = QtWidgets.QHBoxLayout() splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation(1)) self.fileInfo = _PyzInfo(splitter, self.fileSystemModel, self.treeView) self.treeView.clicked.connect(self.fileInfo.update) splitter.addWidget(self.treeView) splitter.addWidget(self.fileInfo) treelayout.addWidget(splitter) layout.addLayout(treelayout) # get last root-path self._path = PathStr('') if CONFIG_FILE: try: self._path = PathStr( open( CONFIG_FILE, 'r').read().decode('unicode-escape')) except IOError: pass # file not existant if not self._path or not self._path.exists(): msgBox = QtWidgets.QMessageBox() msgBox.setText("Please choose your projectDirectory.") msgBox.exec_() self._changeRootDir() self.treeView.setPath(self._path) abspath = os.path.abspath(self._path) self.rootDir.setText(abspath) rootLayout.addWidget(self.rootDir) # GO UPWARDS ROOT-PATH BUTTON btnUpRootDir = QtWidgets.QPushButton('up') btnUpRootDir.clicked.connect(self._goUpRootDir) rootLayout.addWidget(btnUpRootDir) # DEFINE CURRENT DIR AS ROOT-PATH btnDefineRootDir = QtWidgets.QPushButton('set') btnDefineRootDir.clicked.connect(self._defineRootDir) rootLayout.addWidget(btnDefineRootDir) # SELECT ROOT-PATH BUTTON buttonRootDir = QtWidgets.QPushButton('select') buttonRootDir.clicked.connect(self._changeRootDir) rootLayout.addWidget(buttonRootDir) # NEW-BUTTON if self._start_script: newButton = QtWidgets.QPushButton('NEW') newButton.clicked.connect(self._openNew) layout.addWidget(newButton) @staticmethod def rootDir(): try: return PathStr( open(CONFIG_FILE, 'r').read().decode('unicode-escape')) except IOError: # create starter return PathStr.home() def _goUpRootDir(self): self._setRootDir(self._path.dirname()) def _defineRootDir(self): i = self.treeView.selectedIndexes() # if not self.treeView.isIndexHidden(i): if i: if self.fileSystemModel.isDir(i[0]): self._setRootDir(PathStr(self.fileSystemModel.filePath(i[0]))) def _changeRootDir(self): path = self.dialogs.getExistingDirectory() if path: self._setRootDir(path) def _setRootDir(self, path): self._path = path self.rootDir.setText(self._path) root = self.fileSystemModel.setRootPath(self._path) self.treeView.setRootIndex(root) # save last path to file if CONFIG_FILE: open(CONFIG_FILE, 'w').write(self._path.encode('unicode-escape')) def _openNew(self): p = spawn.find_executable("python") os.spawnl(os.P_NOWAIT, p, 'python', '%s' % self._start_script)
class Session(QtCore.QObject): """Session management to be accessible in QtWidgets.QApplication.instance().session * extract the opened (as pyz-zipped) session in a temp folder * create 2nd temp-folder for sessions to be saved * send a close signal to all child structures when exit * write a log file with all output * enable icons in menus of gnome-sessions [linux only] * gives option of debug mode """ # sigPathChanged = QtCore.Signal(object) #path sigSave = QtCore.Signal(object) # state dict sigRestore = QtCore.Signal(object) # state dict def __init__(self, args, **kwargs): """ Args: first_start_dialog (Optional[bool]): Show a different dialog for the first start. name (Optional[str]): The applications name. type (Optional[str]): The file type to be used for saving sessions. icon (Optional[str]): Path to the application icon. """ QtCore.QObject.__init__(self) # SESSION CONSTANTS: self.NAME = kwargs.get('name', __main__.__name__) self.FTYPE = kwargs.get('ftype', 'pyz') self.ICON = kwargs.get('icon', None) # hidden app-preferences folder: self.dir = PathStr.home().mkdir('.%s' % self.NAME) self.APP_CONFIG_FILE = self.dir.join('config.txt') self._tmp_dir_session = None # session specific options: self.opts = _Opts({ 'maxSessions': 3, 'enableGuiIcons': True, 'writeToShell': True, 'createLog': False, 'debugMode': False, 'autosave': False, 'autosaveIntervalMin': 15, 'server': False, }, self) # global options - same for all new and restored sessions: self.app_opts = {'showCloseDialog': True, 'recent sessions': []} if not self.APP_CONFIG_FILE.exists(): # allow different first start dialog: dialog = kwargs.get('first_start_dialog', FirstStart) f = dialog(self) f.exec_() if not f.result(): sys.exit() # create the config file with open(self.APP_CONFIG_FILE, 'w') as f: pass else: with open(self.APP_CONFIG_FILE, 'r') as f: r = f.read() if r: self.app_opts.update(eval(r)) self._icons_enabled = False self.log_file = None dirname = self.app_opts['recent sessions'] if dirname: dirname = PathStr(dirname[-1]).dirname() self.dialogs = Dialogs(dirname) self.saveThread = _SaveThread() self._createdAutosaveFile = None self.tmp_dir_save_session = None # a work-dir for temp. storage: # self.tmp_dir_work = PathStr(tempfile.mkdtemp('%s_work' % self.NAME)) pathName = self._inspectArguments(args) self.setSessionPath(pathName) if self.opts['createLog']: self._setupLogFile() # create connectable stdout and stderr signal: self.streamOut = StreamSignal('out') self.streamErr = StreamSignal('err') self._enableGuiIcons() # Auto-save timer: self.timerAutosave = QtCore.QTimer() self.timerAutosave.timeout.connect(self._autoSave) self.opts.activate() # first thing to do after start: QtCore.QTimer.singleShot(0, self.restoreCurrentState) def setSessionPath(self, path, statename=None): if path: # and path.endswith('.%s' %self.FTYPE): # this script was opened out from a zip-container (named as # '*.pyz') self.path = PathStr(path) self.dir = self.path.dirname().abspath() # extract the zip temporally ZipFile(self.path, 'r').extractall(path=self.tmp_dir_session) self.n_sessions = len(self.stateNames()) # SET STATE snames = self.stateNames() if statename is None: # last one self.current_session = snames[-1] elif statename in snames: self.current_session = statename else: raise Exception( "state '%s' not in saved states %s" % (statename, snames)) else: self.path = None self.n_sessions = 0 self.current_session = None def writeLog(self, write=True): if not self.log_file: return so = self.streamOut.message se = self.streamErr.message w = self.log_file.write if write: try: # ensure only connected once so.disconnect(w) se.disconnect(w) except TypeError: pass so.connect(w) se.connect(w) else: try: so.disconnect(w) se.disconnect(w) except TypeError: pass def _enableGuiIcons(self): # enable icons in all QMenuBars only for this program if generally # disabled if self.opts['enableGuiIcons']: if os.name == 'posix': # linux this_env = str(os.environ.get('DESKTOP_SESSION')) relevant_env = ( 'gnome', 'gnome-shell', 'ubuntustudio', 'xubuntu') if this_env in relevant_env: if 'false' in os.popen( # if the menu-icons on the gnome-desktop are disabled 'gconftool-2 --get /desktop/gnome/interface/menus_have_icons').read(): print('enable menu-icons') os.system( 'gconftool-2 --type Boolean --set /desktop/gnome/interface/menus_have_icons True') self._icons_enabled = True def _setupLogFile(self): lfile = self.tmp_dir_session.join('log.txt') if lfile.exists(): self.log_file = open(lfile, 'a') else: self.log_file = open(lfile, 'w') self.log_file.write(''' #################################### New run at %s #################################### ''' % strftime("%d.%m.%Y|%H:%M:%S", gmtime())) def checkMaxSessions(self, nMax=None): """ check whether max. number of saved sessions is reached if: remove the oldest session """ if nMax is None: nMax = self.opts['maxSessions'] l = self.stateNames() if len(l) > nMax: for f in l[:len(l) - nMax]: self.tmp_dir_session.remove(str(f)) def stateNames(self): """Returns: list: the names of all saved sessions """ if self.current_session: s = self.tmp_dir_session l = s.listdir() l = [x for x in l if s.join(x).isdir()] naturalSorting(l) else: l=[] # bring autosave to first position: if 'autoSave' in l: l.remove('autoSave') l.insert(0, 'autoSave') return l def restorePreviousState(self): s = self.stateNames() if s: i = s.index(self.current_session) if i > 1: self.current_session = s[i - 1] self.restoreCurrentState() def restoreNextState(self): s = self.stateNames() if s: i = s.index(self.current_session) if i < len(s) - 1: self.current_session = s[i + 1] self.restoreCurrentState() def restoreStateName(self, name): """restore the state of given [name]""" self.current_session = name self.restoreCurrentState() def renameState(self, oldStateName, newStateName): s = self.tmp_dir_session.join(oldStateName) s.rename(newStateName) if self.current_session == oldStateName: self.current_session = newStateName print("==> State [%s] renamed to [%s]" % (oldStateName, newStateName)) def _recusiveReplacePlaceholderWithArray(self, state, arrays): def recursive(state): for key, val in list(state.items()): if isinstance(val, dict): recursive(val) elif isinstance(val, str) and val.startswith('arr_'): state[key] = arrays[val] recursive(state) def restoreCurrentState(self): if self.current_session: orig = self.tmp_dir_save_session path = self.tmp_dir_save_session = self.tmp_dir_session.join( self.current_session) with open(path.join('state.pickle'), "rb") as f: state = pickle.load(f) p = path.join('arrays.npz') if p.exists(): arrays = np.load(path.join('arrays.npz')) self._recusiveReplacePlaceholderWithArray(state, arrays) self.dialogs.restoreState(state['dialogs']) self.opts.update(state['session']) self.sigRestore.emit(state) self.tmp_dir_save_session = orig print( "==> State [%s] restored from '%s'" % (self.current_session, self.path)) def addSession(self): self.current_session = self.n_sessions self.n_sessions += 1 self.tmp_dir_save_session = self.tmp_dir_session.join( str(self.n_sessions)).mkdir() self.checkMaxSessions() def quit(self): print('exiting...') # RESET ICONS if self._icons_enabled: print('disable menu-icons') os.system( # restore the standard-setting for seeing icons in the menus 'gconftool-2 --type Boolean --set /desktop/gnome/interface/menus_have_icons False') # WAIT FOR PROMT IF IN DEBUG MODE if self.opts['debugMode']: input("Press any key to end the session...") # REMOVE TEMP FOLDERS try: self.tmp_dir_session.remove() # self.tmp_dir_work.remove() except OSError: pass # in case the folders are used by another process with open(self.APP_CONFIG_FILE, 'w') as f: f.write(str(self.app_opts)) # CLOSE LOG FILE if self.log_file: self.writeLog(False) self.log_file.close() def _inspectArguments(self, args): """inspect the command-line-args and give them to appBase""" if args: self.exec_path = PathStr(args[0]) else: self.exec_path = None session_name = None args = args[1:] openSession = False for arg in args: if arg in ('-h', '--help'): self._showHelp() elif arg in ('-d', '--debug'): print('RUNNGING IN DEBUG-MODE') self.opts['debugMode'] = True elif arg in ('-l', '--log'): print('CREATE LOG') self.opts['createLog'] = True elif arg in ('-s', '--server'): self.opts['server'] = True elif arg in ('-o', '--open'): openSession = True elif openSession: session_name = arg else: print("Argument '%s' not known." % arg) return self._showHelp() return session_name def _showHelp(self): sys.exit(''' %s-sessions can started with the following arguments: [-h or --help] - show the help-page [-d or --debug] - run in debugging-mode [-l or --log] - create log file [-n or --new] - start a new session, don'l load saved properties [-exec [cmd]] - execute python code from this script/executable ''' % self.__class__.__name__) def save(self): """save the current session override, if session was saved earlier""" if self.path: self._saveState(self.path) else: self.saveAs() def saveAs(self, filename=None): if filename is None: # ask for filename: filename = self.dialogs.getSaveFileName(filter="*.%s" % self.FTYPE) if filename: self.path = filename self._saveState(self.path) if self._createdAutosaveFile: self._createdAutosaveFile.remove() print( "removed automatically created '%s'" % self._createdAutosaveFile) self._createdAutosaveFile = None def replace(self, path): """ replace current session with one given by file path """ self.setSessionPath(path) self.restoreCurrentState() def open(self): """open a session to define in a dialog in an extra window""" filename = self.dialogs.getOpenFileName(filter="*.%s" % self.FTYPE) if filename: self.new(filename) def new(self, filename=None): """start a session an independent process""" path = (self.exec_path,) if self.exec_path.filetype() in ('py', 'pyw', 'pyz', self.FTYPE): # get the absolute path to the python-executable p = find_executable("python") path = (p, 'python') + path else: # if run in frozen env (.exe): # first arg if execpath of the next session: path += (self.exec_path,) if filename: path += ('-o', filename) os.spawnl(os.P_NOWAIT, *path) def registerMainWindow(self, win): win.setWindowIcon(QtGui.QIcon(self.ICON)) self._mainWindow = win win.show = self._showMainWindow win.hide = self._hideMainWindow if self.opts['server']: server_ = Server(win) win.hide() else: win.show() @property def tmp_dir_session(self): #only create folder if needed if self._tmp_dir_session is None: # make temp-dir # the directory where the content of the *pyz-file will be copied: self._tmp_dir_session = PathStr( tempfile.mkdtemp( '%s_session' % self.NAME)) return self._tmp_dir_session def _showMainWindow(self): try: # restore autosave del self._autosave except AttributeError: pass self._mainWindow.__class__.show(self._mainWindow) def _hideMainWindow(self): # disable autosave on hidden window self._autosave = self.opts['autosave'] self.opts['autosave'] = False self._mainWindow.__class__.hide(self._mainWindow) def _saveState(self, path): """save current state and add a new state""" self.addSession() # next session self._save(str(self.n_sessions), path) def _autoSave(self): """save state into 'autosave' """ a = 'autoSave' path = self.path if not path: path = self.dir.join('%s.%s' % (a, self.FTYPE)) self._createdAutosaveFile = path self.tmp_dir_save_session = self.tmp_dir_session.join(a).mkdir() self._save(a, path) def blockingSave(self, path): """ saved session to file - returns after finish only called by interactiveTutorial-save at the moment """ self.tmp_dir_save_session = self.tmp_dir_session.join('block').mkdir() state = {'session': dict(self.opts), 'dialogs': self.dialogs.saveState()} self.saveThread.prepare('0', path, self.tmp_dir_session, state) self.sigSave.emit(self) self.saveThread.run() def _save(self, stateName, path): """save into 'stateName' to pyz-path""" print('saving...') state = {'session': dict(self.opts), 'dialogs': self.dialogs.saveState()} self.sigSave.emit(state) self.saveThread.prepare(stateName, path, self.tmp_dir_session, state) self.saveThread.start() self.current_session = stateName r = self.app_opts['recent sessions'] try: # is this session already exists: remove it r.pop(r.index(path)) except ValueError: pass # add this session at the beginning r.insert(0, path)