Commits

Georg Brandl committed 68b267b

gui: implement panel for "multiple datas selected"

  • Participants
  • Parent commits 15a98bc

Comments (0)

Files changed (8)

 GUI
 ===
 
+* load/save: preserve param values when saving as eval_model
+* one model for all: stop sharing params
 * add'l parameters
-* multiple sets: new data/same model, other model
 * keyboard shortcuts in plot
 * export plot as python script
 

File ufit/data/dataset.py

 
         self.y = self.y_raw / self.norm
         self.dy = self.dy_raw / self.norm
+        self.dy[self.dy==0] = 0.1
 
         # points with mask = False are masked out
         self.mask = ones(len(self.x), bool)

File ufit/gui/dataops.py

 
 """Data operations panel."""
 
-from numpy import ones, sqrt
+from numpy import ones, sqrt, mean
 
 from PyQt4.QtCore import pyqtSignature as qtsig, SIGNAL
 from PyQt4.QtGui import QWidget
         self.data.y *= const
         self.data.y_raw *= const
         self.data.dy *= const
+        self.data.dy_raw *= const
         self.emit(SIGNAL('replotRequest'), None)
 
     @qtsig('')
         self.data.y = self.data.y_raw/self.data.norm
         self.data.dy = sqrt(self.data.y_raw)/self.data.norm
         self.emit(SIGNAL('replotRequest'), None)
+
+
+class MultiDataOps(QWidget):
+
+    def __init__(self, parent):
+        QWidget.__init__(self, parent)
+        self.data = None
+
+        loadUi(self, 'multiops.ui')
+
+    def initialize(self, panels):
+        self.panels = panels
+        self.datas = [p.data for p in panels]
+        self.monscale.setText(str(int(mean([d.nscale for d in self.datas]))))
+        self.onemodel.clear()
+        self.onemodel.addItems(['i%d' % p.index for p in panels])
+
+    @qtsig('')
+    def on_rebinBtn_clicked(self):
+        binsize = self.precision.value()
+        for data in self.datas:
+            new_array = rebin(data._data, binsize)
+            data.__init__(data.meta, new_array,
+                          data.xcol, data.ycol, data.ncol,
+                          data.nscale, name=data.name,
+                          sources=data.sources)
+        self.emit(SIGNAL('replotRequest'), None)
+
+    @qtsig('')
+    def on_mulBtn_clicked(self):
+        try:
+            const = float(self.mul_constant.text())
+        except ValueError:
+            return
+        for data in self.datas:
+            data.y *= const
+            data.y_raw *= const
+            data.dy *= const
+            data.dy_raw *= const
+        self.emit(SIGNAL('replotRequest'), None)
+
+    @qtsig('')
+    def on_addBtn_clicked(self):
+        try:
+            const = float(self.add_constant.text())
+        except ValueError:
+            return
+        for data in self.datas:
+            data.y += const
+            data.y_raw += const * data.norm
+        self.emit(SIGNAL('replotRequest'), None)
+
+    @qtsig('')
+    def on_shiftBtn_clicked(self):
+        try:
+            const = float(self.shift_constant.text())
+        except ValueError:
+            return
+        for data in self.datas:
+            data.x += const
+        self.emit(SIGNAL('replotRequest'), None)
+
+    @qtsig('')
+    def on_monscaleBtn_clicked(self):
+        try:
+            const = int(self.monscale.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
+        self.emit(SIGNAL('replotRequest'), None)
+
+    @qtsig('')
+    def on_mergeBtn_clicked(self):
+        precision = self.mergeprecision.value()
+        new_data = self.datas[0].merge(precision, *self.datas[1:])
+        self.emit(SIGNAL('newData'), new_data)
+
+    @qtsig('')
+    def on_onemodelBtn_clicked(self):
+        which = self.onemodel.currentIndex()
+        if which < 0:
+            return
+        model = self.panels[which].model
+        for i, panel in enumerate(self.panels):
+            if i == which:
+                continue
+            panel.handle_new_model(model)

File ufit/gui/main.py

 
 from PyQt4.QtCore import pyqtSignature as qtsig, SIGNAL, QModelIndex, QVariant
 from PyQt4.QtGui import QMainWindow, QVBoxLayout, QApplication, QTabWidget, \
-     QFrame, QMessageBox, QFileDialog, QDialog
+     QMessageBox, QFileDialog, QDialog
 
 from ufit.gui.common import MPLCanvas, MPLToolbar, SettingGroup, loadUi
 from ufit.gui.dataloader import DataLoader
-from ufit.gui.dataops import DataOps
+from ufit.gui.dataops import DataOps, MultiDataOps
 from ufit.gui.modelbuilder import ModelBuilder
 from ufit.gui.fitter import Fitter
 from ufit.gui.datalist import DataListModel
              '<br>'.join(self.data.sources))
 
     def on_mbuilder_newModel(self, model):
+        self.handle_new_model(model, update_mbuilder=False)
+
+    def handle_new_model(self, model, update_mbuilder=True):
+        if update_mbuilder:
+            self.mbuilder.modeldef.setText(model.get_description())
         self.model = model
         self.fitter.initialize(self.model, self.data, fit=False)
         self.setCurrentWidget(self.fitter)
         self.stacker.addWidget(self.dloader)
         self.current_panel = self.dloader
 
-        # XXX stopgap: add some useful things to do with multiple datasets
-        # (e.g. plot fit parameters vs another parameter)
-        self.empty = QFrame(self)
-        self.stacker.addWidget(self.empty)
+        self.multiops = MultiDataOps(self)
+        self.connect(self.multiops, SIGNAL('newData'), self.handle_new_data)
+        self.connect(self.multiops, SIGNAL('replotRequest'), self.plot_multi)
+        self.stacker.addWidget(self.multiops)
 
         self.datalistmodel = DataListModel(self.panels)
         self.datalist.setModel(self.datalistmodel)
             panel.replot(panel._limits)
             self.toolbar.update()
         else:
-            panels = [self.panels[i] for i in indlist]
-            self.canvas.plotter.reset()
-            self.isModal()
-            # XXX this doesn't belong here
-            for p in panels:
-                c = self.canvas.plotter.plot_data(p.data, multi=True)
-                self.canvas.plotter.plot_model(p.model, p.data, labels=False,
-                                               color=c)
-            # XXX better title
-            self.canvas.draw()
-            self.select_new_panel(self.empty)
+            self.plot_multi()
+            self.multiops.initialize([self.panels[i] for i in indlist])
+            self.select_new_panel(self.multiops)
+
+    def plot_multi(self, *ignored):
+        # XXX better title
+        self.canvas.plotter.reset()
+        indlist = [ind.row() for ind in self.datalist.selectedIndexes()]
+        panels = [self.panels[i] for i in indlist]
+        for p in panels:
+            c = self.canvas.plotter.plot_data(p.data, multi=True)
+            self.canvas.plotter.plot_model(p.model, p.data, labels=False,
+                                           color=c)
+        self.canvas.draw()
 
     def handle_new_data(self, data, update=True, model=None):
         panel = DatasetPanel(self, self.canvas, data, model)
     mainwindow = UFitMain()
 
     if args:
-        datafile = args[0]
+        datafile = path.abspath(args[0])
         if datafile.endswith('.ufit'):
             try:
+                mainwindow.filename = datafile
                 mainwindow.load_session(datafile)
             except Exception, err:
                 QMessageBox.warning(mainwindow,
                                     'Error', 'Loading failed: %s' % err)
+                mainwindow.filename = None
         else:
             mainwindow.dloader.set_template(datafile)
 

File ufit/gui/main.ui

           <item>
            <widget class="QPushButton" name="removeBtn">
             <property name="text">
-             <string>Remove</string>
+             <string>Remove selected</string>
             </property>
            </widget>
           </item>
      <x>0</x>
      <y>0</y>
      <width>278</width>
-     <height>19</height>
+     <height>23</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuFile">

File ufit/gui/modelbuilder.py

 from PyQt4.QtGui import QWidget, QListWidgetItem, QDialogButtonBox, \
      QMessageBox, QInputDialog, QTextCursor
 
-from ufit import models, param
-from ufit.models import Background, Gauss, concrete_models
+from ufit.models import Background, Gauss, concrete_models, eval_model
 from ufit.gui.common import loadUi
 
 
         if not modeldef:
             QMessageBox.information(self, 'Error', 'No model defined.')
             return
-        d = models.__dict__.copy()
-        d.update(param.expr_namespace)
-        d.update(param.__dict__)
         try:
-            model = eval(modeldef, d)
+            model = eval_model(modeldef)
         except Exception, e:
             QMessageBox.information(self, 'Error',
                                     'Could not evaluate model: %s' % e)

File ufit/gui/multiops.ui

+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>486</width>
+    <height>474</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <property name="horizontalSpacing">
+      <number>5</number>
+     </property>
+     <property name="verticalSpacing">
+      <number>10</number>
+     </property>
+     <item row="9" column="1" colspan="2">
+      <widget class="QComboBox" name="onemodel"/>
+     </item>
+     <item row="2" column="1" colspan="2">
+      <widget class="QLineEdit" name="add_constant">
+       <property name="text">
+        <string>0</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="3">
+      <widget class="QPushButton" name="shiftBtn">
+       <property name="text">
+        <string>Shift</string>
+       </property>
+      </widget>
+     </item>
+     <item row="9" column="0">
+      <widget class="QLabel" name="label_8">
+       <property name="text">
+        <string>Use one model for all:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="label_7">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Scale with constant:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="0">
+      <widget class="QLabel" name="label_6">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Change monitor:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="0">
+      <widget class="QLabel" name="label_3">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Shift X values:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="3">
+      <widget class="QPushButton" name="monscaleBtn">
+       <property name="text">
+        <string>Change</string>
+       </property>
+      </widget>
+     </item>
+     <item row="11" column="3">
+      <widget class="QPushButton" name="globalfitBtn">
+       <property name="text">
+        <string>Start</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Add a constant:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="11" column="0">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>Global fit:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="3">
+      <widget class="QPushButton" name="rebinBtn">
+       <property name="text">
+        <string>Rebin</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1" colspan="2">
+      <widget class="QLineEdit" name="shift_constant">
+       <property name="text">
+        <string>0</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0" colspan="3">
+      <widget class="QLabel" name="label_10">
+       <property name="font">
+        <font>
+         <weight>75</weight>
+         <bold>true</bold>
+        </font>
+       </property>
+       <property name="text">
+        <string>Operations on multiple datasets</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="3">
+      <widget class="QPushButton" name="mulBtn">
+       <property name="text">
+        <string>Scale</string>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="0" colspan="4">
+      <widget class="Line" name="line">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="1" colspan="2">
+      <widget class="QLineEdit" name="monscale">
+       <property name="text">
+        <string>10000</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="3">
+      <widget class="QPushButton" name="addBtn">
+       <property name="text">
+        <string>Add</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1" colspan="2">
+      <widget class="QLineEdit" name="mul_constant">
+       <property name="text">
+        <string>1</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1" colspan="2">
+      <widget class="QDoubleSpinBox" name="precision">
+       <property name="decimals">
+        <number>4</number>
+       </property>
+       <property name="maximum">
+        <double>10000.000000000000000</double>
+       </property>
+       <property name="singleStep">
+        <double>0.010000000000000</double>
+       </property>
+       <property name="value">
+        <double>0.010000000000000</double>
+       </property>
+      </widget>
+     </item>
+     <item row="9" column="3">
+      <widget class="QPushButton" name="onemodelBtn">
+       <property name="text">
+        <string>Do it</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Rebin individual sets:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="0">
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Merge all with precision:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="1" colspan="2">
+      <widget class="QDoubleSpinBox" name="mergeprecision">
+       <property name="decimals">
+        <number>4</number>
+       </property>
+       <property name="value">
+        <double>0.010000000000000</double>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="3">
+      <widget class="QPushButton" name="mergeBtn">
+       <property name="text">
+        <string>Merge</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>precision</tabstop>
+  <tabstop>rebinBtn</tabstop>
+  <tabstop>add_constant</tabstop>
+  <tabstop>addBtn</tabstop>
+  <tabstop>mul_constant</tabstop>
+  <tabstop>mulBtn</tabstop>
+  <tabstop>shift_constant</tabstop>
+  <tabstop>shiftBtn</tabstop>
+  <tabstop>monscale</tabstop>
+  <tabstop>monscaleBtn</tabstop>
+  <tabstop>globalfitBtn</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>

File ufit/models/base.py

 import operator
 from numpy import concatenate
 
-from ufit import backends, UFitError, Param, Dataset, Result
-from ufit.data.dataset import attrdict
+from ufit import param, backends, UFitError, Param, Dataset, Result
 from ufit.utils import get_chisqr
 from ufit.plotting import DataPlotter
 
-__all__ = ['Model', 'CombinedModel', 'Function']
+__all__ = ['Model', 'CombinedModel', 'Function', 'eval_model']
+
+
+def eval_model(modeldef):
+    from ufit import models
+    d = models.__dict__.copy()
+    d.update(param.expr_namespace)
+    d.update(param.__dict__)
+    model = eval(modeldef, d)
+    model.python_code = modeldef
+    return model
 
 
 class Model(object):
     fcn = None
     _orig_params = None
 
+    # can be set if the model is generated by eval()
+    python_code = None
+
     def __repr__(self):
         return '<%s %r>' % (self.__class__.__name__, self.name)
 
 
     def get_description(self):
         """Get a Python description of the model (no parameters)."""
+        if self.python_code:
+            return self.python_code
         if self.name:
             return '%s(%r)' % (self.__class__.__name__, self.name)
         return '%s()' % self.__class__.__name__
 
     def __reduce__(self):
         """Pickling support: reconstruct the object from a constructor call."""
+        if self.python_code:
+            return (eval_model, (self.python_code,))
         return (self.__class__, (self.name,) + tuple(self.params))
 
     def get_pick_points(self):
 
     def __reduce__(self):
         """Pickling support: reconstruct the object from __init__ parameters."""
+        if self.python_code:
+            return (eval_model, (self.python_code,))
         return (self.__class__, (self._a, self._b, self._opstr))
 
     def get_components(self):
         return ret
 
     def get_description(self):
+        if self.python_code:
+            return self.python_code
         s = ''
         if isinstance(self._a, CombinedModel) and \
             self.op_prio[self._a._opstr] < self.op_prio[self._opstr]:
             self._real_fcn(x, *(p[pv] for pv in pvs))
 
     def get_description(self):
+        if self.python_code:
+            return self.python_code
         return 'Function(%s, %s)' % (self.name, self._real_fcn.func_name)