Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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}")
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
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()
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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  
Ejemplo n.º 10
0
 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)
Ejemplo n.º 11
0
# 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()
Ejemplo n.º 12
0
    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()
Ejemplo n.º 13
0
# 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
Ejemplo n.º 14
0
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()
Ejemplo n.º 15
0
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()
Ejemplo n.º 16
0
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()
Ejemplo n.º 17
0
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"
Ejemplo n.º 18
0
#
# 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
Ejemplo n.º 19
0
# 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:
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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())       
Ejemplo n.º 22
0
    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
Ejemplo n.º 24
0
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"
Ejemplo n.º 25
0
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')
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
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_()
Ejemplo n.º 28
0
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')
Ejemplo n.º 29
0
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)
Ejemplo n.º 30
0
    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()
Ejemplo n.º 31
0
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"
Ejemplo n.º 32
0
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))