def removeConfigurationData(): """ Remove the eric configuration directory. """ try: from PyQt5.QtCore import QSettings except ImportError: try: from PyQt4.QtCore import QSettings except ImportError: print("No PyQt variant installed. The configuration directory") print("cannot be determined. You have to remove it manually.\n") return settings = QSettings(QSettings.IniFormat, QSettings.UserScope, settingsNameOrganization, settingsNameGlobal) settingsDir = os.path.dirname(settings.fileName()) if os.path.exists(settingsDir): print("Found the eric configuration directory") print(" - {0}".format(settingsDir)) answer = "c" while answer not in ["y", "Y", "n", "N", ""]: if sys.version_info[0] == 2: answer = raw_input( "Shall this directory be removed (y/N)? ") else: answer = input( "Shall this directory be removed (y/N)? ") if answer in ["y", "Y"]: shutil.rmtree(settings)
class Settings(object): CORE = "core" LANGUAGES = CORE + "/languages" ENCODING = CORE + "/encoding" WINDOW = "window" WINDOW_GEOMETRY = WINDOW + "/geometry" WINDOW_STATE = WINDOW + "/state" def __init__(self): super().__init__() self._settings = QSettings() self._settings.setIniCodec('UTF-8') logger.debug("Settings file: '{}'".format(self._settings.fileName())) def get(self, name: str) -> Any: try: value = self._settings.value(name) except Exception as e: logger.warn(f"Error getting setting '{name}': {e}") return value def set(self, name: str, value: Any): try: self._settings.setValue(name, value) except Exception as e: logger.warn(f"Error setting setting '{name}' to '{value}': {e}")
def readSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, self.name_company, self.name_product) print(settings.fileName()) pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size)
def removeConfigurationData(): """ Remove the eric configuration directory. """ try: from PyQt5.QtCore import QSettings except ImportError: try: from PyQt4.QtCore import QSettings except ImportError: print("No PyQt variant installed. The configuration directory") print("cannot be determined. You have to remove it manually.\n") return settings = QSettings(QSettings.IniFormat, QSettings.UserScope, settingsNameOrganization, settingsNameGlobal) settingsDir = os.path.dirname(settings.fileName()) if os.path.exists(settingsDir): print("Found the Pymakr configuration directory") print(" - {0}".format(settingsDir)) answer = "c" while answer not in ["y", "Y", "n", "N", ""]: if sys.version_info[0] == 2: answer = raw_input("Shall this directory be removed (y/N)? ") else: answer = input("Shall this directory be removed (y/N)? ") if answer in ["y", "Y"]: shutil.rmtree(settingsDir)
def updateLocationsTable(self): self.locationsTable.setUpdatesEnabled(False) self.locationsTable.setRowCount(0) for i in range(2): if i == 0: if self.scope() == QSettings.SystemScope: continue actualScope = QSettings.UserScope else: actualScope = QSettings.SystemScope for j in range(2): if j == 0: if not self.application(): continue actualApplication = self.application() else: actualApplication = "" settings = QSettings( self.format(), actualScope, self.organization(), actualApplication ) row = self.locationsTable.rowCount() self.locationsTable.setRowCount(row + 1) item0 = QTableWidgetItem() item0.setText(settings.fileName()) item1 = QTableWidgetItem() disable = not (settings.childKeys() or settings.childGroups()) if row == 0: if settings.isWritable(): item1.setText("Read-write") disable = False else: item1.setText("Read-only") self.buttonBox.button(QDialogButtonBox.Ok).setDisabled(disable) else: item1.setText("Read-only fallback") if disable: item0.setFlags(item0.flags() & ~Qt.ItemIsEnabled) item1.setFlags(item1.flags() & ~Qt.ItemIsEnabled) self.locationsTable.setItem(row, 0, item0) self.locationsTable.setItem(row, 1, item1) self.locationsTable.setUpdatesEnabled(True)
def updateLocationsTable(self): self.locationsTable.setUpdatesEnabled(False) self.locationsTable.setRowCount(0) for i in range(2): if i == 0: if self.scope() == QSettings.SystemScope: continue actualScope = QSettings.UserScope else: actualScope = QSettings.SystemScope for j in range(2): if j == 0: if not self.application(): continue actualApplication = self.application() else: actualApplication = '' settings = QSettings(self.format(), actualScope, self.organization(), actualApplication) row = self.locationsTable.rowCount() self.locationsTable.setRowCount(row + 1) item0 = QTableWidgetItem() item0.setText(settings.fileName()) item1 = QTableWidgetItem() disable = not (settings.childKeys() or settings.childGroups()) if row == 0: if settings.isWritable(): item1.setText("Read-write") disable = False else: item1.setText("Read-only") self.buttonBox.button(QDialogButtonBox.Ok).setDisabled(disable) else: item1.setText("Read-only fallback") if disable: item0.setFlags(item0.flags() & ~Qt.ItemIsEnabled) item1.setFlags(item1.flags() & ~Qt.ItemIsEnabled) self.locationsTable.setItem(row, 0, item0) self.locationsTable.setItem(row, 1, item1) self.locationsTable.setUpdatesEnabled(True)
class DockDialog(QMainWindow): def __init__(self): super().__init__() self.title = "PyQt5 StackedWidget" self.top = 200 self.left = 500 self.width = 400 self.height = 300 self.setWindowIcon(QtGui.QIcon("icon.png")) self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.createDockWidget() self.settings = QSettings("PyEcog", "PyEcog_tests") print("reading cofigurations from: " + self.settings.fileName()) self.settings.beginGroup("MainWindow") self.restoreGeometry( self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) self.show() def createDockWidget(self): menubar = self.menuBar() file = menubar.addMenu("File") file.addAction("New") file.addAction("Save") file.addAction("Close") self.dock = QDockWidget("Dockable", self) self.listWiget = QListWidget() list = ["Python", "C++", "Java", "C#"] self.listWiget.addItems(list) self.dock.setWidget(self.listWiget) self.dock.setFloating(False) self.dock.setObjectName("DockableList") self.setCentralWidget(QTextEdit()) self.addDockWidget(Qt.RightDockWidgetArea, self.dock) def closeEvent(self, event): print('closing') settings = QSettings("PyEcog", "PyEcog_tests") settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() self.saveState()
def getLastSaveFolder(self): """ Try to get a selected folder from QSettings file (Mac: ~\library\preferences\) Defaults to userfolder ~ ### Returns: {str}: folder path """ try: settings = QSettings('vmsParser', 'vmsParser') print(settings.fileName()) lastFolder = settings.value('saveFolder', type=str) except: lastFolder = os.path.expanduser('~') return lastFolder
def restoreSettings(fileName, widget): settings = QSettings(fileName, QSettings.IniFormat) try: for obj in widget.findChildren(QWidget): if obj.metaObject().className() == 'QSpinBox': obj.setValue(int(settings.value(obj.objectName()))) if obj.metaObject().className() == 'QDoubleSpinBox': obj.setValue(float(settings.value(obj.objectName()))) if obj.metaObject().className() == 'QCheckBox': obj.setCheckState(int(settings.value(obj.objectName()))) #if obj.metaObject().className() == 'QLineEdit': # obj.setText(str(settings.value(obj.objectName()))) except: print('Error loading ' + settings.fileName() + '. A new INI file will be created.') settings.remove('') # Clear the Settings.ini file
def __init__(self,dbfile,port): self._con=connect(dbfile) from PyQt5.QtCore import QSettings from os import sep from os.path import dirname, join, exists setting = QSettings(QSettings.IniFormat, QSettings.UserScope, 'KonkukUniv', 'rebauth') setting.isWritable() #it makes setting folder if doesn't exist self._settingPath = dirname(setting.fileName().replace('/',sep)) self._cursor = connect(join(self._settingPath,dbfile)) self._cursor.row_factory = Row self._cursor=self._cursor.cursor() dbInitialQueries=(('Cert', 'PIN int default 0', 'ipaddr text not null', 'port int not null','key blob not null','cnt blob not null') ,('Tactics', 'ID int NOT NULL', 'exec_order int default 0', 'type int NOT NULL', 'script text') ,('Strategy', 'URL text NOT NULL', 'type integer NOT NULL','hash blob', 'valid int default 0')) for q in dbInitialQueries: self.executeQuery('CREATE TABLE IF NOT EXISTS '+q[0]+'('+','.join(q[1:])+')') self._cursor.connection.commit() self.socketPool = ClientSocketPool(port, self)
# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import markups import markups.common from os.path import dirname, exists, join from PyQt5.QtCore import QByteArray, QLocale, QSettings, QStandardPaths from PyQt5.QtGui import QFont app_version = "5.3.0" settings = QSettings("ReText project", "ReText") print("Using configuration file:", settings.fileName()) if not str(settings.fileName()).endswith(".conf"): # We are on Windows probably settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "ReText project", "ReText") try: import enchant import enchant.errors except ImportError: enchant_available = False enchant = None else: enchant_available = True try: enchant.Dict()
def __init__(self): QWidget.__init__(self) Ui_MainScreen.__init__(self) self.setupUi(self) self.settings = Settings() self.restore_settings_from_config() # quit app from settings window self.settings.sigExitOAS.connect(self.exit_oas) self.settings.sigRebootHost.connect(self.reboot_host) self.settings.sigShutdownHost.connect(self.shutdown_host) self.settings.sigConfigFinished.connect(self.config_finished) self.settings.sigConfigClosed.connect(self.config_closed) settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("General") if settings.value('fullscreen', True, type=bool): self.showFullScreen() app.setOverrideCursor(QCursor(Qt.BlankCursor)) settings.endGroup() print("Loading Settings from: ", settings.fileName()) self.labelWarning.hide() # init warning prio array (0-2 self.warnings = ["", "", ""] # add hotkey bindings QShortcut(QKeySequence("Ctrl+F"), self, self.toggle_full_screen) QShortcut(QKeySequence("F"), self, self.toggle_full_screen) QShortcut(QKeySequence(16777429), self, self.toggle_full_screen) # 'Display' Key on OAS USB Keyboard QShortcut(QKeySequence(16777379), self, self.shutdown_host) # 'Calculator' Key on OAS USB Keyboard QShortcut(QKeySequence("Ctrl+Q"), self, self.quit_oas) QShortcut(QKeySequence("Q"), self, self.quit_oas) QShortcut(QKeySequence("Ctrl+C"), self, self.quit_oas) QShortcut(QKeySequence("ESC"), self, self.quit_oas) QShortcut(QKeySequence("Ctrl+S"), self, self.show_settings) QShortcut(QKeySequence("Ctrl+,"), self, self.show_settings) QShortcut(QKeySequence(" "), self, self.radio_timer_start_stop) QShortcut(QKeySequence(","), self, self.radio_timer_start_stop) QShortcut(QKeySequence("."), self, self.radio_timer_start_stop) QShortcut(QKeySequence("0"), self, self.radio_timer_reset) QShortcut(QKeySequence("R"), self, self.radio_timer_reset) QShortcut(QKeySequence("1"), self, self.manual_toggle_led1) QShortcut(QKeySequence("2"), self, self.manual_toggle_led2) QShortcut(QKeySequence("3"), self, self.manual_toggle_led3) QShortcut(QKeySequence("4"), self, self.manual_toggle_led4) QShortcut(QKeySequence("M"), self, self.toggle_air1) QShortcut(QKeySequence("/"), self, self.toggle_air1) QShortcut(QKeySequence("P"), self, self.toggle_air2) QShortcut(QKeySequence("*"), self, self.toggle_air2) QShortcut(QKeySequence("S"), self, self.toggle_air4) QShortcut(QKeySequence("Enter"), self, self.get_timer_dialog) QShortcut(QKeySequence("Return"), self, self.get_timer_dialog) self.statusLED1 = False self.statusLED2 = False self.statusLED3 = False self.statusLED4 = False self.LED1on = False self.LED2on = False self.LED3on = False self.LED4on = False # Setup and start timers self.ctimer = QTimer() self.ctimer.timeout.connect(self.constant_update) self.ctimer.start(100) # LED timers self.timerLED1 = QTimer() self.timerLED1.timeout.connect(self.toggle_led1) self.timerLED2 = QTimer() self.timerLED2.timeout.connect(self.toggle_led2) self.timerLED3 = QTimer() self.timerLED3.timeout.connect(self.toggle_led3) self.timerLED4 = QTimer() self.timerLED4.timeout.connect(self.toggle_led4) # Setup OnAir Timers self.timerAIR1 = QTimer() self.timerAIR1.timeout.connect(self.update_air1_seconds) self.Air1Seconds = 0 self.statusAIR1 = False self.timerAIR2 = QTimer() self.timerAIR2.timeout.connect(self.update_air2_seconds) self.Air2Seconds = 0 self.statusAIR2 = False self.timerAIR3 = QTimer() self.timerAIR3.timeout.connect(self.update_air3_seconds) self.Air3Seconds = 0 self.statusAIR3 = False self.radioTimerMode = 0 # count up mode self.timerAIR4 = QTimer() self.timerAIR4.timeout.connect(self.update_air4_seconds) self.Air4Seconds = 0 self.statusAIR4 = False self.streamTimerMode = 0 # count up mode # Setup NTP Check Thread self.checkNTPOffset = CheckNTPOffsetThread(self) # Setup check NTP Timer self.ntpHadWarning = True self.ntpWarnMessage = "" self.timerNTP = QTimer() self.timerNTP.timeout.connect(self.trigger_ntp_check) # initial check self.timerNTP.start(1000) # Setup UDP Socket self.udpsock = QUdpSocket() settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("Network") port = int(settings.value('udpport', 3310)) settings.endGroup() self.udpsock.bind(port, QUdpSocket.ShareAddress) self.udpsock.readyRead.connect(self.udp_cmd_handler) # Setup HTTP Server self.httpd = HttpDaemon(self) self.httpd.start() # display all host addresses self.display_all_hostaddresses() # set NTP warning settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("NTP") if settings.value('ntpcheck', True, type=bool): self.ntpHadWarning = True self.ntpWarnMessage = "waiting for NTP status check" settings.endGroup() # do initial update check self.settings.sigCheckForUpdate.emit()
# You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys import markups import markups.common from os.path import dirname, exists, join from PyQt5.QtCore import QByteArray, QLocale, QSettings, QStandardPaths from PyQt5.QtGui import QFont app_version = "7.1.0 (Git)" settings = QSettings('ReText project', 'ReText') if not str(settings.fileName()).endswith('.conf'): # We are on Windows probably settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ReText project', 'ReText') datadirs = [] def initializeDataDirs(): assert not datadirs try: datadirs.append(dirname(dirname(__file__))) except NameError: pass
class UserSettings(QObject): """ The root of all user settings. """ @inject def __init__(self, configuration: Configuration): super().__init__() QSettings.setDefaultFormat(QSettings.IniFormat) QSettings.setPath(QSettings.IniFormat, QSettings.UserScope, configuration.user_settings_directory) # We fake the application and organisation name to try and use a "nicer" directory for settings. # However, an organisation name is required to ensure this works properly on all systems. # See issues like this: https://www.qtcentre.org/threads/35300-QSplitter-restoreState()-not-working self.__store = QSettings(QSettings.IniFormat, QSettings.UserScope, "nasm", "settings", self) def save(self, key: Union[Key, str], value: any): """ Save a value in the user's settings. """ self.__store.setValue(UserSettings.__key_value(key), value) def save_as_json(self, key: Union[Key, str], value: any): """ Saves the given object in json. Useful for simple types when not natively supported. """ self.__store.setValue(UserSettings.__key_value(key), json.dumps(value)) def save_as_binary(self, key: Union[Key, str], value: any): """ Saves the given object in binary. Useful when other formats are not possible. """ self.__store.setValue(UserSettings.__key_value(key), pickle.dumps(value)) def get(self, key: Union[Key, str], default: any = None) -> any: """ Get a value from the user's settings. Returns the given default if not found, or None otherwise. """ return self.__store.value(UserSettings.__key_value(key), default) def get_from_json(self, key: Union[Key, str], default: any = None): """ Get a value from stored JSON. """ value = self.__store.value(UserSettings.__key_value(key), NOT_FOUND_VALUE) if value == NOT_FOUND_VALUE: return default else: return json.loads(value) def get_from_binary(self, key: Union[Key, str], default: any = None): """ Get a value from stored binary.""" value = self.__store.value(UserSettings.__key_value(key), NOT_FOUND_VALUE) if value == NOT_FOUND_VALUE: return default else: return pickle.loads(value) def save_widget(self, widget: QWidget, group: Union[Key, str]): """ Save a widget's state to the user's settings. """ save_widget(self.__store, widget, UserSettings.__key_value(group)) def restore_widget(self, widget: QWidget, group: Union[Key, str]): """ Restore a widget's state from a user's settings. """ restore_widget(self.__store, widget, UserSettings.__key_value(group)) def file_path(self) -> str: """ Get the file path for the user settings. """ return self.__store.fileName() @staticmethod def __key_value(key: Union[Key, str]): return key if isinstance(key, str) else key.value def clear(self): """ Remove all keys from settings. This is a simple way to ensure the settings file does not grow due to dynamic keys. """ for key in list(self.__store.allKeys()): self.__store.remove(key) def sync(self): """ Ensure the settings file is updated. """ self.__store.sync()
class Config: def __init__(self, filename=None): # Get the config file location if one is specified. self.filename = filename or os.getenv('CAPNSNAP_CONFIG', None) self.qsettings = QSettings("Cap-n_Snap", "Cap-n_Snap") self.hooks = { 'beforeLoad': [], 'afterLoad': [], 'beforeSave': [], 'afterSave': [], } self.load() #enddef def execHooks(self, name): for (hook, userdata) in self.hooks[name]: hook(userdata) #endfor #enddef def hook(self, hn, hook, userdata=None): self.hooks[hn].append((hook, userdata)) #enddef def unhook(self, hn, hook): found = False for i, (fun, userdata) in enumerate(self.hooks[hn]): if fun == hook: found = True break #endif #endfor if found: self.hooks[hn].pop(i) #endif #enddef def beforeLoad(self, hook, userdata=None): self.hook('beforeLoad', hook, userdata) def afterLoad(self, hook, userdata=None): self.hook('afterLoad', hook, userdata) def beforeSave(self, hook, userdata=None): self.hook('beforeSave', hook, userdata) def afterSave(self, hook, userdata=None): self.hook('afterSave', hook, userdata) def load(self): self.execHooks('beforeLoad') if self.filename: log.debug('Loading config from {filename}', filename=self.filename) self.config = json.load(self.filename) else: log.debug('Loading config from {filename}', filename=self.qsettings.fileName()) self.qsettings.sync() self.config = json.loads(self.qsettings.value('settings', '{}')) #endif self.execHooks('afterLoad') #enddef def save(self): self.execHooks('beforeSave') if self.filename: log.debug('Saving config to {filename}', filename=self.filename) json.dump(self.filename, self.config) else: log.debug('Saving config to {filename}', filename=self.qsettings.fileName()) self.qsettings.setValue('settings', json.dumps(self.config)) self.qsettings.sync() #endif self.execHooks('afterSave') #enddef def get(self, key, default=None): return self.config.get(key, default) #enddef def set(self, key, value): self.config[key] = value self.save()
class PiVideoStream(QThread): signals = ObjectSignals() snapshotTaken = pyqtSignal() clipRecorded = pyqtSignal() camera = PiCamera() ## @param ins is the number of instances created. This may not exceed 1. ins = 0 def __init__(self): super().__init__() ## Instance limiter. Checks if an instance exists already. If so, it deletes the current instance. if PiVideoStream.ins >= 1: del self self.msg( "error; multiple instances of {:s} created, while only 1 instance is allowed" .format(__class__.__name__)) return try: PiVideoStream.ins += 1 except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) warnings.filterwarnings('default', category=DeprecationWarning) self.settings = QSettings("settings.ini", QSettings.IniFormat) self.loadSettings() self.initStream() def loadSettings(self): self.msg("info; loading camera settings from " + self.settings.fileName()) frame_size_str = self.settings.value('camera/frame_size') (width, height) = frame_size_str.split('x') self.camera.resolution = raw_frame_size((int(width), int(height))) self.camera.framerate = int(self.settings.value('camera/frame_rate')) self.camera.image_effect = self.settings.value('camera/effect') self.camera.shutter_speed = int( self.settings.value('camera/shutter_speed')) self.camera.iso = int(self.settings.value( 'camera/iso')) # should force unity analog gain # set parameters for speed frame_size_str = self.settings.value('image_frame_size') (width, height) = frame_size_str.split('x') self.image_size = (int(width), int(height)) self.camera.video_denoise = False self.monochrome = True self.use_video_port = True # dunno if setting awb mode manually is really useful ## self.camera.awb_mode = 'off' ## self.camera.awb_gains = 5.0 ## self.camera.meter_mode = 'average' ## self.camera.exposure_mode = 'auto' # 'sports' to reduce motion blur, 'off'after init to freeze settings @pyqtSlot() def initStream(self): # Initialize the camera stream if self.isRunning(): # in case init gets called, while thread is running self.msg("info; video stream is running already") else: # init camera and open stream if self.monochrome: ## self.camera.color_effects = (128,128) # return monochrome image, not required if we take Y frame only. self.rawCapture = PiYArray(self.camera, size=self.camera.resolution) self.stream = self.camera.capture_continuous( self.rawCapture, 'yuv', self.use_video_port) else: self.rawCapture = PiRGBArray(self.camera, size=self.camera.resolution) self.stream = self.camera.capture_continuous( self.rawCapture, 'bgr', self.use_video_port) # allocate memory self.frame = np.empty(self.camera.resolution + (1 if self.monochrome else 3, ), dtype=np.uint8) # restart thread self.start() self.wait_ms(1000) self.msg("info; video stream initialized with frame size = " + str(self.camera.resolution)) @pyqtSlot() def run(self): try: self.fps = FPS().start() for f in self.stream: if self.isInterruptionRequested(): self.signals.finished.emit() return ## self.rawCapture.truncate(0) # Depricated: clear the stream in preparation for the next frame self.rawCapture.seek(0) self.frame = f.array # grab the frame from the stream self.signals.ready.emit() self.signals.result.emit( cv2.resize( self.frame, self.image_size)) # resize to speed up processing self.fps.update() except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) @pyqtSlot() def stop(self): self.msg("info; stopping") if self.isRunning(): self.requestInterruption() self.wait_signal(self.signals.finished, 2000) self.fps.stop() self.quit() ## self.frame.fill(0) # clear frame, not allowed since frame is read-only? ## self.signals.ready.emit() ## self.signals.result.emit(np.zeros(self.image_size)) # could produce an information image here self.msg( "info; video stream stopped, approx. acquisition speed: {:.2f} fps" .format(self.fps.fps())) def msg(self, text): if text: text = self.__class__.__name__ + ";" + str(text) print(text) self.signals.message.emit(text) @pyqtSlot() def changeCameraSettings(self, frame_size=(640, 480), frame_rate=24, format='bgr', effect='none', use_video_port=False, monochrome=True): ''' The use_video_port parameter controls whether the camera’s image or video port is used to capture images. It defaults to False which means that the camera’s image port is used. This port is slow but produces better quality pictures. ''' self.stop() self.camera.resolution = raw_frame_size(frame_size) self.camera.framerate = frame_rate self.camera.image_effect = effect self.use_video_port = use_video_port self.monochrome = monochrome self.initStream() def wait_ms(self, timeout): ''' Block loop until timeout (ms) elapses. ''' loop = QEventLoop() QTimer.singleShot(timeout, loop.exit) loop.exec_() def wait_signal(self, signal, timeout=1000): ''' Block loop until signal emitted, or timeout (ms) elapses. ''' loop = QEventLoop() signal.connect(loop.quit) # only quit is a slot of QEventLoop QTimer.singleShot(timeout, loop.exit) loop.exec_() @pyqtSlot(str) def snapshot(self, filename_prefix=None): # open path (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_'.format(round(time.time() * 1000)) + tail + '.png' ]) # write image self.wait_signal(self.signals.ready, 5000) # wait for first frame to be shot cv2.imwrite(filename, self.frame) self.msg("info; image written to " + filename) @pyqtSlot(str, int) def recordClip(self, filename_prefix=None, duration=10): # open path (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_'.format(round(time.time() * 1000)) + tail + '.avi' ]) self.msg( "TODO; changing camera settings may get the process killed after several hours, probably better to open the stream in video resolution from the start if the videorecording is required!" ) # set video clip parameters frame_size_str = self.settings.value('camera/clip_frame_size') frame_size_str.split('x') frame_size = raw_frame_size((int(frame_size_str.split('x')[0]), int(frame_size_str.split('x')[1]))) frame_rate = int(self.settings.value('camera/clip_frame_rate')) self.changeCameraSettings(frame_size=frame_size, frame_rate=frame_rate, use_video_port=True, monochrome=False) # define the codec and create VideoWriter object fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(filename, fourcc, frame_rate, frame_size) self.msg("info; start recording video to " + filename) # write file for i in range(0, duration * frame_rate): self.signals.progress.emit( int(100 * i / (duration * frame_rate - 1))) self.wait_signal(self.signals.ready, 1000) if self.frame is not None: out.write(self.frame) # close out.release() self.msg("info; recording done") ## self.camera.start_recording(filename) ## self.camera.wait_recording(duration) ## self.camera.stop_recording() # revert to original parameters self.loadSettings() self.initStream() self.clipRecorded.emit()
SEPARATION_PADDING = .05 # Prozent # PROTOCOL TABLE COLORS SELECTED_ROW_COLOR = QColor.fromRgb(0, 0, 255) DIFFERENCE_CELL_COLOR = QColor.fromRgb(255, 0, 0) PROPERTY_FOUND_COLOR = QColor.fromRgb(0, 124, 0, 100) PROPERTY_NOT_FOUND_COLOR = QColor.fromRgb(124, 0, 0, 100) SEPARATION_ROW_HEIGHT = 30 SETTINGS = QSettings(QSettings.IniFormat, QSettings.UserScope, 'urh', 'urh') PROJECT_FILE = "URHProject.xml" DECODINGS_FILE = "decodings.txt" FIELD_TYPE_SETTINGS = os.path.realpath( os.path.join(SETTINGS.fileName(), "..", "fieldtypes.xml")) # DEVICE SETTINGS DEFAULT_IP_USRP = "192.168.10.2" DEFAULT_IP_RTLSDRTCP = "127.0.0.1" # DECODING NAMES DECODING_INVERT = "Invert" DECODING_DIFFERENTIAL = "Differential Encoding" DECODING_REDUNDANCY = "Remove Redundancy" DECODING_DATAWHITENING = "Remove Data Whitening (CC1101)" DECODING_CARRIER = "Remove Carrier" DECODING_BITORDER = "Change Bitorder" DECODING_EDGE = "Edge Trigger" DECODING_SUBSTITUTION = "Substitution" DECODING_EXTERNAL = "External Program"
# # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import markups import markups.common from os.path import dirname, exists, join from PyQt5.QtCore import QByteArray, QLocale, QSettings, QStandardPaths from PyQt5.QtGui import QFont app_version = "5.3.0" settings = QSettings('ReText project', 'ReText') if not str(settings.fileName()).endswith('.conf'): # We are on Windows probably settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ReText project', 'ReText') datadirs = QStandardPaths.standardLocations(QStandardPaths.GenericDataLocation) datadirs = [join(d, 'retext') for d in datadirs] if '__file__' in locals(): datadirs = [dirname(dirname(__file__))] + datadirs icon_path = 'icons/' for dir in datadirs: if exists(join(dir, 'icons')): icon_path = join(dir, 'icons/') break
# Copyright: Dmitry Shachnev 2012-2014 # License: GNU GPL v2 or higher import markups import markups.common import sys from os.path import join, abspath from PyQt5.QtCore import QByteArray, QLocale, QSettings, QStandardPaths from PyQt5.QtGui import QFont app_name = "ReText" app_version = "5.1.0" settings = QSettings('ReText project', 'ReText') print('Using configuration file:', settings.fileName()) if not str(settings.fileName()).endswith('.conf'): # We are on Windows probably settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ReText project', 'ReText') try: import enchant import enchant.errors except ImportError: enchant_available = False enchant = None else: enchant_available = True try:
class MainWindow(QMainWindow): ''' basically handles the combination of the the tree menu bar and the paired view Most of the code here is for setting up the geometry of the gui and the menu bar stuff ''' def __init__(self): super().__init__() # Initialize Main Window geometry self.title = "PyEcog Main" (size, rect) = self.get_available_screen() self.setWindowIcon(QtGui.QIcon("icon.png")) self.setWindowTitle(self.title) self.setGeometry(0, 0, size.width(), size.height()) self.main_model = MainModel() # Populate Main window with widgets # self.createDockWidget() self.build_menubar() self.dock_list = {} self.paired_graphics_view = PairedGraphicsView(parent=self) self.tree_element = FileTreeElement(parent=self) self.dock_list['File Tree'] = QDockWidget("File Tree", self) self.dock_list['File Tree'].setWidget(self.tree_element.widget) self.dock_list['File Tree'].setFloating(False) self.dock_list['File Tree'].setObjectName("File Tree") self.dock_list['File Tree'].setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['File Tree'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Text'] = QDockWidget("Text", self) self.dock_list['Text'].setWidget(QPlainTextEdit()) self.dock_list['Text'].setObjectName("Text") self.annotation_table = AnnotationTableWidget( self.main_model.annotations) self.dock_list['Annotations Table'] = QDockWidget( "Annotations Table", self) self.dock_list['Annotations Table'].setWidget(self.annotation_table) self.dock_list['Annotations Table'].setObjectName("Annotations Table") self.dock_list['Annotations Table'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Annotation Parameter Tree'] = QDockWidget( "Annotation Parameter Tree", self) self.dock_list['Annotation Parameter Tree'].setWidget( AnnotationParameterTee(self.main_model.annotations)) self.dock_list['Annotation Parameter Tree'].setObjectName( "Annotation Parameter Tree") self.dock_list['Annotation Parameter Tree'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.video_element = VideoWindow(project=self.main_model.project) self.dock_list['Video'] = QDockWidget("Video", self) self.dock_list['Video'].setWidget(self.video_element) self.dock_list['Video'].setObjectName("Video") self.dock_list['Video'].setFloating(True) self.dock_list['Video'].hide() self.video_element.mediaPlayer.setNotifyInterval(40) # 25 fps # Video units are in miliseconds, pyecog units are in seconds self.video_element.sigTimeChanged.connect( self.main_model.set_time_position) self.main_model.sigTimeChanged.connect( self.video_element.setGlobalPosition) self.dock_list['FFT'] = QDockWidget("FFT", self) self.dock_list['FFT'].setWidget(FFTwindow(self.main_model)) self.dock_list['FFT'].setObjectName("FFT") self.dock_list['FFT'].hide() self.dock_list['Wavelet'] = QDockWidget("Wavelet", self) self.dock_list['Wavelet'].setWidget(WaveletWindow(self.main_model)) self.dock_list['Wavelet'].setObjectName("Wavelet") self.dock_list['Wavelet'].hide() self.setCentralWidget(self.paired_graphics_view.splitter) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['File Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotation Parameter Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotations Table']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Text']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['FFT']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Wavelet']) settings = QSettings("PyEcog", "PyEcog") settings.beginGroup("StandardMainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.endGroup() self.settings = QSettings("PyEcog", "PyEcog") print("reading configurations from: " + self.settings.fileName()) self.settings.beginGroup("MainWindow") # print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry( self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) try: settings = QSettings("PyEcog", "PyEcog") settings.beginGroup("ProjectSettings") fname = settings.value("ProjectFileName") print('Loading Project:', fname) self.show() self.load_project(fname) # self.main_model.project.load_from_json(fname) # self.main_model.project.load_from_json('/home/mfpleite/Shared/ele_data/proj.pyecog') # print(self.main_model.project.__dict__) self.tree_element.set_rootnode_from_project( self.main_model.project) except Exception as e: print('ERROR in tree build') print(e) def get_available_screen(self): app = QApplication.instance() screen = app.primaryScreen() print('Screen: %s' % screen.name()) size = screen.size() print('Size: %d x %d' % (size.width(), size.height())) rect = screen.availableGeometry() print('Available: %d x %d' % (rect.width(), rect.height())) return (size, rect) def reset_geometry(self): self.settings = QSettings("PyEcog", "PyEcog") # print("reading cofigurations from: " + self.settings.fileName()) self.settings.beginGroup("StandardMainWindow") print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry( self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) self.show() def load_directory(self): print('0penening only folders with h5 files') selected_directory = self.select_directory() temp_animal = Animal(id='-', eeg_folder=selected_directory) temp_project = Project(self.main_model, eeg_data_folder=selected_directory, title=selected_directory) temp_project.add_animal(temp_animal) self.tree_element.set_rootnode_from_project(temp_project) self.main_model.project = temp_project def new_project(self): self.main_model.project.__init__( main_model=self.main_model) #= Project(self.main_model) self.tree_element.set_rootnode_from_project(self.main_model.project) def load_project(self, fname=None): if type(fname) is not str: dialog = QFileDialog() dialog.setWindowTitle('Load Project ...') dialog.setFileMode(QFileDialog.AnyFile) dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setNameFilter('*.pyecog') if dialog.exec(): fname = dialog.selectedFiles()[0] if type(fname) is str: print(fname) self.main_model.project.load_from_json(fname) self.tree_element.set_rootnode_from_project( self.main_model.project) init_time = min( self.main_model.project.current_animal.eeg_init_time) plot_range = np.array([init_time, init_time + 3600]) print('trying to plot ', plot_range) self.paired_graphics_view.set_scenes_plot_channel_data(plot_range) self.main_model.set_time_position(init_time) self.main_model.set_window_pos([init_time, init_time]) def save(self): print('save action triggered') fname = self.main_model.project.project_file if not os.path.isfile(fname): self.save_as() else: print('Saving project to:', fname) self.main_model.project.save_to_json(fname) def save_as(self): dialog = QFileDialog() dialog.setWindowTitle('Save Project as ...') dialog.setFileMode(QFileDialog.AnyFile) dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter('*.pyecog') if dialog.exec(): fname = dialog.selectedFiles()[0] print(fname) self.main_model.project.project_file = fname print('Saving project to:', self.main_model.project.project_file) self.main_model.project.save_to_json(fname) def select_directory(self, label_text='Select a directory'): ''' Method launches a dialog allow user to select a directory ''' dialog = QFileDialog() dialog.setWindowTitle(label_text) dialog.setFileMode(QFileDialog.DirectoryOnly) # we might want to set home directory using settings # for now rely on default behaviour ''' home = os.path.expanduser("~") # default, if no settings available dialog.setDirectory(home) ''' #dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setOption(QFileDialog.ShowDirsOnly, False) dialog.exec() return dialog.selectedFiles()[0] def reload_plot(self): #print('reload') index = self.tree_element.tree_view.currentIndex() self.tree_element.model.data(index, TreeModel.prepare_for_plot_role) full_xrange = self.paired_graphics_view.overview_plot.vb.viewRange( )[0][1] #print(full_xrange) xmin, xmax = self.paired_graphics_view.insetview_plot.vb.viewRange()[0] x_range = xmax - xmin if full_xrange > x_range: #print('called set xrange') self.paired_graphics_view.insetview_plot.vb.setXRange(full_xrange - x_range, full_xrange, padding=0) def load_live_recording(self): ''' This should just change the graphics view/ file node to keep reloading? ''' self.timer = QtCore.QTimer() self.timer.timeout.connect(self.reload_plot) if self.actionLiveUpdate.isChecked(): self.timer.start(100) def open_git_url(self): webbrowser.open('https://github.com/KullmannLab/pyecog2') def open_docs_url(self): webbrowser.open('https://jcornford.github.io/pyecog_docs/') def open_video_window(self): self.dock_list['Video'].show() self.show() def open_fft_window(self): self.dock_list['FFT'].show() self.dock_list['FFT'].widget().updateData() self.show() def open_wavelet_window(self): self.dock_list['Wavelet'].show() self.dock_list['Wavelet'].widget().update_data() self.show() def openNDFconverter(self): print('opening NDF converter') self.ndf_converter = NDFConverterWindow() self.ndf_converter.show() def openProjectEditor(self): print('opening Project Editor') self.projectEditor = ProjecEditWindow(self.main_model.project) self.projectEditor.show() def export_annotations(self): dialog = QFileDialog() dialog.setWindowTitle('Export annotations as ...') dialog.setFileMode(QFileDialog.AnyFile) dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter('*.csv') if dialog.exec(): fname = dialog.selectedFiles()[0] print(fname) print('Exporting annotations to:', fname) self.main_model.project.export_annotations(fname) def build_menubar(self): self.menu_bar = self.menuBar() # FILE section self.menu_file = self.menu_bar.addMenu("File") self.action_NDF_converter = self.menu_file.addAction( "Open NDF converter") self.menu_file.addSeparator() self.action_load_directory = self.menu_file.addAction("Load directory") self.menu_file.addSeparator() self.actionLiveUpdate = self.menu_file.addAction("Live Recording") self.actionLiveUpdate.setCheckable(True) self.actionLiveUpdate.toggled.connect(self.load_live_recording) self.actionLiveUpdate.setChecked(False) #self.live_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_L), self) #self.live_shortcut.connect() self.actionLiveUpdate.setShortcut('Ctrl+L') self.menu_file.addSeparator() self.action_quit = self.menu_file.addAction("Quit") self.action_NDF_converter.triggered.connect(self.openNDFconverter) self.action_load_directory.triggered.connect(self.load_directory) self.action_quit.triggered.connect(self.close) self.actionLiveUpdate.triggered.connect(self.load_live_recording) # PROJECT section self.menu_project = self.menu_bar.addMenu("Project") self.action_edit_project = self.menu_project.addAction( "Edit Project Settings") self.action_edit_project.triggered.connect(self.openProjectEditor) self.menu_project.addSeparator() self.action_new_project = self.menu_project.addAction("New Project") self.action_load_project = self.menu_project.addAction("Load Project") self.action_save = self.menu_project.addAction("Save Project") self.action_save.setShortcut('Ctrl+S') self.action_save_as = self.menu_project.addAction("Save Project as...") self.action_save_as.setShortcut('Ctrl+Shift+S') self.action_new_project.triggered.connect(self.new_project) self.action_load_project.triggered.connect(self.load_project) self.action_save.triggered.connect(self.save) self.action_save_as.triggered.connect(self.save_as) # ANNOTATIONS section self.menu_annotations = self.menu_bar.addMenu("Annotations") self.action_export_annotations = self.menu_annotations.addAction( "Export to CSV") self.action_export_annotations.triggered.connect( self.export_annotations) self.action_import_annotations = self.menu_annotations.addAction( "Import annotations") self.action_import_annotations.setDisabled(True) # CLASSIFIER section self.menu_classifier = self.menu_bar.addMenu("Classifier") self.action_setup_feature_extractor = self.menu_classifier.addAction( "Setup feature extractor") self.action_run_feature_extractor = self.menu_classifier.addAction( "Run feature extractor") self.action_setup_classifier = self.menu_classifier.addAction( "Setup classifier") self.action_train_classifier = self.menu_classifier.addAction( "Train classifier") self.action_run_classifier = self.menu_classifier.addAction( "Run classifier") self.action_review_classifications = self.menu_classifier.addAction( "Review classifications") self.action_setup_feature_extractor.setDisabled(True) self.action_run_feature_extractor.setDisabled(True) self.action_setup_classifier.setDisabled(True) self.action_train_classifier.setDisabled(True) self.action_run_classifier.setDisabled(True) self.action_review_classifications.setDisabled(True) # TOOLS section self.menu_tools = self.menu_bar.addMenu("Tools") self.action_open_video_window = self.menu_tools.addAction("Video") self.action_open_video_window.triggered.connect(self.open_video_window) # To do self.action_open_fft_window = self.menu_tools.addAction("FFT") self.action_open_fft_window.triggered.connect(self.open_fft_window) self.action_open_morlet_window = self.menu_tools.addAction( "Morlet Wavelet Transform") self.action_open_morlet_window.triggered.connect( self.open_wavelet_window) # HELP section self.menu_help = self.menu_bar.addMenu("Help") self.action_reset_geometry = self.menu_help.addAction( "Reset Main Window layout") self.action_reset_geometry.triggered.connect(self.reset_geometry) self.menu_help.addSeparator() self.action_go_to_git = self.menu_help.addAction( "Go to Git Repository") self.action_go_to_git.triggered.connect(self.open_git_url) self.action_go_to_doc = self.menu_help.addAction( "Go to web documentation") self.action_go_to_doc.triggered.connect(self.open_docs_url) self.menu_bar.setNativeMenuBar(False) #self.menubar.addMenu("Edit") #self.menubar.addMenu("View") def closeEvent(self, event): print('closing') settings = QSettings("PyEcog", "PyEcog") settings.beginGroup("MainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.endGroup() settings.beginGroup("ProjectSettings") settings.setValue("ProjectFileName", self.main_model.project.project_file) settings.endGroup() print('current project filename:', self.main_model.project.project_file) # for dock_name in self.dock_list.keys(): # settings.beginGroup(dock_name) # settings.setValue("windowGeometry", self.dock_list[dock_name].saveGeometry()) # # settings.setValue("windowState", self.dock_list[dock_name].saveState()) # settings.endGroup() self.saveState() def keyPressEvent(self, evt): print('Key press captured by Main', evt.key()) if evt.key() == QtCore.Qt.Key_Space: print('Space pressed') self.video_element.play() return if evt.key() == QtCore.Qt.Key_Delete: # self.main_model.annotations.delete_annotation(self.main_model.annotations.focused_annotation) self.annotation_table.removeSelection() return numbered_keys = [ QtCore.Qt.Key_1, QtCore.Qt.Key_2, QtCore.Qt.Key_3, QtCore.Qt.Key_4, QtCore.Qt.Key_5, QtCore.Qt.Key_6, QtCore.Qt.Key_7, QtCore.Qt.Key_8, QtCore.Qt.Key_9, QtCore.Qt.Key_0 ] for i in range(len(self.main_model.annotations.labels)): if evt.key() == numbered_keys[i]: print(i + 1, 'pressed') if self.main_model.annotations.focused_annotation is None: print('Adding new annotation') new_annotation = AnnotationElement( label=self.main_model.annotations.labels[i], start=self.main_model.window[0], end=self.main_model.window[1], notes='') self.main_model.annotations.add_annotation(new_annotation) self.main_model.annotations.focusOnAnnotation( new_annotation) else: print('Calling annotation_table changeSelectionLabel') self.annotation_table.changeSelectionLabel( self.main_model.annotations.labels[i]) # annotation = self.main_model.annotations.focused_annotation # annotation.setLabel(self.main_model.annotations.labels[i]) # self.main_model.annotations.focusOnAnnotation(annotation) return
class MainWindow(QMainWindow): ''' GUI ''' signals = ObjectSignals() def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.central_widget = QWidget() # define central widget self.setCentralWidget(self.central_widget) # set QMainWindow.centralWidget self.appName = "Eltanin" self.image = None # Store constructor arguments (re-used for processing) self.args = args self.kwargs = kwargs self.prevClockTime = None self.settings = QSettings("settings.ini", QSettings.IniFormat) self.initUI() self.loadSettings() self.disableButtons() def initUI(self): self.setWindowTitle(self.appName) screen = QDesktopWidget().availableGeometry() self.imageWidth = round(screen.height() * 0.8) self.imageHeight = round(screen.width() * 0.8) self.imageScalingFactor = 1.0 self.imageScalingStep = 0.1 # Labels self.PixImage = QLabel() self.timerLabel = QLabel() self.imageQualityLabel = QLabel() self.temperatureLabel = QLabel() # a figure instance to plot on self.canvas = FigureCanvas(Figure()) #(figsize=(5, 3))) self.axes = self.canvas.figure.subplots(2, 2, sharex=False, sharey=False) # this is the Navigation widget # it takes the Canvas widget and a parent ## self.toolbar = NavigationToolbar(self.canvas, self) # Buttons self.breakButton = QPushButton("Emergency break") self.restartButton = QPushButton("Restart firmware") self.homeXYZButton = QPushButton("Home XYZ") self.snapshotButton = QPushButton("Snapshot") self.autoFocusButton = QPushButton("AutoFocus") self.recordButton = QPushButton("Record") self.stageOriginButton = QPushButton("Stage Origin") self.runButton = QPushButton("Run") # Progress bar self.pbar = QProgressBar(self) self.pbar.setValue(0) # Spinboxes self.light = QSpinBox(self) self.lightTitle = QLabel("light") self.light.setSuffix("%") self.light.setMinimum(0) self.light.setMaximum(100) self.light.setSingleStep(1) self.stageXTranslation = QDoubleSpinBox(self) self.stageXTranslationTitle = QLabel("X Translation") self.stageXTranslation.setSuffix("mm") self.stageXTranslation.setMinimum(0.0) self.stageXTranslation.setMaximum(100) self.stageXTranslation.setSingleStep(0.1) self.stageYTranslation = QDoubleSpinBox(self) self.stageYTranslationTitle = QLabel("Y Translation") self.stageYTranslation.setSuffix("mm") self.stageYTranslation.setMinimum(0.0) self.stageYTranslation.setMaximum(100) self.stageYTranslation.setSingleStep(0.1) self.stageZTranslation = QDoubleSpinBox(self) self.stageZTranslationTitle = QLabel("Z Translation") self.stageZTranslation.setSuffix("mm") self.stageZTranslation.setMinimum(0.0) self.stageZTranslation.setMaximum(50) self.stageZTranslation.setSingleStep(0.01) # Compose layout grid self.keyWidgets = [self.stageXTranslationTitle, self.stageYTranslationTitle, self.stageZTranslationTitle, self.lightTitle] self.valueWidgets = [self.stageXTranslation, self.stageYTranslation, self.stageZTranslation, self.light] widgetLayout = QGridLayout() for index, widget in enumerate(self.keyWidgets): if widget is not None: widgetLayout.addWidget(widget, index, 0, Qt.AlignCenter) for index, widget in enumerate(self.valueWidgets): if widget is not None: widgetLayout.addWidget(widget, index, 1, Qt.AlignCenter) widgetLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum,QSizePolicy.Expanding)) # variable space widgetLayout.addWidget(self.homeXYZButton, index+1,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.stageOriginButton,index+1,1,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.autoFocusButton, index+4,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.snapshotButton, index+3,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.recordButton, index+3,1,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.runButton, index+5,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.pbar, index+5,1,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.breakButton, index+6,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.restartButton, index+6,1,alignment=Qt.AlignLeft) widgetLayout.addWidget(QLabel("Processing time [ms]: "),index+7,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.timerLabel,index+7,1,alignment=Qt.AlignLeft) widgetLayout.addWidget(QLabel("Image quality [au]: "),index+8,0,alignment=Qt.AlignLeft) widgetLayout.addWidget(self.imageQualityLabel,index+8,1,alignment=Qt.AlignLeft) ## widgetLayout.addWidget(QLabel("Temperature [°C]: "),index+8,0,alignment=Qt.AlignLeft) ## widgetLayout.addWidget(self.temperatureLabel,index+8,1,alignment=Qt.AlignLeft) # Compose final layout layout = QHBoxLayout() layout.addLayout(widgetLayout, Qt.AlignTop|Qt.AlignCenter) layout.addWidget(self.PixImage, Qt.AlignTop|Qt.AlignCenter) layout.addWidget(self.canvas, Qt.AlignTop|Qt.AlignCenter) self.centralWidget().setLayout(layout) @pyqtSlot(int) def updateProgressBar(self, val): if val >= 0 and val <= 100: self.pbar.setValue(val) @pyqtSlot(np.float) def imageQualityUpdate(self, image_quality=None): self.imageQualityLabel.setNum(round(image_quality,2)) def progress_fn(self, n): print("%d%% done" % n) def msg(self, text): if text: text = self.__class__.__name__ + ": " + str(text) print(text) self.signals.message.emit(text) @pyqtSlot(np.ndarray) def update(self, image=None): self.kickTimer() # Measure time delay if not(image is None): # we have a new image height, width = image.shape[:2] # get dimensions self.image = image if self.image_size[0] == width and self.image_size[1] == height else cv2.resize(image, self.image_size) if self.imageScalingFactor > 0 and self.imageScalingFactor < 1: # Crop the image to create a zooming effect height, width = image.shape[:2] # get dimensions delta_height = round(height * (1 - self.imageScalingFactor) / 2) delta_width = round(width * (1 - self.imageScalingFactor) / 2) image = image[delta_height:height - delta_height, delta_width:width - delta_width] height, width = image.shape[:2] # get dimensions if self.imageHeight != height or self.imageWidth != width: # we need scaling scaling_factor = self.imageHeight / float(height) # get scaling factor if self.imageWidth / float(width) < scaling_factor: scaling_factor = self.imageWidth / float(width) image = cv2.resize(image, None, fx=scaling_factor, fy=scaling_factor, interpolation=cv2.INTER_AREA) # resize image if len(image.shape) < 3: # check nr of channels image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # convert to color image height, width = image.shape[:2] # get dimensions qImage = QImage(image.data, width, height, width * 3, QImage.Format_RGB888) # Convert from OpenCV to PixMap self.PixImage.setPixmap(QPixmap(qImage)) self.PixImage.show() @pyqtSlot(int, np.ndarray, np.ndarray) def updatePlot(self, figType, quadrant, x, y): if not (y is None) or not (x is None): # select axes if quadrant == 1: axes = self.axes[0, 1] elif quadrant == 2: axes = self.axes[0, 0] elif quadrant == 3: axes = self.axes[1, 0] elif quadrant == 4: axes = self.axes[1, 1] # plot new data axes.clear() if (figType is FigureTypes.LINEAR) or (figType is None): if x is None: axes.plot(y) else: axes.plot(x, y) elif figType == FigureTypes.SCATTER: if not(x is None): #scatter plot if len(x) >= 100: t = np.arange(0, 100, 1) length = len(x) - 100 axes.scatter(t, x[length:len(x)], c="blue", alpha=0.5) # axes.set_xlabel('frame') # axes.set_ylabel('Distance') elif figType == FigureTypes.HISTOGRAM: #hist if not(x is None): if len(x) > 0: axes.hist(x, 30, density=True, facecolor="blue", alpha=0.5) #axes.set_xlabel('Distance') #axes.set_ylabel('Occurance') axes.figure.canvas.draw() @pyqtSlot() def disableButtons(self): self.homeXYZButton.setEnabled(False) self.snapshotButton.setEnabled(False) self.recordButton.setEnabled(False) self.stageOriginButton.setEnabled(False) self.runButton.setEnabled(False) self.light.setEnabled(False) self.stageXTranslation.setEnabled(False) self.stageYTranslation.setEnabled(False) self.stageZTranslation.setEnabled(False) @pyqtSlot() def enableButtons(self): self.homeXYZButton.setEnabled(True) self.snapshotButton.setEnabled(True) self.recordButton.setEnabled(True) self.runButton.setEnabled(True) self.stageOriginButton.setEnabled(True) self.light.setEnabled(True) self.stageXTranslation.setEnabled(True) self.stageYTranslation.setEnabled(True) self.stageZTranslation.setEnabled(True) @pyqtSlot() def kickTimer(self): clockTime = current_milli_time() # datetime.now() if self.prevClockTime is not None: timeDiff = clockTime - self.prevClockTime self.timerLabel.setNum(round(timeDiff)) # Text("Processing time: " + "{:4d}".format(round(timeDiff)) + " ms") ## self.timerLabel.setText("Processing time: " + "{:4d}".format(round(1000*timeDiff.total_seconds())) + " ms") self.prevClockTime = clockTime @pyqtSlot(np.float) def temperatureUpdate(self, temp=None): self.temperatureLabel.setNum(round(temp)) # Text("Processing time: " + "{:4d}".format(round(timeDiff)) + " ms") def wheelEvent(self, event): if (event.angleDelta().y() > 0) and (self.imageScalingFactor > self.imageScalingStep): # zooming in self.imageScalingFactor -= self.imageScalingStep elif (event.angleDelta().y() < 0) and (self.imageScalingFactor < 1.0): # zooming out self.imageScalingFactor += self.imageScalingStep self.imageScalingFactor = round(self.imageScalingFactor, 2) # strange behaviour, so rounding is necessary self.update() # redraw the image with different scaling def closeEvent(self, event: QCloseEvent): self.saveSettings() self.signals.finished.emit() event.accept() def snapshot(self): ## a mutex is needed here? if not(self.image is None): filename = self.appName + '_'+ str(current_milli_time()) + '.png' cv2.imwrite(filename, self.image) self.msg("Image written to " + filename) def loadSettings(self): self.msg("info;loading settings from " + self.settings.fileName()) frame_size_str = self.settings.value('frame_size') (width, height) = frame_size_str.split('x') self.image_size = (int(width), int(height)) for index, widget in enumerate(self.keyWidgets): # retreive all labeled parameters if isinstance(widget, QLabel): key = "mainwindow/" + widget.text() if self.settings.contains(key): self.valueWidgets[index].setValue(float(self.settings.value(key))) def saveSettings(self): self.msg("info;saving settings to " + self.settings.fileName()) for index, widget in enumerate(self.keyWidgets): # save all labeled parameters if isinstance(widget, QLabel): key = "mainwindow/" + widget.text() self.settings.setValue(key, self.valueWidgets[index].value()) for index, widget in enumerate(self.valueWidgets): # save all labeled parameters if isinstance(widget, QCheckBox): key = "mainwindow/" + widget.text() self.settings.setValue(key, widget.isChecked())
import sys import inspect from os.path import dirname, abspath, join, exists #when in CLI use inspect to locate the source directory src_dir = join(dirname(abspath(inspect.getfile(inspect.currentframe()))), 'src') sys.path.append(src_dir) __author__ = 'saflores' from PyQt5 import QtWidgets from PyQt5.QtCore import QSettings from gui.ui import UI import sys app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ##### check if settings exist, otherwise initialize them ##### mySettings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'Sogeti', 'validate') if not exists(mySettings.fileName()): mySettings.setValue("report/imageQuality", 120) mySettings.sync() print("settings written to " + mySettings.fileName()) ############################################################## ui = UI(MainWindow) MainWindow.show() sys.exit(app.exec_())
class ini_setting: """ class for managing .ini file and project defaults """ release_mode = False def __init__(self, file_name, model): """ constructor: read setting values into a QSetting object Args: file_name: ini file name """ self.file_name = file_name self.model = model self.error_msg = "" self.groups_with_values = {} # all values read self.groups = {} # group names and key names self.config = None try: self.config = QSettings(QSettings.IniFormat, QSettings.UserScope, "EPA", self.model, None) if (not os.path.isfile(self.file_name) and file_name): from shutil import copyfile copyfile(self.config.fileName(), file_name) # self.create_ini_file(file_name) else: self.file_name = self.config.fileName() self.build_setting_map() print("Read project settings from " + self.file_name) except Exception as exINI: self.config = None self.error_msg = "Reading Error" + ":\n" + str(exINI) def create_ini_file(self, file_name): """ Specify .ini file path after instantiation if setting is not yet instantiated: a new setting is instantiated if the new setting has no key: a new ini file is created Args: file_name: the full path to an ini file Returns: True if successful; False if failed """ if os.path.isfile(self.file_name): return False if self.config is None: try: self.config = QSettings(file_name, QSettings.IniFormat) if len(self.config.allKeys()) == 0: # this is a brand new ini file self.config.setValue("Model/Name", self.model) self.config.sync() print("created ini file: " + file_name) else: print("Read settings from ini file: " + file_name) self.file_name = file_name return True except Exception as exINI: self.config = None self.error_msg = "Reading Error" + ":\n" + str(exINI) return False def get_setting_value(self, group, key): """ Get the value of a ini setting, assuming all settings are grouped Args: group: the string name of a group or section key: the string name of a key Returns: a list of two elements, first being the value, second being the value type """ rval = [None, None] if len(self.groups) == 0: return rval if not group in self.groups: return rval self.config.beginGroup(group) qvar = self.config.value(key) if qvar is None: self.config.endGroup() return rval str_val = str(qvar) if len(str_val) > 0: tval, tval_is_good = ParseData.intTryParse(str_val) if tval_is_good: rval[0] = tval rval[1] = "integer" self.config.endGroup() return rval tval, tval_is_good = ParseData.floatTryParse(str_val) if tval_is_good: rval[0] = tval rval[1] = "float" self.config.endGroup() return rval rval[0] = str_val rval[1] = "string" self.config.endGroup() return rval elif str_val == "": rval[0] = "" rval[1] = "string" else: str_list = qvar.toStringList().join(",") if len(str_list) > 0: rval[0] = str_list.strip(",").split(",") rval[1] = "stringlist" self.config.endGroup() return rval self.config.endGroup() return rval def build_setting_map(self): """ Build a setting group-key-value mapping dictionary as below: self.groups_with_values [group_name] -> [key_name] -> [value, value_type] Also create group dictionary keyed on group name, pointing to its list of keys so we can get setting values on a as needed basis self.groups [group_name] -> [key names] """ self.groups.clear() self.groups_with_values.clear() for group in self.config.childGroups(): self.config.beginGroup(group) self.groups_with_values[group] = {} self.groups[group] = [] for key in self.config.childKeys(): self.groups[group].append(key) val, vtype = self.get_setting_value(group, key) self.groups_with_values[group][key] = val # print group + "::" + key + "::" + self.config.value(key).toString() self.config.endGroup() def read_ini_file_cp(self): self.config.read(self.file_name) for section in self.config.sections(): dict1 = self.config_section_map(section) pass pass def config_section_map(self, section): dict1 = {} for option in self.config.options(section): try: dict1[option] = self.config.get(section, option) if dict1[option] == -1: print ("skip: %s" % option) pass except: print ("exception on %s!" % option) dict1[option] = None return dict1
SEPARATION_OPACITY = 0.2 SEPARATION_PADDING = .05 # Prozent # PROTOCOL TABLE COLORS SELECTED_ROW_COLOR = QColor.fromRgb(0, 0, 255) DIFFERENCE_CELL_COLOR = QColor.fromRgb(255, 0, 0) PROPERTY_FOUND_COLOR = QColor.fromRgb(0, 124, 0, 100) PROPERTY_NOT_FOUND_COLOR = QColor.fromRgb(124, 0, 0, 100) SEPARATION_ROW_HEIGHT = 30 SETTINGS = QSettings(QSettings.IniFormat, QSettings.UserScope, 'urh', 'urh') PROJECT_FILE = "URHProject.xml" DECODINGS_FILE = "decodings.txt" FIELD_TYPE_SETTINGS = os.path.realpath(os.path.join(SETTINGS.fileName(), "..", "fieldtypes.xml")) # DEVICE SETTINGS DEFAULT_IP_USRP = "192.168.10.2" DEFAULT_IP_RTLSDRTCP = "127.0.0.1" # DECODING NAMES DECODING_INVERT = "Invert" DECODING_DIFFERENTIAL = "Differential Encoding" DECODING_REDUNDANCY = "Remove Redundancy" DECODING_DATAWHITENING = "Remove Data Whitening (CC1101)" DECODING_CARRIER = "Remove Carrier" DECODING_BITORDER = "Change Bitorder" DECODING_EDGE = "Edge Trigger" DECODING_SUBSTITUTION = "Substitution" DECODING_EXTERNAL = "External Program"
class PiVideoStream(QThread): """ Thread that produces frames for further processing as a PyQtSignal. Picamera is set-up according to sensormode and splitter_port 0 is used for capturing image data. A video stream is set-up, using picamera splitter port 1 and resized to frameSize. Splitter_port 2 is used for capturing video at videoFrameSize. """ image = None finished = pyqtSignal() postMessage = pyqtSignal(str) frame = pyqtSignal(np.ndarray) progress = pyqtSignal(int) captured = pyqtSignal() camera = PiCamera() videoStream = BytesIO() storagePath = None cropRect = [0] * 4 ## @param ins is the number of instances created. This may not exceed 1. ins = 0 def __init__(self): super().__init__() ## Instance limiter. Checks if an instance exists already. If so, it deletes the current instance. if PiVideoStream.ins >= 1: del self self.postMessage.emit( "{}: error; multiple instances of created, while only 1 instance is allowed" .format(__class__.__name__)) return try: PiVideoStream.ins += 1 except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) else: warnings.filterwarnings('default', category=DeprecationWarning) self.settings = QSettings("settings.ini", QSettings.IniFormat) self.loadSettings() ## self.initStream() def loadSettings(self): self.postMessage.emit( "{}: info; loading camera settings from {}".format( self.__class__.__name__, self.settings.fileName())) # load self.monochrome = self.settings.value('camera/monochrome', False, type=bool) self.sensorMode = int(self.settings.value('camera/sensor_mode')) self.frameRate = int(self.settings.value('camera/frame_rate')) # set frame sizes self.frameSize = frame_size_from_string( self.settings.value('frame_size')) self.captureFrameSize = frame_size_from_sensor_mode(self.sensorMode) self.videoFrameSize = frame_size_from_string( self.settings.value('camera/video_frame_size')) if not self.monochrome: self.frameSize = self.frameSize + (3, ) @pyqtSlot() def initStream(self): if self.isRunning(): self.requestInterruption() wait_signal(self.finished, 10000) # Set camera parameters self.camera.exposure_mode = 'backlight' # 'auto' self.camera.awb_mode = 'flash' # 'auto' self.camera.meter_mode = 'backlit' # 'average' self.camera.sensor_mode = self.sensorMode self.camera.resolution = self.captureFrameSize self.camera.framerate = self.frameRate self.camera.image_effect = self.settings.value('camera/effect') self.camera.iso = int(self.settings.value( 'camera/iso')) # should force unity analog gain self.camera.video_denoise = self.settings.value('camera/video_denoise', False, type=bool) # Wait for the automatic gain control to settle wait_ms(3000) # # Now fix the values # self.camera.shutter_speed = self.camera.exposure_speed # self.camera.exposure_mode = 'off' # g = self.camera.awb_gains # self.camera.awb_mode = 'off' # self.camera.awb_gains = g ## # Setup video port, GPU resizes frames, and compresses to mjpeg stream ## self.camera.start_recording(self.videoStream, format='mjpeg', splitter_port=1, resize=self.frameSize) # Setup capture from video port 1 if self.monochrome: self.rawCapture = PiYArray(self.camera, size=self.frameSize) self.captureStream = self.camera.capture_continuous( self.rawCapture, 'yuv', use_video_port=True, splitter_port=1, resize=self.frameSize) else: self.rawCapture = PiRGBArray(self.camera, size=self.frameSize) self.captureStream = self.camera.capture_continuous( self.rawCapture, 'bgr', use_video_port=True, splitter_port=1, resize=self.frameSize) # init crop rectangle if self.cropRect[2] == 0: self.cropRect[2] = self.camera.resolution[1] if self.cropRect[3] == 0: self.cropRect[3] = self.camera.resolution[0] # start the thread self.start(QThread.HighPriority) msg = "{}: info; video stream initialized with frame size = {} and {:d} channels".format(\ __class__.__name__, str(self.camera.resolution), 1 if self.monochrome else 3) self.postMessage.emit(msg) @pyqtSlot() def run(self): try: self.fps = FPS().start() for f in self.captureStream: if self.isInterruptionRequested(): break self.rawCapture.seek(0) img = f.array # grab the frame from the stream self.frame.emit(img) #cv2.resize(img, self.frameSize[:2])) self.fps.update() ## # Grab jpeg from an mpeg video stream ## self.videoStream.seek(0) ## buf = self.videoStream.read() ## if buf.startswith(b'\xff\xd8'): ## # jpeg magic number is detected ## flag = cv2.IMREAD_GRAYSCALE if self.monochrome else cv2.IMREAD_COLOR ## img = cv2.imdecode(np.frombuffer(buf, dtype=np.uint8), flag) ## self.frame.emit(img) ## self.fps.update() ## self.videoStream.truncate(0) except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) finally: self.fps.stop() img = np.zeros(shape=(self.frameSize[1], self.frameSize[0]), dtype=np.uint8) cv2.putText( img, 'Camera suspended', (int(self.frameSize[0] / 2) - 150, int(self.frameSize[1] / 2)), cv2.FONT_HERSHEY_SIMPLEX, 1, (255), 1) for i in range(5): wait_ms(100) self.frame.emit(img) msg = "{}: info; finished, approx. processing speed: {:.2f} fps".format( self.__class__.__name__, self.fps.fps()) self.postMessage.emit(msg) self.finished.emit() @pyqtSlot() def stop(self): self.postMessage.emit("{}: info; stopping".format(__class__.__name__)) try: if self.isRunning(): self.requestInterruption() wait_signal(self.finished, 10000) except Exception as err: msg = "{}: error; stopping method".format(self.__class__.__name__) print(msg) finally: self.quit( ) # Note that thread quit is required, otherwise strange things happen. @pyqtSlot(str) def takeImage(self, filename_prefix=None): if filename_prefix is not None: (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_'.format(round(time.time() * 1000)) + tail + '.png' ]) else: filename = '{:016d}'.format(round(time.time() * 1000)) + '.png' # open path if self.storagePath is not None: filename = os.path.sep.join([self.storagePath, filename]) try: self.camera.capture(filename, use_video_port=False, splitter_port=0, format='png') except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) self.captured.emit() self.postMessage.emit("{}: info; image written to {}".format( __class__.__name__, filename)) @pyqtSlot(str, int) def recordClip(self, filename_prefix=None, duration=10): """ Captures a videoclip of duration at resolution videoFrameSize. The GPU resizes the captured video to the intended resolution. Note that while it seems possble to change the sensormode, reverting to the original mode fails when capturing an image. In many cases, the intended framerate is not achieved. For that reason, ffprobe counts the total number of frames that were actually taken. Next, the h264 video file is boxed using MP4box. For some reason, changing the framerate with MP4Box did not work out. """ if filename_prefix is not None: (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_{}s'.format(round(time.time() * 1000), round(duration)) + tail ]) else: filename = '{:016d}_{}s'.format(round(time.time() * 1000), round(duration)) # open path if self.storagePath is not None: filename = os.path.sep.join([self.storagePath, filename]) # stop current video stream, maybe mpeg and h264 compression cannot run simultaneously? self.postMessage.emit("{}: info; starting recording for {} s".format( __class__.__name__, duration)) try: # GPU resizes frames, and compresses to h264 stream self.camera.start_recording(filename + '.h264', format='h264', splitter_port=2, resize=self.videoFrameSize, sps_timing=True) wait_ms(duration * 1000) self.camera.stop_recording(splitter_port=2) # Wrap an MP4 box around the video nr_of_frames = check_output([ "ffprobe", "-v", "error", "-count_frames", "-select_streams", "v:0", "-show_entries", "stream=nb_read_frames", "-of", "default=nokey=1:noprint_wrappers=1", filename + '.h264' ]) real_fps = duration / float(nr_of_frames) self.postMessage.emit( "{}: info; video clip captured with real framerate: {} fps". format(__class__.__name__, real_fps)) ## run(["MP4Box", "-fps", str(self.frameRate), "-add", filename + '.h264:fps=' + str(real_fps), "-new", filename + '.mp4']) run([ "MP4Box", "-fps", str(self.frameRate), "-add", filename + ".h264", "-new", filename + "_{}fr.mp4".format(int(nr_of_frames)) ]) run(["rm", filename + '.h264']) except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) self.captured.emit() self.postMessage.emit("{}: info; video written to {}".format( __class__.__name__, filename)) @pyqtSlot(str) def setStoragePath(self, path): self.storagePath = path @pyqtSlot(int) def setCropXp1(self, val): if 0 <= val <= self.cropRect[3]: self.cropRect[1] = val else: raise ValueError('crop x1') @pyqtSlot(int) def setCropXp2(self, val): if self.cropRect[1] < val < self.camera.resolution[1]: self.cropRect[3] = val else: raise ValueError('crop x2') @pyqtSlot(int) def setCropYp1(self, val): if 0 <= val <= self.cropRect[2]: self.cropRect[0] = val else: raise ValueError('crop y1') @pyqtSlot(int) def setCropYp2(self, val): if self.cropRect[0] < val < self.camera.resolution[0]: self.cropRect[2] = val else: raise ValueError('crop y2')
class MainWindow(QMainWindow): ''' basically handles the combination of the the tree menu bar and the paired view Most of the code here is for setting up the geometry of the gui and the menu bar stuff ''' def __init__(self, app_handle = None): super().__init__() self.app_handle = app_handle if os.name == 'posix': pyecog_string = '🇵 🇾 🇪 🇨 🇴 🇬' else: pyecog_string = 'PyEcog' print('\n',pyecog_string,'\n') # Initialize Main Window geometry # self.title = "ℙ𝕪𝔼𝕔𝕠𝕘" self.title = pyecog_string (size, rect) = self.get_available_screen() icon_file = pkg_resources.resource_filename('pyecog2', 'icons/icon.png') print('ICON:', icon_file) self.setWindowIcon(QtGui.QIcon(icon_file)) self.app_handle.setWindowIcon(QtGui.QIcon(icon_file)) self.setWindowTitle(self.title) self.setGeometry(0, 0, size.width(), size.height()) # self.setWindowFlags(QtCore.Qt.FramelessWindowHint) # fooling around # self.setWindowFlags(QtCore.Qt.CustomizeWindowHint| QtCore.Qt.Tool) # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.main_model = MainModel() self.autosave_timer = QtCore.QTimer() self.live_recording_timer = QtCore.QTimer() # Populate Main window with widgets # self.createDockWidget() self.dock_list = {} self.paired_graphics_view = PairedGraphicsView(parent=self) self.tree_element = FileTreeElement(parent=self) self.main_model.sigProjectChanged.connect(lambda: self.tree_element.set_rootnode_from_project(self.main_model.project)) self.dock_list['File Tree'] = QDockWidget("File Tree", self) self.dock_list['File Tree'].setWidget(self.tree_element.widget) self.dock_list['File Tree'].setFloating(False) self.dock_list['File Tree'].setObjectName("File Tree") self.dock_list['File Tree'].setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['File Tree'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.plot_controls = PlotControls(self.main_model) self.plot_controls.sigUpdateXrange_i.connect(self.paired_graphics_view.insetview_set_xrange) self.plot_controls.sigUpdateXrange_o.connect(self.paired_graphics_view.overview_set_xrange) self.plot_controls.sigUpdateFilter.connect(self.paired_graphics_view.updateFilterSettings) self.dock_list['Plot Controls'] = QDockWidget("Plot controls", self) self.dock_list['Plot Controls'].setWidget(self.plot_controls) self.dock_list['Plot Controls'].setFloating(False) self.dock_list['Plot Controls'].setObjectName("Plot Controls") self.dock_list['Plot Controls'].setAllowedAreas( Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['Plot Controls'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Hints'] = QDockWidget("Hints", self) self.text_edit = QTextBrowser() hints_file = pkg_resources.resource_filename('pyecog2', 'HelperHints.md') # text = open('HelperHints.md').read() print('hints file:',hints_file) text = open(hints_file).read() text = text.replace('icons/banner_small.png',pkg_resources.resource_filename('pyecog2', 'icons/banner_small.png')) self.text_edit.setMarkdown(text) self.dock_list['Hints'].setWidget(self.text_edit) self.dock_list['Hints'].setObjectName("Hints") # self.dock_list['Hints'].setFloating(False) # self.dock_list['Hints'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.annotation_table = AnnotationTableWidget(self.main_model.annotations,self) # passing self as parent in position 2 self.dock_list['Annotations Table'] = QDockWidget("Annotations Table", self) self.dock_list['Annotations Table'].setWidget(self.annotation_table) self.dock_list['Annotations Table'].setObjectName("Annotations Table") self.dock_list['Annotations Table'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.annotation_parameter_tree = AnnotationParameterTee(self.main_model.annotations) self.dock_list['Annotation Parameter Tree'] = QDockWidget("Annotation Parameter Tree", self) self.dock_list['Annotation Parameter Tree'].setWidget(self.annotation_parameter_tree) self.dock_list['Annotation Parameter Tree'].setObjectName("Annotation Parameter Tree") self.dock_list['Annotation Parameter Tree'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.video_element = VideoWindow(project=self.main_model.project) self.dock_list['Video'] = QDockWidget("Video", self) self.dock_list['Video'].setWidget(self.video_element) self.dock_list['Video'].setObjectName("Video") # self.dock_list['Video'].setFloating(True) # self.dock_list['Video'].hide() self.video_element.mediaPlayer.setNotifyInterval(40) # 25 fps # Video units are in miliseconds, pyecog units are in seconds self.video_element.sigTimeChanged.connect(self.main_model.set_time_position) self.main_model.sigTimeChanged.connect(self.video_element.setGlobalPosition) self.dock_list['FFT'] = QDockWidget("FFT", self) self.dock_list['FFT'].setWidget(FFTwindow(self.main_model)) self.dock_list['FFT'].setObjectName("FFT") # self.dock_list['FFT'].hide() self.dock_list['Wavelet'] = QDockWidget("Wavelet", self) self.dock_list['Wavelet'].setWidget(WaveletWindow(self.main_model)) self.dock_list['Wavelet'].setObjectName("Wavelet") # self.dock_list['Wavelet'].hide() self.dock_list['Console'] = QDockWidget("Console", self) self.dock_list['Console'].setWidget(ConsoleWidget(namespace={'MainWindow':self})) self.dock_list['Console'].setObjectName("Console") self.dock_list['Console'].hide() self.build_menubar() self.setCentralWidget(self.paired_graphics_view.splitter) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Console']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['File Tree']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Hints']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Plot Controls']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Video']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotation Parameter Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotations Table']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['FFT']) self.setCorner(Qt.BottomLeftCorner,Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner,Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_list['Wavelet']) # self.tabifyDockWidget(self.dock_list['Hints'], self.dock_list['File Tree']) self.resizeDocks([self.dock_list['File Tree'], self.dock_list['Hints'], self.dock_list['Plot Controls'],self.dock_list['Video']],[350,100,100,300],Qt.Vertical) self.resizeDocks([self.dock_list['Wavelet']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['Video']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['Video']],[400],Qt.Horizontal) self.resizeDocks([self.dock_list['FFT']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['FFT']],[400],Qt.Horizontal) settings = QSettings("PyEcog","PyEcog") settings.beginGroup("StandardMainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.setValue("darkMode", False) settings.setValue("autoSave", True) settings.endGroup() self.settings = QSettings("PyEcog", "PyEcog") print("reading configurations from: " + self.settings.fileName()) self.settings.beginGroup("MainWindow") # print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry(self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) self.action_darkmode.setChecked(self.settings.value("darkMode",type=bool)) self.toggle_darkmode() # pre toggle darkmode to make sure project loading dialogs are made with the correct color pallete try: settings = QSettings("PyEcog","PyEcog") settings.beginGroup("ProjectSettings") fname = settings.value("ProjectFileName") self.show() if fname.endswith('.pyecog'): print('Loading last opened Project:', fname) self.load_project(fname) else: print('Loading last opened directory:', fname) self.load_directory(fname) except Exception as e: print('ERROR in tree build') print(e) self.action_darkmode.setChecked(self.settings.value("darkMode",type=bool)) self.toggle_darkmode() # toggle again darkmode just to make sure the wavelet window and FFT are updated as well self.action_autosave.setChecked(self.settings.value("autoSave",type=bool)) self.toggle_auto_save() def get_available_screen(self): app = QApplication.instance() screen = app.primaryScreen() print('Screen: %s' % screen.name()) size = screen.size() print('Size: %d x %d' % (size.width(), size.height())) rect = screen.availableGeometry() print('Available: %d x %d' % (rect.width(), rect.height())) return (size,rect) def reset_geometry(self): self.settings = QSettings("PyEcog", "PyEcog") # print("reading cofigurations from: " + self.settings.fileName()) self.settings.beginGroup("StandardMainWindow") print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry(self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) self.action_darkmode.setChecked(self.settings.value("darkMode",type=bool)) self.toggle_darkmode() self.show() def load_directory(self,dirname=None): print('Openening folder') if type(dirname) != str: dirname = self.select_directory() temp_animal = Animal(id='-', eeg_folder=dirname) temp_project = Project(self.main_model,eeg_data_folder=dirname, title=dirname,project_file = dirname) temp_project.add_animal(temp_animal) self.tree_element.set_rootnode_from_project(temp_project) self.main_model.project = temp_project def new_project(self): self.main_model.project.__init__(main_model=self.main_model) #= Project(self.main_model) self.tree_element.set_rootnode_from_project(self.main_model.project) def load_project(self,fname = None): if type(fname) is not str: dialog = QFileDialog(self) dialog.setWindowTitle('Load Project ...') dialog.setFileMode(QFileDialog.ExistingFile) # dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setNameFilter('*.pyecog') if dialog.exec(): fname = dialog.selectedFiles()[0] if type(fname) is str: print('load_project:Loading:',fname) if os.path.isfile(fname+'_autosave'): last_file_modification = os.path.getmtime(fname) last_autosave_modification = os.path.getmtime(fname+'_autosave') if last_autosave_modification>last_file_modification: msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText("A more recently modified autosave file exists, do you want to load it instead?") msg.setDetailedText("File name:" + fname + "\nLast autosave file modification: " + datetime.fromtimestamp(last_autosave_modification).isoformat(sep=' ') + "\nLast project file modification: " + datetime.fromtimestamp(last_file_modification).isoformat( sep=' ') ) msg.setWindowTitle("Load autosave") msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) retval = msg.exec_() if retval == QMessageBox.Yes: fname = fname+'_autosave' self.main_model.project.load_from_json(fname) self.tree_element.set_rootnode_from_project(self.main_model.project) if self.main_model.project.current_animal.eeg_init_time: init_time = np.min(self.main_model.project.current_animal.eeg_init_time) else: init_time = 0 plot_range = np.array([init_time, init_time+3600]) print('trying to plot ', plot_range) self.paired_graphics_view.set_scenes_plot_channel_data(plot_range) self.main_model.set_time_position(init_time) self.main_model.set_window_pos([init_time,init_time]) self.toggle_auto_save() def save(self): print('save action triggered') fname = self.main_model.project.project_file if not os.path.isfile(fname): self.save_as() else: print('Saving project to:', fname) self.main_model.project.save_to_json(fname) self.toggle_auto_save() def save_as(self): dialog = QFileDialog(self) dialog.setWindowTitle('Save Project as ...') dialog.setFileMode(QFileDialog.AnyFile) # dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter('*.pyecog') if dialog.exec(): fname = dialog.selectedFiles()[0] if not fname.endswith('.pyecog'): fname = fname + '.pyecog' print(fname) self.main_model.project.project_file = fname print('Saving project to:', self.main_model.project.project_file) self.main_model.project.save_to_json(fname) self.toggle_auto_save() def auto_save(self): print('autosave_save action triggered') fname = self.main_model.project.project_file if not os.path.isfile(fname): print('warning - project file does not exist yet') elif fname.endswith('.pyecog'): print('Auto saving project to:', fname+'_autosave') self.main_model.project.save_to_json(fname+'_autosave') else: print('project filename not in *.pyecog') def toggle_auto_save(self): if self.action_autosave.isChecked(): self.autosave_timer.start(60000) # autosave every minute else: self.autosave_timer.stop() def toggle_fullscreen(self): if self.action_fullscreen.isChecked(): self.showFullScreen() else: self.showNormal() def toggle_darkmode(self): if self.action_darkmode.isChecked(): print('Setting Dark Mode') # Fusion dark palette adapted from https://gist.github.com/QuantumCD/6245215. palette = QPalette() palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) # palette.setColor(QPalette.Base, QColor(25, 25, 25)) # too Dark palette.setColor(QPalette.Base, QColor(35, 39, 41)) # palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.AlternateBase, QColor(45, 50, 53)) palette.setColor(QPalette.ToolTipBase, Qt.black) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(42, 130, 218)) palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) palette.setColor(QPalette.HighlightedText, Qt.black) self.app_handle.setPalette(palette) self.main_model.color_settings['pen'].setColor(QColor(255,255,255,100)) self.main_model.color_settings['brush'].setColor(QColor(0,0,0,255)) self.paired_graphics_view.set_scenes_plot_channel_data() self.main_model.sigWindowChanged.emit(self.main_model.window) else: print('Setting Light Mode') palette = QPalette() self.app_handle.setPalette(palette) self.main_model.color_settings['pen'].setColor(QColor(0,0,0,100)) self.main_model.color_settings['brush'].setColor(QColor(255,255,255,255)) self.paired_graphics_view.set_scenes_plot_channel_data() self.main_model.sigWindowChanged.emit(self.main_model.window) def select_directory(self, label_text='Select a directory'): ''' Method launches a dialog allow user to select a directory ''' dialog = QFileDialog(self) dialog.setWindowTitle(label_text) dialog.setFileMode(QFileDialog.DirectoryOnly) dialog.setAcceptMode(QFileDialog.AcceptOpen) # we might want to set home directory using settings # for now rely on default behaviour # home = os.path.expanduser("~") # default, if no settings available # dialog.setDirectory(home) # dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setOption(QFileDialog.ShowDirsOnly, False) if dialog.exec(): return dialog.selectedFiles()[0] else: return '' def reload_plot(self): #print('reload') xmin,xmax = self.paired_graphics_view.insetview_plot.vb.viewRange()[0] x_range = xmax-xmin # index = self.tree_element.tree_view.currentIndex() # self.tree_element.model.data(index, TreeModel.prepare_for_plot_role) self.main_model.project.file_buffer.clear_buffer() self.main_model.project.file_buffer.get_data_from_range([xmin,xmax],n_envelope=10,channel=0) buffer_x_max = self.main_model.project.file_buffer.get_t_max_for_live_plot() #print(full_xrange) print('reload_plot',buffer_x_max,xmax) if buffer_x_max > xmax: #print('called set xrange') self.paired_graphics_view.insetview_plot.vb.setXRange(buffer_x_max-x_range,buffer_x_max, padding=0) def load_live_recording(self): self.timer.timeout.connect(self.reload_plot) if self.actionLiveUpdate.isChecked(): self.live_recording_timer.start(100) else: self.live_recording_timer.stop() def open_git_url(self): webbrowser.open('https://github.com/KullmannLab/pyecog2') def open_docs_url(self): webbrowser.open('https://jcornford.github.io/pyecog_docs/') def open_video_window(self): self.dock_list['Video'].show() self.show() def open_fft_window(self): self.dock_list['FFT'].show() self.dock_list['FFT'].widget().updateData() self.show() def open_wavelet_window(self): self.dock_list['Wavelet'].show() self.dock_list['Wavelet'].widget().update_data() self.show() def open_console_window(self): self.dock_list['Console'].show() self.show() def openNDFconverter(self): print('opening NDF converter') self.ndf_converter = NDFConverterWindow(parent=self) self.ndf_converter.show() def openProjectEditor(self): print('opening Project Editor') self.projectEditor = ProjectEditWindow(self.main_model.project,parent=self) self.projectEditor.show() def openFeatureExtractor(self): self.featureExtractorWindow = FeatureExtractorWindow(self.main_model.project,parent=self) self.featureExtractorWindow.show() def openClassifier(self): if hasattr(self,'ClassifierWindow'): self.ClassifierWindow.setWindowState((self.ClassifierWindow.windowState() & ~Qt.WindowMinimized)|Qt.WindowActive) self.ClassifierWindow.raise_() self.ClassifierWindow.show() return self.ClassifierWindow = ClassifierWindow(self.main_model.project,parent=self) geometry = self.ClassifierWindow.geometry() geometry.setHeight(self.geometry().height()) self.ClassifierWindow.setGeometry(geometry) self.ClassifierWindow.show() def export_annotations(self): dialog = QFileDialog(self) dialog.setWindowTitle('Export annotations as ...') dialog.setFileMode(QFileDialog.AnyFile) # dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter('*.csv') if dialog.exec(): fname = dialog.selectedFiles()[0] print(fname) print('Exporting annotations to:', fname) self.main_model.project.export_annotations(fname) def build_menubar(self): self.menu_bar = self.menuBar() # FILE section self.menu_file = self.menu_bar.addMenu("File") self.action_NDF_converter = self.menu_file.addAction("Open NDF converter") self.menu_file.addSeparator() self.action_load_directory = self.menu_file.addAction("Load directory") self.menu_file.addSeparator() self.actionLiveUpdate = self.menu_file.addAction("Live Recording") self.actionLiveUpdate.setCheckable(True) self.actionLiveUpdate.toggled.connect(self.load_live_recording) self.actionLiveUpdate.setChecked(False) self.actionLiveUpdate.setShortcut('Ctrl+L') self.menu_file.addSeparator() self.action_quit = self.menu_file.addAction("Quit") self.action_NDF_converter.triggered.connect(self.openNDFconverter) self.action_load_directory.triggered.connect(self.load_directory) self.action_quit.triggered.connect(self.close) self.actionLiveUpdate.triggered.connect(self.load_live_recording) # PROJECT section self.menu_project = self.menu_bar.addMenu("Project") self.action_edit_project = self.menu_project.addAction("Edit Project Settings") self.action_edit_project.triggered.connect(self.openProjectEditor) self.menu_project.addSeparator() self.action_new_project = self.menu_project.addAction("New Project") self.action_load_project = self.menu_project.addAction("Load Project") self.action_save = self.menu_project.addAction("Save Project") self.action_save.setShortcut('Ctrl+S') self.action_save_as = self.menu_project.addAction("Save Project as...") self.action_save_as.setShortcut('Ctrl+Shift+S') self.action_new_project.triggered.connect(self.new_project) self.action_load_project.triggered.connect(self.load_project) self.action_save.triggered.connect(self.save) self.action_save_as.triggered.connect(self.save_as) self.menu_project.addSeparator() self.action_autosave = self.menu_project.addAction("Enable autosave") self.action_autosave.setCheckable(True) self.action_autosave.toggled.connect(self.toggle_auto_save) self.action_autosave.setChecked(True) self.autosave_timer.timeout.connect(self.auto_save) # ANNOTATIONS section self.menu_annotations = self.menu_bar.addMenu("Annotations") self.annotations_undo = self.menu_annotations.addAction("Undo") self.annotations_undo.setShortcut('Ctrl+Z') self.annotations_undo.triggered.connect(self.main_model.annotations.step_back_in_history) self.annotations_redo = self.menu_annotations.addAction("Redo") self.annotations_redo.setShortcut('Ctrl+Shift+Z') self.annotations_redo.triggered.connect(self.main_model.annotations.step_forward_in_history) self.action_export_annotations = self.menu_annotations.addAction("Export to CSV") self.action_export_annotations.triggered.connect(self.export_annotations) self.action_import_annotations = self.menu_annotations.addAction("Import annotations") self.action_import_annotations.setDisabled(True) # CLASSIFIER section self.menu_classifier = self.menu_bar.addMenu("Classifier") self.action_setup_feature_extractor = self.menu_classifier.addAction("Feature Extractor Options") self.action_setup_classifier = self.menu_classifier.addAction("Classifier Options") self.action_setup_feature_extractor.triggered.connect(self.openFeatureExtractor) self.action_setup_classifier.triggered.connect(self.openClassifier) # self.action_train_classifier.setDisabled(True) # self.action_run_classifier.setDisabled(True) # self.action_review_classifications.setDisabled(True) # TOOLS section self.menu_tools = self.menu_bar.addMenu("Tools") self.action_open_video_window = self.menu_tools.addAction("Video") self.action_open_video_window.triggered.connect(self.open_video_window) # To do self.action_open_fft_window = self.menu_tools.addAction("FFT") self.action_open_fft_window.triggered.connect(self.open_fft_window) self.action_open_morlet_window = self.menu_tools.addAction("Morlet Wavelet Transform") self.action_open_morlet_window.triggered.connect(self.open_wavelet_window) self.action_open_console_window = self.menu_tools.addAction("Console") self.action_open_console_window.triggered.connect(self.open_console_window) # HELP section self.menu_help = self.menu_bar.addMenu("Help") self.action_show_hints = self.menu_help.addAction("Show Hints") self.action_show_hints.triggered.connect(self.dock_list['Hints'].show) self.action_reset_geometry = self.menu_help.addAction("Reset Main Window layout") self.action_reset_geometry.triggered.connect(self.reset_geometry) self.action_fullscreen = self.menu_help.addAction("Full Screen") self.action_fullscreen.setCheckable(True) self.action_fullscreen.toggled.connect(self.toggle_fullscreen) self.action_fullscreen.setChecked(False) self.action_darkmode = self.menu_help.addAction("Dark mode") self.action_darkmode.setCheckable(True) self.action_darkmode.toggled.connect(self.toggle_darkmode) self.action_darkmode.setChecked(False) self.menu_help.addSeparator() self.action_go_to_git = self.menu_help.addAction("Go to Git Repository") self.action_go_to_git.triggered.connect(self.open_git_url) self.action_go_to_doc = self.menu_help.addAction("Go to web documentation") self.action_go_to_doc.triggered.connect(self.open_docs_url) self.menu_bar.setNativeMenuBar(False) #self.menubar.addMenu("Edit") #self.menubar.addMenu("View") def closeEvent(self, event): self.auto_save() print('closing') settings = QSettings("PyEcog","PyEcog") settings.beginGroup("MainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.setValue("darkMode",self.action_darkmode.isChecked()) settings.setValue("autoSave",self.action_autosave.isChecked()) settings.endGroup() settings.beginGroup("ProjectSettings") settings.setValue("ProjectFileName", self.main_model.project.project_file) settings.endGroup() print('current project filename:',self.main_model.project.project_file) # for dock_name in self.dock_list.keys(): # settings.beginGroup(dock_name) # settings.setValue("windowGeometry", self.dock_list[dock_name].saveGeometry()) # # settings.setValue("windowState", self.dock_list[dock_name].saveState()) # settings.endGroup() self.saveState() print('Finished closeEvent') def keyPressEvent(self, evt): print('Key press captured by Main', evt.key()) modifiers = evt.modifiers() if evt.key() == QtCore.Qt.Key_Space: print('Space pressed') self.video_element.play() return if evt.key() == QtCore.Qt.Key_Left: if modifiers == QtCore.Qt.ShiftModifier: self.paired_graphics_view.overview_page_left() else: self.paired_graphics_view.insetview_page_left() return if evt.key() == QtCore.Qt.Key_Right: if modifiers == QtCore.Qt.ShiftModifier: self.paired_graphics_view.overview_page_right() else: self.paired_graphics_view.insetview_page_right() return if evt.key() == QtCore.Qt.Key_Delete: # self.main_model.annotations.delete_annotation(self.main_model.annotations.focused_annotation) self.annotation_table.removeSelection() return numbered_keys = [QtCore.Qt.Key_1,QtCore.Qt.Key_2,QtCore.Qt.Key_3,QtCore.Qt.Key_4,QtCore.Qt.Key_5, QtCore.Qt.Key_6,QtCore.Qt.Key_7,QtCore.Qt.Key_8,QtCore.Qt.Key_9,QtCore.Qt.Key_0] for i in range(len(numbered_keys)): if evt.key() == numbered_keys[i]: print(i+1,'pressed') label = self.annotation_parameter_tree.get_label_from_shortcut(i + 1) if label is not None: if self.main_model.annotations.focused_annotation is None: print('Adding new annotation') new_annotation = AnnotationElement(label = label, start = self.main_model.window[0], end = self.main_model.window[1], notes = '') self.main_model.annotations.add_annotation(new_annotation) self.main_model.annotations.focusOnAnnotation(new_annotation) else: print('Calling annotation_table changeSelectionLabel') self.annotation_table.changeSelectionLabel(label) # annotation = self.main_model.annotations.focused_annotation # annotation.setLabel(self.main_model.annotations.labels[i]) # self.main_model.annotations.focusOnAnnotation(annotation) return
class BatchProcessor(QObject): ''' Worker thread ''' signals = ObjectSignals() setLightPWM = pyqtSignal(float) # control signal gotoXY = pyqtSignal( float, float, bool) # boolean indicate relative to stage origin or not gotoX = pyqtSignal(float, bool) gotoY = pyqtSignal(float, bool) gotoZ = pyqtSignal(float) findDiaphragm = pyqtSignal() disableMotors = pyqtSignal() findWell = pyqtSignal() # computeSharpnessScore = pyqtSignal() rSnapshotTaken = pyqtSignal() # repeater signal # rSharpnessScore = pyqtSignal() # repeater signal rWellFound = pyqtSignal() # repeat signal rDiaphragmFound = pyqtSignal() # repeat signal rClipRecorded = pyqtSignal() # repeat signal rPositionReached = pyqtSignal() # repeat signal takeSnapshot = pyqtSignal(str) recordClip = pyqtSignal(str, int) setLogFileName = pyqtSignal(str) stopCamera = pyqtSignal() startCamera = pyqtSignal() startAutoFocus = pyqtSignal(float) focussed = pyqtSignal() # repeater signal def __init__(self): super().__init__() self.settings = QSettings("settings.ini", QSettings.IniFormat) self.loadSettings() self.timer = QTimer(self) self.timer.timeout.connect(self.run) self.isInterruptionRequested = False self.foundDiaphragmLocation = None self.foundWellLocation = None self.lightLevel = 1.0 # initial value, will be set later by user # self.sharpnessScore = 0 def loadSettings(self): self.msg("info;loading settings from {:s}".format( self.settings.fileName())) self.resolution = float( self.settings.value('camera/resolution_in_px_per_mm')) def loadBatchSettings(self): self.msg("info;loading batch settings from {:s}".format( self.batch_settings.fileName())) # load run info self.run_id = self.batch_settings.value('run/id') self.run_note = self.batch_settings.value('run/note') t = time.strptime( self.batch_settings.value('run/duration')[1], '%H:%M:%S') days = int(self.batch_settings.value('run/duration')[0].split('d')[0]) self.run_duration_s = ( (24 * days + t.tm_hour) * 60 + t.tm_min) * 60 + t.tm_sec t = time.strptime(self.batch_settings.value('run/wait'), '%H:%M:%S') self.run_wait_s = (t.tm_hour * 60 + t.tm_min) * 60 + t.tm_sec s = str(self.batch_settings.value('run/shutdown')) self.shutdown = s.lower() in ['true', '1', 't', 'y', 'yes'] s = str(self.batch_settings.value('run/snapshot')) self.snapshot = s.lower() in ['true', '1', 't', 'y', 'yes'] s = str(self.batch_settings.value('run/videoclip')) self.videoclip = s.lower() in ['true', '1', 't', 'y', 'yes'] self.videoclip_length = int( self.batch_settings.value('run/clip_length')) # load well-plate dimensions and compute well locations self.plate_note = self.batch_settings.value('plate/note') self.nr_of_columns = int( self.batch_settings.value('plate/nr_of_columns')) self.nr_of_rows = int(self.batch_settings.value('plate/nr_of_rows')) self.A1_to_side_offset = float( self.batch_settings.value('plate/A1_to_side_offset')) self.column_well_spacing = float( self.batch_settings.value('plate/column_well_spacing')) self.A1_to_top_offset = float( self.batch_settings.value('plate/A1_to_top_offset')) self.row_well_spacing = float( self.batch_settings.value('plate/row_well_spacing')) self.computeWellLocations() def computeWellLocations(self): # load the wells to process nr_of_wells = self.batch_settings.beginReadArray("wells") self.wells = [] for i in range(0, nr_of_wells): self.batch_settings.setArrayIndex(i) well_id = self.batch_settings.value('id') well_note = self.batch_settings.value('note') r = re.split('(\d+)', well_id) row = ord(r[0].lower()) - 96 col = int(r[1]) location_mm = [round(self.A1_to_side_offset + (col-1)*self.column_well_spacing, 2), \ round(self.A1_to_top_offset + (row-1)*self.row_well_spacing, 2), \ 0] self.wells.append( Well(name=well_id, position=[row, col], location=location_mm)) self.batch_settings.endArray( ) # close array, also required when opening! def msg(self, text): if text: text = self.__class__.__name__ + ";" + str(text) print(text) self.signals.message.emit(text) @pyqtSlot() def start(self): # open storage folder # dlg = QFileDialog() # self.storage_path = QFileDialog.getExistingDirectory(dlg, 'Open storage folder', '/media/pi/', QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) # if self.storage_path == "" or self.storage_path is None: # return self.storage_path = os.path.sep.join( [os.getcwd(), self.settings.value('temp_folder')]) # clear temporary storage path os.system('rm {:s}'.format(os.path.sep.join([self.storage_path, "*.*"]))) # open batch definition file dlg = QFileDialog() self.batch_file_name = QFileDialog.getOpenFileName( dlg, 'Open batch definition file', os.getcwd(), "Ini file (*.ini)")[0] if self.batch_file_name == "" or self.batch_file_name is None: return self.batch_settings = QSettings(self.batch_file_name, QSettings.IniFormat) self.loadBatchSettings() # copy batch definition file to storage folder os.system('cp {:s} {:s}'.format(self.batch_file_name, self.storage_path)) note_file_name = os.path.sep.join( [self.storage_path, self.run_id + ".log"]) self.setLogFileName.emit(note_file_name) # copy files to webdav host conn_settings = QSettings("connections.ini", QSettings.IniFormat) self.webdav_path = os.path.sep.join( [conn_settings.value('webdav/storage_path'), self.run_id]) self.webdav_options = { 'webdav_hostname': conn_settings.value('webdav/hostname'), 'webdav_login': conn_settings.value('webdav/login'), 'webdav_password': conn_settings.value('webdav/password') } try: self.webdav_client = Client(self.webdav_options) self.webdav_client.mkdir(self.webdav_path) self.webdav_client.push(remote_directory=self.webdav_path, local_directory=self.storage_path) except WebDavException as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) # create temporary image storage path self.image_storage_path = os.path.sep.join([self.storage_path, 'img']) if not os.path.exists(self.image_storage_path): os.makedirs(self.image_storage_path) # start-up recipe self.msg("info;plate note: {:s}".format(self.plate_note)) self.msg("info;run note: {:s}".format(self.run_note)) self.msg("info;{:d} wells found in {:s}".format( len(self.wells), self.batch_settings.fileName())) self.msg("info;{:s} run during {:d}s with {:d}s interleave".format( self.run_id, self.run_duration_s, self.run_wait_s)) self.setLightPWM.emit(0.2) self.startCamera.emit() self.msg("info;goto first well") x = self.wells[0].location[0] y = self.wells[0].location[1] z = 33 #self.wells[0].location[2] self.gotoXY.emit(x, y, True) self.wait_signal(self.rPositionReached, 10000) self.gotoZ.emit(z) self.wait_signal(self.rPositionReached, 10000) # Let the user set the x,y,z-location manually by moving to first well and open dialog dialog = ManualPositioningDialog(x, y, z) dialog.stageXTranslation.valueChanged.connect( lambda x: self.gotoX.emit(x, True)) dialog.stageYTranslation.valueChanged.connect( lambda y: self.gotoY.emit(y, True)) dialog.stageZTranslation.valueChanged.connect( lambda z: self.gotoZ.emit(z)) dialog.light.valueChanged.connect( lambda x: self.setLightPWM.emit(x / 100)) dialog.exec_() self.A1_to_side_offset = round(dialog.stageXTranslation.value(), 3) self.A1_to_top_offset = round(dialog.stageYTranslation.value(), 3) z = round(dialog.stageZTranslation.value(), 3) self.lightLevel = round(dialog.light.value() / 100, 3) self.msg("info;user set well A1 at (x,y)=({:.3f},{:.3f})".format( self.A1_to_side_offset, self.A1_to_top_offset)) self.msg("info;user set focus at z={:.3f}".format(z)) self.msg("info;user set light intensity at {:.3f}".format( self.lightLevel)) # recompute the well locations in our batch self.computeWellLocations() for well in self.wells: well.location[ 2] = z # copy z, later on we may do autofocus per well # start timer self.prev_note_nr = 0 # for logging self.start_time_s = time.time() self.timer.start(0) def run(self): ''' Timer call back function, als initiates next one-shot ''' start_run_time_s = time.time() self.setLightPWM.emit(self.lightLevel) self.startCamera.emit() self.msg("info;go home") self.gotoXY.emit(0, 0, False) self.wait_signal(self.rPositionReached, 10000) self.msg("info;goto first well") self.gotoXY.emit(self.wells[0].location[0], self.wells[0].location[1], True) self.wait_signal(self.rPositionReached, 10000) # self.wait_ms(60000) # wait for camera to adjust to light for well in self.wells: self.msg("info;gauging well {:s})".format(well.name)) # split goto xyz command self.gotoX.emit(well.location[0], True) self.wait_signal(self.rPositionReached, 10000) self.gotoY.emit(well.location[1], True) self.wait_signal(self.rPositionReached, 10000) self.gotoZ.emit(well.location[2]) self.wait_signal(self.rPositionReached, 10000) # autofocus if self.batch_settings.value('run/autofocus', False, type=bool): self.new_z = 0 self.startAutoFocus.emit(well.location[2]) self.wait_signal(self.focussed, 100000) if self.new_z != 0: well.location[2] = self.new_z else: # focus failed, so return to initial z position self.gotoZ.emit(well.location[2]) self.wait_signal(self.rPositionReached, 10000) try: # clear temporary storage path os.system('rm {:s}'.format( os.path.sep.join([self.image_storage_path, "*.*"]))) except err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) # take snapshot or video self.wait_ms(2000) prefix = os.path.sep.join([ self.image_storage_path, str(well.position) + "_" + str(well.location) ]) if self.snapshot: self.takeSnapshot.emit(prefix) self.wait_signal(self.rSnapshotTaken) # snapshot taken if self.videoclip and self.videoclip_length > 0: self.recordClip.emit(prefix, self.videoclip_length) self.wait_signal(self.rClipRecorded) # clip recorded try: # push data remote_path = os.path.sep.join([self.webdav_path, well.name]) self.msg(": info; pushing data to {}".format(remote_path)) self.webdav_client.mkdir(remote_path) self.webdav_client.push( remote_directory=remote_path, local_directory=self.image_storage_path) # push log file self.webdav_client.push(remote_directory=self.webdav_path, local_directory=self.storage_path) except WebDavException as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) # Wrapup current round of acquisition self.setLightPWM.emit(0.00) self.stopCamera.emit() elapsed_total_time_s = time.time() - self.start_time_s elapsed_run_time_s = time.time() - start_run_time_s self.msg("info;single run time={:.1f}s".format(elapsed_run_time_s)) self.msg("info;total run time={:.1f}s".format(elapsed_total_time_s)) progress_percentage = int(100 * elapsed_total_time_s / self.run_duration_s) self.signals.progress.emit(progress_percentage) self.msg("info;progress={:d}%".format(progress_percentage)) # send a notification note_nr = int(progress_percentage / 10) if note_nr != self.prev_note_nr: self.prev_note_nr = note_nr message = """Subject: Progress = {}% \n\n Still {} s left""".format( progress_percentage, int(self.run_duration_s - elapsed_total_time_s)) # do something fancy here in future: https://realpython.com/python-send-email/#sending-fancy-emails self.sendNotification(message) # check if we still have time to do another round if elapsed_total_time_s + self.run_wait_s < self.run_duration_s: self.timer.setInterval(self.run_wait_s * 1000) self.msg("info;wait for {:.1f} s".format(self.run_wait_s)) else: self.timer.stop() self.signals.ready.emit() self.msg("info;run finalized") message = """Subject: run finalized""" # do something fancy here in future: https://realpython.com/python-send-email/#sending-fancy-emails self.sendNotification(message) if self.shutdown: self.msg("info;emitting finished") self.signals.finished.emit() def sendNotification(self, message): conn_settings = QSettings("connections.ini", QSettings.IniFormat) port = 465 # For SSL context = ssl.create_default_context() # Create a secure SSL context try: with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server: login = conn_settings.value('smtp/login') password = conn_settings.value('smtp/password') server.login(login, password) server.sendmail(conn_settings.value('smtp/login'), \ conn_settings.value('subscriber/email'), \ message) except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) def requestInterruption(self): self.isInterruptionRequested = True @pyqtSlot(np.ndarray) def diaphragmFound(self, location): self.foundDiaphragmLocation = location self.msg("info;diaphragmFound signal received") self.rDiaphragmFound.emit() @pyqtSlot(np.ndarray) def wellFound(self, location): self.foundWellLocation = location self.msg("info;wellFound signal received") self.rWellFound.emit() # @pyqtSlot(float) # def setSharpnessScore(self, score): # self.sharpnessScore = score # self.msg("info;sharpnessScore signal received") # self.rSharpnessScore.emit() @pyqtSlot() def snapshotTaken(self): self.msg("info;snapshotTaken signal received") self.rSnapshotTaken.emit() @pyqtSlot() def clipRecorded(self): self.msg("info;clipRecorded signal received") self.rClipRecorded.emit() @pyqtSlot() def positionReached(self): self.msg("info;positionReached signal received") self.rPositionReached.emit() @pyqtSlot(float) def focussedSlot(self, val): self.new_z = val self.focussed.emit() @pyqtSlot() def stop(self): self.msg("info;stopping") self.requestInterruption() if self.timer.isActive(): self.timer.stop() self.signals.finished.emit() def wait_ms(self, timeout): ''' Block loop until timeout (ms) elapses. ''' loop = QEventLoop() QTimer.singleShot(timeout, loop.exit) loop.exec_() def wait_signal(self, signal, timeout=100): ''' Block loop until signal emitted, or timeout (ms) elapses. ''' loop = QEventLoop() signal.connect(loop.quit) # only quit is a slot of QEventLoop QTimer.singleShot(timeout, loop.exit) loop.exec_()
class PiVideoStream(QThread): image = None finished = pyqtSignal() postMessage = pyqtSignal(str) frame = pyqtSignal(np.ndarray) progress = pyqtSignal(int) captured = pyqtSignal() camera = PiCamera() storagePath = None cropRect = [0] * 4 ## @param ins is the number of instances created. This may not exceed 1. ins = 0 def __init__(self): super().__init__() ## Instance limiter. Checks if an instance exists already. If so, it deletes the current instance. if PiVideoStream.ins >= 1: del self self.postMessage.emit( "{}: error; multiple instances of created, while only 1 instance is allowed" .format(__class__.__name__)) return try: PiVideoStream.ins += 1 except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) else: warnings.filterwarnings('default', category=DeprecationWarning) self.settings = QSettings("settings.ini", QSettings.IniFormat) self.loadSettings() ## self.initStream() def loadSettings(self): self.postMessage.emit( "{}: info; loading camera settings from {}".format( self.__class__.__name__, self.settings.fileName())) # load self.monochrome = self.settings.value('camera/monochrome', False, type=bool) self.use_video_port = self.settings.value('camera/use_video_port', False, type=bool) self.sensor_mode = int(self.settings.value('camera/sensor_mode')) # set frame sizes if self.sensor_mode == 0: self.frame_size = (4056, 3040) elif self.sensor_mode == 1: self.frame_size = (1920, 1080) elif self.sensor_mode == 2 or self.sensor_mode == 3: self.frame_size = (3280, 2464) elif self.sensor_mode == 4: self.frame_size = (1640, 1232) elif self.sensor_mode == 5: self.frame_size = (1640, 922) elif self.sensor_mode == 6: self.frame_size = (1280, 720) elif self.sensor_mode == 7: self.frame_size = (640, 480) else: raise ValueError frame_size_str = self.settings.value('display_frame_size') (width, height) = frame_size_str.split('x') self.display_frame_size = (int(width), int(height)) if not self.monochrome: self.display_frame_size = self.display_frame_size + (3, ) # set more camera parameters self.camera.resolution = self.frame_size self.camera.sensor_mode = self.sensor_mode self.camera.framerate = int(self.settings.value('camera/frame_rate')) self.camera.image_effect = self.settings.value('camera/effect') self.camera.shutter_speed = int( self.settings.value('camera/shutter_speed')) self.camera.iso = int(self.settings.value( 'camera/iso')) # should force unity analog gain self.camera.video_denoise = self.settings.value('camera/video_denoise', False, type=bool) # dunno if setting awb mode manually is really useful ## self.camera.awb_mode = 'off' ## self.camera.awb_gains = 5.0 ## self.camera.meter_mode = 'average' ## self.camera.exposure_mode = 'auto' # 'sports' to reduce motion blur, 'off'after init to freeze settings @pyqtSlot() def initStream(self): # Initialize the camera stream if self.isRunning(): # in case init gets called, while thread is running self.postMessage.emit( "{}: error; video stream is already running".format( __class__.__name__)) else: # init camera and open stream if self.monochrome: ## self.camera.color_effects = (128,128) # return monochrome image, not required if we take Y frame only. self.rawCapture = PiYArray(self.camera, size=self.camera.resolution) self.stream = self.camera.capture_continuous( self.rawCapture, 'yuv', self.use_video_port) else: self.rawCapture = PiRGBArray(self.camera, size=self.camera.resolution) self.stream = self.camera.capture_continuous( self.rawCapture, 'bgr', self.use_video_port) # allocate memory self.image = np.empty(self.camera.resolution + (1 if self.monochrome else 3, ), dtype=np.uint8) # init crop rectangle if self.cropRect[2] == 0: self.cropRect[2] = self.image.shape[1] if self.cropRect[3] == 0: self.cropRect[3] = self.image.shape[0] # restart thread self.start() wait_ms(1000) msg = "{}: info; video stream initialized with frame size = {} and {:d} channels".format(\ __class__.__name__, str(self.camera.resolution), 1 if self.monochrome else 3) self.postMessage.emit(msg) @pyqtSlot() def run(self): try: self.fps = FPS().start() for f in self.stream: if self.isInterruptionRequested(): self.finished.emit() return self.rawCapture.seek(0) self.image = f.array # grab the frame from the stream ## # Crop ## if (self.cropRect[2] > self.cropRect[0]) and (self.cropRect[3] > self.cropRect[1]): ## self.frame.emit(self.image[self.cropRect[0]:self.cropRect[2], self.cropRect[1]:self.cropRect[3]]) # Emit resized frame for speed self.frame.emit( cv2.resize(self.image, self.display_frame_size[:2])) self.fps.update() except Exception as err: self.postMessage.emit("{}: error; type: {}, args: {}".format( self.__class__.__name__, type(err), err.args)) @pyqtSlot() def stop(self): self.postMessage.emit("{}: info; stopping".format(__class__.__name__)) if self.isRunning(): self.requestInterruption() wait_signal(self.finished, 2000) self.fps.stop() msg = "{}: info; approx. processing speed: {:.2f} fps".format( self.__class__.__name__, self.fps.fps()) self.postMessage.emit(msg) print(msg) self.quit() @pyqtSlot(str) def takeImage(self, filename_prefix=None): if filename_prefix is not None: (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_'.format(round(time.time() * 1000)) + tail + '.png' ]) else: filename = '{:016d}'.format(round(time.time() * 1000)) + '.png' # open path if self.storagePath is not None: filename = os.path.sep.join([self.storagePath, filename]) # write image wait_signal(self.frame, 5000) # wait for first frame to be shot cv2.imwrite(filename, self.image) self.captured.emit() self.postMessage.emit("{}: info; image written to {}".format( __class__.__name__, filename)) @pyqtSlot(str, int) def recordClip(self, filename_prefix=None, duration=10): # open path (head, tail) = os.path.split(filename_prefix) if not os.path.exists(head): os.makedirs(head) filename = os.path.sep.join([ head, '{:016d}_'.format(round(time.time() * 1000)) + tail + '.avi' ]) ##"TODO; changing camera settings may get the process killed after several hours, probably better to open the stream in video resolution from the start if the videorecording is required!") # set video clip parameters self.stop() frame_size_str = self.settings.value('camera/clip_frame_size') (width, height) = frame_size_str.split('x') self.camera.resolution = (int(width), int(height)) self.camera.sensor_mode = int( self.settings.value('camera/clip_sensor_mode')) self.camera.framerate = int( self.settings.value('camera/clip_frame_rate')) self.camera.image_effect = effect self.use_video_port = True self.monochrome = True self.initStream() # define the codec and create VideoWriter object fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(filename, fourcc, frame_rate, frame_size) self.msg("info; start recording video to " + filename) # write file for i in range(0, duration * frame_rate): self.progress.emit(int(100 * i / (duration * frame_rate - 1))) wait_signal(self.frame, 1000) if self.image is not None: out.write(self.image) # close out.release() self.msg("info; recording done") ## self.camera.start_recording(filename) ## self.camera.wait_recording(duration) ## self.camera.stop_recording() # revert to original parameters self.loadSettings() self.initStream() self.clipRecorded.emit() @pyqtSlot(str) def setStoragePath(self, path): self.storagePath = path @pyqtSlot(int) def setCropXp1(self, val): if 0 <= val <= self.cropRect[3]: self.cropRect[1] = val else: raise ValueError('crop x1') @pyqtSlot(int) def setCropXp2(self, val): if self.cropRect[1] < val < self.camera.resolution[1]: self.cropRect[3] = val else: raise ValueError('crop x2') @pyqtSlot(int) def setCropYp1(self, val): if 0 <= val <= self.cropRect[2]: self.cropRect[0] = val else: raise ValueError('crop y1') @pyqtSlot(int) def setCropYp2(self, val): if self.cropRect[0] < val < self.camera.resolution[0]: self.cropRect[2] = val else: raise ValueError('crop y2')
class PrintHat(QThread): signals = ObjectSignals() confirmed = pyqtSignal() # internal signal homed = pyqtSignal() positionReached = pyqtSignal() mutex = QMutex() sio = None eps = 1e-3 ## @param ins is the number of instances created. This may not exceed 1. ins = 0 def __init__(self): super().__init__() self.settings = QSettings("settings.ini", QSettings.IniFormat) self.loadSettings() self.buf = bytearray() self.is_paused = True # Serial read communication thread is pauses self.is_homed = False # PrintHat motors have been homed self.is_ready = False # Printhat and klipper are ready self.has_arrived = False # latest position ahs been arrived self.position_x = None # last known position self.position_y = None self.position_z = None ## Instance limiter. Checks if an instance exists already. If so, it deletes the current instance. if PrintHat.ins >= 1: del self self.msg( "error;multiple instances of {:s} created, while only 1 instance is allowed" .format(__class__.__name__)) return try: self.connectKlipper(self.port) PrintHat.ins += 1 except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) self.stop() def loadSettings(self): self.msg("info;loading settings from {:s}".format( self.settings.fileName())) self.port = self.settings.value('printhat/port') @pyqtSlot() def run(self): """ Initialise the runner function with passed args, kwargs. """ if self.sio: while True: if self.isInterruptionRequested(): self.signals.finished.emit() return try: if not self.is_paused: reply_msg = self.sio.readline() if reply_msg: self.msg("info;printHat replied: " + reply_msg) if 'Klipper state' in reply_msg: if 'Ready' in reply_msg: self.is_ready = True self.signals.ready.emit() elif 'Shutdown' in reply_msg or 'Disconnect' in reply_msg: self.is_ready = False elif '!!' in reply_msg: if 'Must home axis' in reply_msg: self.is_homed = False elif 'ok' in reply_msg: self.confirmed.emit() elif 'X:' in reply_msg: r = re.findall(r"[-+]?\d*\.\d+|\d+", reply_msg) self.position_x = float(r[0]) self.position_y = float(r[1]) self.position_z = float(r[2]) except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) @pyqtSlot() def stop(self): self.msg("info;stopping") self.disableMotors() self.setLightPWM(0.0) self.setFanPWM(0.0) self.wait_signal(self.confirmed, 1000) self.requestInterruption() self.wait_signal(self.signals.finished, 10000) self.wait_ms(500) # some signals may need to settle self.sio = None self.disconnectKlipper() self.quit() def msg(self, text): if text: text = self.__class__.__name__ + ";" + str(text) print(text) self.signals.message.emit(text) ## @brief PrintHat::connect connects to the pseudo serial port /tmp/printer. ## This port is the link with the klipper library which handles all the g-code and communication with the printHat. # @param port is the port to be connected to. def connectKlipper(self, port): try: self.mutex.tryLock(1000) self.msg("info;starting klipper service") # make sure klipper service is active os.system( 'sudo service klipper restart && sudo service klipper status | more' ) ## Try to open the serial port self.wait_ms(1000) ser = serial.Serial(self.port, 250000, timeout=1) self.sio = io.TextIOWrapper( io.BufferedRWPair(ser, ser), line_buffering=True) #, newline="\r\n") if self.sio: #port.is_open: self.msg( "info;connected to printHat via serial port {}".format( self.port)) self.is_paused = False else: self.msg("error;cannot connect to printHat via serial port {}". format(self.port)) self.mutex.unlock() except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) def disconnectKlipper(self): try: self.mutex.tryLock(1000) ## Stop klipper service and show its status self.msg("info;stopping klipper service") os.system( 'sudo service klipper stop && sudo service klipper status | more' ) self.mutex.unlock() except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) finally: return def wait_ms(self, timeout): ''' Block loop until timeout (ms) elapses. ''' loop = QEventLoop() QTimer.singleShot(timeout, loop.exit) loop.exec_() def wait_signal(self, signal, timeout=1000): ''' Block loop until signal received, or until timeout (ms) elapsed. ''' loop = QEventLoop() signal.connect(loop.quit) # only quit is a slot of QEventLoop QTimer.singleShot(timeout, loop.exit) loop.exec_() ## @brief PrintHat::sendGcode writes a byte array containing a G-code to the serial port. # @param gcode_string is the string to be written to the serial port. def sendGcode(self, gcode_string): if self.sio: try: self.sio.write(gcode_string + "\r\n") self.sio.flush( ) # it is buffering. required to get the data out *now* self.msg("info;" + self.sendGcode.__name__ + " " + gcode_string) self.wait_signal(self.confirmed, 10000) except Exception as err: traceback.print_exc() self.signals.error.emit( (type(err), err.args, traceback.format_exc())) else: self.msg("error;no serial connection with printHat.") ## @brief PrintHat::firmwareRestart restarts the firmware and reloads the config in the klipper software. @pyqtSlot() def firmwareRestart(self): self.sendGcode("FIRMWARE_RESTART") ## @brief PrintHat::emergencyBreak stops all motors and shuts down the printHat. A firmware restart command is necessary to restart the system. @pyqtSlot() def emergencyBreak(self): self.msg("error;emergency break! restart the firmware") self.sendGcode("M112") @pyqtSlot(float) def setLightPWM(self, val): ''' Set PrintHAT light output pin to PWM value. Args: val (float): PWM dutycycle, between 0.0 and 1.0. Raises: Returns: ''' self.sendGcode("SET_PIN PIN=light VALUE=" + str(val)) @pyqtSlot(float) def setFanPWM(self, val): ''' Set PrintHAT fan output pin to PWM value. Args: val (float): PWM dutycycle, between 0.0 and 1.0. Raises: Returns: ''' clip_val = 1.0 val = val if val < clip_val else clip_val self.sendGcode("SET_PIN PIN=rpi_fan VALUE={:1.2f}".format(val)) @pyqtSlot() def enableMotors(self): ## why can we not re-enable motors during steps? A: Because M17 is not implemented by Klipper self.msg("info;enable motors") self.sendGcode("M17") @pyqtSlot() def disableMotors(self): # see https://reprap.org/wiki/G-code#M84:_Stop_idle_hold ## On Klipper M84 is equivalent to G-code#M18:_Disable_all_stepper_motors self.msg("info;stop the idle and hold on all axes") self.sendGcode("M84") self.sendGcode("M18") @pyqtSlot() def homeXYZ(self): if self.is_ready: self.msg("info;homeXY called") self.position_x = self.position_y = self.position_z = None self.sendGcode("G28 X Y Z") # wait until we have really reached home, this can take a while for i in range(0, 20): # limit the number of tries self.wait_ms(10000) self.getPosition() if self.position_x is None or self.position_y is None or self.position_z is None: self.wait_ms(1000) # homing is slow, so wait a bit more elif abs(self.position_x) < self.eps and abs( self.position_y) < self.eps and abs( self.position_z) < self.eps: self.msg("info;homeXY confirmed") self.is_homed = True self.homed.emit() break if not self.is_homed: self.msg("error;homeXY failed") else: self.msg("error;printhat not ready") @pyqtSlot() def getPosition(self): self.sendGcode("M400") # Wait for current moves to finish self.sendGcode("M114") if self.position_x is not None and self.position_y is not None and self.position_z is not None: self.msg("info;current position = ({:.3f}, {:.3f}, {:.3f})".format( self.position_x, self.position_y, self.position_z)) else: self.msg("error;current position unknown") @pyqtSlot(float, float, float) def gotoXYZ(self, x=None, y=None, z=None): if self.mutex.tryLock(100): gcode_string = "G1" gcode_string += " X{:.3f}".format(x) if x is not None else "" gcode_string += " Y{:.3f}".format(y) if y is not None else "" gcode_string += " Z{:.3f}".format(z) if z is not None else "" self.sendGcode(gcode_string) # if printhat returns error, initiate homing, and resend G-code if not self.is_homed: self.homeXYZ() self.sendGcode(gcode_string) # wait until we have really reached the desired location prev_x, prev_y, prev_z = 0, 0, 0 for i in range(0, 10): # limit the number of tries self.wait_ms(10) self.getPosition() if self.position_x is not None and self.position_y is not None and self.position_z is not None: error = 0 error += (self.position_x - x)**2 if x is not None else 0 error += (self.position_y - y)**2 if y is not None else 0 error += (self.position_z - z)**2 if z is not None else 0 if sqrt(error) < self.eps \ or ( abs(self.position_x-prev_x) < self.eps and \ abs(self.position_y-prev_y) < self.eps and \ abs(self.position_z-prev_z) < self.eps ): self.msg("info;gotoXYZ confirmed") self.positionReached.emit() break else: prev_x, prev_y, prev_z = self.position_x, self.position_y, self.position_z self.mutex.unlock() else: self.msg("error;mutex lock failed") @pyqtSlot(float, float, bool) def gotoXY(self, x, y, relative=True): ''' Move to a postion in the horizontal plane relative to the stage origin (relative=True) or to home (relative=False) ''' if relative: x += float(self.settings.value( 'camera/centre_wrt_home_in_mm')[0]) - float( self.settings.value('stage/origin_wrt_home_in_mm')[0]) y += float(self.settings.value( 'camera/centre_wrt_home_in_mm')[1]) - float( self.settings.value('stage/origin_wrt_home_in_mm')[1]) self.gotoXYZ(x=x, y=y) @pyqtSlot(float, bool) def gotoX(self, x, relative=True): ''' Move to x relative to the stage origin (relative=True) or to home (relative=False) ''' if relative: x += float(self.settings.value( 'camera/centre_wrt_home_in_mm')[0]) - float( self.settings.value('stage/origin_wrt_home_in_mm')[0]) self.gotoXYZ(x=x) @pyqtSlot(float, bool) def gotoY(self, y, relative=True): ''' Move to y relative to the stage origin (relative=True) or to home (relative=False) ''' if relative: y += float(self.settings.value( 'camera/centre_wrt_home_in_mm')[1]) - float( self.settings.value('stage/origin_wrt_home_in_mm')[1]) self.gotoXYZ(y=y) @pyqtSlot(float) def gotoZ(self, z): self.gotoXYZ(z=z)
def __init__(self): QWidget.__init__(self) Ui_MainScreen.__init__(self) self.setupUi(self) self.settings = Settings() self.restoreSettingsFromConfig() # quit app from settings window self.settings.sigExitOAS.connect(self.exitOAS) self.settings.sigRebootHost.connect(self.reboot_host) self.settings.sigShutdownHost.connect(self.shutdown_host) self.settings.sigConfigFinished.connect(self.configFinished) settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("General") if settings.value('fullscreen', True, type=bool): self.showFullScreen() app.setOverrideCursor(QCursor(Qt.BlankCursor)) settings.endGroup() print("Loading Settings from: ", settings.fileName()) self.labelWarning.hide() # add hotkey bindings QShortcut(QKeySequence("Ctrl+F"), self, self.toggleFullScreen) QShortcut(QKeySequence("F"), self, self.toggleFullScreen) QShortcut(QKeySequence(16777429), self, self.toggleFullScreen) # 'Display' Key on OAS USB Keyboard QShortcut(QKeySequence(16777379), self, self.shutdown_host) # 'Calculator' Key on OAS USB Keyboard QShortcut(QKeySequence("Ctrl+Q"), self, QCoreApplication.instance().quit) QShortcut(QKeySequence("Q"), self, QCoreApplication.instance().quit) QShortcut(QKeySequence("Ctrl+C"), self, QCoreApplication.instance().quit) QShortcut(QKeySequence("ESC"), self, QCoreApplication.instance().quit) QShortcut(QKeySequence("Ctrl+S"), self, self.showsettings) QShortcut(QKeySequence("Ctrl+,"), self, self.showsettings) QShortcut(QKeySequence(" "), self, self.radioTimerStartStop) QShortcut(QKeySequence(","), self, self.radioTimerStartStop) QShortcut(QKeySequence("."), self, self.radioTimerStartStop) QShortcut(QKeySequence("0"), self, self.radioTimerReset) QShortcut(QKeySequence("R"), self, self.radioTimerReset) QShortcut(QKeySequence("1"), self, self.manualToggleLED1) QShortcut(QKeySequence("2"), self, self.manualToggleLED2) QShortcut(QKeySequence("3"), self, self.manualToggleLED3) QShortcut(QKeySequence("4"), self, self.manualToggleLED4) QShortcut(QKeySequence("M"), self, self.toggleAIR1) QShortcut(QKeySequence("/"), self, self.toggleAIR1) QShortcut(QKeySequence("P"), self, self.toggleAIR2) QShortcut(QKeySequence("*"), self, self.toggleAIR2) QShortcut(QKeySequence("S"), self, self.toggleAIR4) QShortcut(QKeySequence("Enter"), self, self.getTimerDialog) QShortcut(QKeySequence("Return"), self, self.getTimerDialog) self.statusLED1 = False self.statusLED2 = False self.statusLED3 = False self.statusLED4 = False self.LED1on = False self.LED2on = False self.LED3on = False self.LED4on = False # Setup and start timers self.ctimer = QTimer() self.ctimer.timeout.connect(self.constantUpdate) self.ctimer.start(100) # LED timers self.timerLED1 = QTimer() self.timerLED1.timeout.connect(self.toggleLED1) self.timerLED2 = QTimer() self.timerLED2.timeout.connect(self.toggleLED2) self.timerLED3 = QTimer() self.timerLED3.timeout.connect(self.toggleLED3) self.timerLED4 = QTimer() self.timerLED4.timeout.connect(self.toggleLED4) # Setup OnAir Timers self.timerAIR1 = QTimer() self.timerAIR1.timeout.connect(self.updateAIR1Seconds) self.Air1Seconds = 0 self.statusAIR1 = False self.timerAIR2 = QTimer() self.timerAIR2.timeout.connect(self.updateAIR2Seconds) self.Air2Seconds = 0 self.statusAIR2 = False self.timerAIR3 = QTimer() self.timerAIR3.timeout.connect(self.updateAIR3Seconds) self.Air3Seconds = 0 self.statusAIR3 = False self.radioTimerMode = 0 # count up mode self.timerAIR4 = QTimer() self.timerAIR4.timeout.connect(self.updateAIR4Seconds) self.Air4Seconds = 0 self.statusAIR4 = False self.streamTimerMode = 0 # count up mode # Setup NTP Check Thread self.checkNTPOffset = checkNTPOffsetThread(self) # Setup check NTP Timer self.ntpHadWarning = True self.ntpWarnMessage = "" self.timerNTP = QTimer() self.timerNTP.timeout.connect(self.triggerNTPcheck) # initial check self.timerNTP.start(1000) # Setup UDP Socket self.udpsock = QUdpSocket() settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("Network") port = int(settings.value('udpport', 3310)) settings.endGroup() self.udpsock.bind(port, QUdpSocket.ShareAddress) self.udpsock.readyRead.connect(self.cmdHandler) # display all host addresses self.displayAllHostaddresses() # set NTP warning settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") settings.beginGroup("NTP") if settings.value('ntpcheck', True, type=bool): self.ntpHadWarning = True self.ntpWarnMessage = "waiting for NTP status check" settings.endGroup()
SEPARATION_OPACITY = 0.25 SEPARATION_PADDING = .05 # percent # PROTOCOL TABLE COLORS SELECTED_ROW_COLOR = QColor.fromRgb(0, 0, 255) DIFFERENCE_CELL_COLOR = QColor.fromRgb(255, 0, 0) PROPERTY_FOUND_COLOR = QColor.fromRgb(0, 124, 0, 100) PROPERTY_NOT_FOUND_COLOR = QColor.fromRgb(124, 0, 0, 100) SEPARATION_ROW_HEIGHT = 30 SETTINGS = QSettings(QSettings.IniFormat, QSettings.UserScope, 'urh', 'urh') PROJECT_FILE = "URHProject.xml" DECODINGS_FILE = "decodings.txt" FIELD_TYPE_SETTINGS = os.path.realpath(os.path.join(SETTINGS.fileName(), "..", "fieldtypes.xml")) # DEVICE SETTINGS DEFAULT_IP_USRP = "192.168.10.2" DEFAULT_IP_RTLSDRTCP = "127.0.0.1" # DECODING NAMES DECODING_INVERT = "Invert" DECODING_DIFFERENTIAL = "Differential Encoding" DECODING_REDUNDANCY = "Remove Redundancy" DECODING_DATAWHITENING = "Remove Data Whitening (CC1101)" DECODING_CARRIER = "Remove Carrier" DECODING_BITORDER = "Change Bitorder" DECODING_EDGE = "Edge Trigger" DECODING_SUBSTITUTION = "Substitution" DECODING_EXTERNAL = "External Program"
class SubSettings: """A wrapper to QSettings. Provides an interface to all available Subconvert options.""" def __init__(self): # The following settings will cause saving config files to e.g. # ~/.config/subconvert/subconvert.ini organization = "subconvert" mainConfFile = "subconvert" programStateFile = "state" self._settings = QSettings(QSettings.IniFormat, QSettings.UserScope, organization, mainConfFile) self._programState = QSettings(QSettings.IniFormat, QSettings.UserScope, organization, programStateFile) def sync(self): self._settings.sync() self._programState.sync() def getUseDefaultDirectory(self): return self._settings.value("gui/use_default_dirs", True) def setUseDefaultDirectory(self, val): self._settings.setValue("gui/use_default_dirs", val) # # Last directory from which a file has been opened # def getLatestDirectory(self): if self.getUseDefaultDirectory(): ret = self._programState.value("gui/latest_dir", QDir.homePath()) if ret: return ret return QDir.homePath() def setLatestDirectory(self, val): self._programState.setValue("gui/latest_dir", val) # # Subtitle "property files" paths, number etc. # def getPropertyFilesPath(self): defaultDirName = "pfiles" defaultPath = os.path.join(os.path.dirname(self._programState.fileName()), defaultDirName) return self._programState.value("pfiles/path", defaultPath) def setPropertyFilesPath(self, val): self._programState.setValue("pfiles/path", val) def getMaxRememberedPropertyFiles(self): defaultMaxValue = 5 return self._programState.value("pfiles/max", defaultMaxValue) def setMaxRememberedPropertyFiles(self, val): self._programState.setValue("pfiles/max", val) def getLatestPropertyFiles(self): return self._programState.value("pfiles/latest", []) def addPropertyFile(self, val): maxPropertyFiles = self.getMaxRememberedPropertyFiles() - 1 propertyFiles = self.getLatestPropertyFiles() if val in propertyFiles: propertyFiles.remove(val) else: propertyFiles = propertyFiles[:maxPropertyFiles] propertyFiles.insert(0, val) self._programState.setValue("pfiles/latest", propertyFiles) def removePropertyFile(self, val): propertyFiles = self.getLatestPropertyFiles() try: index = propertyFiles.index(val) del propertyFiles[index] self._programState.setValue("pfiles/latest", propertyFiles) except ValueError: pass # # Generic functions for windows/widgets. Please note that passed QWidgets must have previously # set objects names via QWidget::setObjectName(str) method. The convention is to use underscores # as words separators (e.g. main_window, my_super_widget, etc.). # def setGeometry(self, widget, val): SubAssert(widget.objectName() != "", "Widget's name isn't set!") self._programState.setValue("gui/%s/geometry" % widget.objectName(), val) def getGeometry(self, widget, default=QByteArray()): SubAssert(widget.objectName() != "", "Widget's name isn't set!") return self._programState.value("gui/%s/geometry" % widget.objectName(), default) def setState(self, widget, val): SubAssert(widget.objectName() != "", "Widget's name isn't set!") self._programState.setValue("gui/%s/state" % widget.objectName(), val) def getState(self, widget, default=QByteArray()): SubAssert(widget.objectName() != "", "Widget's name isn't set!") return self._programState.value("gui/%s/state" % widget.objectName(), default) def setHidden(self, widget, val): SubAssert(widget.objectName() != "", "Widget's name isn't set!") self._programState.setValue("gui/%s/hidden" % widget.objectName(), val) def getHidden(self, widget, default=True): SubAssert(widget.objectName() != "", "Widget's name isn't set!") return str2Bool(self._programState.value("gui/%s/hidden" % widget.objectName(), default))