Пример #1
0
class LayerCommunicator(QtCore.QObject):
    layer_check_changed = QtCore.Signal(object, bool)
Пример #2
0
class SliceWidget(QtWidgets.QWidget):

    label = TextProperty('_ui_label')
    slider_label = TextProperty('_ui_slider.label')
    slider_unit = TextProperty('_ui_slider.text_unit')
    slice_center = ValueProperty('_ui_slider.slider')
    mode = CurrentComboProperty('_ui_mode')
    use_world = ButtonProperty('_ui_slider.checkbox_world')

    slice_changed = QtCore.Signal(int)
    mode_changed = QtCore.Signal(str)

    def __init__(self,
                 label='',
                 world=None,
                 lo=0,
                 hi=10,
                 parent=None,
                 aggregation=None,
                 world_unit=None,
                 world_warning=False):

        super(SliceWidget, self).__init__(parent)

        if aggregation is not None:
            raise NotImplemented("Aggregation option not implemented")

        self._world = np.asarray(world)
        self._world_warning = world_warning
        self._world_unit = world_unit

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(3, 1, 3, 1)
        layout.setSpacing(0)

        top = QtWidgets.QHBoxLayout()
        top.setContentsMargins(3, 3, 3, 3)
        label = QtWidgets.QLabel(label)
        top.addWidget(label)

        mode = QtWidgets.QComboBox()
        mode.addItem('x', 'x')
        mode.addItem('y', 'y')
        mode.addItem('slice', 'slice')
        mode.currentIndexChanged.connect(
            lambda x: self.mode_changed.emit(self.mode))
        mode.currentIndexChanged.connect(self._update_mode)
        top.addWidget(mode)

        layout.addLayout(top)

        slider = load_ui('data_slice_widget.ui',
                         None,
                         directory=os.path.dirname(__file__))
        self._ui_slider = slider

        font = slider.label_warning.font()
        font.setPointSize(font.pointSize() * 0.75)
        slider.label_warning.setFont(font)

        slider.button_first.setStyleSheet('border: 0px')
        slider.button_first.setIcon(get_icon('playback_first'))
        slider.button_prev.setStyleSheet('border: 0px')
        slider.button_prev.setIcon(get_icon('playback_prev'))
        slider.button_back.setStyleSheet('border: 0px')
        slider.button_back.setIcon(get_icon('playback_back'))
        slider.button_stop.setStyleSheet('border: 0px')
        slider.button_stop.setIcon(get_icon('playback_stop'))
        slider.button_forw.setStyleSheet('border: 0px')
        slider.button_forw.setIcon(get_icon('playback_forw'))
        slider.button_next.setStyleSheet('border: 0px')
        slider.button_next.setIcon(get_icon('playback_next'))
        slider.button_last.setStyleSheet('border: 0px')
        slider.button_last.setIcon(get_icon('playback_last'))

        slider.slider.setMinimum(lo)
        slider.slider.setMaximum(hi)
        slider.slider.setValue((lo + hi) / 2)
        slider.slider.valueChanged.connect(
            lambda x: self.slice_changed.emit(self.mode))
        slider.slider.valueChanged.connect(
            nonpartial(self.set_label_from_slider))

        slider.label.setMinimumWidth(80)
        slider.label.setText(str(slider.slider.value()))
        slider.label.editingFinished.connect(
            nonpartial(self.set_slider_from_label))

        self._play_timer = QtCore.QTimer()
        self._play_timer.setInterval(500)
        self._play_timer.timeout.connect(nonpartial(self._play_slice))

        slider.button_first.clicked.connect(
            nonpartial(self._browse_slice, 'first'))
        slider.button_prev.clicked.connect(
            nonpartial(self._browse_slice, 'prev'))
        slider.button_back.clicked.connect(
            nonpartial(self._adjust_play, 'back'))
        slider.button_stop.clicked.connect(
            nonpartial(self._adjust_play, 'stop'))
        slider.button_forw.clicked.connect(
            nonpartial(self._adjust_play, 'forw'))
        slider.button_next.clicked.connect(
            nonpartial(self._browse_slice, 'next'))
        slider.button_last.clicked.connect(
            nonpartial(self._browse_slice, 'last'))

        slider.checkbox_world.toggled.connect(
            nonpartial(self.set_label_from_slider))

        if world is None:
            self.use_world = False
            slider.checkbox_world.hide()
        else:
            self.use_world = not world_warning

        if world_unit:
            self.slider_unit = world_unit
        else:
            self.slider_unit = ''

        layout.addWidget(slider)

        self.setLayout(layout)

        self._ui_label = label
        self._ui_mode = mode
        self._update_mode()
        self._frozen = False

        self._play_speed = 0

        self.set_label_from_slider()

    def set_label_from_slider(self):
        value = self._ui_slider.slider.value()
        if self.use_world:
            text = str(self._world[value])
            if self._world_warning:
                self._ui_slider.label_warning.show()
            else:
                self._ui_slider.label_warning.hide()
            self.slider_unit = self._world_unit
        else:
            text = str(value)
            self._ui_slider.label_warning.hide()
            self.slider_unit = ''
        self._ui_slider.label.setText(text)

    def set_slider_from_label(self):
        text = self._ui_slider.label.text()
        if self.use_world:
            # Don't want to assume world is sorted, pick closest value
            value = np.argmin(np.abs(self._world - float(text)))
            self._ui_slider.label.setText(str(self._world[value]))
        else:
            value = int(text)
        self._ui_slider.slider.setValue(value)

    def _adjust_play(self, action):
        if action == 'stop':
            self._play_speed = 0
        elif action == 'back':
            if self._play_speed > 0:
                self._play_speed = -1
            else:
                self._play_speed -= 1
        elif action == 'forw':
            if self._play_speed < 0:
                self._play_speed = +1
            else:
                self._play_speed += 1
        if self._play_speed == 0:
            self._play_timer.stop()
        else:
            self._play_timer.start()
            self._play_timer.setInterval(500 / abs(self._play_speed))

    def _play_slice(self):
        if self._play_speed > 0:
            self._browse_slice('next', play=True)
        elif self._play_speed < 0:
            self._browse_slice('prev', play=True)

    def _browse_slice(self, action, play=False):

        imin = self._ui_slider.slider.minimum()
        imax = self._ui_slider.slider.maximum()
        value = self._ui_slider.slider.value()

        # If this was not called from _play_slice, we should stop the
        # animation.
        if not play:
            self._adjust_play('stop')

        if action == 'first':
            value = imin
        elif action == 'last':
            value = imax
        elif action == 'prev':
            value = value - 1
            if value < imin:
                value = imax
        elif action == 'next':
            value = value + 1
            if value > imax:
                value = imin
        else:
            raise ValueError("Action should be one of first/prev/next/last")

        self._ui_slider.slider.setValue(value)

    def _update_mode(self, *args):
        if self.mode != 'slice':
            self._ui_slider.hide()
            self._adjust_play('stop')
        else:
            self._ui_slider.show()

    def freeze(self):
        self.mode = 'slice'
        self._ui_mode.setEnabled(False)
        self._ui_slider.hide()
        self._frozen = True

    @property
    def frozen(self):
        return self._frozen
Пример #3
0
class Executor(QtCore.QObject, threading.Thread):
    """Executor represents a thread of control that runs a python function with
    a single input.  Once created with the proper inputs, threading.Thread has
    the following attributes:
        self.module - the loaded module object provided to __init__()
        self.args   - the argument to the target function.  Usually a dict.
        self.func_name - the function name that will be called.
        self.log_manager - the LogManager instance managing logs for this script
        self.failed - defaults to False.  Indicates whether the thread raised an
            exception while running.
        self.execption - defaults to None.  If not None, points to the exception
            raised while running the thread.
    The Executor.run() function is an overridden function from threading.Thread
    and is started in the same manner by calling Executor.start().  The run()
    function is extremely simple by design: Print the arguments to the logfile
    and run the specified function.  If an execption is raised, it is printed
    and saved locally for retrieval later on.
    In keeping with convention, a single Executor thread instance is only
    designed to be run once.  To run the same function again, it is best to
    create a new Executor instance and run that."""

    finished = QtCore.Signal()

    def __init__(self, target, args, kwargs, logfile, tempdir=None):
        QtCore.QObject.__init__(self)
        threading.Thread.__init__(self)
        self.target = target
        self.tempdir = tempdir

        if not args:
            args = ()
        self.args = args

        if not kwargs:
            kwargs = {}
        self.kwargs = kwargs

        if logfile is None:
            logfile = os.path.join(tempfile.mkdtemp(), 'logfile.txt')
        self.logfile = logfile

        self.failed = False
        self.exception = None
        self.traceback = None

    def run(self):
        """Run the python script provided by the user with the arguments
        specified.  This function also prints the arguments to the logfile
        handler.  If an exception is raised in either the loading or execution
        of the module or function, a traceback is printed and the exception is
        saved."""
        try:
            self.target(*self.args, **self.kwargs)
        except Exception as error:
            # We deliberately want to catch all possible exceptions.
            LOGGER.exception(error)
            self.failed = True
            self.exception = error
            self.traceback = traceback.format_exc()
        finally:
            LOGGER.info('Execution finished')

        self.finished.emit()
Пример #4
0
class FilterComboBox(QtWidgets.QToolButton):
    checkedItemsChanged = QtCore.Signal(list)

    def __init__(self, parent=None):
        super(FilterComboBox, self).__init__(parent)
        self.setText("(no filter)")
        # QtGui.QToolButton.InstantPopup would be slightly less work (the
        # whole button works by default, instead of only the arrow) but it is
        # uglier
        self.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)

        menu = FilterMenu(self)
        self.setMenu(menu)
        self._menu = menu
        menu.checkedItemsChanged.connect(self.on_checked_items_changed)
        self.installEventFilter(self)

    def on_checked_items_changed(self, indices_checked):
        num_checked = len(indices_checked)
        model = self._menu._model
        if num_checked == 0 or num_checked == len(model) - 1:
            self.setText("(no filter)")
        elif num_checked == 1:
            self.setText(model[indices_checked[0] + 1].text())
        else:
            self.setText("multi")
        self.checkedItemsChanged.emit(indices_checked)

    def addItem(self, text):
        self._menu.addItem(text)

    def addItems(self, items):
        self._menu.addItems(items)

    def eventFilter(self, obj, event):
        event_type = event.type()

        # this is not enabled because it causes all kind of troubles
        # if event_type == QtCore.QEvent.KeyPress:
        #     key = event.key()
        #
        #     # allow opening the popup via enter/return
        #     if (obj == self and
        #             key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter)):
        #         self.showMenu()
        #         return True

        if event_type == QtCore.QEvent.KeyRelease:
            key = event.key()

            # allow opening the popup with up/down
            if (obj == self and
                    key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
                            QtCore.Qt.Key_Space)):
                self.showMenu()
                return True

            # return key activates *one* item and closes the popup
            # first time the key is sent to self, afterwards to list_view
            elif (obj == self and
                    key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return)):
                self._menu.activate.emit(self._list_view.currentIndex().row())
                self._menu.hide()
                return True

        if event_type == QtCore.QEvent.MouseButtonRelease:
            # clicking anywhere (not just arrow) on the button shows the popup
            if obj == self:
                self.showMenu()

        return False
Пример #5
0
class JupyterWidget(IPythonWidget):
    """A FrontendWidget for a Jupyter kernel."""

    # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
    # an editor is needed for a file. This overrides 'editor' and 'editor_line'
    # settings.
    custom_edit = Bool(False)
    custom_edit_requested = QtCore.Signal(object, object)

    editor = Unicode(default_editor,
                     config=True,
                     help="""
        A command for invoking a GUI text editor. If the string contains a
        {filename} format specifier, it will be used. Otherwise, the filename
        will be appended to the end the command. To use a terminal text editor,
        the command should launch a new terminal, e.g.
        ``"gnome-terminal -- vim"``.
        """)

    editor_line = Unicode(config=True,
                          help="""
        The editor command to use when a specific line number is requested. The
        string should contain two format specifiers: {line} and {filename}. If
        this parameter is not specified, the line number option to the %edit
        magic will be ignored.
        """)

    style_sheet = Unicode(config=True,
                          help="""
        A CSS stylesheet. The stylesheet can contain classes for:
            1. Qt: QPlainTextEdit, QFrame, QWidget, etc
            2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
            3. QtConsole: .error, .in-prompt, .out-prompt, etc
        """)

    syntax_style = Unicode(config=True,
                           help="""
        If not empty, use this Pygments style for syntax highlighting.
        Otherwise, the style sheet is queried for Pygments style
        information.
        """)

    # Prompts.
    in_prompt = Unicode(default_in_prompt, config=True)
    out_prompt = Unicode(default_out_prompt, config=True)
    input_sep = Unicode(default_input_sep, config=True)
    output_sep = Unicode(default_output_sep, config=True)
    output_sep2 = Unicode(default_output_sep2, config=True)

    # JupyterWidget protected class variables.
    _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
    _payload_source_edit = 'edit_magic'
    _payload_source_exit = 'ask_exit'
    _payload_source_next_input = 'set_next_input'
    _payload_source_page = 'page'
    _retrying_history_request = False
    _starting = False

    #---------------------------------------------------------------------------
    # 'object' interface
    #---------------------------------------------------------------------------

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)

        # JupyterWidget protected variables.
        self._payload_handlers = {
            self._payload_source_edit: self._handle_payload_edit,
            self._payload_source_exit: self._handle_payload_exit,
            self._payload_source_page: self._handle_payload_page,
            self._payload_source_next_input: self._handle_payload_next_input
        }
        self._previous_prompt_obj = None
        self._keep_kernel_on_exit = None

        # Initialize widget styling.
        if self.style_sheet:
            self._style_sheet_changed()
            self._syntax_style_changed()
        else:
            self.set_default_style()

        # Initialize language name.
        self.language_name = None

    #---------------------------------------------------------------------------
    # 'BaseFrontendMixin' abstract interface
    #
    # For JupyterWidget,  override FrontendWidget methods which implement the
    # BaseFrontend Mixin abstract interface
    #---------------------------------------------------------------------------

    def _handle_complete_reply(self, rep):
        """Support Jupyter's improved completion machinery.
        """
        self.log.debug("complete: %s", rep.get('content', ''))
        cursor = self._get_cursor()
        info = self._request_info.get('complete')
        if (info and info.id == rep['parent_header']['msg_id']
                and info.pos == self._get_input_buffer_cursor_pos()
                and info.code == self.input_buffer):
            content = rep['content']
            matches = content['matches']
            start = content['cursor_start']
            end = content['cursor_end']

            start = max(start, 0)
            end = max(end, start)

            # Move the control's cursor to the desired end point
            cursor_pos = self._get_input_buffer_cursor_pos()
            if end < cursor_pos:
                cursor.movePosition(QtGui.QTextCursor.Left,
                                    n=(cursor_pos - end))
            elif end > cursor_pos:
                cursor.movePosition(QtGui.QTextCursor.Right,
                                    n=(end - cursor_pos))
            # This line actually applies the move to control's cursor
            self._control.setTextCursor(cursor)

            offset = end - start
            # Move the local cursor object to the start of the match and
            # complete.
            cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
            self._complete_with_items(cursor, matches)

    def _handle_execute_reply(self, msg):
        """Support prompt requests.
        """
        msg_id = msg['parent_header'].get('msg_id')
        info = self._request_info['execute'].get(msg_id)
        if info and info.kind == 'prompt':
            content = msg['content']
            if content['status'] == 'aborted':
                self._show_interpreter_prompt()
            else:
                number = content['execution_count'] + 1
                self._show_interpreter_prompt(number)
            self._request_info['execute'].pop(msg_id)
        else:
            super()._handle_execute_reply(msg)

    def _handle_history_reply(self, msg):
        """ Handle history tail replies, which are only supported
            by Jupyter kernels.
        """
        content = msg['content']
        if 'history' not in content:
            self.log.error("History request failed: %r" % content)
            if content.get('status', '') == 'aborted' and \
                                            not self._retrying_history_request:
                # a *different* action caused this request to be aborted, so
                # we should try again.
                self.log.error("Retrying aborted history request")
                # prevent multiple retries of aborted requests:
                self._retrying_history_request = True
                # wait out the kernel's queue flush, which is currently timed at 0.1s
                time.sleep(0.25)
                self.kernel_client.history(hist_access_type='tail', n=1000)
            else:
                self._retrying_history_request = False
            return
        # reset retry flag
        self._retrying_history_request = False
        history_items = content['history']
        self.log.debug("Received history reply with %i entries",
                       len(history_items))
        items = []
        last_cell = ""
        for _, _, cell in history_items:
            cell = cell.rstrip()
            if cell != last_cell:
                items.append(cell)
                last_cell = cell
        self._set_history(items)

    def _insert_other_input(self, cursor, content, remote=True):
        """Insert function for input from other frontends"""
        n = content.get('execution_count', 0)
        prompt = self._make_in_prompt(n, remote=remote)
        cont_prompt = self._make_continuation_prompt(self._prompt,
                                                     remote=remote)
        cursor.insertText('\n')
        for i, line in enumerate(content['code'].strip().split('\n')):
            if i == 0:
                self._insert_html(cursor, prompt)
            else:
                self._insert_html(cursor, cont_prompt)
            self._insert_plain_text(cursor, line + '\n')

        # Update current prompt number
        self._update_prompt(n + 1)

    def _handle_execute_input(self, msg):
        """Handle an execute_input message"""
        self.log.debug("execute_input: %s", msg.get('content', ''))
        if self.include_output(msg):
            self._append_custom(self._insert_other_input,
                                msg['content'],
                                before_prompt=True)
        elif not self._prompt:
            self._append_custom(self._insert_other_input,
                                msg['content'],
                                before_prompt=True,
                                remote=False)

    def _handle_execute_result(self, msg):
        """Handle an execute_result message"""
        self.log.debug("execute_result: %s", msg.get('content', ''))
        if self.include_output(msg):
            self.flush_clearoutput()
            content = msg['content']
            prompt_number = content.get('execution_count', 0)
            data = content['data']
            if 'text/plain' in data:
                self._append_plain_text(self.output_sep, before_prompt=True)
                self._append_html(self._make_out_prompt(
                    prompt_number, remote=not self.from_here(msg)),
                                  before_prompt=True)
                text = data['text/plain']
                # If the repr is multiline, make sure we start on a new line,
                # so that its lines are aligned.
                if "\n" in text and not self.output_sep.endswith("\n"):
                    self._append_plain_text('\n', before_prompt=True)
                self._append_plain_text(text + self.output_sep2,
                                        before_prompt=True)

                if not self.from_here(msg):
                    self._append_plain_text('\n', before_prompt=True)

    def _handle_display_data(self, msg):
        """The base handler for the ``display_data`` message."""
        # For now, we don't display data from other frontends, but we
        # eventually will as this allows all frontends to monitor the display
        # data. But we need to figure out how to handle this in the GUI.
        if self.include_output(msg):
            self.flush_clearoutput()
            data = msg['content']['data']
            metadata = msg['content']['metadata']
            # In the regular JupyterWidget, we simply print the plain text
            # representation.
            if 'text/plain' in data:
                text = data['text/plain']
                self._append_plain_text(text, True)
            # This newline seems to be needed for text and html output.
            self._append_plain_text('\n', True)

    def _handle_kernel_info_reply(self, rep):
        """Handle kernel info replies."""
        content = rep['content']
        self.language_name = content['language_info']['name']
        pygments_lexer = content['language_info'].get('pygments_lexer', '')

        try:
            # Other kernels with pygments_lexer info will have to be
            # added here by hand.
            if pygments_lexer == 'ipython3':
                lexer = IPython3Lexer()
            elif pygments_lexer == 'ipython2':
                lexer = IPythonLexer()
            else:
                lexer = get_lexer_by_name(self.language_name)
            self._highlighter._lexer = lexer
        except ClassNotFound:
            pass

        self.kernel_banner = content.get('banner', '')
        if self._starting:
            # finish handling started channels
            self._starting = False
            super()._started_channels()

    def _started_channels(self):
        """Make a history request"""
        self._starting = True
        self.kernel_client.kernel_info()
        self.kernel_client.history(hist_access_type='tail', n=1000)

    #---------------------------------------------------------------------------
    # 'FrontendWidget' protected interface
    #---------------------------------------------------------------------------

    def _process_execute_error(self, msg):
        """Handle an execute_error message"""
        self.log.debug("execute_error: %s", msg.get('content', ''))

        content = msg['content']

        traceback = '\n'.join(content['traceback']) + '\n'
        if False:
            # FIXME: For now, tracebacks come as plain text, so we can't
            # use the html renderer yet.  Once we refactor ultratb to
            # produce properly styled tracebacks, this branch should be the
            # default
            traceback = traceback.replace(' ', '&nbsp;')
            traceback = traceback.replace('\n', '<br/>')

            ename = content['ename']
            ename_styled = '<span class="error">%s</span>' % ename
            traceback = traceback.replace(ename, ename_styled)

            self._append_html(traceback)
        else:
            # This is the fallback for now, using plain text with ansi
            # escapes
            self._append_plain_text(traceback,
                                    before_prompt=not self.from_here(msg))

    def _process_execute_payload(self, item):
        """ Reimplemented to dispatch payloads to handler methods.
        """
        handler = self._payload_handlers.get(item['source'])
        if handler is None:
            # We have no handler for this type of payload, simply ignore it
            return False
        else:
            handler(item)
            return True

    def _show_interpreter_prompt(self, number=None):
        """ Reimplemented for IPython-style prompts.
        """
        # If a number was not specified, make a prompt number request.
        if number is None:
            msg_id = self.kernel_client.execute('', silent=True)
            info = self._ExecutionRequest(msg_id, 'prompt')
            self._request_info['execute'][msg_id] = info
            return

        # Show a new prompt and save information about it so that it can be
        # updated later if the prompt number turns out to be wrong.
        self._prompt_sep = self.input_sep
        self._show_prompt(self._make_in_prompt(number), html=True)
        block = self._control.document().lastBlock()
        length = len(self._prompt)
        self._previous_prompt_obj = self._PromptBlock(block, length, number)

        # Update continuation prompt to reflect (possibly) new prompt length.
        self._set_continuation_prompt(self._make_continuation_prompt(
            self._prompt),
                                      html=True)

    def _update_prompt(self, new_prompt_number):
        """Replace the last displayed prompt with a new one."""
        if self._previous_prompt_obj is None:
            return

        block = self._previous_prompt_obj.block

        # Make sure the prompt block has not been erased.
        if block.isValid() and block.text():

            # Remove the old prompt and insert a new prompt.
            cursor = QtGui.QTextCursor(block)
            cursor.movePosition(QtGui.QTextCursor.Right,
                                QtGui.QTextCursor.KeepAnchor,
                                self._previous_prompt_obj.length)
            prompt = self._make_in_prompt(new_prompt_number)
            self._prompt = self._insert_html_fetching_plain_text(
                cursor, prompt)

            # When the HTML is inserted, Qt blows away the syntax
            # highlighting for the line, so we need to rehighlight it.
            self._highlighter.rehighlightBlock(cursor.block())

            # Update the prompt cursor
            self._prompt_cursor.setPosition(cursor.position() - 1)

            # Store the updated prompt.
            block = self._control.document().lastBlock()
            length = len(self._prompt)
            self._previous_prompt_obj = self._PromptBlock(
                block, length, new_prompt_number)

    def _show_interpreter_prompt_for_reply(self, msg):
        """ Reimplemented for IPython-style prompts.
        """
        # Update the old prompt number if necessary.
        content = msg['content']
        # abort replies do not have any keys:
        if content['status'] == 'aborted':
            if self._previous_prompt_obj:
                previous_prompt_number = self._previous_prompt_obj.number
            else:
                previous_prompt_number = 0
        else:
            previous_prompt_number = content['execution_count']
        if self._previous_prompt_obj and \
                self._previous_prompt_obj.number != previous_prompt_number:
            self._update_prompt(previous_prompt_number)
            self._previous_prompt_obj = None

        # Show a new prompt with the kernel's estimated prompt number.
        self._show_interpreter_prompt(previous_prompt_number + 1)

    #---------------------------------------------------------------------------
    # 'JupyterWidget' interface
    #---------------------------------------------------------------------------

    def set_default_style(self, colors='lightbg'):
        """ Sets the widget style to the class defaults.

        Parameters
        ----------
        colors : str, optional (default lightbg)
            Whether to use the default light background or dark
            background or B&W style.
        """
        colors = colors.lower()
        if colors == 'lightbg':
            self.style_sheet = styles.default_light_style_sheet
            self.syntax_style = styles.default_light_syntax_style
        elif colors == 'linux':
            self.style_sheet = styles.default_dark_style_sheet
            self.syntax_style = styles.default_dark_syntax_style
        elif colors == 'nocolor':
            self.style_sheet = styles.default_bw_style_sheet
            self.syntax_style = styles.default_bw_syntax_style
        else:
            raise KeyError("No such color scheme: %s" % colors)

    #---------------------------------------------------------------------------
    # 'JupyterWidget' protected interface
    #---------------------------------------------------------------------------

    def _edit(self, filename, line=None):
        """ Opens a Python script for editing.

        Parameters
        ----------
        filename : str
            A path to a local system file.

        line : int, optional
            A line of interest in the file.
        """
        if self.custom_edit:
            self.custom_edit_requested.emit(filename, line)
        elif not self.editor:
            self._append_plain_text(
                'No default editor available.\n'
                'Specify a GUI text editor in the `JupyterWidget.editor` '
                'configurable to enable the %edit magic')
        else:
            try:
                filename = '"%s"' % filename
                if line and self.editor_line:
                    command = self.editor_line.format(filename=filename,
                                                      line=line)
                else:
                    try:
                        command = self.editor.format()
                    except KeyError:
                        command = self.editor.format(filename=filename)
                    else:
                        command += ' ' + filename
            except KeyError:
                self._append_plain_text('Invalid editor command.\n')
            else:
                try:
                    Popen(command, shell=True)
                except OSError:
                    msg = 'Opening editor with command "%s" failed.\n'
                    self._append_plain_text(msg % command)

    def _make_in_prompt(self, number, remote=False):
        """ Given a prompt number, returns an HTML In prompt.
        """
        try:
            body = self.in_prompt % number
        except TypeError:
            # allow in_prompt to leave out number, e.g. '>>> '
            from xml.sax.saxutils import escape
            body = escape(self.in_prompt)
        if remote:
            body = self.other_output_prefix + body
        return '<span class="in-prompt">%s</span>' % body

    def _make_continuation_prompt(self, prompt, remote=False):
        """ Given a plain text version of an In prompt, returns an HTML
            continuation prompt.
        """
        end_chars = '...: '
        space_count = len(prompt.lstrip('\n')) - len(end_chars)
        if remote:
            space_count += len(self.other_output_prefix.rsplit('\n')[-1])
        body = '&nbsp;' * space_count + end_chars
        return '<span class="in-prompt">%s</span>' % body

    def _make_out_prompt(self, number, remote=False):
        """ Given a prompt number, returns an HTML Out prompt.
        """
        try:
            body = self.out_prompt % number
        except TypeError:
            # allow out_prompt to leave out number, e.g. '<<< '
            from xml.sax.saxutils import escape
            body = escape(self.out_prompt)
        if remote:
            body = self.other_output_prefix + body
        return '<span class="out-prompt">%s</span>' % body

    #------ Payload handlers --------------------------------------------------

    # Payload handlers with a generic interface: each takes the opaque payload
    # dict, unpacks it and calls the underlying functions with the necessary
    # arguments.

    def _handle_payload_edit(self, item):
        self._edit(item['filename'], item['line_number'])

    def _handle_payload_exit(self, item):
        self._keep_kernel_on_exit = item['keepkernel']
        self.exit_requested.emit(self)

    def _handle_payload_next_input(self, item):
        self.input_buffer = item['text']

    def _handle_payload_page(self, item):
        # Since the plain text widget supports only a very small subset of HTML
        # and we have no control over the HTML source, we only page HTML
        # payloads in the rich text widget.
        data = item['data']
        if 'text/html' in data and self.kind == 'rich':
            self._page(data['text/html'], html=True)
        else:
            self._page(data['text/plain'], html=False)

    #------ Trait change handlers --------------------------------------------

    @observe('style_sheet')
    def _style_sheet_changed(self, changed=None):
        """ Set the style sheets of the underlying widgets.
        """
        self.setStyleSheet(self.style_sheet)
        if self._control is not None:
            self._control.document().setDefaultStyleSheet(self.style_sheet)

        if self._page_control is not None:
            self._page_control.document().setDefaultStyleSheet(
                self.style_sheet)

    @observe('syntax_style')
    def _syntax_style_changed(self, changed=None):
        """ Set the style for the syntax highlighter.
        """
        if self._highlighter is None:
            # ignore premature calls
            return
        if self.syntax_style:
            self._highlighter.set_style(self.syntax_style)
            self._ansi_processor.set_background_color(self.syntax_style)
        else:
            self._highlighter.set_style_sheet(self.style_sheet)

    #------ Trait default initializers -----------------------------------------

    @default('banner')
    def _banner_default(self):
        return "Jupyter QtConsole {version}\n".format(version=__version__)
Пример #6
0
class LaserGUI(GUIBase):
    """ FIXME: Please document
    """
    _modclass = 'lasergui'
    _modtype = 'gui'

    ## declare connectors
    _in = {'laserlogic': 'LaserLogic'}

    sigLaser = QtCore.Signal(bool)
    sigShutter = QtCore.Signal(bool)
    sigPower = QtCore.Signal(float)
    sigCurrent = QtCore.Signal(float)
    sigCtrlMode = QtCore.Signal(ControlMode)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self, e=None):
        """ Definition and initialisation of the GUI plus staring the measurement.

        @param object e: Fysom.event object from Fysom class.
                         An object created by the state machine module Fysom,
                         which is connected to a specific event (have a look in
                         the Base Class). This object contains the passed event,
                         the state before the event happened and the destination
                         of the state which should be reached after the event
                         had happened.
        """
        self._laser_logic = self.get_in_connector('laserlogic')

        #####################
        # Configuring the dock widgets
        # Use the inherited class 'CounterMainWindow' to create the GUI window
        self._mw = LaserWindow()

        # Setup dock widgets
        self._mw.setDockNestingEnabled(True)
        self._mw.actionReset_View.triggered.connect(self.restoreDefaultView)

        # set up plot
        pw = self._mw.graphicsView
        plot1 = pw.plotItem
        plot1.setLabel('left',
                       'Some Value',
                       units='some unit',
                       color='#00ff00')
        plot1.setLabel('bottom', 'Number of values', units='some unit')
        self.plots = {}
        colorlist = (palette.c1, palette.c2, palette.c3, palette.c4,
                     palette.c5, palette.c6)
        i = 0
        for k in self._laser_logic.data:
            if k != 'time':
                self.plots[k] = plot1.plot()
                self.plots[k].setPen(colorlist[(2 * i) % len(colorlist)])
                i += 1

        self.updateButtonsEnabled()

        self._mw.laserButton.clicked.connect(self.changeLaserState)
        self._mw.shutterButton.clicked.connect(self.changeShutterState)
        self.sigLaser.connect(self._laser_logic.set_laser_state)
        self.sigShutter.connect(self._laser_logic.set_shutter_state)
        self.sigCurrent.connect(self._laser_logic.set_current)
        self.sigPower.connect(self._laser_logic.set_power)
        self._mw.controlModeButtonGroup.buttonClicked.connect(
            self.changeControlMode)
        self.sliderProxy = pg.SignalProxy(
            self._mw.setValueVerticalSlider.valueChanged, 0.1, 5,
            self.updateFromSlider)
        self._mw.setValueDoubleSpinBox.editingFinished.connect(
            self.updateFromSpinBox)
        self._laser_logic.sigUpdate.connect(self.updateGui)

    def on_deactivate(self, e):
        """ Deactivate the module properly.

        @param object e: Fysom.event object from Fysom class. A more detailed
                         explanation can be found in the method initUI.
        """
        self._mw.close()

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    def restoreDefaultView(self):
        """ Restore the arrangement of DockWidgets to the default
        """
        # Show any hidden dock widgets
        self._mw.adjustDockWidget.show()
        self._mw.plotDockWidget.show()

        # re-dock any floating dock widgets
        self._mw.adjustDockWidget.setFloating(False)
        self._mw.plotDockWidget.setFloating(False)

        # Arrange docks widgets
        self._mw.addDockWidget(QtCore.Qt.DockWidgetArea(1),
                               self._mw.adjustDockWidget)
        self._mw.addDockWidget(QtCore.Qt.DockWidgetArea(2),
                               self._mw.plotDockWidget)

    def changeLaserState(self, on):
        """ """
        self._mw.laserButton.setEnabled(False)
        self.sigLaser.emit(on)

    def changeShutterState(self, on):
        """ """
        self._mw.shutterButton.setEnabled(False)
        self.sigShutter.emit(on)

    @QtCore.Slot(int)
    def changeControlMode(self, buttonId):
        """ """
        cur = self._mw.currentRadioButton.isChecked(
        ) and self._mw.currentRadioButton.isEnabled()
        pwr = self._mw.powerRadioButton.isChecked(
        ) and self._mw.powerRadioButton.isEnabled()
        if pwr and not cur:
            lpr = self._laser_logic.laser_power_range
            self._mw.setValueDoubleSpinBox.setRange(lpr[0], lpr[1])
            self._mw.setValueDoubleSpinBox.setValue(
                self._laser_logic._laser.get_power_setpoint())
            self._mw.setValueVerticalSlider.setValue(
                self._laser_logic._laser.get_power_setpoint() /
                (lpr[1] - lpr[0]) * 100 - lpr[0])
            self.sigCtrlMode.emit(ControlMode.POWER)
        elif cur and not pwr:
            lcr = self._laser_logic.laser_current_range
            self._mw.setValueDoubleSpinBox.setRange(lcr[0], lcr[1])
            self._mw.setValueDoubleSpinBox.setValue(
                self._laser_logic._laser.get_current_setpoint())
            self._mw.setValueVerticalSlider.setValue(
                self._laser_logic._laser.get_current_setpoint() /
                (lcr[1] - lcr[0]) * 100 - lcr[0])
            self.sigCtrlMode.emit(ControlMode.CURRENT)
        else:
            self.log.error('Nope.')

    @QtCore.Slot()
    def updateButtonsEnabled(self):
        """ """
        self._mw.laserButton.setEnabled(self._laser_logic.laser_can_turn_on)
        if self._laser_logic.laser_state == LaserState.ON:
            self._mw.laserButton.setText('Laser: ON')
            self._mw.laserButton.setStyleSheet('')
        elif self._laser_logic.laser_state == LaserState.OFF:
            self._mw.laserButton.setText('Laser: OFF')
        elif self._laser_logic.laser_state == LaserState.LOCKED:
            self._mw.laserButton.setText('INTERLOCK')
        else:
            self._mw.laserButton.setText('Laser: ?')

        self._mw.shutterButton.setEnabled(self._laser_logic.has_shutter)
        if self._laser_logic.laser_shutter == ShutterState.OPEN:
            self._mw.shutterButton.setText('Shutter: OPEN')
        elif self._laser_logic.laser_shutter == ShutterState.CLOSED:
            self._mw.shutterButton.setText('Shutter: CLOSED')
        elif self._laser_logic.laser_shutter == ShutterState.NOSHUTTER:
            self._mw.shutterButton.setText('No shutter.')
        else:
            self._mw.laserButton.setText('Shutter: ?')

        self._mw.currentRadioButton.setEnabled(
            self._laser_logic.laser_can_current)
        self._mw.powerRadioButton.setEnabled(self._laser_logic.laser_can_power)

    @QtCore.Slot()
    def updateGui(self):
        """ """
        self._mw.currentLabel.setText('{0:6.3f} {1}'.format(
            self._laser_logic.laser_current,
            self._laser_logic.laser_current_unit))
        self._mw.powerLabel.setText('{0:6.3f} W'.format(
            self._laser_logic.laser_power))
        self._mw.extraLabel.setText(self._laser_logic.laser_extra)
        self.updateButtonsEnabled()
        for k in self.plots:
            self.plots[k].setData(x=self._laser_logic.data['time'],
                                  y=self._laser_logic.data[k])

    @QtCore.Slot()
    def updateFromSpinBox(self):
        """ """
        self._mw.setValueVerticalSlider.setValue(
            self._mw.setValueDoubleSpinBox.value())
        cur = self._mw.currentRadioButton.isChecked(
        ) and self._mw.currentRadioButton.isEnabled()
        pwr = self._mw.powerRadioButton.isChecked(
        ) and self._mw.powerRadioButton.isEnabled()
        if pwr and not cur:
            self.sigPower.emit(self._mw.setValueDoubleSpinBox.value())
        elif cur and not pwr:
            self.sigCurrent.emit(self._mw.setValueDoubleSpinBox.value())

    @QtCore.Slot()
    def updateFromSlider(self):
        """ """
        cur = self._mw.currentRadioButton.isChecked(
        ) and self._mw.currentRadioButton.isEnabled()
        pwr = self._mw.powerRadioButton.isChecked(
        ) and self._mw.powerRadioButton.isEnabled()
        if pwr and not cur:
            lpr = self._laser_logic.laser_power_range
            self._mw.setValueDoubleSpinBox.setValue(
                lpr[0] + self._mw.setValueVerticalSlider.value() / 100 *
                (lpr[1] - lpr[0]))
            self.sigPower.emit(lpr[0] +
                               self._mw.setValueVerticalSlider.value() / 100 *
                               (lpr[1] - lpr[0]))
        elif cur and not pwr:
            self._mw.setValueDoubleSpinBox.setValue(
                self._mw.setValueVerticalSlider.value())
            self.sigCurrent.emit(self._mw.setValueDoubleSpinBox.value())
Пример #7
0
class PIDLogic(GenericLogic):
    """
    Control a process via software PID.
    """
    _modclass = 'pidlogic'
    _modtype = 'logic'
    ## declare connectors
    _connectors = {
        'controller': 'PIDControllerInterface',
        'savelogic': 'SaveLogic'
    }

    sigUpdateDisplay = QtCore.Signal()

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self.log.info('The following configuration was found.')

        # checking for the right configuration
        for key in config.keys():
            self.log.info('{0}: {1}'.format(key, config[key]))

        #number of lines in the matrix plot
        self.NumberOfSecondsLog = 100
        self.threadlock = Mutex()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._controller = self.get_connector('controller')
        self._save_logic = self.get_connector('savelogic')

        config = self.getConfiguration()

        # load parameters stored in app state store
        if 'bufferlength' in self._statusVariables:
            self.bufferLength = self._statusVariables['bufferlength']
        else:
            self.bufferLength = 1000

        if 'timestep' in self._statusVariables:
            self.timestep = self._statusVariables['timestep']
        else:
            self.timestep = 100

        self.history = np.zeros([3, self.bufferLength])
        self.savingState = False
        self.enabled = False
        self.timer = QtCore.QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(self.timestep)
        self.timer.timeout.connect(self.loop)

    def on_deactivate(self):
        """ Perform required deactivation. """

        # save parameters stored in ap state store
        self._statusVariables['bufferlength'] = self.bufferLength
        self._statusVariables['timestep'] = self.timestep

    def getBufferLength(self):
        """ Get the current data buffer length.
        """
        return self.bufferLength

    def startLoop(self):
        """ Start the data recording loop.
        """
        self.enabled = True
        self.timer.start(self.timestep)

    def stopLoop(self):
        """ Stop the data recording loop.
        """
        self.enabled = False

    def loop(self):
        """ Execute step in the data recording loop: save one of each control and process values
        """
        self.history = np.roll(self.history, -1, axis=1)
        self.history[0, -1] = self._controller.get_process_value()
        self.history[1, -1] = self._controller.get_control_value()
        self.history[2, -1] = self._controller.get_setpoint()
        self.sigUpdateDisplay.emit()
        if self.enabled:
            self.timer.start(self.timestep)

    def getSavingState(self):
        """ Return whether we are saving data

            @return bool: whether we are saving data right now
        """
        return self.savingState

    def startSaving(self):
        """ Start saving data.

            Function does nothing right now.
        """
        pass

    def saveData(self):
        """ Stop saving data and write data to file.

            Function does nothing right now.
        """
        pass

    def setBufferLength(self, newBufferLength):
        """ Change buffer length to new value.

            @param int newBufferLength: new buffer length
        """
        self.bufferLength = newBufferLength
        self.history = np.zeros([3, self.bufferLength])

    def get_kp(self):
        """ Return the proportional constant.

            @return float: proportional constant of PID controller
        """
        return self._controller.get_kp()

    def set_kp(self, kp):
        """ Set the proportional constant of the PID controller.

            @prarm float kp: proportional constant of PID controller
        """
        return self._controller.set_kp(kp)

    def get_ki(self):
        """ Get the integration constant of the PID controller

            @return float: integration constant of the PID controller
        """
        return self._controller.get_ki()

    def set_ki(self, ki):
        """ Set the integration constant of the PID controller.

            @param float ki: integration constant of the PID controller
        """
        return self._controller.set_ki(ki)

    def get_kd(self):
        """ Get the derivative constant of the PID controller

            @return float: the derivative constant of the PID controller
        """
        return self._controller.get_kd()

    def set_kd(self, kd):
        """ Set the derivative constant of the PID controller

            @param float kd: the derivative constant of the PID controller
        """
        return self._controller.set_kd(kd)

    def get_setpoint(self):
        """ Get the current setpoint of the PID controller.

            @return float: current set point of the PID controller
        """
        return self.history[2, -1]

    def set_setpoint(self, setpoint):
        """ Set the current setpoint of the PID controller.

            @param float setpoint: new set point of the PID controller
        """
        self._controller.set_setpoint(setpoint)

    def get_manual_value(self):
        """ Return the control value for manual mode.

            @return float: control value for manual mode
        """
        return self._controller.get_manual_value()

    def set_manual_value(self, manualvalue):
        """ Set the control value for manual mode.

            @param float manualvalue: control value for manual mode of controller
        """
        return self._controller.set_manual_value(manualvalue)

    def get_enabled(self):
        """ See if the PID controller is controlling a process.

            @return bool: whether the PID controller is preparing to or conreolling a process
        """
        return self.enabled

    def set_enabled(self, enabled):
        """ Set the state of the PID controller.

            @param bool enabled: desired state of PID controller
        """
        if enabled and not self.enabled:
            self.startLoop()
        if not enabled and self.enabled:
            self.stopLoop()

    def get_control_limits(self):
        """ Get the minimum and maximum value of the control actuator.

            @return list(float): (minimum, maximum) values of the control actuator
        """
        return self._controller.get_control_limits()

    def set_control_limits(self, limits):
        """ Set the minimum and maximum value of the control actuator.

            @param list(float) limits: (minimum, maximum) values of the control actuator

            This function does nothing, control limits are handled by the control module
        """
        return self._controller.set_control_limits(limits)

    def get_pv(self):
        """ Get current process input value.

            @return float: current process input value
        """
        return self.history[0, -1]

    def get_cv(self):
        """ Get current control output value.

            @return float: control output value
        """
        return self.history[1, -1]
Пример #8
0
class PropertyEditor( QtWidgets.QFrame ):
	propertyChanged = QtCore.Signal( object, str, object )
	objectChanged   = QtCore.Signal( object )
	contextMenuRequested = QtCore.Signal( object, str )
	
	_fieldEditorCacheWidget = None
	_fieldEditorCache = {}

	def __init__( self, parent ):	
		super( PropertyEditor, self ).__init__( parent )
		if not PropertyEditor._fieldEditorCacheWidget:
			PropertyEditor._fieldEditorCacheWidget = QtWidgets.QWidget()
		layout = QtWidgets.QFormLayout( )
		self.setLayout( layout )
		self._layout = layout
		self._layout.setHorizontalSpacing( 4 )
		self._layout.setVerticalSpacing( 2 )
		self._layout.setContentsMargins( 4 , 4 , 4 , 4 )
		self._layout.setLabelAlignment( Qt.AlignLeft )
		self._layout.setFieldGrowthPolicy( QtWidgets.QFormLayout.ExpandingFieldsGrow )
		self.setSizePolicy( 
			QtWidgets.QSizePolicy.Expanding,
			QtWidgets.QSizePolicy.Expanding
		)
		self.editors    = {}
		self.target     = None
		self.refreshing = False
		self.context    = None
		self.model      = False
		self.readonly   = False
		self.clear()
		
	def addFieldEditor( self, field ):
		editor = buildFieldEditor( self, field )
		if not editor:
			logging.info( 'no field editor for:' + str(field.label) )
			return None
		label = field.label
		labelWidget  = editor.initLabel( label, self )
		editorWidget = editor.initEditor( self )
		if labelWidget in (None, False):
			self._layout.addRow ( editorWidget )
		else:
			labelWidget.setObjectName( 'FieldLabel' )
			self._layout.addRow ( labelWidget, editorWidget )

		if editorWidget:
			editorWidget.setObjectName( 'FieldEditor' )

		self.editors[ field ] = editor
		editor.postInit( self )
		return editor

	def getFieldEditor( self, field ):
		return self.editors.get( field, None )

	def addSeparator( self ):
		line = QtWidgets.QFrame( self )
		line.setSizePolicy(
			QtWidgets.QSizePolicy.Expanding,
			QtWidgets.QSizePolicy.Fixed
		)
		# line.setStyleSheet('background:none; border:none; ')
		line.setStyleSheet('background:none; border-top:1px solid #353535; margin: 2px 0 2px 0;')
		line.setMinimumSize( 30, 7  )
		self._layout.addRow( line )

	def clear( self ):
		for editor in self.editors.values():
			editor.clear()
			
		layout = self._layout
		while layout.count() > 0:
			child = layout.takeAt( 0 )
			if child :
				w = child.widget()
				if w:
					w.setParent( None )
			else:
				break
		self.editors.clear()
		self.target  = None

	def setContext( self, context ):
		self.context = context

	def onObjectChanged( self ):
		if self.refreshing: return
		self.objectChanged.emit( self.target )
		return self.refreshAll()

	def onPropertyChanged( self, field, value ):
		if self.refreshing : return
		self.model.setFieldValue( self.target, field.id, value )
		self.propertyChanged.emit( self.target, field.id, value )
		self.objectChanged.emit( self.target )
		
	def onContextMenuRequested( self, field ):
		self.contextMenuRequested.emit( self.target, field.id )

	def setReadonly( self, readonly = True ):
		self.readonly = readonly
		self.refreshAll()

	def isReadonly( self ):
		return self.readonly

	def getTarget( self ):
		return self.target
		
	def setTarget( self, target, **kwargs ):
		oldtarget = self.target
		self.hide()
		model = kwargs.get( 'model', None )
		if not model: model = ModelManager.get().getModel(target)
		if not model:
			self.model = None
			self.clear()
			return

		rebuildFields = model != self.model
		assert(model)
		wasSeparator = False
		if rebuildFields:
			self.clear()
			
			self.refreshing = True
			#install field info
			currentId = None
			for field in model.fieldList:
				currentId = field.id
				if field.getOption('no_edit'):
					if field.id == '----' and not wasSeparator:
						wasSeparator = True
						self.addSeparator()
					continue
				lastId = currentId
				self.addFieldEditor( field )			
				wasSeparator = False
			assert self.refreshing
			self.refreshing = False
			
		self.model  = model
		self.target = target
		self.refreshAll()
		self.show()

	def refreshFor( self, target ):
		if target == self.target:
			return self.refreshAll()
			
	def refreshAll( self ):
		target=self.target
		if not target: return
		for field in self.model.fieldList: #todo: just use propMap to iter?
			self._refreshField( field )

	def refreshField( self, fieldId ):
		for field in self.model.fieldList: #todo: just use propMap to iter?
			if field.id == fieldId:
				self._refreshField( field )
				return True
		return False

	def _refreshField( self, field ):
		target = self.target
		if not target: return
		editor = self.editors.get( field, None )
		if editor:		
			v = self.model.getFieldValue( target, field.id )
			self.refreshing = True #avoid duplicated update
			editor.refreshing = True
			editor.refreshState()
			editor.set( v )
			editor.refreshing = False
			self.refreshing = False
			editor.setOverrided( self.model.isFieldOverrided( target, field.id ) )

	def refershFieldState( self, fieldId ):
		target = self.target
		if not target: return
		for field in self.model.fieldList: #todo: just use propMap to iter?
			if field.id == fieldId:
				editor = self.editors.get( field, None )
				if not editor: return
				editor.setOverrided( self.model.isFieldOverrided( target, field.id ) )
Пример #9
0
class PulsedMasterLogic(GenericLogic):
    """
    This logic module combines the functionality of two modules.

    It can be used to generate pulse sequences/waveforms and to control the settings for the pulse
    generator via SequenceGeneratorLogic. Essentially this part controls what is played on the
    pulse generator.
    Furthermore it can be used to set up a pulsed measurement with an already set-up pulse generator
    together with a fast counting device via PulsedMeasurementLogic.

    The main purpose for this module is to provide a single interface while maintaining a modular
    structure for complex pulsed measurements. Each of the sub-modules can be used without this
    module but more care has to be taken in that case.
    Automatic transfer of information from one sub-module to the other for convenience is also
    handled here.
    Another important aspect is the use of this module in scripts (e.g. jupyter notebooks).
    All calls to sub-module setter functions (PulsedMeasurementLogic and SequenceGeneratorLogic)
    are decoupled from the calling thread via Qt queued connections.
    This ensures a more intuitive and less error prone use of scripting.
    """
    _modclass = 'pulsedmasterlogic'
    _modtype = 'logic'

    # declare connectors
    pulsedmeasurementlogic = Connector(interface='PulsedMeasurementLogic')
    sequencegeneratorlogic = Connector(interface='SequenceGeneratorLogic')

    # PulsedMeasurementLogic control signals
    sigDoFit = QtCore.Signal(str)
    sigToggleMeasurement = QtCore.Signal(bool, str)
    sigToggleMeasurementPause = QtCore.Signal(bool)
    sigTogglePulser = QtCore.Signal(bool)
    sigToggleExtMicrowave = QtCore.Signal(bool)
    sigFastCounterSettingsChanged = QtCore.Signal(dict)
    sigMeasurementSettingsChanged = QtCore.Signal(dict)
    sigExtMicrowaveSettingsChanged = QtCore.Signal(dict)
    sigAnalysisSettingsChanged = QtCore.Signal(dict)
    sigExtractionSettingsChanged = QtCore.Signal(dict)
    sigTimerIntervalChanged = QtCore.Signal(float)
    sigAlternativeDataTypeChanged = QtCore.Signal(str)
    sigManuallyPullData = QtCore.Signal()

    # signals for master module (i.e. GUI) coming from PulsedMeasurementLogic
    sigMeasurementDataUpdated = QtCore.Signal()
    sigTimerUpdated = QtCore.Signal(float, int, float)
    sigFitUpdated = QtCore.Signal(str, np.ndarray, object)
    sigMeasurementStatusUpdated = QtCore.Signal(bool, bool)
    sigPulserRunningUpdated = QtCore.Signal(bool)
    sigExtMicrowaveRunningUpdated = QtCore.Signal(bool)
    sigExtMicrowaveSettingsUpdated = QtCore.Signal(dict)
    sigFastCounterSettingsUpdated = QtCore.Signal(dict)
    sigMeasurementSettingsUpdated = QtCore.Signal(dict)
    sigAnalysisSettingsUpdated = QtCore.Signal(dict)
    sigExtractionSettingsUpdated = QtCore.Signal(dict)

    # SequenceGeneratorLogic control signals
    sigSavePulseBlock = QtCore.Signal(object)
    sigSaveBlockEnsemble = QtCore.Signal(object)
    sigSaveSequence = QtCore.Signal(object)
    sigDeletePulseBlock = QtCore.Signal(str)
    sigDeleteBlockEnsemble = QtCore.Signal(str)
    sigDeleteSequence = QtCore.Signal(str)
    sigLoadBlockEnsemble = QtCore.Signal(str)
    sigLoadSequence = QtCore.Signal(str)
    sigSampleBlockEnsemble = QtCore.Signal(str)
    sigSampleSequence = QtCore.Signal(str)
    sigClearPulseGenerator = QtCore.Signal()
    sigGeneratorSettingsChanged = QtCore.Signal(dict)
    sigSamplingSettingsChanged = QtCore.Signal(dict)
    sigGeneratePredefinedSequence = QtCore.Signal(str, dict)

    # signals for master module (i.e. GUI) coming from SequenceGeneratorLogic
    sigBlockDictUpdated = QtCore.Signal(dict)
    sigEnsembleDictUpdated = QtCore.Signal(dict)
    sigSequenceDictUpdated = QtCore.Signal(dict)
    sigAvailableWaveformsUpdated = QtCore.Signal(list)
    sigAvailableSequencesUpdated = QtCore.Signal(list)
    sigSampleEnsembleComplete = QtCore.Signal(object)
    sigSampleSequenceComplete = QtCore.Signal(object)
    sigLoadedAssetUpdated = QtCore.Signal(str, str)
    sigGeneratorSettingsUpdated = QtCore.Signal(dict)
    sigSamplingSettingsUpdated = QtCore.Signal(dict)
    sigPredefinedSequenceGenerated = QtCore.Signal(object, bool)

    def __init__(self, config, **kwargs):
        """ Create PulsedMasterLogic object with connectors.

          @param dict kwargs: optional parameters
        """
        super().__init__(config=config, **kwargs)

        # Dictionary servings as status register
        self.status_dict = dict()
        return

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """

        # Initialize status register
        self.status_dict = {
            'sampling_ensemble_busy': False,
            'sampling_sequence_busy': False,
            'sampload_busy': False,
            'loading_busy': False,
            'pulser_running': False,
            'measurement_running': False,
            'microwave_running': False,
            'predefined_generation_busy': False,
            'fitting_busy': False
        }

        # Connect signals controlling PulsedMeasurementLogic
        self.sigDoFit.connect(self.pulsedmeasurementlogic().do_fit,
                              QtCore.Qt.QueuedConnection)
        self.sigToggleMeasurement.connect(
            self.pulsedmeasurementlogic().toggle_pulsed_measurement,
            QtCore.Qt.QueuedConnection)
        self.sigToggleMeasurementPause.connect(
            self.pulsedmeasurementlogic().toggle_measurement_pause,
            QtCore.Qt.QueuedConnection)
        self.sigTogglePulser.connect(
            self.pulsedmeasurementlogic().toggle_pulse_generator,
            QtCore.Qt.QueuedConnection)
        self.sigToggleExtMicrowave.connect(
            self.pulsedmeasurementlogic().toggle_microwave,
            QtCore.Qt.QueuedConnection)
        self.sigFastCounterSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_fast_counter_settings,
            QtCore.Qt.QueuedConnection)
        self.sigMeasurementSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_measurement_settings,
            QtCore.Qt.QueuedConnection)
        self.sigExtMicrowaveSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_microwave_settings,
            QtCore.Qt.QueuedConnection)
        self.sigAnalysisSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_analysis_settings,
            QtCore.Qt.QueuedConnection)
        self.sigExtractionSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_extraction_settings,
            QtCore.Qt.QueuedConnection)
        self.sigTimerIntervalChanged.connect(
            self.pulsedmeasurementlogic().set_timer_interval,
            QtCore.Qt.QueuedConnection)
        self.sigAlternativeDataTypeChanged.connect(
            self.pulsedmeasurementlogic().set_alternative_data_type,
            QtCore.Qt.QueuedConnection)
        self.sigManuallyPullData.connect(
            self.pulsedmeasurementlogic().manually_pull_data,
            QtCore.Qt.QueuedConnection)

        # Connect signals coming from PulsedMeasurementLogic
        self.pulsedmeasurementlogic().sigMeasurementDataUpdated.connect(
            self.sigMeasurementDataUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigTimerUpdated.connect(
            self.sigTimerUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigFitUpdated.connect(
            self.fit_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigMeasurementStatusUpdated.connect(
            self.measurement_status_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigPulserRunningUpdated.connect(
            self.pulser_running_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtMicrowaveRunningUpdated.connect(
            self.ext_microwave_running_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtMicrowaveSettingsUpdated.connect(
            self.sigExtMicrowaveSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigFastCounterSettingsUpdated.connect(
            self.sigFastCounterSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigMeasurementSettingsUpdated.connect(
            self.sigMeasurementSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigAnalysisSettingsUpdated.connect(
            self.sigAnalysisSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtractionSettingsUpdated.connect(
            self.sigExtractionSettingsUpdated, QtCore.Qt.QueuedConnection)

        # Connect signals controlling SequenceGeneratorLogic
        self.sigSavePulseBlock.connect(
            self.sequencegeneratorlogic().save_block,
            QtCore.Qt.QueuedConnection)
        self.sigSaveBlockEnsemble.connect(
            self.sequencegeneratorlogic().save_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigSaveSequence.connect(
            self.sequencegeneratorlogic().save_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigDeletePulseBlock.connect(
            self.sequencegeneratorlogic().delete_block,
            QtCore.Qt.QueuedConnection)
        self.sigDeleteBlockEnsemble.connect(
            self.sequencegeneratorlogic().delete_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigDeleteSequence.connect(
            self.sequencegeneratorlogic().delete_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigLoadBlockEnsemble.connect(
            self.sequencegeneratorlogic().load_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigLoadSequence.connect(
            self.sequencegeneratorlogic().load_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigSampleBlockEnsemble.connect(
            self.sequencegeneratorlogic().sample_pulse_block_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigSampleSequence.connect(
            self.sequencegeneratorlogic().sample_pulse_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigClearPulseGenerator.connect(
            self.sequencegeneratorlogic().clear_pulser,
            QtCore.Qt.QueuedConnection)
        self.sigGeneratorSettingsChanged.connect(
            self.sequencegeneratorlogic().set_pulse_generator_settings,
            QtCore.Qt.QueuedConnection)
        self.sigSamplingSettingsChanged.connect(
            self.sequencegeneratorlogic().set_generation_parameters,
            QtCore.Qt.QueuedConnection)
        self.sigGeneratePredefinedSequence.connect(
            self.sequencegeneratorlogic().generate_predefined_sequence,
            QtCore.Qt.QueuedConnection)

        # Connect signals coming from SequenceGeneratorLogic
        self.sequencegeneratorlogic().sigBlockDictUpdated.connect(
            self.sigBlockDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigEnsembleDictUpdated.connect(
            self.sigEnsembleDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSequenceDictUpdated.connect(
            self.sigSequenceDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigAvailableWaveformsUpdated.connect(
            self.sigAvailableWaveformsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigAvailableSequencesUpdated.connect(
            self.sigAvailableSequencesUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigGeneratorSettingsUpdated.connect(
            self.sigGeneratorSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSamplingSettingsUpdated.connect(
            self.sigSamplingSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigPredefinedSequenceGenerated.connect(
            self.predefined_sequence_generated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSampleEnsembleComplete.connect(
            self.sample_ensemble_finished, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSampleSequenceComplete.connect(
            self.sample_sequence_finished, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigLoadedAssetUpdated.connect(
            self.loaded_asset_updated, QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """

        @return:
        """
        # Disconnect all signals
        # Disconnect signals controlling PulsedMeasurementLogic
        self.sigDoFit.disconnect()
        self.sigToggleMeasurement.disconnect()
        self.sigToggleMeasurementPause.disconnect()
        self.sigTogglePulser.disconnect()
        self.sigToggleExtMicrowave.disconnect()
        self.sigFastCounterSettingsChanged.disconnect()
        self.sigMeasurementSettingsChanged.disconnect()
        self.sigExtMicrowaveSettingsChanged.disconnect()
        self.sigAnalysisSettingsChanged.disconnect()
        self.sigExtractionSettingsChanged.disconnect()
        self.sigTimerIntervalChanged.disconnect()
        self.sigAlternativeDataTypeChanged.disconnect()
        self.sigManuallyPullData.disconnect()
        # Disconnect signals coming from PulsedMeasurementLogic
        self.pulsedmeasurementlogic().sigMeasurementDataUpdated.disconnect()
        self.pulsedmeasurementlogic().sigTimerUpdated.disconnect()
        self.pulsedmeasurementlogic().sigFitUpdated.disconnect()
        self.pulsedmeasurementlogic().sigMeasurementStatusUpdated.disconnect()
        self.pulsedmeasurementlogic().sigPulserRunningUpdated.disconnect()
        self.pulsedmeasurementlogic().sigExtMicrowaveRunningUpdated.disconnect(
        )
        self.pulsedmeasurementlogic(
        ).sigExtMicrowaveSettingsUpdated.disconnect()
        self.pulsedmeasurementlogic().sigFastCounterSettingsUpdated.disconnect(
        )
        self.pulsedmeasurementlogic().sigMeasurementSettingsUpdated.disconnect(
        )
        self.pulsedmeasurementlogic().sigAnalysisSettingsUpdated.disconnect()
        self.pulsedmeasurementlogic().sigExtractionSettingsUpdated.disconnect()

        # Disconnect signals controlling SequenceGeneratorLogic
        self.sigSavePulseBlock.disconnect()
        self.sigSaveBlockEnsemble.disconnect()
        self.sigSaveSequence.disconnect()
        self.sigDeletePulseBlock.disconnect()
        self.sigDeleteBlockEnsemble.disconnect()
        self.sigDeleteSequence.disconnect()
        self.sigLoadBlockEnsemble.disconnect()
        self.sigLoadSequence.disconnect()
        self.sigSampleBlockEnsemble.disconnect()
        self.sigSampleSequence.disconnect()
        self.sigClearPulseGenerator.disconnect()
        self.sigGeneratorSettingsChanged.disconnect()
        self.sigSamplingSettingsChanged.disconnect()
        self.sigGeneratePredefinedSequence.disconnect()
        # Disconnect signals coming from SequenceGeneratorLogic
        self.sequencegeneratorlogic().sigBlockDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigEnsembleDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigSequenceDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigAvailableWaveformsUpdated.disconnect()
        self.sequencegeneratorlogic().sigAvailableSequencesUpdated.disconnect()
        self.sequencegeneratorlogic().sigGeneratorSettingsUpdated.disconnect()
        self.sequencegeneratorlogic().sigSamplingSettingsUpdated.disconnect()
        self.sequencegeneratorlogic(
        ).sigPredefinedSequenceGenerated.disconnect()
        self.sequencegeneratorlogic().sigSampleEnsembleComplete.disconnect()
        self.sequencegeneratorlogic().sigSampleSequenceComplete.disconnect()
        self.sequencegeneratorlogic().sigLoadedAssetUpdated.disconnect()
        return

    #######################################################################
    ###             Pulsed measurement properties                       ###
    #######################################################################
    @property
    def fast_counter_constraints(self):
        return self.pulsedmeasurementlogic().fast_counter_constraints

    @property
    def fast_counter_settings(self):
        return self.pulsedmeasurementlogic().fast_counter_settings

    @property
    def ext_microwave_constraints(self):
        return self.pulsedmeasurementlogic().ext_microwave_constraints

    @property
    def ext_microwave_settings(self):
        return self.pulsedmeasurementlogic().ext_microwave_settings

    @property
    def measurement_settings(self):
        return self.pulsedmeasurementlogic().measurement_settings

    @property
    def timer_interval(self):
        return self.pulsedmeasurementlogic().timer_interval

    @property
    def analysis_methods(self):
        return self.pulsedmeasurementlogic().analysis_methods

    @property
    def extraction_methods(self):
        return self.pulsedmeasurementlogic().extraction_methods

    @property
    def analysis_settings(self):
        return self.pulsedmeasurementlogic().analysis_settings

    @property
    def extraction_settings(self):
        return self.pulsedmeasurementlogic().extraction_settings

    @property
    def signal_data(self):
        return self.pulsedmeasurementlogic().signal_data

    @property
    def signal_alt_data(self):
        return self.pulsedmeasurementlogic().signal_alt_data

    @property
    def measurement_error(self):
        return self.pulsedmeasurementlogic().measurement_error

    @property
    def raw_data(self):
        return self.pulsedmeasurementlogic().raw_data

    @property
    def laser_data(self):
        return self.pulsedmeasurementlogic().laser_data

    @property
    def alternative_data_type(self):
        return self.pulsedmeasurementlogic().alternative_data_type

    @property
    def fit_container(self):
        return self.pulsedmeasurementlogic().fc

    #######################################################################
    ###             Pulsed measurement methods                          ###
    #######################################################################
    @QtCore.Slot(dict)
    def set_measurement_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigMeasurementSettingsChanged.emit(settings_dict)
        else:
            self.sigMeasurementSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_fast_counter_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigFastCounterSettingsChanged.emit(settings_dict)
        else:
            self.sigFastCounterSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_ext_microwave_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigExtMicrowaveSettingsChanged.emit(settings_dict)
        else:
            self.sigExtMicrowaveSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_analysis_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigAnalysisSettingsChanged.emit(settings_dict)
        else:
            self.sigAnalysisSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_extraction_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigExtractionSettingsChanged.emit(settings_dict)
        else:
            self.sigExtractionSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(int)
    @QtCore.Slot(float)
    def set_timer_interval(self, interval):
        """

        @param int|float interval: The timer interval to set in seconds.
        """
        if isinstance(interval, (int, float)):
            self.sigTimerIntervalChanged.emit(interval)
        return

    @QtCore.Slot(str)
    def set_alternative_data_type(self, alt_data_type):
        """

        @param alt_data_type:
        @return:
        """
        if isinstance(alt_data_type, str):
            self.sigAlternativeDataTypeChanged.emit(alt_data_type)
        return

    @QtCore.Slot()
    def manually_pull_data(self):
        """
        """
        self.sigManuallyPullData.emit()
        return

    @QtCore.Slot(bool)
    def toggle_ext_microwave(self, switch_on):
        """

        @param switch_on:
        """
        if isinstance(switch_on, bool):
            self.sigToggleExtMicrowave.emit(switch_on)
        return

    @QtCore.Slot(bool)
    def ext_microwave_running_updated(self, is_running):
        """

        @param is_running:
        """
        if isinstance(is_running, bool):
            self.status_dict['microwave_running'] = is_running
            self.sigExtMicrowaveRunningUpdated.emit(is_running)
        return

    @QtCore.Slot(bool)
    def toggle_pulse_generator(self, switch_on):
        """

        @param switch_on:
        """
        if isinstance(switch_on, bool):
            self.sigTogglePulser.emit(switch_on)
        return

    @QtCore.Slot(bool)
    def pulser_running_updated(self, is_running):
        """

        @param is_running:
        """
        if isinstance(is_running, bool):
            self.status_dict['pulser_running'] = is_running
            self.sigPulserRunningUpdated.emit(is_running)
        return

    @QtCore.Slot(bool)
    @QtCore.Slot(bool, str)
    def toggle_pulsed_measurement(self, start, stash_raw_data_tag=''):
        """

        @param bool start:
        @param str stash_raw_data_tag:
        """
        if isinstance(start, bool) and isinstance(stash_raw_data_tag, str):
            self.sigToggleMeasurement.emit(start, stash_raw_data_tag)
        return

    @QtCore.Slot(bool)
    def toggle_pulsed_measurement_pause(self, pause):
        """

        @param pause:
        """
        if isinstance(pause, bool):
            self.sigToggleMeasurementPause.emit(pause)
        return

    @QtCore.Slot(bool, bool)
    def measurement_status_updated(self, is_running, is_paused):
        """

        @param is_running:
        @param is_paused:
        """
        if isinstance(is_running, bool) and isinstance(is_paused, bool):
            self.status_dict['measurement_running'] = is_running
            self.sigMeasurementStatusUpdated.emit(is_running, is_paused)
        return

    @QtCore.Slot(str)
    def do_fit(self, fit_function):
        """

        @param fit_function:
        """
        if isinstance(fit_function, str):
            self.status_dict['fitting_busy'] = True
            self.sigDoFit.emit(fit_function)
        return

    @QtCore.Slot(str, np.ndarray, object)
    def fit_updated(self, fit_name, fit_data, fit_result):
        """

        @return:
        """
        self.status_dict['fitting_busy'] = False
        self.sigFitUpdated.emit(fit_name, fit_data, fit_result)
        return

    def save_measurement_data(self, tag, with_error):
        """
        Prepare data to be saved and create a proper plot of the data.
        This is just handed over to the measurement logic.

        @param str tag: a filetag which will be included in the filename
        @param bool with_error: select whether errors should be saved/plotted
        """
        self.pulsedmeasurementlogic().save_measurement_data(tag, with_error)
        return

    #######################################################################
    ###             Sequence generator properties                       ###
    #######################################################################
    @property
    def pulse_generator_constraints(self):
        return self.sequencegeneratorlogic().pulse_generator_constraints

    @property
    def pulse_generator_settings(self):
        return self.sequencegeneratorlogic().pulse_generator_settings

    @property
    def generation_parameters(self):
        return self.sequencegeneratorlogic().generation_parameters

    @property
    def analog_channels(self):
        return self.sequencegeneratorlogic().analog_channels

    @property
    def digital_channels(self):
        return self.sequencegeneratorlogic().digital_channels

    @property
    def saved_pulse_blocks(self):
        return self.sequencegeneratorlogic().saved_pulse_blocks

    @property
    def saved_pulse_block_ensembles(self):
        return self.sequencegeneratorlogic().saved_pulse_block_ensembles

    @property
    def saved_pulse_sequences(self):
        return self.sequencegeneratorlogic().saved_pulse_sequences

    @property
    def sampled_waveforms(self):
        return self.sequencegeneratorlogic().sampled_waveforms

    @property
    def sampled_sequences(self):
        return self.sequencegeneratorlogic().sampled_sequences

    @property
    def loaded_asset(self):
        return self.sequencegeneratorlogic().loaded_asset

    @property
    def generate_methods(self):
        return self.sequencegeneratorlogic().generate_methods

    @property
    def generate_method_params(self):
        return self.sequencegeneratorlogic().generate_method_params

    #######################################################################
    ###             Sequence generator methods                          ###
    #######################################################################
    @QtCore.Slot()
    def clear_pulse_generator(self):
        still_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.status_dict[
                    'loading_busy'] or self.status_dict['sampload_busy']
        if still_busy:
            self.log.error(
                'Can not clear pulse generator. Sampling/Loading still in progress.'
            )
        else:
            self.sigClearPulseGenerator.emit()
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, bool)
    def sample_ensemble(self, ensemble_name, with_load=False):
        already_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.sequencegeneratorlogic(
                ).module_state() == 'locked'
        if already_busy:
            self.log.error(
                'Sampling of a different asset already in progress.\n'
                'PulseBlockEnsemble "{0}" not sampled!'.format(ensemble_name))
        else:
            if with_load:
                self.status_dict['sampload_busy'] = True
            self.status_dict['sampling_ensemble_busy'] = True
            self.sigSampleBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(object)
    def sample_ensemble_finished(self, ensemble):
        self.status_dict['sampling_ensemble_busy'] = False
        self.sigSampleEnsembleComplete.emit(ensemble)
        if self.status_dict['sampload_busy'] and not self.status_dict[
                'sampling_sequence_busy']:
            if ensemble is None:
                self.status_dict['sampload_busy'] = False
                self.sigLoadedAssetUpdated.emit(*self.loaded_asset)
            else:
                self.load_ensemble(ensemble.name)
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, bool)
    def sample_sequence(self, sequence_name, with_load=False):
        already_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.sequencegeneratorlogic(
                ).module_state() == 'locked'
        if already_busy:
            self.log.error(
                'Sampling of a different asset already in progress.\n'
                'PulseSequence "{0}" not sampled!'.format(sequence_name))
        else:
            if with_load:
                self.status_dict['sampload_busy'] = True
            self.status_dict['sampling_sequence_busy'] = True
            self.sigSampleSequence.emit(sequence_name)
        return

    @QtCore.Slot(object)
    def sample_sequence_finished(self, sequence):
        self.status_dict['sampling_sequence_busy'] = False
        self.sigSampleSequenceComplete.emit(sequence)
        if self.status_dict['sampload_busy']:
            if sequence is None:
                self.status_dict['sampload_busy'] = False
                self.sigLoadedAssetUpdated.emit(*self.loaded_asset)
            else:
                self.load_sequence(sequence.name)
        return

    @QtCore.Slot(str)
    def load_ensemble(self, ensemble_name):
        if self.status_dict['loading_busy']:
            self.log.error(
                'Loading of a different asset already in progress.\n'
                'PulseBlockEnsemble "{0}" not loaded!'.format(ensemble_name))
        else:
            self.status_dict['loading_busy'] = True
            self.sigLoadBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(str)
    def load_sequence(self, sequence_name):
        if self.status_dict['loading_busy']:
            self.log.error(
                'Loading of a different asset already in progress.\n'
                'PulseSequence "{0}" not loaded!'.format(sequence_name))
        else:
            self.status_dict['loading_busy'] = True
            self.sigLoadSequence.emit(sequence_name)
        return

    @QtCore.Slot(str, str)
    def loaded_asset_updated(self, asset_name, asset_type):
        """

        @param asset_name:
        @param asset_type:
        @return:
        """
        self.status_dict['sampload_busy'] = False
        self.status_dict['loading_busy'] = False
        self.sigLoadedAssetUpdated.emit(asset_name, asset_type)
        # Transfer sequence information from PulseBlockEnsemble or PulseSequence to
        # PulsedMeasurementLogic to be able to invoke measurement settings from them
        if not asset_type:
            # If no asset loaded or asset type unknown, clear sequence_information dict

            object_instance = None
        elif asset_type == 'PulseBlockEnsemble':
            object_instance = self.saved_pulse_block_ensembles.get(asset_name)
        elif asset_type == 'PulseSequence':
            object_instance = self.saved_pulse_sequences.get(asset_name)
        else:
            object_instance = None

        if object_instance is None:
            self.pulsedmeasurementlogic().sampling_information = dict()
            self.pulsedmeasurementlogic().measurement_information = dict()
        else:
            self.pulsedmeasurementlogic(
            ).sampling_information = object_instance.sampling_information
            self.pulsedmeasurementlogic(
            ).measurement_information = object_instance.measurement_information
        return

    @QtCore.Slot(object)
    def save_pulse_block(self, block_instance):
        """

        @param block_instance:
        @return:
        """
        self.sigSavePulseBlock.emit(block_instance)
        return

    @QtCore.Slot(object)
    def save_block_ensemble(self, ensemble_instance):
        """


        @param ensemble_instance:
        @return:
        """
        self.sigSaveBlockEnsemble.emit(ensemble_instance)
        return

    @QtCore.Slot(object)
    def save_sequence(self, sequence_instance):
        """

        @param sequence_instance:
        @return:
        """
        self.sigSaveSequence.emit(sequence_instance)
        return

    @QtCore.Slot(str)
    def delete_pulse_block(self, block_name):
        """

        @param block_name:
        @return:
        """
        self.sigDeletePulseBlock.emit(block_name)
        return

    @QtCore.Slot(str)
    def delete_block_ensemble(self, ensemble_name):
        """

        @param ensemble_name:
        @return:
        """
        self.sigDeleteBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(str)
    def delete_sequence(self, sequence_name):
        """

        @param sequence_name:
        @return:
        """
        self.sigDeleteSequence.emit(sequence_name)
        return

    @QtCore.Slot(dict)
    def set_pulse_generator_settings(self, settings_dict=None, **kwargs):
        """
        Either accept a settings dictionary as positional argument or keyword arguments.
        If both are present both are being used by updating the settings_dict with kwargs.
        The keyword arguments take precedence over the items in settings_dict if there are
        conflicting names.

        @param settings_dict:
        @param kwargs:
        @return:
        """
        if not isinstance(settings_dict, dict):
            settings_dict = kwargs
        else:
            settings_dict.update(kwargs)
        self.sigGeneratorSettingsChanged.emit(settings_dict)
        return

    @QtCore.Slot(dict)
    def set_generation_parameters(self, settings_dict=None, **kwargs):
        """
        Either accept a settings dictionary as positional argument or keyword arguments.
        If both are present both are being used by updating the settings_dict with kwargs.
        The keyword arguments take precedence over the items in settings_dict if there are
        conflicting names.

        @param settings_dict:
        @param kwargs:
        @return:
        """
        if not isinstance(settings_dict, dict):
            settings_dict = kwargs
        else:
            settings_dict.update(kwargs)

        # Force empty gate channel if fast counter is not gated
        if 'gate_channel' in settings_dict and not self.fast_counter_settings.get(
                'is_gated'):
            settings_dict['gate_channel'] = ''
        self.sigSamplingSettingsChanged.emit(settings_dict)
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, dict)
    @QtCore.Slot(str, dict, bool)
    def generate_predefined_sequence(self,
                                     generator_method_name,
                                     kwarg_dict=None,
                                     sample_and_load=False):
        """

        @param generator_method_name:
        @param kwarg_dict:
        @param sample_and_load:
        @return:
        """
        if not isinstance(kwarg_dict, dict):
            kwarg_dict = dict()
        self.status_dict['predefined_generation_busy'] = True
        if sample_and_load:
            self.status_dict['sampload_busy'] = True
        self.sigGeneratePredefinedSequence.emit(generator_method_name,
                                                kwarg_dict)
        return

    @QtCore.Slot(object, bool)
    def predefined_sequence_generated(self, asset_name, is_sequence):
        self.status_dict['predefined_generation_busy'] = False
        if asset_name is None:
            self.status_dict['sampload_busy'] = False
        self.sigPredefinedSequenceGenerated.emit(asset_name, is_sequence)
        if self.status_dict['sampload_busy']:
            if is_sequence:
                self.sample_sequence(asset_name, True)
            else:
                self.sample_ensemble(asset_name, True)
        return

    def get_ensemble_info(self, ensemble):
        """
        """
        return self.sequencegeneratorlogic().get_ensemble_info(
            ensemble=ensemble)

    def get_sequence_info(self, sequence):
        """
        """
        return self.sequencegeneratorlogic().get_sequence_info(
            sequence=sequence)
Пример #10
0
class ResizingScrolledPanel(QW.QScrollArea):

    okSignal = QC.Signal()

    def __init__(self, parent):

        QW.QScrollArea.__init__(self, parent)

        self.setWidget(QW.QWidget(self))

        self.setWidgetResizable(True)

        self.widget().installEventFilter(ResizingEventFilter(self))

    def _OKParent(self):

        self.okSignal.emit()

    def CheckValid(self):

        pass

    def CleanBeforeDestroy(self):

        pass

    def sizeHint(self):

        if self.widget():

            # just as a fun note, QScrollArea does a 12 x 8 character height sizeHint on its own here due to as-yet invalid widget size, wew lad

            frame_width = self.frameWidth()

            frame_size = QC.QSize(frame_width * 2, frame_width * 2)

            size_hint = self.widget().sizeHint() + frame_size

            #visible_size = self.widget().visibleRegion().boundingRect().size()
            #size_hint = self.widget().sizeHint() + self.size() - visible_size

            available_screen_size = QW.QApplication.desktop(
            ).availableGeometry(self).size()

            screen_fill_factor = 0.85  # don't let size hint be bigger than this percentage of the available screen width/height

            if size_hint.width(
            ) > screen_fill_factor * available_screen_size.width():

                size_hint.setWidth(screen_fill_factor *
                                   available_screen_size.width())

            if size_hint.height(
            ) > screen_fill_factor * available_screen_size.height():

                size_hint.setHeight(screen_fill_factor *
                                    available_screen_size.height())

            return size_hint

        else:

            return QW.QScrollArea.sizeHint(self)

    def UserIsOKToOK(self):

        return True

    def UserIsOKToCancel(self):

        return True

    def WidgetJustSized(self, width_larger, height_larger):

        widget_minimum_size_hint = self.widget().minimumSizeHint()
        widget_normal_size_hint = self.widget().sizeHint()

        widget_size_hint = QC.QSize(
            max(widget_minimum_size_hint.width(),
                widget_normal_size_hint.width()),
            max(widget_minimum_size_hint.height(),
                widget_normal_size_hint.height()))

        my_size = self.size()

        width_increase = 0
        height_increase = 0

        # + 2 because it is late and that seems to stop scrollbars lmao

        if width_larger:

            width_increase = max(
                0,
                widget_size_hint.width() - my_size.width() + 2)

        if height_larger:

            height_increase = max(
                0,
                widget_size_hint.height() - my_size.height() + 2)

        if width_increase > 0 or height_increase > 0:

            window = self.window()

            if isinstance(window, (ClientGUITopLevelWindows.DialogThatResizes,
                                   ClientGUITopLevelWindows.FrameThatResizes)):

                desired_size_delta = QC.QSize(width_increase, height_increase)

                ClientGUITopLevelWindows.ExpandTLWIfPossible(
                    window, window._frame_key, desired_size_delta)
Пример #11
0
class ProgressBar(QtWidgets.QFrame):
    progress_signal = QtCore.Signal(int)
    stop_signal = QtCore.Signal()

    def __init__(
        self,
        app: QtWidgets.QApplication,
        tasks: List[Task],
        signal_task: bool = False,
        auto_run: bool = True,
        can_cancel: bool = False,
    ):
        super().__init__(None)
        self.app = app

        self.tasks = tasks
        self.signal_task = signal_task

        self.setObjectName("ProgressBar")
        self.setStyleSheet("#ProgressBar{border: 1px solid #aaa}")

        self.setMinimumWidth(400)
        self.setWindowFlags(QtCore.Qt.SplashScreen
                            | QtCore.Qt.FramelessWindowHint)

        self.status = QtWidgets.QLabel()
        self.progress_bar = QtWidgets.QProgressBar(self)
        self.progress_bar.setGeometry(30, 40, 500, 75)

        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.status)
        self.layout.addWidget(self.progress_bar)
        if can_cancel:
            cancel_button = QtWidgets.QPushButton(t("Cancel"))
            cancel_button.clicked.connect(self.cancel)
            self.layout.addWidget(cancel_button)
        self.setLayout(self.layout)

        self.show()
        if auto_run:
            self.run()

    def cancel(self):
        self.stop_signal.emit()
        self.close()

    @reusables.log_exception("fastflix")
    def run(self):
        ratio = 100 // len(self.tasks)
        self.progress_bar.setValue(0)

        if self.signal_task:
            self.status.setText(self.tasks[0].name)
            self.progress_signal.connect(self.update_progress)
            self.tasks[0].kwargs["signal"] = self.progress_signal
            self.tasks[0].kwargs["stop_signal"] = self.stop_signal
            self.tasks[0].command(config=self.app.fastflix.config,
                                  app=self.app,
                                  **self.tasks[0].kwargs)

        else:
            for i, task in enumerate(self.tasks, start=1):
                self.status.setText(task.name)
                self.app.processEvents()
                try:
                    task.command(config=self.app.fastflix.config,
                                 app=self.app,
                                 **task.kwargs)
                except Exception:
                    logger.exception(
                        f"Could not run task {task.name} with config {self.app.fastflix.config}"
                    )
                    raise
                self.progress_bar.setValue(int(i * ratio))

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.app.processEvents()
Пример #12
0
class NavigationGraphicsView(QtWidgets.QGraphicsView):
    """Graphics view for dataset navigation.

    The view usually displays an auto-scaled low resolution overview
    of the scene with a red box indicating the area currently displayed
    in the high resolution view.

    :SIGNALS:

        * :attr:`mousePressed`
        * :attr:`mouseMoved`

    """

    BOXCOLOR = QtGui.QColor(QtCore.Qt.red)
    BOXWIDTH = 60

    #: SIGNAL: it is emitted when a mouse button is presses on the view
    #:
    #: :param point:
    #:     the scene position
    #: :param mousebutton:
    #:     the ID of the pressed button
    #: :param dragmode:
    #:     current darg mode
    #:
    #: :C++ signature: `void mousePressed(QPointF, Qt::MouseButtons,
    #:                                    QGraphicsView::DragMode)`
    mousePressed = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
                                 QtWidgets.QGraphicsView.DragMode)

    #: SIGNAL: it is emitted when the mouse is moved on the view
    #:
    #: :param point:
    #:     the scene position
    #: :param mousebutton:
    #:     the ID of the pressed button
    #: :param dragmode:
    #:     current drag mode
    #:
    #: :C++ signature: `void mouseMoved(QPointF, Qt::MouseButtons,
    #:                                    QGraphicsView::DragMode)`
    mouseMoved = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
                               QtWidgets.QGraphicsView.DragMode)

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self._viewbox = None
        self._autoscale = True
        self.setMouseTracking(True)

        # default pen
        self._pen = QtGui.QPen()
        self._pen.setColor(self.BOXCOLOR)
        self._pen.setWidth(self.BOXWIDTH)

    @property
    def viewbox(self):
        """Viewport box in scene coordinates"""
        return self._viewbox

    @viewbox.setter
    def viewbox(self, box):
        """Set the viewport box in scene coordinates"""
        assert isinstance(box, (QtCore.QRect, QtCore.QRectF))
        self._viewbox = box
        if self.isVisible():
            # @WARNING: calling "update" on the scene causes a repaint of
            #           *all* attached views and for each view the entire
            #           exposedRect is updated.
            #           Using QGraphicsView.invalidateScene with the
            #           QtWidgets.QGraphicsScene.ForegroundLayer parameter
            #           should be faster and repaint only one layer of the
            #           current view.

            # @TODO: check
            # self.invalidateScene(self.sceneRect(),
            #                      QtWidgets.QGraphicsScene.ForegroundLayer)
            self.scene().update()

    def drawForeground(self, painter, rect):
        if not self.viewbox:
            return

        pen = painter.pen()
        try:
            box = self.viewbox.intersected(self.sceneRect())
            painter.setPen(self._pen)
            painter.drawRect(box)
            # painter.drawConvexPolygon(self.viewbox) #@TODO: check
        finally:
            painter.setPen(pen)

    def fitInView(self, rect=None, aspectRatioMode=QtCore.Qt.KeepAspectRatio):
        if not rect:
            scene = self.scene()
            if scene:
                rect = scene.sceneRect()
            else:
                return
        QtWidgets.QGraphicsView.fitInView(self, rect, aspectRatioMode)

    @property
    def autoscale(self):
        return self._autoscale

    @autoscale.setter
    def autoscale(self, flag):
        self._autoscale = bool(flag)
        if self._autoscale:
            self.fitInView()
        else:
            self.setTransform(QtGui.QTransform())
            self.update()

    def resizeEvent(self, event):
        if self.autoscale:
            self.fitInView()
        return QtWidgets.QGraphicsView.resizeEvent(self, event)

    # @TODO: use event filters
    def mousePressEvent(self, event):
        pos = self.mapToScene(event.pos())
        self.mousePressed.emit(pos, event.buttons(), self.dragMode())
        return QtWidgets.QGraphicsView.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        pos = self.mapToScene(event.pos())
        self.mouseMoved.emit(pos, event.buttons(), self.dragMode())
        return QtWidgets.QGraphicsView.mouseMoveEvent(self, event)
Пример #13
0
class SliceWidget(QtWidgets.QWidget):

    slice_changed = QtCore.Signal(int)

    def __init__(self, label='', world=None, lo=0, hi=10,
                 parent=None, world_unit=None,
                 world_warning=False):

        super(SliceWidget, self).__init__(parent)

        self.state = SliceState()
        self.state.label = label
        self.state.slice_center = (lo + hi) // 2

        self._world = np.asarray(world)
        self._world_warning = world_warning
        self._world_unit = world_unit

        self.ui = load_ui('data_slice_widget.ui', self,
                          directory=os.path.dirname(__file__))

        autoconnect_callbacks_to_qt(self.state, self.ui)

        font = self.text_warning.font()
        font.setPointSize(font.pointSize() * 0.75)
        self.text_warning.setFont(font)

        self.button_first.setStyleSheet('border: 0px')
        self.button_first.setIcon(get_icon('playback_first'))
        self.button_prev.setStyleSheet('border: 0px')
        self.button_prev.setIcon(get_icon('playback_prev'))
        self.button_back.setStyleSheet('border: 0px')
        self.button_back.setIcon(get_icon('playback_back'))
        self.button_stop.setStyleSheet('border: 0px')
        self.button_stop.setIcon(get_icon('playback_stop'))
        self.button_forw.setStyleSheet('border: 0px')
        self.button_forw.setIcon(get_icon('playback_forw'))
        self.button_next.setStyleSheet('border: 0px')
        self.button_next.setIcon(get_icon('playback_next'))
        self.button_last.setStyleSheet('border: 0px')
        self.button_last.setIcon(get_icon('playback_last'))

        self.value_slice_center.setMinimum(lo)
        self.value_slice_center.setMaximum(hi)
        self.value_slice_center.valueChanged.connect(nonpartial(self.set_label_from_slider))

        # Figure out the optimal format to use to show the world values. We do
        # this by figuring out the precision needed so that when converted to
        # a string, every string value is different.
        if world is not None:
            if np.max(np.abs(world)) > 1e5 or np.max(np.abs(world)) < 1e-5:
                fmt_type = 'e'
            else:
                fmt_type = 'f'
            relative = np.abs(np.diff(world) / world[:-1])
            ndec = max(2, min(int(np.ceil(-np.log10(np.min(relative)))) + 1, 15))
            self.label_fmt = "{:." + str(ndec) + fmt_type + "}"
        else:
            self.label_fmt = "{:g}"

        self.text_slider_label.setMinimumWidth(80)
        self.state.slider_label = self.label_fmt.format(self.value_slice_center.value())
        self.text_slider_label.editingFinished.connect(nonpartial(self.set_slider_from_label))

        self._play_timer = QtCore.QTimer()
        self._play_timer.setInterval(500)
        self._play_timer.timeout.connect(nonpartial(self._play_slice))

        self.button_first.clicked.connect(nonpartial(self._browse_slice, 'first'))
        self.button_prev.clicked.connect(nonpartial(self._browse_slice, 'prev'))
        self.button_back.clicked.connect(nonpartial(self._adjust_play, 'back'))
        self.button_stop.clicked.connect(nonpartial(self._adjust_play, 'stop'))
        self.button_forw.clicked.connect(nonpartial(self._adjust_play, 'forw'))
        self.button_next.clicked.connect(nonpartial(self._browse_slice, 'next'))
        self.button_last.clicked.connect(nonpartial(self._browse_slice, 'last'))

        self.bool_use_world.toggled.connect(nonpartial(self.set_label_from_slider))

        if world is None:
            self.state.use_world = False
            self.bool_use_world.hide()
        else:
            self.state.use_world = not world_warning

        if world_unit:
            self.state.slider_unit = world_unit
        else:
            self.state.slider_unit = ''

        self._play_speed = 0

        self.set_label_from_slider()

    def set_label_from_slider(self):
        value = self.state.slice_center
        if self.state.use_world:
            value = self._world[value]
            if self._world_warning:
                self.text_warning.show()
            else:
                self.text_warning.hide()
            self.state.slider_unit = self._world_unit
            self.state.slider_label = self.label_fmt.format(value)
        else:
            self.text_warning.hide()
            self.state.slider_unit = ''
            self.state.slider_label = str(value)

    def set_slider_from_label(self):

        # Ignore recursive calls - we do this rather than ignore_callback
        # below when setting slider_label, otherwise we might be stopping other
        # subscribers to that event from being correctly updated
        if getattr(self, '_in_set_slider_from_label', False):
            return
        else:
            self._in_set_slider_from_label = True

        text = self.text_slider_label.text()
        if self.state.use_world:
            # Don't want to assume world is sorted, pick closest value
            value = np.argmin(np.abs(self._world - float(text)))
            self.state.slider_label = self.label_fmt.format(self._world[value])
        else:
            value = int(text)
        self.value_slice_center.setValue(value)

        self._in_set_slider_from_label = False

    def _adjust_play(self, action):
        if action == 'stop':
            self._play_speed = 0
        elif action == 'back':
            if self._play_speed > 0:
                self._play_speed = -1
            else:
                self._play_speed -= 1
        elif action == 'forw':
            if self._play_speed < 0:
                self._play_speed = +1
            else:
                self._play_speed += 1
        if self._play_speed == 0:
            self._play_timer.stop()
        else:
            self._play_timer.start()
            self._play_timer.setInterval(500 / abs(self._play_speed))

    def _play_slice(self):
        if self._play_speed > 0:
            self._browse_slice('next', play=True)
        elif self._play_speed < 0:
            self._browse_slice('prev', play=True)

    def _browse_slice(self, action, play=False):

        imin = self.value_slice_center.minimum()
        imax = self.value_slice_center.maximum()
        value = self.value_slice_center.value()

        # If this was not called from _play_slice, we should stop the
        # animation.
        if not play:
            self._adjust_play('stop')

        if action == 'first':
            value = imin
        elif action == 'last':
            value = imax
        elif action == 'prev':
            value = value - 1
            if value < imin:
                value = imax
        elif action == 'next':
            value = value + 1
            if value > imax:
                value = imin
        else:
            raise ValueError("Action should be one of first/prev/next/last")

        self.value_slice_center.setValue(value)
Пример #14
0
class CounterLogic(GenericLogic):
    """ This logic module gathers data from a hardware counting device.

    @signal sigCounterUpdate: there is new counting data available
    @signal sigCountContinuousNext: used to simulate a loop in which the data
                                    acquisition runs.
    @sigmal sigCountGatedNext: ???

    @return error: 0 is OK, -1 is error
    """
    sigCounterUpdated = QtCore.Signal()

    sigCountDataNext = QtCore.Signal()

    sigGatedCounterFinished = QtCore.Signal()
    sigGatedCounterContinue = QtCore.Signal(bool)
    sigCountingSamplesChanged = QtCore.Signal(int)
    sigCountLengthChanged = QtCore.Signal(int)
    sigCountFrequencyChanged = QtCore.Signal(float)
    sigSavingStatusChanged = QtCore.Signal(bool)
    sigCountStatusChanged = QtCore.Signal(bool)
    sigCountingModeChanged = QtCore.Signal(CountingMode)

    _modclass = 'CounterLogic'
    _modtype = 'logic'

    ## declare connectors
    _connectors = {
        'counter1': 'SlowCounterInterface',
        'savelogic': 'SaveLogic'
    }

    def __init__(self, config, **kwargs):
        """ Create CounterLogic object with connectors.

        @param dict config: module configuration
        @param dict kwargs: optional parameters
        """
        super().__init__(config=config, **kwargs)

        #locking for thread safety
        self.threadlock = Mutex()

        self.log.info('The following configuration was found.')

        # checking for the right configuration
        for key in config.keys():
            self.log.info('{0}: {1}'.format(key, config[key]))

        # in bins
        self._count_length = 300
        self._smooth_window_length = 10
        self._counting_samples = 1  # oversampling
        # in hertz
        self._count_frequency = 50

        # self._binned_counting = True  # UNUSED?
        self._counting_mode = CountingMode['CONTINUOUS']

        self._saving = False
        return

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        # Connect to hardware and save logic
        self._counting_device = self.get_connector('counter1')
        self._save_logic = self.get_connector('savelogic')

        # Recall saved app-parameters
        if 'count_length' in self._statusVariables:
            self._count_length = self._statusVariables['count_length']
        if 'smooth_window_length' in self._statusVariables:
            self._smooth_window_length = self._statusVariables[
                'smooth_window_length']
        if 'counting_samples' in self._statusVariables:
            self._counting_samples = self._statusVariables['counting_samples']
        if 'count_frequency' in self._statusVariables:
            self._count_frequency = self._statusVariables['count_frequency']
        if 'counting_mode' in self._statusVariables:
            self._counting_mode = CountingMode[
                self._statusVariables['counting_mode']]
        if 'saving' in self._statusVariables:
            self._saving = self._statusVariables['saving']

        constraints = self.get_hardware_constraints()
        number_of_detectors = constraints.max_detectors

        # initialize data arrays
        self.countdata = np.zeros(
            [len(self.get_channels()), self._count_length])
        self.countdata_smoothed = np.zeros(
            [len(self.get_channels()), self._count_length])
        self.rawdata = np.zeros(
            [len(self.get_channels()), self._counting_samples])
        self._already_counted_samples = 0  # For gated counting
        self._data_to_save = []

        # Flag to stop the loop
        self.stopRequested = False

        self._saving_start_time = time.time()

        # connect signals
        self.sigCountDataNext.connect(self.count_loop_body,
                                      QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        # Save parameters to disk
        self._statusVariables['count_length'] = self._count_length
        self._statusVariables[
            'smooth_window_length'] = self._smooth_window_length
        self._statusVariables['counting_samples'] = self._counting_samples
        self._statusVariables['count_frequency'] = self._count_frequency
        self._statusVariables['counting_mode'] = self._counting_mode.name
        self._statusVariables['saving'] = self._saving

        # Stop measurement
        if self.getState() == 'locked':
            self._stopCount_wait()

        self.sigCountDataNext.disconnect()
        return

    def get_hardware_constraints(self):
        """
        Retrieve the hardware constrains from the counter device.

        @return SlowCounterConstraints: object with constraints for the counter
        """
        return self._counting_device.get_constraints()

    def set_counting_samples(self, samples=1):
        """
        Sets the length of the counted bins.
        The counter is stopped first and restarted afterwards.

        @param int samples: oversampling in units of bins (positive int ).

        @return int: oversampling in units of bins.
        """
        # Determine if the counter has to be restarted after setting the parameter
        if self.getState() == 'locked':
            restart = True
        else:
            restart = False

        if samples > 0:
            self._stopCount_wait()
            self._counting_samples = int(samples)
            # if the counter was running, restart it
            if restart:
                self.startCount()
        else:
            self.log.warning(
                'counting_samples has to be larger than 0! Command ignored!')
        self.sigCountingSamplesChanged.emit(self._counting_samples)
        return self._counting_samples

    def set_count_length(self, length=300):
        """ Sets the time trace in units of bins.

        @param int length: time trace in units of bins (positive int).

        @return int: length of time trace in units of bins

        This makes sure, the counter is stopped first and restarted afterwards.
        """
        if self.getState() == 'locked':
            restart = True
        else:
            restart = False

        if length > 0:
            self._stopCount_wait()
            self._count_length = int(length)
            # if the counter was running, restart it
            if restart:
                self.startCount()
        else:
            self.log.warning(
                'count_length has to be larger than 0! Command ignored!')
        self.sigCountLengthChanged.emit(self._count_length)
        return self._count_length

    def set_count_frequency(self, frequency=50):
        """ Sets the frequency with which the data is acquired.

        @param float frequency: the desired frequency of counting in Hz

        @return float: the actual frequency of counting in Hz

        This makes sure, the counter is stopped first and restarted afterwards.
        """
        constraints = self.get_hardware_constraints()

        if self.getState() == 'locked':
            restart = True
        else:
            restart = False

        if constraints.min_count_frequency <= frequency <= constraints.max_count_frequency:
            self._stopCount_wait()
            self._count_frequency = frequency
            # if the counter was running, restart it
            if restart:
                self.startCount()
        else:
            self.log.warning('count_frequency not in range! Command ignored!')
        self.sigCountFrequencyChanged.emit(self._count_frequency)
        return self._count_frequency

    def get_count_length(self):
        """ Returns the currently set length of the counting array.

        @return int: count_length
        """
        return self._count_length

    #FIXME: get from hardware
    def get_count_frequency(self):
        """ Returns the currently set frequency of counting (resolution).

        @return float: count_frequency
        """
        return self._count_frequency

    def get_counting_samples(self):
        """ Returns the currently set number of samples counted per readout.

        @return int: counting_samples
        """
        return self._counting_samples

    def get_saving_state(self):
        """ Returns if the data is saved in the moment.

        @return bool: saving state
        """
        return self._saving

    def start_saving(self, resume=False):
        """
        Sets up start-time and initializes data array, if not resuming, and changes saving state.
        If the counter is not running it will be started in order to have data to save.

        @return bool: saving state
        """
        if not resume:
            self._data_to_save = []
            self._saving_start_time = time.time()

        self._saving = True

        # If the counter is not running, then it should start running so there is data to save
        if self.getState() != 'locked':
            self.startCount()

        self.sigSavingStatusChanged.emit(self._saving)
        return self._saving

    def save_data(self, to_file=True, postfix=''):
        """ Save the counter trace data and writes it to a file.

        @param bool to_file: indicate, whether data have to be saved to file
        @param str postfix: an additional tag, which will be added to the filename upon save

        @return dict parameters: Dictionary which contains the saving parameters
        """
        # stop saving thus saving state has to be set to False
        self._saving = False
        self._saving_stop_time = time.time()

        # write the parameters:
        parameters = OrderedDict()
        parameters['Start counting time'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_start_time))
        parameters['Stop counting time'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time))
        parameters['Count frequency (Hz)'] = self._count_frequency
        parameters['Oversampling (Samples)'] = self._counting_samples
        parameters[
            'Smooth Window Length (# of events)'] = self._smooth_window_length

        if to_file:
            # If there is a postfix then add separating underscore
            if postfix == '':
                filelabel = 'count_trace'
            else:
                filelabel = 'count_trace_' + postfix

            # prepare the data in a dict or in an OrderedDict:
            header = 'Time (s)'
            for i, detector in enumerate(self.get_channels()):
                header = header + ',Signal{0} (counts/s)'.format(i)

            data = {header: self._data_to_save}
            filepath = self._save_logic.get_path_for_module(
                module_name='Counter')

            fig = self.draw_figure(data=np.array(self._data_to_save))
            self._save_logic.save_data(data,
                                       filepath=filepath,
                                       parameters=parameters,
                                       filelabel=filelabel,
                                       plotfig=fig,
                                       delimiter='\t')
            self.log.info('Counter Trace saved to:\n{0}'.format(filepath))

        self.sigSavingStatusChanged.emit(self._saving)
        return self._data_to_save, parameters

    def draw_figure(self, data):
        """ Draw figure to save with data file.

        @param: nparray data: a numpy array containing counts vs time for all detectors

        @return: fig fig: a matplotlib figure object to be saved to file.
        """
        count_data = data[:, 1:len(self.get_channels()) + 1]
        time_data = data[:, 0]

        # Scale count values using SI prefix
        prefix = ['', 'k', 'M', 'G']
        prefix_index = 0
        while np.max(count_data) > 1000:
            count_data = count_data / 1000
            prefix_index = prefix_index + 1
        counts_prefix = prefix[prefix_index]

        # Use qudi style
        plt.style.use(self._save_logic.mpl_qd_style)

        # Create figure
        fig, ax = plt.subplots()
        ax.plot(time_data, count_data, linestyle=':', linewidth=0.5)
        ax.set_xlabel('Time (s)')
        ax.set_ylabel('Fluorescence (' + counts_prefix + 'c/s)')
        return fig

    def set_counting_mode(self, mode='CONTINUOUS'):
        """Set the counting mode, to change between continuous and gated counting.
        Possible options are:
            'CONTINUOUS'    = counts continuously
            'GATED'         = bins the counts according to a gate signal
            'FINITE_GATED'  = finite measurement with predefined number of samples

        @return str: counting mode
        """
        constraints = self.get_hardware_constraints()
        if self.getState() != 'locked':
            if CountingMode[mode] in constraints.counting_mode:
                self._counting_mode = CountingMode[mode]
                self.log.debug('New counting mode: {}'.format(
                    self._counting_mode))
            else:
                self.log.warning(
                    'Counting mode not supported from hardware. Command ignored!'
                )
            self.sigCountingModeChanged.emit(self._counting_mode)
        else:
            self.log.error(
                'Cannot change counting mode while counter is still running.')
        return self._counting_mode

    def get_counting_mode(self):
        """ Retrieve the current counting mode.

        @return str: one of the possible counting options:
                'CONTINUOUS'    = counts continuously
                'GATED'         = bins the counts according to a gate signal
                'FINITE_GATED'  = finite measurement with predefined number of samples
        """
        return self._counting_mode

    # FIXME: Not implemented for self._counting_mode == 'gated'
    def startCount(self):
        """ This is called externally, and is basically a wrapper that
            redirects to the chosen counting mode start function.

            @return error: 0 is OK, -1 is error
        """
        # Sanity checks
        constraints = self.get_hardware_constraints()
        if self._counting_mode not in constraints.counting_mode:
            self.log.error(
                'Unknown counting mode "{0}". Cannot start the counter.'
                ''.format(self._counting_mode))
            self.sigCountStatusChanged.emit(False)
            return -1

        with self.threadlock:
            # Lock module
            if self.getState() != 'locked':
                self.lock()
            else:
                self.log.warning(
                    'Counter already running. Method call ignored.')
                return 0

            # Set up clock
            clock_status = self._counting_device.set_up_clock(
                clock_frequency=self._count_frequency)
            if clock_status < 0:
                self.unlock()
                self.sigCountStatusChanged.emit(False)
                return -1

            # Set up counter
            if self._counting_mode == CountingMode['FINITE_GATED']:
                counter_status = self._counting_device.set_up_counter(
                    counter_buffer=self._count_length)
            # elif self._counting_mode == CountingMode['GATED']:
            #
            else:
                counter_status = self._counting_device.set_up_counter()
            if counter_status < 0:
                self._counting_device.close_clock()
                self.unlock()
                self.sigCountStatusChanged.emit(False)
                return -1

            # initialising the data arrays
            self.rawdata = np.zeros(
                [len(self.get_channels()), self._counting_samples])
            self.countdata = np.zeros(
                [len(self.get_channels()), self._count_length])
            self.countdata_smoothed = np.zeros(
                [len(self.get_channels()), self._count_length])
            self._sampling_data = np.empty(
                [len(self.get_channels()), self._counting_samples])

            # the sample index for gated counting
            self._already_counted_samples = 0

            # Start data reader loop
            self.sigCountStatusChanged.emit(True)
            self.sigCountDataNext.emit()
            return

    def stopCount(self):
        """ Set a flag to request stopping counting.
        """
        if self.getState() == 'locked':
            with self.threadlock:
                self.stopRequested = True
        return

    def count_loop_body(self):
        """ This method gets the count data from the hardware for the continuous counting mode (default).

        It runs repeatedly in the logic module event loop by being connected
        to sigCountContinuousNext and emitting sigCountContinuousNext through a queued connection.
        """
        if self.getState() == 'locked':
            with self.threadlock:
                # check for aborts of the thread in break if necessary
                if self.stopRequested:
                    # close off the actual counter
                    cnt_err = self._counting_device.close_counter()
                    clk_err = self._counting_device.close_clock()
                    if cnt_err < 0 or clk_err < 0:
                        self.log.error(
                            'Could not even close the hardware, giving up.')
                    # switch the state variable off again
                    self.stopRequested = False
                    self.unlock()
                    self.sigCounterUpdated.emit()
                    return

                # read the current counter value
                self.rawdata = self._counting_device.get_counter(
                    samples=self._counting_samples)
                if self.rawdata[0, 0] < 0:
                    self.log.error(
                        'The counting went wrong, killing the counter.')
                    self.stopRequested = True
                else:
                    if self._counting_mode == CountingMode['CONTINUOUS']:
                        self._process_data_continous()
                    elif self._counting_mode == CountingMode['GATED']:
                        self._process_data_gated()
                    elif self._counting_mode == CountingMode['FINITE_GATED']:
                        self._process_data_finite_gated()
                    else:
                        self.log.error(
                            'No valid counting mode set! Can not process counter data.'
                        )

            # call this again from event loop
            self.sigCounterUpdated.emit()
            self.sigCountDataNext.emit()
        return

    def save_current_count_trace(self, name_tag=''):
        """ The currently displayed counttrace will be saved.

        @param str name_tag: optional, personal description that will be
                             appended to the file name

        @return: dict data: Data which was saved
                 str filepath: Filepath
                 dict parameters: Experiment parameters
                 str filelabel: Filelabel

        This method saves the already displayed counts to file and does not
        accumulate them. The counttrace variable will be saved to file with the
        provided name!
        """

        # If there is a postfix then add separating underscore
        if name_tag == '':
            filelabel = 'snapshot_count_trace'
        else:
            filelabel = 'snapshot_count_trace_' + name_tag

        stop_time = self._count_length / self._count_frequency
        time_step_size = stop_time / len(self.countdata)
        x_axis = np.arange(0, stop_time, time_step_size)

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        chans = self.get_channels()
        savearr = np.empty((len(chans) + 1, len(x_axis)))
        savearr[0] = x_axis
        datastr = 'Time (s)'

        for i, ch in enumerate(chans):
            savearr[i + 1] = self.countdata[i]
            datastr += ',Signal {0} (counts/s)'.format(i)

        data[datastr] = savearr.transpose()

        # write the parameters:
        parameters = OrderedDict()
        timestr = time.strftime('%d.%m.%Y %Hh:%Mmin:%Ss',
                                time.localtime(time.time()))
        parameters['Saved at time'] = timestr
        parameters['Count frequency (Hz)'] = self._count_frequency
        parameters['Oversampling (Samples)'] = self._counting_samples
        parameters[
            'Smooth Window Length (# of events)'] = self._smooth_window_length

        filepath = self._save_logic.get_path_for_module(module_name='Counter')
        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   delimiter='\t')

        self.log.debug('Current Counter Trace saved to: {0}'.format(filepath))
        return data, filepath, parameters, filelabel

    def get_channels(self):
        """ Shortcut for hardware get_counter_channels.

            @return list(str): return list of active counter channel names
        """
        return self._counting_device.get_counter_channels()

    def _process_data_continous(self):
        """
        Processes the raw data from the counting device
        @return:
        """
        for i, ch in enumerate(self.get_channels()):
            # remember the new count data in circular array
            self.countdata[i, 0] = np.average(self.rawdata[i])
        # move the array to the left to make space for the new data
        self.countdata = np.roll(self.countdata, -1, axis=1)
        # also move the smoothing array
        self.countdata_smoothed = np.roll(self.countdata_smoothed, -1, axis=1)
        # calculate the median and save it
        window = -int(self._smooth_window_length / 2) - 1
        for i, ch in enumerate(self.get_channels()):
            self.countdata_smoothed[i, window:] = np.median(
                self.countdata[i, -self._smooth_window_length:])

        # save the data if necessary
        if self._saving:
            # if oversampling is necessary
            if self._counting_samples > 1:
                chans = self.get_channels()
                self._sampling_data = np.empty(
                    [len(chans) + 1, self._counting_samples])
                self._sampling_data[
                    0, :] = time.time() - self._saving_start_time
                for i, ch in enumerate(chans):
                    self._sampling_data[i + 1, 0] = self.rawdata[i]

                self._data_to_save.extend(list(self._sampling_data))
            # if we don't want to use oversampling
            else:
                # append tuple to data stream (timestamp, average counts)
                chans = self.get_channels()
                newdata = np.empty((len(chans) + 1, ))
                newdata[0] = time.time() - self._saving_start_time
                for i, ch in enumerate(chans):
                    newdata[i + 1] = self.countdata[i, -1]
                self._data_to_save.append(newdata)
        return

    def _process_data_gated(self):
        """
        Processes the raw data from the counting device
        @return:
        """
        # remember the new count data in circular array
        self.countdata[0] = np.average(self.rawdata[0])
        # move the array to the left to make space for the new data
        self.countdata = np.roll(self.countdata, -1)
        # also move the smoothing array
        self.countdata_smoothed = np.roll(self.countdata_smoothed, -1)
        # calculate the median and save it
        self.countdata_smoothed[-int(self._smooth_window_length / 2) -
                                1:] = np.median(
                                    self.
                                    countdata[-self._smooth_window_length:])

        # save the data if necessary
        if self._saving:
            # if oversampling is necessary
            if self._counting_samples > 1:
                self._sampling_data = np.empty((self._counting_samples, 2))
                self._sampling_data[:,
                                    0] = time.time() - self._saving_start_time
                self._sampling_data[:, 1] = self.rawdata[0]
                self._data_to_save.extend(list(self._sampling_data))
            # if we don't want to use oversampling
            else:
                # append tuple to data stream (timestamp, average counts)
                self._data_to_save.append(
                    np.array((time.time() - self._saving_start_time,
                              self.countdata[-1])))
        return

    def _process_data_finite_gated(self):
        """
        Processes the raw data from the counting device
        @return:
        """
        if self._already_counted_samples + len(self.rawdata[0]) >= len(
                self.countdata):
            needed_counts = len(self.countdata) - self._already_counted_samples
            self.countdata[0:needed_counts] = self.rawdata[0][0:needed_counts]
            self.countdata = np.roll(self.countdata, -needed_counts)
            self._already_counted_samples = 0
            self.stopRequested = True
        else:
            # replace the first part of the array with the new data:
            self.countdata[0:len(self.rawdata[0])] = self.rawdata[0]
            # roll the array by the amount of data it had been inserted:
            self.countdata = np.roll(self.countdata, -len(self.rawdata[0]))
            # increment the index counter:
            self._already_counted_samples += len(self.rawdata[0])
        return

    def _stopCount_wait(self, timeout=5.0):
        """
        Stops the counter and waits until it actually has stopped.

        @param timeout: float, the max. time in seconds how long the method should wait for the
                        process to stop.

        @return: error code
        """
        self.stopCount()
        start_time = time.time()
        while self.getState() == 'locked':
            time.sleep(0.1)
            if time.time() - start_time >= timeout:
                self.log.error(
                    'Stopping the counter timed out after {0}s'.format(
                        timeout))
                return -1
        return 0
Пример #15
0
class QuickEditView(QtWidgets.QWidget):
    error_signal = QtCore.Signal(object)

    def __init__(self, subcontext, parent=None, default_msg="All"):
        super(QuickEditView, self).__init__(parent)
        self._default_selector_msg = default_msg
        button_layout = QtWidgets.QHBoxLayout()
        self.plot_selector = QtWidgets.QComboBox()
        self.plot_selector.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                         QtWidgets.QSizePolicy.Fixed)
        self.plot_selector.setSizeAdjustPolicy(
            QtWidgets.QComboBox.AdjustToContents)
        self.plot_selector.setMinimumContentsLength(12)
        self.plot_selector.setEditable(True)
        self.plot_selector.completer().setCompletionMode(
            QtWidgets.QCompleter.PopupCompletion)
        self.plot_selector.view().setMinimumWidth(100)

        self.plot_selector.completer().setFilterMode(QtCore.Qt.MatchContains)

        self.plot_selector.addItem(self._default_selector_msg)
        self.plot_selector.setEditable(False)
        self.x_axis_changer = AxisChangerWidget("X", self)

        self.autoscale = None
        self.autoscale = QtWidgets.QCheckBox("Autoscale y")
        self.autoscale.setToolTip(
            "While pan or zoom are enabled autoscale is disabled")

        self.y_axis_changer = AxisChangerWidget("Y", self)

        self.errors = QtWidgets.QCheckBox("Errors")
        self.errors.stateChanged.connect(self._emit_errors)

        button_layout.addWidget(self.plot_selector)
        button_layout.addWidget(self.x_axis_changer.view)
        button_layout.addWidget(self.autoscale)
        button_layout.addWidget(self.y_axis_changer.view)
        button_layout.addWidget(self.errors)
        self.setLayout(button_layout)

    @property
    def get_multiple_selection_name(self):
        return self._default_selector_msg

    """ plot selection """

    def disable_plot_selection(self):
        self.plot_selector.setEnabled(False)

    def add_subplot(self, name):
        self.plot_selector.blockSignals(True)
        self.plot_selector.addItem(name)
        self.plot_selector.adjustSize()
        self.plot_selector.blockSignals(False)

    def rm_subplot(self, index):
        self.plot_selector.removeItem(index)
        self.plot_selector.adjustSize()

    def current_selection(self):
        return self.plot_selector.currentText()

    def find_subplot(self, name):
        return self.plot_selector.findText(name)

    def set_selection(self, index: int):
        self.plot_selector.setCurrentIndex(index)

    def get_selection_index(self) -> int:
        return self.plot_selector.currentIndex()

    def plot_at_index(self, index):
        return self.plot_selector.itemText(index)

    def number_of_plots(self):
        return self.plot_selector.count()

    def clear_subplots(self):
        self.plot_selector.blockSignals(True)
        self.plot_selector.clear()
        self.plot_selector.addItem(self._default_selector_msg)
        self.plot_selector.blockSignals(False)

    def connect_plot_selection(self, slot):
        self.plot_selector.currentIndexChanged.connect(slot)

    """ x axis selection """

    def connect_x_range_changed(self, slot):
        self.x_axis_changer.on_range_changed(slot)

    def set_plot_x_range(self, limits):
        self.x_axis_changer.set_limits(limits)

    def get_x_bounds(self):
        return self.x_axis_changer.get_limits()

    """ y axis selection """

    def connect_y_range_changed(self, slot):
        self.y_axis_changer.on_range_changed(slot)

    def set_plot_y_range(self, limits):
        self.y_axis_changer.set_limits(limits)

    def get_y_bounds(self):
        return self.y_axis_changer.get_limits()

    def disable_yaxis_changer(self):
        self.y_axis_changer.view.setEnabled(False)

    def enable_yaxis_changer(self):
        self.y_axis_changer.view.setEnabled(True)

    """ auto scale selection """

    def connect_autoscale_changed(self, slot):
        self.autoscale.clicked.connect(slot)

    @property
    def autoscale_state(self):
        return self.autoscale.checkState()

    def disable_autoscale(self):
        self.autoscale.setEnabled(False)

    def enable_autoscale(self):
        self.autoscale.setEnabled(True)

    def set_autoscale(self, state: bool):
        self.autoscale.setChecked(state)

    def uncheck_autoscale(self):
        self.autoscale.setChecked(False)

    """ errors selection """

    # need our own signal that sends a bool

    def _emit_errors(self):
        state = self.get_errors()
        self.error_signal.emit(state)

    def connect_errors_changed(self, slot):
        self.error_signal.connect(slot)

    def set_errors(self, state):
        self.errors.setChecked(state)

    def get_errors(self):
        return self.errors.isChecked()
Пример #16
0
class BetterListCtrl(QW.QTreeWidget):

    columnListContentsChanged = QC.Signal()
    columnListStatusChanged = QC.Signal()

    def __init__(self,
                 parent,
                 column_list_type,
                 height_num_chars,
                 data_to_tuples_func,
                 use_simple_delete=False,
                 delete_key_callback=None,
                 activation_callback=None,
                 style=None,
                 column_types_to_name_overrides=None):

        QW.QTreeWidget.__init__(self, parent)

        self._have_shown_a_column_data_error = False

        self._creation_time = HydrusData.GetNow()

        self._column_list_type = column_list_type

        self._column_list_status: ClientGUIListStatus.ColumnListStatus = HG.client_controller.column_list_manager.GetStatus(
            self._column_list_type)
        self._original_column_list_status = self._column_list_status

        self.setAlternatingRowColors(True)
        self.setColumnCount(self._column_list_status.GetColumnCount())
        self.setSortingEnabled(
            False
        )  # Keeping the custom sort implementation. It would be better to use Qt's native sorting in the future so sort indicators are displayed on the headers as expected.
        self.setSelectionMode(QW.QAbstractItemView.ExtendedSelection)
        self.setRootIsDecorated(False)

        self._initial_height_num_chars = height_num_chars
        self._forced_height_num_chars = None

        self._data_to_tuples_func = data_to_tuples_func

        self._use_simple_delete = use_simple_delete

        self._menu_callable = None

        (self._sort_column_type,
         self._sort_asc) = self._column_list_status.GetSort()

        self._indices_to_data_info = {}
        self._data_to_indices = {}

        # old way
        '''
        #sizing_column_initial_width = self.fontMetrics().boundingRect( 'x' * sizing_column_initial_width_num_chars ).width()
        total_width = self.fontMetrics().boundingRect( 'x' * sizing_column_initial_width_num_chars ).width()
        
        resize_column = 1
        
        for ( i, ( name, width_num_chars ) ) in enumerate( columns ):
            
            if width_num_chars == -1:
                
                width = -1
                
                resize_column = i + 1
                
            else:
                
                width = self.fontMetrics().boundingRect( 'x' * width_num_chars ).width()
                
                total_width += width
                
            
            self.headerItem().setText( i, name )
            
            self.setColumnWidth( i, width )
            
        
        # Technically this is the previous behavior, but the two commented lines might work better in some cases (?)
        self.header().setStretchLastSection( False )
        self.header().setSectionResizeMode( resize_column - 1 , QW.QHeaderView.Stretch )
        #self.setColumnWidth( resize_column - 1, sizing_column_initial_width )
        #self.header().setStretchLastSection( True )
        
        self.setMinimumWidth( total_width )
        '''
        main_tlw = HG.client_controller.GetMainTLW()

        # if last section is set too low, for instance 3, the column seems unable to ever shrink from initial (expanded to fill space) size
        #  _    _  ___  _    _    __     __   ___
        # ( \/\/ )(  _)( \/\/ )  (  )   (  ) (   \
        #  \    /  ) _) \    /    )(__  /__\  ) ) )
        #   \/\/  (___)  \/\/    (____)(_)(_)(___/
        #
        # I think this is because of mismatch between set size and min size! So ensuring we never set smaller than that initially should fix this???!?

        MIN_SECTION_SIZE_CHARS = 3
        MIN_LAST_SECTION_SIZE_CHARS = 10

        self._min_section_width = ClientGUIFunctions.ConvertTextToPixelWidth(
            main_tlw, MIN_SECTION_SIZE_CHARS)

        self.header().setMinimumSectionSize(self._min_section_width)

        last_column_index = self._column_list_status.GetColumnCount() - 1

        for (i, column_type) in enumerate(
                self._column_list_status.GetColumnTypes()):

            self.headerItem().setData(i, QC.Qt.UserRole, column_type)

            if column_types_to_name_overrides is not None and column_type in column_types_to_name_overrides:

                name = column_types_to_name_overrides[column_type]

            else:

                name = CGLC.column_list_column_name_lookup[
                    self._column_list_type][column_type]

            self.headerItem().setText(i, name)
            self.headerItem().setToolTip(i, name)

            if i == last_column_index:

                width_chars = MIN_SECTION_SIZE_CHARS

            else:

                width_chars = self._column_list_status.GetColumnWidth(
                    column_type)

            width_chars = max(width_chars, MIN_SECTION_SIZE_CHARS)

            # ok this is a pain in the neck issue, but fontmetrics changes afte widget init. I guess font gets styled on top afterwards
            # this means that if I use this window's fontmetrics here, in init, then it is different later on, and we get creeping growing columns lmao
            # several other places in the client are likely affected in different ways by this also!
            width_pixels = ClientGUIFunctions.ConvertTextToPixelWidth(
                main_tlw, width_chars)

            self.setColumnWidth(i, width_pixels)

        self.header().setStretchLastSection(True)

        self._delete_key_callback = delete_key_callback
        self._activation_callback = activation_callback

        self._widget_event_filter = QP.WidgetEventFilter(self)
        self._widget_event_filter.EVT_KEY_DOWN(self.EventKeyDown)
        self.itemDoubleClicked.connect(self.EventItemActivated)

        self.header().setSectionsMovable(
            False)  # can only turn this on when we move from data/sort tuples
        # self.header().setFirstSectionMovable( True ) # same
        self.header().setSectionsClickable(True)
        self.header().sectionClicked.connect(self.EventColumnClick)

        #self.header().sectionMoved.connect( self._DoStatusChanged ) # same
        self.header().sectionResized.connect(self._SectionsResized)

    def _AddDataInfo(self, data_info):

        (data, display_tuple, sort_tuple) = data_info

        if data in self._data_to_indices:

            return

        append_item = QW.QTreeWidgetItem()

        for i in range(len(display_tuple)):

            text = display_tuple[i]

            if len(text) > 0:

                text = text.splitlines()[0]

            append_item.setText(i, text)
            append_item.setToolTip(i, text)

        self.addTopLevelItem(append_item)

        index = self.topLevelItemCount() - 1

        self._indices_to_data_info[index] = data_info
        self._data_to_indices[data] = index

    def _SectionsResized(self, logical_index, old_size, new_size):

        self._DoStatusChanged()

        self.updateGeometry()

    def _DoStatusChanged(self):

        self._column_list_status = self._GenerateCurrentStatus()

        HG.client_controller.column_list_manager.SaveStatus(
            self._column_list_status)

    def _GenerateCurrentStatus(self) -> ClientGUIListStatus.ColumnListStatus:

        status = ClientGUIListStatus.ColumnListStatus()

        status.SetColumnListType(self._column_list_type)

        main_tlw = HG.client_controller.GetMainTLW()

        columns = []

        header = self.header()

        num_columns = header.count()

        last_column_index = num_columns - 1

        # ok, the big pain in the ass situation here is getting a precise last column size that is reproduced on next dialog launch
        # ultimately, with fuzzy sizing, style padding, scrollbars appearing, and other weirdness, the more precisely we try to define it, the more we will get dialogs that grow/shrink by a pixel each time
        # *therefore*, the actual solution here is to move to snapping with a decent snap distance. the user loses size setting precision, but we'll snap back to a decent size every time, compensating for fuzz

        LAST_COLUMN_SNAP_DISTANCE_CHARS = 5

        for visual_index in range(num_columns):

            logical_index = header.logicalIndex(visual_index)

            column_type = self.headerItem().data(logical_index, QC.Qt.UserRole)
            width_pixels = header.sectionSize(logical_index)
            shown = not header.isSectionHidden(logical_index)

            if visual_index == last_column_index:

                if self.verticalScrollBar().isVisible():

                    width_pixels += max(
                        0, min(self.verticalScrollBar().width(), 20))

            width_chars = ClientGUIFunctions.ConvertPixelsToTextWidth(
                main_tlw, width_pixels)

            if visual_index == last_column_index:

                # here's the snap magic
                width_chars = round(
                    width_chars // LAST_COLUMN_SNAP_DISTANCE_CHARS
                ) * LAST_COLUMN_SNAP_DISTANCE_CHARS

            columns.append((column_type, width_chars, shown))

        status.SetColumns(columns)

        status.SetSort(self._sort_column_type, self._sort_asc)

        return status

    def _GetDisplayAndSortTuples(self, data):

        try:

            (display_tuple, sort_tuple) = self._data_to_tuples_func(data)

        except Exception as e:

            if not self._have_shown_a_column_data_error:

                HydrusData.ShowText(
                    'A multi-column list was unable to generate text or sort data for one or more rows! Please send hydrus dev the traceback!'
                )
                HydrusData.ShowException(e)

                self._have_shown_a_column_data_error = True

            error_display_tuple = [
                'unable to display'
                for i in range(self._column_list_status.GetColumnCount())
            ]

            return (error_display_tuple, None)

        better_sort = []

        for item in sort_tuple:

            if isinstance(item, str):

                item = HydrusData.HumanTextSortKey(item)

            better_sort.append(item)

        sort_tuple = tuple(better_sort)

        return (display_tuple, sort_tuple)

    def _GetSelected(self):

        indices = []

        for i in range(self.topLevelItemCount()):

            if self.topLevelItem(i).isSelected():

                indices.append(i)

        return indices

    def _RecalculateIndicesAfterDelete(self):

        indices_and_data_info = sorted(self._indices_to_data_info.items())

        self._indices_to_data_info = {}
        self._data_to_indices = {}

        for (index, (old_index,
                     data_info)) in enumerate(indices_and_data_info):

            (data, display_tuple, sort_tuple) = data_info

            self._data_to_indices[data] = index
            self._indices_to_data_info[index] = data_info

    def _ShowMenu(self):

        try:

            menu = self._menu_callable()

        except HydrusExceptions.DataMissing:

            return

        CGC.core().PopupMenu(self, menu)

    def _SortDataInfo(self):

        sort_column_index = self._column_list_status.GetColumnIndexFromType(
            self._sort_column_type)

        data_infos = list(self._indices_to_data_info.values())

        data_infos_good = [(data, display_tuple, sort_tuple)
                           for (data, display_tuple, sort_tuple) in data_infos
                           if sort_tuple is not None]
        data_infos_bad = [(data, display_tuple, sort_tuple)
                          for (data, display_tuple, sort_tuple) in data_infos
                          if sort_tuple is None]

        def sort_key(data_info):

            (data, display_tuple, sort_tuple) = data_info

            return (sort_tuple[sort_column_index], sort_tuple
                    )  # add the sort tuple to get secondary sorting

        try:

            data_infos_good.sort(key=sort_key, reverse=not self._sort_asc)

        except Exception as e:

            HydrusData.ShowText(
                'A multi-column list failed to sort! Please send hydrus dev the traceback!'
            )
            HydrusData.ShowException(e)

        data_infos_bad.extend(data_infos_good)

        data_infos = data_infos_bad

        return data_infos

    def _SortAndRefreshRows(self):

        selected_data_quick = set(self.GetData(only_selected=True))

        self.clearSelection()

        sorted_data_info = self._SortDataInfo()

        self._indices_to_data_info = {}
        self._data_to_indices = {}

        for (index, data_info) in enumerate(sorted_data_info):

            self._indices_to_data_info[index] = data_info

            (data, display_tuple, sort_tuple) = data_info

            self._data_to_indices[data] = index

            self._UpdateRow(index, display_tuple)

            if data in selected_data_quick:

                self.topLevelItem(index).setSelected(True)

    def _UpdateRow(self, index, display_tuple):

        for (column_index, value) in enumerate(display_tuple):

            if len(value) > 0:

                value = value.splitlines()[0]

            tree_widget_item = self.topLevelItem(index)

            existing_value = tree_widget_item.text(column_index)

            if existing_value != value:

                tree_widget_item.setText(column_index, value)
                tree_widget_item.setToolTip(column_index, value)

    def AddDatas(self, datas: typing.Iterable[object]):

        for data in datas:

            (display_tuple, sort_tuple) = self._GetDisplayAndSortTuples(data)

            self._AddDataInfo((data, display_tuple, sort_tuple))

        self.columnListContentsChanged.emit()

    def AddMenuCallable(self, menu_callable):

        self._menu_callable = menu_callable

        self.setContextMenuPolicy(QC.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.EventShowMenu)

    def DeleteDatas(self, datas: typing.Iterable[object]):

        deletees = [(self._data_to_indices[data], data) for data in datas]

        deletees.sort(reverse=True)

        # The below comment is most probably obsolote (from before the Qt port), but keeping it just in case it is not and also as an explanation.
        #
        # I am not sure, but I think if subsequent deleteitems occur in the same event, the event processing of the first is forced!!
        # this means that button checking and so on occurs for n-1 times on an invalid indices structure in this thing before correcting itself in the last one
        # if a button update then tests selected data against the invalid index and a selection is on the i+1 or whatever but just got bumped up into invalid area, we are exception city
        # this doesn't normally affect us because mostly we _are_ deleting selections when we do deletes, but 'try to link url stuff' auto thing hit this
        # I obviously don't want to recalc all indices for every delete
        # so I wrote a catch in getdata to skip the missing error, and now I'm moving the data deletion to a second loop, which seems to help

        for (index, data) in deletees:

            self.takeTopLevelItem(index)

        for (index, data) in deletees:

            del self._data_to_indices[data]

            del self._indices_to_data_info[index]

        self._RecalculateIndicesAfterDelete()

        self.columnListContentsChanged.emit()

    def DeleteSelected(self):

        indices = self._GetSelected()

        indices.sort(reverse=True)

        for index in indices:

            (data, display_tuple,
             sort_tuple) = self._indices_to_data_info[index]

            item = self.takeTopLevelItem(index)

            del item

            del self._data_to_indices[data]

            del self._indices_to_data_info[index]

        self._RecalculateIndicesAfterDelete()

        self.columnListContentsChanged.emit()

    def EventColumnClick(self, col):

        sort_column_type = self._column_list_status.GetColumnTypeFromIndex(col)

        if sort_column_type == self._sort_column_type:

            self._sort_asc = not self._sort_asc

        else:

            self._sort_column_type = sort_column_type

            self._sort_asc = True

        self._SortAndRefreshRows()

        self._DoStatusChanged()

    def EventItemActivated(self, item, column):

        if self._activation_callback is not None:

            self._activation_callback()

    def EventKeyDown(self, event):

        (modifier,
         key) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple(event)

        if key in ClientGUIShortcuts.DELETE_KEYS_QT:

            self.ProcessDeleteAction()

        elif key in (ord('A'), ord('a')) and modifier == QC.Qt.ControlModifier:

            self.selectAll()

        else:

            return True  # was: event.ignore()

    def EventShowMenu(self):

        QP.CallAfter(self._ShowMenu)

    def ForceHeight(self, rows):

        self._forced_height_num_chars = rows

        self.updateGeometry()

        # +2 for the header row and * 1.25 for magic rough text-to-rowheight conversion

        #existing_min_width = self.minimumWidth()

        #( width_gumpf, ideal_client_height ) = ClientGUIFunctions.ConvertTextToPixels( self, ( 20, int( ( ideal_rows + 2 ) * 1.25 ) ) )

        #QP.SetMinClientSize( self, ( existing_min_width, ideal_client_height ) )

    def GetData(self, only_selected=False):

        if only_selected:

            indices = self._GetSelected()

        else:

            indices = list(self._indices_to_data_info.keys())

        result = []

        for index in indices:

            # this can get fired while indices are invalid, wew
            if index not in self._indices_to_data_info:

                continue

            (data, display_tuple,
             sort_tuple) = self._indices_to_data_info[index]

            result.append(data)

        return result

    def HasData(self, data: object):

        return data in self._data_to_indices

    def HasOneSelected(self):

        return len(self.selectedItems()) == 1

    def HasSelected(self):

        return len(self.selectedItems()) > 0

    def ProcessDeleteAction(self):

        if self._use_simple_delete:

            self.ShowDeleteSelectedDialog()

        elif self._delete_key_callback is not None:

            self._delete_key_callback()

    def SelectDatas(self, datas: typing.Iterable[object]):

        for data in datas:

            if data in self._data_to_indices:

                index = self._data_to_indices[data]

                self.topLevelItem(index).setSelected(True)

    def SetData(self, datas: typing.Iterable[object]):

        existing_datas = set(self._data_to_indices.keys())

        # useful to preserve order here sometimes (e.g. export file path generation order)
        datas_to_add = [data for data in datas if data not in existing_datas]
        datas_to_update = [data for data in datas if data in existing_datas]
        datas_to_delete = existing_datas.difference(datas)

        if len(datas_to_delete) > 0:

            self.DeleteDatas(datas_to_delete)

        if len(datas_to_update) > 0:

            self.UpdateDatas(datas_to_update)

        if len(datas_to_add) > 0:

            self.AddDatas(datas_to_add)

        self._SortAndRefreshRows()

        self.columnListContentsChanged.emit()

    def ShowDeleteSelectedDialog(self):

        from hydrus.client.gui import ClientGUIDialogsQuick

        result = ClientGUIDialogsQuick.GetYesNo(self, 'Remove all selected?')

        if result == QW.QDialog.Accepted:

            self.DeleteSelected()

    def _GetRowHeightEstimate(self):

        if self.topLevelItemCount() > 0:

            height = self.rowHeight(self.indexFromItem(self.topLevelItem(0)))

        else:

            (width_gumpf,
             height) = ClientGUIFunctions.ConvertTextToPixels(self, (20, 1))

        return height

    def minimumSizeHint(self):

        width = 0

        for i in range(self.columnCount() - 1):

            width += self.columnWidth(i)

        width += self._min_section_width  # the last column

        width += self.frameWidth() * 2

        if self._forced_height_num_chars is None:

            min_num_rows = 4

        else:

            min_num_rows = self._forced_height_num_chars

        header_size = self.header().sizeHint(
        )  # this is better than min size hint for some reason ?( 69, 69 )?

        data_area_height = self._GetRowHeightEstimate() * min_num_rows

        PADDING = 10

        min_size_hint = QC.QSize(
            width,
            header_size.height() + data_area_height + PADDING)

        return min_size_hint

    def resizeEvent(self, event):

        self._DoStatusChanged()

        return QW.QTreeWidget.resizeEvent(self, event)

    def sizeHint(self):

        width = 0

        # all but last column

        for i in range(self.columnCount() - 1):

            width += self.columnWidth(i)

        #

        # ok, we are going full slippery dippery doo now
        # the issue is: when we first boot up, we want to give a 'hey, it would be nice' size of the last actual recorded final column
        # HOWEVER, after that: we want to use the current size of the last column
        # so, if it is the first couple of seconds, lmao. after that, oaml
        # I later updated this to use the columnWidth, rather than hickery dickery text-to-pixel-width, since it was juddering resize around text width phase

        last_column_type = self._column_list_status.GetColumnTypes()[-1]

        if HydrusData.TimeHasPassed(self._creation_time + 2):

            width += self.columnWidth(self.columnCount() - 1)

        else:

            last_column_chars = self._original_column_list_status.GetColumnWidth(
                last_column_type)

            main_tlw = HG.client_controller.GetMainTLW()

            width += ClientGUIFunctions.ConvertTextToPixelWidth(
                main_tlw, last_column_chars)

        #

        width += self.frameWidth() * 2

        if self._forced_height_num_chars is None:

            num_rows = self._initial_height_num_chars

        else:

            num_rows = self._forced_height_num_chars

        header_size = self.header().sizeHint()

        data_area_height = self._GetRowHeightEstimate() * num_rows

        PADDING = 10

        size_hint = QC.QSize(width,
                             header_size.height() + data_area_height + PADDING)

        return size_hint

    def Sort(self, sort_column_type=None, sort_asc=None):

        if sort_column_type is not None:

            self._sort_column_type = sort_column_type

        if sort_asc is not None:

            self._sort_asc = sort_asc

        self._SortAndRefreshRows()

        self.columnListContentsChanged.emit()

        self._DoStatusChanged()

    def UpdateDatas(self,
                    datas: typing.Optional[typing.Iterable[object]] = None):

        if datas is None:

            # keep it sorted here, which is sometimes useful

            indices_and_datas = sorted(
                ((index, data)
                 for (data, index) in self._data_to_indices.items()))

            datas = [data for (index, data) in indices_and_datas]

        sort_data_has_changed = False
        sort_index = self._column_list_status.GetColumnIndexFromType(
            self._sort_column_type)

        for data in datas:

            (display_tuple, sort_tuple) = self._GetDisplayAndSortTuples(data)

            data_info = (data, display_tuple, sort_tuple)

            index = self._data_to_indices[data]

            existing_data_info = self._indices_to_data_info[index]

            if data_info != existing_data_info:

                if not sort_data_has_changed:

                    (existing_data, existing_display_tuple,
                     existing_sort_tuple) = existing_data_info

                    if existing_sort_tuple is not None and sort_tuple is not None:

                        # this does not govern secondary sorts, but let's not spam sorts m8
                        if sort_tuple[sort_index] != existing_sort_tuple[
                                sort_index]:

                            sort_data_has_changed = True

                self._indices_to_data_info[index] = data_info

                self._UpdateRow(index, display_tuple)

        self.columnListContentsChanged.emit()

        return sort_data_has_changed

    def SetNonDupeName(self, obj: object):

        current_names = {o.GetName() for o in self.GetData() if o is not obj}

        HydrusSerialisable.SetNonDupeName(obj, current_names)

    def ReplaceData(self, old_data: object, new_data: object):

        new_data = QP.ListsToTuples(new_data)

        data_index = self._data_to_indices[old_data]

        (display_tuple, sort_tuple) = self._GetDisplayAndSortTuples(new_data)

        data_info = (new_data, display_tuple, sort_tuple)

        self._indices_to_data_info[data_index] = data_info

        del self._data_to_indices[old_data]

        self._data_to_indices[new_data] = data_index

        self._UpdateRow(data_index, display_tuple)
Пример #17
0
class CounterGui(GUIBase):
    """ FIXME: Please document
    """

    # declare connectors
    counterlogic1 = Connector(interface='CounterLogic')

    sigStartCounter = QtCore.Signal()
    sigStopCounter = QtCore.Signal()

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Definition and initialisation of the GUI.
        """
        self._counting_logic = self.counterlogic1()

        #####################
        # Configuring the dock widgets
        # Use the inherited class 'CounterMainWindow' to create the GUI window
        self._mw = CounterMainWindow()

        # Setup dock widgets
        self._mw.centralwidget.hide()
        self._mw.trace_selection_DockWidget.hide()
        self._mw.setDockNestingEnabled(True)

        # Plot labels.
        self._pw = self._mw.counter_trace_PlotWidget

        self._pw.setLabel('left', 'Fluorescence', units='counts/s')
        self._pw.setLabel('bottom', 'Time', units='s')

        self.curves = []

        for i, ch in enumerate(self._counting_logic.get_channels()):
            if i % 2 == 0:
                # Create an empty plot curve to be filled later, set its pen
                self.curves.append(
                    pg.PlotDataItem(pen=pg.mkPen(palette.c1), symbol=None))
                self._pw.addItem(self.curves[-1])
                self.curves.append(
                    pg.PlotDataItem(pen=pg.mkPen(palette.c2, width=3),
                                    symbol=None))
                self._pw.addItem(self.curves[-1])
            else:
                self.curves.append(
                    pg.PlotDataItem(pen=pg.mkPen(palette.c3,
                                                 style=QtCore.Qt.DotLine),
                                    symbol='s',
                                    symbolPen=palette.c3,
                                    symbolBrush=palette.c3,
                                    symbolSize=5))
                self._pw.addItem(self.curves[-1])
                self.curves.append(
                    pg.PlotDataItem(pen=pg.mkPen(palette.c4, width=3),
                                    symbol=None))
                self._pw.addItem(self.curves[-1])

        # setting the x axis length correctly
        self._pw.setXRange(
            0,
            self._counting_logic.get_count_length() /
            self._counting_logic.get_count_frequency())

        #####################
        # Setting default parameters
        self._mw.count_length_SpinBox.setValue(
            self._counting_logic.get_count_length())
        self._mw.count_freq_SpinBox.setValue(
            self._counting_logic.get_count_frequency())
        self._mw.oversampling_SpinBox.setValue(
            self._counting_logic.get_counting_samples())
        self._display_trace = 1
        self._trace_selection = [True, True, True, True]

        #####################
        # Connecting user interactions
        self._mw.start_counter_Action.triggered.connect(self.start_clicked)
        self._mw.record_counts_Action.triggered.connect(self.save_clicked)

        self._mw.count_length_SpinBox.valueChanged.connect(
            self.count_length_changed)
        self._mw.count_freq_SpinBox.valueChanged.connect(
            self.count_frequency_changed)
        self._mw.oversampling_SpinBox.valueChanged.connect(
            self.oversampling_changed)

        if len(self.curves) >= 2:
            self._mw.trace_1_checkbox.setChecked(True)
        else:
            self._mw.trace_1_checkbox.setEnabled(False)
            self._mw.trace_1_radiobutton.setEnabled(False)

        if len(self.curves) >= 4:
            self._mw.trace_2_checkbox.setChecked(True)
        else:
            self._mw.trace_2_checkbox.setEnabled(False)
            self._mw.trace_2_radiobutton.setEnabled(False)

        if len(self.curves) >= 6:
            self._mw.trace_3_checkbox.setChecked(True)
        else:
            self._mw.trace_3_checkbox.setEnabled(False)
            self._mw.trace_3_radiobutton.setEnabled(False)

        if len(self.curves) >= 8:
            self._mw.trace_4_checkbox.setChecked(True)
        else:
            self._mw.trace_4_checkbox.setEnabled(False)
            self._mw.trace_4_radiobutton.setEnabled(False)

        self._mw.trace_1_checkbox.stateChanged.connect(
            self.trace_selection_changed)
        self._mw.trace_2_checkbox.stateChanged.connect(
            self.trace_selection_changed)
        self._mw.trace_3_checkbox.stateChanged.connect(
            self.trace_selection_changed)
        self._mw.trace_4_checkbox.stateChanged.connect(
            self.trace_selection_changed)

        self._mw.trace_1_radiobutton.setChecked(True)
        self._mw.trace_1_radiobutton.released.connect(
            self.trace_display_changed)
        self._mw.trace_2_radiobutton.released.connect(
            self.trace_display_changed)
        self._mw.trace_3_radiobutton.released.connect(
            self.trace_display_changed)
        self._mw.trace_4_radiobutton.released.connect(
            self.trace_display_changed)

        # Connect the default view action
        self._mw.restore_default_view_Action.triggered.connect(
            self.restore_default_view)

        #####################
        # starting the physical measurement
        self.sigStartCounter.connect(self._counting_logic.startCount)
        self.sigStopCounter.connect(self._counting_logic.stopCount)

        ##################
        # Handling signals from the logic

        self._counting_logic.sigCounterUpdated.connect(self.updateData)

        # ToDo:
        # self._counting_logic.sigCountContinuousNext.connect()
        # self._counting_logic.sigCountGatedNext.connect()
        # self._counting_logic.sigCountFiniteGatedNext.connect()
        # self._counting_logic.sigGatedCounterFinished.connect()
        # self._counting_logic.sigGatedCounterContinue.connect()

        self._counting_logic.sigCountingSamplesChanged.connect(
            self.update_oversampling_SpinBox)
        self._counting_logic.sigCountLengthChanged.connect(
            self.update_count_length_SpinBox)
        self._counting_logic.sigCountFrequencyChanged.connect(
            self.update_count_freq_SpinBox)
        self._counting_logic.sigSavingStatusChanged.connect(
            self.update_saving_Action)
        self._counting_logic.sigCountingModeChanged.connect(
            self.update_counting_mode_ComboBox)
        self._counting_logic.sigCountStatusChanged.connect(
            self.update_count_status_Action)

        # Throw a deprecation warning pop-up to encourage users to switch to
        # TimeSeriesGui/TimeSeriesReaderLogic
        dialog = QtWidgets.QDialog(self._mw)
        dialog.setWindowTitle('Deprecation warning')
        label1 = QtWidgets.QLabel('Deprecation Warning:')
        label1.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
        font = label1.font()
        font.setPointSize(12)
        label1.setFont(font)
        label2 = QtWidgets.QLabel(
            'The modules CounterGui, CounterLogic and '
            'NationalInstrumentsXSeries are deprecated for time series '
            'streaming (also called "slow counting") and will be removed in '
            'the future.\nPlease consider switching to TimeSeriesGui, '
            'TimeSeriesReaderLogic and NIXSeriesInStreamer.\nSee default.cfg '
            'for a configuration template.')
        label2.setAlignment(QtCore.Qt.AlignVCenter)
        label2.setWordWrap(True)
        button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok)
        button_box.setCenterButtons(True)
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(label1)
        layout.addWidget(label2)
        layout.addWidget(button_box)
        button_box.accepted.connect(dialog.accept)
        dialog.setLayout(layout)
        dialog.exec()
        return 0

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()
        return

    def on_deactivate(self):
        # FIXME: !
        """ Deactivate the module
        """
        # disconnect signals
        self._mw.start_counter_Action.triggered.disconnect()
        self._mw.record_counts_Action.triggered.disconnect()
        self._mw.count_length_SpinBox.valueChanged.disconnect()
        self._mw.count_freq_SpinBox.valueChanged.disconnect()
        self._mw.oversampling_SpinBox.valueChanged.disconnect()
        self._mw.trace_1_checkbox.stateChanged.disconnect()
        self._mw.trace_2_checkbox.stateChanged.disconnect()
        self._mw.trace_3_checkbox.stateChanged.disconnect()
        self._mw.trace_4_checkbox.stateChanged.disconnect()
        self._mw.restore_default_view_Action.triggered.disconnect()
        self.sigStartCounter.disconnect()
        self.sigStopCounter.disconnect()
        self._counting_logic.sigCounterUpdated.disconnect()
        self._counting_logic.sigCountingSamplesChanged.disconnect()
        self._counting_logic.sigCountLengthChanged.disconnect()
        self._counting_logic.sigCountFrequencyChanged.disconnect()
        self._counting_logic.sigSavingStatusChanged.disconnect()
        self._counting_logic.sigCountingModeChanged.disconnect()
        self._counting_logic.sigCountStatusChanged.disconnect()

        self._mw.close()
        return

    def updateData(self):
        """ The function that grabs the data and sends it to the plot.
        """

        if self._counting_logic.module_state() == 'locked':
            if 0 < self._counting_logic.countdata_smoothed[(
                    self._display_trace - 1), -1] < 10:
                self._mw.count_value_Label.setText('{0:,.6f}'.format(
                    self._counting_logic.countdata_smoothed[(
                        self._display_trace - 1), -1]))
            else:
                self._mw.count_value_Label.setText('{0:,.0f}'.format(
                    self._counting_logic.countdata_smoothed[(
                        self._display_trace - 1), -1]))

            x_vals = (np.arange(0, self._counting_logic.get_count_length()) /
                      self._counting_logic.get_count_frequency())

            ymax = -1
            ymin = 2000000000
            for i, ch in enumerate(self._counting_logic.get_channels()):
                self.curves[2 * i].setData(y=self._counting_logic.countdata[i],
                                           x=x_vals)
                self.curves[2 * i + 1].setData(
                    y=self._counting_logic.countdata_smoothed[i], x=x_vals)
                if ymax < self._counting_logic.countdata[i].max(
                ) and self._trace_selection[i]:
                    ymax = self._counting_logic.countdata[i].max()
                if ymin > self._counting_logic.countdata[i].min(
                ) and self._trace_selection[i]:
                    ymin = self._counting_logic.countdata[i].min()

            if ymin == ymax:
                ymax += 0.1
            self._pw.setYRange(0.95 * ymin, 1.05 * ymax)

        if self._counting_logic.get_saving_state():
            self._mw.record_counts_Action.setText('Save')
            self._mw.count_freq_SpinBox.setEnabled(False)
            self._mw.oversampling_SpinBox.setEnabled(False)
        else:
            self._mw.record_counts_Action.setText('Start Saving Data')
            self._mw.count_freq_SpinBox.setEnabled(True)
            self._mw.oversampling_SpinBox.setEnabled(True)

        if self._counting_logic.module_state() == 'locked':
            self._mw.start_counter_Action.setText('Stop counter')
            self._mw.start_counter_Action.setChecked(True)
        else:
            self._mw.start_counter_Action.setText('Start counter')
            self._mw.start_counter_Action.setChecked(False)
        return 0

    def start_clicked(self):
        """ Handling the Start button to stop and restart the counter.
        """
        if self._counting_logic.module_state() == 'locked':
            self._mw.start_counter_Action.setText('Start counter')
            self.sigStopCounter.emit()
        else:
            self._mw.start_counter_Action.setText('Stop counter')
            self.sigStartCounter.emit()
        return self._counting_logic.module_state()

    def save_clicked(self):
        """ Handling the save button to save the data into a file.
        """
        if self._counting_logic.get_saving_state():
            self._mw.record_counts_Action.setText('Start Saving Data')
            self._mw.count_freq_SpinBox.setEnabled(True)
            self._mw.oversampling_SpinBox.setEnabled(True)
            self._counting_logic.save_data()
        else:
            self._mw.record_counts_Action.setText('Save')
            self._mw.count_freq_SpinBox.setEnabled(False)
            self._mw.oversampling_SpinBox.setEnabled(False)
            self._counting_logic.start_saving()
        return self._counting_logic.get_saving_state()

    ########
    # Input parameters changed via GUI

    def trace_selection_changed(self):
        """ Handling any change to the selection of the traces to display.
        """
        if self._mw.trace_1_checkbox.isChecked():
            self._trace_selection[0] = True
        else:
            self._trace_selection[0] = False
        if self._mw.trace_2_checkbox.isChecked():
            self._trace_selection[1] = True
        else:
            self._trace_selection[1] = False
        if self._mw.trace_3_checkbox.isChecked():
            self._trace_selection[2] = True
        else:
            self._trace_selection[2] = False
        if self._mw.trace_4_checkbox.isChecked():
            self._trace_selection[3] = True
        else:
            self._trace_selection[3] = False

        for i, ch in enumerate(self._counting_logic.get_channels()):
            if self._trace_selection[i]:
                self._pw.addItem(self.curves[2 * i])
                self._pw.addItem(self.curves[2 * i + 1])
            else:
                self._pw.removeItem(self.curves[2 * i])
                self._pw.removeItem(self.curves[2 * i + 1])

    def trace_display_changed(self):
        """ Handling of a change in teh selection of which counts should be shown.
        """

        if self._mw.trace_1_radiobutton.isChecked():
            self._display_trace = 1
        elif self._mw.trace_2_radiobutton.isChecked():
            self._display_trace = 2
        elif self._mw.trace_3_radiobutton.isChecked():
            self._display_trace = 3
        elif self._mw.trace_4_radiobutton.isChecked():
            self._display_trace = 4
        else:
            self._display_trace = 1

    def count_length_changed(self):
        """ Handling the change of the count_length and sending it to the measurement.
        """
        self._counting_logic.set_count_length(
            self._mw.count_length_SpinBox.value())
        self._pw.setXRange(
            0,
            self._counting_logic.get_count_length() /
            self._counting_logic.get_count_frequency())
        return self._mw.count_length_SpinBox.value()

    def count_frequency_changed(self):
        """ Handling the change of the count_frequency and sending it to the measurement.
        """
        self._counting_logic.set_count_frequency(
            self._mw.count_freq_SpinBox.value())
        self._pw.setXRange(
            0,
            self._counting_logic.get_count_length() /
            self._counting_logic.get_count_frequency())
        return self._mw.count_freq_SpinBox.value()

    def oversampling_changed(self):
        """ Handling the change of the oversampling and sending it to the measurement.
        """
        self._counting_logic.set_counting_samples(
            samples=self._mw.oversampling_SpinBox.value())
        self._pw.setXRange(
            0,
            self._counting_logic.get_count_length() /
            self._counting_logic.get_count_frequency())
        return self._mw.oversampling_SpinBox.value()

    ########
    # Restore default values

    def restore_default_view(self):
        """ Restore the arrangement of DockWidgets to the default
        """
        # Show any hidden dock widgets
        self._mw.counter_trace_DockWidget.show()
        # self._mw.slow_counter_control_DockWidget.show()
        self._mw.slow_counter_parameters_DockWidget.show()
        self._mw.trace_selection_DockWidget.hide()

        # re-dock any floating dock widgets
        self._mw.counter_trace_DockWidget.setFloating(False)
        self._mw.slow_counter_parameters_DockWidget.setFloating(False)
        self._mw.trace_selection_DockWidget.setFloating(True)

        # Arrange docks widgets
        self._mw.addDockWidget(QtCore.Qt.DockWidgetArea(1),
                               self._mw.counter_trace_DockWidget)
        self._mw.addDockWidget(QtCore.Qt.DockWidgetArea(8),
                               self._mw.slow_counter_parameters_DockWidget)
        self._mw.addDockWidget(
            QtCore.Qt.DockWidgetArea(QtCore.Qt.LeftDockWidgetArea),
            self._mw.trace_selection_DockWidget)

        # Set the toolbar to its initial top area
        self._mw.addToolBar(QtCore.Qt.TopToolBarArea,
                            self._mw.counting_control_ToolBar)
        return 0

    ##########
    # Handle signals from logic

    def update_oversampling_SpinBox(self, oversampling):
        """Function to ensure that the GUI displays the current value of the logic

        @param int oversampling: adjusted oversampling to update in the GUI in bins
        @return int oversampling: see above
        """
        self._mw.oversampling_SpinBox.blockSignals(True)
        self._mw.oversampling_SpinBox.setValue(oversampling)
        self._mw.oversampling_SpinBox.blockSignals(False)
        return oversampling

    def update_count_freq_SpinBox(self, count_freq):
        """Function to ensure that the GUI displays the current value of the logic

        @param float count_freq: adjusted count frequency in Hz
        @return float count_freq: see above
        """
        self._mw.count_freq_SpinBox.blockSignals(True)
        self._mw.count_freq_SpinBox.setValue(count_freq)
        self._pw.setXRange(
            0,
            self._counting_logic.get_count_length() / count_freq)
        self._mw.count_freq_SpinBox.blockSignals(False)
        return count_freq

    def update_count_length_SpinBox(self, count_length):
        """Function to ensure that the GUI displays the current value of the logic

        @param int count_length: adjusted count length in bins
        @return int count_length: see above
        """
        self._mw.count_length_SpinBox.blockSignals(True)
        self._mw.count_length_SpinBox.setValue(count_length)
        self._pw.setXRange(
            0, count_length / self._counting_logic.get_count_frequency())
        self._mw.count_length_SpinBox.blockSignals(False)
        return count_length

    def update_saving_Action(self, start):
        """Function to ensure that the GUI-save_action displays the current status

        @param bool start: True if the measurment saving is started
        @return bool start: see above
        """
        if start:
            self._mw.record_counts_Action.setText('Save')
            self._mw.count_freq_SpinBox.setEnabled(False)
            self._mw.oversampling_SpinBox.setEnabled(False)
        else:
            self._mw.record_counts_Action.setText('Start Saving Data')
            self._mw.count_freq_SpinBox.setEnabled(True)
            self._mw.oversampling_SpinBox.setEnabled(True)
        return start

    def update_count_status_Action(self, running):
        """Function to ensure that the GUI-save_action displays the current status

        @param bool running: True if the counting is started
        @return bool running: see above
        """
        if running:
            self._mw.start_counter_Action.setText('Stop counter')
        else:
            self._mw.start_counter_Action.setText('Start counter')
        return running

    # TODO:
    def update_counting_mode_ComboBox(self):
        self.log.warning('Not implemented yet')
        return 0

    # TODO:
    def update_smoothing_ComboBox(self):
        self.log.warning('Not implemented yet')
        return 0
Пример #18
0
class DataViewer(ViewerBase, QtWidgets.QMainWindow):
    """
    Base class for all Qt DataViewer widgets.

    This defines a minimal interface, and implemlements the following::

       * An automatic call to unregister on window close
       * Drag and drop support for adding data
    """

    window_closed = QtCore.Signal()

    _layer_artist_container_cls = QtLayerArtistContainer
    _layer_style_widget_cls = None

    LABEL = 'Override this'

    _toolbar_cls = None
    tools = []

    def __init__(self, session, parent=None):
        """
        :type session: :class:`~glue.core.Session`
        """
        QtWidgets.QMainWindow.__init__(self, parent)
        ViewerBase.__init__(self, session)
        self.setWindowIcon(get_qapp().windowIcon())
        self._view = LayerArtistWidget(
            layer_style_widget_cls=self._layer_style_widget_cls,
            hub=session.hub)
        self._view.layer_list.setModel(self._layer_artist_container.model)
        self._tb_vis = {}  # store whether toolbars are enabled
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAcceptDrops(True)
        self.setAnimated(False)
        self._toolbars = []
        self._warn_close = True
        self.setContentsMargins(2, 2, 2, 2)
        self._mdi_wrapper = None  # GlueMdiSubWindow that self is embedded in
        self.statusBar().setStyleSheet("QStatusBar{font-size:10px}")

        # close window when last plot layer deleted
        self._layer_artist_container.on_empty(lambda: self.close(warn=False))
        self._layer_artist_container.on_changed(self.update_window_title)

    @property
    def selected_layer(self):
        return self._view.layer_list.current_artist()

    def remove_layer(self, layer):
        self._layer_artist_container.pop(layer)

    def dragEnterEvent(self, event):
        """ Accept the event if it has data layers"""
        if event.mimeData().hasFormat(LAYER_MIME_TYPE):
            event.accept()
        elif event.mimeData().hasFormat(LAYERS_MIME_TYPE):
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """ Add layers to the viewer if contained in mime data """
        if event.mimeData().hasFormat(LAYER_MIME_TYPE):
            self.request_add_layer(event.mimeData().data(LAYER_MIME_TYPE))

        assert event.mimeData().hasFormat(LAYERS_MIME_TYPE)

        for layer in event.mimeData().data(LAYERS_MIME_TYPE):
            self.request_add_layer(layer)

        event.accept()

    def mousePressEvent(self, event):
        """ Consume mouse press events, and prevent them from propagating
            down to the MDI area """
        event.accept()

    apply_roi = set_cursor(Qt.WaitCursor)(ViewerBase.apply_roi)

    def close(self, warn=True):
        self._warn_close = warn
        QtWidgets.QMainWindow.close(self)
        ViewerBase.close(self)
        self._warn_close = True

    def mdi_wrap(self):
        """Wrap this object in a GlueMdiSubWindow"""
        from glue.app.qt.mdi_area import GlueMdiSubWindow
        sub = GlueMdiSubWindow()
        sub.setWidget(self)
        self.destroyed.connect(sub.close)
        sub.resize(self.size())
        self._mdi_wrapper = sub

        return sub

    @property
    def position(self):
        target = self._mdi_wrapper or self
        pos = target.pos()
        return pos.x(), pos.y()

    @position.setter
    def position(self, xy):
        x, y = xy
        self.move(x, y)

    def move(self, x=None, y=None):
        """
        Move the viewer to a new XY pixel location

        You can also set the position attribute to a new tuple directly.

        Parameters
        ----------
        x : int (optional)
           New x position
        y : int (optional)
           New y position
        """
        x0, y0 = self.position
        if x is None:
            x = x0
        if y is None:
            y = y0
        if self._mdi_wrapper is not None:
            self._mdi_wrapper.move(x, y)
        else:
            QtWidgets.QMainWindow.move(self, x, y)

    @property
    def viewer_size(self):
        if self._mdi_wrapper is not None:
            sz = self._mdi_wrapper.size()
        else:
            sz = self.size()
        return sz.width(), sz.height()

    @viewer_size.setter
    def viewer_size(self, value):
        width, height = value
        self.resize(width, height)
        if self._mdi_wrapper is not None:
            self._mdi_wrapper.resize(width, height)

    def closeEvent(self, event):
        """ Call unregister on window close """

        if not self._confirm_close():
            event.ignore()
            return

        if self._hub is not None:
            self.unregister(self._hub)

        self._layer_artist_container.clear_callbacks()
        self._layer_artist_container.clear()

        super(DataViewer, self).closeEvent(event)
        event.accept()

        self.window_closed.emit()

    def _confirm_close(self):
        """Ask for close confirmation

        :rtype: bool. True if user wishes to close. False otherwise
        """
        if self._warn_close and (
                not os.environ.get('GLUE_TESTING')) and self.isVisible():
            buttons = QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
            dialog = QtWidgets.QMessageBox.warning(
                self,
                "Confirm Close",
                "Do you want to close this window?",
                buttons=buttons,
                defaultButton=QtWidgets.QMessageBox.Cancel)
            return dialog == QtWidgets.QMessageBox.Ok
        return True

    def _confirm_large_data(self, data):
        if not settings.SHOW_LARGE_DATA_WARNING:
            # Ignoring large data warning
            return True
        else:
            warn_msg = (
                "WARNING: Data set has %i points, and may render slowly."
                " Continue?" % data.size)
            title = "Add large data set?"
            ok = QtWidgets.QMessageBox.Ok
            cancel = QtWidgets.QMessageBox.Cancel
            buttons = ok | cancel
            result = QtWidgets.QMessageBox.question(self,
                                                    title,
                                                    warn_msg,
                                                    buttons=buttons,
                                                    defaultButton=cancel)
            return result == ok

    def layer_view(self):
        return self._view

    def options_widget(self):
        return QtWidgets.QWidget()

    def addToolBar(self, tb):
        super(DataViewer, self).addToolBar(tb)
        self._toolbars.append(tb)
        self._tb_vis[tb] = True

    def initialize_toolbar(self):

        from glue.config import viewer_tool

        self.toolbar = self._toolbar_cls(self)

        for tool_id in self.tools:
            mode_cls = viewer_tool.members[tool_id]
            mode = mode_cls(self)
            self.toolbar.add_tool(mode)

        self.addToolBar(self.toolbar)

    def show_toolbars(self):
        """Re-enable any toolbars that were hidden with `hide_toolbars()`

        Does not re-enable toolbars that were hidden by other means
        """
        for tb in self._toolbars:
            if self._tb_vis.get(tb, False):
                tb.setEnabled(True)

    def hide_toolbars(self):
        """ Disable all the toolbars in the viewer.

        This action can be reversed by calling `show_toolbars()`
        """
        for tb in self._toolbars:
            self._tb_vis[tb] = self._tb_vis.get(tb, False) or tb.isVisible()
            tb.setEnabled(False)

    def set_focus(self, state):
        if state:
            css = """
            DataViewer
            {
            border: 2px solid;
            border-color: rgb(56, 117, 215);
            }
            """
            self.setStyleSheet(css)
            self.show_toolbars()
        else:
            css = """
            DataViewer
            {
            border: none;
            }
            """
            self.setStyleSheet(css)
            self.hide_toolbars()

    def __str__(self):
        return self.LABEL

    def unregister(self, hub):
        """
        Override to perform cleanup operations when disconnecting from hub
        """
        pass

    @property
    def window_title(self):
        return str(self)

    def update_window_title(self):
        self.setWindowTitle(self.window_title)

    def set_status(self, message):
        sb = self.statusBar()
        sb.showMessage(message)
Пример #19
0
class NewFileInDirectoryWatcher(QtCore.QObject):
    """
    This class watches a given filepath for any new files with a given file extension added to it.

    Typical usage::
        def callback_fcn(path):
            print(path)

        watcher = NewFileInDirectoryWatcher(example_path, file_types = ['.tif', '.tiff'])
        watcher.file_added.connect(callback_fcn)

    """
    file_added = QtCore.Signal(str)

    def __init__(self, path=None, file_types=None, activate=False):
        """
        :param path: path to folder which will be watched
        :param file_types: list of file types which will be watched for, e.g. ['.tif', '.jpeg]
        :param activate: whether or not the Watcher will already emit signals
        """
        super(NewFileInDirectoryWatcher, self).__init__()

        self._file_system_watcher = QtCore.QFileSystemWatcher()
        if path is None:
            path = os.getcwd()
        self._file_system_watcher.addPath(path)
        self._files_in_path = os.listdir(path)

        self._file_system_watcher.directoryChanged.connect(
            self._directory_changed)
        self._file_system_watcher.blockSignals(~activate)

        self._file_changed_watcher = QtCore.QFileSystemWatcher()
        self._file_changed_watcher.fileChanged.connect(self._file_changed)

        if file_types is None:
            self.file_types = set([])
        else:
            self.file_types = set(file_types)

    @property
    def path(self):
        return self._file_system_watcher.directories()[0]

    @path.setter
    def path(self, new_path):
        if len(self._file_system_watcher.directories()):
            self._file_system_watcher.removePath(
                self._file_system_watcher.directories()[0])
        self._file_system_watcher.addPath(new_path)
        self._files_in_path = os.listdir(new_path)

    def activate(self):
        """
        activates the watcher to emit signals when a new file is added
        """
        self._file_system_watcher.blockSignals(False)

    def deactivate(self):
        """
        deactivates the watcher so it will not emit a signal when a new file is added
        """
        self._file_system_watcher.blockSignals(True)

    def _directory_changed(self):
        """
        internal function which determines whether the change in directory is an actual new file. If a new file was
        detected it looks if it has the right extension and checks the file size. When the file is not completely
        written yet it watches it for changes and will call the _file_changed function which wil acctually emit the
        signal.
        """
        files_now = os.listdir(self.path)
        files_added = [f for f in files_now if not f in self._files_in_path]

        if len(files_added) > 0:
            new_file_path = os.path.join(str(self.path), files_added[-1])

            # abort if the new_file added is actually a directory...
            if os.path.isdir(new_file_path):
                self._files_in_path = files_now
                return

            valid_file = False
            for file_type in self.file_types:
                if new_file_path.endswith(file_type):
                    valid_file = True
                    break

            if valid_file:
                if self._file_closed(new_file_path):
                    self.file_added.emit(new_file_path)
                else:
                    self._file_changed_watcher.addPath(new_file_path)
            self._files_in_path = files_now

    def _file_closed(self, path):
        """
        Checks whether a file is used by other processes.
        """
        # since it is hard to ask the operating system for this directly, the change in file size is checked.
        size1 = os.stat(path).st_size
        time.sleep(0.10)
        size2 = os.stat(path).st_size

        return size1 == size2

    def _file_changed(self, path):
        """
        internal function callback for the file_changed_watcher. The watcher is invoked if a new file is detected but
        the file is still below 100 bytes (basically only the file handle created, and no data yet). The _file_changed
        callback function is then invoked when the data is completely written into the file. To ensure that everything
        is correct this function also checks whether the file is above 100 byte after the system sends a file changed
        signal.
        :param path: file path of the watched file
        """
        if self._file_closed(path):
            self.file_added.emit(path)
            self._file_changed_watcher.removePath(path)
Пример #20
0
 class ClassWithSignal(QtCore.QObject):
     signal = QtCore.Signal()
Пример #21
0
class Model(QtCore.QAbstractItemModel):

    dataNeedsRefresh = QtCore.Signal()
    dataAboutToBeRefreshed = QtCore.Signal()
    dataRefreshed = QtCore.Signal()

    @property
    def rootItem(self):
        return self._rootItem

    @property
    def dataSource(self):
        return self._dataSource

    @property
    def sorter(self):
        return self._sorter

    def __init__(self):
        super(Model, self).__init__()
        self._columnCount = 0
        self._dataSource = None
        self._sorter = None
        self._uuidLookup = {}
        self._totalItemCount = 0

        self._sorter = ModelItemSorter(self)
        self._sorter.sortingChanged.connect(self.doSort)
        self._maintainSorted = False

        self._headerItem = self.newItem()
        self._rootItem = self.newItem()
        self._rootItem.setModel(self)
        self._rootItem.setTotalChildCount(-1)

        self.dataNeedsRefresh.connect(self.requestRefresh)

    def addToTotalItemCount(self, count):
        self._totalItemCount += count

    def num_items(self):
        return self._totalItemCount

    def setColumnCount(self, columnCount):
        countDelta = columnCount - self._columnCount
        if countDelta > 0:
            self.insertColumns(self._columnCount, countDelta)
        elif countDelta < 0:
            self.removeColumns(self._columnCount + countDelta, -countDelta)

    def rowCount(self, parentIndex=QtCore.QModelIndex()):
        return self.itemFromIndex(parentIndex).childCount()

    def columnCount(self, parentIndex=QtCore.QModelIndex()):
        return self._columnCount

    def index(self, row, column, parentIndex=QtCore.QModelIndex()):
        if self.hasIndex(row, column, parentIndex):
            childItem = self.itemFromIndex(parentIndex).child(row)
            if childItem:
                return self.createIndex(row, column, childItem)
        return QtCore.QModelIndex()

    def parent(self, index):
        if index.isValid():
            parentItem = self.itemFromIndex(index, False).parent()
            return self.indexFromItem(parentItem)
        return QtCore.QModelIndex()

    def flags(self, index):
        if not index.isValid():
            return self._rootItem.flags()
        return self.itemFromIndex(index, False).flags(index.column())

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid():
            return self.itemFromIndex(index, False).data(index.column(), role)
        return None

    def dataType(self, index):
        dataType = self.data(index, common.ROLE_TYPE)
        if dataType in common.TYPES:
            return dataType

        dataType = self.headerData(index.column(), QtCore.Qt.Horizontal, common.ROLE_TYPE)
        if dataType in common.TYPES:
            return dataType

        return common.TYPE_DEFAULT

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal:
            return self._headerItem.data(section, role)
        return None

    def indexFromItem(self, item):
        if item and item.model is self and item.parent():
            row = item.parent().childPosition(item)
            if row >= 0:
                return self.createIndex(row, 0, item)
        return QtCore.QModelIndex()

    def itemFromIndex(self, index, validate=True):
        if validate:
            if not index.isValid():
                return self._rootItem
        return index.internalPointer()

    def uuidFromIndex(self, index):
        item = self.itemFromIndex(index)
        if item:
            return item.uuid
        return None

    def indexFromUuid(self, uuid):
        if uuid is not None:
            modelName = (self.dataSource or self).__class__.__name__
            try:
                item = self._uuidLookup[uuid]
            except KeyError:
                pass
            else:
                if item.model is self and item.uuid == uuid:
                    return self.indexFromItem(item)
                else:
                    _LOGGER.debug("%s: removing invalid uniqueId key %s -> %s" % (modelName, uuid, item))
                    del self._uniqueIdLookup[uuid]
            # lookup failed, search for the item the slow way
            _LOGGER.debug("%s: uniqueId lookup failed; performing exhaustive search for %s" % (modelName, uuid))
            for item in self.iterItems():
                if item.uuid == uuid:
                    _LOGGER.debug("%s: adding new uniqueId key %s -> %s" % (modelName, uuid, item))
                    self._uuidLookup[uuid] = item
                    return self.indexFromItem(item)
        return QtCore.QModelIndex()

    def setData(self, index, value, role=QtCore.Qt.DisplayRole):
        return self.setItemData(index, {role: value})

    def setItemData(self, index, roles):
        item = self.itemFromIndex(index)
        column = index.column()
        for (role, value) in roles.items():
            item.setData(value, column, role)

        self.dataChanged.emit(index, index)
        return True

    def setHeaderData(self, section, orientation, value, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal:
            if self._headerItem.setData(value, section, role):
                success = True
                if role == QtCore.Qt.DisplayRole and not self.headerData(section, orientation, common.ROLE_NAME):
                    success = self._headerItem.setData(value, section, common.ROLE_NAME)

                if success:
                    self.headerDataChanged.emit(orientation, section, section)
                    return success
        return False

    def _emitDataChanged(self, itemList):
        for (parentItem, first, last) in self._splitContiguousSegments(itemList):
            _LOGGER.debug("emitting data changed: rows %d-%d of parent %s" % (first, last, parentItem))
            parentIndex = self.indexFromItem(parentItem)
            startIndex = self.index(first, 0, parentIndex)
            endIndex = self.index(last, self._columnCount - 1, parentIndex)
            self.dataChanged.emit(startIndex, endIndex)

    def newItem(self):
        item = ModelItem(self._columnCount)
        return item

    def clear(self):
        self.beginResetModel()
        self._rootItem.removeAllChildren()
        self._rootItem.setTotalChildCount(-1)
        self._totalItemCount = 0
        self._uuidLookup = {}
        self.endResetModel()

    def appendRow(self, parentIndex=QtCore.QModelIndex()):
        return self.appendRows(1, parentIndex)

    def appendRows(self, count, parentIndex=QtCore.QModelIndex()):
        return self.insertRows(self.rowCount(parentIndex), count, parentIndex)

    def insertRows(self, position, count, parentIndex=QtCore.QModelIndex()):
        return self.insertItems(position, [self.newItem() for i in range(count)], parentIndex)

    def insertItem(self, position, item, parentIndex=QtCore.QModelIndex()):
        return self.insertItems(position, [item], parentIndex)

    def insertItems(self, position, itemList, parentIndex=QtCore.QModelIndex()):
        parentItem = self.itemFromIndex(parentIndex)

        if not 0 <= position <= parentItem.childCount():
            return False
        if not itemList:
            return True

        self.beginInsertRows(parentIndex, position, position + len(itemList) - 1)

        for item in itemList:
            uuid = item.uuid
            if uuid is not None:
                self._uuidLookup[uuid] = item
            if item.hasChildren():
                for descendant in item.iterTree(includeRoot=False):
                    uuid = descendant.uuid
                    if uuid is not None:
                        self._uuidLookup[uuid] = descendant

        assert parentItem.insertChildren(position, itemList)

        self.endInsertRows()

        return True

    def appendItem(self, item, parentIndex=QtCore.QModelIndex()):
        return self.appendItems([item], parentIndex)

    def appendItems(self, itemList, parentIndex=QtCore.QModelIndex()):
        return self.insertItems(self.rowCount(parentIndex), itemList, parentIndex)

    def setItems(self, itemList):
        self.beginResetModel()
        self._rootItem.removeAllChildren()
        self._uuidLookup = {}
        self._rootItem.appendChildren(itemList)
        for item in self.iterItems():
            if item.uuid is not None:
                self._uuidLookup[item.uuid] = item
        self.endResetModel()
        self._rootItem.setTotalChildCount(len(itemList))

    def removeRows(self, position, count, parentIndex=QtCore.QModelIndex()):
        if count < 0 or position < 0:
            return False
        if count == 0:
            return True
        parentItem = self.itemFromIndex(parentIndex)
        if position + count > parentItem.childCount():
            return False
        return self.removeItems([parentItem.child(i) for i in range(position, position + count)])

    def removeItem(self, item):
        return self.removeItems([item])

    def removeItems(self, itemList):
        if not itemList:
            return True
        for item in itemList:
            if item.model is not self:
                return False
        itemsToRemove = set(itemList)
        for item in itemsToRemove:
            for descendant in item.iterTree(includeRoot=True):
                if descendant.uuid is not None:
                    try:
                        del self._uuidLookup[descendant.uuid]
                    except KeyError:
                        pass
        segments = list(self._splitContiguousSegments(itemsToRemove))
        for (parentItem, first, last) in reversed(segments):
            if parentItem.model is self:
                _LOGGER.debug("removing: rows %d-%d of parent %s" % (first, last, parentItem))
                parentIndex = self.indexFromItem(parentItem)
                self.beginRemoveRows(parentIndex, first, last)
                parentItem.removeChildren(first, last - first + 1)
                self._totalItemCount -= last - first + 1
                self.endRemoveRows()

        return True

    def disableItem(self, item):
        return self.disableItems([item])

    def disableItems(self, itemList):
        pass

    def reviveItem(self, item):
        return self.reviveItems([item])

    def reviveItems(self, itemList):
        pass

    def enableItem(self, item):
        return self.enableItems([item])

    def enableItems(self, itemList):
        pass

    def moveRow(self, fromPosition, toPosition, fromParent=QtCore.QModelIndex(), toParent=QtCore.QModelIndex()):
        return self.moveRows(fromPosition, 1, toPosition, fromParent, toParent)

    def moveRows(self, fromPosition, count, toPosition, fromParent=QtCore.QModelIndex(), toParent=QtCore.QModelIndex()):
        if fromPosition < 0 or count < 0:
            return False
        if count == 0:
            return True
        fromParentItem = self.itemFromIndex(fromParent)
        toParentItem = self.itemFromIndex(toParent)
        if fromPosition + count > fromParentItem.childCount() or toPosition > toParentItem.childCount():
            return False
        if fromParentItem is toParentItem and fromPosition <= toPosition < fromPosition + count:
            return True
        itemsToMove = fromParentItem.children()[fromPosition:fromPosition + count]

        assert len(itemsToMove) == count

        sourceLast = fromPosition + count - 1

        if fromPosition <= toPosition <= sourceLast and fromParent == toParent:
            return True

        if toPosition > sourceLast:
            toPosition += 1

        if not self.beginMoveRows(fromParent, fromPosition, sourceLast, toParent, toPosition):
            return False

        assert fromParentItem.removeChildren(fromPosition, count)
        if fromParentItem is toParentItem and fromPosition + count < toPosition:
            toPosition -= count

        assert toParentItem.insertChildren(toPosition, itemsToMove)
        self.endMoveRows()
        return True

    def moveItem(self, item, toPosition, toParent=QtCore.QModelIndex()):
        return self.moveItems([item], toPosition, toParent)

    def moveItems(self, itemList, toPosition, toParent=QtCore.QModelIndex()):
        if not itemList:
            return True
        for item in itemList:
            if item.model is not self:
                return False

        toParentItem = self.itemFromIndex(toParent)

        if toPosition < 0:
            toPosition = toParentItem.childCount() - 1
        elif toPosition > toParentItem.childCount() - 1:
            toPosition = 0

        itemSet = set(itemList)
        parentItem = toParentItem
        while parentItem:
            if parentItem in itemSet:
                return False
            parentItem = parentItem.parent()
        segments = list(self._splitContiguousSegments(itemList))

        for (fromParentItem, first, last) in reversed(segments):
            fromParentIndex = self.indexFromItem(fromParentItem)
            toParentIndex = self.indexFromItem(toParentItem)
            count = last - first + 1
            if not self.moveRows(first, count, toPosition, fromParentIndex, toParentIndex):
                return False
            if fromParentItem is toParentItem and toPosition > last:
                toPosition -= count
        self._emitDataChanged(itemList)
        return True

    def insertColumns(self, position, count, parentIndex=QtCore.QModelIndex()):
        if position < 0 or position > self._columnCount:
            return False
        if count > 0:
            self.beginInsertColumns(QtCore.QModelIndex(), position, position + count - 1)
            self._headerItem.insertColumns(position, count)
            self._rootItem.insertColumns(position, count)
            self._columnCount += count
            self.endInsertColumns()
        return True

    def removeColumns(self, position, count, parentIndex=QtCore.QModelIndex()):
        if position < 0 or position + count > self._columnCount:
            return False
        if count > 0:
            self.beginRemoveColumns(QtCore.QModelIndex(), position, position + count - 1)
            self._headerItem.removeColumns(position, count)
            self._rootItem.removeColumns(position, count)
            self._columnCount -= count
            self.endRemoveColumns()
        return True

    def hasChildren(self, parentIndex=QtCore.QModelIndex()):
        item = self.itemFromIndex(parentIndex)
        if item.childCount() > 0:
            return True

        return False

    def doSort(self, refresh=True):
        """
        """
        # do the actual sort
        if self._dataSource:
            try:
                self._dataSource.sortByColumns(self._sorter.sortColumns, self._sorter.sortDirections, refresh=refresh)
                return
            except NotImplementedError:
                pass

        # save persistent indexes
        oldPersistentMap = dict([(self.itemFromIndex(idx), idx.row()) for idx in self.persistentIndexList()])

        self.layoutAboutToBeChanged.emit()

        # sort all items
        self._sorter.sortItems([self._rootItem])

        # update persistent indexes
        fromList = []
        toList = []
        for (item, oldRow) in oldPersistentMap.items():
            newIdx = self.indexFromItem(item)
            for column in range(self._columnCount):
                fromList.append(self.createIndex(oldRow, column, newIdx.internalPointer()))
                toList.append(self.createIndex(newIdx.row(), column, newIdx.internalPointer()))
        self.changePersistentIndexList(fromList, toList)

        self.layoutChanged.emit()

    def beginResetModel(self):
        self.modelAboutToBeReset.emit()

    def endResetModel(self):
        persistentIndexList = self.persistentIndexList()
        for index in persistentIndexList:
            self.changePersistentIndex(index, QtCore.QModelIndex())
        self.modelReset.emit()

    def setDataSource(self, dataSource):
        if self._dataSource:
            self._dataSource.dataNeedsRefresh.disconnect(self.dataNeedsRefresh)
            self._dataSource.setModel(None)
            self._dataSource.setParent(None)
        self._dataSource = dataSource
        self.clear()
        if self._dataSource:
            self._dataSource.setModel(self)
            self._dataSource.setParent(self)
            self.setColumnCount(len(self._dataSource.headerItem))
            self._headerItem = self._dataSource.headerItem
            self._dataSource.dataNeedsRefresh.connect(self.dataNeedsRefresh)
            self.dataNeedsRefresh.emit()
        else:
            self.setColumnCount(0)

    def requestRefresh(self):
        if self._dataSource and (reload or self._dataSource.needToRefresh):
            self._dataSource.setNeedToRefresh(False)

        self.dataAboutToBeRefreshed.emit()

        itemList = self._dataSource.fetchItems(QtCore.QModelIndex())

        self.setItems(itemList)

        self.dataRefreshed.emit()

        return True

    def iterItems(self, includeRoot=True):
        return self._rootItem.iterTree(includeRoot=includeRoot)

    def _splitContiguousSegments(self, itemList):
        partitions = {}
        for item in itemList:
            index = self.indexFromItem(item)
            parentIndex = index.parent()

            if parentIndex not in partitions:
                partitions[parentIndex] = set()
            parentIndex[parentIndex].add(index.row())

        for parentIndex in sorted(partitions):
            parentItem = self.itemFromIndex(parentIndex)
            rowList = partitions[parentIndex]
            sequences = [map(operator.itemgetter(1), g) for k, g in
                         itertools.groupby(enumerate(sorted(rowList)), calcGroupingKey)]

            for seq in sequences:
                yield (parentItem, seq[0], seq[-1])
Пример #22
0
class mpvWidget(QW.QWidget):

    launchMediaViewer = QC.Signal()

    def __init__(self, parent):

        QW.QWidget.__init__(self, parent)

        self._canvas_type = ClientGUICommon.CANVAS_PREVIEW

        self._stop_for_slideshow = False

        # This is necessary since PyQT stomps over the locale settings needed by libmpv.
        # This needs to happen after importing PyQT before creating the first mpv.MPV instance.
        locale.setlocale(locale.LC_NUMERIC, 'C')

        self.setAttribute(QC.Qt.WA_DontCreateNativeAncestors)
        self.setAttribute(QC.Qt.WA_NativeWindow)

        loglevel = 'debug' if HG.mpv_report_mode else 'fatal'

        # loglevels: fatal, error, debug
        self._player = mpv.MPV(wid=str(int(self.winId())),
                               log_handler=log_handler,
                               loglevel=loglevel)

        # hydev notes on OSC:
        # OSC is by default off, default input bindings are by default off
        # difficult to get this to intercept mouse/key events naturally, so you have to pipe them to the window with 'command', but this is not excellent
        # general recommendation when using libmpv is to just implement your own stuff anyway, so let's do that for prototype

        #self._player[ 'input-default-bindings' ] = True

        self.UpdateConf()

        self._player.loop = True

        # this makes black screen for audio (rather than transparent)
        self._player.force_window = True

        # this actually propagates up to the OS-level sound mixer lmao, otherwise defaults to ugly hydrus filename
        self._player.title = 'hydrus mpv player'

        # pass up un-button-pressed mouse moves to parent, which wants to do cursor show/hide
        self.setMouseTracking(True)
        #self.setFocusPolicy(QC.Qt.StrongFocus)#Needed to get key events
        self._player.input_cursor = False  #Disable mpv mouse move/click event capture
        self._player.input_vo_keyboard = False  #Disable mpv key event capture, might also need to set input_x11_keyboard

        self._media = None

        self._file_is_loaded = False
        self._disallow_seek_on_this_file = False

        self._times_to_play_gif = 0

        self._current_seek_to_start_count = 0

        self._InitialiseMPVCallbacks()

        self.destroyed.connect(self._player.terminate)

        HG.client_controller.sub(self, 'UpdateAudioMute', 'new_audio_mute')
        HG.client_controller.sub(self, 'UpdateAudioVolume', 'new_audio_volume')
        HG.client_controller.sub(self, 'UpdateConf', 'notify_new_options')
        HG.client_controller.sub(self, 'SetLogLevel', 'set_mpv_log_level')

        self._my_shortcut_handler = ClientGUIShortcuts.ShortcutsHandler(
            self, [], catch_mouse=True)

    def _GetAudioOptionNames(self):

        if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:

            if HG.client_controller.new_options.GetBoolean(
                    'media_viewer_uses_its_own_audio_volume'):

                return ClientGUIMediaControls.volume_types_to_option_names[
                    ClientGUIMediaControls.AUDIO_MEDIA_VIEWER]

        elif self._canvas_type == ClientGUICommon.CANVAS_PREVIEW:

            if HG.client_controller.new_options.GetBoolean(
                    'preview_uses_its_own_audio_volume'):

                return ClientGUIMediaControls.volume_types_to_option_names[
                    ClientGUIMediaControls.AUDIO_PREVIEW]

        return ClientGUIMediaControls.volume_types_to_option_names[
            ClientGUIMediaControls.AUDIO_GLOBAL]

    def _GetCorrectCurrentMute(self):

        (global_mute_option_name, global_volume_option_name
         ) = ClientGUIMediaControls.volume_types_to_option_names[
             ClientGUIMediaControls.AUDIO_GLOBAL]

        mute_option_name = global_mute_option_name

        if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:

            (mute_option_name, volume_option_name
             ) = ClientGUIMediaControls.volume_types_to_option_names[
                 ClientGUIMediaControls.AUDIO_MEDIA_VIEWER]

        elif self._canvas_type == ClientGUICommon.CANVAS_PREVIEW:

            (mute_option_name, volume_option_name
             ) = ClientGUIMediaControls.volume_types_to_option_names[
                 ClientGUIMediaControls.AUDIO_PREVIEW]

        return HG.client_controller.new_options.GetBoolean(
            mute_option_name) or HG.client_controller.new_options.GetBoolean(
                global_mute_option_name)

    def _GetCorrectCurrentVolume(self):

        (mute_option_name, volume_option_name
         ) = ClientGUIMediaControls.volume_types_to_option_names[
             ClientGUIMediaControls.AUDIO_GLOBAL]

        if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:

            if HG.client_controller.new_options.GetBoolean(
                    'media_viewer_uses_its_own_audio_volume'):

                (mute_option_name, volume_option_name
                 ) = ClientGUIMediaControls.volume_types_to_option_names[
                     ClientGUIMediaControls.AUDIO_MEDIA_VIEWER]

        elif self._canvas_type == ClientGUICommon.CANVAS_PREVIEW:

            if HG.client_controller.new_options.GetBoolean(
                    'preview_uses_its_own_audio_volume'):

                (mute_option_name, volume_option_name
                 ) = ClientGUIMediaControls.volume_types_to_option_names[
                     ClientGUIMediaControls.AUDIO_PREVIEW]

        return HG.client_controller.new_options.GetInteger(volume_option_name)

    def _InitialiseMPVCallbacks(self):
        def qt_file_loaded_event():

            if not QP.isValid(self):

                return

            self._file_is_loaded = True

        def qt_seek_event():

            if not QP.isValid(self):

                return

            if not self._file_is_loaded:

                return

            current_timestamp_s = self._player.time_pos

            if self._media is not None and current_timestamp_s is not None and current_timestamp_s <= 1.0:

                self._current_seek_to_start_count += 1

                if self._stop_for_slideshow:

                    self.Pause()

                if self._times_to_play_gif != 0 and self._current_seek_to_start_count >= self._times_to_play_gif:

                    self.Pause()

        player = self._player

        @player.event_callback(mpv.MpvEventID.SEEK)
        def seek_event(event):

            QP.CallAfter(qt_seek_event)

        @player.event_callback(mpv.MpvEventID.FILE_LOADED)
        def file_loaded_event(event):

            QP.CallAfter(qt_file_loaded_event)

    def ClearMedia(self):

        self.SetMedia(None)

    def GetAnimationBarStatus(self):

        buffer_indices = None

        if self._media is None or not self._file_is_loaded:

            current_frame_index = 0
            current_timestamp_ms = 0
            paused = True

        else:

            current_timestamp_s = self._player.time_pos

            if current_timestamp_s is None:

                current_frame_index = 0
                current_timestamp_ms = None

            else:

                current_timestamp_ms = current_timestamp_s * 1000

                num_frames = self._media.GetNumFrames()

                if num_frames is None or num_frames == 1:

                    current_frame_index = 0

                else:

                    current_frame_index = int(
                        round(
                            (current_timestamp_ms / self._media.GetDuration())
                            * num_frames))

                    current_frame_index = min(current_frame_index,
                                              num_frames - 1)

                current_timestamp_ms = min(current_timestamp_ms,
                                           self._media.GetDuration())

            paused = self._player.pause

        return (current_frame_index, current_timestamp_ms, paused,
                buffer_indices)

    def GotoPreviousOrNextFrame(self, direction):

        if not self._file_is_loaded:

            return

        command = 'frame-step'

        if direction == 1:

            command = 'frame-step'

        elif direction == -1:

            command = 'frame-back-step'

        self._player.command(command)

    def HasPlayedOnceThrough(self):

        return self._current_seek_to_start_count > 0

    def IsPlaying(self):

        return not self._player.pause

    def Pause(self):

        self._player.pause = True

    def PausePlay(self):

        self._player.pause = not self._player.pause

    def Play(self):

        self._player.pause = False

    def ProcessApplicationCommand(self, command: CAC.ApplicationCommand):

        command_processed = True

        if command.IsSimpleCommand():

            action = command.GetSimpleAction()

            if action == CAC.SIMPLE_PAUSE_MEDIA:

                self.Pause()

            elif action == CAC.SIMPLE_PAUSE_PLAY_MEDIA:

                self.PausePlay()

            elif action == CAC.SIMPLE_MEDIA_SEEK_DELTA:

                (direction, duration_ms) = command.GetSimpleData()

                self.SeekDelta(direction, duration_ms)

            elif action == CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM:

                if self._media is not None:

                    ClientGUIMedia.OpenExternally(self._media)

            elif action == CAC.SIMPLE_CLOSE_MEDIA_VIEWER and self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:

                self.window().close()

            elif action == CAC.SIMPLE_LAUNCH_MEDIA_VIEWER and self._canvas_type == ClientGUICommon.CANVAS_PREVIEW:

                self.launchMediaViewer.emit()

            else:

                command_processed = False

        else:

            command_processed = False

        return command_processed

    def Seek(self, time_index_ms):

        if not self._file_is_loaded:

            return

        if self._disallow_seek_on_this_file:

            return

        time_index_s = time_index_ms / 1000

        try:

            self._player.seek(time_index_s, reference='absolute')

        except:

            self._disallow_seek_on_this_file = True

            # on some files, this seems to fail with a SystemError lmaoooo
            # with the same elegance, we will just pass all errors

    def SeekDelta(self, direction, duration_ms):

        if not self._file_is_loaded:

            return

        current_timestamp_s = self._player.time_pos

        new_timestamp_ms = max(0, (current_timestamp_s * 1000) +
                               (direction * duration_ms))

        if new_timestamp_ms > self._media.GetDuration():

            new_timestamp_ms = 0

        self.Seek(new_timestamp_ms)

    def SetCanvasType(self, canvas_type):

        self._canvas_type = canvas_type

        if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:

            shortcut_set = 'media_viewer_media_window'

        else:

            shortcut_set = 'preview_media_window'

        self._my_shortcut_handler.SetShortcuts([shortcut_set])

    def SetLogLevel(self, level: str):

        self._player.set_loglevel(level)

    def SetMedia(self, media, start_paused=False):

        if media == self._media:

            return

        self._file_is_loaded = False
        self._disallow_seek_on_this_file = False

        self._media = media

        self._times_to_play_gif = 0

        if self._media is not None and self._media.GetMime(
        ) == HC.IMAGE_GIF and not HG.client_controller.new_options.GetBoolean(
                'always_loop_gifs'):

            hash = self._media.GetHash()

            path = HG.client_controller.client_files_manager.GetFilePath(
                hash, HC.IMAGE_GIF)

            self._times_to_play_gif = HydrusImageHandling.GetTimesToPlayGIF(
                path)

        self._current_seek_to_start_count = 0

        if self._media is None:

            self._player.pause = True

            if len(self._player.playlist) > 0:

                try:

                    self._player.command('playlist-remove', 'current')

                except:

                    pass  # sometimes happens after an error--screw it

        else:

            hash = self._media.GetHash()
            mime = self._media.GetMime()

            client_files_manager = HG.client_controller.client_files_manager

            path = client_files_manager.GetFilePath(hash, mime)

            self._player.visibility = 'always'

            self._stop_for_slideshow = False

            self._player.pause = True

            try:

                self._player.loadfile(path)

            except Exception as e:

                HydrusData.ShowException(e)

            self._player.volume = self._GetCorrectCurrentVolume()
            self._player.mute = self._GetCorrectCurrentMute()
            self._player.pause = start_paused

    def StopForSlideshow(self, value):

        self._stop_for_slideshow = value

    def UpdateAudioMute(self):

        self._player.mute = self._GetCorrectCurrentMute()

    def UpdateAudioVolume(self):

        self._player.volume = self._GetCorrectCurrentVolume()

    def UpdateConf(self):

        mpv_config_path = HG.client_controller.GetMPVConfPath()

        if not os.path.exists(mpv_config_path):

            default_mpv_config_path = HG.client_controller.GetDefaultMPVConfPath(
            )

            if not os.path.exists(default_mpv_config_path):

                HydrusData.ShowText(
                    'There is no default mpv configuration file to load! Perhaps there is a problem with your install?'
                )

                return

            else:

                HydrusPaths.MirrorFile(default_mpv_config_path,
                                       mpv_config_path)

        #To load an existing config file (by default it doesn't load the user/global config like standalone mpv does):

        load_f = getattr(mpv, '_mpv_load_config_file', None)

        if load_f is not None and callable(load_f):

            try:

                load_f(self._player.handle, mpv_config_path.encode('utf-8'))  # pylint: disable=E1102

            except Exception as e:

                HydrusData.ShowText(
                    'MPV could not load its configuration file! This was probably due to an invalid parameter value inside the conf. The error follows:'
                )

                HydrusData.ShowException(e)

        else:

            HydrusData.Print(
                'Was unable to load mpv.conf--has the MPV API changed?')
Пример #23
0
class FilterMenu(QtWidgets.QMenu):
    activate = QtCore.Signal(int)
    checkedItemsChanged = QtCore.Signal(list)

    def __init__(self, parent=None):
        super(QtWidgets.QMenu, self).__init__(parent)

        self._list_view = QtWidgets.QListView(parent)
        self._list_view.setFrameStyle(0)
        model = SequenceStandardItemModel()
        self._list_view.setModel(model)
        self._model = model
        self.addItem("(select all)")
        model[0].setTristate(True)

        action = QtWidgets.QWidgetAction(self)
        action.setDefaultWidget(self._list_view)
        self.addAction(action)
        self.installEventFilter(self)
        self._list_view.installEventFilter(self)
        self._list_view.window().installEventFilter(self)

        model.itemChanged.connect(self.on_model_item_changed)
        self._list_view.pressed.connect(self.on_list_view_pressed)
        self.activate.connect(self.on_activate)

    def on_list_view_pressed(self, index):
        item = self._model.itemFromIndex(index)
        # item is None when the button has not been used yet (and this is
        # triggered via enter)
        if item is not None:
            item.checked = not item.checked

    def on_activate(self, row):
        target_item = self._model[row]
        for item in self._model[1:]:
            item.checked = item is target_item

    def on_model_item_changed(self, item):
        model = self._model
        model.blockSignals(True)
        if item.index().row() == 0:
            # (un)check first => (un)check others
            for other in model[1:]:
                other.checked = item.checked

        items_checked = [item for item in model[1:] if item.checked]
        num_checked = len(items_checked)

        if num_checked == 0 or num_checked == len(model) - 1:
            model[0].checked = bool(num_checked)
        elif num_checked == 1:
            model[0].checked = 'partial'
        else:
            model[0].checked = 'partial'
        model.blockSignals(False)
        is_checked = [i for i, item in enumerate(model[1:]) if item.checked]
        self.checkedItemsChanged.emit(is_checked)

    def addItem(self, text):
        item = StandardItem(text)
        # not editable
        item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
        item.checked = True
        self._model.appendRow(item)

    def addItems(self, items):
        for item in items:
            self.addItem(item)

    def eventFilter(self, obj, event):
        event_type = event.type()

        if event_type == QtCore.QEvent.KeyRelease:
            key = event.key()

            # tab key closes the popup
            if obj == self._list_view.window() and key == QtCore.Qt.Key_Tab:
                self.hide()

            # return key activates *one* item and closes the popup
            # first time the key is sent to the menu, afterwards to
            # list_view
            elif (obj == self._list_view and
                          key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return)):
                self.activate.emit(self._list_view.currentIndex().row())
                self.hide()
                return True

        return False
Пример #24
0
class Measurement(QtCore.QObject):
    """
    Base class for ScopeFoundry Measurement objects
    
    to subclass, implement :meth:`setup`, :meth:`run` 
    
    for measurements with graphical interfaces, 
    subclass and additionally implement :meth:`setup_figure`, :meth:`update_display` 
    """

    measurement_sucessfully_completed = QtCore.Signal(())
    """signal sent when full measurement is complete"""
    measurement_interrupted = QtCore.Signal(())
    """signal sent when  measurement is complete due to an interruption"""

    #measurement_state_changed = QtCore.Signal(bool) # signal sent when measurement started or stopped

    def __init__(self, app, name=None):
        """
        :type app: BaseMicroscopeApp
                
        """

        QtCore.QObject.__init__(self)
        self.log = get_logger_from_class(self)

        if not hasattr(self, 'name'):
            self.name = self.__class__.__name__

        if name is not None:
            self.name = name

        self.app = app

        self.display_update_period = 0.1  # seconds
        self.display_update_timer = QtCore.QTimer(self)
        self.display_update_timer.timeout.connect(
            self._on_display_update_timer)
        self.acq_thread = None

        self.interrupt_measurement_called = False

        #self.logged_quantities = OrderedDict()
        self.settings = LQCollection()
        self.operations = OrderedDict()

        self.activation = self.settings.New(
            'activation', dtype=bool,
            ro=False)  # does the user want to the thread to be running
        self.running = self.settings.New(
            'running', dtype=bool, ro=True)  # is the thread actually running?
        self.progress = self.settings.New('progress',
                                          dtype=float,
                                          unit="%",
                                          si=False,
                                          ro=True)
        self.settings.New(
            'profile', dtype=bool, initial=False
        )  # Run a profile on the run to find performance problems

        self.activation.updated_value[bool].connect(self.start_stop)

        self.add_operation("start", self.start)
        self.add_operation("interrupt", self.interrupt)
        #self.add_operation('terminate', self.terminate)
        #self.add_operation("setup", self.setup)
        #self.add_operation("setup_figure", self.setup_figure)
        self.add_operation("update_display", self.update_display)
        self.add_operation('show_ui', self.show_ui)

        if hasattr(self, 'ui_filename'):
            self.load_ui()

        self.setup()

    def setup(self):
        """Override this to set up logged quantities and gui connections
        Runs during __init__, before the hardware connection is established
        Should generate desired LoggedQuantities"""
        pass
        #raise NotImplementedError()

    def setup_figure(self):
        """
        Overide setup_figure to build graphical interfaces. 
        This function is run on ScopeFoundry startup.
        """
        self.log.info("Empty setup_figure called")
        pass

    @QtCore.Slot()
    def start(self):
        """
        Starts the measurement
        
        calls *pre_run*
        creates acquisition thread 
        runs thread
        starts display timer which calls update_display periodically
        calls post run when thread is finished
        """
        #self.start_stop(True)
        self.activation.update_value(True)

    def _start(self):
        """
        Starts the measurement
        
        calls *pre_run*
        creates acquisition thread 
        runs thread
        starts display timer which calls update_display periodically
        calls post run when thread is finished
        """
        self.log.info("measurement {} start".format(self.name))
        self.interrupt_measurement_called = False
        if (self.acq_thread is not None) and self.is_measuring():
            raise RuntimeError(
                "Cannot start a new measurement while still measuring")
        #self.acq_thread = threading.Thread(target=self._thread_run)
        self.acq_thread = MeasurementQThread(self)
        self.acq_thread.finished.connect(self.post_run)
        #self.measurement_state_changed.emit(True)
        self.running.update_value(True)
        self.pre_run()
        self.acq_thread.start()
        self.t_start = time.time()
        self.display_update_timer.start(self.display_update_period * 1000)

    def pre_run(self):
        """Override this method to enable main-thread initialization prior to measurement thread start"""
        pass

    def run(self):
        """
        *run* method runs in an separate thread and is used for data acquisition
        
        No GUI updates should occur within the *run* function, any Qt related GUI work 
        should occur in :meth:`update_display` 
        """

        if hasattr(self, '_run'):
            self.log.warning("warning _run is deprecated, use run")
            self._run()
        else:
            raise NotImplementedError(
                "Measurement {}.run() not defined".format(self.name))

    def post_run(self):
        """Override this method to enable main-thread finalization after to measurement thread completes"""
        pass

    def _thread_run(self):
        """
        This function governs the behavior of the measurement thread. 
        """
        self.set_progress(
            50.)  # set progress bars to default run position at 50%
        try:
            if self.settings['profile']:
                import cProfile
                profile = cProfile.Profile()
                profile.enable()
            self.run()
        #except Exception as err:
        #    self.interrupt_measurement_called = True
        #    raise err
        finally:
            self.running.update_value(False)
            self.activation.update_value(False)
            self.set_progress(0.)  # set progress bars back to zero
            #self.measurement_state_changed.emit(False)
            if self.interrupt_measurement_called:
                self.measurement_interrupted.emit()
                self.interrupt_measurement_called = False
            else:
                self.measurement_sucessfully_completed.emit()
            if self.settings['profile']:
                profile.disable()
                profile.print_stats(sort='time')

    @property
    def gui(self):
        self.log.warning(
            "Measurement.gui is deprecated, use Measurement.app " +
            repr(DeprecationWarning))
        return self.app

    def set_progress(self, pct):
        """
        This function updates the logged quantity progress which is used for the display of progress bars in the UI.
         
        ==============  ==============================================================================================
        **Arguments:** 
        pct             The percentage of progress given by a measurement module                                      
        ==============  ==============================================================================================
        """
        self.progress.update_value(pct)

    @QtCore.Slot()
    def interrupt(self):
        """
        Kindly ask the measurement to stop.
        
        This raises the :attr:`interrupt_measurement_called` flag
        To actually stop, the threaded :meth:`run` method must check
        for this flag and exit
        """
        self.log.info("measurement {} interrupt".format(self.name))
        self.interrupt_measurement_called = True
        self.activation.update_value(False)
        #Make sure display is up to date
        #self._on_display_update_timer()

    def terminate(self):
        """
        Terminate MeasurementQThread. Usually a bad idea:
        This will not clean up the thread correctly and usually
        requires a reboot of the App
        """
        self.acq_thread.terminate()

    def start_stop(self, start):
        """
        Use boolean *start* to either start (True) or
        interrupt (False) measurement. Test.
        """
        self.log.info("{} start_stop {}".format(self.name, start))
        if start:
            self._start()
        else:
            self.interrupt()

    def is_measuring(self):
        """
        Returns whether the acquisition thread is running
        """

        if self.acq_thread is None:
            self.running.update_value(False)
            self.activation.update_value(False)
            self.settings['progress'] = 0.0
            return False
        else:
            #resp =  self.acq_thread.is_alive()
            resp = self.acq_thread.isRunning()
            self.running.update_value(resp)
            return resp

    def update_display(self):
        "Override this function to provide figure updates when the display timer runs"
        pass

    @QtCore.Slot()
    def _on_display_update_timer(self):
        try:
            self.update_display()
        except Exception as err:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            self.log.error("{} Failed to update figure1: {}. {}".format(
                self.name, err,
                traceback.format_exception(exc_type, exc_value,
                                           exc_traceback)))
        finally:
            if not self.is_measuring():
                self.display_update_timer.stop()

    def add_logged_quantity(self, name, **kwargs):
        """
        Create a new :class:`LoggedQuantity` and adds it to the measurement's
        :attr:`settings` (:class:`LQCollection`)
        """
        lq = self.settings.New(name=name, **kwargs)
        return lq

    def add_operation(self, name, op_func):
        """
        Used to create a logged quantity connection between a button in the Measurement tree
        and a function.

        ==============  =================
        **type name:**  **type op_func:**
        str             QtCore.Slot
        ==============  =================
        """
        self.operations[name] = op_func

    def start_nested_measure_and_wait(self,
                                      measure,
                                      nested_interrupt=True,
                                      polling_func=None,
                                      polling_time=0.1,
                                      start_time=0.0):
        """
        Start another nested measurement *measure* and wait until completion.
        
        
        Optionally it can call a polling function *polling_func* with no arguments
        at an interval *polling_time* in seconds. 
        
        if *nested_interrupt* is True then interrupting the nested *measure* will
        also interrupt the outer measurement. *nested_interrupt* defaults to True
        
        start_time is how the time period after starting the nested measurement
        that checks for completion of nested measurement
        """

        measure.settings['activation'] = True

        # wait until measurement has started
        t0 = time.time()
        while not measure.settings['running']:
            # check for startup timeout
            if (time.time() - t0) > 1.0:
                break
            time.sleep(0.001)

        time.sleep(start_time)

        last_polling = time.time()
        while measure.is_measuring():
            if self.interrupt_measurement_called:
                measure.interrupt()
            if measure.interrupt_measurement_called and nested_interrupt:
                #print("nested interrupt bubbling up")
                self.interrupt()
            time.sleep(0.010)

            # polling
            if polling_func:
                t = time.time()
                if t - last_polling > polling_time:
                    #print("poll", t - last_polling )
                    polling_func()
                    last_polling = t

    def load_ui(self, ui_fname=None):
        """
        Loads and shows user interface.

        ==============  ===============================================================
        **Arguments:** 
        ui_fname        filename of user interface file (usually made with Qt Designer)                                      
        ==============  ===============================================================
        """

        # TODO destroy and rebuild UI if it already exists
        if ui_fname is not None:
            self.ui_filename = ui_fname
        # Load Qt UI from .ui file
        self.ui = load_qt_ui_file(self.ui_filename)
        #self.show_ui()

    def show_ui(self):
        """
        Shows the graphical user interface of this measurement. :attr:`ui`
        """
        self.app.bring_measure_ui_to_front(self)
#         if self.app.mdi and self.ui.parent():
#             self.ui.parent().raise_()
#             return
#         self.ui.show()
#         self.ui.activateWindow()
#         self.ui.raise_() #just to be sure it's on top
#         if self.app.mdi and self.ui.parent():
#             self.ui.parent().raise_()

    def new_control_widgets(self):

        self.controls_groupBox = QtWidgets.QGroupBox(self.name)
        self.controls_formLayout = QtWidgets.QFormLayout()
        self.controls_groupBox.setLayout(self.controls_formLayout)

        self.control_widgets = OrderedDict()
        for lqname, lq in self.settings.as_dict().items():
            #: :type lq: LoggedQuantity
            if lq.choices is not None:
                widget = QtWidgets.QComboBox()
            elif lq.dtype in [int, float]:
                if lq.si:
                    widget = pg.SpinBox()
                else:
                    widget = QtWidgets.QDoubleSpinBox()
            elif lq.dtype in [bool]:
                widget = QtWidgets.QCheckBox()
            elif lq.dtype in [str]:
                widget = QtWidgets.QLineEdit()
            lq.connect_bidir_to_widget(widget)

            # Add to formlayout
            self.controls_formLayout.addRow(lqname, widget)
            self.control_widgets[lqname] = widget

        self.op_buttons = OrderedDict()
        for op_name, op_func in self.operations.items():
            op_button = QtWidgets.QPushButton(op_name)
            op_button.clicked.connect(op_func)
            self.controls_formLayout.addRow(op_name, op_button)

        return self.controls_groupBox

    def add_widgets_to_tree(self, tree):
        """
        Adds Measurement items and their controls to Measurements tree in the user interface.
        """
        #if tree is None:
        #    tree = self.app.ui.measurements_treeWidget
        tree.setColumnCount(2)
        tree.setHeaderLabels(["Measurements", "Value"])

        self.tree_item = QtWidgets.QTreeWidgetItem(tree, [self.name, ""])
        tree.insertTopLevelItem(0, self.tree_item)
        #self.tree_item.setFirstColumnSpanned(True)
        self.tree_progressBar = QtWidgets.QProgressBar()
        tree.setItemWidget(self.tree_item, 1, self.tree_progressBar)
        self.progress.updated_value.connect(self.tree_progressBar.setValue)

        # Add logged quantities to tree
        self.settings.add_widgets_to_subtree(self.tree_item)

        # Add operation buttons to tree
        self.op_buttons = OrderedDict()
        for op_name, op_func in self.operations.items():
            op_button = QtWidgets.QPushButton(op_name)
            op_button.clicked.connect(op_func)
            self.op_buttons[op_name] = op_button
            #self.controls_formLayout.addRow(op_name, op_button)
            op_tree_item = QtWidgets.QTreeWidgetItem(self.tree_item,
                                                     [op_name, ""])
            tree.setItemWidget(op_tree_item, 1, op_button)

    def web_ui(self):
        return "Hardware {}".format(self.name)
Пример #25
0
class FFTView(QtWidgets.QWidget):
    """
    creates the layout for the FFT GUI
    """
    # signals
    buttonSignal = QtCore.Signal()
    tableClickSignal = QtCore.Signal(object, object)
    phaseCheckSignal = QtCore.Signal()

    def __init__(self, parent=None):
        super(FFTView, self).__init__(parent)
        self.grid = QtWidgets.QGridLayout(self)

        # add splitter for resizing
        splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)

        # make table
        self.FFTTable = QtWidgets.QTableWidget(self)
        self.FFTTable.resize(800, 800)
        self.FFTTable.setRowCount(6)
        self.FFTTable.setColumnCount(2)
        self.FFTTable.setColumnWidth(0, 300)
        self.FFTTable.setColumnWidth(1, 300)
        self.FFTTable.verticalHeader().setVisible(False)
        self.FFTTable.horizontalHeader().setStretchLastSection(True)
        self.FFTTable.setHorizontalHeaderLabels(
            ("FFT Property;Value").split(";"))
        # populate table
        options = ['test']

        table_utils.setRowName(self.FFTTable, 0, "Workspace")
        self.ws = table_utils.addComboToTable(self.FFTTable, 0, options)
        self.Im_box_row = 1
        table_utils.setRowName(self.FFTTable, self.Im_box_row,
                               "Imaginary Data")
        self.Im_box = table_utils.addCheckBoxToTable(self.FFTTable, True,
                                                     self.Im_box_row)

        table_utils.setRowName(self.FFTTable, 2, "Imaginary Workspace")
        self.Im_ws = table_utils.addComboToTable(self.FFTTable, 2, options)

        self.shift_box_row = 3
        table_utils.setRowName(self.FFTTable, self.shift_box_row, "Auto shift")
        self.shift_box = table_utils.addCheckBoxToTable(
            self.FFTTable, True, self.shift_box_row)

        table_utils.setRowName(self.FFTTable, 4, "Shift")
        self.shift = table_utils.addDoubleToTable(self.FFTTable, 0.0, 4)
        self.FFTTable.hideRow(4)

        table_utils.setRowName(self.FFTTable, 5, "Use Raw data")
        self.Raw_box = table_utils.addCheckBoxToTable(self.FFTTable, True, 5)

        self.FFTTable.resizeRowsToContents()
        # make advanced table options
        self.advancedLabel = QtWidgets.QLabel("\n Advanced Options")
        self.FFTTableA = QtWidgets.QTableWidget(self)
        self.FFTTableA.resize(800, 800)
        self.FFTTableA.setRowCount(4)
        self.FFTTableA.setColumnCount(2)
        self.FFTTableA.setColumnWidth(0, 300)
        self.FFTTableA.setColumnWidth(1, 300)
        self.FFTTableA.verticalHeader().setVisible(False)
        self.FFTTableA.horizontalHeader().setStretchLastSection(True)
        self.FFTTableA.setHorizontalHeaderLabels(
            ("Advanced Property;Value").split(";"))

        table_utils.setRowName(self.FFTTableA, 0, "Apodization Function")
        options = ["Lorentz", "Gaussian", "None"]
        self.apodization = table_utils.addComboToTable(self.FFTTableA, 0,
                                                       options)

        table_utils.setRowName(self.FFTTableA, 1,
                               "Decay Constant (micro seconds)")
        self.decay = table_utils.addDoubleToTable(self.FFTTableA, 4.4, 1)

        table_utils.setRowName(self.FFTTableA, 2, "Negative Padding")
        self.negativePadding = table_utils.addCheckBoxToTable(
            self.FFTTableA, True, 2)

        table_utils.setRowName(self.FFTTableA, 3, "Padding")
        self.padding = table_utils.addSpinBoxToTable(self.FFTTableA, 1, 3)
        self.FFTTableA.resizeRowsToContents()

        # make button
        self.button = QtWidgets.QPushButton('Calculate FFT', self)
        self.button.setStyleSheet("background-color:lightgrey")
        # connects
        self.FFTTable.cellClicked.connect(self.tableClick)
        self.button.clicked.connect(self.buttonClick)
        self.ws.currentIndexChanged.connect(self.phaseCheck)
        # add to layout
        self.FFTTable.setMinimumSize(40, 158)
        self.FFTTableA.setMinimumSize(40, 127)
        table_utils.setTableHeaders(self.FFTTable)
        table_utils.setTableHeaders(self.FFTTableA)

        # add to layout
        splitter.addWidget(self.FFTTable)
        splitter.addWidget(self.advancedLabel)
        splitter.addWidget(self.FFTTableA)
        self.grid.addWidget(splitter)
        self.grid.addWidget(self.button)

    def getLayout(self):
        return self.grid

    def addItems(self, options):
        self.ws.clear()
        self.ws.addItems(options)
        self.Im_ws.clear()
        self.Im_ws.addItems(options)
        self.phaseQuadChanged()

    def removeIm(self, pattern):
        index = self.Im_ws.findText(pattern)
        self.Im_ws.removeItem(index)

    def removeRe(self, pattern):
        index = self.ws.findText(pattern)
        self.ws.removeItem(index)

    # connect signals
    def phaseCheck(self):
        self.phaseCheckSignal.emit()

    def tableClick(self, row, col):
        self.tableClickSignal.emit(row, col)

    def buttonClick(self):
        self.buttonSignal.emit()

    # responses to commands
    def activateButton(self):
        self.button.setEnabled(True)

    def deactivateButton(self):
        self.button.setEnabled(False)

    def setPhaseBox(self):
        self.FFTTable.setRowHidden(8, "PhaseQuad" not in self.workspace)

    def changed(self, box, row):
        self.FFTTable.setRowHidden(row, box.checkState() == QtCore.Qt.Checked)

    def changedHideUnTick(self, box, row):
        self.FFTTable.setRowHidden(row, box.checkState() != QtCore.Qt.Checked)

    def phaseQuadChanged(self):
        # hide complex ws
        self.FFTTable.setRowHidden(
            2, "PhaseQuad" in self.workspace
            or self.Im_box.checkState() != QtCore.Qt.Checked)

    def set_raw_checkbox_state(self, state):
        if state:
            self.Raw_box.setCheckState(QtCore.Qt.Checked)
        else:
            self.Raw_box.setCheckState(QtCore.Qt.Unchecked)

    def setup_raw_checkbox_changed(self, slot):
        self.FFTTable.itemChanged.connect(self.raw_checkbox_changed)
        self.signal_raw_option_changed = slot

    def raw_checkbox_changed(self, table_item):
        if table_item == self.Raw_box:
            self.signal_raw_option_changed()

    def getImBoxRow(self):
        return self.Im_box_row

    def getShiftBoxRow(self):
        return self.shift_box_row

    def getImBox(self):
        return self.Im_box

    def getShiftBox(self):
        return self.shift_box

    def warning_popup(self, message):
        warning(message, parent=self)

    @property
    def workspace(self):
        return str(self.ws.currentText())

    @workspace.setter
    def workspace(self, name):
        index = self.ws.findText(name)
        if index == -1:
            return
        self.ws.setCurrentIndex(index)

    @property
    def imaginary_workspace(self):
        return str(self.Im_ws.currentText())

    @imaginary_workspace.setter
    def imaginary_workspace(self, name):
        index = self.Im_ws.findText(name)
        if index == -1:
            return
        self.Im_ws.setCurrentIndex(index)

    @property
    def imaginary_data(self):
        return self.Im_box.checkState() == QtCore.Qt.Checked

    @imaginary_data.setter
    def imaginary_data(self, value):
        if value:
            self.Im_box.setCheckState(QtCore.Qt.Checked)
        else:
            self.Im_box.setCheckState(QtCore.Qt.Unchecked)

    @property
    def auto_shift(self):
        return self.shift_box.checkState() == QtCore.Qt.Checked

    @property
    def use_raw_data(self):
        return self.Raw_box.checkState() == QtCore.Qt.Checked

    @property
    def apodization_function(self):
        return str(self.apodization.currentText())

    @property
    def decay_constant(self):
        return float(self.decay.text())

    @property
    def negative_padding(self):
        return self.negativePadding.checkState() == QtCore.Qt.Checked

    @property
    def padding_value(self):
        return int(self.padding.text())
Пример #26
0
class PowerScannerLogic(GenericLogic):
    """This logic module controls scans of DC voltage on the third analog
    output channel of the NI Card.  It collects count-rate as a function of voltage.
    """

    sig_data_updated = QtCore.Signal()

    # declare connectors
    confocalscanner1 = Connector(interface='ConfocalScannerInterface')
    savelogic = Connector(interface='SaveLogic')

    scan_range = StatusVar('scan_range', [0, 1])
    number_of_repeats = StatusVar(default=10)
    resolution = StatusVar('resolution', 100)
    _scan_speed = StatusVar('scan_speed', 1)
    _static_v = StatusVar('goto_voltage', 0)

    sigChangeVoltage = QtCore.Signal(float)
    sigVoltageChanged = QtCore.Signal(float)
    sigScanNextLine = QtCore.Signal()
    signal_change_position = QtCore.Signal(str)
    sigUpdatePlots = QtCore.Signal()
    sigScanFinished = QtCore.Signal()
    sigScanStarted = QtCore.Signal()

    def __init__(self, **kwargs):
        """ Create VoltageScanningLogic object with connectors.

          @param dict kwargs: optional parameters
        """
        super().__init__(**kwargs)

        # locking for thread safety
        self.threadlock = Mutex()
        self.stopRequested = False

        self.fit_x = []
        self.fit_y = []
        self.plot_x = []
        self.plot_y = []
        self.plot_y2 = []

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._scanning_device = self.confocalscanner1()
        self._save_logic = self.savelogic()

        # Reads in the maximal scanning range. The unit of that scan range is
        # micrometer!
        self.z_range = self._scanning_device.get_position_range()[2]

        # Initialise the current position of all four scanner channels.
        self.current_position = self._scanning_device.get_scanner_position()

        # initialise the range for scanning
        self.scan_range = [0.0, 1.0]
        self.set_scan_range(self.scan_range)

        self._static_v = 1.0

        # Keep track of the current static voltage even while a scan may cause the real-time
        # voltage to change.
        self.goto_voltage(self._static_v)

        # Sets connections between signals and functions
        self.sigChangeVoltage.connect(self._change_voltage,
                                      QtCore.Qt.QueuedConnection)
        self.sigScanNextLine.connect(self._do_next_line,
                                     QtCore.Qt.QueuedConnection)

        # Initialization of internal counter for scanning
        self._scan_counter_up = 0
        self._scan_counter_down = 0
        # Keep track of scan direction
        self.upwards_scan = True

        # calculated number of points in a scan, depends on speed and max step size
        self._num_of_steps = 50  # initialising.  This is calculated for a given ramp.

        #############################

        # TODO: allow configuration with respect to measurement duration
        self.acquire_time = 20  # seconds
        self._scan_speed = 5

        # default values for clock frequency and slowness
        # slowness: steps during retrace line
        self.set_resolution(self.resolution)
        self._goto_speed = 10  # 0.01  # volt / second
        self.set_scan_speed(self._scan_speed)
        self._smoothing_steps = 10  # steps to accelerate between 0 and scan_speed
        self._max_step = 0.01  # volt

        self._current_z = 0
        self._change_position('activation')

        ##############################

        # Initialie data matrix
        self._initialise_data_matrix(100)

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        self.set_voltage(0)
        self.stopRequested = True

    @QtCore.Slot(float)
    def goto_voltage(self, volts=None):
        """Forwarding the desired output voltage to the scanning device.

        @param float volts: desired voltage (volts)

        @return int: error code (0:OK, -1:error)
        """
        # print(tag, x, y, z)
        # Changes the respective value
        if volts is not None:
            self._static_v = volts

        # Checks if the scanner is still running
        if (self.module_state() == 'locked'
                or self._scanning_device.module_state() == 'locked'):
            self.log.error('Cannot goto, because scanner is locked!')
            return -1
        else:
            self.sigChangeVoltage.emit(volts)
            return 0

    def _change_voltage(self, new_voltage):
        """ Threaded method to change the hardware voltage for a goto.

        @return int: error code (0:OK, -1:error)
        """
        ramp_scan = self._generate_ramp(self.get_current_voltage(),
                                        new_voltage, self._goto_speed)
        self._initialise_scanner()
        ignored_counts = self._scan_line(ramp_scan)
        self._close_scanner()
        self.sigVoltageChanged.emit(new_voltage)
        return 0

    def _goto_during_scan(self, voltage=None):

        if voltage is None:
            return -1

        goto_ramp = self._generate_ramp(self.get_current_voltage(), voltage,
                                        self._goto_speed)
        ignored_counts = self._scan_line(goto_ramp)

        return 0

    def set_clock_frequency(self, clock_frequency):
        """Sets the frequency of the clock

        @param int clock_frequency: desired frequency of the clock

        @return int: error code (0:OK, -1:error)
        """
        self._clock_frequency = float(clock_frequency)
        # checks if scanner is still running
        if self.module_state() == 'locked':
            return -1
        else:
            return 0

    def set_resolution(self, resolution):
        """ Calculate clock rate from scan speed and desired number of pixels """
        self.resolution = resolution
        scan_range = abs(self.scan_range[1] - self.scan_range[0])
        duration = scan_range / self._scan_speed
        new_clock = resolution / duration
        return self.set_clock_frequency(new_clock)

    def set_scan_range(self, scan_range):
        """ Set the scan range """
        r_max = np.clip(scan_range[1], self.z_range[0], self.z_range[1])
        r_min = np.clip(scan_range[0], self.z_range[0], r_max)
        self.scan_range = [r_min, r_max]

    def set_voltage(self, volts):
        """ Set the channel idle voltage """
        self._static_v = np.clip(volts, self.z_range[0], self.z_range[1])
        self.goto_voltage(self._static_v)

    def set_scan_speed(self, scan_speed):
        """ Set scan speed in volt per second """
        self._scan_speed = np.clip(scan_speed, 1e-9, 1e6)
        self._goto_speed = self._scan_speed

    def set_scan_lines(self, scan_lines):
        self.number_of_repeats = int(np.clip(scan_lines, 1, 1e6))

    def _initialise_data_matrix(self, scan_length):
        """ Initializing the ODMR matrix plot. """

        self.scan_matrix = np.zeros((self.number_of_repeats, scan_length))
        self.scan_matrix2 = np.zeros((self.number_of_repeats, scan_length))
        self.plot_x = np.linspace(self.scan_range[0], self.scan_range[1],
                                  scan_length)
        self.plot_y = np.zeros(scan_length)
        self.plot_y2 = np.zeros(scan_length)
        self.fit_x = np.linspace(self.scan_range[0], self.scan_range[1],
                                 scan_length)
        self.fit_y = np.zeros(scan_length)

    def get_current_voltage(self):
        """returns current voltage of hardware device(atm NIDAQ 3rd output)"""
        return self._scanning_device.get_scanner_position()[2]

    def _initialise_scanner(self):
        """Initialise the clock and locks for a scan"""
        self.module_state.lock()
        self._scanning_device.module_state.lock()

        returnvalue = self._scanning_device.set_up_scanner_clock(
            clock_frequency=self._clock_frequency)
        if returnvalue < 0:
            self._scanning_device.module_state.unlock()
            self.module_state.unlock()
            self.set_position('scanner')
            return -1

        returnvalue = self._scanning_device.set_up_scanner()
        if returnvalue < 0:
            self._scanning_device.module_state.unlock()
            self.module_state.unlock()
            self.set_position('scanner')
            return -1

        return 0

    def _change_position(self, tag):
        """ Threaded method to change the hardware position.

        @return int: error code (0:OK, -1:error)
        """
        ch_array = ['z']
        pos_array = [self._current_z]
        pos_dict = {}

        # for i, ch in enumerate(self.get_scanner_axes()):
        pos_dict[ch_array[0]] = pos_array[0]

        self._scanning_device.scanner_set_position(**pos_dict)
        return 0

    def get_scanner_axes(self):
        """ Get axes from scanning device.
          @return list(str): names of scanner axes
        """
        return self._scanning_device.get_scanner_axes()

    def set_position(self, tag, z=None):
        """Forwarding the desired new position from the GUI to the scanning device.

        @param string tag: TODO

        @param float a: if defined, changes to postion in a-direction (microns)

        @return int: error code (0:OK, -1:error)
        """
        # Changes the respective value
        if z is not None:
            self._current_z = z

        # Checks if the scanner is still running
        if self.module_state(
        ) == 'locked' or self._scanning_device.module_state() == 'locked':
            return -1
        else:
            self._change_position(tag)
            self.signal_change_position.emit(tag)
            return 0

    def start_scanning(self, v_min=None, v_max=None):
        """Setting up the scanner device and starts the scanning procedure

        @return int: error code (0:OK, -1:error)
        """

        self.current_position = self._scanning_device.get_scanner_position()
        print(self.current_position)

        if v_min is not None:
            self.scan_range[0] = v_min
        else:
            v_min = self.scan_range[0]
        if v_max is not None:
            self.scan_range[1] = v_max
        else:
            v_max = self.scan_range[1]

        self._scan_counter_up = 0
        self._scan_counter_down = 0
        self.upwards_scan = True

        # TODO: Generate Ramps
        self._upwards_ramp = self._generate_ramp(v_min, v_max,
                                                 self._scan_speed)
        self._downwards_ramp = self._generate_ramp(v_max, v_min,
                                                   self._scan_speed)

        self._initialise_data_matrix(len(self._upwards_ramp[3]))

        # Lock and set up scanner
        returnvalue = self._initialise_scanner()
        if returnvalue < 0:
            # TODO: error message
            return -1

        self.sigScanNextLine.emit()
        self.sigScanStarted.emit()
        return 0

    def stop_scanning(self):
        """Stops the scan

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.stopRequested = True
        return 0

    def _close_scanner(self):
        """Close the scanner and unlock"""
        with self.threadlock:
            self.kill_scanner()
            self.stopRequested = False
            if self.module_state.can('unlock'):
                self.module_state.unlock()

    def _do_next_line(self):
        """ If stopRequested then finish the scan, otherwise perform next repeat of the scan line
        """
        # stops scanning
        if self.stopRequested or self._scan_counter_down >= self.number_of_repeats:
            print(self.current_position)
            self._goto_during_scan(self._static_v)
            self._close_scanner()
            self.sigScanFinished.emit()
            return

        if self._scan_counter_up == 0:
            # move from current voltage to start of scan range.
            self._goto_during_scan(self.scan_range[0])

        if self.upwards_scan:
            counts = self._scan_line(self._upwards_ramp)
            self.scan_matrix[self._scan_counter_up] = counts
            self.plot_y += counts
            self._scan_counter_up += 1
            self.upwards_scan = False
        else:
            counts = self._scan_line(self._downwards_ramp)
            self.scan_matrix2[self._scan_counter_down] = counts
            self.plot_y2 += counts
            self._scan_counter_down += 1
            self.upwards_scan = True

        self.sigUpdatePlots.emit()
        self.sigScanNextLine.emit()

    def _generate_ramp(self, voltage1, voltage2, speed):
        """Generate a ramp vrom voltage1 to voltage2 that
        satisfies the speed, step, smoothing_steps parameters.  Smoothing_steps=0 means that the
        ramp is just linear.

        @param float voltage1: voltage at start of ramp.

        @param float voltage2: voltage at end of ramp.
        """

        # It is much easier to calculate the smoothed ramp for just one direction (upwards),
        # and then to reverse it if a downwards ramp is required.

        v_min = min(voltage1, voltage2)
        v_max = max(voltage1, voltage2)

        if v_min == v_max:
            ramp = np.array([v_min, v_max])
        else:
            # These values help simplify some of the mathematical expressions
            linear_v_step = speed / self._clock_frequency
            smoothing_range = self._smoothing_steps + 1

            # Sanity check in case the range is too short

            # The voltage range covered while accelerating in the smoothing steps
            v_range_of_accel = sum(n * linear_v_step / smoothing_range
                                   for n in range(0, smoothing_range))

            # Obtain voltage bounds for the linear part of the ramp
            v_min_linear = v_min + v_range_of_accel
            v_max_linear = v_max - v_range_of_accel

            if v_min_linear > v_max_linear:
                self.log.warning(
                    'Voltage ramp too short to apply the '
                    'configured smoothing_steps. A simple linear ramp '
                    'was created instead.')
                num_of_linear_steps = np.rint((v_max - v_min) / linear_v_step)
                ramp = np.linspace(v_min, v_max, int(num_of_linear_steps))

            else:

                num_of_linear_steps = np.rint(
                    (v_max_linear - v_min_linear) / linear_v_step)

                # Calculate voltage step values for smooth acceleration part of ramp
                smooth_curve = np.array([
                    sum(n * linear_v_step / smoothing_range
                        for n in range(1, N))
                    for N in range(1, smoothing_range)
                ])

                accel_part = v_min + smooth_curve
                decel_part = v_max - smooth_curve[::-1]

                linear_part = np.linspace(v_min_linear, v_max_linear,
                                          int(num_of_linear_steps))

                ramp = np.hstack((accel_part, linear_part, decel_part))

        # Reverse if downwards ramp is required
        if voltage2 < voltage1:
            ramp = ramp[::-1]

        # Put the voltage ramp into a scan line for the hardware (4-dimension)
        spatial_pos = self._scanning_device.get_scanner_position()

        ##################### WARNING #############################
        # the position of the ramp in the array defines the channel of the NI card
        scan_line = np.vstack((np.ones(
            (len(ramp), )) * spatial_pos[0], np.ones(
                (len(ramp), )) * spatial_pos[1], ramp, np.ones(
                    (len(ramp), )) * spatial_pos[3]))

        return scan_line

    def _scan_line(self, line_to_scan=None):
        """do a single voltage scan from voltage1 to voltage2

        """
        if line_to_scan is None:
            self.log.error('Voltage scanning logic needs a line to scan!')
            return -1
        try:
            # scan of a single line
            counts_on_scan_line = self._scanning_device.scan_line(line_to_scan)
            return counts_on_scan_line.transpose()[0]

        except Exception as e:
            self.log.error('The scan went wrong, killing the scanner.')
            self.stop_scanning()
            self.sigScanNextLine.emit()
            raise e

    def kill_scanner(self):
        """Closing the scanner device.

        @return int: error code (0:OK, -1:error)
        """
        try:
            self._scanning_device.close_scanner()
            self._scanning_device.close_scanner_clock()
        except Exception as e:
            self.log.exception('Could not even close the scanner, giving up.')
            raise e
        try:
            if self._scanning_device.module_state.can('unlock'):
                self._scanning_device.module_state.unlock()
        except:
            self.log.exception('Could not unlock scanning device.')
        return 0

    def save_data(self,
                  tag=None,
                  colorscale_range=None,
                  percentile_range=None):
        """ Save the counter trace data and writes it to a file.

        @return int: error code (0:OK, -1:error)
        """
        if tag is None:
            tag = ''

        self._saving_stop_time = time.time()

        filepath = self._save_logic.get_path_for_module(
            module_name='PowerScanning')
        filepath2 = self._save_logic.get_path_for_module(
            module_name='PowerScanning')
        filepath3 = self._save_logic.get_path_for_module(
            module_name='PowerScanning')
        timestamp = datetime.datetime.now()

        if len(tag) > 0:
            filelabel = tag + '_volt_data'
            filelabel2 = tag + '_volt_data_raw_trace'
            filelabel3 = tag + '_volt_data_raw_retrace'
        else:
            filelabel = 'volt_data'
            filelabel2 = 'volt_data_raw_trace'
            filelabel3 = 'volt_data_raw_retrace'

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        data['frequency (Hz)'] = self.plot_x
        data['trace count data (counts/s)'] = self.plot_y
        data['retrace count data (counts/s)'] = self.plot_y2

        data2 = OrderedDict()
        data2['count data (counts/s)'] = self.scan_matrix[:self.
                                                          _scan_counter_up, :]

        data3 = OrderedDict()
        data3[
            'count data (counts/s)'] = self.scan_matrix2[:self.
                                                         _scan_counter_down, :]

        parameters = OrderedDict()
        parameters['Number of frequency sweeps (#)'] = self._scan_counter_up
        parameters['Start Voltage (V)'] = self.scan_range[0]
        parameters['Stop Voltage (V)'] = self.scan_range[1]
        parameters['Scan speed [V/s]'] = self._scan_speed
        parameters['Clock Frequency (Hz)'] = self._clock_frequency

        fig = self.draw_figure(self.scan_matrix,
                               self.plot_x,
                               self.plot_y,
                               self.fit_x,
                               self.fit_y,
                               cbar_range=colorscale_range,
                               percentile_range=percentile_range)

        fig2 = self.draw_figure(self.scan_matrix2,
                                self.plot_x,
                                self.plot_y2,
                                self.fit_x,
                                self.fit_y,
                                cbar_range=colorscale_range,
                                percentile_range=percentile_range)

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   fmt='%.6e',
                                   delimiter='\t',
                                   timestamp=timestamp)

        self._save_logic.save_data(data2,
                                   filepath=filepath2,
                                   parameters=parameters,
                                   filelabel=filelabel2,
                                   fmt='%.6e',
                                   delimiter='\t',
                                   timestamp=timestamp,
                                   plotfig=fig)

        self._save_logic.save_data(data3,
                                   filepath=filepath3,
                                   parameters=parameters,
                                   filelabel=filelabel3,
                                   fmt='%.6e',
                                   delimiter='\t',
                                   timestamp=timestamp,
                                   plotfig=fig2)

        self.log.info('Power Scan saved to:\n{0}'.format(filepath))
        return 0

    def draw_figure(self,
                    matrix_data,
                    freq_data,
                    count_data,
                    fit_freq_vals,
                    fit_count_vals,
                    cbar_range=None,
                    percentile_range=None):
        """ Draw the summary figure to save with the data.

        @param: list cbar_range: (optional) [color_scale_min, color_scale_max].
                                 If not supplied then a default of data_min to data_max
                                 will be used.

        @param: list percentile_range: (optional) Percentile range of the chosen cbar_range.

        @return: fig fig: a matplotlib figure object to be saved to file.
        """

        # If no colorbar range was given, take full range of data
        if cbar_range is None:
            cbar_range = np.array([np.min(matrix_data), np.max(matrix_data)])
        else:
            cbar_range = np.array(cbar_range)

        prefix = ['', 'k', 'M', 'G', 'T']
        prefix_index = 0

        # Rescale counts data with SI prefix
        while np.max(count_data) > 1000:
            count_data = count_data / 1000
            fit_count_vals = fit_count_vals / 1000
            prefix_index = prefix_index + 1

        counts_prefix = prefix[prefix_index]

        # Rescale frequency data with SI prefix
        prefix_index = 0

        while np.max(freq_data) > 1000:
            freq_data = freq_data / 1000
            fit_freq_vals = fit_freq_vals / 1000
            prefix_index = prefix_index + 1

        mw_prefix = prefix[prefix_index]

        # Rescale matrix counts data with SI prefix
        prefix_index = 0

        while np.max(matrix_data) > 1000:
            matrix_data = matrix_data / 1000
            cbar_range = cbar_range / 1000
            prefix_index = prefix_index + 1

        cbar_prefix = prefix[prefix_index]

        # Use qudi style
        plt.style.use(self._save_logic.mpl_qd_style)

        # Create figure
        fig, (ax_mean, ax_matrix) = plt.subplots(nrows=2, ncols=1)

        ax_mean.plot(freq_data, count_data, linestyle=':', linewidth=0.5)

        # Do not include fit curve if there is no fit calculated.
        if max(fit_count_vals) > 0:
            ax_mean.plot(fit_freq_vals, fit_count_vals, marker='None')

        ax_mean.set_ylabel('Fluorescence (' + counts_prefix + 'c/s)')
        ax_mean.set_xlim(np.min(freq_data), np.max(freq_data))

        matrixplot = ax_matrix.imshow(
            matrix_data,
            cmap=plt.get_cmap('inferno'),  # reference the right place in qd
            origin='lower',
            vmin=cbar_range[0],
            vmax=cbar_range[1],
            extent=[
                np.min(freq_data),
                np.max(freq_data), 0, self.number_of_repeats
            ],
            aspect='auto',
            interpolation='nearest')

        ax_matrix.set_xlabel('Frequency (' + mw_prefix + 'Hz)')
        ax_matrix.set_ylabel('Scan #')

        # Adjust subplots to make room for colorbar
        fig.subplots_adjust(right=0.8)

        # Add colorbar axis to figure
        cbar_ax = fig.add_axes([0.85, 0.15, 0.02, 0.7])

        # Draw colorbar
        cbar = fig.colorbar(matrixplot, cax=cbar_ax)
        cbar.set_label('Fluorescence (' + cbar_prefix + 'c/s)')

        # remove ticks from colorbar for cleaner image
        cbar.ax.tick_params(which='both', length=0)

        # If we have percentile information, draw that to the figure
        if percentile_range is not None:
            cbar.ax.annotate(str(percentile_range[0]),
                             xy=(-0.3, 0.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90)
            cbar.ax.annotate(str(percentile_range[1]),
                             xy=(-0.3, 1.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90)
            cbar.ax.annotate('(percentile)',
                             xy=(-0.3, 0.5),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90)

        return fig
Пример #27
0
class DataSlice(QtWidgets.QWidget):
    """
    A DatSlice widget provides an inteface for selection
    slices through an N-dimensional dataset

    QtCore.Signals
    -------
    slice_changed : triggered when the slice through the data changes
    """
    slice_changed = QtCore.Signal()

    def __init__(self, data=None, parent=None):
        """
        :param data: :class:`~glue.core.data.Data` instance, or None
        """
        super(DataSlice, self).__init__(parent)
        self._slices = []
        self._data = None

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(4)
        layout.setContentsMargins(0, 3, 0, 3)
        self.layout = layout
        self.setLayout(layout)
        self.set_data(data)

    @property
    def ndim(self):
        return len(self.shape)

    @property
    def shape(self):
        return tuple() if self._data is None else self._data.shape

    def _clear(self):
        for _ in range(self.layout.count()):
            self.layout.takeAt(0)

        for s in self._slices:
            s.close()

        self._slices = []

    def set_data(self, data):
        """
        Change datasets

        :parm data: :class:`~glue.core.data.Data` instance
        """

        # remove old widgets
        self._clear()

        self._data = data

        if data is None or data.ndim < 3:
            return

        # create slider widget for each dimension...
        for i, s in enumerate(data.shape):

            # TODO: For now we simply pass a single set of world coordinates,
            # but we will need to generalize this in future. We deliberately
            # check the type of data.coords here since we want to treat
            # subclasses differently.
            if type(data.coords) != Coordinates:
                world = data.coords.world_axis(data, i)
                world_unit = data.coords.world_axis_unit(i)
                world_warning = len(data.coords.dependent_axes(i)) > 1
            else:
                world = None
                world_unit = None
                world_warning = False

            slider = SliceWidget(data.get_world_component_id(i).label,
                                 hi=s - 1,
                                 world=world,
                                 world_unit=world_unit,
                                 world_warning=world_warning)

            if i == self.ndim - 1:
                slider.mode = 'x'
            elif i == self.ndim - 2:
                slider.mode = 'y'
            else:
                slider.mode = 'slice'
            self._slices.append(slider)

            # save ref to prevent PySide segfault
            self.__on_slice = partial(self._on_slice, i)
            self.__on_mode = partial(self._on_mode, i)

            slider.slice_changed.connect(self.__on_slice)
            slider.mode_changed.connect(self.__on_mode)
            if s == 1:
                slider.freeze()

        # ... and add to the layout
        for s in self._slices[::-1]:
            self.layout.addWidget(s)
            if s is not self._slices[0]:
                line = QtWidgets.QFrame()
                line.setFrameShape(QtWidgets.QFrame.HLine)
                line.setFrameShadow(QtWidgets.QFrame.Sunken)
                self.layout.addWidget(line)
            s.show()  # this somehow fixes #342

        self.layout.addStretch(5)

    def _on_slice(self, index, slice_val):
        self.slice_changed.emit()

    def _on_mode(self, index, mode_index):
        s = self.slice

        def isok(ss):
            # valid slice description: 'x' and 'y' both appear
            c = Counter(ss)
            return c['x'] == 1 and c['y'] == 1

        if isok(s):
            self.slice_changed.emit()
            return

        for i in range(len(s)):
            if i == index:
                continue
            if self._slices[i].frozen:
                continue

            for mode in 'x', 'y', 'slice':
                if self._slices[i].mode == mode:
                    continue

                ss = list(s)
                ss[i] = mode
                if isok(ss):
                    self._slices[i].mode = mode
                    return

        else:
            raise RuntimeError("Corrupted Data Slice")

    @property
    def slice(self):
        """
        A description of the slice through the dataset

        A tuple of lenght equal to the dimensionality of the data

        Each element is an integer, 'x', or 'y'
        'x' and 'y' indicate the horizontal and vertical orientation
        of the slice
        """
        if self.ndim < 3:
            return {0: tuple(), 1: ('x', ), 2: ('y', 'x')}[self.ndim]

        return tuple(s.mode if s.mode != 'slice' else s.slice_center
                     for s in self._slices)

    @slice.setter
    def slice(self, value):
        for v, s in zip(value, self._slices):
            if v in ['x', 'y']:
                s.mode = v
            else:
                s.mode = 'slice'
                s.slice_center = v
Пример #28
0
class BasicToolbar(QtWidgets.QToolBar):

    tool_activated = QtCore.Signal()
    tool_deactivated = QtCore.Signal()

    def __init__(self, parent, default_mouse_mode_cls=None):
        """
        Create a new toolbar object
        """

        super(BasicToolbar, self).__init__(parent=parent)

        self.actions = {}
        self.tools = {}
        self.setIconSize(QtCore.QSize(25, 25))
        self.layout().setSpacing(1)
        self.setFocusPolicy(Qt.StrongFocus)
        self._active_tool = None
        self._default_mouse_mode_cls = default_mouse_mode_cls
        self._default_mouse_mode = None
        self.setup_default_modes()

    def setup_default_modes(self):
        if self._default_mouse_mode_cls is not None:
            self._default_mouse_mode = self._default_mouse_mode_cls(
                self.parent())
            self._default_mouse_mode.activate()

    @property
    def active_tool(self):
        return self._active_tool

    @active_tool.setter
    def active_tool(self, new_tool):

        old_tool = self._active_tool

        # If the tool is as before, we don't need to do anything
        if old_tool is new_tool:
            return

        # Otheriwse, if the tool changes, then we need to disable the previous
        # tool...
        if old_tool is not None:
            self.deactivate_tool(old_tool)
            if isinstance(old_tool, CheckableTool):
                button = self.actions[old_tool.tool_id]
                if button.isChecked():
                    button.blockSignals(True)
                    button.setChecked(False)
                    button.blockSignals(False)

        # ... and enable the new one
        if new_tool is not None:
            self.activate_tool(new_tool)
            if isinstance(new_tool, CheckableTool):
                button = self.actions[new_tool.tool_id]
                if not button.isChecked():
                    button.blockSignals(True)
                    button.setChecked(True)
                    button.blockSignals(False)

        if isinstance(new_tool, CheckableTool):
            self._active_tool = new_tool
            self.parent().set_status(new_tool.status_tip)
            self.tool_activated.emit()
        else:
            self._active_tool = None
            self.parent().set_status('')
            self.tool_deactivated.emit()

    def activate_tool(self, tool):
        if self._default_mouse_mode is not None:
            self._default_mouse_mode.deactivate()
        tool.activate()

    def deactivate_tool(self, tool):
        if isinstance(tool, CheckableTool):
            tool.deactivate()
        if self._default_mouse_mode is not None:
            self._default_mouse_mode.activate()

    def add_tool(self, tool):

        parent = QtWidgets.QToolBar.parent(self)

        if isinstance(tool.icon, six.string_types):
            if os.path.exists(tool.icon):
                icon = QtGui.QIcon(tool.icon)
            else:
                icon = get_icon(tool.icon)
        else:
            icon = tool.icon

        action = QtWidgets.QAction(icon, tool.action_text, parent)

        def toggle(checked):
            if checked:
                self.active_tool = tool
            else:
                self.active_tool = None

        def trigger(checked):
            self.active_tool = tool

        parent.addAction(action)

        if isinstance(tool, CheckableTool):
            action.toggled.connect(toggle)
        else:
            action.triggered.connect(trigger)

        shortcut = None

        if tool.shortcut is not None:

            # Make sure that the keyboard shortcut is unique
            for m in self.tools.values():
                if tool.shortcut == m.shortcut:
                    warnings.warn(
                        "Tools '{0}' and '{1}' have the same shortcut "
                        "('{2}'). Ignoring shortcut for "
                        "'{1}'".format(m.tool_id, tool.tool_id, tool.shortcut))
                    break
            else:
                shortcut = tool.shortcut
                action.setShortcut(tool.shortcut)
                action.setShortcutContext(Qt.WidgetShortcut)

        if shortcut is None:
            action.setToolTip(tool.tool_tip)
        else:
            action.setToolTip(tool.tool_tip +
                              " [shortcut: {0}]".format(shortcut))

        action.setCheckable(isinstance(tool, CheckableTool))
        self.actions[tool.tool_id] = action

        menu_actions = tool.menu_actions()
        if len(menu_actions) > 0:
            menu = QtWidgets.QMenu(self)
            for ma in tool.menu_actions():
                ma.setParent(self)
                menu.addAction(ma)
            action.setMenu(menu)
            menu.triggered.connect(trigger)

        self.addAction(action)

        # Bind tool visibility to tool.enabled
        def toggle(state):
            action.setVisible(state)
            action.setEnabled(state)

        add_callback(tool, 'enabled', toggle)

        self.tools[tool.tool_id] = tool

        return action

    def cleanup(self):
        # We need to make sure we set _default_mouse_mode to None otherwise
        # we keep a reference to the viewer (parent) inside the mouse mode,
        # creating a circular reference.
        self._default_mouse_mode = None
        self.active_tool = None
Пример #29
0
class Canvas(QtWidgets.QWidget):

    zoomRequest = QtCore.Signal(int, QtCore.QPoint)
    scrollRequest = QtCore.Signal(int, int)
    newShape = QtCore.Signal()
    selectionChanged = QtCore.Signal(bool)
    shapeMoved = QtCore.Signal()
    drawingPolygon = QtCore.Signal(bool)
    edgeSelected = QtCore.Signal(bool)

    CREATE, EDIT = 0, 1

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.shapesBackups = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QtGui.QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QtCore.QPointF()
        self.prevMovePoint = QtCore.QPointF()
        self.offsets = QtCore.QPointF(), QtCore.QPointF()
        self.scale = 1.0
        self.pixmap = QtGui.QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self.hEdge = None
        self.movingShape = False
        self._painter = QtGui.QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QtWidgets.QMenu(), QtWidgets.QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)

    def storeShapes(self):
        shapesBackup = []
        for shape in self.shapes:
            shapesBackup.append(shape.copy())
        if len(self.shapesBackups) >= 10:
            self.shapesBackups = self.shapesBackups[-9:]
        self.shapesBackups.append(shapesBackup)

    @property
    def isShapeRestorable(self):
        if len(self.shapesBackups) < 2:
            return False
        return True

    def restoreShape(self):
        if not self.isShapeRestorable:
            return
        self.shapesBackups.pop()  # latest
        shapesBackup = self.shapesBackups.pop()
        self.shapes = shapesBackup
        self.storeShapes()
        self.repaint()

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        if QT5:
            pos = self.transformPos(ev.pos())
        else:
            pos = self.transformPos(ev.posF())

        self.prevMovePoint = pos
        self.restoreCursor()

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and \
                        self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and
                    # colorise to alert the user.
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[0] = self.current[-1]
                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()
            return

        # Polygon copy moving.
        if QtCore.Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        self.movingShape = False
        if QtCore.Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.repaint()
                self.movingShape = True
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.repaint()
                self.movingShape = True
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            index_edge = shape.nearestEdge(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex = index
                self.hShape = shape
                self.hEdge = index_edge
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex = None
                self.hShape = shape
                self.hEdge = index_edge
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape, self.hEdge = None, None, None
        self.edgeSelected.emit(self.hEdge is not None)

    def addPointToEdge(self):
        if (self.hShape is None and self.hEdge is None
                and self.prevMovePoint is None):
            return
        shape = self.hShape
        index = self.hEdge
        point = self.prevMovePoint
        shape.insertPoint(index, point)
        shape.highlightVertex(index, shape.MOVE_VERTEX)
        self.hShape = shape
        self.hVertex = index
        self.hEdge = None

    def mousePressEvent(self, ev):
        if QT5:
            pos = self.transformPos(ev.pos())
        else:
            pos = self.transformPos(ev.posF())
        if ev.button() == QtCore.Qt.LeftButton:
            if self.drawing():
                if self.current:
                    self.current.addPoint(self.line[1])
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()
                elif not self.outOfPixmap(pos):
                    self.current = Shape()
                    self.current.addPoint(pos)
                    self.line.points = [pos, pos]
                    self.setHiding()
                    self.drawingPolygon.emit(True)
                    self.update()
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == QtCore.Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == QtCore.Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == QtCore.Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        if self.movingShape:
            self.storeShapes()
            self.shapeMoved.emit()

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        # del shape.fill_color
        # del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            shape.label = self.selectedShape.label
            self.deleteSelected()
            self.shapes.append(shape)
        self.storeShapes()
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)
        shape.moveVertexBy(index, pos - point)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QtCore.QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QtCore.QPointF(min(0,
                                      self.pixmap.width() - o2.x()),
                                  min(0,
                                      self.pixmap.height() - o2.y()))
        # XXX: The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason.
        # self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.storeShapes()
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            self.storeShapes()
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QtCore.QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing)
        p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
        p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self._hideBackround) and \
                    self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical ones."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        if QT5:
            return QtCore.QPoint(x, y)
        else:
            return QtCore.QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.storeShapes()
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        # d = distance(p1 - p2)
        # m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QtCore.QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QtCore.QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QtCore.QPointF(x, y)

    def intersectingEdges(self, point1, point2, points):
        """Find intersecting edges.

        For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen.
        """
        (x1, y1) = point1
        (x2, y2) = point2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QtCore.QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QtCore.QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        if QT5:
            mods = ev.modifiers()
            delta = ev.angleDelta()
            if QtCore.Qt.ControlModifier == int(mods):
                # with Ctrl/Command key
                # zoom
                self.zoomRequest.emit(delta.y(), ev.pos())
            else:
                # scroll
                self.scrollRequest.emit(delta.x(), QtCore.Qt.Horizontal)
                self.scrollRequest.emit(delta.y(), QtCore.Qt.Vertical)
        else:
            if ev.orientation() == QtCore.Qt.Vertical:
                mods = ev.modifiers()
                if QtCore.Qt.ControlModifier == int(mods):
                    # with Ctrl/Command key
                    self.zoomRequest.emit(ev.delta(), ev.pos())
                else:
                    self.scrollRequest.emit(
                        ev.delta(), QtCore.Qt.Horizontal if
                        (QtCore.Qt.ShiftModifier
                         == int(mods)) else QtCore.Qt.Vertical)
            else:
                self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == QtCore.Qt.Key_Escape and self.current:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == QtCore.Qt.Key_Return and self.canCloseShape():
            self.finalise()

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        self.shapesBackups.pop()
        self.storeShapes()
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def undoLastPoint(self):
        if not self.current or self.current.isClosed():
            return
        self.current.popPoint()
        if len(self.current) > 0:
            self.line[0] = self.current[-1]
        else:
            self.current = None
            self.drawingPolygon.emit(False)
        self.repaint()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.storeShapes()
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QtWidgets.QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QtWidgets.QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.shapesBackups = []
        self.update()
Пример #30
0
class ElementPickerWidget(FigureWidget):
    """
    Tool window for picking elements of an interactive periodic table.
    Takes a signal in the constructor, and a parent control.
    """
    element_toggled = QtCore.Signal(str)

    def __init__(self, main_window, parent):
        super(ElementPickerWidget, self).__init__(main_window, parent)
        self.signal = None
        self.create_controls()
        self.table.element_toggled.connect(self._toggle_element)
        self.set_signal(self.signal)
        self._only_lines = ['Ka', 'La', 'Ma', 'Kb', 'Lb1', 'Mb']

    def _on_figure_change(self, figure):
        super(ElementPickerWidget, self)._on_figure_change(figure)
        signal = win2sig(figure, plotting_signal=self.ui._plotting_signal)
        self.set_signal(signal)

    def set_signal(self, signal):
        self.signal = signal
        self.setWindowTitle(tr("Element picker"))

        if self.isEDS():
            self.map_btn.show()
        else:
            self.map_btn.hide()

        if signal is None or signal.signal is None:
            self.set_enabled(False)
            return
        else:
            self.set_enabled(True)

        # Enable markers if plot has any
        with block_signals(self.chk_markers):
            markers = (hasattr(signal.signal, '_xray_markers')
                       and bool(signal.signal._xray_markers))
            self.chk_markers.setChecked(markers)

        # Make sure we have the Sample node, and Sample.elements
        if not hasattr(signal.signal.metadata, 'Sample'):
            signal.signal.metadata.add_node('Sample')
        if not hasattr(signal.signal.metadata.Sample, 'elements'):
            signal.signal.metadata.Sample.elements = []

        self._set_elements(signal.signal.metadata.Sample.elements)

        # Disable elements which hyperspy does not accept
        hsyp_elem = list(elements_db.keys())
        for w in self.table.children():
            if isinstance(w, ExClickLabel):
                elem = w.text()
                if elem not in hsyp_elem:
                    self.table.disable_element(elem)
                else:
                    self.table.enable_element(elem)

    def set_enabled(self, value):
        self.setEnabled(value)

    @property
    def markers(self):
        return self.chk_markers.isChecked()

    def isEDS(self):
        if self.signal is None or self.signal.signal is None:
            return False
        return isinstance(self.signal.signal, edstypes)

    def isEELS(self):
        if self.signal is None or self.signal.signal is None:
            return False
        return isinstance(self.signal.signal, hyperspy.signals.EELSSpectrum)

    def _toggle_element(self, element):
        """
        Makes sure the element is toggled correctly for both EDS and EELS.
        Dependent on hyperspy implementation, as there are currently no
        remove_element functions.
        """
        if self.isEELS():
            self._toggle_element_eels(element)
        elif self.isEDS():
            self._toggle_element_eds(element)

    def _toggle_element_eds(self, element):
        s = self.signal.signal
        lines_added = []
        lines_removed = []
        self.ui.record_code("signal = ui.get_selected_signal()")
        if element in s.metadata.Sample.elements:
            # Element present, we're removing it
            s.metadata.Sample.elements.remove(element)
            self.ui.record_code(
                "signal.metadata.Sample.elements.remove('%s')" % element)
            if 'Sample.xray_lines' in s.metadata:
                for line in reversed(s.metadata.Sample.xray_lines):
                    if line.startswith(element):
                        s.metadata.Sample.xray_lines.remove(line)
                        lines_removed.append(line)
                if len(s.metadata.Sample.xray_lines) < 1:
                    del s.metadata.Sample.xray_lines
            else:
                lines_removed.extend(
                    s._get_lines_from_elements([element],
                                               only_one=False,
                                               only_lines=self._only_lines))
        else:
            lines_added = s._get_lines_from_elements(
                [element], only_one=False, only_lines=self._only_lines)
            if 'Sample.xray_lines' in s.metadata:
                s.add_lines(lines_added)  # Will also add element
                self.ui.record_code("signal.add_lines(%s)" % str(lines_added))
            else:
                s.add_elements((element, ))
                self.ui.record_code("signal.add_elements(%s)" % str([element]))
        if self.markers:
            if lines_added:
                s.add_xray_lines_markers(lines_added)
            if lines_removed:
                s.remove_xray_lines_markers(lines_removed)

    def _toggle_element_eels(self, element):
        s = self.signal.signal
        self.ui.record_code("signal = ui.get_selected_signal()")
        if element in s.metadata.Sample.elements:
            s.elements.remove(element)
            s.subshells = set()
            s.add_elements([])  # Will set metadata.Sample.elements
            self.ui.record_code("signal.elements.remove('%s')" % str(element))
            self.ui.record_code("signal.subshells = set()")
            self.ui.record_code("signal.add_elements([])")
        else:
            s.add_elements((element, ))
            self.ui.record_code("signal.add_elements(%s)" % str([element]))

    def _toggle_subshell(self, subshell, checked):
        if not self.isEDS():
            return
        s = self.signal.signal
        element, ss = subshell.split('_')

        # Figure out whether element should be toggled
        active, _ = self._get_element_subshells(element)
        if checked:
            any_left = True
        elif ss in active:
            active.remove(ss)
            any_left = len(active) > 0

        # If any(subshells toggled) != element toggled, we should toggle
        # element
        if self.table.toggled[element] != any_left:
            # Update table toggle
            self.table.toggle_element(element)
            # Update signal state
            if not any_left:
                # Remove element
                self._toggle_element(element)

        self.ui.record_code("signal = ui.get_selected_signal()")
        if 'Sample.xray_lines' not in s.metadata and len(active) > 0:
            lines = [element + '_' + a for a in active]
            s.add_lines(lines)
            self.ui.record_code("signal.add_lines(%s)" % str(lines))
            if self.markers:
                if checked:
                    s.add_xray_lines_markers(lines)
                else:
                    s.remove_xray_lines_markers([subshell])
        else:
            if checked:
                s.add_lines([subshell])
                self.ui.record_code("signal.add_lines(%s)" % str([subshell]))
                if self.markers:
                    s.add_xray_lines_markers([subshell])
            elif 'Sample.xray_lines' in s.metadata:
                if subshell in s.metadata.Sample.xray_lines:
                    s.metadata.Sample.xray_lines.remove(subshell)
                    self.ui.record_code(
                        "signal.metadata.Sample.xray_lines.remove('%s')" %
                        str(subshell))
                    if self.markers:
                        s.remove_xray_lines_markers([subshell])
                # If all lines are disabled, fall back to element defined
                # (Not strictly needed)
                if len(s.metadata.Sample.xray_lines) < 1:
                    del s.metadata.Sample.xray_lines
                    self.ui.record_code(
                        "del signal.metadata.Sample.xray_lines")

    def _set_elements(self, elements):
        """
        Sets the table elements to passed parameter. Does not modify elements
        in signal! That is handled by the _toggle_element* functions
        """
        self.table.set_elements(elements)

    def set_element(self, element, value):
        """Sets the state of element in table and adds/removes in signal if
        necessary
        """
        self.table.set_element(element, value)
        s = self.signal.signal
        if (element in s.metadata.Sample.elements) != value:
            self._toggle_element(element)

    def _on_toggle_markers(self, value):
        """Toggles peak markers on the plot, i.e. adds/removes markers for all
        elements added on signal.
        """
        w = self.signal
        s = self.signal.signal
        if value:
            if self.isEDS():
                w.keep_on_close = True
                s._plot_xray_lines(xray_lines=True)
                w.update_figures()
                w.keep_on_close = False
        else:
            if self.isEDS():
                for m in reversed(s._plot.signal_plot.ax_markers):
                    m.close(render_figure=False)
                if hasattr(s, '_xray_markers'):
                    s._xray_markers.clear()

    def make_map(self):
        """
        Make integrated intensity maps for the defines elements. Currently
        only implemented for EDS signals.
        """
        if self.isEELS():
            pass  # TODO: EELS maps
        elif self.isEDS():
            imgs = self.signal.signal.get_lines_intensity(only_one=False)
            for im in imgs:
                im.plot()

    def _get_element_subshells(self, element, include_pre_edges=False):
        s = self.signal.signal
        subshells = []
        possible_subshells = []
        if self.isEELS():
            subshells[:] = [ss.split('_')[0] for ss in s.subshells]

            Eaxis = s.axes_manager.signal_axes[0].axis
            if not include_pre_edges:
                start_energy = Eaxis[0]
            else:
                start_energy = 0.
            end_energy = Eaxis[-1]
            for shell in elements_db[element]['Atomic_properties'][
                    'Binding_energies']:
                if shell[-1] != 'a':
                    if start_energy <= \
                            elements_db[element]['Atomic_properties'][
                            'Binding_energies'][shell]['onset_energy (eV)'] \
                            <= end_energy:
                        possible_subshells.add(shell)
        elif self.isEDS():
            if 'Sample.xray_lines' in s.metadata:
                xray_lines = s.metadata.Sample.xray_lines
                for line in xray_lines:
                    c_element, subshell = line.split("_")
                    if c_element == element:
                        subshells.append(subshell)
            elif ('Sample.elements' in s.metadata
                  and element in s.metadata.Sample.elements):
                xray_lines = s._get_lines_from_elements(
                    [element], only_one=False, only_lines=self._only_lines)
                for line in xray_lines:
                    _, subshell = line.split("_")
                    subshells.append(subshell)
            possible_xray_lines = \
                s._get_lines_from_elements([element], only_one=False,
                                           only_lines=self._only_lines)
            for line in possible_xray_lines:
                _, subshell = line.split("_")
                possible_subshells.append(subshell)

        return (subshells, possible_subshells)

    def element_context(self, widget, point):
        if not self.isEDS():
            return
        cm = QtWidgets.QMenu()
        element = widget.text()
        active, possible = self._get_element_subshells(element)
        for ss in possible:
            key = element + '_' + ss
            ac = cm.addAction(ss)
            ac.setCheckable(True)
            ac.setChecked(ss in active)
            f = partial(self._toggle_subshell, key)
            ac.toggled[bool].connect(f)
        if possible:
            cm.exec_(widget.mapToGlobal(point))

    def create_controls(self):
        """
        Create UI controls.
        """
        self.table = PeriodicTableWidget(self)
        self.table.element_toggled.connect(self.element_toggled)  # Forward
        for w in self.table.children():
            if not isinstance(w, ExClickLabel):
                continue
            w.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
            f = partial(self.element_context, w)
            w.customContextMenuRequested[QtCore.QPoint].connect(f)

        self.chk_markers = QtWidgets.QCheckBox(tr("Markers"))
        self.chk_markers.toggled[bool].connect(self._on_toggle_markers)
        self.map_btn = QtWidgets.QPushButton(tr("Map"))
        self.map_btn.clicked.connect(self.make_map)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(self.table)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(self.chk_markers)
        hbox.addWidget(self.map_btn)
        vbox.addLayout(hbox)

        w = QtWidgets.QWidget()
        w.setLayout(vbox)
        self.setWidget(w)