Skip to content

CellBlender Plotting Plug Ins

cnlbob edited this page Oct 31, 2015 · 3 revisions

CellBlender Plotting Plug Ins

CellBlender is designed to provide simulation results to a number of plotting / analysis programs. These programs typically plot the results for viewing, but they could perform additional processing as desired. This page gives an overview of the plotting system and describes the interface used by CellBlender's plotters.


Overview and Basic Plotting Interface

The CellBlender plotting system expects all plug-ins to contain an __init__.py program located in a uniquely named subdirectory below cellblender/data_plotters. For example, these are some of the current plug-ins:

  • cellblender/data_plotters/mpl_plot/__init__.py
  • cellblender/data_plotters/java_plot/__init__.py
  • cellblender/data_plotters/xmgrace/__init__.py
  • cellblender/data_plotters/mpl_simple/__init__.py

The __init__.py program contained in each subdirectory is responsible for providing 3 functions:

  • get_name() - Returns the name of this plotter (typically displayed on a button or control)
  • requirements_met() - Returns true if the system supports this plotter (supporting libraries etc)
  • plot(data_path, plot_spec) - Called when the user requests plotting using this plotter

As mentioned above, the first two functions (get_name and requirements_met) are generally fairly simple. Sometimes the requirements_met function may require some hunting around to find things like a valid Python installation or a Java Virtual Machine, or a special program like XmGrace or MatLab or Excel.

The function that does most of the work is the plot function. The plot function will get passed two string values:

  • data_path - path to the location of the "react_data" folder which contains plotting data files
  • plot_spec - generic description of the plotting requested (file names, titles, colors, etc)

The plot function runs inside of Blender's Python environment, and it is responsible for launching the external plotting program and communicating with it. This typically involves translating the generic plotting description (contained in "plot_spec") into command line arguments and other communications to the external plotting program. This translation need not include every possible generic option - especially if the external program doesn't support all generic options.

Let's take a look at the __init__.py file for XmGrace (cellblender/data_plotters/xmgrace/__init__.py):

import os
import subprocess

def find_in_path(program_name):
    for path in os.environ.get('PATH', '').split(os.pathsep):
        full_name = os.path.join(path, program_name)
        if os.path.exists(full_name) and not os.path.isdir(full_name):
            return full_name
    return None

def get_name():
    return("XmGrace Plotter")

def requirements_met():
    path = find_in_path("xmgrace")
    if path is None:
        print("Required program \"xmgrace\" was not found")
        return False
    else:
        return True

def plot(data_path, plot_spec):
    plot_cmd = find_in_path("xmgrace")

    for plot_param in plot_spec.split():
        if plot_param[0:2] == "f=":
            plot_cmd = plot_cmd + " " + plot_param[2:]

    pid = subprocess.Popen(plot_cmd.split(), cwd=data_path)

The program starts out with a simple helper function (find_in_path) that's used to find the program itself (xmgrace in this case) in the user's path. The next function (get_name) returns the name of this plotter which will be shown to the user. The requirements_met function ensures that the program xmgrace is found somewhere in the user's path.

The final function is the plot function itself, and it takes two parameters:

  • data_path - path to the location of the "react_data" folder which contains plotting data files
  • plot_spec - generic description of the plotting requested (file names, titles, colors, etc)

The data_path is the shared base path for all of the files that need to be plotted. Everything else is in the plot_spec string. Let's look at a simple example:

plot_spec = "f=seed_00001/a.cube.dat  f=seed_00002/a.cube.dat  f=seed_00003/a.cube.dat"

If we assume that the data_path was "/home/project/model" then this requests plotting of:

/home/project/model/seed_00001/a.cube.dat
/home/project/model/seed_00002/a.cube.dat
/home/project/model/seed_00003/a.cube.dat

In other words, the plot string will contain space-separated strings starting with "f=" followed by a relative path name for each file that needs to be plotted. As a bare minimum, the plotting program should show some rendering of the data contained in those 3 files. That's what's done in the following section of the XmGrace plotter:

def plot(data_path, plot_spec):
    plot_cmd = find_in_path("xmgrace")

    for plot_param in plot_spec.split():
        if plot_param[0:2] == "f=":
            plot_cmd = plot_cmd + " " + plot_param[2:]

    pid = subprocess.Popen(plot_cmd.split(), cwd=data_path)

The plot_cmd string starts with the path to xmgrace (something like "/usr/bin/xmgrace"). The plot function then splits the command string and looks for any substrings that start with "f=". Each such file name is simply appended to the plot_cmd string which will eventually become the command line sent to XmGrace via the subprocess.Popen call.

It turns out that the XmGrace plotter is fairly simple because XmGrace naturally accepts a list of file names as command line parameters. In other cases, the __init__.py program might need to launch the plotting program and then send it a series of commands to cause the plotting. Here's an slimmed down example of using the GnuPlot program for plotting:

p = subprocess.Popen ( [plot_cmd, "-persist"], cwd=data_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )

# Send the basic setup commands 
p.stdin.write ( "set terminal x11 size 1200,900\n".encode() )
p.stdin.write ( "set multiplot\n".encode() )

plot_w = 1.0 / num_cols
plot_h = 1.0 / num_rows

cmd = "set size " + str(plot_w) + "," + str(plot_h) + "\n"
p.stdin.write ( cmd.encode() )

row = 0
col = 0
for f in file_list:
  cmd = "set origin " + str(col*plot_w) + "," + str(1.0-((row+1)*plot_h)) + "\n"
  p.stdin.write ( cmd.encode() )

  cmd = "plot \"" + f + "\" with lines" + legend + "\n"

  p.stdin.write ( cmd.encode() )
  
  col += 1
  if col >= num_cols:
    col = 0
    row += 1

p.stdin.write ( "unset multiplot\n".encode() )
p.stdin.flush()

In this example, the __init__.py program runs GnuPlot with a pipe to stdin and then sends the individual plotting commands along that pipe. This allows __init__.py to do pretty much whatever could be done manually through the GnuPlot command line interface.

Detailed Generic Plotting Specification

The previous section described the minimal requirements for a CellBlender plotting plug-in. It must provide a name. It must provide a check of whether the current system meets its requirements. It must search a whitespace-delimited string for substrings starting with "f=" and then plot the subsequent filenames. But the Generic Plotting Specification string also contains additional information that can be used to greatly enhance the resulting plots. This section describes that additional information.

Pages and Plots

plot_spec = "f=a1.dat plot f=a2.dat page f=b1.dat plot f=b2.dat"

The Generic Plotting Specification provides for the insertion of "page" and "plot" keywords to suggest the desired arrangement of the resulting plots. It's up to each plotter to decide how to interpret these keywords. In the earlier XmGrace example, you'll notice that these keywords were completely ignored when generating the command line argument to start XmGrace. On the other hand, if you look at the mpl_plot.py file, you'll find that the plot specification is first split by the "page" tokens into sublists (one per page). Each of those pages is then further subdivided by the "plot" tokens to generate separate plots within each page. Note that the precise meaning of the words "page" and "plot" are subject to interpretation by each plotting program. Some programs might implement separate pages as separate tabs in a tabbed dialog box or separate sheets in a spreadsheet. Others might represent separate pages as completely separate windows within the same program or even as separately executed programs. Alternatively, the page commands might be completely ignored. This is all up to the discretion of the person implementing any particular plotting program. The same is true for the "plot" command. Multiple files that are not separated by any plot or page commands are generally assumed to share the same axes. The insertion of a "plot" command is intended to indicate a new set of axes for subsequent files. Again, this is just a guideline, and the actual interpretation is up to the implementation.

With that background, we might expect that this command:

plot_spec = "f=a1.dat plot f=a2.dat page f=b1.dat f=b2.dat"

will generate two "pages" with a1 and a2 drawn on separate axes in the first page, and with b1 and b2 sharing the same axes on the second page. Note that plotting programs should try to be tolerant of extra page and plot tokens at the start or end of the list.

Other Generic Plotting Commands

The "Generic Plotting Commands" have evolved mostly on an "as-needed" basis. This is likely to continue, so any list might be expected to change. In general, the commands are intended to apply to files that follow them. They typically remain in effect until changed.

The following list is from an actual plot specification generated by CellBlender. These would normally be in one long string (separated by white space), but they're shown one line at a time here to make them easier to read. Note that quotes are not currently used with string arguments which prohibits embedded spaces in names and labels.

  • xlabel=time(s)
  • ylabel=count
  • legend=0
  • page
  • color=#d500ff
  • title=b.World.dat
  • f=seed_00001/b.World.dat
  • color=#d500ff
  • title=b.World.dat
  • f=seed_00002/b.World.dat
  • color=#d500ff
  • title=b.World.dat
  • f=seed_00003/b.World.dat
  • page
  • color=#ffffff
  • title=spike.World.dat
  • f=seed_00001/spike.World.dat
  • color=#ffffff
  • title=spike.World.dat
  • f=seed_00002/spike.World.dat
  • color=#ffffff
  • title=spike.World.dat
  • f=seed_00003/spike.World.dat
  • page
  • color=#ff0000
  • title=a.cube.dat
  • f=seed_00001/a.cube.dat
  • color=#ff0000
  • title=a.cube.dat
  • f=seed_00002/a.cube.dat
  • color=#ff0000
  • title=a.cube.dat
  • f=seed_00003/a.cube.dat

The previous plot command would be expected to generate three separate "pages" with 3 files of differing seeds sharing the same axes on each page.

Conclusion

The plotting interface was designed to be extensible to make it relatively easy for CellBlender users to add their own plotting programs. These plotting programs could simply draw the data for display (as shown in these examples) or they could also perform any arbitrary analysis as well. It's easy to imagine an environment where many different post-processing steps have been programmed as separate "plotters" which may each be looking for different signatures in the data. It's also easy to imagine "plotters" that perform other functions like categorizing and archiving particular simulation results at the push of a button.