source: sasview/calculatorview/src/sans/perspectives/calculator/model_editor.py @ fdef956

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since fdef956 was fdef956, checked in by Jae Cho <jhjcho@…>, 12 years ago

sum model editot can handle multi model too.

  • Property mode set to 100644
File size: 44.7 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2009, University of Tennessee
10################################################################################
11
12
13import wx
14import sys
15import os
16from wx.py.editwindow import EditWindow
17
18if sys.platform.count("win32") > 0:
19    FONT_VARIANT = 0
20    PNL_WIDTH = 450
21    PNL_HITE = 320
22else:
23    FONT_VARIANT = 1
24    PNL_WIDTH = 590
25    PNL_HITE = 400
26M_NAME = 'Model'
27EDITOR_WIDTH = 800
28EDITOR_HEIGTH = 720
29PANEL_WIDTH = 500
30_BOX_WIDTH = 55
31
32   
33def _compileFile(path):
34    """
35    Compile the file in the path
36    """
37    try:
38        import py_compile
39        py_compile.compile(file=path, doraise=True)
40        return ''
41    except:
42        type, value, traceback = sys.exc_info()
43        return value
44   
45def _deleteFile(path):
46    """
47    Delete file in the path
48    """
49    try:
50        os.remove(path)
51    except:
52        raise
53
54 
55class TextDialog(wx.Dialog):
56    """
57    Dialog for easy custom sum models 
58    """
59    def __init__(self, parent=None, base=None, id=None, title='', model_list=[], plugin_dir=None):
60        """
61        Dialog window popup when selecting 'Easy Custom Sum/Multiply' on the menu
62        """
63        wx.Dialog.__init__(self, parent=parent, id=id, 
64                           title=title, size=(PNL_WIDTH, PNL_HITE))
65        self.parent = base
66        #Font
67        self.SetWindowVariant(variant=FONT_VARIANT)
68        # default
69        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
70        self.font.SetPointSize(10)
71        self.overwrite_name = False
72        self.plugin_dir = plugin_dir
73        self.model_list = model_list
74        self.model1_string = "SphereModel"
75        self.model2_string = "CylinderModel"
76        self.name = 'Sum' + M_NAME
77        self._notes = ''
78        self.operator = '+'
79        self.operator_cbox = None
80        self.explanation = ''
81        self.explanationctr = None
82        self.sizer = None
83        self._msg_box = None
84        self.msg_sizer = None
85        self._build_sizer()
86        self.model1_name = str(self.model1.GetValue())
87        self.model2_name = str(self.model2.GetValue())
88        self.good_name = True
89        self.fill_oprator_combox()
90       
91    def _layout_name(self):
92        """
93        Do the layout for file/function name related widgets
94        """
95        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
96        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
97        #title name [string]
98        name_txt = wx.StaticText(self, -1, 'Function Name : ') 
99        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
100        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
101        self.name_tcl.SetValue('')
102        self.name_tcl.SetFont(self.font)
103        hint_name = "Unique Sum/Multiply Model Function Name."
104        self.name_tcl.SetToolTipString(hint_name)
105        self.name_hsizer.AddMany([(name_txt, 0, wx.LEFT|wx.TOP, 10),
106                            (self.name_tcl, -1, 
107                             wx.EXPAND|wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])
108        self.name_sizer.AddMany([(self.name_hsizer, -1, 
109                                        wx.LEFT|wx.TOP, 10)])
110       
111       
112    def _layout_description(self):
113        """
114        Do the layout for description related widgets
115        """
116        self.desc_sizer = wx.BoxSizer(wx.HORIZONTAL)
117        #title name [string]
118        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ') 
119        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
120        self.desc_tcl.SetValue('')
121        #self.name_tcl.SetFont(self.font)
122        hint_desc = "Write a short description of this model function."
123        self.desc_tcl.SetToolTipString(hint_desc)
124        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT|wx.TOP, 10),
125                                    (self.desc_tcl, -1, 
126                                    wx.EXPAND|wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])     
127 
128    def _build_sizer(self):
129        """
130        Build gui
131        """
132        box_width = 195 # combobox width
133        vbox  = wx.BoxSizer(wx.VERTICAL)
134        self.sizer = wx.GridBagSizer(1, 3)
135        self._layout_name()
136        self._layout_description()
137       
138       
139        sum_description= wx.StaticBox(self, -1, 'Select', 
140                                       size=(PNL_WIDTH-30, 70))
141        sum_box = wx.StaticBoxSizer(sum_description, wx.VERTICAL)
142        model1_box = wx.BoxSizer(wx.HORIZONTAL)
143        model2_box = wx.BoxSizer(wx.HORIZONTAL)
144        model_vbox = wx.BoxSizer(wx.VERTICAL)
145        self.model1 =  wx.ComboBox(self, -1, style=wx.CB_READONLY)
146        wx.EVT_COMBOBOX(self.model1, -1, self.on_model1)
147        self.model1.SetMinSize((box_width*5/6, -1))
148        self.model1.SetToolTipString("model1")
149       
150        self.operator_cbox = wx.ComboBox(self, -1, size=(50, -1), 
151                                         style=wx.CB_READONLY)
152        wx.EVT_COMBOBOX(self.operator_cbox, -1, self.on_select_operator)
153        operation_tip = "Add: +, Multiply: * "
154        self.operator_cbox.SetToolTipString(operation_tip)
155       
156        self.model2 =  wx.ComboBox(self, -1, style=wx.CB_READONLY)
157        wx.EVT_COMBOBOX(self.model2, -1, self.on_model2)
158        self.model2.SetMinSize((box_width*5/6, -1))
159        self.model2.SetToolTipString("model2")
160        self._set_model_list()
161       
162         # Buttons on the bottom
163        self.static_line_1 = wx.StaticLine(self, -1)
164        self.okButton = wx.Button(self,wx.ID_OK, 'Apply', size=(box_width/2, 25))
165        self.okButton.Bind(wx.EVT_BUTTON, self.check_name)
166        self.closeButton = wx.Button(self,wx.ID_CANCEL, 'Close', 
167                                     size=(box_width/2, 25))
168        # Intro
169        self.explanation  = "  custom model = scale_factor * (model1 %s model2)\n"\
170                            % self.operator
171        #explanation  += "  Note: This will overwrite the previous sum model.\n"
172        model_string = " Model%s (p%s):"
173        # msg
174        self._msg_box = wx.StaticText(self, -1, self._notes)
175        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
176        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 0)
177        vbox.Add(self.name_hsizer)
178        vbox.Add(self.desc_sizer)
179        vbox.Add(self.sizer)
180        ix = 0
181        iy = 1
182        self.explanationctr = wx.StaticText(self, -1, self.explanation)
183        self.sizer.Add(self.explanationctr , (iy, ix),
184                 (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
185        model1_box.Add(wx.StaticText(self,-1, model_string% (1, 1)), -1, 0)
186        model1_box.Add((box_width-15,10))
187        model1_box.Add(wx.StaticText(self, -1, model_string% (2, 2)), -1, 0)
188        model2_box.Add(self.model1, -1, 0)
189        model2_box.Add((15,10))
190        model2_box.Add(self.operator_cbox, 0, 0)
191        model2_box.Add((15,10))
192        model2_box.Add(self.model2, -1, 0)
193        model_vbox.Add(model1_box, -1, 0)
194        model_vbox.Add(model2_box, -1, 0)
195        sum_box.Add(model_vbox, -1, 10)
196        iy += 1
197        ix = 0
198        self.sizer.Add(sum_box, (iy, ix),
199                  (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
200        vbox.Add((10,10))
201        vbox.Add(self.static_line_1, 0, wx.EXPAND, 10)
202        vbox.Add(self.msg_sizer, 0, 
203                 wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE|wx.BOTTOM, 10)
204        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
205        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
206        sizer_button.Add(self.okButton, 0, 
207                         wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 0)
208        sizer_button.Add(self.closeButton, 0,
209                          wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)       
210        vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
211         
212        self.SetSizer(vbox)
213        self.Centre()
214       
215    def on_change_name(self, event=None):
216        """
217        Change name
218        """
219        if event is not None:
220            event.Skip()
221        self.name_tcl.SetBackgroundColour('white')
222        self.Refresh()
223   
224    def check_name(self, event=None):
225        """
226        Check name if exist already
227        """
228        self.on_change_name(None)
229        list_fnames = os.listdir(self.plugin_dir)
230
231        # function/file name
232        title = self.name_tcl.GetValue().lstrip().rstrip()
233        if title == '':
234            text = self.operator_cbox.GetLabel().strip()
235            if text == '+':
236                name = 'Sum'
237            else:
238                name = 'Multi'
239            self.name = name + M_NAME
240            title = self.name
241        self.name = title
242        t_fname = title + '.py'
243        if not self.overwrite_name:
244            if t_fname in list_fnames and title != self.name:
245                self.name_tcl.SetBackgroundColour('pink')
246                self.good_name = False
247                info = 'Error'
248                msg = "Name exists already."
249                wx.MessageBox(msg, info) 
250                self._notes = msg
251                color = 'red'
252                self._msg_box.SetLabel(msg)
253                self._msg_box.SetForegroundColour(color)
254                return self.good_name
255        self.fname = os.path.join(self.plugin_dir, t_fname)
256        s_title = title
257        if len(title) > 20:
258            s_title = title[0:19] + '...'
259        self._notes = "Model function (%s) has been set! \n" % str(s_title)
260        self.good_name = True
261        self.on_apply(self.fname)
262        return self.good_name
263   
264    def on_apply(self, path):
265        """
266        On Apply
267        """
268        try:
269            label = self.getText()
270            fname = path
271            name1 = label[0]
272            name2 = label[1]
273            self.write_string(fname, name1, name2)
274            self.compile_file(fname)
275            self.parent.update_custom_combo()
276            msg = self._notes
277            info = 'Info'
278            color = 'blue'
279        except:
280            msg= "Easy Custom Sum/Multipy: Error occurred..."
281            info = 'Error'
282            color = 'red'
283        self._msg_box.SetLabel(msg)
284        self._msg_box.SetForegroundColour(color)
285        if self.parent.parent != None:
286            from sans.guiframe.events import StatusEvent
287            wx.PostEvent(self.parent.parent, StatusEvent(status = msg, 
288                                                      info=info))
289        else:
290            raise
291                 
292    def _set_model_list(self):
293        """
294        Set the list of models
295        """
296        # list of model names
297        list = self.model_list
298        if len(list) > 1:
299            list.sort()
300        for idx in range(len(list)):
301            self.model1.Append(list[idx],idx) 
302            self.model2.Append(list[idx],idx)
303        self.model1.SetStringSelection(self.model1_string)
304        self.model2.SetStringSelection(self.model2_string)
305           
306    def on_model1(self, event):
307        """
308        Set model1
309        """
310        event.Skip()
311        self.model1_name = str(self.model1.GetValue())
312        self.model1_string = self.model1_name
313           
314    def on_model2(self, event):
315        """
316        Set model2
317        """
318        event.Skip()
319        self.model2_name = str(self.model2.GetValue())
320        self.model2_string = self.model2_name
321       
322    def on_select_operator(self, event=None):
323        """
324        On Select an Operator
325        """
326        item = event.GetEventObject()
327        text = item.GetLabel().strip()
328        if text == '+':
329            name = 'Sum'
330        else:
331            name = 'Multi'
332        self.operator = str(text)
333        self.explanation = "  custom model = scale_factor * "
334        self.explanation += "(model1 %s model2)\n"% self.operator
335        self.explanationctr.SetLabel(self.explanation)
336        self.name = name + M_NAME
337        self.sizer.Layout()
338             
339    def fill_oprator_combox(self):
340        """
341        fill the current combobox with the operator
342        """   
343        operator_list = [' +', ' *']
344        for oper in operator_list:
345            pos = self.operator_cbox.Append(str(oper))
346            self.operator_cbox.SetClientData(pos, str(oper.strip()))
347        self.operator_cbox.SetSelection(0)
348           
349    def getText(self):
350        """
351        Returns model name string as list
352        """
353        return [self.model1_name, self.model2_name]
354   
355    def write_string(self, fname, name1, name2):
356        """
357        Write and Save file
358        """
359        self.fname = fname 
360        description = self.desc_tcl.GetValue().lstrip().rstrip()
361        if description == '':
362            description = name1 + self.operator + name2
363        name = self.name_tcl.GetValue().lstrip().rstrip()
364        if name == '':
365            text = self.operator_cbox.GetLabel().strip()
366            if text == '+':
367                name = 'Sum'
368            else:
369                name = 'Multi'
370            self.name = name + M_NAME
371        path = self.fname
372        try:
373            out_f =  open(path,'w')
374        except :
375            raise
376        lines = SUM_TEMPLATE.split('\n')
377        for line in lines:
378            if line.count("import %s as P1"):
379                out_f.write(line % (name1, name1) + "\n")
380            elif line.count("import %s as P2"):
381                out_f.write(line % (name2, name2) + "\n")
382            elif line.count("self.description = '%s'"):
383                out_f.write(line % description + "\n")
384            elif line.count("run") and line.count("%s"):
385                out_f.write(line % self.operator)
386            elif line.count("evalDistribution") and line.count("%s"):
387                out_f.write(line % self.operator)
388            else:
389                out_f.write(line + "\n")
390        out_f.close()
391        #else:
392        #    msg = "Name exists already."
393       
394    def compile_file(self, path):
395        """
396        Compile the file in the path
397        """
398        path = self.fname
399        _compileFile(path)
400       
401    def delete_file(self, path):
402        """
403        Delete file in the path
404        """
405        _deleteFile(path)
406
407
408class EditorPanel(wx.ScrolledWindow):
409    """
410    Custom model function editor
411    """
412    def __init__(self, parent, base, path, title, *args, **kwds):
413        kwds['name'] = title
414        kwds["size"] = (EDITOR_WIDTH, EDITOR_HEIGTH)
415        kwds["style"]= wx.FULL_REPAINT_ON_RESIZE
416        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
417        #self.SetupScrolling()
418        self.parent = parent
419        self.base = base
420        self.path = path
421        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
422        self.font.SetPointSize(10)
423        self.reader = None
424        self.name = 'untitled'
425        self.overwrite_name = False
426        self.is_2d = False
427        self.fname = None
428        self.param_strings = ''
429        self.function_strings = ''
430        self._notes = ""
431        self._msg_box = None
432        self.msg_sizer = None
433        self.warning = ""
434        self._description = "New Custom Model"
435        #self._default_save_location = os.getcwd()
436        self._do_layout()
437        #self.bt_apply.Disable()
438
439             
440    def _define_structure(self):
441        """
442        define initial sizer
443        """
444        #w, h = self.parent.GetSize()
445        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
446        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
447        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
448        self.desc_sizer = wx.BoxSizer(wx.VERTICAL)
449        self.param_sizer = wx.BoxSizer(wx.VERTICAL)
450        self.function_sizer = wx.BoxSizer(wx.VERTICAL)
451        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
452        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
453       
454    def _layout_name(self):
455        """
456        Do the layout for file/function name related widgets
457        """
458        #title name [string]
459        name_txt = wx.StaticText(self, -1, 'Function Name : ') 
460        overwrite_cb = wx.CheckBox(self, -1, "Overwrite?", (10, 10))
461        overwrite_cb.SetValue(False)
462        overwrite_cb.SetToolTipString("Overwrite it if already exists?")
463        wx.EVT_CHECKBOX(self, overwrite_cb.GetId(), self.on_over_cb)
464        #overwrite_cb.Show(False)
465        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
466        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
467        self.name_tcl.SetValue('MyFunction')
468        self.name_tcl.SetFont(self.font)
469        hint_name = "Unique Model Function Name."
470        self.name_tcl.SetToolTipString(hint_name)
471        self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT|wx.TOP, 0),
472                                       (overwrite_cb, 0, wx.LEFT, 20)])
473        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT|wx.TOP, 10),
474                                       (self.name_hsizer, 0, 
475                                        wx.LEFT|wx.TOP|wx.BOTTOM, 10)])
476       
477       
478    def _layout_description(self):
479        """
480        Do the layout for description related widgets
481        """
482        #title name [string]
483        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ') 
484        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
485        self.desc_tcl.SetValue('')
486        #self.name_tcl.SetFont(self.font)
487        hint_desc = "Write a short description of the model function."
488        self.desc_tcl.SetToolTipString(hint_desc)
489        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT|wx.TOP, 10),
490                                       (self.desc_tcl, 0, 
491                                        wx.LEFT|wx.TOP|wx.BOTTOM, 10)])     
492    def _layout_param(self):
493        """
494        Do the layout for parameter related widgets
495        """
496        param_txt = wx.StaticText(self, -1, 'Fit Parameters (if any): ') 
497        param_tip = "#Set the parameters and initial values.\n"
498        param_tip += "#Example:\n"
499        param_tip += "A = 1\nB = 1"
500        #param_txt.SetToolTipString(param_tip)
501        id  = wx.NewId() 
502        self.param_tcl = EditWindow(self, id, wx.DefaultPosition, 
503                            wx.DefaultSize, wx.CLIP_CHILDREN|wx.SUNKEN_BORDER)
504        self.param_tcl.setDisplayLineNumbers(True)
505        self.param_tcl.SetToolTipString(param_tip)
506        self.param_sizer.AddMany([(param_txt, 0, wx.LEFT, 10),
507                        (self.param_tcl, 1, wx.EXPAND|wx.ALL, 10)])
508
509   
510    def _layout_function(self):
511        """
512        Do the layout for function related widgets
513        """
514        function_txt = wx.StaticText(self, -1, 'Function(x) : ') 
515        hint_function = "#Example:\n"
516        hint_function += "if x <= 0:\n"
517        hint_function += "    y = A + B\n"
518        hint_function += "else:\n"
519        hint_function += "    y = A + B * cos(2 * pi * x)\n"
520        hint_function += "return y\n"
521        id  = wx.NewId() 
522        self.function_tcl = EditWindow(self, id, wx.DefaultPosition, 
523                            wx.DefaultSize, wx.CLIP_CHILDREN|wx.SUNKEN_BORDER)
524        self.function_tcl.setDisplayLineNumbers(True)
525        self.function_tcl.SetToolTipString(hint_function)
526        self.function_sizer.Add(function_txt, 0, wx.LEFT, 10)
527        self.function_sizer.Add( self.function_tcl, 1, wx.EXPAND|wx.ALL, 10)
528       
529    def _layout_msg(self):
530        """
531        Layout msg
532        """
533        self._msg_box = wx.StaticText(self, -1, self._notes)
534        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 10) 
535                   
536    def _layout_button(self): 
537        """
538        Do the layout for the button widgets
539        """         
540        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
541        self.bt_apply.SetToolTipString("Save changes into the imported data.")
542        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
543       
544        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
545        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
546        self.bt_close.SetToolTipString("Close this panel.")
547       
548        self.button_sizer.AddMany([(self.bt_apply, 0, 
549                                    wx.LEFT, EDITOR_WIDTH * 0.8),
550                                   (self.bt_close, 0, 
551                                    wx.LEFT|wx.BOTTOM, 15)])
552         
553    def _do_layout(self):
554        """
555        Draw the current panel
556        """
557        self._define_structure()
558        self._layout_name()
559        self._layout_description()
560        self._layout_param()
561        self._layout_function()
562        self._layout_msg()
563        self._layout_button()
564        self.main_sizer.AddMany([(self.name_sizer, 0, 
565                                        wx.EXPAND|wx.ALL, 5),
566                                 (wx.StaticLine(self), 0, 
567                                       wx.ALL|wx.EXPAND, 5),
568                                 (self.desc_sizer, 0, 
569                                        wx.EXPAND|wx.ALL, 5),
570                                 (wx.StaticLine(self), 0, 
571                                       wx.ALL|wx.EXPAND, 5),
572                                (self.param_sizer, 0,
573                                         wx.EXPAND|wx.ALL, 5),
574                                 (wx.StaticLine(self), 0, 
575                                       wx.ALL|wx.EXPAND, 5),
576                                (self.function_sizer, 1,
577                                         wx.EXPAND|wx.ALL, 5),
578                                 (wx.StaticLine(self), 0, 
579                                       wx.ALL|wx.EXPAND, 5),
580                                 (self.msg_sizer, 0, 
581                                        wx.EXPAND|wx.ALL, 5),
582                                (self.button_sizer, 0,
583                                         wx.EXPAND|wx.ALL, 5)])
584        self.SetSizer(self.main_sizer)
585        self.SetAutoLayout(True)
586   
587    def get_notes(self):
588        """
589        return notes
590        """
591        return self._notes
592                 
593    def on_change_name(self, event=None):
594        """
595        Change name
596        """
597        if event is not None:
598            event.Skip()
599        self.name_tcl.SetBackgroundColour('white')
600        self.Refresh()
601   
602    def check_name(self):
603        """
604        Check name if exist already
605        """
606        self._notes = ''
607        self.on_change_name(None)
608        plugin_dir = self.path
609        list_fnames = os.listdir(plugin_dir)
610        # function/file name
611        title = self.name_tcl.GetValue().lstrip().rstrip()
612        self.name = title
613        t_fname = title + '.py'
614        if not self.overwrite_name:
615            if t_fname in list_fnames:
616                self.name_tcl.SetBackgroundColour('pink')
617                return False
618        self.fname = os.path.join(plugin_dir, t_fname)
619        s_title = title
620        if len(title) > 20:
621            s_title = title[0:19] + '...'
622        self._notes += "Model function name set "
623        self._notes += "to %s. \n" % str(s_title)
624        return True
625   
626    def on_over_cb(self, event):
627        """
628        Set overwrite name flag on cb event
629        """
630        if event is not None:
631            event.Skip()
632        cb = event.GetEventObject()
633        self.overwrite_name = cb.GetValue()
634       
635    def on_click_apply(self, event):
636        """   
637        Changes are saved in data object imported to edit
638        """
639        #must post event here
640        event.Skip()
641        info = 'Info'
642        # Sort out the errors if occur
643        if self.check_name():
644            name = self.name_tcl.GetValue().lstrip().rstrip()
645            description = self.desc_tcl.GetValue()
646            param_str = self.param_tcl.GetText()
647            func_str = self.function_tcl.GetText()
648            # No input for the model function
649            if func_str.lstrip().rstrip():
650                if func_str.count('return') > 0:
651                    self.write_file(self.fname, description, param_str, func_str)
652                    tr_msg = _compileFile(self.fname)
653                    msg = tr_msg.__str__()
654                    # Compile error
655                    if msg:
656                        _deleteFile(self.fname)
657                        msg +=  "\nCompile Failed"
658                    else:
659                        msg = ''
660                else:
661                    msg = "Error: The func(x) must 'return' a value at least.\n"
662                    msg += "For example: \n\nreturn 2*x"
663            else:
664                msg = 'Error: Function is not defined.'
665        else:
666            msg = "Name exists already."
667        # Prepare for the messagebox
668        if not msg:
669            if self.base != None:
670                self.base.update_custom_combo()
671            msg = "Successful!!!"
672            msg += "  " + self._notes
673            msg += " Please look for it in the 'Customized Models' box."
674            info = 'Info'
675            color = 'blue'
676        else:
677            info = 'Error'
678            color = 'red'
679            wx.MessageBox(msg, info) 
680       
681        self._msg_box.SetLabel(msg)
682        self._msg_box.SetForegroundColour(color)
683        # Send msg to the top window 
684        if self.base != None:
685                from sans.guiframe.events import StatusEvent
686                wx.PostEvent(self.base.parent, StatusEvent(status = msg, 
687                                                      info=info))
688        self.warning = msg
689
690               
691    def write_file(self, fname, desc_str, param_str, func_str): 
692        """
693        Write content in file
694       
695        :param fname: full file path
696        :param desc_str: content of the description strings
697        :param param_str: content of params; Strings 
698        :param func_str: content of func; Strings
699        """ 
700        try:
701            out_f =  open(fname,'w')
702        except :
703            raise
704        # Prepare the content of the function
705        lines = CUSTOM_TEMPLATE.split('\n')
706
707        has_scipy = func_str.count("scipy.")
708        self.is_2d = func_str.count("#self.ndim = 2")
709        line_2d = ''
710        if self.is_2d:
711            line_2d = CUSTOM_2D_TEMP.split('\n')
712        line_test = TEST_TEMPLATE.split('\n')
713        local_params = ''
714        spaces = '        '#8spaces
715        # write function here
716        for line in lines:
717            # The location where to put the strings is
718            # hard-coded in the template as shown below.
719            if line.count("#self.params here"):
720                for param_line in param_str.split('\n'):
721                    p_line = param_line.lstrip().rstrip()
722                    if p_line:
723                        p0_line = self.set_param_helper(p_line)
724                        local_params += self.set_function_helper(p_line)
725                        out_f.write(p0_line)
726            elif line.count("#local params here"):
727                if local_params:
728                    out_f.write(local_params)
729            elif line.count("self.description = "):
730                des0 = self.name + "\\n"
731                desc = str(desc_str.lstrip().rstrip().replace('\"', ''))
732                out_f.write(line% (des0 + desc) + "\n")
733            elif line.count("def function(self, x=0.0%s):"):
734                if self.is_2d:
735                    y_str = ', y=0.0'
736                    out_f.write(line% y_str + "\n")
737                else:
738                    out_f.write(line% '' + "\n")
739            elif line.count("#function here"):
740                for func_line in func_str.split('\n'):
741                    f_line = func_line.rstrip()
742                    if f_line:
743                        out_f.write(spaces + f_line + "\n")
744                if not func_str:
745                    dep_var = 'y'
746                    if self.is_2d:
747                        dep_var = 'z'
748                    out_f.write(spaces + 'return %s'% dep_var + "\n")
749            elif line.count("#import scipy?"):
750                if has_scipy:
751                    out_f.write("import scipy" + "\n")
752            #elif line.count("name = "):
753            #    out_f.write(line % self.name + "\n")
754            elif line:
755                out_f.write(line + "\n")
756        # run string for 2d
757        if line_2d:
758            for line in line_2d:
759                out_f.write(line + "\n")
760        # Test strins
761        for line in line_test:
762            out_f.write(line + "\n")
763   
764        out_f.close() 
765   
766    def set_param_helper(self, line):   
767        """
768        Get string in line to define the params dictionary
769       
770        :param line: one line of string got from the param_str
771        """
772        flag = True
773        params_str = ''
774        spaces = '        '#8spaces
775        items = line.split(";")
776        for item in items:
777            name = item.split("=")[0].lstrip().rstrip()
778            try:
779                value = item.split("=")[1].lstrip().rstrip()
780                float(value)
781            except:
782                value = 1.0 # default
783            params_str += spaces + "self.params['%s'] = %s\n"% (name, value)
784           
785        return params_str
786
787    def set_function_helper(self, line):   
788        """
789        Get string in line to define the local params
790       
791        :param line: one line of string got from the param_str
792        """
793        flag = True
794        params_str = ''
795        spaces = '        '#8spaces
796        items = line.split(";")
797        for item in items:
798            name = item.split("=")[0].lstrip().rstrip()
799            params_str += spaces + "%s = self.params['%s']\n"% (name, name)
800        return params_str
801   
802    def get_warning(self):
803        """
804        Get the warning msg
805        """
806        return self.warning
807       
808    def on_close(self, event):
809        """
810        leave data as it is and close
811        """
812        self.parent.Close()
813        event.Skip()
814       
815class EditorWindow(wx.Frame):
816    def __init__(self, parent, base, path, title, 
817                 size=(EDITOR_WIDTH, EDITOR_HEIGTH), *args, **kwds):
818        kwds["title"] = title
819        kwds["size"] = size
820        wx.Frame.__init__(self, parent=None, *args, **kwds)
821        self.parent = parent
822        self.panel = EditorPanel(parent=self, base=parent, 
823                                 path=path, title=title)
824        self.Show(True)
825        wx.EVT_CLOSE(self, self.OnClose)
826   
827    def OnClose(self, event): 
828        """
829        On close event
830        """
831        if self.parent != None:
832            self.parent.new_model_frame = None
833        self.Destroy() 
834
835## Templates for custom models
836CUSTOM_TEMPLATE = """
837from sans.models.pluginmodel import Model1DPlugin
838from math import *
839import os
840import sys
841import numpy
842#import scipy?
843class Model(Model1DPlugin):
844    name = ""                             
845    def __init__(self):
846        Model1DPlugin.__init__(self, name=self.name) 
847        #set name same as file name
848        self.name = self.get_fname()                                                   
849        #self.params here
850        self.description = "%s"
851        self.set_details()
852    def function(self, x=0.0%s):
853        #local params here
854        #function here
855"""
856CUSTOM_2D_TEMP = """
857    def run(self, x=0.0, y=0.0):
858        if x.__class__.__name__ == 'list':
859            x_val = x[0]
860            y_val = y[0]*0.0
861            return self.function(x_val, y_val)
862        elif x.__class__.__name__ == 'tuple':
863            msg = "Tuples are not allowed as input to BaseComponent models"
864            raise ValueError, msg
865        else:
866            return self.function(x, 0.0)
867    def runXY(self, x=0.0, y=0.0):
868        if x.__class__.__name__ == 'list':
869            return self.function(x, y)
870        elif x.__class__.__name__ == 'tuple':
871            msg = "Tuples are not allowed as input to BaseComponent models"
872            raise ValueError, msg
873        else:
874            return self.function(x, y)
875    def evalDistribution(self, qdist):
876        if qdist.__class__.__name__ == 'list':
877            msg = "evalDistribution expects a list of 2 ndarrays"
878            if len(qdist)!=2:
879                raise RuntimeError, msg
880            if qdist[0].__class__.__name__ != 'ndarray':
881                raise RuntimeError, msg
882            if qdist[1].__class__.__name__ != 'ndarray':
883                raise RuntimeError, msg
884            v_model = numpy.vectorize(self.runXY, otypes=[float])
885            iq_array = v_model(qdist[0], qdist[1])
886            return iq_array
887        elif qdist.__class__.__name__ == 'ndarray':
888            v_model = numpy.vectorize(self.runXY, otypes=[float])
889            iq_array = v_model(qdist)
890            return iq_array
891"""
892TEST_TEMPLATE = """
893    def get_fname(self):
894        path = sys._getframe().f_code.co_filename
895        basename  = os.path.basename(path)
896        name, _ = os.path.splitext(basename)
897        return name
898######################################################################
899## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!       
900if __name__ == "__main__":
901    m= Model()
902    out1 = m.runXY(0.0)
903    out2 = m.runXY(0.01)
904    isfine1 = numpy.isfinite(out1)
905    isfine2 = numpy.isfinite(out2)
906    print "Testing the value at Q = 0.0:"
907    print out1, " : finite? ", isfine1
908    print "Testing the value at Q = 0.01:"
909    print out2, " : finite? ", isfine2
910    if isfine1 and isfine2:
911        print "===> Simple Test: Passed!"
912    else:
913        print "===> Simple Test: Failed!"
914"""
915SUM_TEMPLATE = """
916# A sample of an experimental model function for Sum/Multiply(Pmodel1,Pmodel2)
917import copy
918from sans.models.pluginmodel import Model1DPlugin
919# User can change the name of the model (only with single functional model)
920#P1_model:
921from sans.models.%s import %s as P1
922
923#P2_model:
924from sans.models.%s import %s as P2
925import os
926import sys
927
928class Model(Model1DPlugin):
929    name = ""
930    def __init__(self):
931        Model1DPlugin.__init__(self, name='')
932        p_model1 = P1()
933        p_model2 = P2()
934        ## Setting  model name model description
935        self.description = '%s'
936        self.name = self.get_fname()
937        if self.name.rstrip().lstrip() == '':
938            self.name = self._get_name(p_model1.name, p_model2.name)
939        if self.description.rstrip().lstrip() == '':
940            self.description = p_model1.name
941            self.description += p_model2.name
942            self.fill_description(p_model1, p_model2)
943
944        ## Define parameters
945        self.params = {}
946
947        ## Parameter details [units, min, max]
948        self.details = {}
949       
950        # non-fittable parameters
951        self.non_fittable = p_model1.non_fittable 
952        self.non_fittable += p_model2.non_fittable 
953           
954        ##models
955        self.p_model1= p_model1
956        self.p_model2= p_model2
957       
958       
959        ## dispersion
960        self._set_dispersion()
961        ## Define parameters
962        self._set_params()
963        ## New parameter:Scaling factor
964        self.params['scale_factor'] = 1
965       
966        ## Parameter details [units, min, max]
967        self._set_details()
968        self.details['scale_factor'] = ['', None, None]
969
970       
971        #list of parameter that can be fitted
972        self._set_fixed_params() 
973        ## parameters with orientation
974        for item in self.p_model1.orientation_params:
975            new_item = "p1_" + item
976            if not new_item in self.orientation_params:
977                self.orientation_params.append(new_item)
978           
979        for item in self.p_model2.orientation_params:
980            new_item = "p2_" + item
981            if not new_item in self.orientation_params:
982                self.orientation_params.append(new_item)
983        # get multiplicity if model provide it, else 1.
984        try:
985            multiplicity1 = p_model1.multiplicity
986            try:
987                multiplicity2 = p_model2.multiplicity
988            except:
989                multiplicity2 = 1
990        except:
991            multiplicity1 = 1
992            multiplicity2 = 1
993        ## functional multiplicity of the model
994        self.multiplicity1 = multiplicity1 
995        self.multiplicity2 = multiplicity2   
996        self.multiplicity_info = []   
997       
998    def _clone(self, obj):
999        obj.params     = copy.deepcopy(self.params)
1000        obj.description     = copy.deepcopy(self.description)
1001        obj.details    = copy.deepcopy(self.details)
1002        obj.dispersion = copy.deepcopy(self.dispersion)
1003        obj.p_model1  = self.p_model1.clone()
1004        obj.p_model2  = self.p_model2.clone()
1005        #obj = copy.deepcopy(self)
1006        return obj
1007   
1008    def _get_name(self, name1, name2):
1009        p1_name = self._get_upper_name(name1)
1010        if not p1_name:
1011            p1_name = name1
1012        name = p1_name
1013        name += "%s"% (self.operator)
1014        p2_name = self._get_upper_name(name2)
1015        if not p2_name:
1016            p2_name = name2
1017        name += p2_name
1018        return name
1019   
1020    def _get_upper_name(self, name=None):
1021        if name == None:
1022            return ""
1023        upper_name = ""
1024        str_name = str(name)
1025        for index in range(len(str_name)):
1026            if str_name[index].isupper():
1027                upper_name += str_name[index]
1028        return upper_name
1029       
1030    def _set_dispersion(self):
1031        ##set dispersion only from p_model
1032        for name , value in self.p_model1.dispersion.iteritems():
1033            #if name.lower() not in self.p_model1.orientation_params:
1034            new_name = "p1_" + name
1035            self.dispersion[new_name]= value
1036        for name , value in self.p_model2.dispersion.iteritems():
1037            #if name.lower() not in self.p_model2.orientation_params:
1038            new_name = "p2_" + name
1039            self.dispersion[new_name]= value
1040           
1041    def function(self, x=0.0):
1042        return 0
1043                               
1044    def getProfile(self):
1045        try:
1046            x,y = self.p_model1.getProfile()
1047        except:
1048            x = None
1049            y = None
1050           
1051        return x, y
1052   
1053    def _set_params(self):
1054        for name , value in self.p_model1.params.iteritems():
1055            # No 2D-supported
1056            #if name not in self.p_model1.orientation_params:
1057            new_name = "p1_" + name
1058            self.params[new_name]= value
1059           
1060        for name , value in self.p_model2.params.iteritems():
1061            # No 2D-supported
1062            #if name not in self.p_model2.orientation_params:
1063            new_name = "p2_" + name
1064            self.params[new_name]= value
1065               
1066        # Set "scale" as initializing
1067        self._set_scale_factor()
1068     
1069           
1070    def _set_details(self):
1071        for name ,detail in self.p_model1.details.iteritems():
1072            new_name = "p1_" + name
1073            #if new_name not in self.orientation_params:
1074            self.details[new_name]= detail
1075           
1076        for name ,detail in self.p_model2.details.iteritems():
1077            new_name = "p2_" + name
1078            #if new_name not in self.orientation_params:
1079            self.details[new_name]= detail
1080   
1081    def _set_scale_factor(self):
1082        pass
1083       
1084               
1085    def setParam(self, name, value):
1086        # set param to this (p1, p2) model
1087        self._setParamHelper(name, value)
1088       
1089        ## setParam to p model
1090        model_pre = name.split('_', 1)[0]
1091        new_name = name.split('_', 1)[1]
1092        if model_pre == "p1":
1093            if new_name in self.p_model1.getParamList():
1094                self.p_model1.setParam(new_name, value)
1095        elif model_pre == "p2":
1096             if new_name in self.p_model2.getParamList():
1097                self.p_model2.setParam(new_name, value)
1098        elif name.lower() == 'scale_factor':
1099            self.params['scale_factor'] = value
1100        else:
1101            raise ValueError, "Model does not contain parameter %s" % name
1102           
1103    def getParam(self, name):
1104        # Look for dispersion parameters
1105        toks = name.split('.')
1106        if len(toks)==2:
1107            for item in self.dispersion.keys():
1108                # 2D not supported
1109                if item.lower()==toks[0].lower():
1110                    for par in self.dispersion[item]:
1111                        if par.lower() == toks[1].lower():
1112                            return self.dispersion[item][par]
1113        else:
1114            # Look for standard parameter
1115            for item in self.params.keys():
1116                if item.lower()==name.lower():
1117                    return self.params[item]
1118        return 
1119        #raise ValueError, "Model does not contain parameter %s" % name
1120       
1121    def _setParamHelper(self, name, value):
1122        # Look for dispersion parameters
1123        toks = name.split('.')
1124        if len(toks)== 2:
1125            for item in self.dispersion.keys():
1126                if item.lower()== toks[0].lower():
1127                    for par in self.dispersion[item]:
1128                        if par.lower() == toks[1].lower():
1129                            self.dispersion[item][par] = value
1130                            return
1131        else:
1132            # Look for standard parameter
1133            for item in self.params.keys():
1134                if item.lower()== name.lower():
1135                    self.params[item] = value
1136                    return
1137           
1138        raise ValueError, "Model does not contain parameter %s" % name
1139             
1140   
1141    def _set_fixed_params(self):
1142        for item in self.p_model1.fixed:
1143            new_item = "p1" + item
1144            self.fixed.append(new_item)
1145        for item in self.p_model2.fixed:
1146            new_item = "p2" + item
1147            self.fixed.append(new_item)
1148
1149        self.fixed.sort()
1150               
1151                   
1152    def run(self, x = 0.0):
1153        self._set_scale_factor()
1154        return self.params['scale_factor'] * \
1155(self.p_model1.run(x) %s self.p_model2.run(x))
1156   
1157    def runXY(self, x = 0.0):
1158        self._set_scale_factor()
1159        return self.params['scale_factor'] * \
1160(self.p_model1.runXY(x) %s self.p_model2.runXY(x))
1161   
1162    ## Now (May27,10) directly uses the model eval function
1163    ## instead of the for-loop in Base Component.
1164    def evalDistribution(self, x = []):
1165        self._set_scale_factor()
1166        return self.params['scale_factor'] * \
1167(self.p_model1.evalDistribution(x) %s \
1168self.p_model2.evalDistribution(x))
1169
1170    def set_dispersion(self, parameter, dispersion):
1171        value= None
1172        new_pre = parameter.split("_", 1)[0]
1173        new_parameter = parameter.split("_", 1)[1]
1174        try:
1175            if new_pre == 'p1' and \
1176new_parameter in self.p_model1.dispersion.keys():
1177                value= self.p_model1.set_dispersion(new_parameter, dispersion)
1178            if new_pre == 'p2' and \
1179new_parameter in self.p_model2.dispersion.keys():
1180                value= self.p_model2.set_dispersion(new_parameter, dispersion)
1181            self._set_dispersion()
1182            return value
1183        except:
1184            raise
1185
1186    def fill_description(self, p_model1, p_model2):
1187        description = ""
1188        description += "This model gives the summation or multiplication of"
1189        description += "%s and %s. "% ( p_model1.name, p_model2.name )
1190        self.description += description
1191         
1192    def get_fname(self):
1193        path = sys._getframe().f_code.co_filename
1194        basename  = os.path.basename(path)
1195        name, _ = os.path.splitext(basename)
1196        return name     
1197           
1198if __name__ == "__main__":
1199    m1= Model()
1200    #m1.setParam("p1_scale", 25) 
1201    #m1.setParam("p1_length", 1000)
1202    #m1.setParam("p2_scale", 100)
1203    #m1.setParam("p2_rg", 100)
1204    out1 = m1.runXY(0.01)
1205
1206    m2= Model()
1207    #m2.p_model1.setParam("scale", 25)
1208    #m2.p_model1.setParam("length", 1000)
1209    #m2.p_model2.setParam("scale", 100)
1210    #m2.p_model2.setParam("rg", 100)
1211    out2 = m2.p_model1.runXY(0.01) %s m2.p_model2.runXY(0.01)\n
1212    print "My name is %s."% m1.name
1213    print out1, " = ", out2
1214    if out1 == out2:
1215        print "===> Simple Test: Passed!"
1216    else:
1217        print "===> Simple Test: Failed!"
1218"""
1219     
1220#if __name__ == "__main__":
1221#    app = wx.PySimpleApp()
1222#    frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"])   
1223#    frame.Show(True)
1224#    app.MainLoop()             
1225
1226if __name__ == "__main__":
1227    from sans.perspectives.fitting import models
1228    dir_path = models.find_plugins_dir()
1229    app  = wx.App()
1230    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
1231    app.MainLoop()         
Note: See TracBrowser for help on using the repository browser.