class RosBagWidget(QtGui.QWidget): ## # @brief # # @return def __init__(self): QtGui.QWidget.__init__(self) self.file_name = '' self.bag = None self.data = {} self.plot_data = {} self.gridLayout = None self.label1 = None self.loadBtn = None self.tree = None self.addBtn = None self.editBtn = None self.removeBtn = None self.plotBtn = None self.plotTree = None self.plotData = None self.plotwin = None self.plot_num = 1 self.params = [{ 'type': 'group', 'name': u'plot params', 'children': [{ 'type': 'str', 'name': 'x axis name', 'value': u'time(s)' }, { 'type': 'str', 'name': 'y axis name', 'value': u'position(m)' }, { 'type': 'str', 'name': 'plot name', 'value': u'plot name' }] }] self.line_mark_list = [ '+-', '*-', 'o-', '^-', 's-', 'x-', '.-', 'p-', 'h-', 'd-' ] self.msg_types = [] self.topics = {} self.topics_data = {} self.msg_types_blacklist = [ 'sensor_msgs/PointCloud', 'sensor_msgs/Image' ] self.setup_ui(self) ## # @brief # # @param Form # # @return def setup_ui(self, Form): self.setObjectName("Form") self.resize(217, 499) self.gridLayout = QtGui.QGridLayout(self) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setVerticalSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label1 = QtGui.QLabel("") self.label1.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) self.label1.setStyleSheet("border:1px solid black;") self.gridLayout.addWidget(self.label1, 0, 0) self.loadBtn = QtGui.QPushButton("...") self.loadBtn.setFixedWidth(20) self.gridLayout.addWidget(self.loadBtn, 0, 1) self.loadBtn.clicked.connect(self.load_file) self.tree = DataTreeWidget(data=self.data) self.gridLayout.addWidget(self.tree, 1, 0, 1, 2) self.addBtn = QtGui.QPushButton("") add_icon = QtGui.QIcon() add_icon.addPixmap(QtGui.QPixmap("resources/add.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.addBtn.setIcon(add_icon) self.addBtn.clicked.connect(self.add_plot) self.gridLayout.addWidget(self.addBtn, 0, 2) self.editBtn = QtGui.QPushButton("") edit_icon = QtGui.QIcon() edit_icon.addPixmap(QtGui.QPixmap("resources/edit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.editBtn.setIcon(edit_icon) self.editBtn.clicked.connect(self.edit_plot) self.gridLayout.addWidget(self.editBtn, 0, 3) self.removeBtn = QtGui.QPushButton("") remove_icon = QtGui.QIcon() remove_icon.addPixmap(QtGui.QPixmap("resources/remove.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.removeBtn.setIcon(remove_icon) self.removeBtn.clicked.connect(self.remove_plot) self.gridLayout.addWidget(self.removeBtn, 0, 4) self.plotBtn = QtGui.QPushButton("plot") # plot_icon = QtGui.QIcon() # plot_icon.addPixmap(QtGui.QPixmap("resources/plot.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) # self.plotBtn.setIcon(plot_icon) self.plotBtn.clicked.connect(self.plot) self.gridLayout.addWidget(self.plotBtn, 0, 5) self.plotTree = ParameterTree() self.plotData = Parameter.create(name='params', type='group', children=self.params) self.plotTree.setParameters(self.plotData, showTop=False) self.gridLayout.addWidget(self.plotTree, 1, 2, 1, 4) # self.tree2.setSizePolicity(self.tree1.width()/2, 499) # self.tree1.resize(setWidth(150) # self.tree2.setWidth(50) self.plotData.sigTreeStateChanged.connect(self.change) ## # @brief # # @param param # @param changes # # @return def change(self, param, changes): # todo change plot params print("tree changes:(to do)") for param, change, data in changes: path = self.plotData.childPath(param) print(path) if path is not None: childName = '.'.join(path) else: childName = param.name() print(' parameter: %s' % childName) print(' change: %s' % change) print(' data: %s' % str(data)) print(' ----------') ## # @brief # # @param file_name # @param start_dir # # @return def load_file(self, file_name=None, start_dir=None): """Load a rosbag (*.bag) file. """ start = time.clock() if not file_name: if not start_dir: start_dir = '.' self.file_dialog = FileDialog(None, "Load rosbag ..", start_dir, "rosbag (*.bag)") # file_dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.file_dialog.show() self.file_dialog.fileSelected.connect(self.load_file) return file_name = unicode(file_name) self.file_name = file_name self.label1.setText(str(file_name)) self.file_info(file_name) end = time.clock() print("load file cost %s s" % (end - start)) ## # @brief # # @param file_name # # @return def file_info(self, file_name=None): if not file_name: self.data = {} else: self.bag = rosbag.Bag(file_name) self.tree.setData(self.bag.get_type_and_topic_info()[1]) # print(self.bag.get_type_and_topic_info()[1]) bag_length = self.get_bag_length( self.bag.get_type_and_topic_info()[1]) * 10 if len(file_name) * 8 > bag_length: bag_length = len(file_name) * 8 self.label1.setFixedWidth(bag_length) # clear data self.msg_types = [] self.topics = {} self.topics_data = {} # print(self.get_bag_length(self.bag.get_type_and_topic_info()[1])) for m in self.bag.get_type_and_topic_info()[0]: self.msg_types.append(m) for t in self.bag.get_type_and_topic_info()[1]: if self.bag.get_type_and_topic_info( )[1][t][0] not in self.msg_types_blacklist: get_topic_params_cmd = "rostopic echo -b " + file_name + " -n 1 -p " + t topic_parms = os.popen(get_topic_params_cmd).read() topic_parms_list = self.topic_str2list(topic_parms) self.topics[t] = topic_parms_list get_topic_data_cmd = "rostopic echo -b " + self.file_name + " -p " + t topic_data = os.popen(get_topic_data_cmd).read() topic_data_list = self.topic_data_str2list(topic_data[:-1]) self.topics_data[t] = topic_data_list else: print(t + "is not proper for plot") # print("topic_data_list", topic_data_list) # break @staticmethod ## # @brief # # @param data # # @return def get_bag_length(data): max_topic_len = 0 max_topic_type_len = 0 for topic in data: if len(topic) > max_topic_len: max_topic_len = len(topic) if len(data[topic][0]) > max_topic_type_len: max_topic_type_len = len(data[topic][0]) # print(max_topic_len, max_topic_type_len) return max_topic_len + max_topic_type_len @staticmethod ## # @brief # # @param topic_str # # @return def topic_str2list(topic_str): # print(len(topic_str)) # print(topic_str) topic_list = [] if len(topic_str) > 0: # print(topic_str[0]) out1 = re.split('\n', topic_str) params = re.split(',', out1[0]) values = re.split(',', out1[1]) # print(len(params), len(values)) for i in range(len(params)): if values[i].strip('-').replace(".", '').isdigit(): topic_list.append(params[i]) # print(topic_list) return topic_list @staticmethod ## # @brief # # @param topic_data_str # # @return def topic_data_str2list(topic_data_str): topic_data_list = [] if len(topic_data_str) > 0: out1 = re.split('\n', topic_data_str) for o in out1: data_list = re.split(',', o) topic_data_list.append(data_list) return topic_data_list ## # @brief # # @return def add_plot(self): if len(self.file_name): #print("add plot") self.plotwin = PlotWin(self.topics, self.plot_num) self.plotwin.setWindowModality(QtCore.Qt.ApplicationModal) self.plotwin.setGeometry(500, 500, 600, 150) if self.plotwin.exec_(): # accepted #print("ok") self.plot_num = self.plot_num + 1 if len(self.plotwin.plot_name.text()) > 0: self.plotwin.param['name'] = unicode( self.plotwin.plot_name.text()) + " " + unicode( self.plotwin.topic_name.currentText()) self.plotwin.param['type'] = 'group' x = { 'name': 'x', 'type': 'str', 'value': unicode(self.plotwin.field1.currentText()) } y = { 'name': 'y', 'type': 'str', 'value': unicode(self.plotwin.field2.currentText()) } color = self.plotwin.colorBtn.color(mode='byte') color_str = '' for i in color[:-1]: c = str(hex(i)).replace("0x", '') if len(c) < 2: c = '0' + c color_str = color_str + c # print(color, color_str) line_color = { 'name': 'line_color', 'type': 'color', 'value': color_str, 'tip': "This is a color button" } line_mark = { 'name': 'line_mark', 'type': 'list', 'values': self.line_mark_list, 'value': unicode(self.plotwin.line_mark.currentText()) } self.plotwin.param['children'] = [ x, y, line_color, line_mark ] self.params.append(self.plotwin.param) self.plotData = Parameter.create( name='params', type='group', children=[self.plotwin.param]) self.plotTree.addParameters(self.plotData, showTop=False) else: print("Please input the plot name first.") else: print("cancel") else: print("Please add bag file first!") ## # @brief # # @return def edit_plot(self): # print("edit plot", self.plotTree.selectedItems(), self.plotTree.currentItem()) if hasattr(self.plotTree.currentItem(), "param"): print("edit plot " + re.split( '\'', str(getattr(self.plotTree.currentItem(), "param")))[1] + "(to do)") # if hasattr(getattr(self.plotTree.currentItem(), "param"), "name"): # print(getattr(getattr(self.plotTree.currentItem(), "param"), "name")) # print("%x" % id(self.params[0])) ## # @brief # # @return def remove_plot(self): if hasattr(self.plotTree.currentItem(), "param"): print("remove plot " + re.split( '\'', str(getattr(self.plotTree.currentItem(), "param")))[1] + "(to do)") ## # @brief # # @return def plot(self): print("plot") plots = [] for p in self.params[1:]: topic_name = re.split(' ', p['name']) # print(topic_name[1]) x_name = None y_name = None line_color = None line_mark = None for c in p['children']: if c['name'] == 'x': x_name = c['value'] elif c['name'] == 'y': y_name = c['value'] elif c['name'] == 'line_color': line_color = c['value'] elif c['name'] == 'line_mark': line_mark = c['value'] plots.append([ topic_name[0], topic_name[1], x_name, y_name, line_color, line_mark ]) # print(plots) # plt.figure(figsize=(12, 10)) for p in plots: x, y = self.get_bag_data(p) plt.plot(x, y, p[5], color=("#" + p[4]), label=p[0]) plt.xlabel(self.params[0]['children'][0]['value']) plt.ylabel(self.params[0]['children'][1]['value']) plt.title(self.params[0]['children'][2]['value']) # plt.ylim(-1.2, 1.2) plt.legend(loc='upper right') plt.grid(True) # plt.savefig(gflags.FLAGS.title + '.svg', format= 'svg') plt.show() ## # @brief # # @param plot_info # # @return def get_bag_data(self, plot_info): x = [] y = [] plot_data = self.topics_data[plot_info[1]] x_index = plot_data[0].index(plot_info[2]) y_index = plot_data[0].index(plot_info[3]) # print(plot_data, x_index, y_index) for pd in plot_data[1:]: # print('len(pd)', len(pd)) # print('x_index', x_index) # print('y_index', y_index) x.append(pd[x_index]) y.append(pd[y_index]) # break return x, y
class FunctionWidget(fw.FeatureWidget): """ Subclass of FeatureWidget that defines attributes to show parameters to a given function and run the function with the given parameters. These should be used with the corresponding FunctionManager to run Tomography pipeline workflows Attributes ---------- func_name : str Function name subfunc_name : str Specific function name input_functions : dict dictionary with keys being parameters of this function to be overriden, and values being a FunctionWidget whose function will override said parameter param_dict : dict Dictionary with parameter names and values _function : function Function object corresponding to the function represented by widget package : str name of package to which function belongs params : pyqtgraph.Parameter Parameter instance with function parameter exposed in UI missing_args : list of str Names of missing arguments not contained in param_dict Signals ------- sigTestRange(QtGui.QWidget, str, tuple, dict) Emitted when parameter range test is requested. Emits the sending widget, a string with a message to log, and a tuple with the range values for the parameter Parameters ---------- name : str generic name of function subname : str specific name of function under the generic name category package : python package package input_functions : dict, optional dictionary with keys being parameters of this function to be overriden, and values being a FunctionWidget whose function will override said parameter checkable : bool, optional bool to set the function to be toggled on and of when running constructed workflows closeable : bool, optional bool to set if the function can be deleted from the pipeline editor parent : QWidget parent of this FunctionWidget """ sigTestRange = QtCore.Signal(QtGui.QWidget, str, tuple) # TODO perhaps its better to not pass in the package object but only a string, package object can be retrived from reconpkgs.packages dict def __init__(self, name, subname, package, input_functions=None, checkable=True, closeable=True, parent=None): self.name = name if name != subname: self.name += ' (' + subname + ')' super(FunctionWidget, self).__init__(self.name, checkable=checkable, closeable=closeable, parent=parent) self.func_name = name self.subfunc_name = subname self.input_functions = {} self.param_dict = {} self._function = getattr(package, config.names[self.subfunc_name][0]) #perhaps unnecessary self.package = package.__name__ # TODO have the children kwarg be passed to __init__ self.params = Parameter.create( name=self.name, children=config.parameters[self.subfunc_name], type='group', readonly=False) self.form = ParameterTree(showHeader=False) self.form.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.form.customContextMenuRequested.connect(self.paramMenuRequested) self.form.setParameters(self.params, showTop=True) # Initialize parameter dictionary with keys and default values self.updateParamsDict() argspec = inspect.getargspec(self._function) default_argnum = len(argspec[3]) self.param_dict.update({ key: val for (key, val) in zip(argspec[0][-default_argnum:], argspec[3]) }) for key, val in self.param_dict.iteritems(): if key in [p.name() for p in self.params.children()]: self.params.param(key).setValue(val) self.params.param(key).setDefault(val) # Create a list of argument names (this will most generally be the data passed to the function) self.missing_args = [ i for i in argspec[0] if i not in self.param_dict.keys() ] self.parammenu = QtGui.QMenu() action = QtGui.QAction('Test Parameter Range', self) action.triggered.connect(self.testParamTriggered) self.parammenu.addAction(action) self.previewButton.customContextMenuRequested.connect( self.menuRequested) self.menu = QtGui.QMenu() if input_functions is not None: for param, ipf in input_functions.iteritems(): self.addInputFunction(param, ipf) # wire up param changed signals for param in self.params.children(): param.sigValueChanged.connect(self.paramChanged) # change on/off icons icon = QtGui.QIcon() if checkable: icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_51.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_45.png"), QtGui.QIcon.Normal, QtGui.QIcon.On) self.previewButton.setCheckable(True) else: icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_45.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.previewButton.setCheckable(False) self.previewButton.setChecked(True) self.previewButton.setIcon(icon) self.previewButton.setFlat(True) self.previewButton.setChecked(True) self.previewButton.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.allowed_types = { 'str': str, 'int': int, 'float': float, 'bool': bool, 'unicode': unicode } # set widgets to never hide their subfunctions self.expand() def collapse(self): """ This catches all "collapse" requests and passes them, so that FunctionWidgets are not collapsable """ pass @property def enabled(self): """ Boolean showing if the function widget is enabled (eye open/closed) """ if self.previewButton.isChecked( ) or not self.previewButton.isCheckable(): return True return False @enabled.setter def enabled(self, val): """ Set enabled value by toggling the previewButton only if the widget is checkable """ if val and self.previewButton.isCheckable(): self.previewButton.setChecked(True) else: self.previewButton.setChecked(False) @property def exposed_param_dict(self): """ Parameter dictionary with only the parameters that are shown in GUI """ param_dict = { key: val for (key, val) in self.updated_param_dict.iteritems() if key in [param.name() for param in self.params.children()] } return param_dict @property def partial(self): """ Package up all parameters into a functools.partial """ return partial(self._function, **self.updated_param_dict) @property def updated_param_dict(self): """ Return the param dict of the FunctionWidget updated with proper types """ if hasattr(self, 'defaults'): param_dict = {} for key, val in self.param_dict.iteritems(): if type(val) is str and 'None' in val: pass elif key in self.defaults.iterkeys(): arg_type = self.defaults[key]['type'] try: param_dict[key] = arg_type(val) except ValueError: param_dict[key] = None else: param_dict[key] = val return param_dict else: return self.param_dict @property def func_signature(self): """ String for function signature. Hopefully this can eventually be used to save workflows as scripts :) """ signature = str(self._function.__name__) + '(' for arg in self.missing_args: signature += '{},'.format(arg) for param, value in self.param_dict.iteritems(): signature += '{0}={1},'.format(param, value) if not isinstance(value, str) else \ '{0}=\'{1}\','.format(param, value) return signature[:-1] + ')' def updateParamsDict(self): """ Update the values of the parameter dictionary with the current values in UI """ self.param_dict.update( {param.name(): param.value() for param in self.params.children()}) for p, ipf in self.input_functions.iteritems(): ipf.updateParamsDict() def addInputFunction(self, parameter, functionwidget): """ Add an input function widget Parameters ---------- parameter : str Parameter name that will be overriden by return value of the input function functionwidget : FunctionWidget FunctionWidget representing the input function """ if parameter in self.input_functions: # Check to see if parameter already has input function if functionwidget.subfunc_name == self.input_functions[ parameter].subfunc_name: raise AttributeError( 'Input function already exists' ) # skip if the input function already exists self.removeInputFunction( parameter) # Remove it if it will be replaced self.input_functions[parameter] = functionwidget self.addSubFeature(functionwidget) functionwidget.sigDelete.connect( lambda: self.removeInputFunction(parameter)) def removeInputFunction(self, parameter): """ Remove the input function for the given parameter Parameters ---------- parameter : str Parameter name that will be overriden by return value of the input function """ function = self.input_functions.pop(parameter) self.removeSubFeature(function) def paramChanged(self, param): """ Slot connected to a pg.Parameter.sigChanged signal """ if hasattr(self, 'defaults'): try: arg_type = self.defaults[param.name()]['type'] try: self.allowed_types[arg_type](param.value()) self.param_dict.update({param.name(): param.value()}) except ValueError: if param.value() == "None": self.param_dict.update({param.name(): param.value()}) else: param.setValue(self.param_dict[param.name()]) except KeyError: self.param_dict.update({param.name(): param.value()}) else: self.param_dict.update({param.name(): param.value()}) def allReadOnly(self, boolean): """ Make all parameter read only """ for param in self.params.children(): param.setReadonly(boolean) def menuRequested(self): """ Context menu for functionWidget. Default is not menu. """ pass def paramMenuRequested(self, pos): """ Menus when a parameter in the form is right clicked """ if self.form.currentItem().parent(): self.parammenu.exec_(self.form.mapToGlobal(pos)) def testParamTriggered(self): """ Slot when a parameter range is clicked. Will emit the parameter name and the chosen range """ param = self.form.currentItem().param if param.type() == 'int' or param.type() == 'float': start, end, step = None, None, None if 'limits' in param.opts: start, end = param.opts['limits'] step = (end - start) / 3 + 1 elif param.value() is not None: start, end, step = param.value() / 2, 4 * ( param.value()) / 2, param.value() / 2 test = TestRangeDialog(param.type(), (start, end, step)) elif param.type() == 'list': test = TestListRangeDialog(param.opts['values']) else: return if test.exec_(): self.sigTestRange.emit(self, param.name(), test.selectedRange())