Project

General

Profile

Creating new parameter gui in a Leginion node

Added by Nicolas Coudray over 13 years ago

Hello,

Could you please, write a small tutorial on what should be done to create new parameters GUI in a Leginon node and how we should write the python GUI with database record?

This question is related to issue #1194 about the need to parametrize Matlab algorithms through a GUI, if, instead of using a Matlab GUI, we are considering a modification of the Leginon GUI.
Through these new parameters GUI, the user will then be able to change the default parameters used by the Matlab algorithms.

Thank you in advance,
Nicolas C.


Replies (3)

RE: Creating new parameter gui in a Leginion node - Added by Anchi Cheng over 13 years ago

In Leginon we call these parameters settings of the node class. There are components that make up a setting of a node that a user can change in Leginon gui and have that recorded in the database so that it will be set automatically next time the same user starts Leginon. I will use MatlabTargetFinder class as an example here to illustrate the tasks required to create these components:

  1. Mapping of the setting to a data type in the node class, defined in the file leginon/leginondata.py
  2. Assignment of a default value for the class, defined in the file that defines the class. In this case, leginon/matlabtargetfinder.py
  3. Creation of a widget in the wxPython gui file for the same class. In this case, leginon/gui/wx/MatlabTargetFinder.py
  4. Usage of the setting in some function of the class. This would be in leginon/matlabtargetfinder.py in this case.

As an example, we will add an integer entry for a binning factor that we use to reduce the size of the image that is to be passed to Matlab script.

Mapping the parameter:

  1. Search for the class name "MatlabTargetFinder" in leginon/leginondata.py. You should find the definition of its sinedon data class MatlabTargetFinderSettingsData.
    class MatlabTargetFinderSettingsData(TargetFinderSettingsData):
      def typemap(cls):
        return TargetFinderSettingsData.typemap() + (
          ('test image', str),
          ('module path', str),
        )
      typemap = classmethod(typemap)
    

    In this class, two settings are added in additional to what is defined in its base class,TargetFinderSettingsData. 'test image' and 'module path' are python built-in str type. Look around the file and you will find several common types sinedon knows how to propagate the database with.
  2. To add the new setting, this line is inserted after the existing settings definition (i.e., the line containing the word 'module path'):
         ('binning',int),
    

    Make sure you follow the indentation required by Python. In Leginon, the standard indentation uses tab, not spaces.
  3. save the file.

Assign default value to the new setting

  1. Search in the module file (leginon/matlabtargetfinder.py in this case) the string "defaultsettings". You should find it at the beginning of the class definition you want to add settings to. In this case, we find
    class MatlabTargetFinder(targetfinder.TargetFinder):
      panelclass = gui.wx.MatlabTargetFinder.Panel
      settingsclass = leginondata.MatlabTargetFinderSettingsData
      defaultsettings = dict(targetfinder.TargetFinder.defaultsettings)
      defaultsettings.update({
        'module': '',
        'test image': '',
      })
    

    You can see that the two existing settings are both default to empty strings.
  2. Insert your new setting and default as a line after the existing ones like this:
        'binning': 1,
    
  3. Save this. We will go over the part about using the setting later.

Create a widget in the gui

The settings are normally hidden in the panel and their values are changes in a pop-up dialog window. The gui is created with wxPython. Leginon often use GridBagSizer so that the layout is flexable. It does make it harder to write the code, but simple addition at the end is not too hard, even if it does not look great.

  1. Search for "ScrolledSettings" in the module gui file, leginon/gui/wx/MatlabTargetFinder.py in this case.
    There is a basic settings layout and a full, advanced layout. In the case of MatlabTargetFinder, the basic layout is just the ScrolledSettings of its base class, TargetFinder. The advanced layout is where we put the customized settings of MatlabTargetFinder. The gui of 'module path' setting is defined with these lines:
         self.widgets['module path'] = filebrowse.FileBrowseButton(self,labelText='Matlab File:', fileMask='*.m')
         sz = wx.GridBagSizer(5, 5)
         sz.Add(self.widgets['module path'], (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL)
    
    • The first line defines an item in self.widgets. self.widgets contain items with each of the settings of MatlabFinder as the key. Leginon will handle the saving of the values assigned to items in self.widgets when the setting change is confirmed (by clicking the Apply button or clicking the OK button to close the dialog)
    • The second line defines a GridBagSizer so that we can put all the widgets in there.
    • The third line add the widget defined in the first line into the GridBagSizer defined in line 2. (0, 0) says that the widget is to be placed at row 0 and column 0. (1,1) says that the widget spends one row and one column. the rest is for alignment relative to other widgets in the same column or row.
    • Note that the current leginon code look a bit different with it start at row 1 column 0. wxPython just moved it up automatically since the one at (0,0) was commented out.
  2. To add our new settings, we need to first define the item in self.widgets. Therefore, we add this line before the GridBagSizer is defined:
        self.widgets['binning'] = IntEntry(self, -1, min=1, chars=2)
    

    This is a small Entry box for integer defined in leginon/gui/wx/Entry.py. You will need to import that module at the beginning of of this script like this:
    from leginon.gui.wx.Entry import IntEntry
    
  3. We now want to add this widget into the same GridBagSizer. Put this line after that of the existing widget addition:
         sz.Add(self.widgets['binning'], (1, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL)
    

    This puts the widget at row 1, column 0, that is, below the last one, and occupies the same width of the grid as the last one. This is a basic gui. If you want a label with this, you might want to create a GridBagSizer to contain both the label and the entry so that they appear on the same line without gaps.
        szbinning = wx.GridBagSizer(5, 5)
        label = wx.StaticText(self, -1, 'Bin the image before sending to Matlab by:')
        szbinning.Add(label, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL)
        szbinning.Add(self.widgets['binning'], (0, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE)
    
        sz.Add(szbinning, (1, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL)
    

    As you can see it can get complicate if you want the perfect look. There are a lot of examples in different node classes such as leginon/gui/wx/Acquisition.py

Use the setting in the class

A setting is called in the class by its name as the key. Therefore, self.settings['binning'] will have the value of the current binning factor saved in the database when it is used. Now to use our binning setting in the function findTargets after the numpy array, 'image' is obtained through calling imdata['image'], this line is added before mlabraw or pymat put it into Matlab:

    image = imagefun.bin(image,self.settings['binnning'])

Since it uses imagefun from pyami package, you need to import that at the beginning of the script for this to work.
from pyami import imagefun

Of course in this case, you may need to write the part in the function matlabFindTargets to scale back the targets found back to the original image dimension. I won't go into that.

RE: Creating new parameter gui in a Leginion node - Added by Nicolas Coudray over 13 years ago

Hello,

Thank you very much for this tutorial, Anchi.

The implementation of the buttons to obtain the GUI suggested in issue #1194 has been tried. So, the leginon/gui/wx/MatlabTargetFinder.py file has been modified as follows:

In the "def initialize(sef)" function of the "ScrolledSettings" class, between the line

sz.Add(self.widgets['module path'], (1, 0), (1, 1),wx.ALIGN_CENTER_VERTICAL)

and the line

sbsz.Add(sz, 1, wx.EXPAND|wx.ALL, 5)

these lines have been added:

self.widgets['parametergui path'] = filebrowse.FileBrowseButton(self,
labelText='Matlab GUI File:', fileMask='*.m')
sz.Add(self.widgets['parametergui path'], (2, 0), (1, 1),
wx.ALIGN_CENTER_VERTICAL)
paramguibut = wx.Button(self, -1, 'Run parametrization GUI')
self.Bind(wx.EVT_BUTTON, self.onRunParamGUI, paramguibut)
sz.Add(paramguibut, (3, 0), (1, 1),
wx.ALIGN_CENTER_VERTICAL)

Then, in the same class, a new function has been defined to launch the Matlab GUI for the specific paramtrization of the algo:

def onRunParamGUI(self, evt):
self.handle = pymat.open()
d, f = os.path.split(self.widgets['parametergui path'].GetValue()) #self.settings['parametergui path'])
if d:
pymat.eval(self.handle, 'path(path, \'%s\')' % d)
if not f[:-2]:
raise RuntimeError
pymat.eval(self.handle, '%s' % f[:-2])

At the beginning of the script:

import os.path
try:
import mlabraw as pymat
except:
pymat = None

have been added.

It looks like it does what we need. However, when we first try to select a file through the new box, the following message appears:

(python:18538): Gtk-CRITICAL *: gtk_file_system_unix_get_info: assertion `g_path_is_absolute (filename)' failed
(python:18538): Gtk-WARNING *
: file_system_unix=0x143e31f0 still has handle=0x143f5ac0 at finalization which is NOT CANCELLED!

It however does not seem to be critical.
Cheers,
N.

RE: Creating new parameter gui in a Leginion node - Added by Anchi Cheng over 13 years ago

Glad that you get it working.

Google search of the error showed this page with a solution of first making the path absolution. You might want to give it a try.

http://wxpython-users.1045709.n5.nabble.com/Python-2-5-Warnings-td2360746.html

    (1-3/3)