예제 #1
0
class InspectorWindow(QMainWindow):
    replotRequest = pyqtSignal(object)
    closed = pyqtSignal()

    def __init__(self, parent):
        self._updating = False
        self._data = None
        QMainWindow.__init__(self, parent)
        loadUi(self, 'inspector.ui')
        # layout = QVBoxLayout()
        # layout.setContentsMargins(0, 0, 0, 0)
        # layout.addWidget(self.canvas)
        self.sgroup = SettingGroup('inspector')
        self.tbl.verticalHeader().setDefaultSectionSize(
            self.tbl.verticalHeader().minimumSectionSize() + 2)

        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)

    def setDataset(self, data):
        self.data = data
        self.dataName.setText('%s - %s' % (data.name, data.title))
        self._updating = True
        self.tbl.setRowCount(len(data.meta))
        for i, key in enumerate(sorted(data.meta, key=lambda n: n.lower())):
            key_item = QTableWidgetItem(key)
            key_item.setFlags(key_item.flags() & ~Qt.ItemIsEditable)
            self.tbl.setItem(i, 0, key_item)
            if key.startswith('col_'):
                value_item = QTableWidgetItem(str(data.meta[key]))
                value_item.setFlags(value_item.flags() & ~Qt.ItemIsEditable)
            else:
                value_item = QTableWidgetItem(srepr(data.meta[key]))
            self.tbl.setItem(i, 1, value_item)
        self._updating = False

    def closeEvent(self, event):
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
        self.closed.emit()
        return QMainWindow.closeEvent(self, event)

    def on_tbl_itemChanged(self, item):
        if self._updating:
            return
        try:
            new_value = eval(str(item.text()))
        except Exception:
            QMessageBox.error(self, 'Error',
                              'The new value is not a valid expression.')
            return
        else:
            key = str(self.tbl.item(item.row(), 0).text())
            self.data.meta[key] = new_value
        self.replotRequest.emit(None)
        session.set_dirty()
예제 #2
0
class QIPythonWidget(RichIPythonWidget):
    """Convenience class for a live IPython console widget."""

    closeme = pyqtSignal()
    redrawme = pyqtSignal()

    def __init__(self, customBanner=None, *args, **kwargs):
        if customBanner is not None:
            self.banner = customBanner
        super(QIPythonWidget, self).__init__(*args, **kwargs)
        self.kernel_manager = kernel_manager = QtInProcessKernelManager()
        kernel_manager.start_kernel()
        kernel_manager.kernel.gui = 'qt4'
        self.kernel_client = kernel_client = self._kernel_manager.client()
        kernel_client.start_channels()

        def stop():
            kernel_client.stop_channels()
            kernel_manager.shutdown_kernel()
            self.closeme.emit()

        self.exit_requested.connect(stop)

        def redraw():
            self.redrawme.emit()

        self.executed.connect(redraw)

    def pushVariables(self, variableDict):
        """Given a dictionary containing name / value pairs, push those
        variables to the IPython console widget.
        """
        self.kernel_manager.kernel.shell.push(variableDict)

    def clearTerminal(self):
        """Clears the terminal."""
        self._control.clear()

    def printText(self, text):
        """Prints some plain text to the console."""
        self._append_plain_text(text)

    def executeCommand(self, command):
        """Execute a command in the frame of the console widget."""
        self._execute(command, True)
예제 #3
0
class ItemTreeView(QTreeView):
    newSelection = pyqtSignal()

    def __init__(self, parent):
        QTreeView.__init__(self, parent)
        self.header().hide()
        # self.setRootIsDecorated(False)
        # self.setStyleSheet("QTreeView::branch { display: none; }")
        self.setItemDelegate(ItemListDelegate(self))
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)

    def selectionChanged(self, selected, deselected):
        self.newSelection.emit()
        QTreeView.selectionChanged(self, selected, deselected)
예제 #4
0
class AnnotationWindow(QMainWindow):
    closed = pyqtSignal()

    def __init__(self, parent):
        self._updating = False
        self._data = None
        QMainWindow.__init__(self, parent)
        loadUi(self, 'annotations.ui')
        self.sgroup = SettingGroup('annotations')

        session.propsRequested.connect(self.on_session_propsRequested)
        session.propsUpdated.connect(self.on_session_propsUpdated)

        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)

        self._editing = True
        self.on_session_propsUpdated()

    def on_textBox_textChanged(self):
        if self._editing:
            session.set_dirty()

    def on_session_propsRequested(self):
        session.props.annotations = self.textBox.toPlainText()

    def on_session_propsUpdated(self):
        if 'annotations' in session.props:
            self._editing = False
            self.textBox.setText(session.props.annotations)
            self._editing = True

    def closeEvent(self, event):
        self.on_session_propsRequested()
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
        self.closed.emit()
        return QMainWindow.closeEvent(self, event)
예제 #5
0
class DataOps(QWidget):
    replotRequest = pyqtSignal(object)
    pickRequest = pyqtSignal(object)
    titleChanged = pyqtSignal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.item = None
        self.data = None
        self.picking = None
        self.picked_points = []

        loadUi(self, 'dataops.ui')
        self.pickedLbl.hide()

    def initialize(self, item):
        self.data = item.data
        self.model = item.model
        self.item = item
        if self.data.fitmin is not None:
            self.limitminEdit.setText('%.5g' % self.data.fitmin)
        if self.data.fitmax is not None:
            self.limitmaxEdit.setText('%.5g' % self.data.fitmax)
        self.monscaleEdit.setText(str(self.data.nscale))
        self.titleEdit.setText(self.data.title)
        self.nameEdit.setText(self.data.name)
        self.fftNpointsEdit.setText(str(len(self.data.x) * 4))

    def on_canvas_pick(self, event):
        if not hasattr(event, 'artist'):
            return
        if self.picking:
            xdata = event.artist.get_xdata()[event.ind]
            ydata = event.artist.get_ydata()[event.ind]
            self.picked_points.append(xdata)
            self.pickedLbl.setText('%d picked' % len(self.picked_points))
            event.canvas.figure.gca().plot([xdata], [ydata],
                                           'ow',
                                           ms=8,
                                           mec='blue')
            event.canvas.draw()

    @pyqtSlot()
    def on_badResetBtn_clicked(self):
        self.data.reset_mask()
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_badPointsBtn_clicked(self):
        if self.picking == 'bad':
            self.badPointsBtn.setText('Start')
            self.pickedLbl.hide()
            self.picking = None
            self.removeBadPoints(self.picked_points)
        elif not self.picking:
            self.badPointsBtn.setText('Click points on plot, then '
                                      'here to finish')
            self.pickRequest.emit(self)
            self.picking = 'bad'
            self.picked_points = []
            self.pickedLbl.setText('0 picked')
            self.pickedLbl.show()

    @pyqtSlot()
    def on_limitsBtn_clicked(self):
        try:
            limitmin = float(self.limitminEdit.text())
        except ValueError:
            limitmin = None
        try:
            limitmax = float(self.limitmaxEdit.text())
        except ValueError:
            limitmax = None
        self.data.fitmin, self.data.fitmax = limitmin, limitmax
        self.replotRequest.emit(None)
        session.set_dirty()

    def removeBadPoints(self, points):
        """'Remove' bad data points (just mask them out)."""
        for point in points:
            self.data.mask[self.data.x == point] = False
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_rebinBtn_clicked(self):
        try:
            binsize = float(self.precisionEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error', 'Enter a valid precision.')
            return
        new_array, new_meta = rebin(self.data._data, binsize, self.data.meta)
        self.data.__init__(new_meta,
                           new_array,
                           self.data.xcol,
                           self.data.ycol,
                           self.data.ncol,
                           self.data.nscale,
                           name=self.data.name,
                           sources=self.data.sources)
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_floatmergeBtn_clicked(self):
        try:
            binsize = float(self.precisionEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error', 'Enter a valid precision.')
            return
        new_array, new_meta = floatmerge(self.data._data, binsize,
                                         self.data.meta)
        self.data.__init__(new_meta,
                           new_array,
                           self.data.xcol,
                           self.data.ycol,
                           self.data.ncol,
                           self.data.nscale,
                           name=self.data.name,
                           sources=self.data.sources)
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_cloneBtn_clicked(self):
        new_data = self.data.copy()
        new_model = self.model.copy()
        from ufit.gui.scanitem import ScanDataItem
        session.add_item(ScanDataItem(new_data, new_model), self.item.group)

    @pyqtSlot()
    def on_arbBtn_clicked(self):
        dlg = QDialog(self)
        loadUi(dlg, 'change.ui')
        with SettingGroup('main') as settings:
            dlg.xEdit.setText(settings.value('changex', 'x'))
            dlg.yEdit.setText(settings.value('changey', 'y'))
            dlg.dyEdit.setText(settings.value('changedy', 'dy'))
            if dlg.exec_() != QDialog.Accepted:
                return
            settings.setValue('changex', dlg.xEdit.text())
            settings.setValue('changey', dlg.yEdit.text())
            settings.setValue('changedy', dlg.dyEdit.text())
        xfml = dlg.xEdit.text()
        yfml = dlg.yEdit.text()
        dyfml = dlg.dyEdit.text()
        new_x = []
        new_y = []
        new_dy = []
        ns = {}
        for dpoint in zip(self.data.x, self.data.y_raw, self.data.dy_raw):
            ns.update(x=dpoint[0], y=dpoint[1], dy=dpoint[2])
            new_x.append(eval(xfml, ns))
            new_y.append(eval(yfml, ns))
            new_dy.append(eval(dyfml, ns))
        self.data.x[:] = new_x
        self.data.y_raw[:] = new_y
        self.data.dy_raw[:] = new_dy
        self.data.y = self.data.y_raw / self.data.norm
        self.data.dy = self.data.dy_raw / self.data.norm
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_mulBtn_clicked(self):
        try:
            const = float(self.scaleConstEdit.text())
        except ValueError:
            return
        self.data.y *= const
        self.data.y_raw *= const
        self.data.dy *= const
        self.data.dy_raw *= const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_addBtn_clicked(self):
        try:
            const = float(self.addConstEdit.text())
        except ValueError:
            return
        self.data.y += const
        self.data.y_raw += const * self.data.norm
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_scaleXBtn_clicked(self):
        try:
            const = float(self.scaleXConstEdit.text())
        except ValueError:
            return
        self.data.x *= const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_shiftBtn_clicked(self):
        try:
            const = float(self.shiftConstEdit.text())
        except ValueError:
            return
        self.data.x += const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_monscaleBtn_clicked(self):
        try:
            const = int(self.monscaleEdit.text())
        except ValueError:
            return
        self.data.rescale(const)
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_titleBtn_clicked(self):
        self.data.meta.title = str(self.titleEdit.text())
        self.titleChanged.emit()
        session.set_dirty()
        self.replotRequest.emit(None)

    @pyqtSlot()
    def on_nameBtn_clicked(self):
        self.data.name = str(self.nameEdit.text())
        session.set_dirty()
        self.replotRequest.emit(None)

    @pyqtSlot()
    def on_subtractBtn_clicked(self):
        from ufit.gui.scanitem import ScanDataItem
        dlg = QDialog(self)
        loadUi(dlg, 'subtract.ui')
        data2obj = dlg.setList.populate(ScanDataItem)
        if dlg.exec_() != QDialog.Accepted:
            return
        witems = dlg.setList.selectedItems()
        if not witems:
            return
        try:
            prec = float(dlg.precisionEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error',
                                'Please enter a valid precision.')
            return

        new_data = self.data.subtract(data2obj[witems[0].type()].data, prec,
                                      dlg.destructBox.isChecked())

        if not dlg.destructBox.isChecked():
            new_model = self.model.copy()
            from ufit.gui.scanitem import ScanDataItem
            session.add_item(ScanDataItem(new_data, new_model),
                             self.item.group)
        else:
            self.replotRequest.emit(None)
            session.set_dirty()

    @pyqtSlot()
    def on_fftBtn_clicked(self):
        try:
            npoints = int(self.fftNpointsEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error',
                                'Please enter a valid number of points.')
            return
        xmin = self.data.x.min()
        xmax = self.data.x.max()
        xinterp = linspace(xmin, xmax, npoints)
        yinterp = interp1d(self.data.x, self.data.y, kind='linear')
        yfft = fft(yinterp(xinterp))
        p2 = abs(yfft) / npoints
        p1 = p2[:npoints // 2 + 2]
        p1[1:-1] *= 2
        dx = (xmax - xmin) / (npoints - 1)

        new_data = ScanData.from_arrays(name='FFT(' + self.data.name + ')',
                                        x=(1. / dx) *
                                        arange(npoints // 2 + 2) / npoints,
                                        y=p1,
                                        dy=0.01 * ones(p1.shape),
                                        xcol='1/' + self.data.xaxis,
                                        ycol='|P1|')
        new_model = self.model.copy()
        from ufit.gui.scanitem import ScanDataItem
        session.add_item(ScanDataItem(new_data, new_model), self.item.group)
예제 #6
0
class MPLCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""

    logzChanged = pyqtSignal()
    replotRequest = pyqtSignal()

    def __init__(self, parent, width=10, height=6, dpi=72, maincanvas=False):
        fig = Figure(figsize=(width, height), dpi=dpi)
        fig.set_facecolor('white')
        self.print_width = 0
        self.main = parent
        self.logz = False
        self.axes = fig.add_subplot(111)
        self.plotter = DataPlotter(self, self.axes)
        # make tight_layout do the right thing
        self.axes.set_xlabel('x')
        self.axes.set_ylabel('y')
        self.axes.set_title('(data title)\n(info)')
        FigureCanvas.__init__(self, fig)

        # create a figure manager so that we can use pylab commands on the
        # main viewport
        def make_active(event):
            Gcf.set_active(self.manager)

        self.manager = FigureManagerQT(self, 1)
        self.manager._cidgcf = self.mpl_connect('button_press_event',
                                                make_active)
        Gcf.set_active(self.manager)
        self.setParent(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.updateGeometry()
        # actually get key events
        self.setFocusPolicy(Qt.StrongFocus)
        self.mpl_connect('key_press_event', self.key_press)
        # These will not do anything in standalone mode, but do not hurt.
        if maincanvas:
            session.propsRequested.connect(self.on_session_propsRequested)
            session.propsUpdated.connect(self.on_session_propsUpdated)

    def on_session_propsRequested(self):
        session.props.canvas_logz = self.logz

    def on_session_propsUpdated(self):
        if 'canvas_logz' in session.props:
            self.logz = session.props.canvas_logz
            self.logzChanged.emit()

    def key_press(self, event):
        if key_press_handler:
            key_press_handler(event, self)

    def resizeEvent(self, event):
        # reimplemented to add tight_layout()
        w = event.size().width()
        h = event.size().height()
        dpival = float(self.figure.dpi)
        winch = w / dpival
        hinch = h / dpival
        self.figure.set_size_inches(winch, hinch)
        try:
            self.figure.tight_layout(pad=2)
        except Exception:
            pass
        self.plotter.save_layout()
        self.draw()
        self.update()
        QWidget.resizeEvent(self, event)

    def print_(self):
        sio = BytesIO()
        self.print_figure(sio, format='svg')
        svg = QSvgRenderer(QByteArray(sio.getvalue()))
        sz = svg.defaultSize()
        aspect = sz.width() / float(sz.height())

        printer = QPrinter(QPrinter.HighResolution)
        printer.setOrientation(QPrinter.Landscape)

        dlg = QDialog(self)
        loadUi(dlg, 'printpreview.ui')
        dlg.width.setValue(self.print_width or 500)
        ppw = QPrintPreviewWidget(printer, dlg)
        dlg.layout().insertWidget(1, ppw)

        def render(printer):
            height = printer.height() * (dlg.width.value() / 1000.)
            width = aspect * height
            painter = QPainter(printer)
            svg.render(painter, QRectF(0, 0, width, height))

        def sliderchanged(newval):
            ppw.updatePreview()

        ppw.paintRequested.connect(render)
        dlg.width.valueChanged.connect(sliderchanged)
        if dlg.exec_() != QDialog.Accepted:
            return
        self.print_width = dlg.width.value()
        pdlg = QPrintDialog(printer, self)
        if pdlg.exec_() != QDialog.Accepted:
            return
        render(printer)

    def ufit_replot(self):
        self.replotRequest.emit()
예제 #7
0
class MPLToolbar(NavigationToolbar2QT):

    popoutRequested = pyqtSignal()

    icon_name_map = {
        'home.png': 'magnifier-zoom-fit.png',
        'back.png': 'arrow-180.png',
        'forward.png': 'arrow.png',
        'move.png': 'arrow-move.png',
        'zoom_to_rect.png': 'selection-resize.png',
        'filesave.png': 'document-pdf.png',
        'printer.png': 'printer.png',
        'pyconsole.png': 'terminal--arrow.png',
        'log-x.png': 'log-x.png',
        'log-y.png': 'log-y.png',
        'log-z.png': 'log-z.png',
        'exwindow.png': 'chart--arrow.png',
    }

    toolitems = list(NavigationToolbar2QT.toolitems)
    del toolitems[7]  # subplot adjust
    toolitems.insert(
        0, ('Log x', 'Logarithmic X scale', 'log-x', 'logx_callback'))
    toolitems.insert(
        1, ('Log y', 'Logarithmic Y scale', 'log-y', 'logy_callback'))
    toolitems.insert(
        2,
        ('Log z', 'Logarithmic Z scale for images', 'log-z', 'logz_callback'))
    toolitems.insert(3, (None, None, None, None))
    toolitems.append(
        ('Print', 'Print the figure', 'printer', 'print_callback'))
    toolitems.append(('Pop out', 'Show the figure in a separate window',
                      'exwindow', 'popout_callback'))
    toolitems.append(
        ('Execute', 'Show Python console', 'pyconsole', 'exec_callback'))

    def _init_toolbar(self):
        NavigationToolbar2QT._init_toolbar(self)
        self.locLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self._actions['logx_callback'].setCheckable(True)
        self._actions['logy_callback'].setCheckable(True)
        self._actions['logz_callback'].setCheckable(True)
        self.canvas.logzChanged.connect(self.on_canvas_logzChanged)

    def _icon(self, name, color=None):
        if name in self.icon_name_map:
            return QIcon(':/' + self.icon_name_map[name])
        return QIcon()

    def home(self):
        # always unzoom completely
        if hasattr(self, '_views'):
            self._views.clear()
        if hasattr(self, '_positions'):
            self._positions.clear()
        self.canvas.figure.gca().autoscale()
        self.canvas.draw()
        return NavigationToolbar2QT.home(self)

    def logx_callback(self):
        ax = self.canvas.figure.gca()
        if ax.get_xscale() == 'linear':
            ax.set_xscale('symlog')
            self._actions['logx_callback'].setChecked(True)
        else:
            ax.set_xscale('linear')
            self._actions['logx_callback'].setChecked(False)
        self.canvas.draw()

    def logy_callback(self):
        ax = self.canvas.figure.gca()
        if ax.get_yscale() == 'linear':
            ax.set_yscale('symlog')
            self._actions['logy_callback'].setChecked(True)
        else:
            ax.set_yscale('linear')
            self._actions['logy_callback'].setChecked(False)
        self.canvas.draw()

    def logz_callback(self):
        ax = self.canvas.figure.gca()
        self.canvas.logz = not self.canvas.logz
        session.set_dirty()
        self._actions['logz_callback'].setChecked(self.canvas.logz)
        for im in ax.get_images():
            if self.canvas.logz:
                im.set_norm(LogNorm())
            else:
                im.set_norm(None)
        self.canvas.draw()

    def on_canvas_logzChanged(self):
        self._actions['logz_callback'].setChecked(self.canvas.logz)

    def print_callback(self):
        self.canvas.print_()

    def popout_callback(self):
        self.popoutRequested.emit()

    def exec_callback(self):
        try:
            from ufit.gui.console import ConsoleWindow
        except ImportError:
            logger.exception('Qt console window cannot be opened without '
                             'IPython; import error was:')
            QMessageBox.information(
                self, 'ufit', 'Please install IPython with qtconsole to '
                'activate this function.')
            return
        w = ConsoleWindow(self)
        w.ipython.executeCommand('from ufit.lab import *')
        w.ipython.pushVariables({
            'fig':
            self.canvas.figure,
            'ax':
            self.canvas.figure.gca(),
            'D': [item for group in session.groups for item in group.items],
        })
        w.show()

    def save_figure(self, *args):
        filetypes = self.canvas.get_supported_filetypes_grouped()
        sorted_filetypes = sorted(filetypes.items())

        start = self.canvas.get_default_filename()
        filters = []
        for name, exts in sorted_filetypes:
            if 'eps' in exts or 'emf' in exts or 'jpg' in exts or \
               'pgf' in exts or 'raw' in exts:
                continue
            exts_list = " ".join(['*.%s' % ext for ext in exts])
            filter = '%s (%s)' % (name, exts_list)
            filters.append(filter)
        filters = ';;'.join(filters)
        fname, _ = QFileDialog.getSaveFileName(self,
                                               'Choose a filename to save to',
                                               start, filters)
        if fname:
            try:
                self.canvas.print_figure(text_type(fname))
            except Exception as e:
                logger.exception('Error saving file')
                QMessageBox.critical(self, 'Error saving file', str(e))
예제 #8
0
class ScanDataItem(SessionItem):
    newModel = pyqtSignal(object, bool)

    itemtype = 'scan'

    def __init__(self, data, model=None):
        self.data = data
        self.model = model or default_model(data)
        SessionItem.__init__(self)

    def change_model(self, model, keep_param_values=True):
        self.model = model
        self.newModel.emit(model, keep_param_values)
        session.set_dirty()

    def after_load(self):
        self.data.after_load()  # upgrade datastructures

    def __reduce__(self):
        return (self.__class__, (self.data, self.model))

    def create_panel(self, mainwindow, canvas):
        return ScanDataPanel(mainwindow, canvas, self)

    def create_multi_panel(self, mainwindow, canvas):
        return MultiDataOps(mainwindow, canvas)

    def update_htmldesc(self):
        title = self.data.title
        # XXX self.dataops.titleEdit.setText(title)
        self.title = title
        htmldesc = '<big><b>%s</b></big>' % self.index + \
            (title and ' - %s' % title or '') + \
            (self.data.environment and
             '<br>%s' % ', '.join(self.data.environment) or '') + \
            ('<br><small>%s</small>' % '<br>'.join(self.data.sources[:5]))
        if len(self.data.sources) > 5:
            htmldesc += '<br><small>...</small>'
        self.htmldesc = htmldesc
        session.itemsUpdated.emit()

    def export_python(self, filename):
        with open(filename, 'wb') as fp:
            fp.write(b'from ufit.lab import *\n')
            fp.write(b'\n')
            self.data.export_python(fp, 'data')
            fp.write('\n')
            self.model.export_python(fp, 'model')
            fp.write(b'''\
## just plot current values
data.plot()
model.plot_components(data)
model.plot(data)

## to fit again use this...
#result = model.fit(data)
#result.printout()
#result.plot()

show()
''')

    def export_ascii(self, filename):
        with open(filename, 'wb') as fp:
            self.data.export_ascii(fp)

    def export_fits(self, filename):
        xx = linspace(self.data.x.min(), self.data.x.max(), 1000)
        paramvalues = prepare_params(self.model.params, self.data.meta)[3]
        yy = self.model.fcn(paramvalues, xx)
        yys = []
        for comp in self.model.get_components():
            if comp is self.model:
                continue
            yys.append(comp.fcn(paramvalues, xx))
        savetxt(filename, array([xx, yy] + yys).T)
예제 #9
0
class MultiDataOps(QWidget):
    replotRequest = pyqtSignal(object)

    def __init__(self, parent, canvas):
        QWidget.__init__(self, parent)
        self.canvas = canvas
        self.replotRequest.connect(self.plot)

        loadUi(self, 'multiops.ui')

    def initialize(self, items):
        self.items = [i for i in items if isinstance(i, ScanDataItem)]
        self.datas = [i.data for i in self.items]
        self.monscaleEdit.setText(
            str(int(mean([d.nscale for d in self.datas]))))
        self.onemodelBox.clear()
        self.onemodelBox.addItems(['%d' % i.index for i in self.items])

    def plot(self, limits=True, canvas=None):
        canvas = canvas or self.canvas
        xlabels = set()
        ylabels = set()
        titles = set()
        canvas.plotter.reset()
        for i in self.items:
            c = canvas.plotter.plot_data(i.data, multi=True)
            canvas.plotter.plot_model(i.model, i.data, labels=False, color=c)
            xlabels.add(i.data.xaxis)
            ylabels.add(i.data.yaxis)
            titles.add(i.data.title)
        canvas.plotter.plot_finish(
            ', '.join(xlabels), ', '.join(ylabels),
            from_encoding(', '.join(titles), 'ascii', 'ignore'))
        canvas.draw()

    @pyqtSlot()
    def on_rebinBtn_clicked(self):
        try:
            binsize = float(self.precisionEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error', 'Enter a valid precision.')
            return
        for data in self.datas:
            new_array, new_meta = rebin(array(data._data), binsize, data.meta)
            data.__init__(new_meta,
                          new_array,
                          data.xcol,
                          data.ycol,
                          data.ncol,
                          data.nscale,
                          name=data.name,
                          sources=data.sources)
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_mulBtn_clicked(self):
        try:
            const = float(self.scaleConstEdit.text())
        except ValueError:
            return
        for data in self.datas:
            data.y *= const
            data.y_raw *= const
            data.dy *= const
            data.dy_raw *= const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_addBtn_clicked(self):
        try:
            const = float(self.addConstEdit.text())
        except ValueError:
            return
        for data in self.datas:
            data.y += const
            data.y_raw += const * data.norm
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_scaleXBtn_clicked(self):
        try:
            const = float(self.scaleXConstEdit.text())
        except ValueError:
            return
        for data in self.datas:
            data.x *= const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_shiftBtn_clicked(self):
        try:
            const = float(self.shiftConstEdit.text())
        except ValueError:
            return
        for data in self.datas:
            data.x += const
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_monscaleBtn_clicked(self):
        try:
            const = int(self.monscaleEdit.text())
        except ValueError:
            return
        for data in self.datas:
            data.nscale = const
            data.norm = data.norm_raw / const
            data.y = data.y_raw / data.norm
            data.dy = sqrt(data.y_raw) / data.norm
            data.yaxis = data.ycol + ' / %s %s' % (const, data.ncol)
        self.replotRequest.emit(None)
        session.set_dirty()

    @pyqtSlot()
    def on_mergeBtn_clicked(self):
        try:
            precision = float(self.mergeEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error', 'Enter a valid precision.')
            return
        new_data = self.datas[0].merge(precision, *self.datas[1:])
        session.add_item(ScanDataItem(new_data), self.items[-1].group)

    @pyqtSlot()
    def on_floatMergeBtn_clicked(self):
        try:
            precision = float(self.mergeEdit.text())
        except ValueError:
            QMessageBox.warning(self, 'Error', 'Enter a valid precision.')
            return
        new_data = self.datas[0].merge(precision,
                                       floatmerge=True,
                                       *self.datas[1:])
        session.add_item(ScanDataItem(new_data), self.items[-1].group)

    @pyqtSlot()
    def on_onemodelBtn_clicked(self):
        which = self.onemodelBox.currentIndex()
        if which < 0:
            return
        model = self.items[which].model
        with_params = self.onemodelWithParamsBox.isChecked()
        for i, item in enumerate(self.items):
            if i == which:
                continue
            item.change_model(model.copy(), keep_param_values=not with_params)
        self.replotRequest.emit(None)

    @pyqtSlot()
    def on_fitallBtn_clicked(self):
        for item in self.items:
            res = item.model.fit(item.data)
            session.modelFitted.emit(item, res)
        self.replotRequest.emit(None)

    @pyqtSlot()
    def on_paramsetBtn_clicked(self):
        dlg = ParamSetDialog(self, self.items)
        if dlg.exec_() != QDialog.Accepted:
            return
        session.add_item(ScanDataItem(dlg.new_data), self.items[-1].group)

    @pyqtSlot()
    def on_mappingBtn_clicked(self):
        item = MappingItem([item.data for item in self.items], None)
        session.add_item(item, self.items[-1].group)

    @pyqtSlot()
    def on_globalfitBtn_clicked(self):
        QMessageBox.warning(self, 'Sorry', 'Not implemented yet.')

    def export_ascii(self, filename):
        base, ext = path.splitext(filename)
        for i, item in enumerate(self.items):
            item.export_ascii(base + '.%d' % i + ext)

    def export_fits(self, filename):
        base, ext = path.splitext(filename)
        for i, item in enumerate(self.items):
            item.export_fits(base + '.%d' % i + ext)

    def export_python(self, filename):
        base, ext = path.splitext(filename)
        for i, item in enumerate(self.items):
            item.export_python(base + '.%d' % i + ext)
예제 #10
0
class Fitter(QWidget):
    closeRequest = pyqtSignal()
    pickRequest = pyqtSignal(object)
    replotRequest = pyqtSignal(object)

    def __init__(self, parent, standalone=False, fit_kws={}):
        QWidget.__init__(self, parent)
        self.item = getattr(parent, 'item', None)
        self.logger = logger.getChild('fitter')
        self.picking = None
        self.last_result = None
        self.model = None
        self.data = None
        self.param_controls = {}
        self.fit_kws = fit_kws

        self.standalone = standalone
        self.createUI(standalone)

    def createUI(self, standalone):
        loadUi(self, 'fitter.ui')

        if standalone:
            self.buttonBox.addButton(QDialogButtonBox.Close)
        self.buttonBox.addButton('Initial guess', QDialogButtonBox.HelpRole)
        self.buttonBox.addButton('Save params', QDialogButtonBox.ResetRole)
        self.buttonBox.addButton('Restore saved', QDialogButtonBox.ResetRole)
        self.buttonBox.addButton('Replot', QDialogButtonBox.ActionRole)
        fitbtn = self.buttonBox.addButton('Fit', QDialogButtonBox.ApplyRole)
        fitbtn.setShortcut(QKeySequence('Ctrl+F'))
        fitbtn.setIcon(QIcon.fromTheme('dialog-ok'))

    def initialize(self, model, data, fit=True, keep_old=True):
        self.picking = None
        self.last_result = None

        old_model = self.model
        self.modelLabel.setText(model.get_description())
        self.data = data
        self.model = model

        self.param_controls = {}
        self.create_param_controls()

        # try to transfer values of old parameters to new
        if keep_old and old_model is not None:
            oldp_dict = dict((p.name, p) for p in old_model.params)
            self.restore_from_params(oldp_dict)

        session.modelFitted.connect(self.on_modelFitted)

        if self.standalone:
            if fit:
                self.do_fit()
            else:
                self.replotRequest.emit(None)

    def create_param_controls(self):
        self.param_controls = {}
        self.param_frame = QFrame(self)
        layout = QGridLayout()
        for j, text in enumerate(('Param', 'Value', 'Error', 'Fix', 'Expr',
                                  'Min', 'Max', 'Delta')):
            ctl = QLabel(text, self)
            ctl.setFont(self.statusLabel.font())
            layout.addWidget(ctl, 0, j)
        i = 1
        self.original_params = {}
        combo_items = [''] + [par.name for par in self.model.params] + \
            ['data.' + m for m in sorted(self.data.meta)
             if isinstance(self.data.meta[m], number_types)]
        for p in self.model.params:
            e0 = QLabel(p.name, self)
            e1 = SmallLineEdit('%.5g' % p.value, self)
            e2 = QLabel(u'± %.5g' % p.error, self)
            e3 = QCheckBox(self)
            e4 = QComboBox(self)
            e4.setEditable(True)
            e4.addItems(combo_items)
            if p.expr and is_float(p.expr):
                e1.setText(p.expr)
                e3.setChecked(True)
                e4.lineEdit().setText('')
            else:
                e4.lineEdit().setText(p.expr or '')
            e5 = SmallLineEdit(p.pmin is not None and '%.5g' % p.pmin or '', self)
            e6 = SmallLineEdit(p.pmax is not None and '%.5g' % p.pmax or '', self)
            e7 = SmallLineEdit(p.delta and '%.5g' % p.delta or '', self)
            ctls = self.param_controls[p] = (e0, e1, e2, e3, e4, e5, e6, e7)
            for j, ctl in enumerate(ctls):
                layout.addWidget(ctl, i, j)
            i += 1
            self.original_params[p.name] = p.copy()
            e1.returnPressed.connect(self.do_plot)
            e4.lineEdit().returnPressed.connect(self.do_plot)
            e5.returnPressed.connect(self.do_plot)
            e6.returnPressed.connect(self.do_plot)
            e3.clicked.connect(self.update_enables)
            e4.editTextChanged.connect(self.update_enables)
        layout.setRowStretch(i+1, 1)
        self.param_frame.setLayout(layout)
        self.param_scroll.setWidget(self.param_frame)
        self.update_enables()

    def update_enables(self, *ignored):
        for p, ctls in iteritems(self.param_controls):
            # if there is an expr...
            if ctls[4].currentText():
                # disable value and minmax, check "fixed" and disable "fixed"
                ctls[1].setEnabled(False)
                ctls[3].setCheckState(Qt.PartiallyChecked)  # implicitly fixed
                ctls[3].setEnabled(False)
                ctls[5].setEnabled(False)
                ctls[6].setEnabled(False)
                ctls[7].setEnabled(False)
            # else, if "fixed" is checked...
            elif ctls[3].checkState() == Qt.Checked:
                # enable value, but disable expr and minmax
                ctls[1].setEnabled(True)
                ctls[4].setEnabled(False)
                ctls[5].setEnabled(False)
                ctls[6].setEnabled(False)
                ctls[7].setEnabled(False)
            # else: not fixed, no expr
            else:
                # enable everything
                ctls[1].setEnabled(True)
                ctls[3].setEnabled(True)
                ctls[3].setCheckState(Qt.Unchecked)
                ctls[3].setTristate(False)
                ctls[4].setEnabled(True)
                ctls[5].setEnabled(True)
                ctls[6].setEnabled(True)
                ctls[7].setEnabled(True)

    def on_buttonBox_clicked(self, button):
        role = self.buttonBox.buttonRole(button)
        if role == QDialogButtonBox.RejectRole:
            self.closeRequest.emit()
        elif role == QDialogButtonBox.ApplyRole:
            self.do_fit()
        elif role == QDialogButtonBox.ActionRole:
            self.do_plot()
        elif role == QDialogButtonBox.HelpRole:
            self.do_pick()
        else:
            if button.text() == 'Save params':
                self.save_original_params()
                self.statusLabel.setText('Current parameter values saved.')
            else:
                self.restore_from_params(self.original_params)
                self.statusLabel.setText('Saved parameter values restored.')

    def update_from_controls(self):
        for p, ctls in iteritems(self.param_controls):
            _, val, _, fx, expr, pmin, pmax, delta = ctls
            p.value = float(val.text()) if val.text() else 0
            if fx.checkState() == Qt.Checked:
                p.expr = str(val.text())
            else:
                p.expr = str(expr.currentText())
            p.pmin = float(pmin.text()) if pmin.text() else None
            p.pmax = float(pmax.text()) if pmax.text() else None
            p.delta = float(delta.text()) if delta.text() else 0
        self.update_enables()
        session.set_dirty()

    def restore_from_params(self, other_params):
        for p in self.model.params:
            if p.name not in other_params:
                continue
            p0 = other_params[p.name]
            ctls = self.param_controls[p]
            ctls[1].setText('%.5g' % p0.value)
            ctls[2].setText(u'± %.5g' % p0.error)
            ctls[3].setChecked(False)
            if p0.expr and is_float(p0.expr):
                ctls[1].setText(p0.expr)
                ctls[3].setChecked(True)
                ctls[4].lineEdit().setText('')
            else:
                ctls[4].lineEdit().setText(p0.expr or '')
            ctls[5].setText(p0.pmin is not None and '%.5g' % p0.pmin or '')
            ctls[6].setText(p0.pmax is not None and '%.5g' % p0.pmax or '')
            ctls[7].setText(p0.delta and '%.5g' % p0.delta or '')
        session.set_dirty()
        self.do_plot()

    def save_original_params(self):
        self.original_params = {}
        for p in self.model.params:
            self.original_params[p.name] = p.copy()

    def on_canvas_pick(self, event):
        if not self.picking:
            return
        if not hasattr(event, 'xdata') or event.xdata is None:
            return
        self._pick_values.append((event.xdata, event.ydata))
        if len(self._pick_values) == len(self._pick_points):
            self.picking = False
            self.statusLabel.setText('')
            self._pick_finished()
        else:
            self.statusLabel.setText('%s: click on %s' %
                                     (self.picking,
                                      self._pick_points[len(self._pick_values)]))

    def do_pick(self, *args):
        if self.picking:
            return
        self.pickRequest.emit(self)
        self._pick_points = self.model.get_pick_points()
        self._pick_values = []
        self.picking = 'Guess'
        self.statusLabel.setText('Guess: click on %s' % self._pick_points[0])

        def callback():
            self.model.apply_pick(self._pick_values)
            for p in self.model.params:
                ctls = self.param_controls[p]
                if not p.expr:
                    ctls[1].setText('%.5g' % p.value)
            session.set_dirty()
            self.do_plot()
        self._pick_finished = callback

    def do_plot(self, *ignored):
        self.update_from_controls()
        self.replotRequest.emit(None)

    def do_fit(self):
        if self.picking:
            QMessageBox.information(self, 'Fitting',
                                    'Please finish the picking operation first.')
            return
        self.update_from_controls()
        self.statusLabel.setText('Working...')
        self.statusLabel.repaint()
        QApplication.processEvents()
        try:
            res = self.model.fit(self.data, **self.fit_kws)
        except Exception as e:
            self.logger.exception('Error during fit')
            self.statusLabel.setText('Error during fit: %s' % e)
            return
        self.on_modelFitted(self.item, res)

        self.replotRequest.emit(True)
        session.set_dirty()

    def on_modelFitted(self, item, res):
        if item is not self.item:
            return

        self.statusLabel.setText(
            (res.success and 'Converged. ' or 'Failed. ') + res.message +
            ' Reduced chi^2 = %.3g.' % res.chisqr)

        for p in res.params:
            self.param_controls[p][1].setText('%.5g' % p.value)
            self.param_controls[p][2].setText(u'± %.5g' % p.error)

        self.last_result = res
예제 #11
0
class ModelBuilder(QWidget):
    closeRequest = pyqtSignal()
    pickRequest = pyqtSignal(object)
    newModel = pyqtSignal(object, object, object)

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.logger = logger.getChild('model')
        self.gauss_picking = 0
        self.gauss_peak_pos = 0, 0
        self.pick_model = None
        self.data = None
        self.last_model = None
        self.createUI()

    def createUI(self):
        loadUi(self, 'modelbuilder.ui')
        self.modeldefStacker.setCurrentIndex(0)
        self.model_dict = {}
        for model in concrete_models:
            QListWidgetItem(model.__name__, self.premodelsList)
            self.model_dict[model.__name__] = model
        self.buttonBox.addButton('Check', QDialogButtonBox.NoRole)
        self.buttonBox.addButton(QDialogButtonBox.Apply)

    def on_buttonBox_clicked(self, button):
        role = self.buttonBox.buttonRole(button)
        if role == QDialogButtonBox.ResetRole:
            self.modeldefEdit.setText('')
        elif role == QDialogButtonBox.NoRole:
            self.eval_model()
        else:  # "apply"
            self.eval_model(final=True)

    @pyqtSlot()
    def on_gaussOnlyBtn_clicked(self):
        if self.gauss_picking:
            self._finish_picking()
            return
        self.gaussOnlyBtn.setText('Back to full modeling mode')
        self.pickRequest.emit(self)
        self.gauss_picking = 1
        self.gauss_picked_points = []
        self.modeldefStacker.setCurrentIndex(1)
        self.pick_model = Background(bkgd=self.data.y.min())
        self.newModel.emit(self.pick_model, True, False)

    def on_canvas_pick(self, event):
        if not self.gauss_picking:
            return
        if hasattr(event, 'artist'):
            return
        if self.gauss_picking % 2 == 1:
            # first click, picked position
            self.gauss_peak_pos = event.xdata, event.ydata
        else:
            # second click, picked width
            pos = self.gauss_peak_pos[0]
            ampl = self.gauss_peak_pos[1] - self.data.y.min()
            fwhm = abs(pos - event.xdata) * 2
            self.pick_model += GaussInt('p%02d' % (self.gauss_picking / 2),
                                        pos=pos,
                                        int=fwhm * ampl * 2.5,
                                        fwhm=fwhm)
            self.newModel.emit(self.pick_model, True, False)
        self.gauss_picking += 1

    def _finish_picking(self):
        if not self.gauss_picking:
            return
        self.gauss_picking = None
        self.gaussOnlyBtn.setText('Gauss peaks only mode')
        self.modeldefStacker.setCurrentIndex(0)

    def on_premodelsList_currentItemChanged(self, current, previous):
        model = self.model_dict[str(current.text())]
        self.modelinfoLbl.setText(model.__doc__)

    def on_premodelsList_itemDoubleClicked(self, item):
        self.on_addmodelBtn_clicked()

    @pyqtSlot()
    def on_addmodelBtn_clicked(self):
        modelitem = self.premodelsList.currentItem()
        if not modelitem:
            return
        modelcls = str(modelitem.text())
        modelname = QInputDialog.getText(
            self, 'ufit', 'Please enter a name '
            'for the model part:')[0]
        if not modelname:
            return
        self.insert_model_code('%s(%r)' % (modelcls, str(modelname)))

    @pyqtSlot()
    def on_addCustomBtn_clicked(self):
        dlg = QDialog(self)
        loadUi(dlg, 'custommodel.ui')
        while 1:
            if dlg.exec_() != QDialog.Accepted:
                return
            modelname = str(dlg.nameBox.text())
            params = str(dlg.paramBox.text())
            value = str(dlg.valueEdit.toPlainText()).strip()
            if not ident_re.match(modelname):
                QMessageBox.warning(
                    self, 'Error', 'Please enter a valid model '
                    'name (must be a Python identifier using '
                    'only alphabetic characters and digits).')
                continue
            if not params:
                QMessageBox.warning(self, 'Error',
                                    'Please enter some parameters.')
                continue
            for param in params.split():
                if not ident_re.match(param):
                    QMessageBox.warning(
                        self, 'Error', 'Parameter name %s is not valid (must '
                        'be a Python identifier using only alphabetic '
                        'characters and digits).' % param)
                    params = None
                    break
            if not params:
                continue
            break
        self.insert_model_code('Custom(%r, %r, %r)' %
                               (modelname, params, value))

    def insert_model_code(self, code):
        currentmodel = str(self.modeldefEdit.toPlainText())
        prefix = ''
        if currentmodel:
            prefix = ' + '
        tc = self.modeldefEdit.textCursor()
        tc.movePosition(QTextCursor.End)
        tc.insertText(prefix + code)

    def initialize(self, data, model):
        self.model = model
        self.data = data
        self.modeldefEdit.setText(model.get_description())

    def eval_model(self, final=False):
        modeldef = str(self.modeldefEdit.toPlainText()).replace('\n', ' ')
        if not modeldef:
            QMessageBox.information(self, 'Error', 'No model defined.')
            return
        try:
            model = eval_model(modeldef)
        except Exception as e:
            self.logger.exception('Could not evaluate model')
            QMessageBox.information(self, 'Error',
                                    'Could not evaluate model: %s' % e)
            return
        if final:
            self._finish_picking()
            self.last_model = model
            self.newModel.emit(model, False, True)
            self.closeRequest.emit()
        else:
            self.statusLbl.setText('Model definition is good.')
예제 #12
0
class DataLoader(QWidget):
    closeRequest = pyqtSignal()
    newDatas = pyqtSignal(object, object)

    def __init__(self, parent, plotter, standalone=False):
        QWidget.__init__(self, parent)
        self.logger = logger.getChild('loader')
        self.plotter = plotter
        self.last_data = []
        self.loader = Loader()
        self.createUI(standalone)

        self.sgroup = SettingGroup('main')

        # These will not do anything in standalone mode, but do not hurt.
        session.propsRequested.connect(self.on_session_propsRequested)
        session.propsUpdated.connect(self.on_session_propsUpdated)
        session.itemsUpdated.connect(self.on_session_itemsUpdated)
        session.groupAdded.connect(self.on_session_itemsUpdated)

        with self.sgroup as settings:
            data_template_path = settings.value('last_data_template', '')
            if data_template_path:
                self.templateEdit.setText(data_template_path)
                self.set_template(data_template_path, 0, silent=True)

    def createUI(self, standalone):
        loadUi(self, 'dataloader.ui')
        self.dataformatBox.addItem('auto')
        for fmt in sorted(data_formats):
            self.dataformatBox.addItem(fmt)

        self.buttonBox.addButton(QDialogButtonBox.Open)
        self.buttonBox.addButton('Preview', QDialogButtonBox.NoRole)

    def on_session_propsRequested(self):
        session.props.template = self.templateEdit.text()

    def on_session_propsUpdated(self):
        if 'template' in session.props:
            self.templateEdit.setText(session.props.template)

    def on_session_itemsUpdated(self, _ignored=None):
        # list of groups may have changed
        self.groupBox.clear()
        for group in session.groups:
            self.groupBox.addItem(group.name)

    def on_buttonBox_clicked(self, button):
        role = self.buttonBox.buttonRole(button)
        if role == QDialogButtonBox.RejectRole:
            self.closeRequest.emit()
        elif role == QDialogButtonBox.NoRole:  # "preview"
            self.open_data()
        else:  # "open"
            self.open_data(final=True)

    def on_dataformatBox_currentIndexChanged(self, i):
        self.loader.format = str(self.dataformatBox.currentText())

    @pyqtSlot()
    def on_numorHelpBtn_clicked(self):
        QMessageBox.information(
            self, 'Numor Help', '''\
The numor string contains file numbers, with the following operators:

, loads multiple files
- loads multiple sequential files
+ merges multiple files
> merges multiple sequential files

For example:

* 10-15,23  loads files 10 through 15 and 23 in 7 separate datasets.
* 10+11,23+24 loads two datasets consisting of files 10 and 11 merged \
into one set, as well as files 23 and 24.
* 10>15+23 merges files 10 through 15 and 23 into one single dataset.
* 10,11,12+13,14 loads four sets.
''')

    def open_browser(self, directory):
        bwin = BrowseWindow(self)
        bwin.show()
        QApplication.processEvents()
        try:
            bwin.set_directory(directory)
        except OSError:
            pass
        bwin.activateWindow()

    def add_numors(self, numors):
        ranges = []
        prev = -1
        start = None
        last = None
        for num in numors:
            if last is not None:
                if num != last + 1:
                    ranges.append((start, last))
                    start = num
            else:
                start = num
            last = num

        ranges.append((start, last))

        s = ''.join(',%s' % ('%s' % s if s == e else '%s-%s' % (s, e))
                    for (s, e) in ranges)
        prev = self.numorsEdit.text()
        if prev:
            self.numorsEdit.setText(prev + s)
        else:
            self.numorsEdit.setText(s[1:])
        self.open_data()  # preview

    @pyqtSlot()
    def on_browseBtn_clicked(self):
        templ = path_to_str(self.templateEdit.text())
        self.open_browser(path.dirname(templ))

    @pyqtSlot()
    def on_settemplateBtn_clicked(self):
        previous = self.templateEdit.text()
        if previous:
            startdir = path.dirname(previous)
        else:
            startdir = '.'
        fn = path_to_str(
            QFileDialog.getOpenFileName(self, 'Choose a file', startdir,
                                        'All files (*)')[0])
        if not fn:
            return
        dtempl, numor = extract_template(fn)
        self.set_template(dtempl, numor)

    def set_template(self, dtempl, numor, silent=True):
        self.templateEdit.setText(str_to_path(dtempl))
        with self.sgroup as settings:
            settings.setValue('last_data_template', dtempl)
        self.loader.template = dtempl
        try:
            cols, xguess, yguess, dyguess, mguess, nmon = \
                self.loader.guess_cols(numor)
        except Exception as e:
            if not silent:
                self.logger.exception('Could not read column names')
                QMessageBox.information(self, 'Error',
                                        'Could not read column names: %s' % e)
            return
        self.xcolBox.clear()
        self.xcolBox.addItem('auto')
        self.xcolBox.setCurrentIndex(0)
        self.ycolBox.clear()
        self.ycolBox.addItem('auto')
        self.ycolBox.setCurrentIndex(0)
        self.dycolBox.clear()
        self.dycolBox.addItem('auto')
        self.dycolBox.addItem('sqrt(Y)')
        self.dycolBox.setCurrentIndex(0)
        self.moncolBox.clear()
        self.moncolBox.addItem('auto')
        self.moncolBox.addItem('none')
        self.moncolBox.setCurrentIndex(0)
        self.filtercolBox.clear()
        self.filtercolBox.addItem('none')
        self.filtercolBox.setCurrentIndex(0)

        for i, name in enumerate(cols):
            self.xcolBox.addItem(name)
            self.ycolBox.addItem(name)
            self.dycolBox.addItem(name)
            self.moncolBox.addItem(name)
            self.filtercolBox.addItem(name)
        self.monscaleEdit.setText(str(nmon or 1))
        self.numorsEdit.setText(str(numor))
        self.open_data()

    def open_data(self, final=False):
        try:
            prec = float(self.precisionEdit.text())
        except ValueError:
            QMessageBox.information(self, 'Error', 'Enter a valid precision.')
            return
        floatmerge = self.rbFloatMerge.isChecked()
        xcol = str(self.xcolBox.currentText())
        ycol = str(self.ycolBox.currentText())
        dycol = str(self.dycolBox.currentText())
        mcol = str(self.moncolBox.currentText())
        fcol = str(self.filtercolBox.currentText())
        if mcol == 'none':
            mcol = None
        if dycol == 'sqrt(Y)':
            dycol = None
        try:
            mscale = int(self.monscaleEdit.text())
        except Exception:
            QMessageBox.information(self, 'Error',
                                    'Monitor scale must be integer.')
            return
        if fcol == 'none':
            filter = None
        else:
            try:
                val = float(self.filtervalEdit.text())
            except ValueError:
                val = bytes(self.filtervalEdit.text(), 'utf-8')
            filter = {fcol: val}
        dtempl = path_to_str(self.templateEdit.text())
        self.loader.template = dtempl
        numors = str(self.numorsEdit.text())
        try:
            datas = self.loader.load_numors(numors, prec, xcol, ycol, dycol,
                                            mcol, mscale, floatmerge, filter)
        except Exception as e:
            self.logger.exception('Error while loading data file')
            QMessageBox.information(self, 'Error', str(e))
            return
        self.last_data = datas
        if final:
            self.newDatas.emit(datas, self.groupBox.currentText())
            self.closeRequest.emit()
        else:
            self.plot()

    def initialize(self):
        pass

    def plot(self, limits=True, canvas=None):
        self.plotter.reset()
        xlabels = set()
        ylabels = set()
        titles = set()
        for data in self.last_data:
            xlabels.add(data.xaxis)
            ylabels.add(data.yaxis)
            titles.add(data.title)
            if isinstance(data, ImageData):  # XXX this plots only one
                self.plotter.plot_image(data, multi=True)
                break
            else:
                self.plotter.plot_data(data, multi=True)
        self.plotter.plot_finish(', '.join(xlabels), ', '.join(ylabels),
                                 ', '.join(titles))
        self.plotter.draw()
예제 #13
0
class UfitSession(QObject):

    propsRequested = pyqtSignal()
    propsUpdated = pyqtSignal()
    dirtyChanged = pyqtSignal(bool)
    filenameChanged = pyqtSignal()
    modelFitted = pyqtSignal(object, object)

    groupAdded = pyqtSignal(object)
    groupUpdated = pyqtSignal(object)

    itemsUpdated = pyqtSignal()
    itemUpdated = pyqtSignal(object)
    itemAdded = pyqtSignal(object)

    def __init__(self):
        QObject.__init__(self)
        self.filename = None
        self.groups = []
        self.all_items = set()
        self.clear()

    @property
    def dirname(self):
        if self.filename:
            return path.dirname(self.filename)
        return ''

    def clear(self):
        self.groups[:] = [ItemGroup('Default')]
        self.groups[0].expanded = True
        self.all_items.clear()
        self.props = attrdict()
        self.itemsUpdated.emit()
        self.propsUpdated.emit()
        self.dirtyChanged.emit(False)

    def new(self):
        self.filename = None
        self.filenameChanged.emit()
        self.clear()

    def set_filename(self, filename):
        self.filename = filename
        self.filenameChanged.emit()

    def _load_v0(self, info):
        info['version'] = 1
        info['datasets'] = info.pop('panels')
        info['template'] = ''
        self._load_v1(info)

    def _load_v1(self, info):
        info['version'] = 2
        datasets = info.pop('datasets')
        info['panels'] = [('dataset', d[0], d[1]) for d in datasets]
        self._load_v2(info)

    def _load_v2(self, info):
        info['version'] = 3
        group = ItemGroup('Default')
        info['groups'] = [group]
        panels = info.pop('panels')
        from ufit.gui.mappingitem import MappingItem
        from ufit.gui.scanitem import ScanDataItem
        for panel in panels:
            if panel[0] == 'dataset':
                group.items.append(ScanDataItem(panel[1], panel[2]))
            elif panel[0] == 'mapping':
                group.items.append(MappingItem(panel[1], panel[2]))
        info['props'] = attrdict()
        info['props'].template = info.pop('template')
        self._load_v3(info)

    def _load_v3(self, info):
        self.props = info['props']
        self.groups[:] = info['groups']

    def load(self, filename):
        self.clear()
        # unpickle everything
        with open(filename, 'rb') as fp:
            if six.PY3:
                info = pickle.load(fp, encoding='latin1')
            else:
                info = pickle.load(fp)
        # load with the respective method
        savever = info.get('version', 0)
        try:
            getattr(self, '_load_v%d' % savever)(info)
        except AttributeError:
            raise UFitError('save version %d not supported' % savever)
        self.filename = filename
        # reassign indices (also to regenerate descriptions)
        for group in self.groups:
            for i, item in enumerate(group.items):
                item.set_group(group, i + 1)
                item.after_load()
                self.all_items.add(item)
            group.update_htmldesc()
        # let GUI elements update from propsdata
        self.itemsUpdated.emit()
        self.propsUpdated.emit()
        self.filenameChanged.emit()

    def save(self):
        # let GUI elements update the stored propsdata
        self.propsRequested.emit()
        if self.filename is None:
            raise UFitError('session has no filename yet')
        info = {
            'version':  SAVE_VERSION,
            'groups':   self.groups,
            'props':    self.props,
        }
        with open(self.filename, 'wb') as fp:
            pickle.dump(info, fp, protocol=pickle.HIGHEST_PROTOCOL)
        self.dirtyChanged.emit(False)

    def add_group(self, name):
        group = ItemGroup(name)
        self.groups.append(group)
        self.set_dirty()
        self.groupAdded.emit(group)
        return group

    def remove_group(self, group):
        self.groups.remove(group)
        for item in group.items:
            self.all_items.discard(item)
        self.set_dirty()
        self.itemsUpdated.emit()

    def rename_group(self, group, name):
        group.name = name
        group.update_htmldesc()
        self.set_dirty()
        self.groupUpdated.emit(group)

    def add_item(self, item, group=None):
        if group is None:
            group = self.groups[-1]
        self.all_items.add(item)
        group.items.append(item)
        group.update_htmldesc()
        item.set_group(group, len(group.items))
        self.set_dirty()
        self.itemAdded.emit(item)

    def add_items(self, items, group=None):
        if not items:
            return
        if group is None:
            group = self.groups[-1]
        self.all_items.update(items)
        for item in items:
            group.items.append(item)
            item.set_group(group, len(group.items))
        group.update_htmldesc()
        self.set_dirty()
        self.itemsUpdated.emit()
        self.itemAdded.emit(items[-1])

    def remove_items(self, items):
        renumber_groups = set()
        for item in items:
            renumber_groups.add(item.group)
            item.group.items.remove(item)
            self.all_items.discard(item)
        for group in renumber_groups:
            if not group.items:
                self.groups.remove(group)
            for i, item in enumerate(group.items):
                item.set_group(group, i + 1)
            group.update_htmldesc()
        self.set_dirty()
        self.itemsUpdated.emit()

    def move_items(self, items, newgroup):
        renumber_groups = set([newgroup])
        for item in items:
            renumber_groups.add(item.group)
            item.group.items.remove(item)
            newgroup.items.append(item)
        for group in renumber_groups:
            for i, item in enumerate(group.items):
                item.set_group(group, i + 1)
            group.update_htmldesc()
        self.set_dirty()
        self.itemsUpdated.emit()

    def copy_items(self, items, newgroup):
        from ufit.gui.scanitem import ScanDataItem
        for item in items:
            new_data = item.data.copy()
            new_model = item.model.copy()
            new_item = ScanDataItem(new_data, new_model)
            self.all_items.add(new_item)
            newgroup.items.append(new_item)
            newgroup.update_htmldesc()
            new_item.set_group(newgroup, len(newgroup.items))
        self.set_dirty()
        self.itemsUpdated.emit()
        self.itemAdded.emit(items[-1])

    def reorder_groups(self, new_structure):
        del self.groups[:]
        for group, items in new_structure:
            self.groups.append(group)
            group.items[:] = items
            for i, item in enumerate(items):
                item.set_group(group, i + 1)
            group.update_htmldesc()
        self.set_dirty()
        self.itemsUpdated.emit()

    def set_dirty(self):
        self.dirtyChanged.emit(True)