class Dashboard(Plugin):
    """
    Base class from which dashboards should inherit.

    :param context: the plugin context
    :type context: qt_gui.plugin.Plugin
    """
    def __init__(self, context):
        super(Dashboard, self).__init__(context)
        self.context = context
        self.setup(context)

        if not hasattr(self, 'name'):
            self.name = 'Dashboard'
        if not hasattr(self, 'max_icon_size'):
            self.max_icon_size = QSize(50, 30)
        self._main_widget = QToolBar()
        self._main_widget.setIconSize(self.max_icon_size)
        self._main_widget.setObjectName(self.name)
        self._main_widget.setWindowTitle(self.name)
        if context.serial_number() > 1:
            self._main_widget.setWindowTitle(self._main_widget.windowTitle() +
                                             (' (%d)' %
                                              context.serial_number()))

        # Convert list of widgets into layout
        self.add_widgets()

        # Display the dashboard
        context.add_toolbar(self._main_widget)

    def setup(self, context):
        """
        Called during ``__init__`` Subclasses should do initialization here.

        NOTE when overriding this method you should provide a ``self.name`` to
        avoid naming conflicts.

        :param context: The plugin context
        :type context: qt_gui.plugin.Plugin
        """
        pass

    def shutdown_plugin(self):
        """
        Called when the toolbar is closed by Qt.
        """
        for widget in self._widgets:
            if hasattr(widget, 'shutdown_widget'):
                widget.shutdown_widget()
            if hasattr(widget, 'close'):
                widget.close()

        self.shutdown_dashboard()

    def shutdown_dashboard(self):
        """
        Called after shutdown plugin, subclasses should do cleanup here, not in shutdown_plugin
        """
        pass

    def get_widgets(self):
        """
        Most of the dashboard customization should be done here.
        If this function is not overriden the dashboard will display nothing.

        :returns: List of lists containing dashboard widgets, or list of lists
                  containing a string followed by a list of dashboard widgets.
        """
        return []

    def add_widgets(self):
        """
        Add groups of widgets to _main_widget. Supports group labels.

        This method can be reimplemented in order to customize appearances.
        """
        widgets = self.get_widgets()
        self._widgets = [
        ]  # stores widgets which may need to be shut down when done
        for group in widgets:
            # Check for group label
            if isinstance(group[0], str):
                grouplabel, v = group
                box = QGroupBox(grouplabel)
                box.setContentsMargins(0, 18, 0, 0)  # LTRB
                # Apply the center-label directive only for single-icon groups
                if len(group[1]) == 1:
                    box.setAlignment(Qt.AlignHCenter)
            else:
                box = QGroupBox()
                box.setContentsMargins(0, 0, 0, 0)  # LTRB
                v = group
            # Add widgets to QGroupBox
            layout = QHBoxLayout()
            layout.setSpacing(0)
            layout.setContentsMargins(0, 0, 0, 0)  # LTRB
            for i in v:
                try:
                    try:
                        i.setIconSize(
                            self.max_icon_size)  # without this, icons are tiny
                    except AttributeError as e:
                        # triggers with battery which uses a QLabel instead of a QToolButton-based widget
                        pass
                    layout.addWidget(i)
                    self._widgets.append(i)
                except:
                    raise Exception(
                        "All widgets must be a subclass of QWidget!")

            layout.activate()
            box.setLayout(layout)
            self._main_widget.addWidget(box)
            self._main_widget.addSeparator()
Exemple #2
0
class Top(Plugin):
    NODE_FIELDS = [
        'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads'
    ]
    OUT_FIELDS = [
        'node_name', 'pid', 'cpu_percent', 'memory_percent', 'num_threads'
    ]
    FORMAT_STRS = ['%s', '%s', '%0.2f', '%0.2f', '%s']
    NODE_LABELS = ['Node', 'PID', 'CPU %', 'Mem %', 'Num Threads']
    SORT_TYPE = [str, str, float, float, float]
    TOOLTIPS = {
        0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))),
        3: ('memory_info', lambda x:
            ('Resident: %0.2f MiB, Virtual: %0.2f MiB' %
             (x[0] / 2**20, x[1] / 2**20)))
    }

    _node_info = NodeInfo()

    name_filter = re.compile('')

    def __init__(self, context):
        super(Top, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('Top')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q",
                            "--quiet",
                            action="store_true",
                            dest="quiet",
                            help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        # if not args.quiet:
        #     print 'arguments: ', args
        #     print 'unknowns: ', unknowns

        self._selected_node = ''
        self._selected_node_lock = RLock()

        # Setup the toolbar
        self._toolbar = QToolBar()
        self._filter_box = QLineEdit()
        self._regex_box = QCheckBox()
        self._regex_box.setText('regex')
        self._toolbar.addWidget(QLabel('Filter'))
        self._toolbar.addWidget(self._filter_box)
        self._toolbar.addWidget(self._regex_box)

        self._filter_box.returnPressed.connect(self.update_filter)
        self._regex_box.stateChanged.connect(self.update_filter)

        # Create a container widget and give it a layout
        self._container = QWidget()
        self._container.setWindowTitle('Process Monitor')
        self._layout = QVBoxLayout()
        self._container.setLayout(self._layout)

        self._layout.addWidget(self._toolbar)

        # Create the table widget
        self._table_widget = QTreeWidget()
        self._table_widget.setObjectName('TopTable')
        self._table_widget.setColumnCount(len(self.NODE_LABELS))
        self._table_widget.setHeaderLabels(self.NODE_LABELS)
        self._table_widget.itemClicked.connect(self._tableItemClicked)
        self._table_widget.setSortingEnabled(True)
        self._table_widget.setAlternatingRowColors(True)

        self._layout.addWidget(self._table_widget)
        context.add_widget(self._container)

        # Add a button for killing nodes
        self._kill_button = QPushButton('Kill Node')
        self._layout.addWidget(self._kill_button)
        self._kill_button.clicked.connect(self._kill_node)

        # Update twice since the first cpu% lookup will always return 0
        self.update_table()
        self.update_table()

        self._table_widget.resizeColumnToContents(0)

        # Start a timer to trigger updates
        self._update_timer = QTimer()
        self._update_timer.setInterval(1000)
        self._update_timer.timeout.connect(self.update_table)
        self._update_timer.start()

    def _tableItemClicked(self, item, column):
        with self._selected_node_lock:
            self._selected_node = item.text(0)

    def update_filter(self, *args):
        if self._regex_box.isChecked():
            expr = self._filter_box.text()
        else:
            expr = re.escape(self._filter_box.text())
        self.name_filter = re.compile(expr)
        self.update_table()

    def _kill_node(self):
        self._node_info.kill_node(self._selected_node)

    def update_one_item(self, row, info):
        twi = TopWidgetItem()
        for col, field in enumerate(self.OUT_FIELDS):
            val = info[field]
            twi.setText(col, self.FORMAT_STRS[col] % val)
        self._table_widget.insertTopLevelItem(row, twi)

        for col, (key, func) in self.TOOLTIPS.items():
            twi.setToolTip(col, func(info[key]))

        with self._selected_node_lock:
            if twi.text(0) == self._selected_node:
                twi.setSelected(True)

        twi.setHidden(len(self.name_filter.findall(info['node_name'])) == 0)

    def update_table(self):
        self._table_widget.clear()
        infos = self._node_info.get_all_node_fields(self.NODE_FIELDS)
        for nx, info in enumerate(infos):
            self.update_one_item(nx, info)

    def shutdown_plugin(self):
        self._update_timer.stop()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('filter_text', self._filter_box.text())
        instance_settings.set_value('is_regex',
                                    int(self._regex_box.checkState()))

    def restore_settings(self, plugin_settings, instance_settings):
        self._filter_box.setText(instance_settings.value('filter_text'))
        is_regex_int = instance_settings.value('is_regex')
        if is_regex_int:
            self._regex_box.setCheckState(Qt.CheckState(is_regex_int))
        else:
            self._regex_box.setCheckState(Qt.CheckState(0))
        self.update_filter()
Exemple #3
0
class HandEyeCalibration(Plugin):
    PLUGIN_TITLE = ' Intel OTC Robotics: Hand-Eye Calibration'

    def __init__(self, context):
        super(HandEyeCalibration, self).__init__(context)
        self.context = context
        self.node = context.node
        self.widget = QWidget()
        self.widget.setObjectName(self.PLUGIN_TITLE)
        self.widget.setWindowTitle(self.PLUGIN_TITLE)

        # Data
        self.Tsamples = []

        # Toolbar
        _, path_pkg = get_resource('packages', 'handeye_dashboard')
        print("{}".format(path_pkg))

        self.snapshot_action = QAction(QIcon.fromTheme('camera-photo'),
                                       'Take a snapshot', self.widget)
        path = path_pkg + '/share/handeye_dashboard/images/capture.png'
        self.calibrate_action = QAction(QIcon(QPixmap.fromImage(QImage(path))),
                                        'Get the camera/robot transform',
                                        self.widget)
        self.clear_action = QAction(QIcon.fromTheme('edit-clear'),
                                    'Clear the record data.', self.widget)
        path = path_pkg + '/share/handeye_dashboard/images/UR5.png'
        self.execut_action = QAction(QIcon(QPixmap.fromImage(QImage(path))),
                                     'EStart the publishing the TF.',
                                     self.widget)
        self.toolbar = QToolBar()
        self.toolbar.addAction(self.snapshot_action)
        self.toolbar.addAction(self.calibrate_action)
        self.toolbar.addAction(self.clear_action)
        self.toolbar.addAction(self.execut_action)

        # Toolbar0
        self.l0 = QLabel(self.widget)
        self.l0.setText("Camera-Mount-Type: ")
        self.l0.setFixedWidth(150)
        self.l0.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.combobox = QComboBox(self.widget)
        self.combobox.addItem('attached on robot')
        self.combobox.addItem('fixed beside robot')
        self.toolbar0 = QToolBar()
        self.toolbar0.addWidget(self.l0)
        self.toolbar0.addWidget(self.combobox)

        # Toolbar1
        self.l1 = QLabel(self.widget)
        self.l1.setText("Camera-Frame: ")
        self.l1.setFixedWidth(150)
        self.l1.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.camera_frame = QLineEdit(self.widget)
        self.camera_frame.setText("camera_link")
        self.toolbar1 = QToolBar()
        self.toolbar1.addWidget(self.l1)
        self.toolbar1.addWidget(self.camera_frame)

        # Toolbar2
        self.l2 = QLabel(self.widget)
        self.l2.setText("Object-Frame: ")
        self.l2.setFixedWidth(150)
        self.l2.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.object_frame = QLineEdit(self.widget)
        self.object_frame.setText("calib_board")
        self.toolbar2 = QToolBar()
        self.toolbar2.addWidget(self.l2)
        self.toolbar2.addWidget(self.object_frame)

        # Toolbar3
        self.l3 = QLabel(self.widget)
        self.l3.setText("Robot-Base-Frame: ")
        self.l3.setFixedWidth(150)
        self.l3.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.base_frame = QLineEdit(self.widget)
        self.base_frame.setText("base")
        self.toolbar3 = QToolBar()
        self.toolbar3.addWidget(self.l3)
        self.toolbar3.addWidget(self.base_frame)

        # Toolbar4
        self.l4 = QLabel(self.widget)
        self.l4.setText("End-Effector-Frame: ")
        self.l4.setFixedWidth(150)
        self.l4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.endeffector_frame = QLineEdit(self.widget)
        self.endeffector_frame.setText("tool0")
        self.toolbar4 = QToolBar()
        self.toolbar4.addWidget(self.l4)
        self.toolbar4.addWidget(self.endeffector_frame)

        # Toolbar5
        self.l5 = QLabel(self.widget)
        self.l5.setText("Sample-Number: ")
        self.l5.setFixedWidth(150)
        self.l5.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.le5 = QLineEdit(self.widget)
        self.le5.setValidator(QIntValidator())
        self.le5.setText('10')
        self.le5.setReadOnly(True)
        self.toolbar5 = QToolBar()
        self.toolbar5.addWidget(self.l5)
        self.toolbar5.addWidget(self.le5)

        # TreeView
        self.treeview = QTreeView()
        self.treeview.setAlternatingRowColors(True)
        self.model = QStandardItemModel(self.treeview)
        self.treeview.setModel(self.model)
        self.treeview.setHeaderHidden(True)

        # TextEdit
        self.textedit = QTextEdit(self.widget)
        self.textedit.setReadOnly(True)

        # Layout
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.toolbar0)
        self.layout.addWidget(self.toolbar1)
        self.layout.addWidget(self.toolbar2)
        self.layout.addWidget(self.toolbar3)
        self.layout.addWidget(self.toolbar4)
        self.layout.addWidget(self.toolbar5)
        self.layout.addWidget(self.toolbar)
        self.layoutH = QHBoxLayout()
        self.layoutH.addWidget(self.treeview)
        self.layoutH.addWidget(self.textedit)
        self.layout.addLayout(self.layoutH)
        self.widget.setLayout(self.layout)
        # Add the widget to the user interface
        if context.serial_number() > 1:
            self.widget.setWindowTitle(self.widget.windowTitle() +
                                       (' (%d)' % context.serial_number()))
        context.add_widget(self.widget)
        # Make the connections
        self.snapshot_action.triggered.connect(self.take_snapshot)
        self.calibrate_action.triggered.connect(self.calibration)
        self.clear_action.triggered.connect(self.clear)
        self.execut_action.triggered.connect(self.execution)

        # Package path
        self.path_pkg = path_pkg

        # Set up TF
        self.cli = self.node.create_client(HandeyeTF, 'handeye_tf_service')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.node.get_logger().info(
                'service not available, waiting again...')
        self.req = HandeyeTF.Request()

    def clear(self):
        # >>> Clear the recorded samples
        self.textedit.append('Clearing the recorded data ...')
        self.textedit.clear()
        self.Tsamples = []
        self.model.clear()

    def get_tf_transform(self, frame_id, child_frame_id):
        self.req.transform.header.frame_id = frame_id
        self.req.transform.child_frame_id = child_frame_id
        self.req.publish.data = False

        future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self.node, future)

        transform = TransformStamped()

        try:
            result = future.result()
        except Exception as e:
            self.node.get_logger().info('Service call failed %r' % (e, ))
        else:
            transform = result.tf_lookup_result

        return transform

    def publish_tf_transform(self, transform_to_publish):
        self.req.publish.data = True
        self.req.transform = transform_to_publish

        future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self.node, future)

        try:
            future.result()
        except Exception as e:
            self.node.get_logger().info('Service call failed %r' % (e, ))
        else:
            self.node.get_logger().info(
                'Send the camera-robot transform :\n\tfrom `{}` to `{}`.'.
                format(self.req.transform.header.frame_id,
                       self.req.transform.child_frame_id))

    def take_snapshot(self):
        # >>> Take the snapshot
        self.textedit.append('Taking snapshot ...')

        # Get the transform from `tool0` to `base_link`
        T = self.get_tf_transform(self.base_frame.text(),
                                  self.endeffector_frame.text())
        bTe = np.zeros((4, 4))
        q = [
            T.transform.rotation.w, T.transform.rotation.x,
            T.transform.rotation.y, T.transform.rotation.z
        ]
        bTe = br.quaternion.to_transform(q)
        bTe[:3, 3] = np.array([
            T.transform.translation.x, T.transform.translation.y,
            T.transform.translation.z
        ])
        self.textedit.append('Lookup transform: from `{}` to `{}`.'.format(
            self.base_frame.text(), self.endeffector_frame.text()))
        self.node.get_logger().info(bcolors.OKGREEN + 'bTe:' + bcolors.ENDC +
                                    '\n{}'.format(bTe))

        # Get the transform from `calib_board` to `camera_link`
        T = self.get_tf_transform(self.camera_frame.text(),
                                  self.object_frame.text())
        cTo = np.zeros((4, 4))
        q = [
            T.transform.rotation.w, T.transform.rotation.x,
            T.transform.rotation.y, T.transform.rotation.z
        ]
        cTo = br.quaternion.to_transform(q)
        cTo[:3, 3] = np.array([
            T.transform.translation.x, T.transform.translation.y,
            T.transform.translation.z
        ])
        self.textedit.append('Lookup transform: from `{}` to `{}`.'.format(
            self.camera_frame.text(), self.object_frame.text()))
        self.node.get_logger().info(bcolors.OKGREEN + 'cTo:' + bcolors.ENDC +
                                    '\n{}'.format(cTo))

        parent = QStandardItem('Snapshot {}'.format(len(self.Tsamples)))
        child_1 = QStandardItem('bTe:\n{}\n{}\n{}\n{}'.format(
            bTe[0, :], bTe[1, :], bTe[2, :], bTe[3, :]))
        child_2 = QStandardItem('cTo:\n{}\n{}\n{}\n{}'.format(
            cTo[0, :], cTo[1, :], cTo[2, :], cTo[3, :]))
        parent.appendRow(child_1)
        parent.appendRow(child_2)
        self.model.appendRow(parent)
        self.Tsamples.append((bTe, cTo))
        self.le5.setText(str(len(self.Tsamples)))

    def calibration(self):
        # >>> Compute the calibration
        self.textedit.append('Making the calibration ...')
        if len(self.Tsamples) == 0:
            self.textedit.append(
                'No transform recorded, please take snapshots.')
            return
        # save samples to `dataset.json` file
        save_samples_to_file(self.Tsamples)
        import handeye
        if self.combobox.currentIndex() == 0:
            solver_cri = handeye.calibrator.HandEyeCalibrator(setup='Moving')
        if self.combobox.currentIndex() == 1:
            solver_cri = handeye.calibrator.HandEyeCalibrator(setup='Fixed')
        for sample in self.Tsamples:
            solver_cri.add_sample(sample[0], sample[1])
        try:
            bTc = solver_cri.solve(method=handeye.solver.Daniilidis1999)
            # save the calibration result to 'camera-robot.json' file
            file_output = '/tmp/' + 'camera-robot.json'
            with open(file_output, 'w') as f:
                json.dump(bTc.tolist(), f)
        except Exception:
            self.textedit.append("Failed to solve the hand-eye calibration.")

    def execution(self):
        # >>> Publish the camera-robot transform
        self.textedit.append('Publishing the camera TF ...')
        file_input = '/tmp/' + 'camera-robot.json'
        with open(file_input, 'r') as f:
            datastore = json.load(f)

        to_frame = self.camera_frame.text()
        if self.combobox.currentIndex() == 0:
            from_frame = self.endeffector_frame.text()
        if self.combobox.currentIndex() == 1:
            from_frame = self.base_frame.text()

        bTc = np.array(datastore)
        static_transformStamped = TransformStamped()
        static_transformStamped.header.stamp = ROSClock().now().to_msg()
        static_transformStamped.header.frame_id = from_frame
        static_transformStamped.child_frame_id = to_frame

        static_transformStamped.transform.translation.x = bTc[0, 3]
        static_transformStamped.transform.translation.y = bTc[1, 3]
        static_transformStamped.transform.translation.z = bTc[2, 3]

        q = br.transform.to_quaternion(bTc)
        static_transformStamped.transform.rotation.x = q[1]
        static_transformStamped.transform.rotation.y = q[2]
        static_transformStamped.transform.rotation.z = q[3]
        static_transformStamped.transform.rotation.w = q[0]

        self.publish_tf_transform(static_transformStamped)

        output_string = "camera-robot pose:\n"
        output_string += "  Translation: [{}, {}, {}]\n".format(
            bTc[0, 3], bTc[1, 3], bTc[2, 3])
        output_string += "  Rotation: in Quaternion [{}, {}, {}, {}]".format(
            q[0], q[1], q[2], q[3])
        file_path = '/tmp/' + 'camera-robot.txt'
        with open(file_path, 'w') as f:
            f.write(output_string)

    def shutdown_plugin(self):
        """
    Unregister subscribers when the plugin shutdown
    """
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # Nothing to be done here
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # Nothing to be done here
        pass
Exemple #4
0
class Dashboard(Plugin):
    """
    Base class from which dashboards should inherit.

    :param context: the plugin context
    :type context: qt_gui.plugin.Plugin
    """
    def __init__(self, context):
        super(Dashboard, self).__init__(context)
        self.context = context
        self.setup(context)

        if not hasattr(self, 'name'):
            self.name = 'Dashboard'
        if not hasattr(self, 'max_icon_size'):
            self.max_icon_size = QSize(50, 30)
        self._main_widget = QToolBar()
        self._main_widget.setIconSize(self.max_icon_size)
        self._main_widget.setObjectName(self.name)
        self._main_widget.setWindowTitle(self.name)
        if context.serial_number() > 1:
            self._main_widget.setWindowTitle(self._main_widget.windowTitle() + (' (%d)' % context.serial_number()))

        # Convert list of widgets into layout
        self.add_widgets()

        # Display the dashboard
        context.add_toolbar(self._main_widget)

    def setup(self, context):
        """
        Called during ``__init__`` Subclasses should do initialization here.

        NOTE when overriding this method you should provide a ``self.name`` to
        avoid naming conflicts.

        :param context: The plugin context
        :type context: qt_gui.plugin.Plugin
        """
        pass

    def shutdown_plugin(self):
        """
        Called when the toolbar is closed by Qt.
        """
        for widget in self._widgets:
            if hasattr(widget, 'shutdown_widget'):
                widget.shutdown_widget()
            if hasattr(widget, 'close'):
                widget.close()

        self.shutdown_dashboard()

    def shutdown_dashboard(self):
        """
        Called after shutdown plugin, subclasses should do cleanup here, not in shutdown_plugin
        """
        pass

    def get_widgets(self):
        """
        Most of the dashboard customization should be done here.
        If this function is not overriden the dashboard will display nothing.

        :returns: List of lists containing dashboard widgets, or list of lists
                  containing a string followed by a list of dashboard widgets.
        """
        return []

    def add_widgets(self):
        """
        Add groups of widgets to _main_widget. Supports group labels.

        This method can be reimplemented in order to customize appearances.
        """
        widgets = self.get_widgets()
        self._widgets = [] # stores widgets which may need to be shut down when done
        for group in widgets:
            # Check for group label
            if isinstance(group[0], str):
                grouplabel, v = group
                box = QGroupBox(grouplabel)
                box.setContentsMargins(0, 18, 0, 0) # LTRB
                # Apply the center-label directive only for single-icon groups
                if len(group[1]) == 1:
                    box.setAlignment(Qt.AlignHCenter)
            else:
                box = QGroupBox()
                box.setContentsMargins(0, 0, 0, 0) # LTRB
                v = group
            # Add widgets to QGroupBox
            layout = QHBoxLayout()
            layout.setSpacing(0)
            layout.setContentsMargins(0, 0, 0, 0) # LTRB
            for i in v:
                try:
                    try:
                        i.setIconSize(self.max_icon_size) # without this, icons are tiny
                    except AttributeError as e:
                        # triggers with battery which uses a QLabel instead of a QToolButton-based widget
                        pass
                    layout.addWidget(i)
                    self._widgets.append(i)
                except:
                    raise Exception("All widgets must be a subclass of QWidget!")

            layout.activate()
            box.setLayout(layout)
            self._main_widget.addWidget(box)
            self._main_widget.addSeparator()
class Top(Plugin):

    NODE_FIELDS   = [             'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads']
    OUT_FIELDS    = ['node_name', 'pid', 'cpu_percent',     'memory_percent',     'num_threads'    ]
    FORMAT_STRS   = ['%s',        '%s',  '%0.2f',           '%0.2f',              '%s'             ]
    NODE_LABELS   = ['Node',      'PID', 'CPU %',           'Mem %',              'Num Threads'    ]
    SORT_TYPE     = [str,         str,   float,             float,                float            ]
    TOOLTIPS      = {
        0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))),
        3: ('memory_info', lambda x: ('Resident: %0.2f MiB, Virtual: %0.2f MiB' % (x[0]/2**20, x[1]/2**20)))
    }

    _node_info = NodeInfo()

    name_filter = re.compile('')

    def __init__(self, context):
        super(Top, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('Top')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()
        # Add argument(s) to the parser.
        parser.add_argument("-q", "--quiet", action="store_true",
                      dest="quiet",
                      help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        # if not args.quiet:
        #     print 'arguments: ', args
        #     print 'unknowns: ', unknowns

        self._selected_node = ''
        self._selected_node_lock = RLock()

        # Setup the toolbar
        self._toolbar = QToolBar()
        self._filter_box = QLineEdit()
        self._regex_box  = QCheckBox()
        self._regex_box.setText('regex')
        self._toolbar.addWidget(QLabel('Filter'))
        self._toolbar.addWidget(self._filter_box)
        self._toolbar.addWidget(self._regex_box)

        self._filter_box.returnPressed.connect(self.update_filter)
        self._regex_box.stateChanged.connect(self.update_filter)

        # Create a container widget and give it a layout
        self._container = QWidget()
        self._container.setWindowTitle('Process Monitor')
        self._layout    = QVBoxLayout()
        self._container.setLayout(self._layout)

        self._layout.addWidget(self._toolbar)

        # Create the table widget
        self._table_widget = QTreeWidget()
        self._table_widget.setObjectName('TopTable')
        self._table_widget.setColumnCount(len(self.NODE_LABELS))
        self._table_widget.setHeaderLabels(self.NODE_LABELS)
        self._table_widget.itemClicked.connect(self._tableItemClicked)
        self._table_widget.setSortingEnabled(True)
        self._table_widget.setAlternatingRowColors(True)

        self._layout.addWidget(self._table_widget)
        context.add_widget(self._container)

        # Add a button for killing nodes
        self._kill_button = QPushButton('Kill Node')
        self._layout.addWidget(self._kill_button)
        self._kill_button.clicked.connect(self._kill_node)

        # Update twice since the first cpu% lookup will always return 0
        self.update_table()
        self.update_table()

        self._table_widget.resizeColumnToContents(0)

        # Start a timer to trigger updates
        self._update_timer = QTimer()
        self._update_timer.setInterval(1000)
        self._update_timer.timeout.connect(self.update_table)
        self._update_timer.start()

    def _tableItemClicked(self, item, column):
        with self._selected_node_lock:
            self._selected_node = item.text(0)

    def update_filter(self, *args):
        if self._regex_box.isChecked():
            expr = self._filter_box.text()
        else:
            expr = re.escape(self._filter_box.text())
        self.name_filter = re.compile(expr)
        self.update_table()

    def _kill_node(self):
        self._node_info.kill_node(self._selected_node)

    def update_one_item(self, row, info):
        twi = TopWidgetItem()
        for col, field in enumerate(self.OUT_FIELDS):
            val = info[field]
            twi.setText(col, self.FORMAT_STRS[col] % val)
        self._table_widget.insertTopLevelItem(row, twi)

        for col, (key, func) in self.TOOLTIPS.items():
            twi.setToolTip(col, func(info[key]))

        with self._selected_node_lock:
            if twi.text(0) == self._selected_node:
                twi.setSelected(True)

        twi.setHidden(len(self.name_filter.findall(info['node_name'])) == 0)

    def update_table(self):
        self._table_widget.clear()
        infos = self._node_info.get_all_node_fields(self.NODE_FIELDS)
        for nx, info in enumerate(infos):
            self.update_one_item(nx, info)

    def shutdown_plugin(self):
        self._update_timer.stop()

    def save_settings(self, plugin_settings, instance_settings):        
        instance_settings.set_value('filter_text', self._filter_box.text())
        instance_settings.set_value('is_regex', int(self._regex_box.checkState()))

    def restore_settings(self, plugin_settings, instance_settings):
        self._filter_box.setText(instance_settings.value('filter_text'))
        is_regex_int = instance_settings.value('is_regex')
        if is_regex_int:
            self._regex_box.setCheckState(Qt.CheckState(is_regex_int))
        else:
            self._regex_box.setCheckState(Qt.CheckState(0))
        self.update_filter()