Beispiel #1
0
class NonExclusivePushButtons2(gpi.NonExclusivePushButtons):
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(NonExclusivePushButtons2, self).__init__(title, parent)

    # overwrite set_val
    def set_val(self, value):
        if type(value) is not list:
            value = [value]

        # remove any values that don't have a corresponding button
        names = [button.text() for button in self.buttons]
        temp = []
        for i in range(len(value)):
            if value[i] in names:
                temp.append(value[i])
        value = temp

        self._value = value
        for button_ind in range(len(self.buttons)):
            name = self.buttons[button_ind].text()
            if name in value:
                self.buttons[button_ind].setChecked(True)
            else:
                self.buttons[button_ind].setChecked(False)

    # overwrite findValue
    def findValue(self, value):
        valarr = []
        for button in self.buttons:
            if button.isChecked():
                valarr.append(button.text())
        self._value = valarr
        return
Beispiel #2
0
class GPITabBar(QtWidgets.QTabBar):
    currentChanged = gpi.Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._labels_at_press = None

    def clearTabs(self):
        while self.count() > 0:
            self.removeTab(0)

    def addTabsFromList(self, names):
        for i in range(len(names)):
            self.addTab(str(names[i]))

    def labels(self):
        return [str(self.tabText(i)) for i in range(self.count())]

    def mousePressEvent(self, event):
        self._labels_at_press = self.labels()
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        if self.labels() != self._labels_at_press:
            self.currentChanged.emit()
Beispiel #3
0
class OrderButtons(gpi.GenericWidgetGroup):
    """A set of reorderable tabs."""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        # at least one button
        wdgLayout = QtWidgets.QGridLayout()
        self.wdg = GPITabBar()
        self.wdg.addTab('0')
        self.wdg.setMovable(True)
        self.wdg.currentChanged.connect(self.valueChanged.emit)
        # layout
        wdgLayout.addWidget(self.wdg)
        self.setLayout(wdgLayout)

    # setters
    def set_val(self, names):
        """list(str,str,...) | A list of labels (e.g. ['b1', 'b2',...])."""
        self.wdg.clearTabs()
        self.wdg.addTabsFromList(names)

    # getters
    def get_val(self):
        return self.wdg.labels()
Beispiel #4
0
class GPIState(QtCore.QObject):
    """A single FSM state to be used with the GPI_FSM class.  It manages its
    transitions, entry and exit functions.
    """
    entered = gpi.Signal()
    exited = gpi.Signal()

    def __init__(self, name, func, machine=None, efunc=None):
        super(GPIState, self).__init__()
        if not isinstance(name, str):
            msg = "expecting str arg: GPIState.__init__(>str<,func,GPI_FSM)"
            log.critical(msg)
        if machine:
            if not isinstance(machine, GPI_FSM):
                msg = "expecting GPI_FSM arg: GPIState.__init__(str,func,>GPI_FSM<)"
                log.critical(msg)
        self._name = str(name)
        self._transitions = {}
        self._func = func  # on entry
        self._efunc = efunc  # on exit
        if machine:
            machine.addState(self)

    def addTransition(self, sig, state):
        if not isinstance(sig, str):
            msg = "expecting str arg: GPIState.addTransition(>str<,GPIState)"
            log.critical(msg)
        self._transitions[sig] = state

    @property
    def name(self):
        return self._name

    def transitions(self):
        return self._transitions

    def onEntry(self, sig):
        self.entered.emit()
        if self._func:
            self._func(sig)

    def onExit(self, sig):
        self.exited.emit()
        if self._efunc:
            self._efunc(sig)
Beispiel #5
0
class ATask(QtCore.QObject):
    '''App-Loop or Main-Loop executed task.  This will block GUI updates.  Data
    is communicated directly.

        NOTE: The apploop-type blocks until finished, obviating the need for
        signals or timer checks
    '''

    finished = gpi.Signal()
    terminated = gpi.Signal()

    def __init__(self, func, title, label, proxy):
        super(ATask, self).__init__()
        self._func = func
        self._title = title
        self._label = label
        self._proxy = proxy
        self._cnt = 0

    def run(self):
        # This try/except is only good for catching compute() exceptions
        # not run() terminations.
        try:
            self._retcode = self._func()
        except:
            log.error('APPLOOP: \'' + str(self._title) + '\':\'' +
                      str(self._label) + '\' compute() failed.\n' +
                      str(traceback.format_exc()))
            self._retcode = Return.ComputeError

    def terminate(self):
        pass  # can't happen b/c blocking mainloop

    def wait(self):
        pass  # can't happen b/c blocking mainloop

    def isRunning(self):
        pass  # can't happen b/c blocking mainloop

    def quit(self):
        pass

    def start(self):
        self.run()
        self.finished.emit()
Beispiel #6
0
class StringBoxes(gpi.GenericWidgetGroup):
    """A set of editable string boxes"""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(StringBoxes, self).__init__(title, parent)

        # at least one string box
        self.boxes = []
        self.boxes.append(QtWidgets.QLineEdit())
        self.vbox = QtWidgets.QVBoxLayout()
        self.vbox.setSpacing(0)
        for box in self.boxes:
            self.vbox.addWidget(box)
            box.returnPressed.connect(self.valueChanged)
        self.setLayout(self.vbox)

    # setters
    def set_strings(self, strlist):
        """str | Set the string (str)."""
        ind = 0
        if len(strlist) < len(self.boxes):
            strlendiff = len(self.boxes) - len(strlist)
            strlist.extend(strlendiff * [''])
            log.warn("StringBoxes: list of strings shorter than number of \
                boxes. Filling rest with empty strings.")

        if len(strlist) > len(self.boxes):
            log.warn("StringBoxes: list of strings longer than number of \
                boxes. Only using first portion of strings.")

        for box in self.boxes:
            string = strlist[ind]
            box.setText(string)
            ind = ind + 1

    def set_length(self, length):
        # add specified number of string boxes
        while length > len(self.boxes):
            newbox = QtWidgets.QLineEdit()
            self.boxes.append(newbox)
            newbox.returnPressed.connect(self.valueChanged)
            self.vbox.addWidget(newbox)
        while length < len(self.boxes):
            oldbox = self.boxes.pop()
            oldbox.setParent(None)

        if length != len(self.boxes):
            log.critical("StringBoxes: length not properly set!")

    def get_strings(self):
        strings = []
        for box in self.boxes:
            strings.append(box.text())
        return strings
Beispiel #7
0
class MainWin_close(QtWidgets.QMainWindow):
    window_closed = gpi.Signal()
    def __init__(self):
        super().__init__()
        self._isActive = True

    def closeEvent(self, event):
        super().closeEvent(event)
        self.window_closed.emit()
        self._isActive = False

    def isActive(self):
        return self._isActive
Beispiel #8
0
class Tee(QtCore.QObject):
    '''(Mostly Unused) An attempt at providing console output to a GPI internal
    console window.  This is part of an unfinished feature that is meant to
    give the user easy access to logging information. '''

    newStreamTxt = gpi.Signal(str)

    def __init__(self, stdIO=None, parent=None):
        """Redirect to a pyqtSignal and stdIO stream.
        stdIO = alternate stream ( can be the original sys.stdout )
        """
        super(Tee, self).__init__(parent)
        self._stdIO = stdIO
        self._fromProc = False

    def setMultiProc(self, val=True):
        self._fromProc = val

    def isMultiProc(self):
        return self._fromProc

    def write(self, m):
        #if self._color and self._edit:
        #    tc = self._edit.textColor()
        #    self._edit.setTextColor(self._color)
        #
        #        if self._edit:
        #            self._edit.moveCursor(QtGui.QTextCursor.End)
        #            self._edit.insertPlainText( m )
        #
        #        if self._color and self._edit:
        #            self._edit.setTextColor(tc)
        #
        if not self.isMultiProc():
            self.newStreamTxt.emit(m)

        if self._stdIO:
            self._stdIO.write(m)

    def flush(self):
        self._stdIO.flush()
Beispiel #9
0
class PTask(multiprocessing_context.Process, QtCore.QObject):
    '''A forked process node task. Memmaps are used to communicate data.

    NOTE: The process-type has to be checked periodically to see if its alive,
    from the spawning process.
    '''

    finished = gpi.Signal()
    terminated = gpi.Signal()

    def __init__(self, func, title, label, proxy):
        multiprocessing_context.Process.__init__(self)
        QtCore.QObject.__init__(self)
        self._func = func
        self._title = title
        self._label = label
        self._proxy = proxy
        self._cnt = 0

        # Since we don't know when the process finishes
        # probe at regular intervals.
        # -it would be nicer to have the process check-in with the GPI
        #  main proc when its done.
        self._timer = QtCore.QTimer()
        self._timer.timeout.connect(self.checkProcess)
        self._timer.start(10)  # 10msec update

    def run(self):
        # This try/except is only good for catching compute() exceptions
        # not run() terminations.
        try:
            self._proxy.append(['retcode', self._func()])
        except:
            log.error('PROCESS: \'' + str(self._title) + '\':\'' +
                      str(self._label) + '\' compute() failed.\n' +
                      str(traceback.format_exc()))
            self._proxy.append(['retcode', Return.ComputeError])

    def terminate(self):
        self._timer.stop()
        super(PTask, self).terminate()

    def wait(self):
        self.join()

    def isRunning(self):
        return self.is_alive()

    def retcodeExists(self):
        for o in self._proxy:
            if o[0] == 'retcode':
                return True
        return False

    def checkProcess(self):
        if self.is_alive():
            return
        # else if its not alive:
        self._timer.stop()
        if self.retcodeExists():
            # we assume its termination was deliberate.
            self.finished.emit()
        else:
            self.terminated.emit()
Beispiel #10
0
class ComboBox_GROUP(gpi.GenericWidgetGroup):
    """A combination of two comboBoxes for image quality measure of two images next to each other.
        """
    valueChanged = gpi.Signal()
    
    def __init__(self, title, parent=None):
        super(ComboBox_GROUP, self).__init__(title, parent)
        
        self._val = {}
        self._val['image_quality_left'] = 0
        self._val['image_quality_right'] = 0

        #image_quality_left
        self.iql = gpi.ComboBox('image_quality_left')
        self.iql.set_items(['choose..', 'excellent', 'good', 'diagnostic', 'non-diagnostic'])
        #self.iql.set_val('choose..')
        self.iql.set_index(0)

        #image_quality_right
        self.iqr = gpi.ComboBox('image_quality_right')
        self.iqr.set_items(['choose..', 'excellent', 'good', 'diagnostic', 'non-diagnostic'])
        #self.iqr.set_val('choose..')
        self.iqr.set_index(0)

        self.iql.valueChanged.connect(self.iqlChange)
        self.iqr.valueChanged.connect(self.iqrChange)
        
        vbox = QtGui.QHBoxLayout()
        vbox.addWidget(self.iql)
        vbox.addWidget(self.iqr)
        vbox.setStretch(0, 0)
        vbox.setStretch(1, 0)
        vbox.setContentsMargins(0, 0, 0, 0)  # we don't need margins here
        vbox.setSpacing(0)
        self.setLayout(vbox)

    # setters
    def set_val(self, val):
      """A python-dict containing: image_quality_left and image_quality_right parms. """
      if 'image_quality_left' in val:
        # check if identical so this does not change every time compute is called (according to code in FFTW)
        if self._val['image_quality_left'] != val['image_quality_left']:
          self._val['image_quality_left'] = val['image_quality_left']
          self.iql.blockSignals(True)
          self.iql.set_index(val['image_quality_left'])
          self.iql.blockSignals(False)

      if 'image_quality_right' in val:
        if self._val['image_quality_right'] != val['image_quality_right']:
          self._val['image_quality_right'] = val['image_quality_right']
          self.iqr.blockSignals(True)
          self.iqr.set_index(val['image_quality_right'])
          self.iqr.blockSignals(False)

    # getters
    def get_val(self):
      return self._val

    # support
    def iqlChange(self, val):
      self._val['image_quality_left'] = val
      self.valueChanged.emit()

    def iqrChange(self, val):
      self._val['image_quality_right'] = val
      self.valueChanged.emit()
Beispiel #11
0
class DICOM_HDR_GROUP(gpi.GenericWidgetGroup):
    """A combination of Textboxes and StringBoxes to allow for easy display of
    DICOM header info and editing of the values"""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(DICOM_HDR_GROUP, self).__init__(title, parent)
        self._val = {}
        self._val['tags'] = ''
        self._val['desc'] = ''
        self._val['VRs'] = ''
        self._val['values'] = ''

        self.tb1 = TextBoxes('Tags:')
        self.tb1.set_length(1)
        self.tb2 = TextBoxes('Descriptions:')
        self.tb2.set_length(1)
        self.tb3 = TextBoxes('VRs:')
        self.tb3.set_length(1)

        self.sb = StringBoxes('Values:')
        self.sb.set_length(1)
        self.sb.valueChanged.connect(self.valueChanged)

        vbox = QtWidgets.QHBoxLayout()
        vbox.addWidget(self.tb1)
        vbox.addWidget(self.tb2)
        vbox.addWidget(self.tb3)
        vbox.addWidget(self.sb)
        vbox.setStretch(0, 0)
        vbox.setStretch(1, 0)
        vbox.setStretch(2, 0)
        vbox.setStretch(3, 0)
        vbox.setContentsMargins(0, 0, 0, 0)  # we don't need margins here
        vbox.setSpacing(0)
        self.setLayout(vbox)

    # setters
    def set_info(self, val):
        """A python-dict containing: tags, desc, and VRs parms. """
        if 'tags' in val:
            self._val['tags'] = val['tags']
            self.setTagsQuietly(val['tags'])
        if 'desc' in val:
            self._val['desc'] = val['desc']
            self.setDescriptionsQuietly(val['desc'])
        if 'VRs' in val:
            self._val['VRs'] = val['VRs']
            self.setVRsQuietly(val['VRs'])
        if 'values' in val:
            self._val['values'] = val['values']
            self.setValuesQuietly(val['values'])

    def set_length(self, length):
        self.tb1.set_length(length)
        self.tb2.set_length(length)
        self.tb3.set_length(length)
        self.sb.set_length(length)

    # getters
    def get_val(self):
        return self.sb.get_strings()

    # support
    def setTagsQuietly(self, val):
        self.tb1.blockSignals(True)
        self.tb1.set_strings(val)
        self.tb1.blockSignals(False)

    def setDescriptionsQuietly(self, val):
        self.tb2.blockSignals(True)
        self.tb2.set_strings(val)
        self.tb2.blockSignals(False)

    def setVRsQuietly(self, val):
        self.tb3.blockSignals(True)
        self.tb3.set_strings(val)
        self.tb3.blockSignals(False)

    def setValuesQuietly(self, val):
        self.sb.blockSignals(True)
        self.sb.set_strings(val)
        self.sb.blockSignals(False)
Beispiel #12
0
class RESHAPE_GROUP(gpi.GenericWidgetGroup):
    """Widget for entering a new shape via spin boxes"""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        # buttons for adding/removing a box
        self.sub = gpi.BasicPushButton()
        self.sub.set_toggle(False)
        self.sub.set_button_title(u'\u2212')
        self.sub.valueChanged.connect(self.removeBox)
        self.add = gpi.BasicPushButton()
        self.add.set_toggle(False)
        self.add.set_button_title(u'\uFF0B')
        self.add.valueChanged.connect(self.addBox)

        self.hbox = QtWidgets.QHBoxLayout()
        self.hbox.setSpacing(0)
        self.hbox.addWidget(self.sub)
        self.hbox.addWidget(self.add)

        # at least one spin box
        self.boxes = []
        self.boxes.append(gpi.BasicSpinBox())
        self.vbox = QtWidgets.QVBoxLayout()
        self.vbox.setSpacing(0)

        self.vbox.addLayout(self.hbox)
        for box in self.boxes:
            self.vbox.addWidget(box)
            box.valueChanged.connect(self.valueChanged)
        self.setLayout(self.vbox)

    # setters
    def set_vals(self, inlist):
        while len(inlist) < len(self.boxes):
            oldbox = self.boxes.pop(0)
            self.vbox.removeWidget(oldbox)
            oldbox.deleteLater()
            oldbox.setParent(None)
            oldbox = None

        while len(inlist) > len(self.boxes):
            newbox = gpi.BasicSpinBox()
            self.boxes.insert(0, newbox)
            newbox.valueChanged.connect(self.valueChanged)
            newbox.set_min(1)
            newbox.set_val(1)
            newbox.set_max(gpi.GPI_INT_MAX)
            self.vbox.addWidget(newbox)

        if len(inlist) != len(self.boxes):
            self.log.critical("RESHAPE_GROUP: number of boxes not correct!")

        ind = 0
        for box in self.boxes:
            inval = inlist[ind]
            box.set_val(inval)
            ind = ind + 1

    # support
    def removeBox(self):
        if len(self.boxes) > 1:
            oldbox = self.boxes.pop(0)
            self.vbox.removeWidget(oldbox)
            oldbox.deleteLater()
            oldbox.setParent(None)
            oldbox = None
        self.valueChanged.emit()

    def addBox(self):
        newbox = gpi.BasicSpinBox()
        self.boxes.insert(0, newbox)
        newbox.valueChanged.connect(self.valueChanged)
        newbox.set_min(1)
        newbox.set_val(1)
        newbox.set_max(gpi.GPI_INT_MAX)
        self.vbox.addWidget(newbox)
        self.valueChanged.emit()

    def get_vals(self):
        vals = []
        for box in self.boxes:
            vals.append(box.get_val())
        return vals
Beispiel #13
0
class DataSliders(gpi.GenericWidgetGroup):
    """Combines the BasicCWFCSliders with ExclusivePushButtons
    for a unique widget element useful for reduce dimensions.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)
        self.sl = gpi.BasicCWFCSliders()
        self.sl.valueChanged.connect(self.valueChanged)
        # at least one button
        self.button_names = ['C/W']
        self.buttons = []
        cnt = 0
        wdgLayout = QtWidgets.QGridLayout()
        for name in self.button_names:
            newbutton = QtWidgets.QPushButton(name)
            newbutton.setCheckable(True)
            newbutton.setAutoExclusive(True)
            self.buttons.append(newbutton)
            newbutton.clicked.connect(self.findValue)
            newbutton.clicked.connect(self.valueChanged)
            wdgLayout.addWidget(newbutton, 0, cnt, 1, 1)
            cnt += 1
        # layout
        wdgLayout.addWidget(self.sl, 1, 0, 1, 4)
        wdgLayout.setVerticalSpacing(0)
        wdgLayout.setSpacing(0)
        self.setLayout(wdgLayout)
        # default
        self.set_min(1)
        self._selection = 0  # pass is default
        #self.buttons[self._selection].setChecked(True)
        self.sl.set_allvisible(False)

    # setters
    def set_val(self, val):
        """A python-dict containing keys: center, width, floor, ceiling,
        and selection.
        """
        self.sl.set_center(val['center'])
        self.sl.set_width(val['width'])
        self.sl.set_floor(val['floor'])
        self.sl.set_ceiling(val['ceiling'])
        self._selection = val['selection']
        self.buttons[self._selection].blockSignals(True)
        self.buttons[self._selection].setChecked(True)
        self.buttons[self._selection].blockSignals(False)
        self.findValue(None)

    def set_min(self, val):
        """A min value for center, width, floor and ceiling (int)."""
        self.sl.set_min(val)

    def set_max(self, val):
        """A max value for center, width, floor and ceiling (int)."""
        self.sl.set_max(val)

    # getters
    def get_val(self):
        val = {}
        val['selection'] = self._selection
        val['center'] = self.sl.get_center()
        val['width'] = self.sl.get_width()
        val['floor'] = self.sl.get_floor()
        val['ceiling'] = self.sl.get_ceiling()
        return val

    def get_min(self):
        return self.sl.get_min()

    def get_max(self):
        return self.sl.get_max()

    # support

    def setCropBounds(self):
        self.sl.set_min_width(1)

    def setSliceBounds(self):
        self.sl.set_min_width(0)
        self.sl.set_width(1)

    def setPassBounds(self):
        self.sl.set_min_width(1)
        self.sl.set_width(self.get_max())
        # JGP For Pass, reset cwfc
        self.sl.set_center((self.get_max() + 1) / 2)
        self.sl.set_width(self.get_max())
        self.sl.set_floor(1)
        self.sl.set_ceiling(self.get_max())

    def findValue(self, value):
        cnt = 0
        for button in self.buttons:
            if button.isChecked():
                self._selection = cnt
            cnt += 1
        # hide appropriate sliders
        if self._selection == 0:  # C/W
            self.sl.set_cwvisible(True)
            self.setCropBounds()
Beispiel #14
0
class WindowLevel(gpi.GenericWidgetGroup):
    """Provides an interface to the BasicCWFCSliders."""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)
        self.sl = gpi.BasicCWFCSliders()
        self.sl.valueChanged.connect(self.valueChanged)
        self.pb = gpi.BasicPushButton()
        self.pb.set_button_title('reset')
        # layout
        wdgLayout = QtWidgets.QVBoxLayout()
        wdgLayout.addWidget(self.sl)
        wdgLayout.addWidget(self.pb)
        self.setLayout(wdgLayout)
        # default
        self.set_min(0)
        self.set_max(100)
        self.sl.set_allvisible(True)
        self.reset_sliders()
        self.pb.valueChanged.connect(self.reset_sliders)

    # setters
    def set_val(self, val):
        """Set multiple values with a python-dict with keys:
        level, window, floor and ceiling. -Requires integer
        values for each key.
        """
        self.sl.set_center(val['level'])
        self.sl.set_width(val['window'])
        self.sl.set_floor(val['floor'])
        self.sl.set_ceiling(val['ceiling'])

    def set_min(self, val):
        """Set min for level, window, floor and ceiling (int)."""
        self.sl.set_min(val)

    def set_max(self, val):
        """Set max for level, window, floor and ceiling (int)."""
        self.sl.set_max(val)

    # getters
    def get_val(self):
        val = {}
        val['level'] = self.sl.get_center()
        val['window'] = self.sl.get_width()
        val['floor'] = self.sl.get_floor()
        val['ceiling'] = self.sl.get_ceiling()
        return val

    def get_min(self):
        return self.sl.get_min()

    def get_max(self):
        return self.sl.get_max()

    def reset_sliders(self):
        val = {}
        val['window'] = 100
        val['level'] = 50
        val['floor'] = 0
        val['ceiling'] = 100
        self.set_val(val)
Beispiel #15
0
class NodeAPI(QtWidgets.QWidget):
    """
    Base class for all external nodes.

    External nodes implement the extensible methods of this class: i.e.
    compute(), validate(), execType(), etc... to create a unique Node module.
    """
    GPIExtNodeType = ExternalNodeType  # ensures the subclass is of THIS class
    modifyWdg = gpi.Signal(str, dict)

    def __init__(self, node):
        super(NodeAPI, self).__init__()

        #self.setToolTip("Double Click to Show/Hide each Widget")
        self.node = node

        self.label = ''
        self._detailLabel = ''
        self._docText = None
        self.parmList = []  # deprecated, since dicts have direct name lookup
        self.parmDict = {}  # mirror parmList for now
        self.parmSettings = {}  # for buffering wdg parms before copying to a PROCESS
        self.shdmDict = {} # for storing base addresses

        # grid for module widgets
        self.layout = QtWidgets.QGridLayout()

        # this must exist before user-widgets are added so that they can get
        # node label updates
        self.wdglabel = QtWidgets.QLineEdit(self.label)

        # allow logger to be used in initUI()
        self.log = manager.getLogger(node.getModuleName())

        try:
            self._initUI_ret = self.initUI()
        except:
            log.error('initUI() failed. '+str(node.item.fullpath)+'\n'+str(traceback.format_exc()))
            self._initUI_ret = -1  # error

        # make a label box with the unique id
        labelGroup = HidableGroupBox("Node Label")
        labelLayout = QtWidgets.QGridLayout()
        self.wdglabel.textChanged.connect(self.setLabel)
        labelLayout.addWidget(self.wdglabel, 0, 1)
        labelGroup.setLayout(labelLayout)
        self.layout.addWidget(labelGroup, len(self.parmList) + 1, 0)
        labelGroup.set_collapsed(True)
        labelGroup.setToolTip("Displays the Label on the Canvas (Double Click)")

        # make an about box with the unique id
        self.aboutGroup = HidableGroupBox("About")
        aboutLayout = QtWidgets.QGridLayout()
        self.about_button = QtWidgets.QPushButton("Open Node &Documentation")
        self.about_button.clicked.connect(self.openNodeDocumentation)
        aboutLayout.addWidget(self.about_button, 0, 1)
        self.aboutGroup.setLayout(aboutLayout)
        self.layout.addWidget(self.aboutGroup, len(self.parmList) + 2, 0)
        self.aboutGroup.set_collapsed(True)
        self.aboutGroup.setToolTip("Node Documentation (docstring + autodocs, Double Click)")

        # window (just a QTextEdit) that will show documentation text
        self.doc_text_win = QtWidgets.QTextEdit()
        self.doc_text_win.setPlainText(self.generateHelpText())
        self.doc_text_win.setReadOnly(True)
        doc_text_font = QtGui.QFont("Monospace", 14)
        self.doc_text_win.setFont(doc_text_font)
        self.doc_text_win.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
        self.doc_text_win.setWindowTitle(node.getModuleName() + " Documentation")

        hbox = QtWidgets.QHBoxLayout()
        self._statusbar_sys = QtWidgets.QLabel('')
        self._statusbar_usr = QtWidgets.QLabel('')
        hbox.addWidget(self._statusbar_sys)
        hbox.addWidget(self._statusbar_usr, 0, (QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter))

        # window resize grip
        self._grip = QtWidgets.QSizeGrip(self)
        hbox.addWidget(self._grip)

        self.layout.addLayout(hbox, len(self.parmList) + 3, 0)
        self.layout.setRowStretch(len(self.parmList) + 3, 0)

        # uid display
        # uid   = QtWidgets.QLabel("uid: "+str(self.node.getID()))
        # uid.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
        # self.layout.addWidget(uid,len(self.parmList)+2,0)

        # instantiate the layout
        self.setLayout(self.layout)

        # run through all widget titles since each widget parent is now set.
        for parm in self.parmList:
            parm.setDispTitle()

        # instantiate the layout
        # self.setGeometry(50, 50, 300, 40)

        self.setTitle(node.getModuleName())

        self._starttime = 0
        self._startline = 0

    def initUI_return(self):
        return self._initUI_ret

    def getWidgets(self):
        # return a list of widgets
        return self.parmList

    def getWidgetNames(self):
        return list(self.parmDict.keys())

    def starttime(self):
        """Begin the timer for the node `Wall Time` calculation.

        Nodes store their own runtime, which is displayed in a tooltip when
        hovering over the node on the canvas (see :doc:`../ui`). Normally, each
        node reports the complete time it takes to run its :py:meth:`compute`
        function. However, a dev can use this method along with
        :py:meth:`endtime` to set the portion of :py:meth:`compute` to be used
        to calculate the `Wall Time`.
        """
        self._startline = inspect.currentframe().f_back.f_lineno
        self._starttime = time.time()

    def endtime(self, msg=''):
        """Begin the timer for the node `Wall Time` calculation.

        Nodes store their own runtime, which is displayed in a tooltip when
        hovering over the node on the canvas (see :doc:`../ui`). Normally, each
        node reports the complete time it takes to run its :py:meth:`compute`
        function. However, a dev can use this method along with
        :py:meth:`starttime` to set the portion of :py:meth:`compute` to be
        used to calculate the `Wall Time`. This method also prints the `Wall
        Time` to stdout, along with an optional additional message, using
        :py:meth:`gpi.logger.PrintLogger.node`).

        Args:
            msg (string): a message to be sent to stdout (using
                :py:meth:`gpi.logger.PrintLogger.node`) along with the `Wall
                Time`
        """
        ttime = time.time() - self._starttime
        eline = inspect.currentframe().f_back.f_lineno
        log.node(self.node.getName()+' - '+str(ttime)+'sec, between lines:'+str(self._startline)+'-'+str(eline)+'. '+msg)

    def stringifyExecType(self):
        if self.execType() is GPI_PROCESS:
            return " [Process]"
        elif self.execType() is GPI_THREAD:
            return " [Thread]"
        else:
            return " [App-Loop]"

    def setStatus_sys(self, msg):
        msg += self.stringifyExecType()
        self._statusbar_sys.setText(msg)

    def setStatus(self, msg):
        self._statusbar_usr.setText(msg)

    def execType(self):
        # default executable type
        # return GPI_THREAD
        return GPI_PROCESS  # this is the safest
        # return GPI_APPLOOP

    def setReQueue(self, val=False):  # NODEAPI
        # At the end of a nodeQueue, these tasked are checked for
        # more events.
        if self.node.inDisabledState():
            return
        if self.node.nodeCompute_thread.execType() == GPI_PROCESS:
            self.node.nodeCompute_thread.addToQueue(['setReQueue', val])
        else:
            self.node._requeue = val

    def reQueueIsSet(self):
        return self.node._requeue

    def getLabel(self):
        return self.label

    def moduleExists(self, name):
        """Give the user a simple module checker for the node validate
        function.
        """
        try:
            imp.find_module(name)
        except ImportError:
            return False
        else:
            return True

    def moduleValidated(self, name):
        """Provide a stock error message in the event a c/ext module cannot
        be found.
        """
        if not self.moduleExists(name):
            log.error("The \'" + name + "\' module cannot be found, compute() aborted.")
            return 1
        return 0

    def setLabelWidget(self, newlabel=''):
        self.wdglabel.setText(newlabel)

    def setLabel(self, newlabel=''):
        self.label = str(newlabel)
        self.updateTitle()
        self.node.updateOutportPosition()
        self.node.graph.scene().update(self.node.boundingRect())
        self.node.update()

    def openNodeDocumentation(self):
        self.doc_text_win.show()

        # setting the size only works if we have shown the widget
        # set the width based on some ideal width (max at 800px)
        docwidth = self.doc_text_win.document().idealWidth()
        lmargin = self.doc_text_win.contentsMargins().left()
        rmargin = self.doc_text_win.contentsMargins().right()
        scrollbar_width = 20 # estimate, scrollbar overlaps content otherwise
        total_width = min(lmargin + docwidth + rmargin + scrollbar_width, 800)
        self.doc_text_win.setFixedWidth(total_width)

        # set the height based on the content size
        docheight= self.doc_text_win.document().size().height()
        self.doc_text_win.setMinimumHeight(min(docheight, 200))
        self.doc_text_win.setMaximumHeight(docheight)

    def setDetailLabel(self, newDetailLabel='', elideMode='middle'):
        """Set an additional label for the node.

        This offers a way to programmatically set an additional label for a
        node (referred to as the `detail label`), which shows up underneath the
        normal node tile and label (as set wihtin the node menu). This is used
        by the `core` library to show file paths for the file reader/writer
        nodes, for example.

        Args:
            newDetailLabel (string): The detail label for the node (e.g. file
                path, operator, ...)
            elideMode ({'middle', 'left', 'right', 'none'}, optional): Method
                to use when truncating the detail label with an ellipsis.
        """
        self._detailLabel = str(newDetailLabel)
        self._detailElideMode = elideMode
        self.node.updateOutportPosition()

    def getDetailLabel(self):
        """Get the current node detail label.

        Returns:
            string: The node detail label, as set by :py:meth:`setDetailLabel`
        """
        return self._detailLabel

    def getDetailLabelElideMode(self):
        """How the detail label should be elided if it's too long:"""
        mode = self._detailElideMode
        qt_mode = QtCore.Qt.ElideMiddle
        if mode == 'left':
            qt_mode = QtCore.Qt.ElideLeft
        elif mode == 'right':
            qt_mode = QtCore.Qt.ElideRight
        elif mode == 'none':
            qt_mode = QtCore.Qt.ElideNone
        else: # default, mode == 'middle'
            qt_mode = QtCore.Qt.ElideMiddle

        return qt_mode

    # to be subclassed and reimplemented.
    def initUI(self):
        """Initialize the node UI (Node Menu).

        This method is intended to be reimplemented by the external node
        developer. This is where :py:class:`Port` and :py:class:`Widget`
        objects are added and initialized. This method is called whenever a
        node is added to the canvas.

        See :ref:`adding-widgets` and :ref:`adding-ports` for more detail.
        """

        # window
        #self.setWindowTitle(self.node.name)

        # IO Ports
        #self.addInPort('in1', str, obligation=REQUIRED)
        #self.addOutPort('out2', int)
        pass

    def windowActivationChange(self, test):
        self.node.graph.scene().makeOnlyTheseNodesSelected([self.node])

    def getSettings(self):  # NODEAPI
        """Wrap up all settings from each widget."""
        s = {}
        s['label'] = self.label
        s['parms'] = []
        for parm in self.parmList:
            s['parms'].append(copy.deepcopy(parm.getSettings()))
        return s

    def loadSettings(self, s):
        self.setLabelWidget(s['label'])

        # modify node widgets
        for parm in s['parms']:
            # the NodeAPI has instantiated the widget by name, this will
            # change the wdg-ID, however, this step is only dependend on
            # unique widget names.

            if not self.getWidget(parm['name']):
                log.warn("Trying to load settings; can't find widget with name: \'" + \
                    stw(parm['name']) + "\', skipping...")
                continue

            log.debug('Setting widget: \'' + stw(parm['name']) + '\'')
            try:
                self.modifyWidget_direct(parm['name'], **parm['kwargs'])
            except:
                log.error('Failed to set widget: \'' + stw(parm['name']) + '\'\n' + str(traceback.format_exc()))

            if parm['kwargs']['inport']:  # widget-inports
                self.addWidgetInPortByName(parm['name'])
            if parm['kwargs']['outport']:  # widget-outports
                self.addWidgetOutPortByName(parm['name'])


    def generateHelpText(self):
        """Gather the __doc__ string of the ExternalNode derived class,
        all the set_ methods for each attached widget, and any attached
        GPI-types from each of the ports.
        """
        if self._docText is not None:
            return self._docText

        # NODE DOC
        node_doc = "NODE: \'" + self.node._moduleName + "\'\n" + "    " + \
            str(self.__doc__)

        # WIDGETS DOC
        # parm_doc = ""  # contains parameter info
        wdg_doc = "\n\nAPPENDIX A: (WIDGETS)\n"  # generic widget ref info
        for parm in self.parmList:  # wdg order matters
            wdg_doc += "\n  \'" + parm.getTitle() + "\': " + \
                str(parm.__class__) + "\n"
            numSpaces = 8
            set_doc = "\n".join((numSpaces * " ") + i for i in str(
                parm.__doc__).splitlines())
            wdg_doc += set_doc + "\n"
            # set methods
            for member in dir(parm):
                if member.startswith('set_'):
                    wdg_doc += (8 * " ") + member + inspect.formatargspec(
                        *inspect.getargspec(getattr(parm, member)))
                    set_doc = str(inspect.getdoc(getattr(parm, member)))
                    numSpaces = 16
                    set_doc = "\n".join((
                        numSpaces * " ") + i for i in set_doc.splitlines())
                    wdg_doc += "\n" + set_doc + "\n"

        # PORTS DOC
        port_doc = "\n\nAPPENDIX B: (PORTS)\n"  # get the port type info
        for port in self.node.getPorts():
            typ = port.GPIType()
            port_doc += "\n  \'" + port.portTitle + "\': " + \
                str(typ.__class__) + "|" + str(type(port)) + "\n"
            numSpaces = 8
            set_doc = "\n".join((numSpaces * " ") + i for i in str(
                typ.__doc__).splitlines())
            port_doc += set_doc + "\n"

            # set methods
            for member in dir(typ):
                if member.startswith('set_'):
                    port_doc += (8 * " ") + member + inspect.formatargspec(
                        *inspect.getargspec(getattr(typ, member)))
                    set_doc = str(inspect.getdoc(getattr(typ, member)))
                    numSpaces = 16
                    set_doc = "\n".join((
                        numSpaces * " ") + i for i in set_doc.splitlines())
                    port_doc += "\n" + set_doc + "\n"

        # GETTERS/SETTERS
        getset_doc = "\n\nAPPENDIX C: (GETTERS/SETTERS)\n\n"
        getset_doc += (8 * " ") + "Node Initialization Setters: (initUI())\n\n"
        getset_doc += self.formatFuncDoc(self.addWidget)
        getset_doc += self.formatFuncDoc(self.addInPort)
        getset_doc += self.formatFuncDoc(self.addOutPort)
        getset_doc += (
            8 * " ") + "Node Compute Getters/Setters: (compute())\n\n"
        getset_doc += self.formatFuncDoc(self.getVal)
        getset_doc += self.formatFuncDoc(self.getAttr)
        getset_doc += self.formatFuncDoc(self.setAttr)
        getset_doc += self.formatFuncDoc(self.getData)
        getset_doc += self.formatFuncDoc(self.setData)

        self._docText = node_doc  # + wdg_doc + port_doc + getset_doc

        self.doc_text_win.setPlainText(self._docText)

        return self._docText

    def formatFuncDoc(self, func):
        """Generate auto-doc for passed func obj."""
        numSpaces = 24
        fdoc = inspect.getdoc(func)
        set_doc = "\n".join((
            numSpaces * " ") + i for i in str(fdoc).splitlines())
        rdoc = (16 * " ") + func.__name__ + \
            inspect.formatargspec(*inspect.getargspec(func)) \
            + "\n" + set_doc + "\n\n"
        return rdoc

    def printWidgetValues(self):
        # for debugging
        for parm in self.parmList:
            log.debug("widget: " + str(parm))
            log.debug("value: " + str(parm.get_val()))

    # abstracting IF for user
    def addInPort(self, title=None, type=None, obligation=REQUIRED,
                  menuWidget=None, cyclic=False, **kwargs):
        """Add an input port to the node.

        Input ports collect data from other nodes for use/processing within a
        node compute function. Ports may only be added in the :py:meth:`initUI`
        routine. Data at an input node can be accessed using
        :py:meth:`getData`, but is typically read-only.

        Args:
            title (str): port title (shown in tooltips) and unique identifier
            type (str): class name of extended type (e.g. ``np.complex64``)
            obligation: ``gpi.REQUIRED`` (default) or ``gpi.OPTIONAL``
            menuWidget: `for internal use`, devs should leave as default
                ``None``
            cyclic (bool): whether the port should allow reverse-flow from
                downstream nodes (default is ``False``)
            kwargs: any set_<arg> method belonging to the ``GPIDefaultType``
                derived class
        """
        self.node.addInPort(title, type, obligation, menuWidget, cyclic, **kwargs)
        self.node.update()

    # abstracting IF for user
    def addOutPort(self, title=None, type=None, obligation=REQUIRED,
                   menuWidget=None, **kwargs):
        """Add an output port to the node.

        Output nodes provide a conduit for passing data to downstream nodes.
        Ports may only be added in the :py:meth:`initUI` routine.  Data at an
        output port can be accessed using :py:meth:`setData` and
        :py:meth:`getData` from within :py:meth:`validate` and
        :py:meth:`compute`.

        Args:
            title (str): port title (shown in tooltips) and unique identifier
            type (str): class name of extended type (e.g. ``np.float32``)
            obligation: ``gpi.REQUIRED`` (default) or ``gpi.OPTIONAL``
            menuWidget: `for internal use`, debs should leave as default
                ``None``
            kwargs: any set_<arg> method belonging to the ``GPIDefaultType``
                derived class
        """
        self.node.addOutPort(title, type, obligation, menuWidget, **kwargs)
        self.node.update()

    def addWidgetInPortByName(self, title):
        wdg = self.findWidgetByName(title)
        self.addWidgetInPort(wdg)

    def addWidgetInPortByID(self, wdgID):
        wdg = self.findWidgetByID(wdgID)
        self.addWidgetInPort(wdg)

    def addWidgetInPort(self, wdg):
        self.addInPort(title=wdg.getTitle(), type=wdg.getDataType(),
                       obligation=OPTIONAL, menuWidget=wdg)

    def removeWidgetInPort(self, wdg):
        port = self.findWidgetInPortByName(wdg.getTitle())
        self.node.removePortByRef(port)

    def addWidgetOutPortByID(self, wdgID):
        wdg = self.findWidgetByID(wdgID)
        self.addWidgetOutPort(wdg)

    def addWidgetOutPortByName(self, title):
        wdg = self.findWidgetByName(title)
        self.addWidgetOutPort(wdg)

    def addWidgetOutPort(self, wdg):
        self.addOutPort(
            title=wdg.getTitle(), type=wdg.getDataType(), menuWidget=wdg)

    def removeWidgetOutPort(self, wdg):
        port = self.findWidgetOutPortByName(wdg.getTitle())
        self.node.removePortByRef(port)

    def addWidget(self, wdg=None, title=None, **kwargs):
        """Add a widget to the node UI.

        Args:
            wdg (str): The widget class name (see :doc:`widgets` for a list of
                built-in widget classes)
            title (str): A unique name for the widget, and title shown in the
                Node Menu
            kwargs: Additional arguments passed to the set_<arg> methods
                specific to the chosen widget class
        """

        if (wdg is None) or (title is None):
            log.critical("addWidget(): widgets need a title" \
                + " AND a wdg-str! Aborting.")
            return

        # check existence first
        if self.node.titleExists(title):
            log.critical("addWidget(): Widget title \'" + str(title) \
                + "\' is already in use! Aborting.")
            return

        ypos = len(self.parmList)

        # try widget def's packaged with node def first
        wdgGroup = None

        # first see if the node code contains the widget def
        wdgGroup = self.node.item.getWidget(wdg)

        # get widget from standard gpi widgets
        if wdgGroup is None:
            if hasattr(BUILTIN_WIDGETS, wdg):
                wdgGroup = getattr(BUILTIN_WIDGETS, wdg)

        # if its still not found then its a big deal
        if wdgGroup is None:
            log.critical("\'" + wdg + "\' widget not found.")
            raise Exception("Widget not found.")

        # instantiate if not None
        else:
            wdgGroup = wdgGroup(title)

        wdgGroup.setNodeName(self.node.getModuleName())
        wdgGroup._setNodeLabel(self.label)

        wdgGroup.valueChanged.connect(lambda: self.wdgEvent(title))
        wdgGroup.portStateChange.connect(lambda: self.changePortStatus(title))
        wdgGroup.returnWidgetToOrigin.connect(self.returnWidgetToNodeMenu)
        self.wdglabel.textChanged.connect(wdgGroup._setNodeLabel)

        # add to menu layout
        self.layout.addWidget(wdgGroup, ypos, 0)
        self.parmList.append(wdgGroup)
        self.parmDict[title] = wdgGroup  # the new way

        self.modifyWidget_direct(title, **kwargs)

    def returnWidgetToNodeMenu(self, wdgid):
        # find widget by id
        wdgid = int(wdgid)
        ind = 0
        for parm in self.parmList:
            if wdgid == parm.get_id():
                self.layout.addWidget(parm, ind, 0)
                if self.node.isMarkedForDeletion():
                    parm.hide()
                else:
                    parm.show()
                break
            ind += 1

    def blockWdgSignals(self, val):
        """Block all signals, especially valueChanged."""
        for parm in self.parmList:
            parm.blockSignals(val)

    def blockSignals_byWdg(self, title, val):
        # get the over-reporting widget by name and block it
        self.parmDict[title].blockSignals(val)

    def changePortStatus(self, title):
        """Add a new in- or out-port tied to this widget."""
        log.debug("changePortStatus: " + str(title))
        wdg = self.findWidgetByName(title)

        # make sure the port is either added or will be added.
        if wdg.inPort_ON:
            if self.findWidgetInPortByName(title) is None:
                self.addWidgetInPort(wdg)
        else:
            if self.findWidgetInPortByName(title):
                self.removeWidgetInPort(wdg)
        if wdg.outPort_ON:
            if self.findWidgetOutPortByName(title) is None:
                self.addWidgetOutPort(wdg)
        else:
            if self.findWidgetOutPortByName(title):
                self.removeWidgetOutPort(wdg)

    def findWidgetByName(self, title):
        for parm in self.parmList:
            if parm.getTitle() == title:
                return parm

    def findWidgetByID(self, wdgID):
        for parm in self.parmList:
            if parm.id() == wdgID:
                return parm

    def findWidgetInPortByName(self, title):
        for port in self.node.inportList:
            if port.isWidgetPort():
                if port.portTitle == title:
                    return port

    def findWidgetOutPortByName(self, title):
        for port in self.node.outportList:
            if port.isWidgetPort():
                if port.portTitle == title:
                    return port

    def modifyWidget_setter(self, src, kw, val):
        sfunc = "set_" + kw
        if hasattr(src, sfunc):
            func = getattr(src, sfunc)
            func(val)
        else:
            try:
                ttl = src.getTitle()
            except:
                ttl = str(src)

            log.critical("modifyWidget_setter(): Widget \'" + stw(ttl) \
                + "\' doesn't have attr \'" + stw(sfunc) + "\'.")

    def modifyWidget_direct(self, pnumORtitle, **kwargs):
        src = self.getWidget(pnumORtitle)

        for k, v in list(kwargs.items()):
            if k != 'val':
                self.modifyWidget_setter(src, k, v)

        # set 'val' last so that bounds don't cause a temporary conflict.
        if 'val' in kwargs:
            self.modifyWidget_setter(src, 'val', kwargs['val'])

    def modifyWidget_buffer(self, title, **kwargs):
        """GPI_PROCESSes have to use buffered widget attributes to effect the
        same changes to attributes during compute() as with GPI_THREAD or
        GPI_APPLOOP.
        """
        src = self.getWdgFromBuffer(title)

        try:
            for k, v in list(kwargs.items()):
                src['kwargs'][k] = v
        except:
            log.critical("modifyWidget_buffer() FAILED to modify buffered attribute")


    def lockWidgetUpdates(self):
        self._widgetUpdateMutex_locked = True

    def unlockWidgetUpdates(self):
        self._widgetUpdateMutex_locked = False

    def widgetsUpdatesLocked(self):
        return self._widgetUpdateMutex_locked

    def wdgEvent(self, title):
        # Captures all valueChanged events from widgets.
        # 'title' is for interrogating who changed in compute().

        # Once the event status has been set, disable valueChanged signals
        # until the node has successfully completed.
        # -this was b/c sliders etc. were causing too many signals resulting
        # in a recursion overload to this function.
        #self.blockWdgSignals(True)
        self.blockSignals_byWdg(title, True)

        if self.node.hasEventPending():
            self.node.appendEvent({GPI_WIDGET_EVENT: title})
            return
        else:
            self.node.setEventStatus({GPI_WIDGET_EVENT: title})

        # Can start event from any applicable 'check' transition
        if self.node.graph.inIdleState():
            self.node.graph._switchSig.emit('check')

    # for re-drawing the menu title to match module/node instance
    def updateTitle(self):
        # Why is this trying the scrollArea? isn't it always a scroll???
        if self.label == '':
            try:
                self.node._nodeIF_scrollArea.setWindowTitle(self.node.name)
            except:
                self.setWindowTitle(self.node.name)
        else:
            try:
                augtitle = self.node.name + ": " + self.label
                self.node._nodeIF_scrollArea.setWindowTitle(augtitle)
            except:
                augtitle = self.node.name + ": " + self.label
                self.setWindowTitle(augtitle)

    def setTitle(self, title):
        self.node.name = title
        self.updateTitle()

    # Queue actions for widgets and ports
    def setAttr(self, title, **kwargs):
        """Set specific attributes of a given widget.

        This method may be used to set attributes of any widget during any of
        the core node functions: :py:meth:`initUI`, :py:meth:`validate`, or
        :py:meth:`compute`.

        Args:
            title (str): the widget name (unique identifier)
            kwargs: args corresponding to the ``get_<arg>`` methods of the
                widget class. See :doc:`widgets` for the list of built-in
                widgets and associated attributes.
        """
        try:
            # start = time.time()
            # either emit a signal or save a queue
            if self.node.inDisabledState():
                return
            if self.node.nodeCompute_thread.execType() == GPI_PROCESS:
                # PROCESS
                self.node.nodeCompute_thread.addToQueue(['modifyWdg', title, kwargs])
                #self.modifyWidget_buffer(title, **kwargs)
            elif self.node.nodeCompute_thread.execType() == GPI_THREAD:
                # THREAD
                # can't modify QObjects in thread, but we can send a signal to do
                # so
                self.modifyWdg.emit(title, kwargs)
            else:
                # APPLOOP
                self.modifyWidget_direct(str(title), **kwargs)

        except:
            raise GPIError_nodeAPI_setAttr('self.setAttr(\''+stw(title)+'\',...) failed in the node definition, check the widget name, attribute name and attribute type().')

        # log.debug("modifyWdg(): time: "+str(time.time() - start)+" sec")

    def allocArray(self, shape=(1,), dtype=np.float32, name='local'):
        """return a shared memory array if the node is run as a process.
            -the array name needs to be unique
        """
        if self.node.nodeCompute_thread.execType() == GPI_PROCESS:
            buf, shd = DataProxy()._genNDArrayMemmap(shape, dtype, self.node.getID(), name)

            if shd is not None:
                # saving the reference id allows the node developer to decide
                # on the fly if the preallocated array will be used in the final
                # setData() call.
                self.shdmDict[str(id(buf))] = shd.filename

            return buf
        else:
            return np.ndarray(shape, dtype=dtype)

    def setData(self, title, data):
        """Set the data at an :py:class:`OutPort`.

        This is typically called in :py:meth:`compute` to set data at an output
        port, making the data available to downstream nodes.
        :py:class:`InPort` ports are read-only, so this method should only be used
        with :py:class:`OutPort` ports.

        Args:
            title (str): name of the port to send the object reference
            data: any object corresponding to a ``GPIType`` class allowed by
                this port
        """
        try:
            # start = time.time()
            # either set directly or save a queue
            if self.node.inDisabledState():
                return
            if self.node.nodeCompute_thread.execType() == GPI_PROCESS:

                #  numpy arrays
                if type(data) is np.memmap or type(data) is np.ndarray:
                    if str(id(data)) in self.shdmDict: # pre-alloc
                        s = DataProxy().NDArray(data, shdf=self.shdmDict[str(id(data))], nodeID=self.node.getID(), portname=title)
                    else:
                        s = DataProxy().NDArray(data, nodeID=self.node.getID(), portname=title)

                    # for split objects to pass thru individually
                    # this will be a list of DataProxy objects
                    if type(s) is list:
                        for i in s:
                            self.node.nodeCompute_thread.addToQueue(['setData', title, i])
                    # a single DataProxy object
                    else:
                        self.node.nodeCompute_thread.addToQueue(['setData', title, s])

                # all other non-numpy data that are pickleable
                else:
                    # PROCESS output other than numpy
                    self.node.nodeCompute_thread.addToQueue(['setData', title, data])
            else:
                # THREAD or APPLOOP
                self.node.setData(title, data)
                # log.debug("setData(): time: "+str(time.time() - start)+" sec")

        except:
            print((str(traceback.format_exc())))
            raise GPIError_nodeAPI_setData('self.setData(\''+stw(title)+'\',...) failed in the node definition, check the output name and data type().')

    def getData(self, title):
        """Get the data from a :py:class:`Port` for this node.

        Usually this is used to get input data from a :py:class:`InPort`,
        though in some circumstances (e.g. in the `Glue` node) it may be used
        to get data from an :py:class:`OutPort`. This method is available for
        devs to use in :py:meth:`validate` and :py:meth:`compute`.

        Args:
            title (str): the name of the GPI :py:class:`Port` object
        Returns:
            Data from the :py:class:`Port` object. The return will have a type
            corresponding to a ``GPIType`` class allowed by this port.
        """
        try:
            port = self.node.getPortByNumOrTitle(title)
            if isinstance(port, InPort):
                data = port.getUpstreamData()
                if type(data) is np.ndarray:
                    # don't allow users to change original array attributes
                    # that aren't protected by the 'writeable' flag
                    buf = np.frombuffer(data.data, dtype=data.dtype)
                    buf.shape = tuple(data.shape)
                    return buf
                else:
                    return data

            elif isinstance(port, OutPort):
                return port.data
            else:
                raise Exception("getData", "Invalid Port Title")
        except:
            raise GPIError_nodeAPI_getData('self.getData(\''+stw(title)+'\') failed in the node definition check the port name.')

    def getInPort(self, pnumORtitle):
        return self.node.getInPort(pnumORtitle)

    def getOutPort(self, pnumORtitle):
        return self.node.getOutPort(pnumORtitle)

############### DEPRECATED NODE API
# TTD v0.3

    def getAttr_fromWdg(self, title, attr):
        """title = (str) wdg-class name
        attr = (str) corresponds to the get_<arg> of the desired attribute.
        """
        log.warn('The \'getAttr_fromWdg()\' function is deprecated, use \'getAttr()\' instead.  '+str(self.node.getFullPath()))
        return self.getAttr(title, attr)

    def getVal_fromParm(self, title):
        """Returns get_val() from wdg-class (see getAttr()).
        """
        log.warn('The \'getVal_fromParm()\' function is deprecated, use \'getVal()\' instead.  '+str(self.node.getFullPath()))
        return self.getVal(title)

    def getData_fromPort(self, title):
        """title = (str) the name of the InPort.
        """
        log.warn('The \'getData_fromPort()\' function is deprecated, use \'getData()\' instead.  '+str(self.node.getFullPath()))
        return self.getData(title)

    def setData_ofPort(self, title, data):
        """title = (str) name of the OutPort to send the object reference.
        data = (object) any object corresponding to a GPIType class.
        """
        log.warn('The \'setData_ofPort()\' function is deprecated, use \'setData()\' instead.  '+str(self.node.getFullPath()))
        self.setData(title, data)

    def modifyWidget(self, title, **kwargs):
        """title = (str) the corresponding widget name.
        kwargs = args corresponding to the get_<arg> methods of the wdg-class.
        """
        log.warn('The \'modifyWidget()\' function is deprecated, use \'setAttr()\' instead.  '+str(self.node.getFullPath()))
        self.setAttr(title, **kwargs)

    def getEvent(self):
        """Allow node developer to get information about what event has caused
        the node to run."""
        log.warn('The \'getEvent()\' function is deprecated, use \'getEvents()\' (its the plural form). '+str(self.node.getFullPath()))
        return self.node.getPendingEvent()

    def portEvent(self):
        """Specifically check for a port event."""
        log.warn('The \'portEvent()\' function is deprecated, use \'portEvents()\' (its the plural form). '+str(self.node.getFullPath()))
        if GPI_PORT_EVENT in self.getEvent():
            return self.getEvent()[GPI_PORT_EVENT]
        return None

    def widgetEvent(self):
        """Specifically check for a wdg event."""
        log.warn('The \'widgetEvent()\' function is deprecated, use \'widgetEvents()\' (its the plural form). '+str(self.node.getFullPath()))
        if GPI_WIDGET_EVENT in self.getEvent():
            return self.getEvent()[GPI_WIDGET_EVENT]
        return None
############### DEPRECATED NODE API

    def getEvents(self):
        """Get information about events that caused the node to run.

        Returns a dictionary containing names of widgets and ports that have
        changed values since the last time the node ran. Additionally contains
        information regarding ``init`` or ``requeue`` events trigered by GPI's
        node-evaluation routines.

        `Note: events from widget-ports count as both widget and port events.`

        Returns:
            dict: all events accumulated since the node was last run

                The event dictionary contains four key:value pairs:
                    * ``GPI_WIDGET_EVENT`` : `set(widget_titles (string)`)
                    * ``GPI_PORT_EVENT`` : `set(port_titles (string)`)
                    * ``GPI_INIT_EVENT`` : ``True`` or ``False``
                    * ``GPI_REQUEUE_EVENT`` : ``True`` or ``False``
        """
        return self.node.getPendingEvents().events

    def portEvents(self):
        """Specifically check for port events.

        Get the names (unique identifier strings) of any ports that have
        changed data since the last run.

        `Note: events from widget-ports count as both widget and port events.`

        Returns:
            set: names (strings) of all ports modified since the node last ran
        """
        return self.node.getPendingEvents().port

    def widgetEvents(self):
        """Specifically check for a widget events.

        Get the names (unique identifier strings) of any widgets that have
        changed data since the last run.

        `Note: events from widget-ports count as both widget and port events.`

        Returns:
            set: names (strings) of all widgets modified since the node last
                ran
        """
        return self.node.getPendingEvents().widget

    def widgetMovingEvent(self, wdgid):
        """Called when a widget drag is being initiated.
        """
        pass

    def getWidgetByID(self, wdgID):
        for parm in self.parmList:
            if parm.id() == wdgID:
                return parm
        log.critical("getWidgetByID(): Cannot find widget id:" + str(wdgID))

    def getWidget(self, pnum):
        """Returns the widget desc handle and position number"""
        # fetch by widget number
        if type(pnum) is int:
            if (pnum < 0) or (pnum >= len(self.parmList)):
                log.error("getWidget(): Target widget out of range: " + str(pnum))
                return
            src = self.parmList[pnum]
        # fetch by widget title
        elif type(pnum) is str:
            src = None
            cnt = 0
            for parm in self.parmList:
                if parm.getTitle() == pnum:
                    src = parm
                    pnum = cnt  # change pnum back to int
                cnt += 1
            if src is None:
                log.error("getWidget(): Failed to find widget: \'" + stw(pnum) + "\'")
                return
        else:
            log.error("getWidget(): Widget identifier must be" + " either int or str")
            return
        return src

    def bufferParmSettings(self):
        """Get list of parms (dict) in self.parmSettings['parms'].
        Called by GPI_PROCESS functor to capture all widget settings needed
        in compute().
        """
        self.parmSettings = self.getSettings()

    def getWdgFromBuffer(self, title):
        # find a specific widget by title
        for wdg in self.parmSettings['parms']:
            if 'name' in wdg:
                if wdg['name'] == title:
                    return wdg


    def getVal(self, title):
        """Returns the widget value.

        Each widget class has a corresponding "main" value. This method will
        return the value of the widget, by passing along the return from its
        `get_val()` method.

        Returns:
            The widget value. The type of the widget value is defined by the
            widget class. See :doc:`widgets` for value types for the built-in
            widget classes.
        """
        try:
            # Fetch widget value by title
            if self.node.inDisabledState():
                return

            if self.node.nodeCompute_thread.execType() == GPI_PROCESS:
                return self.getAttr(title, 'val')

            # threads and main loop can access directly
            return self.getAttr(title, 'val')

        except:
            print(str(traceback.format_exc()))
            raise GPIError_nodeAPI_getVal('self.getVal(\''+stw(title)+'\') failed in the node definition, check the widget name.')

    def getAttr(self, title, attr):
        """Get a specific attribute value from a widget.

        This returns the value of a specific attribute of a widget. Widget
        attributes may be modified by the user manipulating the Node Menu,
        during widget creation using the `kwargs` in :py:meth:`addWidget`, or
        programmatically by :py:meth:`setAttr`.

        Args:
            title (str): The widget name (unique identifier)
            attr (str): The desired attribute
        Returns:
            The desired widget attribute. This value is retrieved by calling
            ``get_<attr>`` on the indicated widget. See :doc:`widgets` for a
            list of attributes for the buil-in widget classes.
        """
        try:
            # Fetch widget value by title
            if self.node.inDisabledState():
                return

            if self.node.nodeCompute_thread.execType() == GPI_PROCESS:
                wdg = self.getWdgFromBuffer(title)
                return self._getAttr_fromWdg(wdg, attr)

            # threads and main loop can access directly
            wdg = self.getWidget(title)
            return self._getAttr_fromWdg(wdg, attr)

        except:
            print(str(traceback.format_exc()))
            raise GPIError_nodeAPI_getAttr('self.getAttr(\''+stw(title)+'\',...) failed in the node definition, check widget name and attribute name.')

    def _getAttr_fromWdg(self, wdg, attr):
        try:
            # input is either a dict or a wdg instance.
            if isinstance(wdg, dict):  # buffered values
                if attr in wdg['kwargs']:
                    return wdg['kwargs'][attr]
                else:
                    # build error msg
                    title = wdg['name']
                    funame = attr
            else:  # direct widget access
                funame = 'get_' + attr
                if hasattr(wdg, funame):
                    return getattr(wdg, funame)()
                else:
                    try:
                        title = wdg.getTitle()
                    except:
                        title = "\'no name\'"
            log.critical("_getAttr(): widget \'" + stw(title) + "\' has no attr \'" + stw(funame) + "\'.")
            #return None
            raise GPIError_nodeAPI_getAttr('_getAttr() failed for widget \''+stw(title)+'\'')
        except:
            #log.critical("_getAttr_fromWdg(): Likely the wrong input arg type.")
            #raise
            print(str(traceback.format_exc()))
            raise GPIError_nodeAPI_getAttr('_getAttr() failed for widget \''+stw(title)+'\'')

    def validate(self):
        """The pre-compute validation step.

        This function is intended to be reimplemented by the external node
        developer. Here the developer can access widget and port data (see
        :ref:`accessing-widgets` and :ref:`accessing-ports`) to perform
        validation checks before :py:meth:`compute` is called.

        Returns:
            An integer corresponding to the result of the validation:
                0: The node successfully passed validation

                1: The node failed validation, compute will not be called and
                the canvas will be paused
        """
        log.debug("Default module validate().")
        return 0

    def compute(self):
        """The module compute routine.

        This function is intended to be reimplemented by the external node
        developer. This is where the main computation of the node is performed.
        The developer has full access to the widget and port data (see
        :ref:`accessing-widgets` and :ref:`accessing-ports`).

        Returns:
            An integer corresponding to the result of the computation:
                0: Compute completed successfully

                1: Compute failed in some way, the canvas will be paused
        """
        log.debug("Default module compute().")
        return 0

    def post_compute_widget_update(self):
        # reset any widget that requires it (i.e. PushButton)
        for parm in self.parmList:
            if hasattr(parm, 'set_reset'):
                parm.set_reset()
Beispiel #16
0
class MatplotDisplay2(gpi.GenericWidgetGroup):
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(MatplotDisplay2, self).__init__(title, parent)

        #self.data = self.get_data2()
        self._data = None
        self.create_main_frame()
        self.on_draw()

    # setters
    def set_val(self, data):
        '''Takes a list of npy arrays.
        '''
        if isinstance(data, list):
            self._data = data
            self.on_draw()
        else:
            return

    # getters
    def get_val(self):
        return self._data

    # support
    def create_main_frame(self):

        self.fig = Figure((5.0, 4.0), dpi=100)
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self)
        self.canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.canvas.setFocus()

        self.mpl_toolbar = NavigationToolbar(self.canvas, self)

        self.canvas.mpl_connect('key_press_event', self.on_key_press)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.canvas)  # the matplotlib canvas
        vbox.addWidget(self.mpl_toolbar)
        self.setLayout(vbox)

    def get_data2(self):
        return np.arange(20).reshape([4, 5]).copy()

    def on_draw(self):
        self.fig.clear()
        self.axes = self.fig.add_subplot(111)
        # self.axes.plot(self.x, self.y, 'ro')
        # self.axes.imshow(self.data, interpolation='nearest')
        # self.axes.plot([1,2,3])

        if self._data is None:
            return

        self.fig.hold(True)

        # plot each set
        # print "--------------------plot the data"
        for data in self._data:

            # check for x, y data
            if data.shape[-1] == 2:
                self.axes.plot(data[..., 0], data[..., 1], alpha=0.8, lw=2.0)
            else:
                self.axes.plot(data, alpha=0.8, lw=2.0)

        self.canvas.draw()

    def on_key_press(self, event):
        # print 'Matplotlib-> you pressed:' + str(event.key)
        # implement the default mpl key press events described at
        # http://matplotlib.org/users/navigation_toolbar.html#navigation-
        # keyboard-shortcuts
        try:
            from matplotlib.backend_bases import key_press_handler
            key_press_handler(event, self.canvas, self.mpl_toolbar)
        except:
            print("key_press_handler import failed. -old matplotlib version.")
Beispiel #17
0
class GPINodeQueue(QtCore.QObject):
    '''The list of nodes to process based on UI and hierarchy changes.
    '''

    finished = gpi.Signal()

    def __init__(self, parent=None):
        super(GPINodeQueue, self).__init__(parent)
        self._queue = []
        self._paused = False
        self._last_node_started = '-init-str-'

    def __str__(self):
        # stringify the status of the queue
        msg =  "GPINodeQueue object:\n"
        msg += "\tis paused: "+str(self.isPaused())+"\n"
        msg += "\tis empty:  "+str(self.isEmpty())+"\n"
        msg += "\tqueue len: "+str(self.getQueueLen())+"\n"
        msg += "\tlast node:  "+str(self._last_node_started)+"\n"
        return msg

    def setPause(self, val):
        self._paused = val

    def isPaused(self):
        return self._paused

    def isEmpty(self):
        if len(self._queue) > 0:
            return False
        return True

    def put(self, node):
        if isinstance(node, Node):
            self._queue.append(node)

    def getQueueLen(self):
        return len(self._queue)

    def resetQueue(self):
        self._queue = []

    def setQueue(self, nlist):
        self._queue = nlist

    def removeNode(self, node):
        # removes the first instance of given 'node'
        if node in self._queue:
            ind = self._queue.index(node)
            self._queue.pop(ind)
            log.debug("removeNode(): Node(" + node.name + \
                "): Removed node from queue.")
            return True  # SUCCESS
        else:
            log.debug("removeNode(): Node(" + node.name + \
                "): This node was not found in the queue.")
            return False  # FAILURE

    def startNextNode(self):
        if self.isPaused():
            log.debug("startNextNode(): blocking for pause.")
            return 'paused'

        # find next node in queue
        if len(self._queue) > 0:
            while not self._queue[0].isReady():
                self._queue.pop(0)
                if len(self._queue) == 0:
                    break

        # if queue is done then finalize
        if len(self._queue) == 0:
            log.debug("startNextNode(): node queue empty, finished.")
            self.finished.emit()
            return 'finished'

        # run next node
        node = self._queue.pop(0)
        self._last_node_started = node.getName()
        if node.hasEventPending():
            node.setEventStatus(None)
            log.debug("startNextNode(): node: "+node.getName())
            node.start()
            return 'started'
Beispiel #18
0
class COMBINE_SPLIT_GROUP(gpi.GenericWidgetGroup):
    """Dimension specific widget to specify data reshaping"""
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        self._val = {}
        self._val['action'] = 0
        self._val['dim_position'] = 1  # 0 - first, 1 - middle, 2 - last
        self._val['combine_dir'] = 0
        self._val['split_prod'] = 1
        self._val['split_val1'] = 1
        self._val['split_val2'] = 1

        # create exclusive push buttons to choose action
        self.action = gpi.ExclusivePushButtons('action')
        self.buttons = ['pass', 'combine', 'split']
        self.action.set_buttons(self.buttons)

        # create exclusive push buttons to choose action
        self.combdir = gpi.ExclusivePushButtons('direction')
        self.buttons = [u'\u2B06', u'\u2B07']
        self.combdir.set_buttons(self.buttons)
        self.combdir.set_visible(False)

        # create integer boxes for splitting a value
        self.sb1 = gpi.BasicSpinBox()
        self.sb1.set_min(1)
        self.sb1.set_val(1)
        self.sb1.set_max(gpi.GPI_INT_MAX)
        self.sb2 = gpi.BasicSpinBox()
        self.sb2.set_min(1)
        self.sb2.set_val(1)
        self.sb2.set_max(gpi.GPI_INT_MAX)
        self.set_sb_visible(False)

        self.action.valueChanged.connect(self.actionChange)
        self.combdir.valueChanged.connect(self.combdirChange)
        self.sb1.valueChanged.connect(self.splitChangeLeft)
        self.sb2.valueChanged.connect(self.splitChangeRight)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(self.action)
        hbox.addWidget(self.combdir)
        hbox.addWidget(self.sb1)
        hbox.addWidget(self.sb2)
        hbox.setSpacing(0)
        self.setLayout(hbox)

    # setters
    def set_action(self, val):
        self._val['action'] = val
        self.action.set_val(val)
        self.actionChange()

    def set_dimpos(self, val):
        """A marker of the dimension position. 0-first, 1-middle, 2-last"""
        self._val['dim_position'] = val
        pos = self._val['dim_position']
        if pos == 0:
            self.buttons = [u'\u2B07']
        elif pos == 1:
            self.buttons = [u'\u2B06', u'\u2B07']
        elif pos == 2:
            self.buttons = [u'\u2B06']
        else:
            self.log.warn("Not a valid dimension position in widget of type "\
                "COMBINE_SPLIT_GROUP. Must be between 0 and 2.")
        self.combdir.set_buttons(self.buttons)

    def set_split_prod(self, val):
        """set a new product of the split dimension."""
        self._val['split_prod'] = val
        self.splitChangeRight()

    def set_sb_visible(self, val):
        self.sb1.setVisible(val)
        self.sb2.setVisible(val)

    # support
    def actionChange(self):
        self._val['action'] = self.action.get_val()
        if self._val['action'] == 0:
            self.combdir.set_visible(False)
            self.set_sb_visible(False)
        elif self._val['action'] == 1:
            self.combdir.set_visible(True)
            self.set_sb_visible(False)
        else:
            self.combdir.set_visible(False)
            self.set_sb_visible(True)
        self.valueChanged.emit()

    def combdirChange(self):
        self._val['combine_dir'] = self.combdir.get_val()
        self.valueChanged.emit()

    def splitChangeLeft(self):
        prod = self._val['split_prod']
        val1 = self.sb1.get_val()
        old_val = self._val['split_val1']
        new_val = val1
        while (prod % val1 != 0 and val1 > 1 and val1 < prod):
            if new_val >= old_val:
                val1 = val1 + 1
            else:
                val1 = val1 - 1
        val2 = prod // val1
        self._val['split_val1'] = val1
        self._val['split_val2'] = val2
        self.setSplitValsQuietly(self._val)
        self.valueChanged.emit()

    def splitChangeRight(self):
        prod = self._val['split_prod']
        val2 = self.sb2.get_val()
        old_val = self._val['split_val2']
        new_val = val2
        while (prod % val2 != 0 and val2 > 1 and val2 < prod):
            if new_val > old_val:
                val2 = val2 + 1
            else:
                val2 = val2 - 1
        val1 = prod // val2
        self._val['split_val1'] = val1
        self._val['split_val2'] = val2
        self.setSplitValsQuietly(self._val)
        self.valueChanged.emit()

    def setSplitValsQuietly(self, val):
        self.sb1.blockSignals(True)
        self.sb2.blockSignals(True)
        self.sb1.set_val(self._val['split_val1'])
        self.sb2.set_val(self._val['split_val2'])
        self.sb1.blockSignals(False)
        self.sb2.blockSignals(False)

    # getters
    def get_action(self):
        return self._val['action']

    def get_combine_dir(self):
        return self._val['combine_dir']

    def get_split_dims(self):
        """return split dims as a list"""
        dims = []
        dims.append(self._val['split_val1'])
        dims.append(self._val['split_val2'])
        return dims
Beispiel #19
0
class GPIFunctor(QtCore.QObject):
    '''A common parent API for each execution type (i.e. ATask, PTask, TTask).
    Handles the data communications to and from each task type. '''

    finished = gpi.Signal(int)
    terminated = gpi.Signal()
    applyQueuedData_finished = gpi.Signal()
    _setData_finished = gpi.Signal()

    def __init__(self, node, parent=None):
        super(GPIFunctor, self).__init__(parent)
        self._node = node
        self._title = node.name
        self._func = node.getModuleCompute()
        self._validate = node.getModuleValidate()
        self._retcode = None
        self._validate_retcode = None

        # for applying data when a GPI_PROCESS is finished
        # this is done in a thread to keep the GUI responsive
        self._applyData_thread = None
        self._setData_finished.connect(self.applyQueuedData_finalMatter)
        self.applyQueuedData_finished.connect(self.finalMatter)
        self._ap_st_time = 0

        # flag for segmented types that need reconstitution on this side
        self._segmentedDataProxy = False

        # For Windows just make them all apploops for now to be safe
        self._execType = node._nodeIF.execType()
        if Specs.inWindows() and (self._execType == GPI_PROCESS):
            # if (self._execType == GPI_PROCESS):
            log.info(
                "init(): <<< WINDOWS Detected >>> Forcing GPI_PROCESS -> GPI_THREAD"
            )
            self._execType = GPI_THREAD
            # self._execType = GPI_APPLOOP

        self._label = node._nodeIF.getLabel()
        self._isTerminated = False
        self._compute_start = 0

        self._manager = None
        self._proxy = None
        self._proc = None
        if self._execType == GPI_PROCESS:
            log.debug("init(): set as GPI_PROCESS: " + str(self._title))
            self._manager = multiprocessing_context.Manager()
            self._proxy = self._manager.list()
            self._proc = PTask(self._func, self._title, self._label,
                               self._proxy)

            # apply data in a thread to make the GUI more responsive
            self._applyData_thread = GPIRunnable(self.applyQueuedData_setData)

        elif self._execType == GPI_THREAD:
            log.debug("init(): set as GPI_THREAD: " + str(self._title))
            self._proc = TTask(self._func, self._title, self._label,
                               self._proxy)

        else:  # default to GPI_APPLOOP
            log.debug("init(): set as GPI_APPLOOP: " + str(self._title))
            self._proc = ATask(self._func, self._title, self._label,
                               self._proxy)

        self._proc.finished.connect(self.computeFinished)
        # In Qt5, terminated was removed: its emission wasn't gauranteed
        # self._proc.terminated.connect(self.computeTerminated)

    def execType(self):
        return self._execType

    def terminate(self):
        self._isTerminated = True
        self.cleanup()
        self._proc.terminate()
        # self.wait() # so that something is waiting
        self.computeTerminated()

    def cleanup(self):
        # make sure the proxy manager for processes is shutdown.
        if self._manager:
            self._manager.shutdown()

        # try to minimize leftover memory from the segmented array transfers
        # force cleanup of mmap
        #if self._segmentedDataProxy:
        gc.collect()

    def curTime(self):
        return time.time() - self._compute_start

    def start(self):
        self._compute_start = time.time()

        # VALIDATE
        # temporarily trick all widget calls to use GPI_APPLOOP for validate()
        tmp_exec = self._execType
        self._execType = GPI_APPLOOP
        try:
            self._validate_retcode = self._validate()
        except:
            self._validate_retcode = 1  # validate error
        self._execType = tmp_exec

        # None as zero

        # send validate() return code thru same channels
        if self._validate_retcode != 0 and self._validate_retcode is not None:
            self._node.appendWallTime(time.time() - self._compute_start)
            self.finished.emit(1)  # validate error
            return

        # COMPUTE
        if self._execType == GPI_PROCESS:
            log.debug("start(): buffer process parms")
            self._node._nodeIF.bufferParmSettings()

            # keep objects on death-row from being copied into processes
            # before they've finally terminated. -otherwise they'll try
            # and terminate within child process and cause a fork error.
            log.debug('start(): garbage collect before spawning GPI_PROCESS')
            gc.collect()

        log.debug("start(): call task.start()")
        self._proc.start()

    def wait(self):
        self._proc.wait()

    def isRunning(self):
        return self._proc.isRunning()

    def returnCode(self):
        return self._retcode

    # GPI_PROCESS support
    def addToQueue(self, item):
        # add internal calls (port, widget, retcode...)
        # to a queue that is processed after compute()
        self._proc._proxy.append(item)

    def computeTerminated(self):
        self.terminated.emit()

    def computeFinished(self):
        if self._execType == GPI_PROCESS:
            self.applyQueuedData()

        else:
            self._retcode = 0  # success
            if Return.isError(self._proc._retcode):
                self._retcode = Return.ComputeError
            self.finalMatter()

    def finalMatter(self):
        log.info("computeFinished():Node \'" + str(self._title) +
                 "\': compute time:" + str(time.time() - self._compute_start) +
                 " sec.")
        self._node.appendWallTime(time.time() - self._compute_start)
        self.finished.emit(self._retcode)  # success

    def applyQueuedData_setData(self):

        for o in self._proxy:
            try:
                log.debug("applyQueuedData_setData(): apply object " +
                          str(o[0]) + ', ' + str(o[1]))
                if o[0] == 'setData':
                    # DataProxy is used for complex data types like numpy
                    if type(o[2]) is DataProxy:

                        # segmented types must be gathered before reassembly
                        if o[2].isSegmented():
                            log.debug("seg proxy is True")
                            self._segmentedDataProxy = True
                        else:
                            log.debug("o[2].getData()")
                            self._node.setData(o[1], o[2].getData())

                    # all other simple types get set directly
                    else:
                        log.debug("direct setData()")
                        self._node.setData(o[1], o[2])
            except:
                log.error("applyQueuedData() failed. " +
                          str(traceback.format_exc()))
                self._retcode = Return.ComputeError
                self._setData_finished.emit()

        # Assemble Segmented Data
        if self._segmentedDataProxy:
            log.warn("Using segmented data proxy...")
            # group all segmented types
            oportData = [
                o for o in self._proxy
                if (o[0] == 'setData') and (type(o[2]) is DataProxy)
            ]
            # take only those that are segmented
            oportData = [o for o in oportData if o[2].isSegmented()]
            # consolidate all outports with large data
            largeports = set([o[1] for o in oportData])

            for port in largeports:
                log.info(
                    "applyQueuedData(): ------ APPENDING SEGMENTED PROXY OBJECTS"
                )

                # gather port segs
                curport = [o for o in oportData if o[1] == port]

                # gather all DataProxy segs
                segs = [o[2] for o in curport]
                buf = DataProxy().getDataFromSegments(segs)

                # if the pieces fail to assemble move on
                if buf is None:
                    log.warn(
                        "applyQueuedData(): segmented proxy object failed to assemble, skipping..."
                    )
                    continue

                self._node.setData(port, buf)

        # run self.applyQueuedData_finalMatter()
        self._setData_finished.emit()

    def applyQueuedData(self):
        # Replay all external compute() events after execution.
        # This ensures that the target is not being used by another set method.
        self._ap_st_time = time.time()
        if self._isTerminated:
            return
        log.debug("applyQueuedData(): Sending data to main loop...")
        if len(self._proxy) == 0:
            log.debug(
                "applyQueuedData(): no data in output queue. Terminated.")
            self.computeTerminated()
            return

        self._segmentedDataProxy = False
        for o in self._proxy:
            try:
                log.debug("applyQueuedData(): apply object " + str(o[0]) +
                          ', ' + str(o[1]))
                if o[0] == 'retcode':
                    self._retcode = o[1]
                    if Return.isError(self._retcode):
                        self._retcode = Return.ComputeError
                    else:
                        self._retcode = 0  # squash Nones
                if o[0] == 'modifyWdg':
                    self._node.modifyWdg(o[1], o[2])
                if o[0] == 'setReQueue':
                    self._node.setReQueue(o[1])
            except:
                log.error("applyQueuedData() failed. " +
                          str(traceback.format_exc()))
                self._retcode = Return.ComputeError

        # transfer all setData() calls to a thread
        log.debug("applyQueuedData(): run _applyData_thread")
        ExecRunnable(self._applyData_thread)

    def applyQueuedData_finalMatter(self):

        if Return.isComputeError(self._retcode):
            self.finished.emit(self._retcode)

        elapsed = (time.time() - self._ap_st_time)
        log.info("applyQueuedData(): time (total queue): " + str(elapsed) +
                 " sec")

        # shutdown the proxy manager
        self.cleanup()

        # start self.finalMatter
        self.applyQueuedData_finished.emit()
Beispiel #20
0
class AddBlockWidgets(gpi.GenericWidgetGroup):
    """A unique widget that display a variable number of StringBoxes (or FileBrowser) depending on the Event being
    configured."""

    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(AddBlockWidgets, self).__init__(title, parent)
        self.button_names_list = [
            'Off', 'Delay', 'SincRF', 'BlockRF', 'G', 'GyPre', 'ArbGrad', 'ADC'
        ]
        self.clicked_button_name, self.clicked_button_index = '', 0
        self.buttons_list, self.string_box_list = [], []

        # Labels for StringBoxes to configure Events
        self.delay_labels = ['Unique Event name', 'Delay (s)']
        self.sinc_rf_labels = [
            'Unique Event name', 'Maximum Gradient (mT/m)',
            'Maximum Slew (T/m/s)', 'Flip Angle (deg)', 'Duration (s)',
            'Frequency Offset', 'Phase Offset', 'Time Bw Product',
            'Apodization', 'Slice Thickness (m)'
        ]
        self.block_rf_labels = [
            'Unique Event name', 'Maximum Gradient (mT/m)',
            'Maximum Slew (T/m/s)', 'Flip Angle (deg)', 'Duration (s)',
            'Frequency Offset', 'Phase Offset', 'Time Bw Product', 'Bandwidth',
            'Slice Thickness (m)'
        ]
        self.trap_labels = [
            'Unique Event name', 'Channel', 'Maximum Gradient (mT/m)',
            'Maximum Slew (T/m/s)', 'Duration (s)', 'Area', 'Flat Time (s)',
            'Flat Area', 'Amplitude (Hz)', 'Rise Time (s)'
        ]
        self.gy_pre_labels = ['Unique Event name', 'Duration (s)', 'Area']
        self.arb_grad_labels = [
            'Unique Event name', 'Channel', 'Maximum Gradient (mT/m)',
            'Maximum Slew (T/m/s)'
        ]
        self.adc_labels = [
            'Unique Event name', 'Number of samples', 'Dwell (s)',
            'Duration (s)', 'Delay (s)', 'Frequency Offset', 'Phase Offset'
        ]
        # Placeholders for StringBoxes to configure Events
        self.delay_placeholders = ['event_unique_name', 'delay']
        self.sinc_rf_placeholders = [
            'event_unique_name', 'max_grad', 'max_slew', 'flip_angle',
            'duration', 'freq_offset', 'phase_offset', 'time_bw_prod',
            'apodization', 'slice_thickness'
        ]
        self.block_rf_placeholders = [
            'event_unique_name', 'max_grad', 'max_slew', 'flip_angle',
            'duration', 'freq_offset', 'phase_offset', 'time_bw_prod',
            'bandwidth', 'slice_thickness'
        ]
        self.trap_placeholders = [
            'event_unique_name', 'channel', 'max_grad', 'max_slew', 'duration',
            'area', 'flat_time', 'flat_area', 'amplitude', 'rise_time'
        ]
        self.gy_pre_placeholders = ['event_unique_name', 'duration', 'area']
        self.arb_grad_placeholders = [
            'event_unique_name', 'channel', 'max_grad', 'max_slew'
        ]
        self.adc_placeholders = [
            'event_unique_name', 'num_samples', 'dwell', 'duration', 'delay',
            'freq_offset', 'phase_offset'
        ]

        # Variable to denote the maximum number of StringBoxes to be added; obviously depends on the Event which has the
        # maximum number of configuration parameters
        self.num_string_boxes = max(len(self.delay_labels),
                                    len(self.sinc_rf_labels),
                                    len(self.block_rf_labels),
                                    len(self.trap_labels),
                                    len(self.gy_pre_labels),
                                    len(self.arb_grad_labels),
                                    len(self.adc_labels))

        # First index is None because the first button is 'Off'. Look into event_def['event_values'] in get_val()
        self.labels = [
            None, self.delay_labels, self.sinc_rf_labels, self.block_rf_labels,
            self.trap_labels, self.gy_pre_labels, self.arb_grad_labels,
            self.adc_labels
        ]
        self.placeholders = [
            None, self.delay_placeholders, self.sinc_rf_placeholders,
            self.block_rf_placeholders, self.trap_placeholders,
            self.gy_pre_placeholders, self.arb_grad_placeholders,
            self.adc_placeholders
        ]

        self.wdg_layout = QtGui.QGridLayout()
        self.add_event_pushbuttons()
        self.add_config_stringboxes()
        self.add_include_in_loop_pushbutton()
        self.add_include_gz_pushbutton()
        self.add_file_browser()

        self.setLayout(self.wdg_layout)
        self.buttons_list[0].setChecked(True)

    def add_event_pushbuttons(self):
        """Adding PushButtons for the Events."""
        col_count = 0
        for name in self.button_names_list:
            new_button = QtGui.QPushButton(name)
            new_button.setCheckable(True)
            new_button.setAutoExclusive(True)
            new_button.clicked.connect(self.button_clicked)
            new_button.clicked.connect(self.valueChanged)
            # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
            self.wdg_layout.addWidget(new_button, 0, col_count, 1, 1)
            self.buttons_list.append(new_button)
            col_count += 1

    def add_config_stringboxes(self):
        """Adding StringBoxes for configuring the Events."""
        for x in range(self.num_string_boxes):
            string_box = gpi.StringBox(str(x))
            string_box.set_visible(False)
            # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
            self.wdg_layout.addWidget(string_box, x + 1, 1, 1, 6)
            self.string_box_list.append(string_box)

    def add_include_in_loop_pushbutton(self):
        """Adding PushButton to toggle Event being included/excluded in loop."""
        self.include_in_loop_pushbutton = QtGui.QPushButton(
            'Add event in loop')
        self.include_in_loop_pushbutton.setCheckable(True)
        self.include_in_loop_pushbutton.setChecked(True)
        self.include_in_loop_pushbutton.setVisible(False)
        self.include_in_loop_pushbutton.clicked.connect(self.button_clicked)
        self.include_in_loop_pushbutton.clicked.connect(self.valueChanged)
        # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
        self.wdg_layout.addWidget(self.include_in_loop_pushbutton, 11, 1, 1, 6)

    def add_include_gz_pushbutton(self):
        """Adding PushButton toggle for Gz along with Rf."""
        self.include_gz_pushbutton = QtGui.QPushButton('Add Gz event with Rf')
        self.include_gz_pushbutton.setCheckable(True)
        self.include_gz_pushbutton.setVisible(False)
        self.include_gz_pushbutton.clicked.connect(self.button_clicked)
        self.include_gz_pushbutton.clicked.connect(self.valueChanged)
        # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
        self.wdg_layout.addWidget(self.include_gz_pushbutton, 12, 1, 1, 6)

    def add_file_browser(self):
        """Adding FileBrowser necessary for ArbGrad Event."""
        self.file_browser = gpi.OpenFileBrowser('Read .hdf5')
        self.file_browser.set_button_title('Read .hdf5')
        self.file_browser.set_visible(False)
        # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
        self.wdg_layout.addWidget(self.file_browser, 5, 1, 1, 6)

    # Getter
    def get_val(self):
        if self.clicked_button_index == self.button_names_list.index('Off'):
            # 'Off' PushButton selected, return empty dict
            return {}
        else:
            """
            event_def contains:
            - event_name : str
                Event name, corresponds to Event button that is selected
            - event_unique_name : str
                Unique Event name; user input
            - event_values : OrderedDict
                key-value pairs of Event parameters_params and values
            - include_in_loop : bool
                If Event should be added to Sequence Ny times
            - include_gz : bool
                If Gz Event should be added to Sequence with Rf Event
            - file_path : str
                Path to .hdf5 file required for arbitrary gradient Event
            """
            event_def = dict()
            keys = self.placeholders[self.clicked_button_index][1:]
            values = [x.get_val() for x in self.string_box_list[1:]]
            for x in values:
                if x == '':
                    values[values.index(x)] = '0'
            event_def['event_values'] = OrderedDict(zip(keys, values))
            event_def['event_name'] = self.clicked_button_name
            event_def['event_unique_name'] = self.string_box_list[0].get_val()
            if event_def['event_unique_name'] == '':
                # Return None to raise an error in compute()
                return None

            event_def[
                'include_in_loop'] = self.include_in_loop_pushbutton.isChecked(
                )
            if self.clicked_button_index == 2:
                # For Rf event, check if Gz has to be included
                event_def['include_gz'] = self.include_gz_pushbutton.isChecked(
                )
            elif self.clicked_button_index == 6:
                # For arbitrary gradient event, retrieve file path
                event_def['file_path'] = self.file_browser.get_val()
            return event_def

    # Setter
    def set_val(self, val):
        self.hide_config_widgets()
        if len(val) != 0:
            event_values = val['event_values']
            event_values['event_unique_name'] = val['event_unique_name']
            self.clicked_button_name = val['event_name']
            self.clicked_button_index = self.button_names_list.index(
                self.clicked_button_name)
            self.buttons_list[self.clicked_button_index].setChecked(True)
            self.show_config_widgets()
            self.include_in_loop_pushbutton.setChecked(
                bool(val['include_in_loop']))
            if 'include_gz' in val:
                self.include_gz_pushbutton.setChecked(val['include_gz'])
            labels = self.labels[self.clicked_button_index]
            placeholders = self.placeholders[self.clicked_button_index]
            for x in range(len(placeholders)):
                self.string_box_list[x].setTitle(labels[x])
                self.string_box_list[x].set_placeholder(placeholders[x])
                self.string_box_list[x].set_val(event_values[placeholders[x]])

    def button_clicked(self):
        """Identifies the button that was clicked and stores the name and ID of the button."""
        for button in self.buttons_list:
            if button.isChecked():
                self.clicked_button_index = self.buttons_list.index(button)
                self.clicked_button_name = self.button_names_list[
                    self.clicked_button_index]
        self.show_config_widgets()

    def show_config_widgets(self):
        """Show appropriate number of StringBoxes and relevant Widgets based on the button that was clicked."""
        self.hide_config_widgets()

        if self.clicked_button_index != 0 and self.clicked_button_index != 5:
            self.include_in_loop_pushbutton.setVisible(True)
        if self.clicked_button_index == 1:
            # Delay
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.delay_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.delay_labels[x])
                for x in range(len(self.delay_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.delay_placeholders[x])
                for x in range(len(self.delay_placeholders))
            ]
        elif self.clicked_button_index == 2:
            # SincRF
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.sinc_rf_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.sinc_rf_labels[x])
                for x in range(len(self.sinc_rf_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.sinc_rf_placeholders[x])
                for x in range(len(self.sinc_rf_placeholders))
            ]
            self.include_gz_pushbutton.setVisible(True)
        elif self.clicked_button_index == 3:
            # BlockRF
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.block_rf_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.block_rf_labels[x])
                for x in range(len(self.block_rf_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.block_rf_placeholders[x])
                for x in range(len(self.block_rf_placeholders))
            ]
            self.include_gz_pushbutton.setVisible(True)
        elif self.clicked_button_index == 3:
            # G
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.trap_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.trap_labels[x])
                for x in range(len(self.trap_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.trap_placeholders[x])
                for x in range(len(self.trap_placeholders))
            ]
        elif self.clicked_button_index == 4:
            # G
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.trap_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.trap_labels[x])
                for x in range(len(self.trap_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.trap_placeholders[x])
                for x in range(len(self.trap_placeholders))
            ]
        elif self.clicked_button_index == 5:
            # GyPre
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.gy_pre_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.gy_pre_labels[x])
                for x in range(len(self.gy_pre_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.gy_pre_placeholders[x])
                for x in range(len(self.gy_pre_placeholders))
            ]
        elif self.clicked_button_index == 6:
            # Arbitrary Grad
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.arb_grad_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.arb_grad_labels[x])
                for x in range(len(self.arb_grad_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.arb_grad_placeholders[x])
                for x in range(len(self.arb_grad_placeholders))
            ]
            self.file_browser.set_visible(True)
        elif self.clicked_button_index == 7:
            # ADC
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.adc_placeholders))
            ]
            [
                self.string_box_list[x].setTitle(self.adc_labels[x])
                for x in range(len(self.adc_labels))
            ]
            [
                self.string_box_list[x].set_placeholder(
                    self.adc_placeholders[x])
                for x in range(len(self.adc_placeholders))
            ]

    def hide_config_widgets(self):
        """Hide all Widgets."""
        [x.set_visible(False) for x in self.string_box_list]
        [x.set_val("") for x in self.string_box_list]
        self.include_in_loop_pushbutton.setVisible(False)
        self.include_gz_pushbutton.setVisible(False)
        self.file_browser.set_visible(False)
Beispiel #21
0
class OpenGLWindow(gpi.GenericWidgetGroup):

    """Provides an embedded GL window.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(OpenGLWindow, self).__init__(title, parent)

        f = QtOpenGL.QGLFormat()
        f.setAccum(True)
        f.setDoubleBuffer(True)
        f.setRgba(True)
        f.setDepth(True)
        f.setAlpha(True)
        self.glWidget = GPIGLWidget(f)

        self.glWidgetArea = QtGui.QScrollArea()
        self.glWidgetArea.setWidget(self.glWidget)
        self.glWidgetArea.setWidgetResizable(True)
        self.glWidgetArea.setHorizontalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOff)
        self.glWidgetArea.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOff)
        self.glWidgetArea.setSizePolicy(QtGui.QSizePolicy.Ignored,
                                        QtGui.QSizePolicy.Ignored)
        self.glWidgetArea.setMinimumSize(50, 50)

        # self.pixmapLabelArea = QtGui.QScrollArea()
        # self.pixmapLabelArea.setWidget(self.pixmapLabel)
        # self.pixmapLabelArea.setSizePolicy(QtGui.QSizePolicy.Ignored,
        #        QtGui.QSizePolicy.Ignored)
        # self.pixmapLabelArea.setMinimumSize(50, 50)

        xSlider = self.createSlider(self.glWidget.xRotationChanged,
                                    self.glWidget.setXRotation)
        ySlider = self.createSlider(self.glWidget.yRotationChanged,
                                    self.glWidget.setYRotation)
        zSlider = self.createSlider(self.glWidget.zRotationChanged,
                                    self.glWidget.setZRotation)

        glBlend = self.createCheckOption('PolySmooth', self.glWidget.setBlend)
        polyFill = self.createCheckOption(
            'Poly-Fill/Line/Point', self.glWidget.setPolyFill, tristate=True)

        # antialiasing requires the accumulation buffer
        enableaccum = True
        if not self.glWidget.format().accum():
            enableaccum = False
        antialiasing = self.createCheckOption(
            'AntiAliasing', self.glWidget.setAntiAliasing, initstate=0, enabled=enableaccum)

        hardwareRender = QtGui.QLabel()
        hardwareRender.setFrameStyle(2)
        if self.glWidget.format().directRendering():
            hardwareRender.setText('Rendering: Hardware')
        else:
            hardwareRender.setText('Rendering: Software')

        #testOption = self.createCheckOption(
        #    'Test Option', self.glWidget.setTest, tristate=False)

        # self.createActions()
        # self.createMenus()
        centralLayout = QtGui.QGridLayout()
        centralLayout.addWidget(self.glWidgetArea, 2, 0, 4, 4)
        # centralLayout.setColumnStretch(0,3)
        centralLayout.setRowStretch(2, 2)
        # centralLayout.addWidget(self.pixmapLabelArea, 0, 1)
        #centralLayout.addWidget(xSlider, 2, 0, 1, 2)
        #centralLayout.addWidget(ySlider, 3, 0, 1, 2)
        #centralLayout.addWidget(zSlider, 4, 0, 1, 2)
        centralLayout.addWidget(polyFill, 1, 0, 1, 1)
        centralLayout.addWidget(glBlend, 1, 1, 1, 1)
        centralLayout.addWidget(antialiasing, 1, 2, 1, 1)
        centralLayout.addWidget(hardwareRender, 0, 0, 1, 1)
        #centralLayout.addWidget(testOption, 0, 2, 1, 1)

        self.setLayout(centralLayout)

        xSlider.setValue(15 * 16)
        ySlider.setValue(345 * 16)
        zSlider.setValue(0 * 16)

        # self.setWindowTitle("Grabber")
        self.resize(400, 300)

    # setters
    def set_val(self, val):
        """set a list of GL command list interface (dict)."""
        self.glWidget.setGPIglList(val)
        self.glWidget.glInit()
        self.glWidget.updateGL()

    def set_resetView(self, val):
        """reset the viewing window"""
        self.glWidget.resetViewingWindow()

    def set_imageARGB(self, val):
        pass

    def set_imageBGRA(self, val):
        pass

    # getters
    def get_val(self):
        """get the held list of GPI-GL objects"""
        return self.glWidget.getGPIglList()

    def get_resetView(self):
        '''This is a one-shot operation, so there is nothing to get'''
        pass

    def get_imageARGB(self):
        '''Render a copy of the GL window and convert to NPY array.
        '''
        fbuff = self.glWidget.grabFrameBuffer(withAlpha=True)
        arr = qimage2numpy(fbuff)
        arr = arr[..., ::-1]
        return arr

    def get_imageBGRA(self):
        '''Render a copy of the GL window and convert to NPY array.  BGRA is
        the fastest since it native.
        '''
        fbuff = self.glWidget.grabFrameBuffer(withAlpha=True)
        arr = qimage2numpy(fbuff)
        return arr

    # support
    def renderIntoPixmap(self):
        size = self.getSize()

        if size.isValid():
            pixmap = self.glWidget.renderPixmap(size.width(), size.height())
            self.setPixmap(pixmap)

    def grabFrameBuffer(self):
        image = self.glWidget.grabFrameBuffer()
        self.setPixmap(QtGui.QPixmap.fromImage(image))

    def clearPixmap(self):
        self.setPixmap(QtGui.QPixmap())

    def about(self):
        QtGui.QMessageBox.about(self, "About Grabber",
                                "The <b>Grabber</b> example demonstrates two approaches for "
                                "rendering OpenGL into a Qt pixmap.")

    def createActions(self):
        self.renderIntoPixmapAct = QtGui.QAction("&Render into Pixmap...",
                                                 self, shortcut="Ctrl+R", triggered=self.renderIntoPixmap)

        self.grabFrameBufferAct = QtGui.QAction("&Grab Frame Buffer", self,
                                                shortcut="Ctrl+G", triggered=self.grabFrameBuffer)

        self.clearPixmapAct = QtGui.QAction("&Clear Pixmap", self,
                                            shortcut="Ctrl+L", triggered=self.clearPixmap)

        self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
                                     triggered=self.close)

        self.aboutAct = QtGui.QAction("&About", self, triggered=self.about)

        self.aboutQtAct = QtGui.QAction("About &Qt", self,
                                        triggered=QtGui.qApp.aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.renderIntoPixmapAct)
        self.fileMenu.addAction(self.grabFrameBufferAct)
        self.fileMenu.addAction(self.clearPixmapAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createCheckOption(self, title, setterSlot, tristate=False, initstate=0, enabled=True):
        checkbox = QtGui.QCheckBox(title)
        checkbox.setTristate(tristate)
        checkbox.setCheckState(initstate)
        checkbox.stateChanged.connect(setterSlot)
        if not enabled:
            checkbox.setEnabled(False)
        return checkbox

    def createSlider(self, changedSignal, setterSlot):
        slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        slider.setRange(0, 360 * 16)
        slider.setSingleStep(16)
        slider.setPageStep(15 * 16)
        slider.setTickInterval(15 * 16)
        slider.setTickPosition(QtGui.QSlider.TicksRight)

        slider.valueChanged.connect(setterSlot)
        changedSignal.connect(slider.setValue)

        return slider

    def setPixmap(self, pixmap):
        self.pixmapLabel.setPixmap(pixmap)
        size = pixmap.size()

        if size - QtCore.QSize(1, 0) == self.pixmapLabelArea.maximumViewportSize():
            size -= QtCore.QSize(1, 0)

        self.pixmapLabel.resize(size)

    def getSize(self):
        text, ok = QtGui.QInputDialog.getText(self, "Grabber",
                                              "Enter pixmap size:", QtGui.QLineEdit.Normal,
                                              "%d x %d" % (self.glWidget.width(), self.glWidget.height()))

        if not ok:
            return QtCore.QSize()

        regExp = QtCore.QRegExp("([0-9]+) *x *([0-9]+)")

        if regExp.exactMatch(text):
            width = regExp.cap(0).toInt()
            height = regExp.cap(1).toInt()
            if width > 0 and width < 2048 and height > 0 and height < 2048:
                return QtCore.QSize(width, height)

        return self.glWidget.size()
Beispiel #22
0
class SpynAxys(gpi.GenericWidgetGroup):
    """A combination of SpinBoxes, DoubleSpinBoxes, and PushButtons
    to form a unique widget suitable for the Spin Generator Axes.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        self._val = {}
        self._val['length'] = 1
        self._val['start'] = 0.
        self._val['end'] = 0. # the original array length

        self.sb = gpi.BasicSpinBox()  # length
        self.sb.set_label('# Spins:')
        self.sb.set_min(1)
        self.sb.set_val(1)
        self.sb.set_max(gpi.GPI_INT_MAX)

        self.db1 = gpi.BasicDoubleSpinBox()  # start
        self.db1.set_label('Start:')
        self.db1.set_min(gpi.GPI_FLOAT_MIN)
        self.db1.set_max(gpi.GPI_FLOAT_MAX)
        self.db1.set_decimals(3)
        self.db1.set_singlestep(1.)
        self.db1.set_val(0)

        self.db2 = gpi.BasicDoubleSpinBox()  # end
        self.db2.set_label('End:')
        self.db2.set_min(gpi.GPI_FLOAT_MIN)
        self.db2.set_max(gpi.GPI_FLOAT_MAX)
        self.db2.set_decimals(3)
        self.db2.set_singlestep(1.)
        self.db2.set_val(0)

        self.sb.valueChanged.connect(self.lenChange)
        self.db1.valueChanged.connect(self.startChange)
        self.db2.valueChanged.connect(self.endChange)

        vbox = QtWidgets.QHBoxLayout()
        vbox.addWidget(self.sb)
        vbox.addWidget(self.db1)
        vbox.addWidget(self.db2)
        vbox.setStretch(0, 0)
        vbox.setStretch(1, 0)
        vbox.setStretch(2, 0)
        vbox.setContentsMargins(0, 0, 0, 0)  # we don't need margins here
        vbox.setSpacing(0)
        self.setLayout(vbox)

    # setters
    def set_val(self, val):
        """A python-dict containing: size, and width parms. """
        sig = False
        if 'length' in val:
            self._val['length'] = val['length']
            self.setLenQuietly(val['length'])
            sig = True
        if 'start' in val:
            self._val['start'] = val['start']
            self.setStartQuietly(val['start'])
            sig = True
        if 'end' in val:
            self._val['end'] = val['end']
            self.setEndQuietly(val['end'])
            sig = True
        if sig:
            self.valueChanged.emit()


    # getters
    def get_val(self):
        return self._val

    # support
    def lenChange(self, val):
        self._val['length'] = val
        self.setLenQuietly(self._val['length'])
        self.valueChanged.emit()

    def startChange(self, val):
        self._val['start'] = val
        self.setStartQuietly(self._val['start'])
        self.valueChanged.emit()

    def endChange(self, val):
        self._val['end'] = val
        self.setEndQuietly(self._val['end'])
        self.valueChanged.emit()

    def setLenQuietly(self, val):
        self.sb.blockSignals(True)
        self.sb.set_val(val)
        self.sb.blockSignals(False)

    def setStartQuietly(self, val):
        self.db1.blockSignals(True)
        self.db1.set_val(val)
        self.db1.blockSignals(False)

    def setEndQuietly(self, val):
        self.db2.blockSignals(True)
        self.db2.set_val(val)
        self.db2.blockSignals(False)
Beispiel #23
0
class LayoutWindow(QtWidgets.QFrame):
    '''This class controls the low level operations on "Layout Windows". It 
    handles the drag and drop events for pulling widgets out of "Node Menus".
    It also handles low-level serialization.
    '''

    changed = gpi.Signal()

    def __init__(self, graph, layoutType, label, parent=None):
        super(LayoutWindow, self).__init__(parent)
        self.setAcceptDrops(True)

        self._graph = graph
        self.setLabel(label)
        self.setLayoutType(layoutType)

        # keep track of child order since Qt doesn't
        self._wdgidList = []

    def setLayoutType(self, typ):
        '''The basic layout is VBox or HBox.
        '''
        self._layoutType = typ
        if typ == 'vbox':
            self._layout = QtWidgets.QVBoxLayout()
        elif typ == 'hbox':
            self._layout = QtWidgets.QHBoxLayout()

    def setLabel(self, lab):
        '''This label allows the layoutWindow to be placed in the correct place
        within the MasterLayout.
        '''
        self._label = lab

    def pushWdgID(self, wdgid):
        '''Push a newly dropped widget to the end of the list.
        '''
        self._wdgidList.append(wdgid)

    def widgetMovingEvent(self, wdgid):
        '''If a drag is initiated the widget will always be moved.
        --see the widget drag event for details in widgets.py.
        '''
        self._wdgidList.remove(wdgid)

    def label(self):
        return self._label

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat('application/gpi-widget'):
            # event.acceptProposedAction()
            event.accept()

    def dragMoveEvent(self, event):
        if event.mimeData().hasFormat('application/gpi-widget'):
            # event.acceptProposedAction()
            event.accept()

    def dropEvent(self, event):
        log.debug("dropped in test window")
        if event.mimeData().hasFormat('application/gpi-widget'):
            mime = event.mimeData()
            itemData = mime.data('application/gpi-widget')
            dataStream = QtCore.QDataStream(itemData,
                                            QtCore.QIODevice.ReadOnly)

            text = QtCore.QByteArray()
            offset = QtCore.QPoint()
            dataStream >> text >> offset

            if self.addWidgetByID(self._graph.getAllNodes(), int(text)):
                # event.acceptProposedAction()
                event.accept()
            else:
                event.ignore()

    def dragLeaveEvent(self, event):
        event.accept()

    def addWidgetByID(self, nodeList, wdgid):
        '''Only search for widgets within the nodeList.
        '''
        parm = self._graph.findWidgetByID(nodeList, wdgid)
        if parm is None:
            log.error("LayoutWindow(): addWidget failed, wdgid not found!!!")
            return False

        # save the actual id
        self.pushWdgID(id(parm))
        self._layout.addWidget(parm)
        self.setLayout(self._layout)

        parm.setDispTitle()  # parent changed

        self.changed.emit()
        return True

    def count(self):
        return len(self._wdgidList)

    def getSettings(self):
        '''Get widget id's from this layout.
        NOTE: widgets are not stored by the QObject in any particular order.
        '''
        s = {}
        s['label'] = self.label()
        s['layoutType'] = self._layoutType
        s['wdgids'] = self._wdgidList

        return s

    def loadSettings(self, s, nodeList):
        '''Given the information generated by getSettings(), load the
        corresponding widgets.  Searches for widgets within the supplied
        nodeList.
        '''
        self.setLayoutType(s['layoutType'])
        for wdg in s['wdgids']:
            log.debug("\t adding wdgid: " + str(wdg))
            self.blockSignals(True)  # keep from calling columnAdjust()
            self.addWidgetByID(nodeList, int(wdg))
            self.blockSignals(False)

    def getWdgByID(self, wdgid):
        for wdg in self.getGPIWidgets():
            if wdgid == wdg.get_id():
                return wdg

    def getGPIWidgets(self):
        '''Search all children for valid GPI widget objects and list them.
        '''
        gpichilds = []
        for wdg in self.children():
            if isWidget(wdg):
                gpichilds.append(wdg)
        return gpichilds
Beispiel #24
0
class SHAPES_GROUP(gpi.GenericWidgetGroup):
    """A combination of Sliders and SpinBoxes to form a
    unique widget suitable for defining the filter shape.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        self._val = {}
        self._val['size'] = 1
        self._val['width'] = 1

        self.sb = gpi.BasicSpinBox()  # size
        self.sb.set_label('size:')
        self.sb.set_min(1)
        self.sb.set_val(1)
        self.sb.set_max(gpi.GPI_INT_MAX)

        self.slb = gpi.BasicSlider()  # width
        self.slb.set_min(1)
        self.slb.set_val(1)
        self.slb.set_max(1)

        self.slider_label = QtWidgets.QLabel('width:')

        self.sb.valueChanged.connect(self.sizeChange)
        self.slb.valueChanged.connect(self.widthChange)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(self.slider_label)
        hbox.addWidget(self.slb)
        hbox.setStretch(0, 0)
        hbox.setSpacing(5)

        hboxGroup = QtWidgets.QHBoxLayout()
        hboxGroup.addWidget(self.sb)
        hboxGroup.addLayout(hbox)
        hboxGroup.setStretch(0, 0)
        hboxGroup.setContentsMargins(0, 0, 0, 0)  # we don't need margins here
        hboxGroup.setSpacing(30)
        self.setLayout(hboxGroup)

    # setters
    def set_val(self, val):
        """A python-dict containing: size, and width parms. """
        sig = False
        if 'size' in val:
            self._val['size'] = val['size']
            self.setSizeQuietly(val['size'])
            sig = True
        if 'width' in val:
            self._val['width'] = val['width']
            self.setWidthQuietly(val['width'])
            sig = True
        if sig:
            self.valueChanged.emit()

    # getters
    def get_val(self):
        return self._val

    # support
    def sizeChange(self, val):
        self._val['size'] = val
        self.setSizeQuietly(val)
        self.valueChanged.emit()

    def widthChange(self, val):
        self._val['width'] = val
        self.setWidthQuietly(val)
        self.valueChanged.emit()

    def setSizeQuietly(self, val):
        self.sb.blockSignals(True)
        self.sb.set_val(val)
        self.slb.set_max(val)
        self.sb.blockSignals(False)

    def setWidthQuietly(self, val):
        self.slb.blockSignals(True)
        self.slb.set_val(val)
        self.slb.blockSignals(False)
Beispiel #25
0
class GPI_FSM(QtCore.QObject):
    """The GPI Canvase and Nodes operate within different states.  The finite
    state machine allows for fast and easy checking to determine whether the
    user has performed an invalid operation or not.
    """
    switched = gpi.Signal()

    def __init__(self, name=''):
        super(GPI_FSM, self).__init__()
        self._name = name
        self._states = []
        self._cur_state = None

    @property
    def curState(self):
        return self._cur_state

    @property
    def curStateName(self):
        return self._cur_state.name

    @property
    def name(self):
        return self._name

    def next(self, dsig):
        '''Validate input signal as part of _cur_state,
        then switch to indicated state.'''
        if isinstance(dsig, str):
            sig = str(dsig)
            dsig = sig
        elif isinstance(dsig, str):
            sig = str(dsig)
            dsig = sig
        elif isinstance(dsig, dict):
            if 'sig' in dsig:
                if isinstance(dsig['sig'], str) or isinstance(
                        dsig['sig'], str):
                    sig = str(dsig['sig'])
                    dsig['sig'] = sig
                else:
                    msg = "expecting str in dict[\'sig\'] in arg: GPI_FSM(" + self._name + ").next(>str<)"
                    log.critical(msg)
            else:
                msg = "expecting str key \'sig\' in arg: GPI_FSM(" + self._name + ").next(>str<)"
                log.critical(msg)
                return
        else:
            msg = "expecting str (or str in dict[\'sig\']) arg: GPI_FSM(" + \
                    self._name + ").next(>str<) type:" + str(type(dsig))
            log.critical(msg)
            return

        if sig in self._cur_state.transitions():
            self._cur_state.onExit(dsig)
            self._cur_state = self._cur_state.transitions()[sig]
            msg = "GPI_FSM(" + self._name + "):next(): Switched to state(" + \
                self._cur_state.name + ")"
            log.debug(msg)
            self._cur_state.onEntry(dsig)
            self.switched.emit()
        else:
            msg = "GPI_FSM(" + self._name + "):next(): state(" + self._cur_state.name + \
                ") has no transition: \'" + str(
                    sig) + "\'\n" + str(self._cur_state.transitions())
            log.debug(msg)

    def addState(self, state):
        if state in self._states:
            msg = "EMPHATIC WARNING!!!: GPI_FSM(" + self._name + "):addState(): Warning, state(" + state.name \
                + ") already exists, skipping..."
            log.critical(msg)
        else:
            self._states.append(state)

    def start(self, state):
        if state in self._states:
            self._cur_state = state
            msg = "GPI_FSM(" + self._name + \
                "):start(): in state(" + state.name + ")"
            log.debug(msg)
            self._cur_state.onEntry('init')
        else:
            msg = "GPI_FSM(" + self._name + "):start(): ERROR, state(" + state.name \
                + ") state not in list, can't start GPI_FSM!"
            log.critical(msg)
Beispiel #26
0
class GPIGLWidget(QtOpenGL.QGLWidget):
    xRotationChanged = gpi.Signal(int)
    yRotationChanged = gpi.Signal(int)
    zRotationChanged = gpi.Signal(int)

    def __init__(self, parent=None):
        super(GPIGLWidget, self).__init__(parent)

        self.xRot = 0
        self.yRot = 0
        self.zRot = 0
        self._vScale = 1.0
        self._xPan = 0.0
        self._yPan = 0.0

        # a list of object descriptions held in dictionaries
        self._GPI_glList = glo.ObjectList()

        # trac number of exceptions thrown in problem code
        self._glError_cnt = 0

        # cache objects in a gl list
        self._glList_cache = None
        self._glList_nonCacheable = []

        # lighting
        self._default_lightPos = [0.0, 0.0, 10.0, 1.0]
        self._lightPos = self._default_lightPos[:]

        # built-in options
        self._blend = 0
        self._test = 0
        self._polyfill = 0
        self._accum_antialiasing = 0

        # self.setMouseTracking(True)

        self.checkFormat()

    def checkFormat(self):
        msg = "\n\taccum buffer:" + str(self.format().accum()) + "\n"
        msg += "\talpha buffer:" + str(self.format().alpha()) + "\n"
        msg += "\tdepth buffer:" + str(self.format().depth()) + "\n"
        msg += "\tdirectRendering:" + \
            str(self.format().directRendering()) + "\n"
        msg += "\tdoubleBuffer:" + str(self.format().doubleBuffer()) + "\n"
        msg += "\thasOverlay:" + str(self.format().hasOverlay()) + "\n"
        msg += "\tplane:" + str(self.format().plane()) + "\n"
        msg += "\trgba:" + str(self.format().rgba()) + "\n"
        log.node(msg)

    def setGPIglList(self, lst):
        if isinstance(lst, glo.ObjectList):
            self._GPI_glList = lst

    def getGPIglList(self):
        return self._GPI_glList

    def __del__(self):
        # cannot guarantee that the underlying object hasn't been deleted
        # before this context is made current
        try:
            self.makeCurrent()
        except:
            log.node(traceback.format_exc())
        self.resetGLCache()

    def setViewScale(self, s):
        self._vScale += s
        self.updateGL()

    def setXRotation(self, angle):
        self.normalizeAngle(angle)

        if angle != self.xRot:
            self.xRot = angle
            self.xRotationChanged.emit(angle)
            self.updateGL()

    def setYRotation(self, angle):
        self.normalizeAngle(angle)

        if angle != self.yRot:
            self.yRot = angle
            self.yRotationChanged.emit(angle)
            self.updateGL()

    def setZRotation(self, angle):
        self.normalizeAngle(angle)

        if angle != self.zRot:
            self.zRot = angle
            self.zRotationChanged.emit(angle)
            self.updateGL()

    def initializeGL(self):

        # from basic gear pos
        GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, self._lightPos)
        GL.glEnable(GL.GL_LIGHTING)
        GL.glEnable(GL.GL_LIGHT0)
        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glEnable(GL.GL_NORMALIZE)

        # set scene to black
        GL.glClearColor(0.0, 0.0, 0.0, 0.0)

        # cache input objects
        self._clipON = False
        # self.instantiateRefs()
        self.cacheGLCommands()

    def paintGL(self):

        GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, self._lightPos)

        if self._blend:

            GL.glCullFace(GL.GL_BACK)
            GL.glEnable(GL.GL_CULL_FACE)
            GL.glBlendFunc(GL.GL_SRC_ALPHA_SATURATE, GL.GL_ONE)

            # something is not properly initialized for this to run
            # correctly at the beginning.
            try:
                GL.glClear(GL.GL_COLOR_BUFFER_BIT)
            except:
                log.node(traceback.format_exc())
                self._glError_cnt += 1

            GL.glEnable(GL.GL_BLEND)
            GL.glEnable(GL.GL_POLYGON_SMOOTH)
            GL.glDisable(GL.GL_DEPTH_TEST)
        else:
            GL.glDisable(GL.GL_CULL_FACE)

            # something is not properly initialized for this to run
            # correctly at the beginning.
            try:
                GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
            except:
                log.node(traceback.format_exc())
                self._glError_cnt += 1

            GL.glDisable(GL.GL_BLEND)
            GL.glDisable(GL.GL_POLYGON_SMOOTH)
            GL.glEnable(GL.GL_DEPTH_TEST)

        if self._polyfill == 2:
            GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_POINT)
        elif self._polyfill == 1:
            GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        else:
            GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)

        if self._accum_antialiasing:
            GL.glClear(GL.GL_ACCUM_BUFFER_BIT)
            cnt = 3
            mult = cnt * 2
            mult *= mult
            invmult = 1.0 / mult
            for i in range(-cnt, cnt):
                for j in range(-cnt, cnt):
                    # jitter and paint
                    GL.glPushMatrix()
                    GL.glTranslatef(i * 0.00511, j * 0.00511, 0.0)
                    self.paintScene()
                    GL.glPopMatrix()

                    # accum
                    GL.glAccum(GL.GL_ACCUM, invmult)
            GL.glAccum(GL.GL_RETURN, 1.0)
        else:
            self.paintScene()

        if self._test:
            pass
        else:
            pass

        # force all commands to complete
        GL.glFlush()

    def paintScene(self):
        '''Does the actuall object rendering calls.
        '''
        # rotate view
        GL.glPushMatrix()
        GL.glRotatef(self.xRot / 16.0, 1.0, 0.0, 0.0)
        GL.glRotatef(self.yRot / 16.0, 0.0, 1.0, 0.0)
        GL.glRotatef(self.zRot / 16.0, 0.0, 0.0, 1.0)

        # pan
        mtx = np.array(GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX))
        A = mtx[0:3, 0:2]
        x = np.transpose(np.array([[self._xPan, self._yPan]]))
        b = np.dot(A, x)
        GL.glTranslatef(b[0], b[1], b[2])

        # zoom
        GL.glScale(self._vScale, self._vScale, self._vScale)

        # render all non-cacheable first
        if len(self._glList_nonCacheable):
            for desc in self._glList_nonCacheable:
                desc.run()

        # use cached list for mouse interactions
        try:
            GL.glCallList(self._glList_cache)
        except:
            log.node(traceback.format_exc())
            self._glError_cnt += 1

        # rotate view
        GL.glPopMatrix()

    def resizeGL(self, width, height):
        side = min(width, height)
        if side < 0:
            return

        GL.glViewport((width - side) // 2, (height - side) // 2, side, side)

        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        GL.glFrustum(-1.0, +1.0, -1.0, 1.0, 5.0, 60.0)
        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glLoadIdentity()
        GL.glTranslatef(0.0, 0.0, -40.0)

    def resetGLCache(self):
        '''reset both cacheable and non-cacheable items
        '''
        if self._glList_cache:
            GL.glDeleteLists(self._glList_cache, 1)
        self._glList_cache = None
        self._glList_nonCacheable = []

    def instantiateRefs(self):
        '''Run thru all objects calling their instantiateLibs() method.
        '''
        for desc in self._GPI_glList:
            desc.instantiateRefs()
            if type(desc) is glo.ClipPlane:
                print("enable clipping", desc.getPlaneNumTr())
                GL.glEnable(desc.getPlaneNumTr())

    def cacheGLCommands(self):
        '''Cache object commands.
        '''
        if self._glList_cache:
            self.resetGLCache()

        self._glList_cache = GL.glGenLists(1)
        GL.glNewList(self._glList_cache, GL.GL_COMPILE)

        # cache all commands in obj list
        for desc in self._GPI_glList.getCacheableList():
            desc.run()

        # special objects
        for plane, desc in self._GPI_glList.getClipPlanes().items():
            print('render: ' + plane)
            desc.run()

        GL.glEndList()

        # get all non-cacheables
        self._glList_nonCacheable = self._GPI_glList.getNonCacheableList()
        if len(self._glList_nonCacheable):
            for desc in self._glList_nonCacheable:
                desc.setGLWidgetRef(self)

    def wheelEvent(self, event):
        if event.delta() > 0:
            self.setViewScale(0.1)
        else:
            self.setViewScale(-0.1)

    def mousePressEvent(self, event):
        self.lastPos = event.pos()

    def mouseMoveEvent(self, event):
        dx = event.x() - self.lastPos.x()
        dy = event.y() - self.lastPos.y()

        printMouseEvent(event)
        modifiers = getKeyboardModifiers()
        modmidbutton_event = (event.buttons() & QtCore.Qt.RightButton
                              and modifiers & QtCore.Qt.ShiftModifier)

        if event.buttons() & QtCore.Qt.LeftButton:
            self._xPan += dx * 0.05
            self._yPan += -dy * 0.05
            self.updateGL()
        elif modmidbutton_event:
            self.setXRotation(self.xRot + 8 * dy)
            self.setYRotation(self.yRot + 8 * dx)
        elif event.buttons() & QtCore.Qt.MidButton:
            self._lightPos[0] += dx * 0.05
            self._lightPos[1] += -dy * 0.05
            self.updateGL()
        elif event.buttons() & QtCore.Qt.RightButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setZRotation(self.zRot + 8 * dx)

        self.lastPos = event.pos()

    def resetViewingWindow(self):
        '''reset pan, zoom, rotate
        '''
        self._xPan = 0.0
        self._yPan = 0.0
        self._vScale = 1.0
        self.xRot = 0.0
        self.yRot = 0.0
        self.zRot = 0.0
        self._lightPos = self._default_lightPos[:]

    def xRotation(self):
        return self.xRot

    def yRotation(self):
        return self.yRot

    def zRotation(self):
        return self.zRot

    def normalizeAngle(self, angle):
        while (angle < 0):
            angle += 360 * 16

        while (angle > 360 * 16):
            angle -= 360 * 16

    def setBlend(self, val):
        self._blend = val
        self.updateGL()

    def setTest(self, val):
        self._test = val
        self.updateGL()

    def setPolyFill(self, val):
        self._polyfill = val
        self.updateGL()

    def setAntiAliasing(self, val):
        self._accum_antialiasing = val
        self.updateGL()
class ConfigSeqWidgets(gpi.GenericWidgetGroup):
    """A unique widget that display a variable number of StringBoxes depending on the Pulse being configured."""

    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super(ConfigSeqWidgets, self).__init__(title, parent)
        self.button_names_list = [
            'Off', 'SincRfPulse', 'HardRfPulse', 'TrapGradPulse',
            'TriangleGradPulse'
        ]
        self.clicked_button_name, self.clicked_button_index = '', 0
        self.buttons_list, self.string_box_list = [], []

        # Labels for StringBoxes to configure Events
        self.sinc_rf_labels = [
            'Name', 'Observe', 'ADCs', 'Apodization', 'Bandwidth', 'Axis',
            'FlipAngle', 'Frequency', 'HardwareMode', 'InitialDelay',
            'InitialPhase', 'Refocusing', 'Symmetry', 'Vector', ' Zeros'
        ]
        self.hard_rf_labels = [
            'Name', 'Observe', 'ADCs', 'Channel', 'Duration', 'FlipAngle',
            'Frequency', 'HardwareMode', 'InitialDelay', 'InitialPhase',
            'Refocusing', 'Symmetry', 'Vector'
        ]
        self.trap_grad_labels = [
            'Name', 'Observe', 'ADCs', 'Area', 'Asymmetric', 'Axis',
            'Duration', 'EddyConvLength', 'EddyCurrents', 'FlatTopArea',
            'FlatTopTime', 'Frequency', 'HardwareMode', 'Hide', 'InitialDelay',
            'InitialPhase', 'MaxAmpl', 'NLG_field', 'PhaseLock', 'SlewRate'
        ]
        self.triangle_grad_labels = [
            'Name', 'Observe', 'ADCs', 'Amplitude', 'Axis', 'Duration',
            'EddyConvLength', 'EddyCurrents', 'HardwareMode', 'Hide',
            'InitialDelay', 'InitialPhase', 'MaxAmpl', 'NLG_field',
            'PhaseLock', 'TriangleType', 'Vector'
        ]

        # Variable to denote the maximum number of StringBoxes to be added; obviously depends on the Event which has the
        # maximum number of configuration parameters_params
        self.num_string_boxes = max(len(self.hard_rf_labels),
                                    len(self.trap_grad_labels))

        # First index is None because the first button is 'Off'. Look into event_def['event_values'] in get_val()
        self.labels = [
            None, self.sinc_rf_labels, self.hard_rf_labels,
            self.trap_grad_labels, self.triangle_grad_labels
        ]

        self.wdg_layout = QtGui.QGridLayout()
        self.add_event_pushbuttons()
        self.add_config_stringboxes()

        self.setLayout(self.wdg_layout)
        self.buttons_list[0].setChecked(True)

    def add_event_pushbuttons(self):
        """Adding PushButtons for the Pulses."""
        col_count = 0
        for name in self.button_names_list:
            new_button = QtGui.QPushButton(name)
            new_button.setCheckable(True)
            new_button.setAutoExclusive(True)
            new_button.clicked.connect(self.button_clicked)
            new_button.clicked.connect(self.valueChanged)
            # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
            self.wdg_layout.addWidget(new_button, 0, col_count, 1, 1)
            self.buttons_list.append(new_button)
            col_count += 1

    def add_config_stringboxes(self):
        """Adding StringBoxes for configuring the Pulses."""
        for x in range(self.num_string_boxes):
            string_box = gpi.StringBox(str(x))
            string_box.set_visible(False)
            # Syntax: addWidget(widget, row, col, rowSpan, colSpan)
            self.wdg_layout.addWidget(string_box, x + 1, 1, 1, 6)
            self.string_box_list.append(string_box)

    # Getter
    def get_val(self):
        if self.clicked_button_index == self.button_names_list.index('Off'):
            # 'Off' PushButton selected, return empty dict
            return None
        else:
            """
            event_def contains:
            - event_name : str
                Event name, corresponds to Event button that is selected. See make_pulse() in JWriteXML_GPI
            - key-value pairs of Event parameters_params and values
            """
            event_def = {}
            labels = self.labels[self.clicked_button_index]
            for i in range(len(self.string_box_list)):
                val = self.string_box_list[i].get_val()
                if val != '':
                    event_def[labels[i]] = val
            event_def['event_name'] = self.clicked_button_name
            return event_def

    # Setter
    def set_val(self, val):
        self.hide_config_widgets()
        if val is not None and len(val) != 0:
            # If Event is not configured, self.clicked_button_name will be ''
            self.clicked_button_name = val['event_name']
            if self.clicked_button_name != '':
                self.clicked_button_index = self.button_names_list.index(
                    self.clicked_button_name)
                self.buttons_list[self.clicked_button_index].setChecked(True)
                self.show_config_widgets()

                labels = self.labels[self.clicked_button_index]
                for x in range(len(labels)):
                    # Set value only if value is present in val dict
                    if labels[x] in val:
                        self.string_box_list[x].setTitle(labels[x])
                        self.string_box_list[x].set_val(val[labels[x]])

    def button_clicked(self):
        """Identifies the button that was clicked and stores the name and ID of the button."""
        for button in self.buttons_list:
            if button.isChecked():
                self.clicked_button_index = self.buttons_list.index(button)
                self.clicked_button_name = self.button_names_list[
                    self.clicked_button_index]
        self.show_config_widgets()

    def show_config_widgets(self):
        """Show appropriate number of StringBoxes and relevant Widgets based on the button that was clicked."""
        self.hide_config_widgets()

        if self.clicked_button_index != 0:
            [
                self.string_box_list[x].set_visible(True)
                for x in range(len(self.labels[self.clicked_button_index]))
            ]
            [
                self.string_box_list[x].setTitle(
                    self.labels[self.clicked_button_index][x])
                for x in range(len(self.labels[self.clicked_button_index]))
            ]

    def hide_config_widgets(self):
        """Hide all Widgets."""
        [x.set_visible(False) for x in self.string_box_list]
        [x.set_val("") for x in self.string_box_list]
Beispiel #28
0
class FFTW_GROUP(gpi.GenericWidgetGroup):
    """A combination of SpinBoxes, DoubleSpinBoxes, and PushButtons
    to form a unique widget suitable for FFT options on dimensions.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        self._val = {}
        self._val['compute'] = False
        self._val['length'] = 1
        self._val['in_len'] = 1  # the original array length

        self.pb = gpi.BasicPushButton()
        self.pb.set_toggle(True)

        self.db = gpi.BasicDoubleSpinBox()  # factor
        self.db.set_label('factor:')
        self.db.set_min(0.001)
        self.db.set_max(gpi.GPI_FLOAT_MAX)
        self.db.set_decimals(3)
        self.db.set_singlestep(0.001)
        self.db.set_val(1)

        self.sb = gpi.BasicSpinBox()  # length
        self.sb.set_label('length:')
        self.sb.set_min(1)
        self.sb.set_val(1)
        self.sb.set_max(gpi.GPI_INT_MAX)

        self.db.valueChanged.connect(self.factChange)
        self.sb.valueChanged.connect(self.lenChange)
        self.pb.valueChanged.connect(self.compChange)

        vbox = QtWidgets.QHBoxLayout()
        vbox.addWidget(self.pb)
        vbox.addWidget(self.db)
        vbox.addWidget(self.sb)
        vbox.setStretch(0, 0)
        vbox.setStretch(1, 0)
        vbox.setStretch(2, 0)
        vbox.setContentsMargins(0, 0, 0, 0)  # we don't need margins here
        vbox.setSpacing(0)
        self.setLayout(vbox)

    # setters
    def set_val(self, val):
        """A python-dict containing: in_len, length, and compute parms. """
        sig = False
        if 'in_len' in val:
            # otherwise this would change every time compute() was called
            if self._val['in_len'] != val['in_len']:
                self._val['in_len'] = val['in_len']
                fact = self.db.get_val()  # set len based on factor
                fact *= self._val['in_len']
                self.setLenQuietly(int(fact))
                self._val['length'] = int(fact)
        if 'length' in val:
            self._val['length'] = val['length']
            self.setLenQuietly(val['length'])
            self.setFactQuietly(
                float(val['length']) / float(self._val['in_len']))
            sig = True
        if 'compute' in val:
            self._val['compute'] = val['compute']
            self.setCompQuietly(val['compute'])
            sig = True
        if sig:
            self.valueChanged.emit()

    def set_reset(self):
        """An override that communicates with the embedded pushbutton. """
        self.pb.set_reset()

    # getters
    def get_val(self):
        return self._val

    # support
    def factChange(self, val):
        self._val['length'] = int(self._val['in_len'] * val)
        self.setLenQuietly(self._val['length'])
        self.valueChanged.emit()

    def lenChange(self, val):
        self._val['length'] = val
        self.setFactQuietly(float(val) / float(self._val['in_len']))
        self.valueChanged.emit()

    def compChange(self, val):
        self._val['compute'] = val
        self.valueChanged.emit()
        if val:
            self.pb.set_button_title('ON')
        else:
            self.pb.set_button_title('')

    def setFactQuietly(self, val):
        self.db.blockSignals(True)
        self.db.set_val(val)
        self.db.blockSignals(False)

    def setLenQuietly(self, val):
        self.sb.blockSignals(True)
        self.sb.set_val(val)
        self.sb.blockSignals(False)

    def setCompQuietly(self, val):
        self.pb.blockSignals(True)
        self.pb.set_val(val)
        self.pb.blockSignals(False)
Beispiel #29
0
class MatplotDisplay(gpi.GenericWidgetGroup):

    """Embeds the matplotlib figure window.
    """
    valueChanged = gpi.Signal()

    def __init__(self, title, parent=None):
        super().__init__(title, parent)

        # gpi interface
        self._collapsables = []
        self._subplotSettings = {}
        #self._subplotPosition = {'right': 0.9, 'bottom': 0.12, 'top': 0.9, 'wspace': 0.2, 'hspace': 0.2, 'left': 0.125}
        self._subplotPosition = {'right': 0.913, 'bottom': 0.119, 'top': 0.912, 'wspace': 0.2, 'hspace': 0.2, 'left': 0.111}
        #self._subplot_keepers = ['yscale', 'xscale'] # linear, log
        self._subplot_keepers = []
        self._lineSettings = []
        self._line_keepers = ['linewidth', 'linestyle', 'label', 'marker', 'markeredgecolor', 'markerfacecolor', 'markersize', 'color', 'alpha']

        # since drawing is slow, don't do it as often, use the timer as a
        # debouncer
        self._on_draw_cnt = 0
        self._updatetimer = QtCore.QTimer()
        self._updatetimer.setSingleShot(True)
        self._updatetimer.timeout.connect(self._on_draw)
        self._updatetimer.setInterval(10)

        # plot specific UI side panel
        #  -sets options for plot window so this needs to be run first
        vbox = QtWidgets.QVBoxLayout()
        vbox.setContentsMargins(0, 0, 0, 0)  # no spaces around this item
        vbox.setSpacing(0)

        # AUTOSCALE
        self._autoscale_btn = gpi.widgets.BasicPushButton(self)
        self._autoscale_btn.set_toggle(True)
        self._autoscale_btn.set_button_title('autoscale')
        self._autoscale_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._autoscale_btn)

        # GRID
        self._grid_btn = gpi.widgets.BasicPushButton(self)
        self._grid_btn.set_toggle(True)
        self._grid_btn.set_button_title('grid')
        self._grid_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._grid_btn)

        # X/Y LIMITS
        lims = QtWidgets.QGridLayout()
        self._xl = gpi.widgets.BasicDoubleSpinBox(self)
        self._xh = gpi.widgets.BasicDoubleSpinBox(self)
        self._yl = gpi.widgets.BasicDoubleSpinBox(self)
        self._yh = gpi.widgets.BasicDoubleSpinBox(self)
        self._xl.valueChanged.connect(self.on_draw)
        self._xh.valueChanged.connect(self.on_draw)
        self._yl.valueChanged.connect(self.on_draw)
        self._yh.valueChanged.connect(self.on_draw)
        self._xl.set_immediate(True)
        self._xh.set_immediate(True)
        self._yl.set_immediate(True)
        self._yh.set_immediate(True)
        self._xl.set_label('max')
        self._xh.set_label('min')
        self._xl.set_decimals(7)
        self._xh.set_decimals(7)
        self._yl.set_decimals(7)
        self._yh.set_decimals(7)
        self._xlab = QtWidgets.QLabel('x limits')
        self._ylab = QtWidgets.QLabel('y limits')
        #self._maxlab = QtWidgets.QLabel('max')
        #self._minlab = QtWidgets.QLabel('min')
        #lims.addWidget(self._maxlab,1,0,1,1)
        #lims.addWidget(self._minlab,2,0,1,1)
        lims.addWidget(self._xlab,0,1,1,1,alignment=QtCore.Qt.AlignHCenter)
        lims.addWidget(self._xh,1,1,1,1,alignment=QtCore.Qt.AlignHCenter)
        lims.addWidget(self._xl,2,1,1,1,alignment=QtCore.Qt.AlignHCenter)
        lims.addWidget(self._ylab,0,2,1,1,alignment=QtCore.Qt.AlignHCenter)
        lims.addWidget(self._yh,1,2,1,1,alignment=QtCore.Qt.AlignHCenter)
        lims.addWidget(self._yl,2,2,1,1,alignment=QtCore.Qt.AlignHCenter)
        self._collapsables.append(self._xlab)
        self._collapsables.append(self._ylab)
        self._collapsables.append(self._xl)
        self._collapsables.append(self._xh)
        self._collapsables.append(self._yl)
        self._collapsables.append(self._yh)
        #self._collapsables.append(self._minlab)
        #self._collapsables.append(self._maxlab)

        # TICK MARKS
        ticks = QtWidgets.QGridLayout()
        self._x_numticks = gpi.widgets.BasicSpinBox(self)
        self._x_numticks.valueChanged.connect(self.on_draw)
        self._y_numticks = gpi.widgets.BasicSpinBox(self)
        self._y_numticks.valueChanged.connect(self.on_draw)
        self._x_ticks = QtWidgets.QLineEdit()
        self._y_ticks = QtWidgets.QLineEdit()
        self._x_ticks.textChanged.connect(lambda txt: self.check_validticks(self._x_ticks))
        self._y_ticks.textChanged.connect(lambda txt: self.check_validticks(self._y_ticks))
        self._x_ticks.setPlaceholderText('comma separated list of x labels')
        self._y_ticks.setPlaceholderText('comma separated list of y labels')
        self._x_ticks.returnPressed.connect(self.on_draw)
        self._y_ticks.returnPressed.connect(self.on_draw)
        self._x_numticks.set_immediate(True)
        self._y_numticks.set_immediate(True)
        self._x_numticks.set_min(2)
        self._y_numticks.set_min(2)
        self._x_numticks.set_max(100)
        self._y_numticks.set_max(100)
        self._x_numticks.set_val(5)
        self._y_numticks.set_val(5)
        self._x_numticks.set_label('x ticks')
        self._y_numticks.set_label('y ticks')
        ticks.addWidget(self._x_numticks, 0,0,1,1)
        ticks.addWidget(self._y_numticks, 1,0,1,1)
        ticks.addWidget(self._x_ticks, 0,1,1,1)
        ticks.addWidget(self._y_ticks, 1,1,1,1)
        self._collapsables.append(self._x_numticks)
        self._collapsables.append(self._y_numticks)
        self._collapsables.append(self._x_ticks)
        self._collapsables.append(self._y_ticks)

        # TITLE, XLABEL, YLABEL
        plotlabels = QtWidgets.QHBoxLayout()
        self._plot_title = QtWidgets.QLineEdit()
        self._plot_xlab = QtWidgets.QLineEdit()
        self._plot_ylab = QtWidgets.QLineEdit()
        self._plot_title.setPlaceholderText('title')
        self._plot_xlab.setPlaceholderText('x label')
        self._plot_ylab.setPlaceholderText('y label')
        self._plot_title.returnPressed.connect(self.on_draw)
        self._plot_xlab.returnPressed.connect(self.on_draw)
        self._plot_ylab.returnPressed.connect(self.on_draw)
        plotlabels.addWidget(self._plot_title)
        plotlabels.addWidget(self._plot_xlab)
        plotlabels.addWidget(self._plot_ylab)
        self._collapsables.append(self._plot_title)
        self._collapsables.append(self._plot_xlab)
        self._collapsables.append(self._plot_ylab)

        # XSCALE, YSCALE
        self._xscale_btn = gpi.widgets.BasicPushButton(self)
        self._xscale_btn.set_toggle(True)
        self._xscale_btn.set_button_title('log(x)')
        self._xscale_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._xscale_btn)
        self._yscale_btn = gpi.widgets.BasicPushButton(self)
        self._yscale_btn.set_toggle(True)
        self._yscale_btn.set_button_title('log(y)')
        self._yscale_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._yscale_btn)

        scale_options_layout = QtWidgets.QHBoxLayout()
        scale_options_layout.addWidget(self._xscale_btn)
        scale_options_layout.addWidget(self._yscale_btn)

        # LEGEND
        self._legend_btn = gpi.widgets.BasicPushButton(self)
        self._legend_btn.set_toggle(True)
        self._legend_btn.set_button_title('legend')
        self._legend_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._legend_btn)

        # HOLD
        self._hold_btn = gpi.widgets.BasicPushButton(self)
        self._hold_btn.set_toggle(True)
        self._hold_btn.set_button_title('hold')
        #self._hold_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._hold_btn)

        # MOVE AXES TO ORIGIN
        # self._origin_axes_btn = gpi.widgets.BasicPushButton(self)
        # self._origin_axes_btn.set_toggle(True)
        # self._origin_axes_btn.set_button_title("axes at (0,0)")
        # self._collapsables.append(self._origin_axes_btn)

        # RESET
        self._reset_btn = gpi.widgets.BasicPushButton(self)
        self._reset_btn.set_toggle(False)
        self._reset_btn.set_button_title('reset')
        self._reset_btn.valueChanged.connect(self._init_parms_)
        self._collapsables.append(self._reset_btn)

        # X=0, Y=0
        self._xeq0_btn = gpi.widgets.BasicPushButton(self)
        self._xeq0_btn.set_toggle(True)
        self._xeq0_btn.set_button_title('x=0')
        self._xeq0_btn.set_val(True)
        self._xeq0_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._xeq0_btn)
        self._yeq0_btn = gpi.widgets.BasicPushButton(self)
        self._yeq0_btn.set_toggle(True)
        self._yeq0_btn.set_button_title('y=0')
        self._yeq0_btn.set_val(True)
        self._yeq0_btn.valueChanged.connect(self.on_draw)
        self._collapsables.append(self._yeq0_btn)

        # LINE OPTIONS
        self._lino_btn = gpi.widgets.BasicPushButton(self)
        self._lino_btn.set_toggle(False)
        self._lino_btn.set_button_title('line options')
        self._lino_btn.valueChanged.connect(self.lineOptionsDialog)
        self._collapsables.append(self._lino_btn)

        # SUBPLOT SPACING OPTIONS
        self._subplotso_btn = gpi.widgets.BasicPushButton(self)
        self._subplotso_btn.set_toggle(False)
        self._subplotso_btn.set_button_title('spacing options')
        self._subplotso_btn.valueChanged.connect(self.subplotSpacingOptions)
        self._collapsables.append(self._subplotso_btn)
        self.adj_window = None

        plot_options_layout = QtWidgets.QHBoxLayout()
        plot_options_layout.addWidget(self._subplotso_btn)
        plot_options_layout.addWidget(self._lino_btn)

        grid_legend_lyt = QtWidgets.QHBoxLayout()
        grid_legend_lyt.addWidget(self._legend_btn)
        grid_legend_lyt.addWidget(self._grid_btn)

        autoscale_scale_lyt = QtWidgets.QHBoxLayout()
        autoscale_scale_lyt.addWidget(self._autoscale_btn)
        autoscale_scale_lyt.addWidget(self._xscale_btn)
        autoscale_scale_lyt.addWidget(self._yscale_btn)
        autoscale_scale_lyt.addWidget(self._xeq0_btn)
        autoscale_scale_lyt.addWidget(self._yeq0_btn)

        # HLINES
        self._hline1 = QtWidgets.QFrame()
        self._hline1.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
        self._hline1.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        self._collapsables.append(self._hline1)

        self._hline2 = QtWidgets.QFrame()
        self._hline2.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
        self._hline2.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        self._collapsables.append(self._hline2)

        self._hline3 = QtWidgets.QFrame()
        self._hline3.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
        self._hline3.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        self._collapsables.append(self._hline3)

        spc = 10
        self._spacer1 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._spacer2 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._spacer3 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._spacer4 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._spacer5 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._spacer6 = QtWidgets.QSpacerItem(1,spc,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self._collapsables.append(self._spacer1)
        self._collapsables.append(self._spacer2)
        self._collapsables.append(self._spacer3)
        self._collapsables.append(self._spacer4)
        self._collapsables.append(self._spacer5)
        self._collapsables.append(self._spacer6)

        # panel layout
        vbox.addLayout(plotlabels)

        vbox.addSpacerItem(self._spacer1)
        vbox.addWidget(self._hline1)
        vbox.addSpacerItem(self._spacer2)

        vbox.addLayout(lims)
        #vbox.addLayout(scale_options_layout)
        #vbox.addWidget(self._autoscale_btn)
        vbox.addLayout(autoscale_scale_lyt)

        vbox.addSpacerItem(self._spacer3)
        vbox.addWidget(self._hline2)
        vbox.addSpacerItem(self._spacer4)

        vbox.addLayout(ticks)
        #vbox.addWidget(self._legend_btn)
        vbox.addLayout(grid_legend_lyt)
        vbox.addLayout(plot_options_layout)
        #vbox.addWidget(self._lino_btn)
        #vbox.addWidget(self._subplotso_btn)

        vbox.addSpacerItem(self._spacer5)
        vbox.addWidget(self._hline3)
        vbox.addSpacerItem(self._spacer6)

        vbox.addWidget(self._hold_btn)
        # vbox.addWidget(self._origin_axes_btn)

        vbox.insertStretch(-1,1)
        vbox.addWidget(self._reset_btn)

        # plot window
        self._data = None
        self._plotwindow = self.create_main_frame()

        # put side panel and plot window together
        hbox = QtWidgets.QHBoxLayout()
        hbox.addLayout(vbox)
        hbox.addLayout(self._plotwindow)
        hbox.setStretch(0,0)
        hbox.setStretch(1,11)
        self.setLayout(hbox)

        #self._on_draw() # draw once to get initial settings
        #self.copySubplotSettings()

        # Don't hide side-panel options by default
        self.set_collapsed(False)
        self.set_grid(True)
        self.set_autoscale(True)

        # DEFAULTS
        self._init_parms_()

    # setters
    def set_val(self, data):
        '''Takes a list of npy arrays.
        '''
        if isinstance(data, list):
            self._data = data
            self.on_draw()

    def set_grid(self, val):
        self._grid_btn.set_val(val)
        self.on_draw()

    def set_autoscale(self, val):
        self._autoscale_btn.set_val(val)
        self.on_draw()

    def set_collapsed(self, val):
        """bool | Only collapse the display options, not the Plot window.
        """
        self._isCollapsed = val
        for wdg in self._collapsables:
            if hasattr(wdg, 'setVisible'):
                wdg.setVisible(not val)

    def set_xlim(self, val, quiet=False):
        '''tuple of floats (min, max)
        '''
        self._xh.set_val(val[0])
        self._xl.set_val(val[1])
        if not quiet:
            self.on_draw()

    def set_ylim(self, val, quiet=False):
        '''tuple of floats (min, max)
        '''
        self._yh.set_val(val[0])
        self._yl.set_val(val[1])
        if not quiet:
            self.on_draw()

    def set_plotOptions(self, val):
        self._subplotSettings = val

    def set_lineOptions(self, val):
        self._lineSettings = val

    def set_plotPosition(self, val):
        self._subplotPosition = val

    def set_ticks(self, s):
        self._x_numticks.set_val(s['xticknum'])
        self._y_numticks.set_val(s['yticknum'])
        self._x_ticks.setText(s['xticks'])
        self._y_ticks.setText(s['yticks'])

    def set_plotlabels(self, s):
        self._plot_title.setText(s['title'])
        self._plot_xlab.setText(s['xlabel'])
        self._plot_ylab.setText(s['ylabel'])
        self.on_draw()

    def set_legend(self, val):
        self._legend_btn.set_val(val)
        self.on_draw()

    def set_xline(self, val):
        self._yeq0_btn.set_val(val)
        self.on_draw()

    def set_yline(self, val):
        self._xeq0_btn.set_val(val)
        self.on_draw()

    def set_scale(self, val):
        self._xscale_btn.set_val(val['xscale'])
        self._yscale_btn.set_val(val['yscale'])
        self.on_draw()

    # getters
    def get_val(self):
        return self._data

    def get_grid(self):
        return self._grid_btn.get_val()

    def get_autoscale(self):
        return self._autoscale_btn.get_val()

    def get_xlim(self):
        return (self._xh.get_val(), self._xl.get_val())

    def get_ylim(self):
        return (self._yh.get_val(), self._yl.get_val())

    def get_plotOptions(self):
        return self._subplotSettings

    def get_lineOptions(self):
        return self._lineSettings

    def get_plotPosition(self):
        return self._subplotPosition

    def get_ticks(self):
        s = {}
        s['xticknum'] = self._x_numticks.get_val()
        s['yticknum'] = self._y_numticks.get_val()
        s['xticks'] = str(self._x_ticks.text())
        s['yticks'] = str(self._y_ticks.text())
        return s

    def get_plotlabels(self):
        s = {}
        s['title'] = str(self._plot_title.text())
        s['xlabel'] = str(self._plot_xlab.text())
        s['ylabel'] = str(self._plot_ylab.text())
        return s

    def get_legend(self):
        return self._legend_btn.get_val()

    def get_xline(self):
        return self._yeq0_btn.get_val()

    def get_yline(self):
        return self._xeq0_btn.get_val()

    def get_scale(self):
        s = {}
        s['xscale'] = self._xscale_btn.get_val()
        s['yscale'] = self._yscale_btn.get_val()
        return s

    # support
    def check_validticks(self, tickwdg):
        s = tickwdg.text()
        comma_cnt = s.count(',')
        if (comma_cnt > 0) or (len(s) == 0):
            color = '#ffffff' # white
            tickwdg.setStyleSheet('QLineEdit { background-color: %s }' % color)
            return

        #color = '#fff79a' # yellow
        color = '#f6989d' # red
        tickwdg.setStyleSheet('QLineEdit { background-color: %s }' % color)
        return

    def create_main_frame(self):
        self.fig = Figure((6.0, 4.8), dpi=100, facecolor='0.98', linewidth=6.0, edgecolor='0.93')
        self.axes = None
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self)
        self.canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.canvas.setFocus()

        self.canvas.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)

        #self.mpl_toolbar = NavigationToolbar(self.canvas, self)
        self.mpl_toolbar = NavbarTools(self.canvas, self)
        self.mpl_toolbar.actionTriggered.connect(self.copySubplotSettings)

        self.canvas.mpl_connect('key_press_event', self.on_key_press)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(self.canvas)  # the matplotlib canvas
        vbox.addWidget(self.mpl_toolbar)
        return vbox
        #self.setLayout(vbox)

    def lineOptionsDialog(self):
        if self.axes is None:
            print("Matplotlib: no lines to modify, skipping line editor")
            return
        figure_edit(self.axes, self)
        self.copySubplotSettings()
        self.on_draw()

    def subplotSpacingOptions(self):
        if self.fig is None:
            return

        # don't allow the user to open extra windows
        if self.adj_window is not None:
            if self.adj_window.isActive():
                self.adj_window.raise_()
                return

        self.adj_window = MainWin_close()
        self.adj_window.window_closed.connect(self.copySubplotSettings)
        win = self.adj_window

        win.setWindowTitle("Subplot Configuration Tool")
        image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' )
        win.setWindowIcon(QtGui.QIcon( image ))

        tool = SubplotToolQt(self.fig, win)
        win.setCentralWidget(tool)
        win.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)

        win.show()

    def copySubplotSettings(self):
        '''Get a copy of the settings found in the 'Figure Options' editor.
        '''
        if self.axes is None:
            return

        # subplot settings
        for k in self._subplot_keepers:
            self._subplotSettings[k] = getattr(self.axes, 'get_'+k)()

        # line settings
        self._lineSettings = []
        for l in self.axes.get_lines():
            s = {}
            for k in self._line_keepers:
                s[k] = getattr(l, 'get_'+k)()
            self._lineSettings.append(s)

        # subplot position
        self._subplotPosition = {}
        self._subplotPosition['left'] = self.fig.subplotpars.left
        self._subplotPosition['right'] = self.fig.subplotpars.right
        self._subplotPosition['top'] = self.fig.subplotpars.top
        self._subplotPosition['bottom'] = self.fig.subplotpars.bottom
        self._subplotPosition['wspace'] = self.fig.subplotpars.wspace
        self._subplotPosition['hspace'] = self.fig.subplotpars.hspace

    def applySubplotSettings(self):
        '''Everytime the plot is drawn it looses its 'Figure Options' so just
        make sure they are applied again.
        '''
        # subplot settings
        for k in self._subplot_keepers:
            if k in self._subplotSettings:
                getattr(self.axes, 'set_'+k)(self._subplotSettings[k])

        #subplot position
        self.fig.subplots_adjust(**self._subplotPosition)

    def _init_parms_(self):
        '''Default parameter settings
        '''
        self._subplotSettings = {}
        self._subplotPosition = {'right': 0.913, 'bottom': 0.119, 'top': 0.912, 'wspace': 0.2, 'hspace': 0.2, 'left': 0.111}
        self._lineSettings = []
        self.set_autoscale(True)
        self.set_grid(True)
        s = {}
        s['xticknum'] = 5
        s['yticknum'] = 5
        s['xticks'] = ''
        s['yticks'] = ''
        self.set_ticks(s)
        s = {}
        s['title'] = ''
        s['xlabel'] = ''
        s['ylabel'] = ''
        self.set_plotlabels(s)
        self.set_legend(False)
        s = {}
        s['xscale'] = False # linear
        s['yscale'] = False
        self.set_scale(s)
        self.on_draw()

    def on_draw(self):
        self._on_draw_cnt += 1
        if not self._updatetimer.isActive():
            self._updatetimer.start()

    def _on_draw(self):

        # HOLD / Create New AXES
        if not self._hold_btn.get_val():
            self.fig.clear()
            self.axes = self.fig.add_subplot(111)

        # AUTOSCALE and LIMITS
        self.axes.set_autoscale_on(self.get_autoscale())
        if not self.get_autoscale():
            self.axes.set_xlim(self.get_xlim())
            self.axes.set_ylim(self.get_ylim())

        # TITLE, XLABEL and YLABEL
        self.axes.set_title(self.get_plotlabels()['title'], fontweight='bold', fontsize=16)
        self.axes.set_xlabel(self.get_plotlabels()['xlabel'], fontsize=14)
        self.axes.set_ylabel(self.get_plotlabels()['ylabel'], fontsize=14)

        # self.axes.plot(self.x, self.y, 'ro')
        # self.axes.imshow(self.data, interpolation='nearest')
        # self.axes.plot([1,2,3])

        # XSCALE
        if self.get_scale()['xscale']:
            self.axes.set_xscale('log')
        else:
            self.axes.set_xscale('linear')

        # YSCALE
        if self.get_scale()['yscale']:
            self.axes.set_yscale('log')
        else:
            self.axes.set_yscale('linear')

        # GRID
        ax_color = '0.5'
        if self.get_grid():
            self.axes.grid(self.get_grid(), color=ax_color)
        else:
            self.axes.grid(self.get_grid())

        # AXES SPINE COLOR
        self.axes.spines['bottom'].set_color(ax_color)
        self.axes.spines['top'].set_color(ax_color)
        self.axes.spines['right'].set_color(ax_color)
        self.axes.spines['left'].set_color(ax_color)
        try:
            # deprecated in Matplotlib 2.0
            self.axes.set_axis_bgcolor('0.97')
        except AttributeError:
            self.axes.set_facecolor('0.97')

        # if self._origin_axes_btn.get_val():
        #     self.axes.spines['left'].set_position('zero')
        #     self.axes.spines['bottom'].set_position('zero')
        #     self.axes.spines['left'].set_smart_bounds(True)
        #     self.axes.spines['bottom'].set_smart_bounds(True)
        #     self.axes.xaxis.set_ticks_position('bottom')
        #     self.axes.yaxis.set_ticks_position('left')

        if self._data is None:
            return

        try:
            self.fig.hold(True)
        except:
            pass

        # plot each set
        # print "--------------------plot the data"
        for data in self._data:
            ln = max(data.shape)
            lw = max(5.0-np.log10(ln), 1.0)
            if ln > 0:
                al = max(1.0-1.0/np.log2(ln), 0.75)
            else:
                al = 0

            if data.shape[-1] == 2:
                self.axes.plot(data[..., 0], data[..., 1], alpha=al, lw=lw)
            else:
                self.axes.plot(data, alpha=al, lw=lw)

        # X=0, Y=0
        if self.get_xline():
            self.axes.axhline(y=0, color='k', zorder=-1, label="y=0")
        if self.get_yline():
            self.axes.axvline(x=0, color='k', zorder=-1, label="x=0")

        # LEGEND
        if self.get_legend():
            handles, labels = self.axes.get_legend_handles_labels()
            self.axes.legend(handles, labels)

        # AUTOSCALE
        if self.get_autoscale():
            self.set_xlim(self.axes.get_xlim(), quiet=True)
            self.set_ylim(self.axes.get_ylim(), quiet=True)

        # X TICKS
        xl = self._x_ticks.text().split(',')
        if len(xl) > 1:
            self.axes.set_xticks(np.linspace(*self.axes.get_xlim(), num=len(xl)))
            self.axes.set_xticklabels(xl)
        else:
            self.axes.set_xticks(np.linspace(*self.axes.get_xlim(), num=self._x_numticks.get_val()))

        # Y TICKS
        yl = self._y_ticks.text().split(',')
        if len(yl) > 1:
            self.axes.set_yticks(np.linspace(*self.axes.get_ylim(), num=len(yl)))
            self.axes.set_yticklabels(yl)
        else:
            self.axes.set_yticks(np.linspace(*self.axes.get_ylim(), num=self._y_numticks.get_val()))

        self.applySubplotSettings()

        self.canvas.draw()

        #print 'draw count: ', self._on_draw_cnt
        self._on_draw_cnt = 0

    def on_key_press(self, event):
        # print 'Matplotlib-> you pressed:' + str(event.key)
        # implement the default mpl key press events described at
        # http://matplotlib.org/users/navigation_toolbar.html#navigation-
        # keyboard-shortcuts
        try:
            from matplotlib.backend_bases import key_press_handler
            key_press_handler(event, self.canvas, self.mpl_toolbar)
        except:
            print("key_press_handler import failed. -old matplotlib version.")