Esempio n. 1
0
class FitterMain(QMainWindow):
    def __init__(self, model, data, fit=True, fit_kws={}):
        QMainWindow.__init__(self)
        layout = QSplitter(Qt.Vertical, self)
        self.canvas = MPLCanvas(self)
        self.toolbar = MPLToolbar(self.canvas, self)
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        self.fitter = Fitter(self, standalone=True, fit_kws=fit_kws)
        self.canvas.mpl_connect('button_press_event',
                                self.fitter.on_canvas_pick)
        self.fitter.closeRequest.connect(self.close)
        self.fitter.replotRequest.connect(self.replot)
        self.fitter.initialize(model, data, fit)
        layout.addWidget(self.fitter)
        self.setCentralWidget(layout)
        self.setWindowTitle('Fitting: data %s' % data.name)

    def replot(self, limits=True):
        plotter = self.canvas.plotter
        plotter.reset(limits=limits)
        try:
            plotter.plot_data(self.fitter.data)
            plotter.plot_model_full(self.fitter.model, self.fitter.data)
        except Exception:
            logger.exception('Error while plotting')
        else:
            plotter.draw()
Esempio n. 2
0
class UFitMain(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.itempanels = {}
        self.multipanels = {}
        self.current_panel = None
        self.inspector_window = None
        self.annotation_window = None

        self.sgroup = SettingGroup('main')
        loadUi(self, 'main.ui')

        self.menuRecent.aboutToShow.connect(self.on_menuRecent_aboutToShow)
        self.menuMoveToGroup = QMenu('Move selected to group', self)
        self.menuMoveToGroup.setIcon(QIcon(':/drawer-open.png'))
        self.menuMoveToGroup.aboutToShow.connect(
            self.on_menuMoveToGroup_aboutToShow)
        self.menuCopyToGroup = QMenu('Copy selected to group', self)
        self.menuCopyToGroup.setIcon(QIcon(':/drawer-double-open.png'))
        self.menuCopyToGroup.aboutToShow.connect(
            self.on_menuCopyToGroup_aboutToShow)
        self.menuRemoveGroup = QMenu('Remove group', self)
        self.menuRemoveGroup.setIcon(QIcon(':/drawer--minus.png'))
        self.menuRemoveGroup.aboutToShow.connect(
            self.on_menuRemoveGroup_aboutToShow)
        self.menuRenameGroup = QMenu('Rename group', self)
        self.menuRenameGroup.aboutToShow.connect(
            self.on_menuRenameGroup_aboutToShow)

        # populate plot view
        layout2 = QVBoxLayout()
        layout2.setContentsMargins(0, 0, 0, 0)
        self.canvas = MPLCanvas(self, maincanvas=True)
        self.canvas.replotRequest.connect(
            lambda: self.current_panel.plot(True))
        self.canvas.mpl_connect('button_press_event', self.on_canvas_pick)
        self.canvas.mpl_connect('pick_event', self.on_canvas_pick)
        self.canvas.mpl_connect('button_release_event', self.on_canvas_click)
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('mainplottoolbar')
        self.toolbar.popoutRequested.connect(self.on_actionPopOut_triggered)
        self.addToolBar(self.toolbar)
        layout2.addWidget(self.canvas)
        self.plotframe.setLayout(layout2)

        # session events
        session.itemsUpdated.connect(self.on_session_itemsUpdated)
        session.itemAdded.connect(self.on_session_itemAdded)
        session.filenameChanged.connect(self.on_session_filenameChanged)
        session.dirtyChanged.connect(self.on_session_dirtyChanged)

        # create data loader
        self.dloader = DataLoader(self, self.canvas.plotter)
        self.dloader.newDatas.connect(self.on_dloader_newDatas)
        self.stacker.addWidget(self.dloader)
        self.current_panel = self.dloader

        # create item model
        self.itemlistmodel = ItemListModel()
        self.itemTree.setModel(self.itemlistmodel)
        self.itemlistmodel.reset()
        self.itemTree.addAction(self.actionMergeData)
        self.itemTree.addAction(self.actionRemoveData)
        self.itemTree.addAction(self.menuMoveToGroup.menuAction())
        self.itemTree.addAction(self.menuCopyToGroup.menuAction())
        self.itemTree.newSelection.connect(self.on_itemTree_newSelection)

        # backend selector
        self.backend_group = QActionGroup(self)
        for backend in backends.available:
            action = QAction(backend.backend_name, self)
            action.setCheckable(True)
            if backends.backend.backend_name == backend.backend_name:
                action.setChecked(True)
            self.backend_group.addAction(action)
            self.menuBackend.addAction(action)
            action.triggered.connect(self.on_backend_action_triggered)

        # manage button
        menu = QMenu(self)
        menu.addAction(self.actionMergeData)
        menu.addAction(self.actionRemoveData)
        menu.addMenu(self.menuMoveToGroup)
        menu.addMenu(self.menuCopyToGroup)
        menu.addAction(self.actionReorder)
        menu.addSeparator()
        menu.addAction(self.actionNewGroup)
        menu.addMenu(self.menuRenameGroup)
        menu.addMenu(self.menuRemoveGroup)
        self.manageBtn.setMenu(menu)

        # right-mouse-button menu for canvas
        menu = self.canvasMenu = QMenu(self)
        menu.addAction(self.actionUnzoom)
        menu.addAction(self.actionHideFit)
        menu.addSeparator()
        menu.addAction(self.actionDrawSymbols)
        menu.addAction(self.actionConnectData)
        menu.addAction(self.actionSmoothImages)
        menu.addSeparator()
        menu.addAction(self.actionDrawGrid)
        menu.addAction(self.actionShowLegend)

        # restore window state
        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)
            splitstate = settings.value('splitstate', QByteArray())
            self.splitter.restoreState(splitstate)
            vsplitstate = settings.value('vsplitstate', QByteArray())
            self.vsplitter.restoreState(vsplitstate)
            self.recent_files = settings.value('recentfiles', []) or []

    def on_dloader_newDatas(self, datas, ingroup):
        for group in session.groups:
            if group.name == ingroup:
                break
        else:
            group = session.add_group(ingroup)
        items = []
        for data in datas:
            if isinstance(data, ScanData):
                items.append(ScanDataItem(data))
            elif isinstance(data, ImageData):
                items.append(ImageDataItem(data))
            else:
                logger.warning('unknown data type: %s' % data)
        session.add_items(items, group)

    def on_session_itemsUpdated(self):
        # remove all panels whose item has vanished
        for item, panel in listitems(self.itempanels):
            if item not in session.all_items:
                self.stacker.removeWidget(panel)
                del self.itempanels[item]

    def on_session_itemAdded(self, item):
        # a single item has been added, show it
        self.itemTree.setCurrentIndex(self.itemlistmodel.index_for_item(item))

    def on_session_filenameChanged(self):
        if session.filename:
            self.setWindowTitle('ufit - %s[*]' % session.filename)
            self._add_recent_file(session.filename)
        else:
            self.setWindowTitle('ufit[*]')

    def on_session_dirtyChanged(self, dirty):
        self.setWindowModified(dirty)

    def _add_recent_file(self, fname):
        """Add to recent file list."""
        if not fname:
            return
        if fname in self.recent_files:
            self.recent_files.remove(fname)
        self.recent_files.insert(0, fname)
        if len(self.recent_files) > max_recent_files:
            self.recent_files.pop(-1)

    def on_menuRecent_aboutToShow(self):
        """Update recent file menu"""
        recent_files = []
        for fname in self.recent_files:
            if fname != session.filename and path.isfile(fname):
                recent_files.append(fname)
        self.menuRecent.clear()
        if recent_files:
            for i, fname in enumerate(recent_files):
                action = QAction('%d - %s' % (i + 1, fname), self)
                action.triggered.connect(self.load_session)
                action.setData(fname)
                self.menuRecent.addAction(action)
        self.actionClearRecent.setEnabled(len(recent_files) > 0)
        self.menuRecent.addSeparator()
        self.menuRecent.addAction(self.actionClearRecent)

    @pyqtSlot()
    def on_actionClearRecent_triggered(self):
        """Clear recent files list"""
        self.recent_files = []

    def select_new_panel(self, panel):
        if panel is not self.current_panel:
            if hasattr(self.current_panel, 'save_limits'):
                self.current_panel.save_limits()
            self.current_panel = panel
        self.stacker.setCurrentWidget(self.current_panel)  # doesn't hurt

    def on_canvas_pick(self, event):
        if isinstance(self.current_panel, ScanDataPanel):
            self.current_panel.on_canvas_pick(event)

    def on_canvas_click(self, event):
        if event.button == 3 and not self.toolbar.mode:  # right button
            self.canvasMenu.popup(QCursor.pos())

    @pyqtSlot()
    def on_loadBtn_clicked(self):
        self.select_new_panel(self.dloader)
        self.itemTree.setCurrentIndex(QModelIndex())

    @pyqtSlot()
    def on_actionNewGroup_triggered(self):
        name = QInputDialog.getText(
            self, 'ufit', 'Please enter a name '
            'for the new group:')[0]
        if not name:
            return
        session.add_group(name)

    def on_menuMoveToGroup_aboutToShow(self):
        self.menuMoveToGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def move_to(_arg=None, group=group):
                items = self.selected_items()
                if not items:
                    return
                session.move_items(items, group)
                self.re_expand_tree()

            action.triggered.connect(move_to)
            self.menuMoveToGroup.addAction(action)

    def on_menuCopyToGroup_aboutToShow(self):
        self.menuCopyToGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def copy_to(_arg=None, group=group):
                items = self.selected_items()
                if not items:
                    return
                session.copy_items(items, group)
                self.re_expand_tree()

            action.triggered.connect(copy_to)
            self.menuCopyToGroup.addAction(action)

    def on_menuRemoveGroup_aboutToShow(self):
        self.menuRemoveGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def remove(_arg=None, group=group):
                session.remove_group(group)
                self.re_expand_tree()

            action.triggered.connect(remove)
            self.menuRemoveGroup.addAction(action)

    def on_menuRenameGroup_aboutToShow(self):
        self.menuRenameGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def rename(_arg=None, group=group):
                name = QInputDialog.getText(self,
                                            'ufit', 'Please enter a new name '
                                            'for the group:',
                                            text=group.name)[0]
                if not name:
                    return
                session.rename_group(group, name)

            action.triggered.connect(rename)
            self.menuRenameGroup.addAction(action)

    @pyqtSlot()
    def on_actionRemoveData_triggered(self):
        items = self.selected_items()
        if not items:
            return
        if QMessageBox.question(
                self, 'ufit', 'OK to remove %d item(s)?' % len(items),
                QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
            return
        session.remove_items(items)
        self.re_expand_tree()

    @pyqtSlot()
    def on_actionReorder_triggered(self):
        dlg = QDialog(self)
        loadUi(dlg, 'reorder.ui')
        data2obj = dlg.itemList.populate()
        if not dlg.exec_():
            return
        new_structure = []
        for i in range(dlg.itemList.count()):
            new_index = dlg.itemList.item(i).type()
            obj = data2obj[new_index]
            if isinstance(obj, ItemGroup):
                new_structure.append((obj, []))
            else:
                if not new_structure:
                    QMessageBox.warning(
                        self, 'ufit', 'Reordering invalid: every data item '
                        'must be below a group')
                    return
                new_structure[-1][1].append(obj)
        session.reorder_groups(new_structure)
        self.re_expand_tree()

    def selected_items(self, itemcls=SessionItem):
        """Return a list of selected items that belong to the given class."""
        items = (index.internalPointer()
                 for index in self.itemTree.selectedIndexes())
        if not itemcls:
            # This will also return ItemGroup objects!
            return list(items)
        return [item for item in items if isinstance(item, itemcls)]

    def on_itemTree_newSelection(self):
        items = self.selected_items(None)
        if len(items) == 1 and isinstance(items[0], ItemGroup):
            # a group is selected -- act as if we selected all items
            items = items[0].items
        if len(items) == 0:
            self.on_loadBtn_clicked()
        elif len(items) == 1:
            item = items[0]
            if item not in self.itempanels:
                panel = self.itempanels[item] = \
                    item.create_panel(self, self.canvas)
                self.stacker.addWidget(panel)
            else:
                panel = self.itempanels[item]
            self.select_new_panel(panel)
            panel.plot(panel.get_saved_limits())
            self.toolbar.update()
            if self.inspector_window and isinstance(item, ScanDataItem):
                self.inspector_window.setDataset(item.data)
        else:
            paneltype = type(items[0])
            if not hasattr(paneltype, 'create_multi_panel'):
                return
            if paneltype not in self.multipanels:
                panel = self.multipanels[paneltype] = \
                    items[0].create_multi_panel(self, self.canvas)
                self.stacker.addWidget(panel)
            else:
                panel = self.multipanels[paneltype]
            panel.initialize(items)
            self.select_new_panel(panel)
            panel.plot()

    def on_itemTree_expanded(self, index):
        index.internalPointer().expanded = True
        session.set_dirty()

    def on_itemTree_collapsed(self, index):
        index.internalPointer().expanded = False
        session.set_dirty()

    def re_expand_tree(self):
        # expand / collapse all groups according to last saved state
        for group in session.groups:
            if group.expanded:
                self.itemTree.expand(self.itemlistmodel.index_for_group(group))
            else:
                self.itemTree.collapse(
                    self.itemlistmodel.index_for_group(group))

    @pyqtSlot()
    def on_actionInspector_triggered(self):
        if self.inspector_window:
            self.inspector_window.activateWindow()
            return
        self.inspector_window = InspectorWindow(self)

        def deref():
            self.inspector_window = None

        self.inspector_window.replotRequest.connect(
            lambda: self.current_panel.plot(True))
        self.inspector_window.closed.connect(deref)
        if isinstance(self.current_panel, ScanDataPanel):
            self.inspector_window.setDataset(self.current_panel.item.data)
        self.inspector_window.show()

    @pyqtSlot()
    def on_actionAnnotations_triggered(self):
        if self.annotation_window:
            self.annotation_window.activateWindow()
            return
        self.annotation_window = AnnotationWindow(self)

        def deref():
            self.annotation_window = None

        self.annotation_window.closed.connect(deref)
        self.annotation_window.show()

    @pyqtSlot()
    def on_actionLoadData_triggered(self):
        self.on_loadBtn_clicked()

    def on_actionHideFit_toggled(self, on):
        self.canvas.plotter.no_fits = on
        self.current_panel.plot()

    def on_actionConnectData_toggled(self, on):
        self.canvas.plotter.lines = on
        self.current_panel.plot()

    def on_actionDrawSymbols_toggled(self, on):
        self.canvas.plotter.symbols = on
        self.current_panel.plot()

    def on_actionShowLegend_toggled(self, on):
        self.canvas.plotter.legend = on
        self.current_panel.plot()

    def on_actionDrawGrid_toggled(self, on):
        self.canvas.plotter.grid = on
        self.current_panel.plot()

    def on_actionSmoothImages_toggled(self, on):
        self.canvas.plotter.imgsmoothing = on
        self.current_panel.plot()

    def _get_export_filename(self, filter='ASCII text (*.txt)'):
        initialdir = session.props.get('lastexportdir', session.dirname)
        filename, _ = QFileDialog.getSaveFileName(self,
                                                  'Select export file name',
                                                  initialdir, filter)
        if filename == '':
            return ''
        expfilename = path_to_str(filename)
        session.props.lastexportdir = path.dirname(expfilename)
        return expfilename

    @pyqtSlot()
    def on_actionExportASCII_triggered(self):
        expfilename = self._get_export_filename()
        if expfilename:
            self.current_panel.export_ascii(expfilename)

    @pyqtSlot()
    def on_actionExportFIT_triggered(self):
        expfilename = self._get_export_filename()
        if expfilename:
            self.current_panel.export_fits(expfilename)

    @pyqtSlot()
    def on_actionExportPython_triggered(self):
        expfilename = self._get_export_filename('Python files (*.py)')
        if expfilename:
            self.current_panel.export_python(expfilename)

    @pyqtSlot()
    def on_actionExportParams_triggered(self):
        items = self.selected_items(ScanDataItem)
        dlg = ParamExportDialog(self, items)
        if dlg.exec_() != QDialog.Accepted:
            return
        expfilename = self._get_export_filename()
        if expfilename:
            try:
                dlg.do_export(expfilename)
            except Exception as e:
                logger.exception('While exporting parameters')
                QMessageBox.warning(self, 'Error', 'Could not export '
                                    'parameters: %s' % e)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        self.canvas.print_()

    @pyqtSlot()
    def on_actionPopOut_triggered(self):
        new_win = QMainWindow()
        canvas = MPLCanvas(new_win)
        toolbar = MPLToolbar(canvas, new_win)
        new_win.addToolBar(toolbar)
        splitter = QSplitter(Qt.Vertical, new_win)
        splitter.addWidget(canvas)
        try:
            from ufit.gui.console import QIPythonWidget
        except ImportError:
            lbl = QLabel(
                'Please install IPython with qtconsole to '
                'activate the console part of this window.', new_win)
            splitter.addWidget(lbl)
        else:
            iw = QIPythonWidget('interactive pylab plotting prompt', new_win)
            iw.pushVariables({'ax': canvas.axes})
            iw.executeCommand('from ufit.lab import *')
            iw.executeCommand('sca(ax)')
            iw.setFocus()
            iw.redrawme.connect(canvas.draw_idle)
            splitter.addWidget(iw)
        new_win.setCentralWidget(splitter)
        self.current_panel.plot(limits=None, canvas=canvas)
        new_win.show()

    @pyqtSlot()
    def on_actionSavePlot_triggered(self):
        self.toolbar.save_figure()

    @pyqtSlot()
    def on_actionUnzoom_triggered(self):
        self.toolbar.home()

    def check_save(self):
        if not self.isWindowModified():  # nothing there to be saved
            return True
        resp = QMessageBox.question(
            self, 'ufit', 'Save current session?\n%s' % session.filename,
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if resp == QMessageBox.Yes:
            return self.save_session()
        elif resp == QMessageBox.No:
            return True
        return False

    @pyqtSlot()
    def on_actionNewSession_triggered(self):
        if not self.check_save():
            return
        session.clear()
        self.on_loadBtn_clicked()

    @pyqtSlot()
    def on_actionLoad_triggered(self):
        if not self.check_save():
            return
        initialdir = session.dirname
        if not initialdir:
            with self.sgroup as settings:
                initialdir = settings.value('loadfiledirectory', '')
        filename, _ = QFileDialog.getOpenFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return
        self.load_session(path_to_str(filename))

    @pyqtSlot()
    def on_actionInsert_triggered(self):
        initialdir = session.dirname
        if not initialdir:
            with self.sgroup as settings:
                initialdir = settings.value('loadfiledirectory', '')
        filename, _ = QFileDialog.getOpenFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return
        self.insert_session(path_to_str(filename))

    def load_session(self, filename=None):
        if not filename:
            # Recent files action
            action = self.sender()
            if isinstance(action, QAction):
                if not self.check_save():
                    return
                filename = action.data()
        try:
            session.load(filename)
            with self.sgroup as settings:
                settings.setValue('loadfiledirectory', path.dirname(filename))
        except Exception as err:
            logger.exception('Loading session %r failed' % filename)
            QMessageBox.warning(self, 'Error', 'Loading failed: %s' % err)
        else:
            self.re_expand_tree()
            self.setWindowModified(False)
            # if there are annotations, show the window automatically
            if session.props.get('annotations'):
                self.on_actionAnnotations_triggered()

    def insert_session(self, filename):
        temp_session.load(filename)
        basename = path.basename(filename)
        if basename.endswith('.ufit'):
            basename = basename[:-5]
        # XXX slight HACK
        for group in temp_session.groups:
            session.add_group(group.name + ' (from %s)' % basename)
            for item in group.items:
                session.add_item(item)

    @pyqtSlot()
    def on_actionSave_triggered(self):
        self.save_session()

    @pyqtSlot()
    def on_actionSaveAs_triggered(self):
        self.save_session_as()

    @pyqtSlot()
    def on_actionQExplorer_triggered(self):
        ReciprocalViewer(self)

    def save_session(self):
        if session.filename is None:
            return self.save_session_as()
        try:
            session.save()
        except Exception as err:
            logger.exception('Saving session failed')
            QMessageBox.warning(self, 'Error', 'Saving failed: %s' % err)
            return False
        return True

    def save_session_as(self):
        initialdir = session.dirname
        filename, _ = QFileDialog.getSaveFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return False
        session.set_filename(path_to_str(filename))
        try:
            session.save()
        except Exception as err:
            logger.exception('Saving session failed')
            QMessageBox.warning(self, 'Error', 'Saving failed: %s' % err)
            return False
        return True

    @pyqtSlot()
    def on_actionMergeData_triggered(self):
        items = self.selected_items(ScanDataItem)
        if len(items) < 2:
            return
        dlg = QDialog(self)
        loadUi(dlg, 'rebin.ui')
        if dlg.exec_():
            try:
                precision = float(dlg.precisionEdit.text())
            except ValueError:
                return
            datalist = [i.data for i in items]
            new_data = datalist[0].merge(precision, *datalist[1:])
            session.add_item(ScanDataItem(new_data), self.items[-1].group)

    @pyqtSlot()
    def on_actionQuit_triggered(self):
        self.close()

    def closeEvent(self, event):
        if not self.check_save():
            event.ignore()
            return
        event.accept()
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
            settings.setValue('splitstate', self.splitter.saveState())
            settings.setValue('vsplitstate', self.vsplitter.saveState())
            settings.setValue('recentfiles', self.recent_files)

    @pyqtSlot()
    def on_actionAbout_triggered(self):
        dlg = QDialog(self)
        dlg.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
        loadUi(dlg, 'about.ui')
        dlg.lbl.setText(dlg.lbl.text().replace('VERSION', __version__))
        dlg.exec_()

    @pyqtSlot()
    def on_backend_action_triggered(self):
        backends.set_backend(str(self.sender().text()))