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 QGraphicsSceneExtend(QGraphicsScene): def __init__(self, parent, *args, **kwargs): super(QGraphicsSceneExtend, self).__init__(parent, *args, **kwargs) self.m = parent.m self.config = ConfigManager() # These config settings are transient (ie. not stored between sessions) self.config.set_defaults({ 'mode': EDITOR_MODE_NORMAL, 'font-family': 'Arial', 'font-size': '12', 'text-bold': False, 'text-italic': False, 'text-underline': False, 'text-color': '#000000', 'color-border': None, # '#000000', 'color-background': None, }) # Pre-set these values (will be used by default) self.config.set('color-background', '#5555ff') self.background_image = QImage(os.path.join(utils.scriptdir, 'icons', 'grid100.png')) if settings.get('Editor/Show_grid'): self.showGrid() else: self.hideGrid() self.mode = EDITOR_MODE_NORMAL self.mode_current_object = None self.annotations = [] def mousePressEvent(self, e): if self.config.get('mode') != EDITOR_MODE_NORMAL: for i in self.selectedItems(): i.setSelected(False) if self.config.get('mode') == EDITOR_MODE_TEXT: tw = AnnotationTextItem(position=e.scenePos()) elif self.config.get('mode') == EDITOR_MODE_REGION: tw = AnnotationRegionItem(position=e.scenePos()) elif self.config.get('mode') == EDITOR_MODE_ARROW: tw = AnnotationRegionItem(position=e.scenePos()) self.addItem(tw) self.mode_current_object = tw tw._createFromMousePressEvent(e) tw.importStyleConfig(self.config) self.annotations.append(tw) else: for i in self.selectedItems(): i.setSelected(False) super(QGraphicsSceneExtend, self).mousePressEvent(e) def mouseMoveEvent(self, e): if self.config.get('mode') == EDITOR_MODE_TEXT and self.mode_current_object: self.mode_current_object._resizeFromMouseMoveEvent(e) elif self.config.get('mode') == EDITOR_MODE_REGION and self.mode_current_object: self.mode_current_object._resizeFromMouseMoveEvent(e) else: super(QGraphicsSceneExtend, self).mouseMoveEvent(e) def mouseReleaseEvent(self, e): if self.config.get('mode'): self.mode_current_object.setSelected(True) self.mode_current_object.setFocus() self.config.set('mode', EDITOR_MODE_NORMAL) self.mode_current_object = None super(QGraphicsSceneExtend, self).mouseReleaseEvent(e) def showGrid(self): self.setBackgroundBrush(QBrush(self.background_image)) def hideGrid(self): self.setBackgroundBrush(QBrush(None)) def onSaveAsImage(self): filename, _ = QFileDialog.getSaveFileName(self.m, 'Save current figure', '', "Tagged Image File Format (*.tif);;\ Portable Network Graphics (*.png)") if filename: self.saveAsImage(filename) def saveAsImage(self, f): self.image = QImage(self.sceneRect().size().toSize(), QImage.Format_ARGB32) self.image.fill(Qt.white) painter = QPainter(self.image) self.render(painter) self.image.save(f) def addApp(self, app, position=None): i = ToolItem(self, app, position=position) self.addItem(i) #i.onShow() return i def removeApp(self, app): i = app.editorItem i.hide() self.removeItem(i) app.editorItem = None def dragEnterEvent(self, e): if e.mimeData().hasFormat('application/x-pathomx-app') or e.mimeData().hasFormat('text/uri-list'): e.accept() else: e.ignore() def dragMoveEvent(self, e): e.accept() def dropEvent(self, e): scenePos = e.scenePos() - QPointF(32, 32) if e.mimeData().hasFormat('application/x-pathomx-app'): try: app_id = str(e.mimeData().data('application/x-pathomx-app'), 'utf-8') # Python 3 except: app_id = str(e.mimeData().data('application/x-pathomx-app')) # Python 2 e.setDropAction(Qt.CopyAction) a = app_launchers[app_id](self.m, position=scenePos, auto_focus=False) #self.centerOn(a.editorItem) e.accept() elif e.mimeData().hasFormat('text/uri-list'): for ufn in e.mimeData().urls(): fn = ufn.path() fnn, ext = os.path.splitext(fn) ext = ext.strip('.') if ext in file_handlers: a = file_handlers[ext](position=scenePos, auto_focus=False, filename=fn) self.centerOn(a.editorItem) e.accept() def getXMLAnnotations(self, root): # Iterate over the entire set (in order) creating a XML representation of the MatchDef and Style for annotation in self.annotations: ase = et.SubElement(root, "Annotation") ase.set('type', type(annotation).__name__) ase.set('x', str(annotation.x())) ase.set('y', str(annotation.y())) ase.set('width', str(annotation.rect().width())) ase.set('height', str(annotation.rect().height())) if hasattr(annotation, 'text'): text = et.SubElement(ase, "Text") text.text = annotation.text.toPlainText() ase = annotation.config.getXMLConfig(ase) return root def setXMLAnnotations(self, root): ANNOTATION_TYPES = { 'AnnotationTextItem': AnnotationTextItem, 'AnnotationRegionItem': AnnotationRegionItem, } for ase in root.findall('Annotation'): # Validate the class definition before creating it if ase.get('type') in ANNOTATION_TYPES: pos = QPointF(float(ase.get('x')), float(ase.get('y'))) aobj = ANNOTATION_TYPES[ase.get('type')](position=pos) aobj.setRect(QRectF(0, 0, float(ase.get('width')), float(ase.get('height')))) to = ase.find('Text') if to is not None: aobj.text.setPlainText(to.text) self.addItem(aobj) self.annotations.append(aobj) aobj.config.setXMLConfig(ase) aobj.applyStyleConfig()
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 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 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 = False is_auto_rerunnable = True is_disableable = True progress = pyqtSignal(float) status = pyqtSignal(str) complete = pyqtSignal() config_panel_size = 150 view = None 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({ 'auto_run_on_config_change': True }) self.current_status = 'inactive' 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.view = MplView(self.parent()) self.setup() self.current_progress = 0 self.progress.connect(self.progress_callback) self.status.connect(self.status_callback) def setup(self): self.data = { 'data': None, } self.view.figure.clear() self.view.redraw() self.status.emit('inactive' if self.current_status == 'inactive' else 'ready') 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.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 defaultButtons(self): buttons = [] 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) 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.current_status = 'ready' self.status.emit('ready') self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) 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) if self.view: self.parent().viewStack.setCurrentWidget(self.view) 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_next_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:]: if tool.current_status != 'inactive': return tool else: return None def get_previous_data(self): t = self.get_previous_tool() if t and 'data' in t.data: return t.data['data'] else: return None def plot(self, **kwargs): pass #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): if self.is_auto_runnable and self.current_status == 'ready' 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') data = self.get_previous_data() self._worker_thread_lock_ = True kwargs = { 'config': self.config.as_dict(), 'progress_callback': self.progress.emit, } if data: kwargs.update(deepcopy(data)) # Close any open figures; ensure new axes. plt.close() self._worker_thread_ = Worker(fn = fn, **kwargs) 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): # Apply post-processing if 'fig' in result and result['fig']: fig = result['fig'] fig.set_size_inches(self.view.get_size_inches(fig.get_dpi())) fig.set_tight_layout(False) fig.tight_layout(pad=0.5, rect=[0.25, 0.10, 0.75, 0.90]) self.view.figure = fig self.view.redraw() self.data = result self.plot() self.progress.emit(1) self.status.emit('complete') def finished(self): # Cleanup self._worker_thread_lock_ = False self.complete.emit() 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)
class QGraphicsSceneExtend(QGraphicsScene): def __init__(self, parent, *args, **kwargs): super(QGraphicsSceneExtend, self).__init__(parent, *args, **kwargs) self.m = parent.m self.config = ConfigManager() # These config settings are transient (ie. not stored between sessions) self.config.set_defaults({ 'mode': EDITOR_MODE_NORMAL, 'font-family': 'Arial', 'font-size': '12', 'text-bold': False, 'text-italic': False, 'text-underline': False, 'text-color': '#000000', 'color-border': None, # '#000000', 'color-background': None, }) # Pre-set these values (will be used by default) self.config.set('color-background', '#5555ff') self.background_image = QImage( os.path.join(utils.scriptdir, 'icons', 'grid100.png')) if settings.get('Editor/Show_grid'): self.showGrid() else: self.hideGrid() self.mode = EDITOR_MODE_NORMAL self.mode_current_object = None self.annotations = [] def mousePressEvent(self, e): if self.config.get('mode') != EDITOR_MODE_NORMAL: for i in self.selectedItems(): i.setSelected(False) if self.config.get('mode') == EDITOR_MODE_TEXT: tw = AnnotationTextItem(position=e.scenePos()) elif self.config.get('mode') == EDITOR_MODE_REGION: tw = AnnotationRegionItem(position=e.scenePos()) elif self.config.get('mode') == EDITOR_MODE_ARROW: tw = AnnotationRegionItem(position=e.scenePos()) self.addItem(tw) self.mode_current_object = tw tw._createFromMousePressEvent(e) tw.importStyleConfig(self.config) self.annotations.append(tw) else: super(QGraphicsSceneExtend, self).mousePressEvent(e) def mouseMoveEvent(self, e): if self.config.get( 'mode') == EDITOR_MODE_TEXT and self.mode_current_object: self.mode_current_object._resizeFromMouseMoveEvent(e) elif self.config.get( 'mode') == EDITOR_MODE_REGION and self.mode_current_object: self.mode_current_object._resizeFromMouseMoveEvent(e) else: super(QGraphicsSceneExtend, self).mouseMoveEvent(e) def mouseReleaseEvent(self, e): if self.config.get('mode'): self.mode_current_object.setSelected(True) self.mode_current_object.setFocus() self.config.set('mode', EDITOR_MODE_NORMAL) self.mode_current_object = None super(QGraphicsSceneExtend, self).mouseReleaseEvent(e) def showGrid(self): self.setBackgroundBrush(QBrush(self.background_image)) def hideGrid(self): self.setBackgroundBrush(QBrush(None)) def onSaveAsImage(self): filename, _ = QFileDialog.getSaveFileName( self.m, 'Save current figure', '', "Tagged Image File Format (*.tif);;\ Portable Network Graphics (*.png)" ) if filename: self.saveAsImage(filename) def saveAsImage(self, f): self.image = QImage(self.sceneRect().size().toSize(), QImage.Format_ARGB32) self.image.fill(Qt.white) painter = QPainter(self.image) self.render(painter) self.image.save(f) def addApp(self, app, position=None): i = ToolItem(self, app, position=position) self.addItem(i) return i def removeApp(self, app): i = app.editorItem i.hide() self.removeItem(i) app.editorItem = None def dragEnterEvent(self, e): if e.mimeData().hasFormat('application/x-pathomx-app') or e.mimeData( ).hasFormat('text/uri-list'): e.accept() else: e.ignore() def dragMoveEvent(self, e): e.accept() def dropEvent(self, e): scenePos = e.scenePos() - QPointF(32, 32) if e.mimeData().hasFormat('application/x-pathomx-app'): try: app_id = str(e.mimeData().data('application/x-pathomx-app'), 'utf-8') # Python 3 except: app_id = str( e.mimeData().data('application/x-pathomx-app')) # Python 2 e.setDropAction(Qt.CopyAction) a = app_launchers[app_id](self.m, position=scenePos, auto_focus=False) #self.centerOn(a.editorItem) e.accept() elif e.mimeData().hasFormat('text/uri-list'): for ufn in e.mimeData().urls(): fn = ufn.path() fnn, ext = os.path.splitext(fn) ext = ext.strip('.') if ext in file_handlers: a = file_handlers[ext](position=scenePos, auto_focus=False, filename=fn) self.centerOn(a.editorItem) e.accept() def getXMLAnnotations(self, root): # Iterate over the entire set (in order) creating a XML representation of the MatchDef and Style for annotation in self.annotations: ase = et.SubElement(root, "Annotation") ase.set('type', type(annotation).__name__) ase.set('x', str(annotation.x())) ase.set('y', str(annotation.y())) ase.set('width', str(annotation.rect().width())) ase.set('height', str(annotation.rect().height())) if hasattr(annotation, 'text'): text = et.SubElement(ase, "Text") text.text = annotation.text.toPlainText() ase = annotation.config.getXMLConfig(ase) return root def setXMLAnnotations(self, root): ANNOTATION_TYPES = { 'AnnotationTextItem': AnnotationTextItem, 'AnnotationRegionItem': AnnotationRegionItem, } for ase in root.findall('Annotation'): # Validate the class definition before creating it if ase.get('type') in ANNOTATION_TYPES: pos = QPointF(float(ase.get('x')), float(ase.get('y'))) aobj = ANNOTATION_TYPES[ase.get('type')](position=pos) aobj.setRect( QRectF(0, 0, float(ase.get('width')), float(ase.get('height')))) to = ase.find('Text') if to is not None: aobj.text.setPlainText(to.text) self.addItem(aobj) self.annotations.append(aobj) aobj.config.setXMLConfig(ase) aobj.applyStyleConfig()
class Automaton(QStandardItem): def __init__(self, *args, **kwargs): super(Automaton, self).__init__(*args, **kwargs) self.setData(self, Qt.UserRole) self.watcher = QFileSystemWatcher() self.timer = QTimer() self.watch_window = {} self.latest_run = {} self.is_running = False self.config = ConfigManager() self.config.set_defaults({ 'mode': MODE_WATCH_FOLDER, 'is_active': True, 'trigger_hold': 1, 'notebook_paths': '', 'output_path': '{home}/{notebook_filename}_{datetime}_', 'output_format': 'html', 'watched_files': [], 'watched_folder': '', 'watch_window': 15, 'iterate_watched_folder': True, 'iterate_wildcard': '.csv', 'timer_seconds': 60, }) self.runner = None self.lock = None self.latest_run = { 'timestamp': None, 'success': None, } # Set up all the triggers self.watcher.fileChanged.connect(self.file_trigger_accumulator) self.watcher.directoryChanged.connect(self.trigger) self.timer.timeout.connect(self.trigger) def startup(self): if self.config.get('is_active') == False: return False if self.config.get('mode') == MODE_TIMER: self.timer.setInterval(self.config.get('timer_seconds') * 1000) self.timer.start() elif self.config.get('mode') == MODE_WATCH_FILES: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watch_window = {} self.watcher.addPaths(self.config.get('watched_files')) elif self.config.get('mode') == MODE_WATCH_FOLDER: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watcher.addPath(self.config.get('watched_folder')) def shutdown(self): if self.config.get('mode') == MODE_TIMER: self.timer.stop() elif self.config.get('mode') == MODE_WATCH_FILES or self.config.get( 'mode') == MODE_WATCH_FOLDER: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watch_window = {} def load_notebook(self, filename): try: with open(filename) as f: nb = reads(f.read(), 'json') except: return None else: return nb def file_trigger_accumulator(self, f): # Accumulate triggers from changed files: if 3 files are specified to watch # we only want to fire once _all_ files have changed (within the given watch window) current_time = datetime.now() self.watch_window[f] = current_time # timestamp self.watch_window = { k: v for k, v in self.watch_window.items() if current_time - timedelta(seconds=self.config.get('watch_window')) < v } if set(self.watch_window.keys()) == set(self.watcher.files() + self.watcher.directories()): self.trigger() def trigger(self, e=None): if self.config.get('is_active') == False: return False # Filesystemwatcher triggered # Get the file and folder information; make available in vars object for run if self.lock is None: self.is_running = True self.update() self.lock = QTimer.singleShot( self.config.get('trigger_hold') * 1000, self.run) # Shutdown so we don't get in infinite loops self.shutdown() def run(self, vars={}): default_vars = { 'home': os.path.expanduser('~'), 'version': VERSION_STRING, } default_vars_and_config = dict( list(default_vars.items()) + list(self.config.config.items())) if self.runner == None: # Postpone init to trigger for responsiveness on add/remove self.runner = NotebookRunner(None, pylab=True, mpl_inline=True) if self.config.get('mode') == MODE_WATCH_FOLDER and self.config.get( 'iterate_watched_folder'): for (dirpath, dirnames, filenames) in os.walk(self.config.get('watched_folder')): break filenames = [ f for f in filenames if self.config.get('iterate_wildcard') in f ] logging.info('Watched folder contains %d files; looping' % len(filenames)) # Filenames contains the list of files in the folder else: filenames = [None] self.latest_run['timestamp'] = datetime.now() try: for f in filenames: now = datetime.now() current_vars = { 'datetime': now.strftime("%Y-%m-%d %H.%M.%S"), 'date': now.date().strftime("%Y-%m-%d"), 'time': now.time().strftime("%H.%M.%S"), 'filename': f, } vars = dict( list(default_vars_and_config.items()) + list(current_vars.items())) for nb_path in self.config.get('notebook_paths'): nb = self.load_notebook(nb_path) if nb: # Add currently running notebook path to vars vars['notebook_path'] = nb_path vars['notebook_filename'] = os.path.basename(nb_path) vars['output_path'] = self.config.get( 'output_path').format(**vars) parent_folder = os.path.dirname(vars['output_path']) if parent_folder: try: utils.mkdir_p(parent_folder) except: # Can't create folder self.latest_run['success'] = False raise self.run_notebook(nb, vars) else: raise NotebookNotFound(nb_path) except: self.latest_run['success'] = False traceback.print_exc() exctype, value = sys.exc_info()[:2] logging.error("%s\n%s\n%s" % (exctype, value, traceback.format_exc())) finally: self.is_running = False self.lock = None self.update() # Restart self.startup() def run_notebook(self, nb, vars={}): if len(nb['worksheets']) == 0: nb['worksheets'] = [NotebookNode({'cells': [], 'metadata': {}})] start = nb['worksheets'][0]['cells'] start.insert( 0, Struct( **{ 'cell_type': 'code', 'language': 'python', 'outputs': [], 'collapsed': False, 'prompt_number': -1, 'input': 'qtipy=%s' % vars, 'metadata': {}, })) self.runner.nb = nb try: self.runner.run_notebook() except: self.latest_run['success'] = False raise else: self.latest_run['success'] = True finally: ext = dict( html='html', slides='slides', latex='latex', markdown='md', python='py', rst='rst', ) output, resources = IPyexport( IPyexporter_map[self.config.get('output_format')], self.runner.nb) output_path = vars['output_path'] + 'notebook.%s' % ext[ self.config.get('output_format')] logging.info("Exporting updated notebook to %s" % output_path) with open(output_path, "w") as f: f.write(output) def update(self): global _w _w.viewer.update(self.index())
class Automaton(QStandardItem): def __init__(self, *args, **kwargs): super(Automaton, self).__init__(*args, **kwargs) self.setData(self, Qt.UserRole) self.watcher = QFileSystemWatcher() self.timer = QTimer() self.watch_window = {} self.latest_run = {} self.is_running = False self.config = ConfigManager() self.config.set_defaults({ 'mode': MODE_WATCH_FOLDER, 'is_active': True, 'trigger_hold': 1, 'notebook_paths': '', 'output_path': '{home}/{notebook_filename}_{datetime}_', 'output_format': 'html', 'watched_files': [], 'watched_folder': '', 'watch_window': 15, 'iterate_watched_folder': True, 'iterate_wildcard': '.csv', 'timer_seconds': 60, }) self.runner = None self.lock = None self.latest_run = { 'timestamp': None, 'success': None, } # Set up all the triggers self.watcher.fileChanged.connect(self.file_trigger_accumulator) self.watcher.directoryChanged.connect(self.trigger) self.timer.timeout.connect(self.trigger) def startup(self): if self.config.get('is_active') == False: return False if self.config.get('mode') == MODE_TIMER: self.timer.setInterval(self.config.get('timer_seconds') * 1000) self.timer.start() elif self.config.get('mode') == MODE_WATCH_FILES: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watch_window = {} self.watcher.addPaths(self.config.get('watched_files')) elif self.config.get('mode') == MODE_WATCH_FOLDER: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watcher.addPath(self.config.get('watched_folder')) def shutdown(self): if self.config.get('mode') == MODE_TIMER: self.timer.stop() elif self.config.get('mode') == MODE_WATCH_FILES or self.config.get('mode') == MODE_WATCH_FOLDER: current_paths = self.watcher.files() + self.watcher.directories() if current_paths: self.watcher.removePaths(current_paths) self.watch_window = {} def load_notebook(self, filename): try: with open(filename) as f: nb = reads(f.read(), 'json') except: return None else: return nb def file_trigger_accumulator(self, f): # Accumulate triggers from changed files: if 3 files are specified to watch # we only want to fire once _all_ files have changed (within the given watch window) current_time = datetime.now() self.watch_window[f] = current_time # timestamp self.watch_window = {k: v for k, v in self.watch_window.items() if current_time - timedelta(seconds=self.config.get('watch_window')) < v} if set(self.watch_window.keys()) == set(self.watcher.files() + self.watcher.directories()): self.trigger() def trigger(self, e=None): if self.config.get('is_active') == False: return False # Filesystemwatcher triggered # Get the file and folder information; make available in vars object for run if self.lock is None: self.is_running = True self.update() self.lock = QTimer.singleShot(self.config.get('trigger_hold') * 1000, self.run) # Shutdown so we don't get in infinite loops self.shutdown() def run(self, vars={}): default_vars = { 'home': os.path.expanduser('~'), 'version': VERSION_STRING, } default_vars_and_config = dict(list(default_vars.items()) + list(self.config.config.items())) if self.runner == None: # Postpone init to trigger for responsiveness on add/remove self.runner = NotebookRunner(None, pylab=True, mpl_inline=True) if self.config.get('mode') == MODE_WATCH_FOLDER and self.config.get('iterate_watched_folder'): for (dirpath, dirnames, filenames) in os.walk(self.config.get('watched_folder')): break filenames = [f for f in filenames if self.config.get('iterate_wildcard') in f] logging.info('Watched folder contains %d files; looping' % len(filenames)) # Filenames contains the list of files in the folder else: filenames = [None] self.latest_run['timestamp'] = datetime.now() try: for f in filenames: now = datetime.now() current_vars = { 'datetime': now.strftime("%Y-%m-%d %H.%M.%S"), 'date': now.date().strftime("%Y-%m-%d"), 'time': now.time().strftime("%H.%M.%S"), 'filename': f, } vars = dict(list(default_vars_and_config.items()) + list(current_vars.items())) for nb_path in self.config.get('notebook_paths'): nb = self.load_notebook(nb_path) if nb: # Add currently running notebook path to vars vars['notebook_path'] = nb_path vars['notebook_filename'] = os.path.basename(nb_path) vars['output_path'] = self.config.get('output_path').format(**vars) parent_folder = os.path.dirname(vars['output_path']) if parent_folder: try: utils.mkdir_p(parent_folder) except: # Can't create folder self.latest_run['success'] = False raise self.run_notebook(nb, vars) else: raise NotebookNotFound(nb_path) except: self.latest_run['success'] = False traceback.print_exc() exctype, value = sys.exc_info()[:2] logging.error("%s\n%s\n%s" % (exctype, value, traceback.format_exc())) finally: self.is_running = False self.lock = None self.update() # Restart self.startup() def run_notebook(self, nb, vars={}): if len(nb['worksheets']) == 0: nb['worksheets'] = [NotebookNode({'cells': [], 'metadata': {}})] start = nb['worksheets'][0]['cells'] start.insert(0, Struct(**{ 'cell_type': 'code', 'language': 'python', 'outputs': [], 'collapsed': False, 'prompt_number': -1, 'input': 'qtipy=%s' % vars, 'metadata': {}, })) self.runner.nb = nb try: self.runner.run_notebook() except: self.latest_run['success'] = False raise else: self.latest_run['success'] = True finally: ext = dict( html='html', slides='slides', latex='latex', markdown='md', python='py', rst='rst', ) output, resources = IPyexport(IPyexporter_map[self.config.get('output_format')], self.runner.nb) output_path = vars['output_path'] + 'notebook.%s' % ext[self.config.get('output_format')] logging.info("Exporting updated notebook to %s" % output_path) with open(output_path, "w") as f: f.write(output) def update(self): global _w _w.viewer.update(self.index())
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()