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

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

trying to fix test problem

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