def setupUi(self, _TableExporerDialog): _TableExporerDialog.setObjectName(_fromUtf8("_TableExporerDialog")) _TableExporerDialog.resize(777, 531) self.gridLayout = QtGui.QGridLayout(_TableExporerDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.mz_plotter = MzPlottingWidget(_TableExporerDialog) self.mz_plotter.setObjectName(_fromUtf8("mz_plotter")) self.gridLayout.addWidget(self.mz_plotter, 0, 2, 3, 1) self.eic_plotter = EicPlottingWidget(_TableExporerDialog) self.eic_plotter.setObjectName(_fromUtf8("eic_plotter")) self.gridLayout.addWidget(self.eic_plotter, 0, 0, 3, 1) self.choose_spectra_widget = ChooseSpectraWidget(_TableExporerDialog) self.choose_spectra_widget.setObjectName( _fromUtf8("choose_spectra_widget")) self.gridLayout.addWidget(self.choose_spectra_widget, 1, 1, 1, 1) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 2, 1, 1, 1) self.integration_widget = IntegrationWidget(_TableExporerDialog) self.integration_widget.setObjectName(_fromUtf8("integration_widget")) self.gridLayout.addWidget(self.integration_widget, 0, 1, 1, 1) self.tableView = QtGui.QTableView(_TableExporerDialog) self.tableView.setObjectName(_fromUtf8("tableView")) self.gridLayout.addWidget(self.tableView, 3, 0, 1, 3) self.retranslateUi(_TableExporerDialog) QtCore.QMetaObject.connectSlotsByName(_TableExporerDialog)
class Ui__TableExporerDialog(object): def setupUi(self, _TableExporerDialog): _TableExporerDialog.setObjectName(_fromUtf8("_TableExporerDialog")) _TableExporerDialog.resize(777, 531) self.gridLayout = QtGui.QGridLayout(_TableExporerDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.mz_plotter = MzPlottingWidget(_TableExporerDialog) self.mz_plotter.setObjectName(_fromUtf8("mz_plotter")) self.gridLayout.addWidget(self.mz_plotter, 0, 2, 3, 1) self.eic_plotter = EicPlottingWidget(_TableExporerDialog) self.eic_plotter.setObjectName(_fromUtf8("eic_plotter")) self.gridLayout.addWidget(self.eic_plotter, 0, 0, 3, 1) self.choose_spectra_widget = ChooseSpectraWidget(_TableExporerDialog) self.choose_spectra_widget.setObjectName(_fromUtf8("choose_spectra_widget")) self.gridLayout.addWidget(self.choose_spectra_widget, 1, 1, 1, 1) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 2, 1, 1, 1) self.integration_widget = IntegrationWidget(_TableExporerDialog) self.integration_widget.setObjectName(_fromUtf8("integration_widget")) self.gridLayout.addWidget(self.integration_widget, 0, 1, 1, 1) self.tableView = QtGui.QTableView(_TableExporerDialog) self.tableView.setObjectName(_fromUtf8("tableView")) self.gridLayout.addWidget(self.tableView, 3, 0, 1, 3) self.retranslateUi(_TableExporerDialog) QtCore.QMetaObject.connectSlotsByName(_TableExporerDialog) def retranslateUi(self, _TableExporerDialog): _TableExporerDialog.setWindowTitle(_translate("_TableExporerDialog", "Dialog", None))
def setup_plot_widgets(self): self.peakmap_plotter = PeakMapPlottingWidget() self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.eic_plotter = EicPlottingWidget(with_range=False) self.mz_plotter = MzPlottingWidget() self.peakmap_plotter.set_logarithmic_scale(1) self.peakmap_plotter.set_gamma(self.gamma) self.eic_plotter.set_overall_range(self.rtmin, self.rtmax) self.mz_plotter.set_overall_range(self.mzmin, self.mzmax)
def setupPlottingWidgets(self): self.eic_plotter = EicPlottingWidget() self.mz_plotter = MzPlottingWidget() self.ts_plotter = TimeSeriesPlottingWidget() pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) pol.setVerticalStretch(5) self.eic_plotter.setSizePolicy(pol) self.mz_plotter.setSizePolicy(pol) self.ts_plotter.setSizePolicy(pol) self.spec_label = QLabel("plot spectra:") self.choose_spec = QListWidget() self.choose_spec.setFixedHeight(90) self.choose_spec.setSelectionMode(QAbstractItemView.ExtendedSelection)
class TableExplorer(EmzedDialog): def __init__(self, tables, offerAbortOption, parent=None, close_callback=None): super(TableExplorer, self).__init__(parent) # function which is called when window is closed. the arguments passed are boolean # flags indication for every table if it was modified: self.close_callback = close_callback # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowFlags(Qt.Window) self.offerAbortOption = offerAbortOption self.models = [ TableModel.table_model_for(table, parent=self) for table in tables ] self.model = None self.tableView = None self.hadFeatures = None self.setupWidgets() self.setupLayout() self.connectSignals() sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setSizePolicy(sizePolicy) self.setSizeGripEnabled(True) self.setupViewForTable(0) def reject(self): super(TableExplorer, self).reject() modified = [len(m.actions) > 0 for m in self.models] if self.close_callback is not None: try: self.close_callback(*modified) except Exception: import traceback traceback.print_exc() def keyPressEvent(self, e): if e.key() != Qt.Key_Escape: super(TableExplorer, self).keyPressEvent(e) def setupWidgets(self): self.setupMenuBar() self.setupTableViews() self.setupPlottingAndIntegrationWidgets() self.setupToolWidgets() if self.offerAbortOption: self.setupAcceptButtons() def setupPlottingAndIntegrationWidgets(self): self.setupPlottingWidgets() self.setupIntegrationWidgets() def setupMenuBar(self): self.menubar = QMenuBar(self) menu = self.buildEditMenu() self.menubar.addMenu(menu) self.chooseTableActions = [] if len(self.models) > 1: menu = self.buildChooseTableMenu() self.menubar.addMenu(menu) def buildEditMenu(self): self.undoAction = QAction("Undo", self) self.undoAction.setShortcut(QKeySequence("Ctrl+Z")) self.redoAction = QAction("Redo", self) self.redoAction.setShortcut(QKeySequence("Ctrl+Y")) menu = QMenu("Edit", self.menubar) menu.addAction(self.undoAction) menu.addAction(self.redoAction) return menu def setupTableViews(self): self.tableViews = [] self.filterWidgets = [] self.filters_enabled = False for i, model in enumerate(self.models): self.tableViews.append(self.setupTableViewFor(model)) self.filterWidgets.append(self.setupFilterWidgetFor(model)) def setupFilterWidgetFor(self, model): t = model.table w = FilterCriteriaWidget(self) w.configure(t) w.LIMITS_CHANGED.connect(model.limits_changed) return w def set_delegates(self): bd = ButtonDelegate(self.tableView, self) types = self.model.table.getColTypes() for i, j in self.model.widgetColToDataCol.items(): if types[j] == CallBack: self.tableView.setItemDelegateForColumn(i, bd) def remove_delegates(self): types = self.model.table.getColTypes() for i, j in self.model.widgetColToDataCol.items(): if types[j] in (bool, CallBack): self.tableView.setItemDelegateForColumn(i, None) def setupTableViewFor(self, model): tableView = EmzedTableView(self) tableView.setModel(model) tableView.horizontalHeader().setResizeMode(QHeaderView.Interactive) tableView.horizontalHeader().setMovable(1) pol = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) tableView.setSizePolicy(pol) tableView.setVisible(False) # before filling the table, disabling sorting accelerates table # construction, sorting is enabled in TableView.showEvent, which is # called after construction tableView.setSortingEnabled(False) return tableView def buildChooseTableMenu(self): menu = QMenu("Choose Table", self.menubar) for i, model in enumerate(self.models): action = QAction(" [%d]: %s" % (i, model.getTitle()), self) menu.addAction(action) self.chooseTableActions.append(action) return menu def setupPlottingWidgets(self): self.eic_plotter = EicPlottingWidget() self.mz_plotter = MzPlottingWidget() self.ts_plotter = TimeSeriesPlottingWidget() pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) pol.setVerticalStretch(5) self.eic_plotter.setSizePolicy(pol) self.mz_plotter.setSizePolicy(pol) self.ts_plotter.setSizePolicy(pol) self.spec_label = QLabel("plot spectra:") self.choose_spec = QListWidget() self.choose_spec.setFixedHeight(90) self.choose_spec.setSelectionMode(QAbstractItemView.ExtendedSelection) def setupIntegrationWidgets(self): self.integration_widget = IntegrationWidget(self) names = [name for (name, __) in algorithm_configs.peakIntegrators] self.integration_widget.set_integration_methods(names) def setupToolWidgets(self): self.chooseGroubLabel = QLabel("Expand selection:", parent=self) self.chooseGroupColumn = QComboBox(parent=self) self.chooseGroupColumn.setMinimumWidth(150) self.choose_visible_columns_button = button("Visible columns") # we introduced this invisible button else qt makes the filter_on_button always # active on mac osx, that means that as soon we press enter in one of the filter # widgets the button is triggered ! # problem does not occur on windows. # self.dummy = QPushButton() # self.dummy.setVisible(False) self.filter_on_button = button("Filter rows") self.sort_label = QLabel("sort by:", parent=self) self.sort_fields_widgets = [] self.sort_order_widgets = [] for i in range(3): w = QComboBox(parent=self) w.setMinimumWidth(100) self.sort_fields_widgets.append(w) w = QComboBox(parent=self) w.addItems(["asc", "desc"]) w.setMaximumWidth(60) self.sort_order_widgets.append(w) self.restrict_to_filtered_button = button("Restrict to filter result") self.remove_filtered_button = button("Remove filter result") self.export_table_button = button("Export table") self.restrict_to_filtered_button.setEnabled(False) self.remove_filtered_button.setEnabled(False) def setupAcceptButtons(self): self.okButton = button("Ok", parent=self) self.abortButton = button("Abort", parent=self) self.result = 1 # default for closing def create_additional_widgets(self, vsplitter): # so that derived classes can add widgets above table ! return None def connect_additional_widgets(self, model): pass def setupLayout(self): vlayout = QVBoxLayout() self.setLayout(vlayout) vsplitter = QSplitter() vsplitter.setOrientation(Qt.Vertical) vsplitter.setOpaqueResize(False) vsplitter.addWidget(self.menubar) # 0 vsplitter.addWidget(self.layoutPlottingAndIntegrationWidgets()) # 1 extra = self.create_additional_widgets(vsplitter) if extra is not None: vsplitter.addWidget(extra) self.table_view_container = QStackedWidget(self) for view in self.tableViews: self.table_view_container.addWidget(view) vsplitter.addWidget(self.table_view_container) # 2 vsplitter.addWidget(self.layoutToolWidgets()) # 3 # self.filter_widgets_box = QScrollArea(self) self.filter_widgets_container = QStackedWidget(self) for w in self.filterWidgets: self.filter_widgets_container.addWidget(w) self.filter_widgets_container.setVisible(False) self.filter_widgets_container.setFrameStyle(QFrame.Plain) vsplitter.addWidget(self.filter_widgets_container) di = 1 if extra is not None else 0 vsplitter.setStretchFactor(0, 1.0) # menubar vsplitter.setStretchFactor(1, 3.0) # plots + integration # vsplitter.setStretchFactor(2, 1.0) # ms2 spec chooser vsplitter.setStretchFactor(2 + di, 5.0) # table vsplitter.setStretchFactor(3 + di, 1.0) # tools vsplitter.setStretchFactor(4 + di, 2.0) # filters vlayout.addWidget(vsplitter) if self.offerAbortOption: vlayout.addLayout(self.layoutButtons()) def layoutButtons(self): hbox = QHBoxLayout() hbox.addWidget(self.abortButton) hbox.setAlignment(self.abortButton, Qt.AlignVCenter) hbox.addWidget(self.okButton) hbox.setAlignment(self.okButton, Qt.AlignVCenter) return hbox def enable_integration_widgets(self, flag=True): self.integration_widget.setEnabled(flag) def enable_spec_chooser_widgets(self, flag=True): self.spec_label.setEnabled(flag) self.choose_spec.setEnabled(flag) def layoutPlottingAndIntegrationWidgets(self): hsplitter = QSplitter() hsplitter.setOpaqueResize(False) middleLayout = QVBoxLayout() middleLayout.setSpacing(5) middleLayout.setMargin(5) middleLayout.addWidget(self.integration_widget) middleLayout.addStretch() middleLayout.addWidget(self.spec_label) middleLayout.addWidget(self.choose_spec) middleLayout.addStretch() middleLayout.addStretch() self.middleFrame = QFrame() self.middleFrame.setLayout(middleLayout) self.middleFrame.setMaximumWidth(250) plot_widgets = self.setup_plot_widgets([ self.ts_plotter, self.eic_plotter, self.middleFrame, self.mz_plotter ]) for widget in plot_widgets: hsplitter.addWidget(widget) return hsplitter def setup_plot_widgets(self, widgets): return widgets def layoutToolWidgets(self): frame = QFrame(parent=self) layout = QGridLayout() row = 0 column = 0 layout.addWidget(self.chooseGroubLabel, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.chooseGroupColumn, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.choose_visible_columns_button, row, column, alignment=Qt.AlignLeft) h_layout = QHBoxLayout() h_layout.addWidget(self.sort_label) for sort_field_w, sort_order_w in zip(self.sort_fields_widgets, self.sort_order_widgets): h_layout.addWidget(sort_field_w) h_layout.addWidget(sort_order_w) column += 1 layout.addLayout(h_layout, row, column, alignment=Qt.AlignLeft) row = 1 column = 0 layout.addWidget(self.filter_on_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.restrict_to_filtered_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.remove_filtered_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.export_table_button, row, column, alignment=Qt.AlignLeft) column += 1 # layout.addWidget(self.dummy, row, column, alignment=Qt.AlignLeft) layout.setColumnStretch(column, 1) frame.setLayout(layout) return frame def set_window_title(self, n_rows_total, n_rows_visible): model_title = self.model.getTitle() title = "%d out of %d rows from %s" % (n_rows_visible, n_rows_total, model_title) self.setWindowTitle(title) def setup_model_dependent_look(self): hasFeatures = self.model.hasFeatures() isIntegrated = self.model.isIntegrated() hasEIC = self.model.hasEIC() hasTimeSeries = self.model.hasTimeSeries() hasSpectra = self.model.hasSpectra() self.eic_only_mode = hasEIC and not hasFeatures # includes: not isIntegrated ! self.has_chromatograms = hasFeatures self.allow_integration = isIntegrated and self.model.implements( "integrate") self.has_time_series = hasTimeSeries self.has_spectra = hasSpectra self.eic_plotter.setVisible(self.eic_only_mode or self.has_chromatograms) self.eic_plotter.enable_range(not self.eic_only_mode) self.mz_plotter.setVisible(self.has_chromatograms or self.has_spectra) self.ts_plotter.setVisible(self.has_time_series) self.enable_integration_widgets(self.allow_integration) self.enable_spec_chooser_widgets(self.has_spectra or self.has_chromatograms) self.enable_integration_widgets(self.allow_integration) self.enable_spec_chooser_widgets(self.has_chromatograms or self.has_spectra) self.middleFrame.setVisible(self.allow_integration or self.has_spectra) self.choose_spec.clear() @protect_signal_handler def handleClick(self, index, model): content = model.data(index) if isUrl(content): QDesktopServices.openUrl(QUrl(content)) @protect_signal_handler def cell_pressed(self, index): self.tableView.selectRow(index.row()) self.tableView.verticalHeader().sectionClicked.emit(index.row()) def connectSignals(self): for i, action in enumerate(self.chooseTableActions): handler = lambda i=i: self.setupViewForTable(i) handler = protect_signal_handler(handler) self.menubar.connect(action, SIGNAL("triggered()"), handler) for view in self.tableViews: vh = view.verticalHeader() vh.setContextMenuPolicy(Qt.CustomContextMenu) vh.customContextMenuRequested.connect( self.openContextMenuVerticalHeader) vh.sectionClicked.connect(self.rowClicked) hh = view.horizontalHeader() hh.setContextMenuPolicy(Qt.CustomContextMenu) hh.customContextMenuRequested.connect( self.openContextMenuHorizontalHeader) model = view.model() handler = lambda idx, model=model: self.handleClick(idx, model) handler = protect_signal_handler(handler) model.ACTION_LIST_CHANGED.connect(self.updateMenubar) view.clicked.connect(handler) view.doubleClicked.connect(self.handle_double_click) view.pressed.connect(self.cell_pressed) self.connect_additional_widgets(model) self.integration_widget.TRIGGER_INTEGRATION.connect(self.do_integrate) self.choose_spec.itemSelectionChanged.connect(self.spectrumChosen) if self.offerAbortOption: self.connect(self.okButton, SIGNAL("clicked()"), self.ok) self.connect(self.abortButton, SIGNAL("clicked()"), self.abort) self.choose_visible_columns_button.clicked.connect( self.choose_visible_columns) self.filter_on_button.clicked.connect(self.filter_toggle) self.remove_filtered_button.clicked.connect(self.remove_filtered) self.restrict_to_filtered_button.clicked.connect( self.restrict_to_filtered) self.export_table_button.clicked.connect(self.export_table) for sort_field_w in self.sort_fields_widgets: sort_field_w.currentIndexChanged.connect(self.sort_fields_changed) for sort_order_w in self.sort_order_widgets: sort_order_w.currentIndexChanged.connect(self.sort_fields_changed) self.eic_plotter.SELECTED_RANGE_CHANGED.connect( self.eic_selection_changed) @protect_signal_handler def sort_fields_changed(self, __): sort_data = [(str(f0.currentText()), str(f1.currentText())) for f0, f1 in zip(self.sort_fields_widgets, self.sort_order_widgets)] sort_data = [(f0, f1) for (f0, f1) in sort_data if f0 != "-" and f0 != ""] if sort_data: self.model.sort_by(sort_data) main_name, main_order = sort_data[0] idx = self.model.widget_col(main_name) if idx is not None: header = self.tableView.horizontalHeader() header.blockSignals(True) header.setSortIndicator( idx, Qt.AscendingOrder if main_order.startswith("asc") else Qt.DescendingOrder) header.blockSignals(False) @protect_signal_handler def filter_toggle(self, *a): self.filters_enabled = not self.filters_enabled for model in self.models: model.setFiltersEnabled(self.filters_enabled) self.filter_widgets_container.setVisible(self.filters_enabled) self.restrict_to_filtered_button.setEnabled(self.filters_enabled) self.remove_filtered_button.setEnabled(self.filters_enabled) if self.filters_enabled: # we add spaces becaus on mac the text field cut when rendered self.filter_on_button.setText("Disable row filtering") self.export_table_button.setText("Export filtered") else: # we add spaces becaus on mac the text field cut when rendered self.filter_on_button.setText("Enable row filtering") self.export_table_button.setText("Export table") @protect_signal_handler def choose_visible_columns(self, *a): self.remove_delegates() col_names, is_currently_visible = self.model.columnames_with_visibility( ) if not col_names: return # zip, sort and unzip then: col_names, is_currently_visible = zip( *sorted(zip(col_names, is_currently_visible))) dlg = ColumnMultiSelectDialog(col_names, is_currently_visible) dlg.exec_() if dlg.column_settings is None: return hide_names = [ n for (n, col_idx, visible) in dlg.column_settings if not visible ] self.update_hidden_columns(hide_names) self.model.save_preset_hidden_column_names() def update_hidden_columns(self, hidden_names): self.model.hide_columns(hidden_names) self.set_delegates() self.setup_choose_group_column_widget(hidden_names) self.setup_sort_fields(hidden_names) self.current_filter_widget.hide_filters(hidden_names) self.model.table.meta["hide_in_explorer"] = hidden_names self.setup_sort_fields(hidden_names) @protect_signal_handler def remove_filtered(self, *a): self.model.remove_filtered() @protect_signal_handler def restrict_to_filtered(self, *a): self.model.restrict_to_filtered() @protect_signal_handler def export_table(self, *a): path = askForSave(extensions=["csv"]) if path is not None: t = self.model.extract_visible_table() if os.path.exists(path): os.remove(path) t.storeCSV(path, as_printed=True) @protect_signal_handler def handle_double_click(self, idx): row, col = self.model.table_index(idx) cell_value = self.model.cell_value(idx) extra_args = dict() if isinstance(cell_value, PeakMap): col_name = self.model.column_name(idx) if "__" in col_name: prefix, __, __ = col_name.partition("__") full_prefix = prefix + "__" else: full_prefix = "" for n in "rtmin", "rtmax", "mzmin", "mzmax", "mz": full_n = full_prefix + n value = self.model.table.getValue(self.model.table.rows[row], full_n, None) extra_args[n] = value if extra_args["mzmin"] is None and extra_args["mzmax"] is None: mz = extra_args["mz"] if mz is not None: mzmin = mz - 10 * 1e-6 * mz # -10 ppm mzmax = mz + 10 * 1e-6 * mz # +19 ppm extra_args["mzmin"] = mzmin extra_args["mzmax"] = mzmax del extra_args["mz"] insp = inspector(cell_value, modal=False, parent=self, **extra_args) if insp is not None: insp() def disconnectModelSignals(self): self.disconnect( self.model, SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"), self.dataChanged) self.model.modelReset.disconnect(self.handle_model_reset) self.menubar.disconnect( self.undoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.undoLastAction)) self.menubar.disconnect( self.redoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.redoLastAction)) def connectModelSignals(self): self.connect( self.model, SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"), self.dataChanged) self.model.modelReset.connect(self.handle_model_reset) self.menubar.connect(self.undoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.undoLastAction)) self.menubar.connect(self.redoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.redoLastAction)) self.model.VISIBLE_ROWS_CHANGE.connect(self.set_window_title) self.model.SORT_TRIGGERED.connect(self.sort_by_click_in_header) @protect_signal_handler def sort_by_click_in_header(self, name, is_ascending): for f in self.sort_fields_widgets: f.blockSignals(True) for f in self.sort_order_widgets: f.blockSignals(True) main_widget = self.sort_fields_widgets[0] idx = main_widget.findText(name) main_widget.setCurrentIndex(idx) self.sort_order_widgets[0].setCurrentIndex(bool(is_ascending)) for i in range(1, len(self.sort_fields_widgets)): self.sort_fields_widgets[i].setCurrentIndex(0) for f in self.sort_fields_widgets: f.blockSignals(False) for f in self.sort_order_widgets: f.blockSignals(False) def group_column_selected(self, idx): self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection) def updateMenubar(self, undoInfo, redoInfo): self.undoAction.setEnabled(undoInfo is not None) self.redoAction.setEnabled(redoInfo is not None) if undoInfo: self.undoAction.setText("Undo: %s" % undoInfo) if redoInfo: self.redoAction.setText("Redo: %s" % redoInfo) def setupViewForTable(self, i): for j, action in enumerate(self.chooseTableActions): txt = unicode(action.text()) # QString -> Python unicode if txt.startswith("*"): txt = " " + txt[1:] action.setText(txt) if i == j: action.setText("*" + txt[1:]) self.table_view_container.setCurrentIndex(i) self.filter_widgets_container.setCurrentIndex(i) if self.model is not None: self.disconnectModelSignals() self.model = self.models[i] self.current_filter_widget = self.filterWidgets[i] self.tableView = self.tableViews[i] hidden = self.model.table.meta.get("hide_in_explorer", ()) self.update_hidden_columns(hidden) try: shown = self.model.load_preset_hidden_column_names() hidden = list(set(self.model.table.getColNames()) - shown) self.update_hidden_columns(hidden) except Exception: pass self.setup_model_dependent_look() if self.model.implements("setNonEditable"): self.model.setNonEditable("method", ["area", "rmse", "method", "params"]) if self.model.implements("addNonEditable"): for col_name in self.model.table.getColNames(): t = self.model.table.getColType(col_name) if t in (list, tuple, object, dict, set) or t is None or has_inspector(t): self.model.addNonEditable(col_name) mod = self.model postfixes = mod.table.supportedPostfixes(mod.integrationColNames()) self.integration_widget.set_postfixes(postfixes) self.setup_choose_group_column_widget(hidden) self.setup_sort_fields(hidden) self.connectModelSignals() self.updateMenubar(None, None) self.set_window_title(len(self.model.table), len(self.model.table)) def setup_choose_group_column_widget(self, hidden_names): before = None if self.chooseGroupColumn.currentIndex() >= 0: before = str(self.chooseGroupColumn.currentText()) self.chooseGroupColumn.clear() t = self.model.table candidates = [ n for (n, f) in zip(t.getColNames(), t.getColFormats()) if f is not None ] visible_names = [n for n in candidates if n not in hidden_names] all_choices = ["- manual multi select -"] + sorted(visible_names) self.chooseGroupColumn.addItems(all_choices) if before is not None and before in all_choices: idx = all_choices.index(before) self.chooseGroupColumn.setCurrentIndex(idx) def setup_sort_fields(self, hidden_names): before = [] for field in self.sort_fields_widgets: if field.currentIndex() >= 0: before.append(str(field.currentText())) else: before.append(None) t = self.model.table candidates = [ n for (n, f) in zip(t.getColNames(), t.getColFormats()) if f is not None ] visible_names = [n for n in candidates if n not in hidden_names] all_choices = ["-"] + visible_names for field in self.sort_fields_widgets: field.clear() field.addItems(all_choices) for choice_before, field in zip(before, self.sort_fields_widgets): if choice_before is not None and choice_before in all_choices: idx = all_choices.index(choice_before) field.setCurrentIndex(idx) @protect_signal_handler def handle_model_reset(self): for name in self.model.table.getColNames(): self.current_filter_widget.update(name) def reset_sort_fields(self): for field in self.sort_fields_widgets: field.setCurrentIndex(0) @protect_signal_handler def dataChanged(self, ix1, ix2, src): minr, maxr = sorted((ix1.row(), ix2.row())) minc, maxc = sorted((ix1.column(), ix2.column())) for r in range(minr, maxr + 1): for c in range(minc, maxc + 1): idx = self.model.createIndex(r, c) self.tableView.update(idx) minc = self.model.widgetColToDataCol[minc] maxc = self.model.widgetColToDataCol[maxc] minr = self.model.widgetRowToDataRow[minr] maxr = self.model.widgetRowToDataRow[maxr] for name in self.model.table.getColNames()[minc:maxc + 1]: self.current_filter_widget.update(name) if self.has_chromatograms: # minr, maxr = sorted((ix1.row(), ix2.row())) if any(minr <= index <= maxr for index in self.model.selected_data_rows): if isinstance(src, IntegrateAction): self.plot_chromatograms(reset=False) else: self.plot_chromatograms(reset=True) self.reset_sort_fields() @protect_signal_handler def abort(self): self.result = 1 self.close() @protect_signal_handler def ok(self): self.result = 0 self.close() @protect_signal_handler def openContextMenuHorizontalHeader(self, point): widget_col_index = self.tableView.horizontalHeader().logicalIndexAt( point) data_col_index = self.model.widgetColToDataCol[widget_col_index] if self.model.column_type(widget_col_index) is bool: menu = QMenu() check_all_action = menu.addAction("check all") uncheck_all_action = menu.addAction("uncheck all") appearAt = self.tableView.horizontalHeader().mapToGlobal(point) chosen = menu.exec_(appearAt) if chosen == check_all_action: self.run_blocked(self.model.set_all, (widget_col_index, True)) if chosen == uncheck_all_action: self.run_blocked(self.model.set_all, (widget_col_index, False)) def run_blocked(self, function, args): @pyqtSlot() def doit(): self.setEnabled(False) self.setCursor(Qt.WaitCursor) try: function(*args) except Exception: pass self.setEnabled(True) self.setCursor(Qt.ArrowCursor) print(QTimer.singleShot(0, doit)) @protect_signal_handler def openContextMenuVerticalHeader(self, point): index = self.tableView.verticalHeader().logicalIndexAt(point) menu = QMenu() if self.model.implements("cloneRow"): cloneAction = menu.addAction("Clone row") else: cloneAction = None if self.model.implements("removeRows"): removeAction = menu.addAction("Delete row") else: removeAction = None undoInfo = self.model.infoLastAction() redoInfo = self.model.infoRedoAction() if undoInfo is not None: undoAction = menu.addAction("Undo %s" % undoInfo) if redoInfo is not None: redoAction = menu.addAction("Redo %s" % redoInfo) appearAt = self.tableView.verticalHeader().mapToGlobal(point) choosenAction = menu.exec_(appearAt) if choosenAction == removeAction: self.model.removeRows([index]) elif choosenAction == cloneAction: self.model.cloneRow(index) elif undoInfo is not None and choosenAction == undoAction: self.model.undoLastAction() elif redoInfo is not None and choosenAction == redoAction: self.model.redoLastAction() @protect_signal_handler def do_integrate(self, method, postfix): # QString -> Python str: method = str(method) postfix = str(postfix) rtmin, rtmax = self.eic_plotter.get_range_selection_limits() for data_row_idx in self.model.selected_data_rows: self.model.integrate(data_row_idx, postfix, method, rtmin, rtmax) @protect_signal_handler def rowClicked(self, widget_row_idx): self.select_rows_in_group(widget_row_idx) self.setup_spectrum_chooser() if self.eic_only_mode: self.plot_eics_only() if self.has_chromatograms: self.plot_chromatograms() if self.has_time_series: self.plot_time_series() def select_rows_in_group(self, widget_row_idx): group_by_idx = self.chooseGroupColumn.currentIndex() # first entry is "manual selection" if group_by_idx == 0: to_select = [ idx.row() for idx in self.tableView.selectionModel().selectedRows() ] self.model.set_selected_data_rows(to_select) return col_name = str(self.chooseGroupColumn.currentText()) to_select = self.model.rows_with_same_value(col_name, widget_row_idx) N = 200 if len(to_select) > N: QMessageBox.warning( self, "Warning", "multiselect would mark %d lines. " "reduced number of lines to %d" % (len(to_select), N)) to_select = to_select[:N] # expand selection mode_before = self.tableView.selectionMode() scrollbar_before = self.tableView.verticalScrollBar().value() self.tableView.setSelectionMode(QAbstractItemView.MultiSelection) for i in to_select: if i != widget_row_idx: # avoid "double click !" wich de-selects current row self.tableView.selectRow(i) self.tableView.setSelectionMode(mode_before) self.tableView.verticalScrollBar().setValue(scrollbar_before) self.model.set_selected_data_rows(to_select) def setup_spectrum_chooser(self): self.choose_spec.clear() spectra = [] labels = [] if self.has_chromatograms: labels.append("spectra from peak") spectra.append( None ) # place holder as ms1 spec is computed from peakmap on demand ! self.first_spec_in_choser_is_ms1 = True else: self.first_spec_in_choser_is_ms1 = False num_extra_spectra = 0 for idx in self.model.selected_data_rows: pf, s = self.model.getMS2Spectra(idx) for pfi, si in zip(pf, s): if si is not None: for sii in si: label = "spectra%s rt=%.2fm" % (pfi, sii.rt / 60.0) if sii.precursors: mz, I = sii.precursors[0] label += " pre=(%.5f, %.2e)" % (mz, I) labels.append(label) spectra.append(sii) num_extra_spectra += 1 self.spectra_listed_in_chooser = spectra self.choose_spec.setVisible(len(spectra) > 0) for label in labels: self.choose_spec.addItem(label) if self.first_spec_in_choser_is_ms1: self.choose_spec.setCurrentRow(0) else: self.choose_spec.selectAll() def plot_time_series(self): rtmin, rtmax, time_series = time_series_curves(self.model) ts_configs = configsForTimeSeries(time_series) self.ts_plotter.del_all_items() self.ts_plotter.reset() self.ts_plotter.add_time_series(time_series, ts_configs) self.ts_plotter.replot() def plot_eics_only(self): if self.eic_only_mode: rtmin, rtmax, curves = eic_curves(self.model) configs = configsForEics(curves) self.eic_plotter.reset() self.eic_plotter.add_eics(curves, configs=configs, labels=None) self.eic_plotter.replot() def plot_chromatograms(self, reset=True): self.eic_plotter.del_all_items() rtmin, rtmax, mzmin, mzmax, curves, fit_shapes = chromatograms( self.model, self.allow_integration) configs = configsForEics(curves) self.eic_plotter.add_eics(curves, configs=configs) for (chromo, baseline), config in zip(fit_shapes, configs): if chromo is None: continue rts, iis = chromo if baseline is None: baseline = 0.0 eic_color = config["color"] color = turn_light(eic_color) self.eic_plotter.add_eic_filled(rts, iis, baseline, color) # allrts are sorted ! if rtmin is not None and rtmax is not None: w = rtmax - rtmin if w == 0: w = 30.0 # seconds if reset: self.eic_plotter.set_rt_axis_limits(rtmin - w, rtmax + w) self.eic_plotter.set_range_selection_limits(rtmin, rtmax) self.eic_plotter.reset_intensity_limits(fac=1.1, rtmin=rtmin - w, rtmax=rtmax + w) self.eic_plotter.replot() @protect_signal_handler def spectrumChosen(self): spectra = [ self.spectra_listed_in_chooser[idx.row()] for idx in self.choose_spec.selectedIndexes() ] labels = [ str(item.data(0).toString()) for item in self.choose_spec.selectedItems() ] ms2_data = [(l, s) for (l, s) in zip(labels, spectra) if s is not None and l is not None] if ms2_data: labels, spectra = zip( *ms2_data) # unzip, works only if ms2_data is not empty self.mz_plotter.plot_spectra([s.peaks for s in spectra], labels) self.mz_plotter.resetAxes() self.mz_plotter.replot() else: self.plot_ms1_spectra() def eic_selection_changed(self, rtmin, rtmax): if not self.first_spec_in_choser_is_ms1: return self.choose_spec.setCurrentRow(0) peakmaps = [ pm for idx in self.model.selected_data_rows for pm in self.model.getPeakmaps(idx) ] windows = [] for idx in self.model.selected_data_rows: for (__, __, mzmin, mzmax) in self.model.getEICWindows(idx): window = (rtmin, rtmax, mzmin, mzmax) windows.append(window) self.plot_spectra_from_peakmaps(peakmaps, windows) def plot_ms1_spectra(self): peakmaps = [ pm for idx in self.model.selected_data_rows for pm in self.model.getPeakmaps(idx) ] windows = [ w for idx in self.model.selected_data_rows for w in self.model.getEICWindows(idx) ] self.plot_spectra_from_peakmaps(peakmaps, windows) def plot_ms2_spectra(self, spectra, labels): self.mz_plotter.plot_spectra([s.peaks for s in spectra], labels) self.mz_plotter.resetAxes() self.mz_plotter.replot() def plot_spectra_from_peakmaps(self, peakmaps, windows): if not peakmaps or not windows: return data = [] mzs = [] for (rtmin, rtmax, mzmin, mzmax), pm in zip(windows, peakmaps): mzs.append(mzmin) mzs.append(mzmax) data.append((pm, rtmin, rtmax, mzmin, mzmax, 3000)) mzmin = min(mzs) mzmax = max(mzs) configs = configsForSpectra(len(peakmaps)) postfixes = self.model.table.supportedPostfixes( self.model.eicColNames()) titles = map(repr, postfixes) self.mz_plotter.plot_peakmaps(data, configs, titles if len(titles) > 1 else None) self.mz_plotter.reset_mz_limits(mzmin, mzmax) self.mz_plotter.replot()
class PeakMapExplorer(EmzedDialog): def __init__(self, ok_rows_container=[], parent=None): super(PeakMapExplorer, self).__init__(parent) self.setWindowFlags(Qt.Window) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.ok_rows = ok_rows_container self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowFlags(Qt.Window) self.gamma = 3.0 self.last_used_directory_for_load = None self.last_used_directory_for_save = None self.history = History() def keyPressEvent(self, e): # avoid closing of dialog when Esc key pressed: if e.key() != Qt.Key_Escape: return super(PeakMapExplorer, self).keyPressEvent(e) def setWindowTitle(self): if self.peakmap2 is None: title = os.path.basename(self.peakmap.meta.get("source", "")) else: p1 = os.path.basename(self.peakmap.meta.get("source", "")) p2 = os.path.basename(self.peakmap2.meta.get("source", "")) title = "yellow=%s, blue=%s" % (p1, p2) super(PeakMapExplorer, self).setWindowTitle(title) def setup(self, peakmap, peakmap2=None, table=None): self.table = table def collect_precursor_mz(pm): for s in pm: if s.precursors: if s.msLevel > 1: yield s.precursors[0][0] self.ms_levels = set(peakmap.getMsLevels()) self.precursor_mz = set(collect_precursor_mz(peakmap)) if peakmap2 is not None: self.ms_levels &= set(peakmap2.getMsLevels()) self.precursor_mz &= set(collect_precursor_mz(peakmap2)) self.ms_levels = sorted(self.ms_levels) self.precursor_mz = sorted(self.precursor_mz) self.setup_table_widgets() self.setup_input_widgets() self.history_list = QComboBox(self) self.setup_ms2_widgets() self.full_pm = peakmap self.full_pm2 = peakmap2 self.dual_mode = self.full_pm2 is not None self.current_ms_level = self.ms_levels[0] self.process_peakmap(self.current_ms_level) self.rtmin, self.rtmax, self.mzmin, self.mzmax = get_range( self.peakmap, self.peakmap2) self.setup_plot_widgets() self.setup_menu_bar() self.setup_layout() self.connect_signals_and_slots() self.setup_initial_values() self.plot_peakmap() def setup_ms2_widgets(self): self.spectra_selector_widget.set_data(self.ms_levels, self.precursor_mz) def setup_table_widgets(self): if self.table is not None: self.table_widget = create_table_widget(self.table, self) self.select_all_peaks = QPushButton("Select all peaks", self) self.unselect_all_peaks = QPushButton("Unselect all peaks", self) self.done_button = QPushButton("Done", self) def setup_menu_bar(self): self.menu_bar = QMenuBar(self) menu = QMenu("Peakmap Explorer", self.menu_bar) self.menu_bar.addMenu(menu) if not self.dual_mode: self.load_action = QAction("Load Peakmap", self) self.load_action.setShortcut(QKeySequence("Ctrl+L")) self.load_action2 = None menu.addAction(self.load_action) else: self.load_action = QAction("Load Yellow Peakmap", self) self.load_action2 = QAction("Load Blue Peakmap", self) menu.addAction(self.load_action) menu.addAction(self.load_action2) self.save_action = QAction("Save selected range as image", self) self.save_action.setShortcut(QKeySequence("Ctrl+S")) menu.addAction(self.save_action) menu = QMenu("Help", self.menu_bar) self.help_action = QAction("Help", self) self.help_action.setShortcut(QKeySequence("F1")) menu.addAction(self.help_action) self.menu_bar.addMenu(menu) def process_peakmap(self, ms_level, pre_mz_min=None, pre_mz_max=None): peakmap = self.full_pm.filter(lambda s: s.msLevel == ms_level) if ms_level > 1 and pre_mz_min is not None: peakmap = peakmap.filter( lambda s: s.precursors[0][0] >= pre_mz_min) if ms_level > 1 and pre_mz_max is not None: peakmap = peakmap.filter( lambda s: s.precursors[0][0] <= pre_mz_max) if self.full_pm2 is not None: peakmap2 = self.full_pm2.filter(lambda s: s.msLevel == ms_level) self.peakmap = peakmap if self.dual_mode: self.peakmap2 = peakmap2 else: self.peakmap2 = None for i, msl in enumerate(self.ms_levels): if msl == ms_level: pass # TODO self.ms_level.setCurrentIndex(i) self.setWindowTitle() def setup_initial_values(self): imax = self.peakmap_plotter.get_total_imax() self.image_scaling_widget.set_max_intensity(imax) self.image_scaling_widget.set_gamma(self.gamma) self.view_range_widget.set_view_range(self.rtmin, self.rtmax, self.mzmin, self.mzmax) def setup_input_widgets(self): self.image_scaling_widget = ImageScalingWidget(self) self.spectra_selector_widget = SpectraSelectorWidget(self) self.view_range_widget = ViewRangeWidget(self) def setup_plot_widgets(self): self.peakmap_plotter = PeakMapPlottingWidget() self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.eic_plotter = EicPlottingWidget(with_range=False) self.mz_plotter = MzPlottingWidget() self.peakmap_plotter.set_logarithmic_scale(1) self.peakmap_plotter.set_gamma(self.gamma) self.eic_plotter.set_overall_range(self.rtmin, self.rtmax) self.mz_plotter.set_overall_range(self.mzmin, self.mzmax) def setup_layout(self): outer_layout = QVBoxLayout() outer_layout.addWidget(self.menu_bar) outer_layout.setStretch(0, 1) h_splitter = QSplitter(self) h_splitter.setOrientation(Qt.Horizontal) # FIRST COLUMN of h_splitter is chromatogram + peakmap: ############################ v_splitter1 = QSplitter(self) v_splitter1.setOrientation(Qt.Vertical) v_splitter1.addWidget(self.eic_plotter) v_splitter1.addWidget(self.peakmap_plotter) self.peakmap_plotter.setMinimumSize(250, 200) v_splitter1.setStretchFactor(0, 1) v_splitter1.setStretchFactor(1, 3) h_splitter.addWidget(v_splitter1) h_splitter.setStretchFactor(0, 2) # SECOND COLUMN of h_splittier holds controlx boxes + mz plot ####################### v_splitter2 = QSplitter(self) v_splitter2.setOrientation(Qt.Vertical) v_splitter2.addWidget(self.image_scaling_widget) v_splitter2.addWidget(self.spectra_selector_widget) v_splitter2.addWidget(self.view_range_widget) v_splitter2.addWidget(self.history_list) v_splitter2.addWidget(self.mz_plotter) v_splitter2.setStretchFactor(0, 0) v_splitter2.setStretchFactor(1, 0) v_splitter2.setStretchFactor(2, 0) v_splitter2.setStretchFactor(3, 0) v_splitter2.setStretchFactor(4, 1) h_splitter.addWidget(v_splitter2) h_splitter.setStretchFactor(1, 1) # THIRD COLUMN of h_splittier holds control table + buttons ########################## if self.table: frame = QFrame(self) layout = QVBoxLayout(frame) frame.setLayout(layout) layout.addWidget(self.table_widget) button_row_layout = QHBoxLayout(frame) button_row_layout.addWidget(self.select_all_peaks) button_row_layout.addWidget(self.unselect_all_peaks) button_row_layout.addWidget(self.done_button) layout.addLayout(button_row_layout) h_splitter.addWidget(frame) h_splitter.setStretchFactor(2, 2) outer_layout.addWidget(h_splitter) self.setLayout(outer_layout) outer_layout.setStretch(1, 99) def connect_signals_and_slots(self): self.image_scaling_widget.USE_LOG_SCALE.connect(self.use_logscale) self.image_scaling_widget.GAMMA_CHANGED.connect(self.gamma_changed) self.image_scaling_widget.IMIN_CHANGED.connect(self.set_image_min) self.image_scaling_widget.IMAX_CHANGED.connect(self.set_image_max) self.spectra_selector_widget.MS_LEVEL_CHOSEN.connect( self.ms_level_chosen) self.spectra_selector_widget.PRECURSOR_RANGE_CHANGED.connect( self.set_precursor_range) self.view_range_widget.RANGE_CHANGED.connect(self.update_image_range) self.connect(self.history_list, SIGNAL("activated(int)"), self.history_item_selected) if self.dual_mode: self.connect(self.load_action, SIGNAL("triggered()"), self.do_load_yellow) self.connect(self.load_action2, SIGNAL("triggered()"), self.do_load_blue) else: self.connect(self.load_action, SIGNAL("triggered()"), self.do_load) self.connect(self.save_action, SIGNAL("triggered()"), self.do_save) self.connect(self.help_action, SIGNAL("triggered()"), self.show_help) self.peakmap_plotter.NEW_IMAGE_LIMITS.connect( self.image_limits_upated_by_user) self.peakmap_plotter.KEY_LEFT.connect( self.user_pressed_left_key_in_plot) self.peakmap_plotter.KEY_RIGHT.connect( self.user_pressed_right_key_in_plot) self.peakmap_plotter.KEY_BACKSPACE.connect( self.user_pressed_backspace_key_in_plot) self.peakmap_plotter.KEY_END.connect(self.user_pressed_end_key_in_plot) self.peakmap_plotter.CURSOR_MOVED.connect(self.cursor_moved_in_plot) self.eic_plotter.CURSOR_MOVED.connect(self.eic_cursor_moved) self.eic_plotter.VIEW_RANGE_CHANGED.connect( self.eic_view_range_changed) self.mz_plotter.CURSOR_MOVED.connect(self.mz_cursor_moved) self.mz_plotter.VIEW_RANGE_CHANGED.connect(self.mz_view_range_changed) if self.table is not None: self.connect(self.table_widget.verticalHeader(), SIGNAL("sectionClicked(int)"), self.row_selected) self.connect(self.table_widget, SIGNAL("itemClicked(QTableWidgetItem*)"), self.cell_clicked) self.connect(self.select_all_peaks, SIGNAL("pressed()"), self.select_all_peaks_button_pressed) self.connect(self.unselect_all_peaks, SIGNAL("pressed()"), self.unselect_all_peaks_button_pressed) self.connect(self.done_button, SIGNAL("pressed()"), self.done_button_pressed) def key_release_handler(evt): tw = self.table_widget active_rows = set( ix.row() for ix in tw.selectionModel().selection().indexes()) if active_rows: row = active_rows.pop() if evt.key() in (Qt.Key_Up, Qt.Key_Down): tw.selectRow(row) tw.verticalHeader().emit(SIGNAL("sectionClicked(int)"), row) return return QTableWidget.keyPressEvent(tw, evt) self.table_widget.keyReleaseEvent = key_release_handler def cursor_moved_in_plot(self, rt, mz): self.eic_plotter.set_cursor_pos(rt) self.mz_plotter.set_cursor_pos(mz) def eic_cursor_moved(self, rt): self.peakmap_plotter.set_cursor_rt(rt) def eic_view_range_changed(self, rtmin, rtmax): """ we want to avoid the loop EIC_RANGE_CHANGED -> VIEW_RANGE_CHANGED -> EIC_RANGE_CHANGED and we do not want to fully block emitting of VIEW_RANGE_CHANGED. so self.peakmap_plotter.blockSignals() does not work here, instead we "cut" the last connection here: """ self.eic_plotter.VIEW_RANGE_CHANGED.disconnect() self.peakmap_plotter.blockSignals(True) self.peakmap_plotter.set_rt_limits(rtmin, rtmax) self.peakmap_plotter.blockSignals(False) self.peakmap_plotter.replot() self.eic_plotter.VIEW_RANGE_CHANGED.connect( self.eic_view_range_changed) def mz_view_range_changed(self, mzmin, mzmax): """ we want to avoid the loop MZ_RANGE_CHANGED -> VIEW_RANGE_CHANGED -> MZ_RANGE_CHANGED and we do not want to fully block emitting of VIEW_RANGE_CHANGED. so self.peakmap_plotter.blockSignals() does not work here, instead we "cut" the last connection here: """ self.mz_plotter.VIEW_RANGE_CHANGED.disconnect() self.peakmap_plotter.blockSignals(True) self.peakmap_plotter.set_mz_limits(mzmin, mzmax) self.peakmap_plotter.blockSignals(False) self.peakmap_plotter.replot() self.mz_plotter.VIEW_RANGE_CHANGED.connect(self.mz_view_range_changed) def mz_cursor_moved(self, mz): self.peakmap_plotter.set_cursor_mz(mz) def image_limits_upated_by_user(self, rtmin, rtmax, mzmin, mzmax): self.update_peakmap_projection_views(rtmin, rtmax, mzmin, mzmax) self.history.new_head((rtmin, rtmax, mzmin, mzmax)) self.update_history_entries() def set_image_min(self, value): self.peakmap_plotter.set_imin(value) self.peakmap_plotter.replot() def set_image_max(self, value): self.peakmap_plotter.set_imax(value) self.peakmap_plotter.replot() def update_peakmap_projection_views(self, rtmin, rtmax, mzmin, mzmax): rts, chroma = self.peakmap.chromatogram(mzmin, mzmax) self.eic_plotter.del_all_items() if self.dual_mode: rts2, chroma2 = self.peakmap2.chromatogram(mzmin, mzmax, rtmin, rtmax) self.eic_plotter.add_eics([(rts, chroma), (rts2, chroma2)], configs=[blue_line, yellow_line]) else: self.eic_plotter.add_eics([(rts, chroma)], configs=[grey_line]) self.eic_plotter.shrink_and_replot(rtmin, rtmax) if self.dual_mode: data = [(self.peakmap, rtmin, rtmax, mzmin, mzmax, 3000), (self.peakmap2, rtmin, rtmax, mzmin, mzmax, 3000)] configs = [dict(color="#aaaa00"), dict(color="#0000aa")] self.mz_plotter.plot_peakmaps(data, configs) else: self.mz_plotter.plot_peakmaps([(self.peakmap, rtmin, rtmax, mzmin, mzmax, 3000)]) self.mz_plotter.shrink_and_replot(mzmin, mzmax) self.view_range_widget.set_view_range(rtmin, rtmax, mzmin, mzmax) def _handle_history_action(self, action): item = action() if item is not None: self.peakmap_plotter.set_limits_no_sig(*item) self.update_peakmap_projection_views(*item) self.update_history_entries() def user_pressed_left_key_in_plot(self): self._handle_history_action(self.history.go_back) def user_pressed_right_key_in_plot(self): self._handle_history_action(self.history.go_forward) def user_pressed_backspace_key_in_plot(self): self._handle_history_action(self.history.go_to_beginning) def user_pressed_end_key_in_plot(self): self._handle_history_action(self.history.go_to_end) def history_item_selected(self, index): self._handle_history_action( lambda index=index: self.history.set_position(index)) @protect_signal_handler def do_save(self): pix = self.peakmap_plotter.paint_pixmap() while True: path = askForSave(self.last_used_directory_for_save, caption="Save Image", extensions=("png", "PNG")) if path is None: break __, ext = os.path.splitext(path) if ext not in (".png", ".PNG"): QMessageBox.warning(self, "Warning", "wrong/missing extension '.png'") else: self.last_used_directory_for_save = os.path.dirname(path) pix.save(path) break return def _do_load(self, title, attribute): path = askForSingleFile(self.last_used_directory_for_load, caption=title, extensions=("mzML", "mzData", "mzXML")) if path is not None: setattr(self, attribute, loadPeakMap(path)) self.process_peakmap() self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.setup_initial_values() self.setWindowTitle() self.peakmap_plotter.replot() self.plot_peakmap() self.last_used_directory_for_load = os.path.dirname(path) @protect_signal_handler def do_load(self): self._do_load("Load Peakmap", "peakmap") @protect_signal_handler def do_load_yellow(self): self._do_load("Load Yellow Peakmap", "peakmap") @protect_signal_handler def do_load_blue(self): self._do_load("Load Blue Peakmap", "peakmap2") @protect_signal_handler def select_all_peaks_button_pressed(self): for row in range(self.table_widget.rowCount()): item = self.table_widget.item(row, 0) item.setCheckState(Qt.Checked) @protect_signal_handler def unselect_all_peaks_button_pressed(self): for row in range(self.table_widget.rowCount()): item = self.table_widget.item(row, 0) item.setCheckState(Qt.Unchecked) @protect_signal_handler def done_button_pressed(self): self.ok_rows[:] = [ i for i in range(len(self.table)) if self.table_widget.item(i, 0).checkState() == Qt.Checked ] self.accept() @protect_signal_handler def row_selected(self, row_idx): row = self.table.getValues(self.table.rows[row_idx]) needed = ["rtmin", "rtmax", "mzmin", "mzmax"] if all(n in row for n in needed): rtmin, rtmax, mzmin, mzmax = [row.get(ni) for ni in needed] self.peakmap_plotter.set_limits(rtmin, rtmax, mzmin, mzmax) else: needed = ["mzmin", "mzmax"] if all(n in row for n in needed): mzmin, mzmax = [row.get(ni) for ni in needed] self.peakmap_plotter.set_limits(self.rtmin, self.rtmax, mzmin, mzmax) @protect_signal_handler def cell_clicked(self, item): row = item.row() self.table_widget.selectRow(row) self.table_widget.verticalHeader().emit(SIGNAL("sectionClicked(int)"), row) @protect_signal_handler def show_help(self): html = resource_string("emzed.core.explorers", "help_peakmapexplorer.html") QWebSettings.globalSettings().setFontFamily(QWebSettings.StandardFont, 'Courier') QWebSettings.globalSettings().setFontSize(QWebSettings.DefaultFontSize, 12) v = QWebView(self) v.setHtml(html) dlg = QDialog(self, Qt.Window) dlg.setMinimumSize(300, 300) l = QVBoxLayout(dlg) l.addWidget(v) dlg.setLayout(l) dlg.show() def update_history_entries(self): self.history_list.clear() for item in self.history.items: rtmin, rtmax, mzmin, mzmax = item str_item = "%10.5f .. %10.5f %6.2fm...%6.2fm " % ( mzmin, mzmax, rtmin / 60.0, rtmax / 60.0) self.history_list.addItem(str_item) self.history_list.setCurrentIndex(self.history.position) @protect_signal_handler def use_logscale(self, is_log): self.peakmap_plotter.set_logarithmic_scale(is_log) self.peakmap_plotter.replot() @protect_signal_handler def ms_level_chosen(self, ms_level): if ms_level != self.current_ms_level: self.current_ms_level = ms_level self.process_peakmap(ms_level) self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.peakmap_plotter.replot() self.plot_peakmap() @protect_signal_handler def set_precursor_range(self, pre_mz_min, pre_mz_max): self.process_peakmap(self.current_ms_level, pre_mz_min, pre_mz_max) self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.peakmap_plotter.replot() self.plot_peakmap() @protect_signal_handler def gamma_changed(self, value): self.peakmap_plotter.set_gamma(value) self.peakmap_plotter.replot() @protect_signal_handler def update_image_range(self, rtmin, rtmax, mzmin, mzmax): rtmin *= 60.0 rtmax *= 60.0 if rtmin < self.rtmin: rtmin = self.rtmin if rtmax > self.rtmax: rtmax = self.rtmax if mzmin < self.mzmin: mzmin = self.mzmin if mzmax > self.mzmax: mzmax = self.mzmax rtmin, rtmax = sorted((rtmin, rtmax)) mzmin, mzmax = sorted((mzmin, mzmax)) self.peakmap_plotter.set_limits(rtmin, rtmax, mzmin, mzmax) def plot_peakmap(self): self.peakmap_plotter.set_limits(self.rtmin, self.rtmax, self.mzmin, self.mzmax)
class PeakMapExplorer(EmzedDialog): def __init__(self, ok_rows_container=[], parent=None): super(PeakMapExplorer, self).__init__(parent) self.setWindowFlags(Qt.Window) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.ok_rows = ok_rows_container self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowFlags(Qt.Window) self.gamma = 3.0 self.last_used_directory_for_load = None self.last_used_directory_for_save = None self.history = History() def keyPressEvent(self, e): # avoid closing of dialog when Esc key pressed: if e.key() != Qt.Key_Escape: return super(PeakMapExplorer, self).keyPressEvent(e) def setWindowTitle(self): if self.peakmap2 is None: title = os.path.basename(self.peakmap.meta.get("source", "")) else: p1 = os.path.basename(self.peakmap.meta.get("source", "")) p2 = os.path.basename(self.peakmap2.meta.get("source", "")) title = "yellow=%s, blue=%s" % (p1, p2) super(PeakMapExplorer, self).setWindowTitle(title) def setup(self, peakmap, peakmap2=None, table=None): self.table = table def collect_precursor_mz(pm): for s in pm: if s.precursors: if s.msLevel > 1: yield s.precursors[0][0] self.ms_levels = set(peakmap.getMsLevels()) self.precursor_mz = set(collect_precursor_mz(peakmap)) if peakmap2 is not None: self.ms_levels &= set(peakmap2.getMsLevels()) self.precursor_mz &= set(collect_precursor_mz(peakmap2)) self.ms_levels = sorted(self.ms_levels) self.precursor_mz = sorted(self.precursor_mz) self.setup_table_widgets() self.setup_input_widgets() self.history_list = QComboBox(self) self.setup_ms2_widgets() self.full_pm = peakmap self.full_pm2 = peakmap2 self.dual_mode = self.full_pm2 is not None self.current_ms_level = self.ms_levels[0] self.process_peakmap(self.current_ms_level) self.rtmin, self.rtmax, self.mzmin, self.mzmax = get_range(self.peakmap, self.peakmap2) self.setup_plot_widgets() self.setup_menu_bar() self.setup_layout() self.connect_signals_and_slots() self.setup_initial_values() self.plot_peakmap() def setup_ms2_widgets(self): self.spectra_selector_widget.set_data(self.ms_levels, self.precursor_mz) def setup_table_widgets(self): if self.table is not None: self.table_widget = create_table_widget(self.table, self) self.select_all_peaks = QPushButton("Select all peaks", self) self.unselect_all_peaks = QPushButton("Unselect all peaks", self) self.done_button = QPushButton("Done", self) def setup_menu_bar(self): self.menu_bar = QMenuBar(self) menu = QMenu("Peakmap Explorer", self.menu_bar) self.menu_bar.addMenu(menu) if not self.dual_mode: self.load_action = QAction("Load Peakmap", self) self.load_action.setShortcut(QKeySequence("Ctrl+L")) self.load_action2 = None menu.addAction(self.load_action) else: self.load_action = QAction("Load Yellow Peakmap", self) self.load_action2 = QAction("Load Blue Peakmap", self) menu.addAction(self.load_action) menu.addAction(self.load_action2) self.save_action = QAction("Save selected range as image", self) self.save_action.setShortcut(QKeySequence("Ctrl+S")) menu.addAction(self.save_action) menu = QMenu("Help", self.menu_bar) self.help_action = QAction("Help", self) self.help_action.setShortcut(QKeySequence("F1")) menu.addAction(self.help_action) self.menu_bar.addMenu(menu) def process_peakmap(self, ms_level, pre_mz_min=None, pre_mz_max=None): peakmap = self.full_pm.filter(lambda s: s.msLevel == ms_level) if ms_level > 1 and pre_mz_min is not None: peakmap = peakmap.filter(lambda s: s.precursors[0][0] >= pre_mz_min) if ms_level > 1 and pre_mz_max is not None: peakmap = peakmap.filter(lambda s: s.precursors[0][0] <= pre_mz_max) if self.full_pm2 is not None: peakmap2 = self.full_pm2.filter(lambda s: s.msLevel == ms_level) self.peakmap = peakmap if self.dual_mode: self.peakmap2 = peakmap2 else: self.peakmap2 = None for i, msl in enumerate(self.ms_levels): if msl == ms_level: pass # TODO self.ms_level.setCurrentIndex(i) self.setWindowTitle() def setup_initial_values(self): imax = self.peakmap_plotter.get_total_imax() self.image_scaling_widget.set_max_intensity(imax) self.image_scaling_widget.set_gamma(self.gamma) self.view_range_widget.set_view_range(self.rtmin, self.rtmax, self.mzmin, self.mzmax) def setup_input_widgets(self): self.image_scaling_widget = ImageScalingWidget(self) self.spectra_selector_widget = SpectraSelectorWidget(self) self.view_range_widget = ViewRangeWidget(self) def setup_plot_widgets(self): self.peakmap_plotter = PeakMapPlottingWidget() self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.eic_plotter = EicPlottingWidget(with_range=False) self.mz_plotter = MzPlottingWidget() self.peakmap_plotter.set_logarithmic_scale(1) self.peakmap_plotter.set_gamma(self.gamma) self.eic_plotter.set_overall_range(self.rtmin, self.rtmax) self.mz_plotter.set_overall_range(self.mzmin, self.mzmax) def setup_layout(self): outer_layout = QVBoxLayout() outer_layout.addWidget(self.menu_bar) outer_layout.setStretch(0, 1) h_splitter = QSplitter(self) h_splitter.setOrientation(Qt.Horizontal) # FIRST COLUMN of h_splitter is chromatogram + peakmap: ############################ v_splitter1 = QSplitter(self) v_splitter1.setOrientation(Qt.Vertical) v_splitter1.addWidget(self.eic_plotter) v_splitter1.addWidget(self.peakmap_plotter) self.peakmap_plotter.setMinimumSize(250, 200) v_splitter1.setStretchFactor(0, 1) v_splitter1.setStretchFactor(1, 3) h_splitter.addWidget(v_splitter1) h_splitter.setStretchFactor(0, 2) # SECOND COLUMN of h_splittier holds controlx boxes + mz plot ####################### v_splitter2 = QSplitter(self) v_splitter2.setOrientation(Qt.Vertical) v_splitter2.addWidget(self.image_scaling_widget) v_splitter2.addWidget(self.spectra_selector_widget) v_splitter2.addWidget(self.view_range_widget) v_splitter2.addWidget(self.history_list) v_splitter2.addWidget(self.mz_plotter) v_splitter2.setStretchFactor(0, 0) v_splitter2.setStretchFactor(1, 0) v_splitter2.setStretchFactor(2, 0) v_splitter2.setStretchFactor(3, 0) v_splitter2.setStretchFactor(4, 1) h_splitter.addWidget(v_splitter2) h_splitter.setStretchFactor(1, 1) # THIRD COLUMN of h_splittier holds control table + buttons ########################## if self.table: frame = QFrame(self) layout = QVBoxLayout(frame) frame.setLayout(layout) layout.addWidget(self.table_widget) button_row_layout = QHBoxLayout(frame) button_row_layout.addWidget(self.select_all_peaks) button_row_layout.addWidget(self.unselect_all_peaks) button_row_layout.addWidget(self.done_button) layout.addLayout(button_row_layout) h_splitter.addWidget(frame) h_splitter.setStretchFactor(2, 2) outer_layout.addWidget(h_splitter) self.setLayout(outer_layout) outer_layout.setStretch(1, 99) def connect_signals_and_slots(self): self.image_scaling_widget.USE_LOG_SCALE.connect(self.use_logscale) self.image_scaling_widget.GAMMA_CHANGED.connect(self.gamma_changed) self.image_scaling_widget.IMIN_CHANGED.connect(self.set_image_min) self.image_scaling_widget.IMAX_CHANGED.connect(self.set_image_max) self.spectra_selector_widget.MS_LEVEL_CHOSEN.connect(self.ms_level_chosen) self.spectra_selector_widget.PRECURSOR_RANGE_CHANGED.connect(self.set_precursor_range) self.view_range_widget.RANGE_CHANGED.connect(self.update_image_range) self.connect(self.history_list, SIGNAL("activated(int)"), self.history_item_selected) if self.dual_mode: self.connect(self.load_action, SIGNAL("triggered()"), self.do_load_yellow) self.connect(self.load_action2, SIGNAL("triggered()"), self.do_load_blue) else: self.connect(self.load_action, SIGNAL("triggered()"), self.do_load) self.connect(self.save_action, SIGNAL("triggered()"), self.do_save) self.connect(self.help_action, SIGNAL("triggered()"), self.show_help) self.peakmap_plotter.NEW_IMAGE_LIMITS.connect(self.image_limits_upated_by_user) self.peakmap_plotter.KEY_LEFT.connect(self.user_pressed_left_key_in_plot) self.peakmap_plotter.KEY_RIGHT.connect(self.user_pressed_right_key_in_plot) self.peakmap_plotter.KEY_BACKSPACE.connect(self.user_pressed_backspace_key_in_plot) self.peakmap_plotter.KEY_END.connect(self.user_pressed_end_key_in_plot) self.peakmap_plotter.CURSOR_MOVED.connect(self.cursor_moved_in_plot) self.eic_plotter.CURSOR_MOVED.connect(self.eic_cursor_moved) self.eic_plotter.VIEW_RANGE_CHANGED.connect(self.eic_view_range_changed) self.mz_plotter.CURSOR_MOVED.connect(self.mz_cursor_moved) self.mz_plotter.VIEW_RANGE_CHANGED.connect(self.mz_view_range_changed) if self.table is not None: self.connect(self.table_widget.verticalHeader(), SIGNAL("sectionClicked(int)"), self.row_selected) self.connect(self.table_widget, SIGNAL("itemClicked(QTableWidgetItem*)"), self.cell_clicked) self.connect(self.select_all_peaks, SIGNAL("pressed()"), self.select_all_peaks_button_pressed) self.connect(self.unselect_all_peaks, SIGNAL("pressed()"), self.unselect_all_peaks_button_pressed) self.connect(self.done_button, SIGNAL("pressed()"), self.done_button_pressed) def key_release_handler(evt): tw = self.table_widget active_rows = set(ix.row() for ix in tw.selectionModel().selection().indexes()) if active_rows: row = active_rows.pop() if evt.key() in (Qt.Key_Up, Qt.Key_Down): tw.selectRow(row) tw.verticalHeader().emit(SIGNAL("sectionClicked(int)"), row) return return QTableWidget.keyPressEvent(tw, evt) self.table_widget.keyReleaseEvent = key_release_handler def cursor_moved_in_plot(self, rt, mz): self.eic_plotter.set_cursor_pos(rt) self.mz_plotter.set_cursor_pos(mz) def eic_cursor_moved(self, rt): self.peakmap_plotter.set_cursor_rt(rt) def eic_view_range_changed(self, rtmin, rtmax): """ we want to avoid the loop EIC_RANGE_CHANGED -> VIEW_RANGE_CHANGED -> EIC_RANGE_CHANGED and we do not want to fully block emitting of VIEW_RANGE_CHANGED. so self.peakmap_plotter.blockSignals() does not work here, instead we "cut" the last connection here: """ self.eic_plotter.VIEW_RANGE_CHANGED.disconnect() self.peakmap_plotter.blockSignals(True) self.peakmap_plotter.set_rt_limits(rtmin, rtmax) self.peakmap_plotter.blockSignals(False) self.peakmap_plotter.replot() self.eic_plotter.VIEW_RANGE_CHANGED.connect(self.eic_view_range_changed) def mz_view_range_changed(self, mzmin, mzmax): """ we want to avoid the loop MZ_RANGE_CHANGED -> VIEW_RANGE_CHANGED -> MZ_RANGE_CHANGED and we do not want to fully block emitting of VIEW_RANGE_CHANGED. so self.peakmap_plotter.blockSignals() does not work here, instead we "cut" the last connection here: """ self.mz_plotter.VIEW_RANGE_CHANGED.disconnect() self.peakmap_plotter.blockSignals(True) self.peakmap_plotter.set_mz_limits(mzmin, mzmax) self.peakmap_plotter.blockSignals(False) self.peakmap_plotter.replot() self.mz_plotter.VIEW_RANGE_CHANGED.connect(self.mz_view_range_changed) def mz_cursor_moved(self, mz): self.peakmap_plotter.set_cursor_mz(mz) def image_limits_upated_by_user(self, rtmin, rtmax, mzmin, mzmax): self.update_peakmap_projection_views(rtmin, rtmax, mzmin, mzmax) self.history.new_head((rtmin, rtmax, mzmin, mzmax)) self.update_history_entries() def set_image_min(self, value): self.peakmap_plotter.set_imin(value) self.peakmap_plotter.replot() def set_image_max(self, value): self.peakmap_plotter.set_imax(value) self.peakmap_plotter.replot() def update_peakmap_projection_views(self, rtmin, rtmax, mzmin, mzmax): rts, chroma = self.peakmap.chromatogram(mzmin, mzmax) self.eic_plotter.del_all_items() if self.dual_mode: rts2, chroma2 = self.peakmap2.chromatogram(mzmin, mzmax, rtmin, rtmax) self.eic_plotter.add_eics([(rts, chroma), (rts2, chroma2)], configs=[blue_line, yellow_line]) else: self.eic_plotter.add_eics([(rts, chroma)], configs=[grey_line]) self.eic_plotter.shrink_and_replot(rtmin, rtmax) if self.dual_mode: data = [(self.peakmap, rtmin, rtmax, mzmin, mzmax, 3000), (self.peakmap2, rtmin, rtmax, mzmin, mzmax, 3000)] configs = [dict(color="#aaaa00"), dict(color="#0000aa")] self.mz_plotter.plot_peakmaps(data, configs) else: self.mz_plotter.plot_peakmaps([(self.peakmap, rtmin, rtmax, mzmin, mzmax, 3000)]) self.mz_plotter.shrink_and_replot(mzmin, mzmax) self.view_range_widget.set_view_range(rtmin, rtmax, mzmin, mzmax) def _handle_history_action(self, action): item = action() if item is not None: self.peakmap_plotter.set_limits_no_sig(*item) self.update_peakmap_projection_views(*item) self.update_history_entries() def user_pressed_left_key_in_plot(self): self._handle_history_action(self.history.go_back) def user_pressed_right_key_in_plot(self): self._handle_history_action(self.history.go_forward) def user_pressed_backspace_key_in_plot(self): self._handle_history_action(self.history.go_to_beginning) def user_pressed_end_key_in_plot(self): self._handle_history_action(self.history.go_to_end) def history_item_selected(self, index): self._handle_history_action(lambda index=index: self.history.set_position(index)) @protect_signal_handler def do_save(self): pix = self.peakmap_plotter.paint_pixmap() while True: path = askForSave(self.last_used_directory_for_save, caption="Save Image", extensions=("png", "PNG") ) if path is None: break __, ext = os.path.splitext(path) if ext not in (".png", ".PNG"): QMessageBox.warning(self, "Warning", "wrong/missing extension '.png'") else: self.last_used_directory_for_save = os.path.dirname(path) pix.save(path) break return def _do_load(self, title, attribute): path = askForSingleFile(self.last_used_directory_for_load, caption=title, extensions=("mzML", "mzData", "mzXML") ) if path is not None: setattr(self, attribute, loadPeakMap(path)) self.process_peakmap() self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.setup_initial_values() self.setWindowTitle() self.peakmap_plotter.replot() self.plot_peakmap() self.last_used_directory_for_load = os.path.dirname(path) @protect_signal_handler def do_load(self): self._do_load("Load Peakmap", "peakmap") @protect_signal_handler def do_load_yellow(self): self._do_load("Load Yellow Peakmap", "peakmap") @protect_signal_handler def do_load_blue(self): self._do_load("Load Blue Peakmap", "peakmap2") @protect_signal_handler def select_all_peaks_button_pressed(self): for row in range(self.table_widget.rowCount()): item = self.table_widget.item(row, 0) item.setCheckState(Qt.Checked) @protect_signal_handler def unselect_all_peaks_button_pressed(self): for row in range(self.table_widget.rowCount()): item = self.table_widget.item(row, 0) item.setCheckState(Qt.Unchecked) @protect_signal_handler def done_button_pressed(self): self.ok_rows[:] = [i for i in range(len(self.table)) if self.table_widget.item(i, 0).checkState() == Qt.Checked] self.accept() @protect_signal_handler def row_selected(self, row_idx): row = self.table.getValues(self.table.rows[row_idx]) needed = ["rtmin", "rtmax", "mzmin", "mzmax"] if all(n in row for n in needed): rtmin, rtmax, mzmin, mzmax = [row.get(ni) for ni in needed] self.peakmap_plotter.set_limits(rtmin, rtmax, mzmin, mzmax) else: needed = ["mzmin", "mzmax"] if all(n in row for n in needed): mzmin, mzmax = [row.get(ni) for ni in needed] self.peakmap_plotter.set_limits(self.rtmin, self.rtmax, mzmin, mzmax) @protect_signal_handler def cell_clicked(self, item): row = item.row() self.table_widget.selectRow(row) self.table_widget.verticalHeader().emit(SIGNAL("sectionClicked(int)"), row) @protect_signal_handler def show_help(self): html = resource_string("emzed.core.explorers", "help_peakmapexplorer.html") QWebSettings.globalSettings().setFontFamily(QWebSettings.StandardFont, 'Courier') QWebSettings.globalSettings().setFontSize(QWebSettings.DefaultFontSize, 12) v = QWebView(self) v.setHtml(html) dlg = QDialog(self, Qt.Window) dlg.setMinimumSize(300, 300) l = QVBoxLayout(dlg) l.addWidget(v) dlg.setLayout(l) dlg.show() def update_history_entries(self): self.history_list.clear() for item in self.history.items: rtmin, rtmax, mzmin, mzmax = item str_item = "%10.5f .. %10.5f %6.2fm...%6.2fm " % (mzmin, mzmax, rtmin / 60.0, rtmax / 60.0) self.history_list.addItem(str_item) self.history_list.setCurrentIndex(self.history.position) @protect_signal_handler def use_logscale(self, is_log): self.peakmap_plotter.set_logarithmic_scale(is_log) self.peakmap_plotter.replot() @protect_signal_handler def ms_level_chosen(self, ms_level): if ms_level != self.current_ms_level: self.current_ms_level = ms_level self.process_peakmap(ms_level) self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.peakmap_plotter.replot() self.plot_peakmap() @protect_signal_handler def set_precursor_range(self, pre_mz_min, pre_mz_max): self.process_peakmap(self.current_ms_level, pre_mz_min, pre_mz_max) self.peakmap_plotter.set_peakmaps(self.peakmap, self.peakmap2) self.peakmap_plotter.replot() self.plot_peakmap() @protect_signal_handler def gamma_changed(self, value): self.peakmap_plotter.set_gamma(value) self.peakmap_plotter.replot() @protect_signal_handler def update_image_range(self, rtmin, rtmax, mzmin, mzmax): rtmin *= 60.0 rtmax *= 60.0 if rtmin < self.rtmin: rtmin = self.rtmin if rtmax > self.rtmax: rtmax = self.rtmax if mzmin < self.mzmin: mzmin = self.mzmin if mzmax > self.mzmax: mzmax = self.mzmax rtmin, rtmax = sorted((rtmin, rtmax)) mzmin, mzmax = sorted((mzmin, mzmax)) self.peakmap_plotter.set_limits(rtmin, rtmax, mzmin, mzmax) def plot_peakmap(self): self.peakmap_plotter.set_limits(self.rtmin, self.rtmax, self.mzmin, self.mzmax)
class TableExplorer(EmzedDialog): def __init__(self, tables, offerAbortOption, parent=None, close_callback=None): super(TableExplorer, self).__init__(parent) # function which is called when window is closed. the arguments passed are boolean # flags indication for every table if it was modified: self.close_callback = close_callback # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowFlags(Qt.Window) self.offerAbortOption = offerAbortOption self.models = [TableModel(table, self) for table in tables] self.model = None self.tableView = None self.hadFeatures = None self.setupWidgets() self.setupLayout() self.connectSignals() sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setSizePolicy(sizePolicy) self.setSizeGripEnabled(True) self.setupViewForTable(0) def reject(self): super(TableExplorer, self).reject() modified = [len(m.actions) > 0 for m in self.models] if self.close_callback is not None: try: self.close_callback(*modified) except: import traceback traceback.print_exc() def keyPressEvent(self, e): if e.key() != Qt.Key_Escape: super(TableExplorer, self).keyPressEvent(e) def setupWidgets(self): self.setupMenuBar() self.setupTableViews() self.setupPlottingAndIntegrationWidgets() self.setupToolWidgets() if self.offerAbortOption: self.setupAcceptButtons() def setupPlottingAndIntegrationWidgets(self): self.setupPlottingWidgets() self.setupIntegrationWidgets() def setupMenuBar(self): self.menubar = QMenuBar(self) menu = self.buildEditMenu() self.menubar.addMenu(menu) self.chooseTableActions = [] if len(self.models) > 1: menu = self.buildChooseTableMenu() self.menubar.addMenu(menu) def buildEditMenu(self): self.undoAction = QAction("Undo", self) self.undoAction.setShortcut(QKeySequence("Ctrl+Z")) self.redoAction = QAction("Redo", self) self.redoAction.setShortcut(QKeySequence("Ctrl+Y")) menu = QMenu("Edit", self.menubar) menu.addAction(self.undoAction) menu.addAction(self.redoAction) return menu def setupTableViews(self): self.tableViews = [] self.filterWidgets = [] self.filters_enabled = False for i, model in enumerate(self.models): self.tableViews.append(self.setupTableViewFor(model)) self.filterWidgets.append(self.setupFilterWidgetFor(model)) def setupFilterWidgetFor(self, model): t = model.table w = FilterCriteriaWidget(self) w.configure(t) w.LIMITS_CHANGED.connect(model.limits_changed) return w def set_delegates(self): bd = ButtonDelegate(self.tableView, self) types = self.model.table.getColTypes() for i, j in self.model.widgetColToDataCol.items(): if types[j] == CallBack: self.tableView.setItemDelegateForColumn(i, bd) def remove_delegates(self): types = self.model.table.getColTypes() for i, j in self.model.widgetColToDataCol.items(): if types[j] in (bool, CallBack): self.tableView.setItemDelegateForColumn(i, None) def setupTableViewFor(self, model): tableView = EmzedTableView(self) tableView.setModel(model) tableView.horizontalHeader().setResizeMode(QHeaderView.Interactive) tableView.horizontalHeader().setMovable(1) pol = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) tableView.setSizePolicy(pol) tableView.setVisible(False) # before filling the table, disabling sorting accelerates table # construction, sorting is enabled in TableView.showEvent, which is # called after construction tableView.setSortingEnabled(False) return tableView def buildChooseTableMenu(self): menu = QMenu("Choose Table", self.menubar) for i, model in enumerate(self.models): action = QAction(" [%d]: %s" % (i, model.getTitle()), self) menu.addAction(action) self.chooseTableActions.append(action) return menu def setupPlottingWidgets(self): self.eic_plotter = EicPlottingWidget() self.mz_plotter = MzPlottingWidget() self.ts_plotter = TimeSeriesPlottingWidget() pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) pol.setVerticalStretch(5) self.eic_plotter.setSizePolicy(pol) self.mz_plotter.setSizePolicy(pol) self.ts_plotter.setSizePolicy(pol) self.spec_label = QLabel("plot spectra:") self.choose_spec = QListWidget() self.choose_spec.setFixedHeight(90) self.choose_spec.setSelectionMode(QAbstractItemView.ExtendedSelection) def setupIntegrationWidgets(self): self.integration_widget = IntegrationWidget(self) names = [name for (name, __) in algorithm_configs.peakIntegrators] self.integration_widget.set_integration_methods(names) def setupToolWidgets(self): self.chooseGroubLabel = QLabel("Expand selection:", parent=self) self.chooseGroupColumn = QComboBox(parent=self) self.chooseGroupColumn.setMinimumWidth(150) self.choose_visible_columns_button = button("Visible columns") # we introduced this invisible button else qt makes the filter_on_button always # active on mac osx, that means that as soon we press enter in one of the filter # widgets the button is triggered ! # problem does not occur on windows. # self.dummy = QPushButton() # self.dummy.setVisible(False) self.filter_on_button = button("Filter rows") self.sort_label = QLabel("sort by:", parent=self) self.sort_fields_widgets = [] self.sort_order_widgets = [] for i in range(3): w = QComboBox(parent=self) w.setMinimumWidth(100) self.sort_fields_widgets.append(w) w = QComboBox(parent=self) w.addItems(["asc", "desc"]) w.setMaximumWidth(60) self.sort_order_widgets.append(w) self.restrict_to_filtered_button = button("Restrict to filter result") self.remove_filtered_button = button("Remove filter result") self.export_table_button = button("Export table") self.restrict_to_filtered_button.setEnabled(False) self.remove_filtered_button.setEnabled(False) def setupAcceptButtons(self): self.okButton = button("Ok", parent=self) self.abortButton = button("Abort", parent=self) self.result = 1 # default for closing def create_additional_widgets(self, vsplitter): # so that derived classes can add widgets above table ! return None def connect_additional_widgets(self, model): pass def setupLayout(self): vlayout = QVBoxLayout() self.setLayout(vlayout) vsplitter = QSplitter() vsplitter.setOrientation(Qt.Vertical) vsplitter.setOpaqueResize(False) vsplitter.addWidget(self.menubar) # 0 vsplitter.addWidget(self.layoutPlottingAndIntegrationWidgets()) # 1 extra = self.create_additional_widgets(vsplitter) if extra is not None: vsplitter.addWidget(extra) self.table_view_container = QStackedWidget(self) for view in self.tableViews: self.table_view_container.addWidget(view) vsplitter.addWidget(self.table_view_container) # 2 vsplitter.addWidget(self.layoutToolWidgets()) # 3 # self.filter_widgets_box = QScrollArea(self) self.filter_widgets_container = QStackedWidget(self) for w in self.filterWidgets: self.filter_widgets_container.addWidget(w) self.filter_widgets_container.setVisible(False) self.filter_widgets_container.setFrameStyle(QFrame.Plain) vsplitter.addWidget(self.filter_widgets_container) di = 1 if extra is not None else 0 vsplitter.setStretchFactor(0, 1.0) # menubar vsplitter.setStretchFactor(1, 3.0) # plots + integration # vsplitter.setStretchFactor(2, 1.0) # ms2 spec chooser vsplitter.setStretchFactor(2 + di, 5.0) # table vsplitter.setStretchFactor(3 + di, 1.0) # tools vsplitter.setStretchFactor(4 + di, 2.0) # filters vlayout.addWidget(vsplitter) if self.offerAbortOption: vlayout.addLayout(self.layoutButtons()) def layoutButtons(self): hbox = QHBoxLayout() hbox.addWidget(self.abortButton) hbox.setAlignment(self.abortButton, Qt.AlignVCenter) hbox.addWidget(self.okButton) hbox.setAlignment(self.okButton, Qt.AlignVCenter) return hbox def enable_integration_widgets(self, flag=True): self.integration_widget.setEnabled(flag) def enable_spec_chooser_widgets(self, flag=True): self.spec_label.setEnabled(flag) self.choose_spec.setEnabled(flag) def layoutPlottingAndIntegrationWidgets(self): hsplitter = QSplitter() hsplitter.setOpaqueResize(False) middleLayout = QVBoxLayout() middleLayout.setSpacing(5) middleLayout.setMargin(5) middleLayout.addWidget(self.integration_widget) middleLayout.addStretch() middleLayout.addWidget(self.spec_label) middleLayout.addWidget(self.choose_spec) middleLayout.addStretch() middleLayout.addStretch() self.middleFrame = QFrame() self.middleFrame.setLayout(middleLayout) self.middleFrame.setMaximumWidth(250) plot_widgets = self.setup_plot_widgets([self.ts_plotter, self.eic_plotter, self.middleFrame, self.mz_plotter]) for widget in plot_widgets: hsplitter.addWidget(widget) return hsplitter def setup_plot_widgets(self, widgets): return widgets def layoutToolWidgets(self): frame = QFrame(parent=self) layout = QGridLayout() row = 0 column = 0 layout.addWidget(self.chooseGroubLabel, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.chooseGroupColumn, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.choose_visible_columns_button, row, column, alignment=Qt.AlignLeft) h_layout = QHBoxLayout() h_layout.addWidget(self.sort_label) for sort_field_w, sort_order_w in zip(self.sort_fields_widgets, self.sort_order_widgets): h_layout.addWidget(sort_field_w) h_layout.addWidget(sort_order_w) column += 1 layout.addLayout(h_layout, row, column, alignment=Qt.AlignLeft) row = 1 column = 0 layout.addWidget(self.filter_on_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.restrict_to_filtered_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.remove_filtered_button, row, column, alignment=Qt.AlignLeft) column += 1 layout.addWidget(self.export_table_button, row, column, alignment=Qt.AlignLeft) column += 1 # layout.addWidget(self.dummy, row, column, alignment=Qt.AlignLeft) layout.setColumnStretch(column, 1) frame.setLayout(layout) return frame def set_window_title(self, table, visible=None): if visible is None: visible = table model_title = self.model.getTitle() title = "%d out of %d rows from %s" % (len(visible), len(table), model_title) self.setWindowTitle(title) def setup_model_dependent_look(self): hasFeatures = self.model.hasFeatures() isIntegrated = self.model.isIntegrated() hasEIC = self.model.hasEIC() hasTimeSeries = self.model.hasTimeSeries() hasSpectra = self.model.hasSpectra() self.eic_only_mode = hasEIC and not hasFeatures # includes: not isIntegrated ! self.has_chromatograms = hasFeatures self.allow_integration = isIntegrated self.has_time_series = hasTimeSeries self.has_spectra = hasSpectra self.eic_plotter.setVisible(self.eic_only_mode or self.has_chromatograms) self.eic_plotter.enable_range(not self.eic_only_mode) self.mz_plotter.setVisible(self.has_chromatograms or self.has_spectra) self.ts_plotter.setVisible(self.has_time_series) self.enable_integration_widgets(self.allow_integration) self.enable_spec_chooser_widgets(self.has_spectra or self.has_chromatograms) self.enable_integration_widgets(self.allow_integration) self.enable_spec_chooser_widgets(self.has_chromatograms or self.has_spectra) self.middleFrame.setVisible(self.allow_integration or self.has_spectra) self.choose_spec.clear() @protect_signal_handler def handleClick(self, index, model): content = model.data(index) if isUrl(content): QDesktopServices.openUrl(QUrl(content)) @protect_signal_handler def cell_pressed(self, index): self.tableView.selectRow(index.row()) self.tableView.verticalHeader().sectionClicked.emit(index.row()) def connectSignals(self): for i, action in enumerate(self.chooseTableActions): handler = lambda i=i: self.setupViewForTable(i) handler = protect_signal_handler(handler) self.menubar.connect(action, SIGNAL("triggered()"), handler) for view in self.tableViews: vh = view.verticalHeader() vh.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(vh, SIGNAL("customContextMenuRequested(QPoint)"), self.openContextMenu) self.connect(vh, SIGNAL("sectionClicked(int)"), self.rowClicked) model = view.model() handler = lambda idx, model=model: self.handleClick(idx, model) handler = protect_signal_handler(handler) self.connect(view, SIGNAL("clicked(QModelIndex)"), handler) self.connect(view, SIGNAL("doubleClicked(QModelIndex)"), self.handle_double_click) view.pressed.connect(self.cell_pressed) self.connect_additional_widgets(model) self.integration_widget.TRIGGER_INTEGRATION.connect(self.do_integrate) self.choose_spec.itemSelectionChanged.connect(self.spectrumChosen) if self.offerAbortOption: self.connect(self.okButton, SIGNAL("clicked()"), self.ok) self.connect(self.abortButton, SIGNAL("clicked()"), self.abort) self.choose_visible_columns_button.clicked.connect(self.choose_visible_columns) self.filter_on_button.clicked.connect(self.filter_toggle) self.remove_filtered_button.clicked.connect(self.remove_filtered) self.restrict_to_filtered_button.clicked.connect(self.restrict_to_filtered) self.export_table_button.clicked.connect(self.export_table) for sort_field_w in self.sort_fields_widgets: sort_field_w.currentIndexChanged.connect(self.sort_fields_changed) for sort_order_w in self.sort_order_widgets: sort_order_w.currentIndexChanged.connect(self.sort_fields_changed) self.eic_plotter.SELECTED_RANGE_CHANGED.connect(self.eic_selection_changed) @protect_signal_handler def sort_fields_changed(self, __): sort_data = [(str(f0.currentText()), str(f1.currentText())) for f0, f1 in zip(self.sort_fields_widgets, self.sort_order_widgets)] sort_data = [(f0, f1) for (f0, f1) in sort_data if f0 != "-" and f0 != ""] if sort_data: self.model.sort_by(sort_data) main_name, main_order = sort_data[0] idx = self.model.widget_col(main_name) if idx is not None: header = self.tableView.horizontalHeader() header.blockSignals(True) header.setSortIndicator( idx, Qt.AscendingOrder if main_order.startswith("asc") else Qt.DescendingOrder) header.blockSignals(False) @protect_signal_handler def filter_toggle(self, *a): self.filters_enabled = not self.filters_enabled for model in self.models: model.setFiltersEnabled(self.filters_enabled) self.filter_widgets_container.setVisible(self.filters_enabled) self.restrict_to_filtered_button.setEnabled(self.filters_enabled) self.remove_filtered_button.setEnabled(self.filters_enabled) if self.filters_enabled: # we add spaces becaus on mac the text field cut when rendered self.filter_on_button.setText("Disable row filtering") self.export_table_button.setText("Export filtered") else: # we add spaces becaus on mac the text field cut when rendered self.filter_on_button.setText("Enable row filtering") self.export_table_button.setText("Export table") @protect_signal_handler def choose_visible_columns(self, *a): self.remove_delegates() col_names, is_currently_visible = self.model.columnames_with_visibility() if not col_names: return # zip, sort and unzip then: col_names, is_currently_visible = zip(*sorted(zip(col_names, is_currently_visible))) dlg = ColumnMultiSelectDialog(col_names, is_currently_visible) dlg.exec_() if dlg.column_settings is None: return hide_names = [n for (n, col_idx, visible) in dlg.column_settings if not visible] self.update_hidden_columns(hide_names) self.model.save_preset_hidden_column_names() def update_hidden_columns(self, hidden_names): self.model.hide_columns(hidden_names) self.set_delegates() self.setup_choose_group_column_widget(hidden_names) self.setup_sort_fields(hidden_names) self.current_filter_widget.hide_filters(hidden_names) self.model.table.meta["hide_in_explorer"] = hidden_names self.setup_sort_fields(hidden_names) @protect_signal_handler def remove_filtered(self, *a): self.model.remove_filtered() @protect_signal_handler def restrict_to_filtered(self, *a): self.model.restrict_to_filtered() @protect_signal_handler def export_table(self, *a): path = askForSave(extensions=["csv"]) if path is not None: t = self.model.extract_visible_table() if os.path.exists(path): os.remove(path) t.storeCSV(path, as_printed=True) @protect_signal_handler def handle_double_click(self, idx): row, col = self.model.table_index(idx) cell_value = self.model.cell_value(idx) extra_args = dict() if isinstance(cell_value, PeakMap): col_name = self.model.column_name(idx) if "__" in col_name: prefix, __, __ = col_name.partition("__") full_prefix = prefix + "__" else: full_prefix = "" for n in "rtmin", "rtmax", "mzmin", "mzmax", "mz": full_n = full_prefix + n value = self.model.table.getValue(self.model.table.rows[row], full_n, None) extra_args[n] = value if extra_args["mzmin"] is None and extra_args["mzmax"] is None: mz = extra_args["mz"] if mz is not None: mzmin = mz - 10 * 1e-6 * mz # -10 ppm mzmax = mz + 10 * 1e-6 * mz # +19 ppm extra_args["mzmin"] = mzmin extra_args["mzmax"] = mzmax del extra_args["mz"] insp = inspector(cell_value, modal=False, parent=self, **extra_args) if insp is not None: insp() def disconnectModelSignals(self): self.disconnect(self.model, SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"), self.dataChanged) self.model.modelReset.disconnect(self.handle_model_reset) self.menubar.disconnect(self.undoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.undoLastAction)) self.menubar.disconnect(self.redoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.redoLastAction)) def connectModelSignals(self): self.connect(self.model, SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"), self.dataChanged) self.model.modelReset.connect(self.handle_model_reset) self.menubar.connect(self.undoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.undoLastAction)) self.menubar.connect(self.redoAction, SIGNAL("triggered()"), protect_signal_handler(self.model.redoLastAction)) self.model.DATA_CHANGE.connect(self.set_window_title) self.model.SORT_TRIGGERED.connect(self.sort_by_click_in_header) @protect_signal_handler def sort_by_click_in_header(self, name, is_ascending): for f in self.sort_fields_widgets: f.blockSignals(True) for f in self.sort_order_widgets: f.blockSignals(True) main_widget = self.sort_fields_widgets[0] idx = main_widget.findText(name) main_widget.setCurrentIndex(idx) self.sort_order_widgets[0].setCurrentIndex(bool(is_ascending)) for i in range(1, len(self.sort_fields_widgets)): self.sort_fields_widgets[i].setCurrentIndex(0) for f in self.sort_fields_widgets: f.blockSignals(False) for f in self.sort_order_widgets: f.blockSignals(False) def group_column_selected(self, idx): self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection) def updateMenubar(self): undoInfo = self.model.infoLastAction() redoInfo = self.model.infoRedoAction() self.undoAction.setEnabled(undoInfo is not None) self.redoAction.setEnabled(redoInfo is not None) if undoInfo: self.undoAction.setText("Undo: %s" % undoInfo) if redoInfo: self.redoAction.setText("Redo: %s" % redoInfo) def setupViewForTable(self, i): for j, action in enumerate(self.chooseTableActions): txt = unicode(action.text()) # QString -> Python unicode if txt.startswith("*"): txt = " " + txt[1:] action.setText(txt) if i == j: action.setText("*" + txt[1:]) self.table_view_container.setCurrentIndex(i) self.filter_widgets_container.setCurrentIndex(i) if self.model is not None: self.disconnectModelSignals() self.model = self.models[i] self.current_filter_widget = self.filterWidgets[i] self.tableView = self.tableViews[i] hidden = self.model.table.meta.get("hide_in_explorer", ()) self.update_hidden_columns(hidden) try: shown = self.model.load_preset_hidden_column_names() hidden = list(set(self.model.table.getColNames()) - shown) self.update_hidden_columns(hidden) except Exception: pass self.setup_model_dependent_look() if self.allow_integration: self.model.setNonEditable("method", ["area", "rmse", "method", "params"]) for col_name in self.model.table.getColNames(): t = self.model.table.getColType(col_name) if t in (list, tuple, object, dict, set) or t is None or has_inspector(t): self.model.addNonEditable(col_name) mod = self.model postfixes = mod.table.supportedPostfixes(mod.integrationColNames()) self.integration_widget.set_postfixes(postfixes) self.setup_choose_group_column_widget(hidden) self.setup_sort_fields(hidden) self.connectModelSignals() self.updateMenubar() self.set_window_title(self.model.table) def setup_choose_group_column_widget(self, hidden_names): before = None if self.chooseGroupColumn.currentIndex() >= 0: before = str(self.chooseGroupColumn.currentText()) self.chooseGroupColumn.clear() t = self.model.table candidates = [n for (n, f) in zip(t.getColNames(), t.getColFormats()) if f is not None] visible_names = [n for n in candidates if n not in hidden_names] all_choices = ["- manual multi select -"] + sorted(visible_names) self.chooseGroupColumn.addItems(all_choices) if before is not None and before in all_choices: idx = all_choices.index(before) self.chooseGroupColumn.setCurrentIndex(idx) def setup_sort_fields(self, hidden_names): before = [] for field in self.sort_fields_widgets: if field.currentIndex() >= 0: before.append(str(field.currentText())) else: before.append(None) t = self.model.table candidates = [n for (n, f) in zip(t.getColNames(), t.getColFormats()) if f is not None] visible_names = [n for n in candidates if n not in hidden_names] all_choices = ["-"] + visible_names for field in self.sort_fields_widgets: field.clear() field.addItems(all_choices) for choice_before, field in zip(before, self.sort_fields_widgets): if choice_before is not None and choice_before in all_choices: idx = all_choices.index(choice_before) field.setCurrentIndex(idx) @protect_signal_handler def handle_model_reset(self): for name in self.model.table.getColNames(): self.current_filter_widget.update(name) def reset_sort_fields(self): for field in self.sort_fields_widgets: field.setCurrentIndex(0) @protect_signal_handler def dataChanged(self, ix1, ix2, src): minr, maxr = sorted((ix1.row(), ix2.row())) minc, maxc = sorted((ix1.column(), ix2.column())) for r in range(minr, maxr + 1): for c in range(minc, maxc + 1): idx = self.model.createIndex(r, c) self.tableView.update(idx) minc = self.model.widgetColToDataCol[minc] maxc = self.model.widgetColToDataCol[maxc] minr = self.model.widgetRowToDataRow[minr] maxr = self.model.widgetRowToDataRow[maxr] for name in self.model.table.getColNames()[minc:maxc + 1]: self.current_filter_widget.update(name) if self.has_chromatograms: # minr, maxr = sorted((ix1.row(), ix2.row())) if any(minr <= index <= maxr for index in self.model.selected_data_rows): if isinstance(src, IntegrateAction): self.plot_chromatograms(reset=False) else: self.plot_chromatograms(reset=True) self.reset_sort_fields() @protect_signal_handler def abort(self): self.result = 1 self.close() @protect_signal_handler def ok(self): self.result = 0 self.close() @protect_signal_handler def openContextMenu(self, point): idx = self.tableView.verticalHeader().logicalIndexAt(point) menu = QMenu() cloneAction = menu.addAction("Clone row") removeAction = menu.addAction("Delete row") undoInfo = self.model.infoLastAction() redoInfo = self.model.infoRedoAction() if undoInfo is not None: undoAction = menu.addAction("Undo %s" % undoInfo) if redoInfo is not None: redoAction = menu.addAction("Redo %s" % redoInfo) appearAt = self.tableView.verticalHeader().mapToGlobal(point) choosenAction = menu.exec_(appearAt) if choosenAction == removeAction: self.model.removeRows([idx]) elif choosenAction == cloneAction: self.model.cloneRow(idx) elif undoInfo is not None and choosenAction == undoAction: self.model.undoLastAction() elif redoInfo is not None and choosenAction == redoAction: self.model.redoLastAction() @protect_signal_handler def do_integrate(self, method, postfix): # QString -> Python str: method = str(method) postfix = str(postfix) rtmin, rtmax = self.eic_plotter.get_range_selection_limits() for data_row_idx in self.model.selected_data_rows: self.model.integrate(postfix, data_row_idx, method, rtmin, rtmax) @protect_signal_handler def rowClicked(self, widget_row_idx): self.select_rows_in_group(widget_row_idx) self.setup_spectrum_chooser() if self.eic_only_mode: self.plot_eics_only() if self.has_chromatograms: self.plot_chromatograms() if self.has_time_series: self.plot_time_series() def select_rows_in_group(self, widget_row_idx): group_by_idx = self.chooseGroupColumn.currentIndex() # first entry is "manual selection" if group_by_idx == 0: to_select = [idx.row() for idx in self.tableView.selectionModel().selectedRows()] self.model.set_selected_data_rows(to_select) return col_name = str(self.chooseGroupColumn.currentText()) widget_rows = self.model.rows_with_same_value(col_name, widget_row_idx) to_select = widget_rows[:200] # avoid to many rows # expand selection mode_before = self.tableView.selectionMode() scrollbar_before = self.tableView.verticalScrollBar().value() self.tableView.setSelectionMode(QAbstractItemView.MultiSelection) for i in to_select: if i != widget_row_idx: # avoid "double click !" wich de-selects current row self.tableView.selectRow(i) self.tableView.setSelectionMode(mode_before) self.tableView.verticalScrollBar().setValue(scrollbar_before) self.model.set_selected_data_rows(to_select) def setup_spectrum_chooser(self): self.choose_spec.clear() spectra = [] labels = [] if self.has_chromatograms: labels.append("spectra from peak") spectra.append(None) # place holder as ms1 spec is computed from peakmap on demand ! self.first_spec_in_choser_is_ms1 = True else: self.first_spec_in_choser_is_ms1 = False num_extra_spectra = 0 for idx in self.model.selected_data_rows: pf, s = self.model.getMS2Spectra(idx) for pfi, si in zip(pf, s): if si is not None: for sii in si: label = "spectra%s rt=%.2fm" % (pfi, sii.rt / 60.0) if sii.precursors: mz, I = sii.precursors[0] label += " pre=(%.5f, %.2e)" % (mz, I) labels.append(label) spectra.append(sii) num_extra_spectra += 1 self.spectra_listed_in_chooser = spectra self.choose_spec.setVisible(len(spectra) > 0) for label in labels: self.choose_spec.addItem(label) if self.first_spec_in_choser_is_ms1: self.choose_spec.setCurrentRow(0) else: self.choose_spec.selectAll() def plot_time_series(self): rtmin, rtmax, time_series = time_series_curves(self.model) ts_configs = configsForTimeSeries(time_series) self.ts_plotter.del_all_items() self.ts_plotter.reset() self.ts_plotter.add_time_series(time_series, ts_configs) self.ts_plotter.replot() def plot_eics_only(self): if self.eic_only_mode: rtmin, rtmax, curves = eic_curves(self.model) configs = configsForEics(curves) self.eic_plotter.reset() self.eic_plotter.add_eics(curves, configs=configs, labels=None) self.eic_plotter.replot() def plot_chromatograms(self, reset=True): self.eic_plotter.del_all_items() rtmin, rtmax, mzmin, mzmax, curves, fit_shapes = chromatograms( self.model, self.allow_integration) configs = configsForEics(curves) self.eic_plotter.add_eics(curves, configs=configs) for ((rts, iis), baseline), config in zip(fit_shapes, configs): if baseline is None: baseline = 0.0 eic_color = config["color"] color = turn_light(eic_color) self.eic_plotter.add_eic_filled(rts, iis, baseline, color) # allrts are sorted ! if rtmin is not None and rtmax is not None: w = rtmax - rtmin if w == 0: w = 30.0 # seconds if reset: self.eic_plotter.set_rt_axis_limits(rtmin - w, rtmax + w) self.eic_plotter.set_range_selection_limits(rtmin, rtmax) self.eic_plotter.reset_intensity_limits(fac=1.1, rtmin=rtmin - w, rtmax=rtmax + w) self.eic_plotter.replot() reset = reset and mzmin is not None and mzmax is not None limits = (mzmin, mzmax) if reset else None @protect_signal_handler def spectrumChosen(self): spectra = [self.spectra_listed_in_chooser[idx.row()] for idx in self.choose_spec.selectedIndexes()] labels = [str(item.data(0).toString()) for item in self.choose_spec.selectedItems()] ms2_data = [(l, s) for (l, s) in zip(labels, spectra) if s is not None and l is not None] if ms2_data: labels, spectra = zip(*ms2_data) # unzip, works only if ms2_data is not empty self.mz_plotter.plot_spectra([s.peaks for s in spectra], labels) self.mz_plotter.resetAxes() self.mz_plotter.replot() else: self.plot_ms1_spectra() def eic_selection_changed(self, rtmin, rtmax): if not self.first_spec_in_choser_is_ms1: return self.choose_spec.setCurrentRow(0) peakmaps = [ pm for idx in self.model.selected_data_rows for pm in self.model.getPeakmaps(idx)] windows = [] for idx in self.model.selected_data_rows: for (__, __, mzmin, mzmax) in self.model.getEICWindows(idx): window = (rtmin, rtmax, mzmin, mzmax) windows.append(window) self.plot_spectra_from_peakmaps(peakmaps, windows) def plot_ms1_spectra(self): peakmaps = [ pm for idx in self.model.selected_data_rows for pm in self.model.getPeakmaps(idx)] windows = [ w for idx in self.model.selected_data_rows for w in self.model.getEICWindows(idx)] self.plot_spectra_from_peakmaps(peakmaps, windows) def plot_ms2_spectra(self, spectra, labels): self.mz_plotter.plot_spectra([s.peaks for s in spectra], labels) self.mz_plotter.resetAxes() self.mz_plotter.replot() def plot_spectra_from_peakmaps(self, peakmaps, windows): if not peakmaps or not windows: return data = [] mzs = [] for (rtmin, rtmax, mzmin, mzmax), pm in zip(windows, peakmaps): mzs.append(mzmin) mzs.append(mzmax) data.append((pm, rtmin, rtmax, mzmin, mzmax, 3000)) mzmin = min(mzs) mzmax = max(mzs) configs = configsForSpectra(len(peakmaps)) postfixes = self.model.table.supportedPostfixes(self.model.eicColNames()) titles = map(repr, postfixes) self.mz_plotter.plot_peakmaps(data, configs, titles if len(titles) > 1 else None) self.mz_plotter.reset_mz_limits(mzmin, mzmax) self.mz_plotter.replot()