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

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 41a8cb3 was 41a8cb3, checked in by Jae Cho <jhjcho@…>, 12 years ago

added msg in the panel

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