class BaseAnnotationItem(ResizableGraphicsItem): handler_cache = {} styles = ['font-family', 'font-size', 'text-bold', 'text-italic', 'text-underline', 'text-color', 'color-border', 'color-background'] minSize = ANNOTATION_MINIMUM_QSIZE def __init__(self, position=None, *args, **kwargs): super(BaseAnnotationItem, self).__init__(*args, **kwargs) # Config for each annotation item, holding the settings (styles, etc) # update-control via the toolbar using add_handler linking self.config = ConfigManager() self.config.updated.connect(self.applyStyleConfig) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsFocusable) if position: self.setPos(position) self.setZValue(-1) def delete(self): self.prepareGeometryChange() self.scene().annotations.remove(self) self.scene().removeItem(self) self.removeHandlers() def keyPressEvent(self, e): if e.key() == Qt.Key_Backspace and e.modifiers() == Qt.ControlModifier: self.delete() else: return super(BaseAnnotationItem, self).keyPressEvent(e) def importStyleConfig(self, config): for k in self.styles: self.config.set(k, config.get(k)) def addHandlers(self): m = self.scene().views()[0].m # Hack; need to switch to importing this for k in self.styles: self.config.add_handler(k, m.styletoolbarwidgets[k]) def removeHandlers(self): for k in self.styles: self.config.remove_handler(k) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if not value: self.removeHandlers() elif change == QGraphicsItem.ItemSelectedHasChanged: if value: self.addHandlers() return super(BaseAnnotationItem, self).itemChange(change, value)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle('PyQtConfig Demo') self.config = ConfigManager() CHOICE_A = 1 CHOICE_B = 2 CHOICE_C = 3 CHOICE_D = 4 map_dict = { 'Choice A': CHOICE_A, 'Choice B': CHOICE_B, 'Choice C': CHOICE_C, 'Choice D': CHOICE_D, } self.config.set_defaults({ 'number': 13, 'text': 'hello', 'active': True, 'combo': CHOICE_C, }) gd = QGridLayout() sb = QSpinBox() gd.addWidget(sb, 0, 1) self.config.add_handler('number', sb) te = QLineEdit() gd.addWidget(te, 1, 1) self.config.add_handler('text', te) cb = QCheckBox() gd.addWidget(cb, 2, 1) self.config.add_handler('active', cb) cmb = QComboBox() cmb.addItems(map_dict.keys()) gd.addWidget(cmb, 3, 1) self.config.add_handler('combo', cmb, mapper=map_dict) self.current_config_output = QTextEdit() gd.addWidget(self.current_config_output, 0, 3, 3, 1) self.config.updated.connect(self.show_config) self.show_config() self.window = QWidget() self.window.setLayout(gd) self.setCentralWidget(self.window) def show_config(self): self.current_config_output.setText(str(self.config.as_dict()))
class LisaConfigWindow(QDialog): # class LisaConfigWindow(QMainWindow): def __init__(self, config_in, ncols=2): super(LisaConfigWindow, self).__init__() self.setWindowTitle('Lisa Config') self.config = ConfigManager() self.ncols = ncols # used with other module. If it is set, lisa config is deleted self.reset_config = False CHOICE_A = "{pairwise_alpha_per_mm2: 45, return_only_object_with_seeds: true}" # CHOICE_A = 23 CHOICE_B = 2 CHOICE_C = 3 CHOICE_D = 4 map_dict = { 'Graph-Cut': CHOICE_A, 'Choice B': CHOICE_B, 'Choice C': CHOICE_C, 'Choice D': CHOICE_D, } config_def = { 'working_voxelsize_mm': 2.0, 'save_filetype': 'pklz', # 'manualroi': True, 'segparams': CHOICE_A, } config_def.update(config_in) self.config.set_defaults(config_def) gd = QGridLayout() gd_max_i = 0 for key, value in config_in.iteritems(): if type(value) is int: sb = QSpinBox() sb.setRange(-100000, 100000) elif type(value) is float: sb = QDoubleSpinBox() elif type(value) is str: sb = QLineEdit() elif type(value) is bool: sb = QCheckBox() else: logger.error("Unexpected type in config dictionary") row = gd_max_i / self.ncols col = (gd_max_i % self.ncols) * 2 gd.addWidget(QLabel(key),row, col +1) gd.addWidget(sb, row, col + 2) self.config.add_handler(key, sb) gd_max_i += 1 # gd.addWidget(QLabel("save filetype"), 1, 1) # te = QLineEdit() # gd.addWidget(te, 1, 2) # self.config.add_handler('save_filetype', te) # # gd.addWidget(QLabel("segmentation parameters"), 2, 1) # te = QLineEdit() # gd.addWidget(te, 2, 2) # self.config.add_handler('segparams', te) # cb = QCheckBox() # gd.addWidget(cb, 2, 2) # self.config.add_handler('active', cb) # gd.addWidget(QLabel("segmentation parameters"), 3, 1) # cmb = QComboBox() # cmb.addItems(map_dict.keys()) # gd.addWidget(cmb, 3, 2) # self.config.add_handler('segparams', cmb, mapper=map_dict) self.current_config_output = QTextEdit() # rid.setColumnMinimumWidth(3, logo.width()/2) text_col = (self.ncols * 2) + 3 gd.setColumnMinimumWidth(text_col, 500) gd.addWidget(self.current_config_output, 1, text_col, (gd_max_i/2)-1, 1) btn_reset_config = QPushButton("Reset and quit", self) btn_reset_config.clicked.connect(self.btnResetConfig) gd.addWidget(btn_reset_config, 0, text_col) btn_save_config = QPushButton("Save and quit", self) btn_save_config.clicked.connect(self.btnSaveConfig) gd.addWidget(btn_save_config, (gd_max_i / 2), text_col) self.config.updated.connect(self.show_config) self.show_config() # my line self.setLayout(gd) # self.window = QWidget() # self.window.setLayout(gd) # self.setCentralWidget(self.window) def add_grid_line(self, key, value): pass def get_config_as_dict(self): dictionary = self.config.as_dict() for key, value in dictionary.iteritems(): from PyQt4.QtCore import pyqtRemoveInputHook pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() # noqa BREAKPOINT if type(value) == PyQt4.QtCore.QString: value = str(value) dictionary[key] = value return dictionary def btnResetConfig(self, event=None): self.reset_config = True self.close() def btnSaveConfig(self, event=None): self.reset_config = False self.close() def show_config(self): text = str(self.get_config_as_dict()) self.current_config_output.setText(text)
class dialogPluginManagement(ui.GenericDialog): def populate_plugin_list(self): disabled_plugins = self.config.get('Plugins/Disabled') while self.plugins_lw.count() > 0: # Empty list self.plugins_lw.takeItem(0) for id, plugin in plugin_metadata.items(): item = QListWidgetItem() item.plugin_metadata = plugin item.plugin_shortname = id if 'image' in plugin: item.setData(Qt.DecorationRole, plugin['image']) item.setData(Qt.DisplayRole, "%s (v%s)" % (plugin['name'], plugin['version'])) item.setData(Qt.UserRole, plugin['description']) item.setData(Qt.UserRole + 1, plugin['author']) if plugin['path'] not in disabled_plugins: item.setData(Qt.UserRole + 2, "Active") self.plugins_lw.addItem(item) def onActivate(self): items = self.plugins_lw.selectedItems() disabled_plugins = self.config.get('Plugins/Disabled') for i in items: plugin_path = plugin_metadata[i.plugin_shortname]['path'] if plugin_path in disabled_plugins: disabled_plugins.remove(plugin_path) self.config.set('Plugins/Disabled', disabled_plugins) self.populate_plugin_list() def onDeactivate(self): items = self.plugins_lw.selectedItems() disabled_plugins = self.config.get('Plugins/Disabled') for i in items: plugin_path = plugin_metadata[i.plugin_shortname]['path'] if plugin_path not in disabled_plugins: disabled_plugins.append(plugin_path) self.config.set('Plugins/Disabled', disabled_plugins) self.populate_plugin_list() def onRefresh(self): get_available_plugins(self.config.get('Plugins/Paths')[:], include_deactivated=True) self.populate_plugin_list() def __init__(self, parent, **kwargs): super(dialogPluginManagement, self).__init__(parent, **kwargs) self.setWindowTitle(tr("Manage Plugins")) self.config = ConfigManager() self.config.defaults = { 'Plugins/Paths': settings.get('Plugins/Paths'), 'Plugins/Disabled': settings.get('Plugins/Disabled'), } self.m = parent self.setFixedSize(self.sizeHint()) self.plugins_lw = QListWidget() self.plugins_lw.setSelectionMode(QAbstractItemView.ExtendedSelection) self.plugins_lw.setItemDelegate(pluginListDelegate(self.plugins_lw)) self.plugins_lw.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) page = QWidget() box = QGridLayout() paths = QHBoxLayout() self.pathlist = QLineEdit() self.config.add_handler('Plugins/Paths', self.pathlist, (lambda x: x.split(';'), lambda x: ';'.join(x))) paths.addWidget(QLabel('Search Paths:')) paths.addWidget(self.pathlist) box.addLayout(paths, 0, 0) box.addWidget(self.plugins_lw, 1, 0) buttons = QVBoxLayout() refresh_btn = QPushButton('Refresh') refresh_btn.clicked.connect(self.onRefresh) buttons.addWidget(refresh_btn) activate_btn = QPushButton('Activate') activate_btn.clicked.connect(self.onActivate) buttons.addWidget(activate_btn) deactivate_btn = QPushButton('Dectivate') deactivate_btn.clicked.connect(self.onDeactivate) buttons.addWidget(deactivate_btn) buttons.addStretch() box.addLayout(buttons, 1, 1) page.setLayout(box) self.layout.addWidget(page) # Stack it all up, with extra buttons self.dialogFinalise() # Refresh the list of available plugins self._init_timer = QTimer.singleShot(0, self.onRefresh) def sizeHint(self): return QSize(600, 300)
class DictWidget(QtGui.QWidget): def __init__(self, config_in, ncols=2, captions=None, hide_keys=None, horizontal=False, show_captions=True, accept_button=False, config_manager=None, radiobuttons=None, dropdownboxes=None): """ :param config_in: dictionary :param ncols: :param captions: :param radiobuttons: {"name_of_radiobutton": [ ["orig_caption_1", orig_caption_2], default_value] ] } """ super(DictWidget, self).__init__() if captions is None: captions = {} if hide_keys is None: hide_keys = [] self.config_in = config_in self.ncols = ncols self.captions = captions self.accept_button = accept_button self.hide_keys = copy.copy(hide_keys) self.horizontal = horizontal self.show_captions = show_captions if radiobuttons is None: radiobuttons = {} self.radiobuttons = radiobuttons if dropdownboxes is None: dropdownboxes = {} self.dropdownboxes = dropdownboxes # hide also temp keys for lists and ndarrays # due to load default params self._get_tmp_composed_keys(config_in) self.hide_keys.extend(self._tmp_composed_keys_list) if config_manager is None: self.config = ConfigManager() self.config.set_defaults(config_in) else: self.config = config_manager self.mainLayout = QGridLayout(self) self.setLayout(self.mainLayout) self.widgets = {} self.grid_i = 0 self.init_ui() def _get_tmp_composed_keys(self, cfg): """ Get list of temporary keys for lists and ndarrays. This keys are used for reconstruction. vytvoří to seznam pomocných klíčů pro seznamy a ndarray :param cfg: :return: """ self._tmp_composed_keys_dict = {} self._tmp_composed_keys_list = [] toappend = {} for key, value in cfg.iteritems(): if key in self.dropdownboxes.keys(): continue if key in self.radiobuttons.keys(): continue if type(value) in (list, np.ndarray): self._tmp_composed_keys_dict[key] = [] array = np.asarray(value) key_array_i = 0 for val in array.tolist(): # key_i = (key, key_array_i) key_i = ComposedDictMetadata((key, key_array_i)) self._tmp_composed_keys_dict[key].append(key_i) self._tmp_composed_keys_list.append(key_i) key_array_i += 1 toappend[key_i] = val cfg.update(toappend) def _create_dropdownbox(self, key, value): # atomic_widget = QWidget() row, col = self.__calculate_new_grid_position() # layout = QBoxLayout(self.horizontal) atomic_widget = QComboBox() # atomic_widget.addItem("C") # atomic_widget.addItem("C++") values = self.dropdownboxes[key] # values = self.dropdownboxes[key][0] if value is not None and value in values: # vali = atomic_widget.findText(value) atomic_widget.findText(value) atomic_widget.addItems(values) # this does not work. I used findText() # atomic_widget.setCurrentIndex(vali) # layout.addWidget(cb) # atomic_widget.setLayout(layout) return atomic_widget def _create_radiobutton(self, key, value): atomic_widget = QWidget() layout = QBoxLayout(self.horizontal) for i, rbkey in enumerate(self.radiobuttons[key][0]): b1 = QRadioButton("Button1") if i == self.radiobuttons[key][1]: b1.setChecked(True) # b1.toggled.connect(lambda:self.btnstate(self.b1)) layout.addWidget(b1) atomic_widget.setLayout(layout) return atomic_widget def init_ui(self): # self.widgets = {} # self.grid_i = 0 grid = self.mainLayout for key, value in self.config_in.iteritems(): if key in self.hide_keys: continue if key in self.captions.keys(): caption = self.captions[key] else: caption = key atomic_widget = self.__get_widget_for_primitive_types(key, value) if atomic_widget is None: if type(value) in (list, np.ndarray): array = np.asarray(value) atomic_widget = self._create_sub_grid_from_ndarray(key, array) row, col = self.__calculate_new_grid_position() grid.addWidget(QLabel(caption), row, col + 1) grid.addLayout(atomic_widget, row, col + 2) continue else: logger.error("Unexpected type in config dictionary") continue # import ipdb; ipdb.set_trace() row, col = self.__calculate_new_grid_position() grid.addWidget(QLabel(caption), row, col + 1) grid.addWidget(atomic_widget, row, col + 2) # gd.setColumnMinimumWidth(text_col, 500) if self.accept_button: btn_accept = QPushButton("Accept", self) btn_accept.clicked.connect(self.btn_accept) text_col = (self.ncols * 2) + 3 grid.addWidget(btn_accept, (self.grid_i / 2), text_col) self.config.updated.connect(self.on_config_update) # def __add_line def __get_widget_for_primitive_types(self, key, value): """ return right widget and connect the value with config_manager :param key: :param value: :return: """ if key in self.dropdownboxes.keys(): atomic_widget = self._create_dropdownbox(key,value) self.config.add_handler(key, atomic_widget) elif key in self.radiobuttons.keys(): atomic_widget = self._create_radiobutton(key,value) self.config.add_handler(key, atomic_widget) elif type(value) is int: atomic_widget = QSpinBox() atomic_widget.setRange(-100000, 100000) self.config.add_handler(key, atomic_widget) elif type(value) is float: atomic_widget = QDoubleSpinBox() atomic_widget.setDecimals(6) atomic_widget.setMaximum(1000000000) self.config.add_handler(key, atomic_widget) elif type(value) is str: atomic_widget = QLineEdit() self.config.add_handler(key, atomic_widget) elif type(value) is bool: atomic_widget = QCheckBox() self.config.add_handler(key, atomic_widget) else: return None return atomic_widget def _create_sub_grid_from_ndarray(self, key, ndarray): hgrid = QGridLayout(self) hgrid_i = 0 for val in ndarray.tolist(): # key_i = key + str(hgrid_i) key_i = (key, hgrid_i) atomic_widget = self.__get_widget_for_primitive_types(key_i, val) hgrid.addWidget(atomic_widget, 0, hgrid_i) hgrid_i += 1 return hgrid def __calculate_new_grid_position(self): row = self.grid_i / self.ncols col = (self.grid_i % self.ncols) * 2 self.grid_i += 1 if self.horizontal: return col, row return row, col def btn_accept(self): print ("btn_accept: " + str(self.config_as_dict())) def on_config_update(self): pass def config_as_dict(self): def _primitive_type(value): if type(value) == PyQt4.QtCore.QString: value = str(value) return value dictionary = self.config.as_dict() dictionary = copy.copy(dictionary) for key, value in dictionary.iteritems(): from PyQt4.QtCore import pyqtRemoveInputHook #pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() # noqa BREAKPOINT # if type(key) == tuple: if type(key) == ComposedDictMetadata: dict_key, value_index = key dict_key = (dict_key) # if dict_key not in dictionary.keys(): # dictionary[dict_key] = {} dictionary[dict_key][value_index] = _primitive_type(value) else: dictionary[key] = _primitive_type(value) for key in dictionary.keys(): # if type(key) == tuple: if type(key) == ComposedDictMetadata: dictionary.pop(key) # for key, value in dictionary.iteritems(): # if type(key) == tuple: # dictionary return dictionary
class ToolBase(QObject): ''' Base tool definition for inclusion in the UI. Define specific config settings; attach a panel widget for configuration. ''' is_manual_runnable = True is_auto_runnable = True is_auto_rerunnable = True is_disableable = True progress = pyqtSignal(float) status = pyqtSignal(str) config_panel_size = 250 view_widget = 'SpectraViewer' def __init__(self, parent, *args, **kwargs): super(ToolBase, self).__init__(parent, *args, **kwargs) self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults({ 'is_active': True, 'auto_run_on_config_change': True }) self.config.updated.connect(self.auto_run_on_config_change) self.buttonBar = QWidget() self.configPanels = QWidget() self.configLayout = QVBoxLayout() self.configLayout.setContentsMargins(0, 0, 0, 0) self.configPanels.setLayout(self.configLayout) self._previous_config_backup_ = {} self._worker_thread_ = None self._worker_thread_lock_ = False self.data = { 'spc': None, } self.current_status = 'ready' self.current_progress = 0 self.progress.connect(self.progress_callback) self.status.connect(self.status_callback) def addConfigPanel(self, panel): self.configLayout.addWidget(panel(self)) def addButtonBar(self, buttons): ''' Create a button bar Supplied with a list of QPushButton objects (already created using helper stubs; see below) :param buttons: :return: ''' btnlayout = QHBoxLayout() btnlayout.addSpacerItem( QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum)) for btn in buttons: btnlayout.addWidget(btn) self.configLayout.addLayout(btnlayout) btnlayout.addSpacerItem( QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum)) def run_manual(self): pass def disable(self): self.status.emit('inactive') self.config.set('is_active', False) self.item.setFlags(Qt.NoItemFlags) def reset(self): self.config.set_many(self.config.defaults) def undo(self): self.config.set_many(self._config_backup_) def deftaultButtons(self): buttons = [] if self.is_disableable: disable = QPushButton( QIcon(os.path.join(utils.scriptdir, 'icons', 'cross.png')), 'Disable') disable.setToolTip('Disable this tool') disable.pressed.connect(self.disable) buttons.append(disable) reset = QPushButton( QIcon( os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Reset to defaults') reset.setToolTip('Reset to defaults') reset.pressed.connect(self.reset) buttons.append(reset) undo = QPushButton( QIcon( os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Undo') undo.setToolTip('Undo recent changes') undo.pressed.connect(self.undo) buttons.append(undo) if self.is_auto_runnable: auto = QPushButton( QIcon(os.path.join(utils.scriptdir, 'icons', 'lightning.png')), 'Auto') auto.setToolTip('Auto-update spectra when settings change') auto.setCheckable(True) auto.pressed.connect(self.run_manual) self.config.add_handler('auto_run_on_config_change', auto) buttons.append(auto) if self.is_manual_runnable: apply = QPushButton( QIcon(os.path.join(utils.scriptdir, 'icons', 'play.png')), 'Apply') apply.setToolTip('Apply current settings to spectra') apply.pressed.connect(self.run_manual) buttons.append(apply) return buttons def enable(self): if self.current_status == 'inactive': self.status.emit('ready') self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.config.set('is_active', True) def activate(self): self.parent().current_tool = self self.enable() self._config_backup_ = self.config.as_dict() self._refresh_plot_timer_ = QTimer.singleShot(0, self.plot) self.parent().viewStack.setCurrentWidget(self.parent().spectraViewer) self.parent().configstack.setCurrentWidget(self.configPanels) self.parent().configstack.setMaximumHeight(self.config_panel_size) def set_active(self, active): self.config.set('is_active', active) def get_previous_tool(self): # Get the previous ACTIVE tool in the tool table n = self.parent().tools.index(self) for tool in self.parent().tools[n - 1::-1]: if tool.current_status != 'inactive': return tool else: return None def get_previous_spc(self): t = self.get_previous_tool() if t: return t.data['spc'] else: return None def plot(self, **kwargs): if 'spc' in self.data: self.parent().spectraViewer.plot(self.data['spc'], **kwargs) def get_plotitem(self): return self.parent().spectraViewer.spectraViewer.plotItem def auto_run_on_config_change(self): pass #if self.is_auto_runnable and self.config.get('is_active') and self.config.get('auto_run_on_config_change'): # self.run_manual() def run(self, fn): ''' Run the target function, passing in the current spectra, and config settings (as dict) :param fn: :return: ''' if self._worker_thread_lock_: return False # Can't run self.progress.emit(0) self.status.emit('active') spc = self.get_previous_spc() self._worker_thread_lock_ = True print(self.config.as_dict()) self._worker_thread_ = Worker(fn=fn, **{ 'spc': deepcopy(spc), 'config': self.config.as_dict(), 'progress_callback': self.progress.emit, }) self._worker_thread_.signals.finished.connect(self.finished) self._worker_thread_.signals.result.connect(self.result) self._worker_thread_.signals.error.connect(self.error) self.parent().threadpool.start(self._worker_thread_) def error(self, error): self.progress.emit(1.0) self.status.emit('error') logging.error(error) self._worker_thread_lock_ = False def result(self, result): self.progress.emit(1) self.status.emit('complete') # Apply post-processing if 'spc' in result: result['spc'] = self.post_process_spc(result['spc']) self.data = result self.plot() def finished(self): # Cleanup self._worker_thread_lock_ = False def progress_callback(self, progress): self.current_progress = progress self.item.setData(Qt.UserRole + 2, progress) def status_callback(self, status): self.current_status = status self.item.setData(Qt.UserRole + 3, status) def post_process_spc(self, spc): ''' Apply post-processing to the spectra before loading into the data store, e.g. for outlier detection, stats etc. :param spc: :return: ''' # Outliers def identify_outliers(data, m=2): return abs(data - np.mean(data, axis=0)) < (m * np.std(data, axis=0)) # Identify outliers on a point by point basis. Count up 'outliers' and score ratio of points that are # outliers for each specra > 5% (make this configurable) is an outlier. spc.outliers = np.sum(~identify_outliers(spc.data), axis=1) / float( spc.data.shape[1]) return spc
class dialogPluginManagement(ui.GenericDialog): def populate_plugin_list(self): disabled_plugins = self.config.get('Plugins/Disabled') while self.plugins_lw.count() > 0: # Empty list self.plugins_lw.takeItem(0) for id, plugin in plugin_metadata.items(): item = QListWidgetItem() item.plugin_metadata = plugin item.plugin_shortname = id if 'image' in plugin: item.setData(Qt.DecorationRole, plugin['image']) item.setData(Qt.DisplayRole, "%s (v%s)" % (plugin['name'], plugin['version'])) item.setData(Qt.UserRole, plugin['description']) item.setData(Qt.UserRole + 1, plugin['author']) if plugin['path'] not in disabled_plugins: item.setData(Qt.UserRole + 2, "Active") self.plugins_lw.addItem(item) def onActivate(self): items = self.plugins_lw.selectedItems() disabled_plugins = self.config.get('Plugins/Disabled') for i in items: plugin_path = plugin_metadata[i.plugin_shortname]['path'] if plugin_path in disabled_plugins: disabled_plugins.remove(plugin_path) self.config.set('Plugins/Disabled', disabled_plugins) self.populate_plugin_list() def onDeactivate(self): items = self.plugins_lw.selectedItems() disabled_plugins = self.config.get('Plugins/Disabled') for i in items: plugin_path = plugin_metadata[i.plugin_shortname]['path'] if plugin_path not in disabled_plugins: disabled_plugins.append(plugin_path) self.config.set('Plugins/Disabled', disabled_plugins) self.populate_plugin_list() def onRefresh(self): get_available_plugins(self.config.get('Plugins/Paths')[:], include_deactivated=True) self.populate_plugin_list() def __init__(self, parent, **kwargs): super(dialogPluginManagement, self).__init__(parent, **kwargs) self.setWindowTitle(tr("Manage Plugins")) self.config = ConfigManager() self.config.defaults = { 'Plugins/Paths': settings.get('Plugins/Paths'), 'Plugins/Disabled': settings.get('Plugins/Disabled'), } self.m = parent self.setFixedSize(self.sizeHint()) self.plugins_lw = QListWidget() self.plugins_lw.setSelectionMode(QAbstractItemView.ExtendedSelection) self.plugins_lw.setItemDelegate(pluginListDelegate(self.plugins_lw)) self.plugins_lw.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) page = QWidget() box = QGridLayout() paths = QHBoxLayout() self.pathlist = QLineEdit() self.config.add_handler( 'Plugins/Paths', self.pathlist, (lambda x: x.split(';'), lambda x: ';'.join(x))) paths.addWidget(QLabel('Search Paths:')) paths.addWidget(self.pathlist) box.addLayout(paths, 0, 0) box.addWidget(self.plugins_lw, 1, 0) buttons = QVBoxLayout() refresh_btn = QPushButton('Refresh') refresh_btn.clicked.connect(self.onRefresh) buttons.addWidget(refresh_btn) activate_btn = QPushButton('Activate') activate_btn.clicked.connect(self.onActivate) buttons.addWidget(activate_btn) deactivate_btn = QPushButton('Dectivate') deactivate_btn.clicked.connect(self.onDeactivate) buttons.addWidget(deactivate_btn) buttons.addStretch() box.addLayout(buttons, 1, 1) page.setLayout(box) self.layout.addWidget(page) # Stack it all up, with extra buttons self.dialogFinalise() # Refresh the list of available plugins self._init_timer = QTimer.singleShot(0, self.onRefresh) def sizeHint(self): return QSize(600, 300)
class AnnotatePeaks(GenericDialog): def __init__(self, parent, config=None, *args, **kwargs): super(AnnotatePeaks, self).__init__(parent, *args, **kwargs) self.setWindowTitle('Annotate Peaks') if config: # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults(config) self.fwd_map_cache = {} # Correlation variables gb = QGroupBox('Peaks') vbox = QVBoxLayout() # Populate the list boxes self.lw_peaks = QListWidgetAddRemove() self.lw_peaks.setSelectionMode(QAbstractItemView.ExtendedSelection) vbox.addWidget(self.lw_peaks) vboxh = QHBoxLayout() self.add_label = QLineEdit() self.add_start = QDoubleSpinBox() self.add_start.setRange(-1, 12) self.add_start.setDecimals(3) self.add_start.setSuffix('ppm') self.add_start.setSingleStep(0.001) self.add_end = QDoubleSpinBox() self.add_end.setRange(-1, 12) self.add_end.setDecimals(3) self.add_end.setSuffix('ppm') self.add_end.setSingleStep(0.001) addc = QPushButton('Add') addc.clicked.connect(self.onPeakAdd) remc = QPushButton('Remove selected') remc.clicked.connect(self.onPeakAdd) loadc = QPushButton("Import from file") loadc.setIcon( QIcon( os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png'))) loadc.clicked.connect(self.onPeakImport) metabh = QPushButton("Auto match via MetaboHunter") metabh.setIcon( QIcon(os.path.join(utils.scriptdir, 'icons', 'metabohunter.png'))) metabh.clicked.connect(self.onPeakImportMetabohunter) vboxh.addWidget(self.add_label) vboxh.addWidget(self.add_start) vboxh.addWidget(self.add_end) vboxh.addWidget(addc) vbox.addWidget(remc) vbox.addLayout(vboxh) vbox.addWidget(loadc) vbox.addWidget(metabh) gb.setLayout(vbox) self.layout.addWidget(gb) self.config.add_handler('annotation/peaks', self.lw_peaks, (self.map_list_fwd, self.map_list_rev)) self.dialogFinalise() def onPeakAdd(self): c = self.config.get( 'annotation/peaks' )[:] # Create new list to force refresh on reassign c.append((self.add_label.text(), float(self.add_start.value()), float(self.add_end.value()))) self.config.set('annotation/peaks', c) def onPeakRemove(self): i = self.lw_peaks.removeItemAt(self.lw_peaks.currentRow()) # c = self.map_list_fwd(i.text()) def onPeakImport(self): filename, _ = QFileDialog.getOpenFileName( self.parent(), 'Load peak annotations from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)" ) if filename: c = self.config.get( 'annotation/peaks' )[:] # Create new list to force refresh on reassign with open(filename, 'rU') as f: reader = csv.reader(f, delimiter=b',', dialect='excel') for row in reader: if row not in c: c.append(row[0], float(row[1]), float(row[2])) self.config.set('annotation/peaks', c) def onPeakImportMetabohunter(self): c = self.config.get( 'annotation/peaks' )[:] # Create new list to force refresh on reassign t = self.parent().current_tool dlg = MetaboHunter(self) if dlg.exec_(): if 'spc' in t.data: # We have a spectra; calcuate mean; reduce size if required spc = t.data['spc'] n = spc.data.shape[1] ppm = spc.ppm spcd = np.mean(spc.data, axis=0) # Set a hard limit on the size of data we submit to be nice. if n > 3000: # Calculate the division required to be under the limit d = np.ceil(float(n) / 3000) # Trim axis to multiple of divisor trim = (n // d) * d spcd = spcd[:trim] ppm = ppm[:trim] # Mean d shape spcd = np.mean(spcd.reshape(-1, d), axis=1) ppm = np.mean(ppm.reshape(-1, d), axis=1) # Submit with settings hmdbs = metabohunter.request( ppm, spcd, metabotype=dlg.config.get('Metabotype'), database=dlg.config.get('Database Source'), ph=dlg.config.get('Sample pH'), solvent=dlg.config.get('Solvent'), frequency=dlg.config.get('Frequency'), method=dlg.config.get('Method'), noise=dlg.config.get('Noise Threshold'), confidence=dlg.config.get('Confidence Threshold'), tolerance=dlg.config.get('Tolerance')) ha = np.array(hmdbs) unique_hmdbs = set(hmdbs) if None in unique_hmdbs: unique_hmdbs.remove(None) # Extract out regions for hmdb in unique_hmdbs: hb = np.diff(ha == hmdb) # These are needed to ensure markers are there for objects starting and ending on array edge if ha[0] == hmdb: hb[0] == True if ha[-1] == hmdb: hb[-1] == True idx = np.nonzero(hb)[0] idx = idx.reshape(-1, 2) if dlg.config.get( 'convert_hmdb_ids_to_names' ) and hmdb in METABOHUNTER_HMDB_NAME_MAP.keys(): label = METABOHUNTER_HMDB_NAME_MAP[hmdb] else: label = hmdb # Now we have an array of all start, stop positions for this item for start, stop in idx: c.append((label, ppm[start], ppm[stop])) self.config.set('annotation/peaks', c) def map_list_fwd(self, s): " Receive text name, return the indexes " return self.fwd_map_cache[s] def map_list_rev(self, x): " Receive the indexes, return the label" s = "%s\t%.2f\t%.2f" % tuple(x) self.fwd_map_cache[s] = x return s
class MetaboHunter(GenericDialog): options = { 'Metabotype': { 'All': 'All', 'Drug': 'Drug', 'Food additive': 'Food additive', 'Mammalian': 'Mammalian', 'Microbial': 'Microbial', 'Plant': 'Plant', 'Synthetic/Industrial chemical': 'Synthetic/Industrial chemical', }, 'Database Source': { 'Human Metabolome Database (HMDB)': 'HMDB', 'Madison Metabolomics Consortium Database (MMCD)': 'MMCD', }, 'Sample pH': { '10.00 - 10.99': 'ph7', '7.00 - 9.99': 'ph7', '6.00 - 6.99': 'ph6', '5.00 - 5.99': 'ph5', '4.00 - 4.99': 'ph4', '3.00 - 3.99': 'ph3', }, 'Solvent': { 'All': 'all', 'Water': 'water', 'CDCl3': 'cdcl3', 'CD3OD': '5d3od', '5% DMSO': '5dmso', }, 'Frequency': { 'All': 'all', '600 MHz': '600', '500 MHz': '500', '400 MHz': '400', }, 'Method': { 'MH1: Highest number of matched peaks': 'HighestNumber', 'MH2: Highest number of matched peaks with shift tolerance': 'HighestNumberNeighbourhood', 'MH3: Greedy selection of metabolites with disjoint peaks': 'Greedy2', 'MH4: Highest number of matched peaks with intensities': 'HighestNumberHeights', 'MH5: Greedy selection of metabolites with disjoint peaks and heights': 'Greedy2Heights', }, } def __init__(self, *args, **kwargs): super(MetaboHunter, self).__init__(*args, **kwargs) self.setWindowTitle('MetaboHunter') # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults({ 'Metabotype': 'All', 'Database Source': 'HMDB', 'Sample pH': 'ph7', 'Solvent': 'water', 'Frequency': 'all', 'Method': 'HighestNumberNeighbourhood', 'Noise Threshold': 0.0001, 'Confidence Threshold': 0.5, 'Tolerance': 0.1, 'convert_hmdb_ids_to_names': True, }) self.lw_combos = {} for o in [ 'Metabotype', 'Database Source', 'Sample pH', 'Solvent', 'Frequency', 'Method' ]: row = QVBoxLayout() cl = QLabel(o) cb = QComboBox() cb.addItems(list(self.options[o].keys())) row.addWidget(cl) row.addWidget(cb) self.config.add_handler(o, cb, self.options[o]) self.layout.addLayout(row) row = QGridLayout() self.lw_spin = {} for n, o in enumerate( ['Noise Threshold', 'Confidence Threshold', 'Tolerance']): cl = QLabel(o) cb = QDoubleSpinBox() cb.setDecimals(4) cb.setRange(0, 1) cb.setSingleStep(0.01) cb.setValue(float(self.config.get(o))) row.addWidget(cl, 0, n) row.addWidget(cb, 1, n) self.config.add_handler(o, cb) self.layout.addLayout(row) row = QHBoxLayout() row.addWidget(QLabel("Convert HMDB IDs to chemical names?")) conv = QCheckBox() self.config.add_handler('convert_hmdb_ids_to_names', conv) row.addWidget(conv) self.layout.addLayout(row) self.dialogFinalise()
class AutomatonDialog(GenericDialog): mode_options = { 'Manual': MODE_MANUAL, 'Watch files': MODE_WATCH_FILES, 'Watch folder': MODE_WATCH_FOLDER, 'Timer': MODE_TIMER, } def __init__(self, parent, **kwargs): super(AutomatonDialog, self).__init__(parent, **kwargs) self.setWindowTitle("Edit Automaton") self.config = ConfigManager() gb = QGroupBox('IPython notebook(s) (*.ipynb)') grid = QGridLayout() notebook_path_le = QLineEdit() self.config.add_handler('notebook_paths', notebook_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x))) grid.addWidget(notebook_path_le, 0, 0, 1, 2) notebook_path_btn = QToolButton() notebook_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'document-attribute-i.png'))) notebook_path_btn.clicked.connect(lambda: self.onNotebookBrowse(notebook_path_le)) grid.addWidget(notebook_path_btn, 0, 2, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) gb = QGroupBox('Automaton mode') grid = QGridLayout() mode_cb = QComboBox() mode_cb.addItems(self.mode_options.keys()) mode_cb.currentIndexChanged.connect(self.onChangeMode) self.config.add_handler('mode', mode_cb, mapper=self.mode_options) grid.addWidget(QLabel('Mode'), 0, 0) grid.addWidget(mode_cb, 0, 1) grid.addWidget(QLabel('Hold trigger'), 1, 0) fwatcher_hold_sb = QSpinBox() fwatcher_hold_sb.setRange(0, 60) fwatcher_hold_sb.setSuffix(' secs') self.config.add_handler('trigger_hold', fwatcher_hold_sb) grid.addWidget(fwatcher_hold_sb, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) self.watchfile_gb = QGroupBox('Watch files') grid = QGridLayout() watched_path_le = QLineEdit() grid.addWidget(watched_path_le, 0, 0, 1, 2) self.config.add_handler('watched_files', watched_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x))) watched_path_btn = QToolButton() watched_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'document-copy.png'))) watched_path_btn.setStatusTip('Add file(s)') watched_path_btn.clicked.connect(lambda: self.onFilesBrowse(watched_path_le)) grid.addWidget(watched_path_btn, 0, 2, 1, 1) grid.addWidget(QLabel('Watch window'), 1, 0) watch_window_sb = QSpinBox() watch_window_sb.setRange(0, 60) watch_window_sb.setSuffix(' secs') self.config.add_handler('watch_window', watch_window_sb) grid.addWidget(watch_window_sb, 1, 1) self.watchfile_gb.setLayout(grid) self.layout.addWidget(self.watchfile_gb) self.watchfolder_gb = QGroupBox('Watch folder') grid = QGridLayout() watched_path_le = QLineEdit() grid.addWidget(watched_path_le, 0, 0, 1, 3) self.config.add_handler('watched_folder', watched_path_le) watched_path_btn = QToolButton() watched_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png'))) watched_path_btn.setStatusTip('Add folder') watched_path_btn.clicked.connect(lambda: self.onFolderBrowse(watched_path_le)) grid.addWidget(watched_path_btn, 0, 3, 1, 1) grid.addWidget(QLabel('Iterate files in folder'), 3, 0) loop_folder_sb = QCheckBox() self.config.add_handler('iterate_watched_folder', loop_folder_sb) grid.addWidget(loop_folder_sb, 3, 1) loop_wildcard_le = QLineEdit() self.config.add_handler('iterate_wildcard', loop_wildcard_le) grid.addWidget(loop_wildcard_le, 3, 2) self.watchfolder_gb.setLayout(grid) self.layout.addWidget(self.watchfolder_gb) self.timer_gb = QGroupBox('Timer') grid = QGridLayout() grid.addWidget(QLabel('Run every'), 0, 0) watch_timer_sb = QSpinBox() watch_timer_sb.setRange(0, 60) watch_timer_sb.setSuffix(' secs') self.config.add_handler('timer_seconds', watch_timer_sb) grid.addWidget(watch_timer_sb, 0, 1) self.timer_gb.setLayout(grid) self.layout.addWidget(self.timer_gb) self.manual_gb = QGroupBox('Manual') # No show grid = QGridLayout() grid.addWidget(QLabel('No configuration'), 0, 0) self.manual_gb.setLayout(grid) self.layout.addWidget(self.manual_gb) gb = QGroupBox('Output') grid = QGridLayout() output_path_le = QLineEdit() self.config.add_handler('output_path', output_path_le) grid.addWidget(output_path_le, 0, 0, 1, 2) notebook_path_btn = QToolButton() notebook_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png'))) notebook_path_btn.clicked.connect(lambda: self.onFolderBrowse(notebook_path_le)) grid.addWidget(notebook_path_btn, 0, 2, 1, 1) export_cb = QComboBox() export_cb.addItems(IPyexporter_map.keys()) self.config.add_handler('output_format', export_cb) grid.addWidget(QLabel('Notebook output format'), 1, 0) grid.addWidget(export_cb, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) self.layout.addStretch() self.finalise() self.onChangeMode(mode_cb.currentIndex()) def onNotebookBrowse(self, t): global _w filenames, _ = QFileDialog.getOpenFileNames(_w, "Load IPython notebook(s)", '', "IPython Notebooks (*.ipynb);;All files (*.*)") if filenames: self.config.set('notebook_paths', filenames) def onFolderBrowse(self, t): global _w filename = QFileDialog.getExistingDirectory(_w, "Select folder to watch") if filename: self.config.set('watched_folder', filename) def onFilesBrowse(self, t): global _w filenames, _ = QFileDialog.getOpenFileNames(_w, "Select file(s) to watch") if filenames: self.config.set('watched_files', filenames) def onChangeMode(self, i): for m, gb in {MODE_MANUAL: self.manual_gb, MODE_WATCH_FILES: self.watchfile_gb, MODE_WATCH_FOLDER: self.watchfolder_gb, MODE_TIMER: self.timer_gb}.items(): if m == list(self.mode_options.items())[i][1]: gb.show() else: gb.hide() def sizeHint(self): return QSize(400, 200)
class AnnotatePeaks(GenericDialog): def __init__(self, parent, config=None, *args, **kwargs): super(AnnotatePeaks, self).__init__(parent, *args, **kwargs) self.setWindowTitle('Annotate Peaks') if config: # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults(config) self.fwd_map_cache = {} # Correlation variables gb = QGroupBox('Peaks') vbox = QVBoxLayout() # Populate the list boxes self.lw_peaks = QListWidgetAddRemove() self.lw_peaks.setSelectionMode(QAbstractItemView.ExtendedSelection) vbox.addWidget(self.lw_peaks) vboxh = QHBoxLayout() self.add_label = QLineEdit() self.add_start = QDoubleSpinBox() self.add_start.setRange(-1, 12) self.add_start.setDecimals(3) self.add_start.setSuffix('ppm') self.add_start.setSingleStep(0.001) self.add_end = QDoubleSpinBox() self.add_end.setRange(-1, 12) self.add_end.setDecimals(3) self.add_end.setSuffix('ppm') self.add_end.setSingleStep(0.001) addc = QPushButton('Add') addc.clicked.connect(self.onPeakAdd) remc = QPushButton('Remove selected') remc.clicked.connect(self.onPeakAdd) loadc = QPushButton("Import from file") loadc.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png'))) loadc.clicked.connect(self.onPeakImport) metabh = QPushButton("Auto match via MetaboHunter") metabh.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'metabohunter.png'))) metabh.clicked.connect(self.onPeakImportMetabohunter) vboxh.addWidget(self.add_label) vboxh.addWidget(self.add_start) vboxh.addWidget(self.add_end) vboxh.addWidget(addc) vbox.addWidget(remc) vbox.addLayout(vboxh) vbox.addWidget(loadc) vbox.addWidget(metabh) gb.setLayout(vbox) self.layout.addWidget(gb) self.config.add_handler('annotation/peaks', self.lw_peaks, (self.map_list_fwd, self.map_list_rev)) self.dialogFinalise() def onPeakAdd(self): c = self.config.get('annotation/peaks')[:] # Create new list to force refresh on reassign c.append( (self.add_label.text(), float(self.add_start.value()), float(self.add_end.value()) ) ) self.config.set('annotation/peaks', c) def onPeakRemove(self): i = self.lw_peaks.removeItemAt(self.lw_peaks.currentRow()) # c = self.map_list_fwd(i.text()) def onPeakImport(self): filename, _ = QFileDialog.getOpenFileName(self.parent(), 'Load peak annotations from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)") if filename: c = self.config.get('annotation/peaks')[:] # Create new list to force refresh on reassign with open(filename, 'rU') as f: reader = csv.reader(f, delimiter=b',', dialect='excel') for row in reader: if row not in c: c.append( row[0], float(row[1]), float(row[2]) ) self.config.set('annotation/peaks', c) def onPeakImportMetabohunter(self): c = self.config.get('annotation/peaks')[:] # Create new list to force refresh on reassign t = self.parent().current_tool dlg = MetaboHunter(self) if dlg.exec_(): if 'spc' in t.data: # We have a spectra; calcuate mean; reduce size if required spc = t.data['spc'] n = spc.data.shape[1] ppm = spc.ppm spcd = np.mean(spc.data, axis=0) # Set a hard limit on the size of data we submit to be nice. if n > 3000: # Calculate the division required to be under the limit d = np.ceil(float(n)/3000) # Trim axis to multiple of divisor trim = (n//d)*d spcd = spcd[:trim] ppm = ppm[:trim] # Mean d shape spcd = np.mean( spcd.reshape(-1,d), axis=1) ppm = np.mean( ppm.reshape(-1,d), axis=1) # Submit with settings hmdbs = metabohunter.request(ppm, spcd, metabotype=dlg.config.get('Metabotype'), database=dlg.config.get('Database Source'), ph=dlg.config.get('Sample pH'), solvent=dlg.config.get('Solvent'), frequency=dlg.config.get('Frequency'), method=dlg.config.get('Method'), noise=dlg.config.get('Noise Threshold'), confidence=dlg.config.get('Confidence Threshold'), tolerance=dlg.config.get('Tolerance') ) ha = np.array(hmdbs) unique_hmdbs = set(hmdbs) if None in unique_hmdbs: unique_hmdbs.remove(None) # Extract out regions for hmdb in unique_hmdbs: hb = np.diff(ha == hmdb) # These are needed to ensure markers are there for objects starting and ending on array edge if ha[0] == hmdb: hb[0] == True if ha[-1] == hmdb: hb[-1] == True idx = np.nonzero(hb)[0] idx = idx.reshape(-1,2) if dlg.config.get('convert_hmdb_ids_to_names') and hmdb in METABOHUNTER_HMDB_NAME_MAP.keys(): label = METABOHUNTER_HMDB_NAME_MAP[hmdb] else: label = hmdb # Now we have an array of all start, stop positions for this item for start, stop in idx: c.append( (label, ppm[start], ppm[stop]) ) self.config.set('annotation/peaks', c) def map_list_fwd(self, s): " Receive text name, return the indexes " return self.fwd_map_cache[s] def map_list_rev(self, x): " Receive the indexes, return the label" s = "%s\t%.2f\t%.2f" % tuple(x) self.fwd_map_cache[s] = x return s
class AnnotateClasses(GenericDialog): def __init__(self, parent, config=None, *args, **kwargs): super(AnnotateClasses, self).__init__(parent, *args, **kwargs) self.setWindowTitle('Annotate Classes') if config: # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults(config) self.fwd_map_cache = {} # Correlation variables gb = QGroupBox('Sample classes') vbox = QVBoxLayout() # Populate the list boxes self.lw_classes = QListWidgetAddRemove() self.lw_classes.setSelectionMode(QAbstractItemView.ExtendedSelection) vbox.addWidget(self.lw_classes) vboxh = QHBoxLayout() self.add_label = QLineEdit() self.add_class = QLineEdit() addc = QPushButton('Add') addc.clicked.connect(self.onClassAdd) remc = QPushButton('Remove selected') remc.clicked.connect(self.onClassAdd) loadc = QPushButton('Import from file') loadc.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png'))) loadc.clicked.connect(self.onClassImport) vboxh.addWidget(self.add_label) vboxh.addWidget(self.add_class) vboxh.addWidget(addc) vbox.addWidget(remc) vbox.addLayout(vboxh) vboxh.addWidget(loadc) gb.setLayout(vbox) self.layout.addWidget(gb) self.config.add_handler('annotation/sample_classes', self.lw_classes, (self.map_list_fwd, self.map_list_rev)) self.dialogFinalise() def onClassAdd(self): c = self.config.get('annotation/sample_classes')[:] # Create new list to force refresh on reassign c.append( (self.add_label.text(), self.add_class.text()) ) self.config.set('annotation/sample_classes', c) def onClassRemove(self): i = self.lw_classes.removeItemAt(self.lw_classes.currentRow()) # c = self.map_list_fwd(i.text()) def onClassImport(self): filename, _ = QFileDialog.getOpenFileName(self.parent(), 'Load classifications from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)") if filename: c = self.config.get('annotation/sample_classes')[:] # Create new list to force refresh on reassign with open(filename, 'rU') as f: reader = csv.reader(f, delimiter=b',', dialect='excel') for row in reader: if row not in c: c.append(row[:2]) self.config.set('annotation/sample_classes', c) def map_list_fwd(self, s): " Receive text name, return the indexes " return self.fwd_map_cache[s] def map_list_rev(self, x): " Receive the indexes, return the label" s = "%s\t%s" % tuple(x) self.fwd_map_cache[s] = x return s
class MetaboHunter(GenericDialog): options = { 'Metabotype': { 'All': 'All', 'Drug': 'Drug', 'Food additive': 'Food additive', 'Mammalian': 'Mammalian', 'Microbial': 'Microbial', 'Plant': 'Plant', 'Synthetic/Industrial chemical': 'Synthetic/Industrial chemical', }, 'Database Source': { 'Human Metabolome Database (HMDB)': 'HMDB', 'Madison Metabolomics Consortium Database (MMCD)': 'MMCD', }, 'Sample pH': { '10.00 - 10.99': 'ph7', '7.00 - 9.99': 'ph7', '6.00 - 6.99': 'ph6', '5.00 - 5.99': 'ph5', '4.00 - 4.99': 'ph4', '3.00 - 3.99': 'ph3', }, 'Solvent': { 'All': 'all', 'Water': 'water', 'CDCl3': 'cdcl3', 'CD3OD': '5d3od', '5% DMSO': '5dmso', }, 'Frequency': { 'All': 'all', '600 MHz': '600', '500 MHz': '500', '400 MHz': '400', }, 'Method': { 'MH1: Highest number of matched peaks': 'HighestNumber', 'MH2: Highest number of matched peaks with shift tolerance': 'HighestNumberNeighbourhood', 'MH3: Greedy selection of metabolites with disjoint peaks': 'Greedy2', 'MH4: Highest number of matched peaks with intensities': 'HighestNumberHeights', 'MH5: Greedy selection of metabolites with disjoint peaks and heights': 'Greedy2Heights', }, } def __init__(self, *args, **kwargs): super(MetaboHunter, self).__init__(*args, **kwargs) self.setWindowTitle('MetaboHunter') # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults({ 'Metabotype': 'All', 'Database Source': 'HMDB', 'Sample pH': 'ph7', 'Solvent': 'water', 'Frequency': 'all', 'Method': 'HighestNumberNeighbourhood', 'Noise Threshold': 0.0001, 'Confidence Threshold': 0.5, 'Tolerance': 0.1, 'convert_hmdb_ids_to_names': True, }) self.lw_combos = {} for o in ['Metabotype', 'Database Source', 'Sample pH', 'Solvent', 'Frequency', 'Method']: row = QVBoxLayout() cl = QLabel(o) cb = QComboBox() cb.addItems(list(self.options[o].keys())) row.addWidget(cl) row.addWidget(cb) self.config.add_handler(o, cb, self.options[o]) self.layout.addLayout(row) row = QGridLayout() self.lw_spin = {} for n, o in enumerate(['Noise Threshold', 'Confidence Threshold', 'Tolerance']): cl = QLabel(o) cb = QDoubleSpinBox() cb.setDecimals(4) cb.setRange(0, 1) cb.setSingleStep(0.01) cb.setValue(float(self.config.get(o))) row.addWidget(cl, 0, n) row.addWidget(cb, 1, n) self.config.add_handler(o, cb) self.layout.addLayout(row) row = QHBoxLayout() row.addWidget(QLabel("Convert HMDB IDs to chemical names?")) conv = QCheckBox() self.config.add_handler('convert_hmdb_ids_to_names', conv) row.addWidget(conv) self.layout.addLayout(row) self.dialogFinalise()
class BaseAnnotationItem(ResizableGraphicsItem): handler_cache = {} styles = [ 'font-family', 'font-size', 'text-bold', 'text-italic', 'text-underline', 'text-color', 'color-border', 'color-background' ] minSize = ANNOTATION_MINIMUM_QSIZE def __init__(self, position=None, *args, **kwargs): super(BaseAnnotationItem, self).__init__(*args, **kwargs) # Config for each annotation item, holding the settings (styles, etc) # update-control via the toolbar using add_handler linking self.config = ConfigManager() self.config.updated.connect(self.applyStyleConfig) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsFocusable) if position: self.setPos(position) self.setZValue(-1) def delete(self): self.prepareGeometryChange() self.scene().annotations.remove(self) self.scene().removeItem(self) self.removeHandlers() def keyPressEvent(self, e): if e.key() == Qt.Key_Backspace and e.modifiers() == Qt.ControlModifier: self.delete() else: return super(BaseAnnotationItem, self).keyPressEvent(e) def importStyleConfig(self, config): for k in self.styles: self.config.set(k, config.get(k)) def addHandlers(self): m = self.scene().views()[0].m # Hack; need to switch to importing this for k in self.styles: self.config.add_handler(k, m.styletoolbarwidgets[k]) def removeHandlers(self): for k in self.styles: self.config.remove_handler(k) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if not value: self.removeHandlers() elif change == QGraphicsItem.ItemSelectedHasChanged: if value: self.addHandlers() return super(BaseAnnotationItem, self).itemChange(change, value)
class SerialDialog(QDialog): def __init__(self, settings, parent=None): super().__init__(parent) self.settings = settings self.serialports = [] # port self.portLabel = QLabel(self.tr("COM Port:")) self.portComboBox = QComboBox() self.portLabel.setBuddy(self.portComboBox) self.refresh_comports(self.portComboBox) # baudrate self.baudrateLabel = QLabel(self.tr("Baudrate:")) self.baudrateComboBox = QComboBox() self.baudrateLabel.setBuddy(self.baudrateComboBox) for br in BAUDRATES: self.baudrateComboBox.addItem(str(br), br) # buttons self.dlgbuttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal) self.dlgbuttons.rejected.connect(self.reject) self.dlgbuttons.accepted.connect(self.accept) # layout layout = QGridLayout() layout.addWidget(self.portLabel, 0, 0) layout.addWidget(self.portComboBox, 0, 1) layout.addWidget(self.baudrateLabel, 1, 0) layout.addWidget(self.baudrateComboBox, 1, 1) layout.addWidget(self.dlgbuttons, 2, 0, 1, 2) self.setLayout(layout) self.setWindowTitle(self.tr("Serial Settings")) # settings defaults = { PORT_SETTING: "", BAUDRATE_SETTING: "115200" } self.tmp_settings = ConfigManager() self.tmp_settings.set_defaults(defaults) self.tmp_settings.set_many( {key: self.settings.get(key) for key in defaults.keys()} ) self.tmp_settings.add_handler(PORT_SETTING, self.portComboBox) self.tmp_settings.add_handler(BAUDRATE_SETTING, self.baudrateComboBox) def accept(self): d = self.tmp_settings.as_dict() self.settings.set_many(d) super().accept() def refresh_comports(self, combobox): self.serialports = serial_ports() for port in self.serialports: combobox.addItem(port) @property def port(self): return self.portComboBox.currentText() @port.setter def port(self, value): if value in self.serialports: self.portComboBox.setCurrentIndex( self.portComboBox.findText(value)) else: raise ValueError("serial port '%s' not available" % value) @property def baudrate(self): return self.baudrateComboBox.currentData()
class AutomatonDialog(GenericDialog): mode_options = { 'Manual': MODE_MANUAL, 'Watch files': MODE_WATCH_FILES, 'Watch folder': MODE_WATCH_FOLDER, 'Timer': MODE_TIMER, } def __init__(self, parent, **kwargs): super(AutomatonDialog, self).__init__(parent, **kwargs) self.setWindowTitle("Edit Automaton") self.config = ConfigManager() gb = QGroupBox('IPython notebook(s) (*.ipynb)') grid = QGridLayout() notebook_path_le = QLineEdit() self.config.add_handler('notebook_paths', notebook_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x))) grid.addWidget(notebook_path_le, 0, 0, 1, 2) notebook_path_btn = QToolButton() notebook_path_btn.setIcon( QIcon( os.path.join(utils.scriptdir, 'icons', 'document-attribute-i.png'))) notebook_path_btn.clicked.connect( lambda: self.onNotebookBrowse(notebook_path_le)) grid.addWidget(notebook_path_btn, 0, 2, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) gb = QGroupBox('Automaton mode') grid = QGridLayout() mode_cb = QComboBox() mode_cb.addItems(self.mode_options.keys()) mode_cb.currentIndexChanged.connect(self.onChangeMode) self.config.add_handler('mode', mode_cb, mapper=self.mode_options) grid.addWidget(QLabel('Mode'), 0, 0) grid.addWidget(mode_cb, 0, 1) grid.addWidget(QLabel('Hold trigger'), 1, 0) fwatcher_hold_sb = QSpinBox() fwatcher_hold_sb.setRange(0, 60) fwatcher_hold_sb.setSuffix(' secs') self.config.add_handler('trigger_hold', fwatcher_hold_sb) grid.addWidget(fwatcher_hold_sb, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) self.watchfile_gb = QGroupBox('Watch files') grid = QGridLayout() watched_path_le = QLineEdit() grid.addWidget(watched_path_le, 0, 0, 1, 2) self.config.add_handler('watched_files', watched_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x))) watched_path_btn = QToolButton() watched_path_btn.setIcon( QIcon(os.path.join(utils.scriptdir, 'icons', 'document-copy.png'))) watched_path_btn.setStatusTip('Add file(s)') watched_path_btn.clicked.connect( lambda: self.onFilesBrowse(watched_path_le)) grid.addWidget(watched_path_btn, 0, 2, 1, 1) grid.addWidget(QLabel('Watch window'), 1, 0) watch_window_sb = QSpinBox() watch_window_sb.setRange(0, 60) watch_window_sb.setSuffix(' secs') self.config.add_handler('watch_window', watch_window_sb) grid.addWidget(watch_window_sb, 1, 1) self.watchfile_gb.setLayout(grid) self.layout.addWidget(self.watchfile_gb) self.watchfolder_gb = QGroupBox('Watch folder') grid = QGridLayout() watched_path_le = QLineEdit() grid.addWidget(watched_path_le, 0, 0, 1, 3) self.config.add_handler('watched_folder', watched_path_le) watched_path_btn = QToolButton() watched_path_btn.setIcon( QIcon( os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png'))) watched_path_btn.setStatusTip('Add folder') watched_path_btn.clicked.connect( lambda: self.onFolderBrowse(watched_path_le)) grid.addWidget(watched_path_btn, 0, 3, 1, 1) grid.addWidget(QLabel('Iterate files in folder'), 3, 0) loop_folder_sb = QCheckBox() self.config.add_handler('iterate_watched_folder', loop_folder_sb) grid.addWidget(loop_folder_sb, 3, 1) loop_wildcard_le = QLineEdit() self.config.add_handler('iterate_wildcard', loop_wildcard_le) grid.addWidget(loop_wildcard_le, 3, 2) self.watchfolder_gb.setLayout(grid) self.layout.addWidget(self.watchfolder_gb) self.timer_gb = QGroupBox('Timer') grid = QGridLayout() grid.addWidget(QLabel('Run every'), 0, 0) watch_timer_sb = QSpinBox() watch_timer_sb.setRange(0, 60) watch_timer_sb.setSuffix(' secs') self.config.add_handler('timer_seconds', watch_timer_sb) grid.addWidget(watch_timer_sb, 0, 1) self.timer_gb.setLayout(grid) self.layout.addWidget(self.timer_gb) self.manual_gb = QGroupBox('Manual') # No show grid = QGridLayout() grid.addWidget(QLabel('No configuration'), 0, 0) self.manual_gb.setLayout(grid) self.layout.addWidget(self.manual_gb) gb = QGroupBox('Output') grid = QGridLayout() output_path_le = QLineEdit() self.config.add_handler('output_path', output_path_le) grid.addWidget(output_path_le, 0, 0, 1, 2) notebook_path_btn = QToolButton() notebook_path_btn.setIcon( QIcon( os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png'))) notebook_path_btn.clicked.connect( lambda: self.onFolderBrowse(notebook_path_le)) grid.addWidget(notebook_path_btn, 0, 2, 1, 1) export_cb = QComboBox() export_cb.addItems(IPyexporter_map.keys()) self.config.add_handler('output_format', export_cb) grid.addWidget(QLabel('Notebook output format'), 1, 0) grid.addWidget(export_cb, 1, 1) gb.setLayout(grid) self.layout.addWidget(gb) self.layout.addStretch() self.finalise() self.onChangeMode(mode_cb.currentIndex()) def onNotebookBrowse(self, t): global _w filenames, _ = QFileDialog.getOpenFileNames( _w, "Load IPython notebook(s)", '', "IPython Notebooks (*.ipynb);;All files (*.*)") if filenames: self.config.set('notebook_paths', filenames) def onFolderBrowse(self, t): global _w filename = QFileDialog.getExistingDirectory(_w, "Select folder to watch") if filename: self.config.set('watched_folder', filename) def onFilesBrowse(self, t): global _w filenames, _ = QFileDialog.getOpenFileNames(_w, "Select file(s) to watch") if filenames: self.config.set('watched_files', filenames) def onChangeMode(self, i): for m, gb in { MODE_MANUAL: self.manual_gb, MODE_WATCH_FILES: self.watchfile_gb, MODE_WATCH_FOLDER: self.watchfolder_gb, MODE_TIMER: self.timer_gb }.items(): if m == list(self.mode_options.items())[i][1]: gb.show() else: gb.hide() def sizeHint(self): return QSize(400, 200)
class AnnotateClasses(GenericDialog): def __init__(self, parent, config=None, *args, **kwargs): super(AnnotateClasses, self).__init__(parent, *args, **kwargs) self.setWindowTitle('Annotate Classes') if config: # Copy in starting state self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults(config) self.fwd_map_cache = {} # Correlation variables gb = QGroupBox('Sample classes') vbox = QVBoxLayout() # Populate the list boxes self.lw_classes = QListWidgetAddRemove() self.lw_classes.setSelectionMode(QAbstractItemView.ExtendedSelection) vbox.addWidget(self.lw_classes) vboxh = QHBoxLayout() self.add_label = QLineEdit() self.add_class = QLineEdit() addc = QPushButton('Add') addc.clicked.connect(self.onClassAdd) remc = QPushButton('Remove selected') remc.clicked.connect(self.onClassAdd) loadc = QPushButton('Import from file') loadc.setIcon( QIcon( os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png'))) loadc.clicked.connect(self.onClassImport) vboxh.addWidget(self.add_label) vboxh.addWidget(self.add_class) vboxh.addWidget(addc) vbox.addWidget(remc) vbox.addLayout(vboxh) vboxh.addWidget(loadc) gb.setLayout(vbox) self.layout.addWidget(gb) self.config.add_handler('annotation/sample_classes', self.lw_classes, (self.map_list_fwd, self.map_list_rev)) self.dialogFinalise() def onClassAdd(self): c = self.config.get( 'annotation/sample_classes' )[:] # Create new list to force refresh on reassign c.append((self.add_label.text(), self.add_class.text())) self.config.set('annotation/sample_classes', c) def onClassRemove(self): i = self.lw_classes.removeItemAt(self.lw_classes.currentRow()) # c = self.map_list_fwd(i.text()) def onClassImport(self): filename, _ = QFileDialog.getOpenFileName( self.parent(), 'Load classifications from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)" ) if filename: c = self.config.get( 'annotation/sample_classes' )[:] # Create new list to force refresh on reassign with open(filename, 'rU') as f: reader = csv.reader(f, delimiter=b',', dialect='excel') for row in reader: if row not in c: c.append(row[:2]) self.config.set('annotation/sample_classes', c) def map_list_fwd(self, s): " Receive text name, return the indexes " return self.fwd_map_cache[s] def map_list_rev(self, x): " Receive the indexes, return the label" s = "%s\t%s" % tuple(x) self.fwd_map_cache[s] = x return s
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle('PyQtConfig Demo') self.vis = ConfigManager() # # CHOICE_A = 1 # CHOICE_B = 2 # CHOICE_C = 3 # CHOICE_D = 4 # # map_dict = { # 'Choice A': CHOICE_A, # 'Choice B': CHOICE_B, # 'Choice C': CHOICE_C, # 'Choice D': CHOICE_D, # } self.vis.set_defaults({ 'text1': 'hello1', 'text2': 'hello2', 'text3': 'hello3', 'text4': 'hello4', }) # self.vis.set_defaults({ # 'number': 13, # 'text': 'hello', # 'active': True, # 'combo': CHOICE_C, # }) gd = QGridLayout() # # sb = QSpinBox() # gd.addWidget(sb, 0, 1) # self.vis.add_handler('number', sb) # # te = QLineEdit() # gd.addWidget(te, 1, 1) # self.vis.add_handler('text', te) # # cb = QCheckBox() # gd.addWidget(cb, 2, 1) # self.vis.add_handler('active', cb) # # cmb = QComboBox() # cmb.addItems(map_dict.keys()) # gd.addWidget(cmb, 3, 1) # self.vis.add_handler('combo', cmb, mapper=map_dict) # # self.current_config_output = QTextEdit() # gd.addWidget(self.current_config_output, 0, 3, 3, 1) i=0 print self.vis.as_dict() for item in self.vis.as_dict().keys(): te = QLineEdit() te.setEnabled(False) self.vis.add_handler(item,te) gd.addWidget(te,i,1) i=i+1 # self.vis.updated.connect(self.show_config) # self.show_config() self.window = QWidget() self.window.setLayout(gd) self.setCentralWidget(self.window)
class DictGui(QDialog): # class LisaConfigWindow(QMainWindow): def __init__(self, config_in, ncols=2): super(DictGui, self).__init__() self.setWindowTitle('Lisa Config') self.config = ConfigManager() self.ncols = ncols # used with other module. If it is set, lisa config is deleted self.reset_config = False CHOICE_A = "{pairwise_alpha_per_mm2: 45, return_only_object_with_seeds: true}" # CHOICE_A = 23 CHOICE_B = 2 CHOICE_C = 3 CHOICE_D = 4 map_dict = { 'Graph-Cut': CHOICE_A, 'Choice B': CHOICE_B, 'Choice C': CHOICE_C, 'Choice D': CHOICE_D, } config_def = { 'working_voxelsize_mm': 2.0, 'save_filetype': 'pklz', # 'manualroi': True, 'segparams': CHOICE_A, } config_def.update(config_in) self.config.set_defaults(config_def) gd = QGridLayout() gd_max_i = 0 for key, value in config_in.items(): if type(value) is int: sb = QSpinBox() sb.setRange(-100000, 100000) elif type(value) is float: sb = QDoubleSpinBox() elif type(value) is str: sb = QLineEdit() elif type(value) is bool: sb = QCheckBox() else: logger.error("Unexpected type in config dictionary") row = gd_max_i / self.ncols col = (gd_max_i % self.ncols) * 2 gd.addWidget(QLabel(key), row, col + 1) gd.addWidget(sb, row, col + 2) self.config.add_handler(key, sb) gd_max_i += 1 # gd.addWidget(QLabel("save filetype"), 1, 1) # te = QLineEdit() # gd.addWidget(te, 1, 2) # self.config.add_handler('save_filetype', te) # # gd.addWidget(QLabel("segmentation parameters"), 2, 1) # te = QLineEdit() # gd.addWidget(te, 2, 2) # self.config.add_handler('segparams', te) # cb = QCheckBox() # gd.addWidget(cb, 2, 2) # self.config.add_handler('active', cb) # gd.addWidget(QLabel("segmentation parameters"), 3, 1) # cmb = QComboBox() # cmb.addItems(map_dict.keys()) # gd.addWidget(cmb, 3, 2) # self.config.add_handler('segparams', cmb, mapper=map_dict) self.current_config_output = QTextEdit() # rid.setColumnMinimumWidth(3, logo.width()/2) text_col = (self.ncols * 2) + 3 gd.setColumnMinimumWidth(text_col, 500) gd.addWidget(self.current_config_output, 1, text_col, (gd_max_i / 2) - 1, 1) btn_reset_config = QPushButton("Default", self) btn_reset_config.clicked.connect(self.btnResetConfig) gd.addWidget(btn_reset_config, 0, text_col) btn_save_config = QPushButton("Ok", self) btn_save_config.clicked.connect(self.btnSaveConfig) gd.addWidget(btn_save_config, (gd_max_i / 2), text_col) self.config.updated.connect(self.show_config) self.show_config() # my line self.setLayout(gd) # self.window = QWidget() # self.window.setLayout(gd) # self.setCentralWidget(self.window) def add_grid_line(self, key, value): pass def get_config_as_dict(self): dictionary = self.config.as_dict() for key, value in dictionary.items(): from PyQt4.QtCore import pyqtRemoveInputHook pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() # noqa BREAKPOINT if type(value) == QString: value = str(value) dictionary[key] = value return dictionary def btnResetConfig(self, event=None): self.reset_config = True self.close() def btnSaveConfig(self, event=None): self.reset_config = False self.close() def show_config(self): text = str(self.get_config_as_dict()) self.current_config_output.setText(text)
class ToolBase(QObject): ''' Base tool definition for inclusion in the UI. Define specific config settings; attach a panel widget for configuration. ''' is_manual_runnable = True is_auto_runnable = True is_auto_rerunnable = True is_disableable = True progress = pyqtSignal(float) status = pyqtSignal(str) config_panel_size = 250 view_widget = 'SpectraViewer' def __init__(self, parent, *args, **kwargs): super(ToolBase, self).__init__(parent, *args, **kwargs) self.config = ConfigManager() self.config.hooks.update(custom_pyqtconfig_hooks.items()) self.config.set_defaults({ 'is_active': True, 'auto_run_on_config_change': True }) self.config.updated.connect(self.auto_run_on_config_change) self.buttonBar = QWidget() self.configPanels = QWidget() self.configLayout = QVBoxLayout() self.configLayout.setContentsMargins(0,0,0,0) self.configPanels.setLayout(self.configLayout) self._previous_config_backup_ = {} self._worker_thread_ = None self._worker_thread_lock_ = False self.data = { 'spc': None, } self.current_status = 'ready' self.current_progress = 0 self.progress.connect(self.progress_callback) self.status.connect(self.status_callback) def addConfigPanel(self, panel): self.configLayout.addWidget( panel(self) ) def addButtonBar(self, buttons): ''' Create a button bar Supplied with a list of QPushButton objects (already created using helper stubs; see below) :param buttons: :return: ''' btnlayout = QHBoxLayout() btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum)) for btn in buttons: btnlayout.addWidget(btn) self.configLayout.addLayout(btnlayout) btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum)) def run_manual(self): pass def disable(self): self.status.emit('inactive') self.config.set('is_active', False) self.item.setFlags(Qt.NoItemFlags) def reset(self): self.config.set_many( self.config.defaults ) def undo(self): self.config.set_many(self._config_backup_) def deftaultButtons(self): buttons = [] if self.is_disableable: disable = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'cross.png')), 'Disable') disable.setToolTip('Disable this tool') disable.pressed.connect(self.disable) buttons.append(disable) reset = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Reset to defaults') reset.setToolTip('Reset to defaults') reset.pressed.connect(self.reset) buttons.append(reset) undo = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Undo') undo.setToolTip('Undo recent changes') undo.pressed.connect(self.undo) buttons.append(undo) if self.is_auto_runnable: auto = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'lightning.png')), 'Auto') auto.setToolTip('Auto-update spectra when settings change') auto.setCheckable(True) auto.pressed.connect(self.run_manual) self.config.add_handler('auto_run_on_config_change', auto) buttons.append(auto) if self.is_manual_runnable: apply = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'play.png')), 'Apply') apply.setToolTip('Apply current settings to spectra') apply.pressed.connect(self.run_manual) buttons.append(apply) return buttons def enable(self): if self.current_status == 'inactive': self.status.emit('ready') self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.config.set('is_active', True) def activate(self): self.parent().current_tool = self self.enable() self._config_backup_ = self.config.as_dict() self._refresh_plot_timer_ = QTimer.singleShot(0, self.plot) self.parent().viewStack.setCurrentWidget(self.parent().spectraViewer) self.parent().configstack.setCurrentWidget(self.configPanels) self.parent().configstack.setMaximumHeight(self.config_panel_size) def set_active(self, active): self.config.set('is_active', active) def get_previous_tool(self): # Get the previous ACTIVE tool in the tool table n = self.parent().tools.index(self) for tool in self.parent().tools[n-1::-1]: if tool.current_status != 'inactive': return tool else: return None def get_previous_spc(self): t = self.get_previous_tool() if t: return t.data['spc'] else: return None def plot(self, **kwargs): if 'spc' in self.data: self.parent().spectraViewer.plot(self.data['spc'], **kwargs) def get_plotitem(self): return self.parent().spectraViewer.spectraViewer.plotItem def auto_run_on_config_change(self): pass #if self.is_auto_runnable and self.config.get('is_active') and self.config.get('auto_run_on_config_change'): # self.run_manual() def run(self, fn): ''' Run the target function, passing in the current spectra, and config settings (as dict) :param fn: :return: ''' if self._worker_thread_lock_: return False # Can't run self.progress.emit(0) self.status.emit('active') spc = self.get_previous_spc() self._worker_thread_lock_ = True print(self.config.as_dict()) self._worker_thread_ = Worker(fn = fn, **{ 'spc': deepcopy(spc), 'config': self.config.as_dict(), 'progress_callback': self.progress.emit, }) self._worker_thread_.signals.finished.connect(self.finished) self._worker_thread_.signals.result.connect(self.result) self._worker_thread_.signals.error.connect(self.error) self.parent().threadpool.start(self._worker_thread_) def error(self, error): self.progress.emit(1.0) self.status.emit('error') logging.error(error) self._worker_thread_lock_ = False def result(self, result): self.progress.emit(1) self.status.emit('complete') # Apply post-processing if 'spc' in result: result['spc'] = self.post_process_spc(result['spc']) self.data = result self.plot() def finished(self): # Cleanup self._worker_thread_lock_ = False def progress_callback(self, progress): self.current_progress = progress self.item.setData(Qt.UserRole + 2, progress) def status_callback(self, status): self.current_status = status self.item.setData(Qt.UserRole + 3, status) def post_process_spc(self, spc): ''' Apply post-processing to the spectra before loading into the data store, e.g. for outlier detection, stats etc. :param spc: :return: ''' # Outliers def identify_outliers(data, m=2): return abs(data - np.mean(data, axis=0)) < (m * np.std(data,axis=0)) # Identify outliers on a point by point basis. Count up 'outliers' and score ratio of points that are # outliers for each specra > 5% (make this configurable) is an outlier. spc.outliers = np.sum( ~identify_outliers(spc.data), axis=1 ) / float(spc.data.shape[1]) return spc