Source code for superplot.super_gui

"""
This module provides the superplot GUI.
"""

# Standard modules
import os
import re
import pickle
import time
import warnings
from collections import OrderedDict

import matplotlib
from distutils.version import StrictVersion

# Runtime check that correct matplotlib version is installed.
# This is a common issue and might not be caught by setup.py
# (i.e. if the user is running from source)
version = StrictVersion(matplotlib.__version__)
required_version = StrictVersion("1.4")
if version < required_version:
    raise ImportError("Superplot requires matplotlib %s. "
                       "You are running matplotlib %s. "
                       "Upgrade via e.g. pip install --force-reinstall --upgrade matplotlib"
                       % (required_version, version))

# External modules
import gtk
import pygtk

try:
    from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
except ImportError as e:
    print "Could not load matplotlib - GTK backend. " \
          "Your version of matplotlib may not be compiled with GTK support. " \
          "Reinstalling matplotlib may fix this problem - see README or " \
          "user manual for instructions"
    raise

# Superplot modules
import data_loader
import superplot.plotlib.plots as plots
from plot_options import plot_options, default

pygtk.require('2.0')


[docs]def open_file_gui(window_title="Open", set_name=None, add_pattern=None, allow_no_file=True): """ GUI for opening a file with a file browser. :param window_title: Window title :type window_title: string :param set_name: Title of filter :type set_name: string :param add_pattern: Acceptable file patterns in filter, e.g ["\\*.pdf"] :type add_pattern: list :param allow_no_file: Allow for no file to be selected :type allow_no_file: bool :returns: Name of file selected with GUI. :rtype: string """ # Make a string for option of not selecting a file if set_name: no_file = "No %s" % set_name else: no_file = "No file" # Make buttons, allowing for cae in which no cancel button is desired if allow_no_file: buttons = (no_file, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) else: buttons = (gtk.STOCK_OPEN, gtk.RESPONSE_OK) # Select the file from a dialog box dialog = gtk.FileChooserDialog(title=window_title, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=buttons) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_current_folder(os.getcwd()) # Only show particular files file_filter = gtk.FileFilter() if set_name: file_filter.set_name(set_name) if add_pattern: for pattern in add_pattern: file_filter.add_pattern(pattern) dialog.add_filter(file_filter) response = dialog.run() if response == gtk.RESPONSE_OK: # Save the file name/path file_name = dialog.get_filename() elif response == gtk.RESPONSE_CANCEL: warnings.warn("No file selected") file_name = None else: warnings.warn("Unexpected response") file_name = None exit() dialog.destroy() print 'File: %s selected' % file_name return file_name
[docs]def save_file_gui(window_title="Save As", set_name=None, add_pattern=None): """ GUI for saving a file with a file browser. :param window_title: Window title :type window_title: string :param set_name: Title of filter :type set_name: string :param add_pattern: Acceptable file patterns in filter, e.g ["\\*.pdf"] :type add_pattern: list :returns: Name of file selected with GUI. :rtype: string """ # Select the file from a dialog box buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) dialog = gtk.FileChooserDialog(title=window_title, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=buttons) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_current_folder(os.getcwd()) # Only show particular files file_filter = gtk.FileFilter() if set_name: file_filter.set_name(set_name) if add_pattern: for pattern in add_pattern: file_filter.add_pattern(pattern) dialog.add_filter(file_filter) response = dialog.run() if response == gtk.RESPONSE_OK: # Save the file name/path file_name = dialog.get_filename() elif response == gtk.RESPONSE_CANCEL: warnings.warn("No file selected") file_name = None else: warnings.warn("Unexpected response") file_name = None print 'File: %s selected' % file_name dialog.destroy() return file_name
[docs]def message_dialog(message_type, message): """ Show a message dialog. :param message_type: Type of dialogue - e.g gtk.MESSAGE_WARNING or \ gtk.MESSAGE_ERROR :type message_type: gtk.MessageType :param message: Text to show in dialogue :type message: string """ md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, message_type, gtk.BUTTONS_CLOSE, message) md.run() md.destroy()
[docs]class GUIControl(object): """ Main GUI element for superplot. Presents controls for selecting plot options, creating a plot, and saving a plot. :param data_file: Path to chain file :type data_file: string :param info_file: Path to info file :type data: string :param xindex: Default x-data index :type xindex: integer :param yindex: Default y-data index :type yindex: integer :param zindex: Default z-data index :type zindex: integer :param default_plot_type: Default plot type index :type default_plot_type: integer """ def __init__(self, data_file, info_file, default_plot_type=0): self.data_file = data_file self.info_file = info_file self.plot_limits = default("plot_limits") self.bin_limits = default("bin_limits") self.fig = None self.plot = None self.options = None # Load data from files self.labels, self.data = data_loader.load(info_file, data_file) # We need at least three columns - posterior, chisq & a data column data_columns = self.data.shape[0] assert data_columns >= 3 # Set x, y & z indices to first three data columns after the # posterior and chisq. If there are less than three such columns # assign to the rightmost available column. self.xindex = 2 self.yindex = min(3, data_columns - 1) self.zindex = min(4, data_columns - 1) # Enumerate available plot types and keep an ordered # dict mapping descriptions to classes. # Using an ordered dict means the order in which classes # are listed in plot_types will be preserved in the GUI. self.plots = OrderedDict() for plot_class in plots.plot_types: self.plots[plot_class.description] = plot_class ####################################################################### # Combo-box for various plot types typetitle = gtk.Button("Plot type:") self.typebox = gtk.combo_box_new_text() for description in self.plots.keys(): self.typebox.append_text(description) self.typebox.set_active(default_plot_type) # Set to default plot type ####################################################################### # Combo box for selecting x-axis variable xtitle = gtk.Button("x-axis variable:") self.xbox = gtk.combo_box_new_text() for label in self.labels.itervalues(): self.xbox.append_text(label) self.xbox.set_wrap_width(5) self.xbox.connect('changed', self._cx) self.xtext = gtk.Entry() self.xtext.set_text(self.labels[self.xindex]) self.xtext.connect("changed", self._cxtext) self.xbox.set_active(self.xindex) ####################################################################### # Combo box for selecting y-axis variable ytitle = gtk.Button("y-axis variable:") self.ybox = gtk.combo_box_new_text() for label in self.labels.itervalues(): self.ybox.append_text(label) self.ybox.set_wrap_width(5) self.ybox.connect('changed', self._cy) self.ytext = gtk.Entry() self.ytext.set_text(self.labels[self.yindex]) self.ytext.connect("changed", self._cytext) self.ybox.set_active(self.yindex) ####################################################################### # Combo box for selecting z-axis variable ztitle = gtk.Button("z-axis variable:") self.zbox = gtk.combo_box_new_text() for label in self.labels.itervalues(): self.zbox.append_text(label) self.zbox.set_wrap_width(5) self.zbox.connect('changed', self._cz) self.ztext = gtk.Entry() self.ztext.set_text(self.labels[self.zindex]) self.ztext.connect("changed", self._cztext) self.zbox.set_active(self.zindex) ####################################################################### # Check buttons for log Scaling self.logx = gtk.CheckButton('Log x-data.') self.logy = gtk.CheckButton('Log y-data.') self.logz = gtk.CheckButton('Log z-data.') ####################################################################### # Text boxt for plot title tplottitle = gtk.Button("Plot title:") self.plottitle = gtk.Entry() self.plottitle.set_text(default("plot_title")) ####################################################################### # Legend properties # Text box for legend title tlegtitle = gtk.Button("Legend title:") self.legtitle = gtk.Entry() self.legtitle.set_text("") # Combo box for legend position tlegpos = gtk.Button("Legend position:") self.legpos = gtk.combo_box_new_text() for loc in ["best", "right", "upper right", "center right", "lower right", "upper left", "center left", "lower left", "upper center", "center", "lower center", "no legend"]: self.legpos.append_text(loc) self.legpos.set_active(0) # Default is first in above list - "best" ####################################################################### # Spin button for number of bins per dimension tbins = gtk.Button("Bins per dimension:") self.bins = gtk.SpinButton() self.bins.set_increments(10, 10) self.bins.set_range(5, 10000) self.bins.set_value(default("nbins")) ####################################################################### # Axes limits alimits = gtk.Button("Comma separated plot limits\n" "x_min, x_max, y_min, y_max:") self.alimits = gtk.Entry() self.alimits.connect("changed", self._calimits) self.alimits.append_text("") ####################################################################### # Bin limits blimits = gtk.Button("Comma separated bin limits\n" "x_min, x_max, y_min, y_max:") self.blimits = gtk.Entry() self.blimits.connect("changed", self._cblimits) self.blimits.append_text("") ####################################################################### # Check buttons for optional plot elements self.show_best_fit = gtk.CheckButton("Best-fit") self.show_posterior_mean = gtk.CheckButton("Posterior mean") self.show_posterior_median = gtk.CheckButton("Posterior median") self.show_posterior_mode = gtk.CheckButton("Posterior mode") self.show_credible_regions = gtk.CheckButton("Credible regions") self.show_conf_intervals = gtk.CheckButton("Confidence intervals") self.show_posterior_pdf = gtk.CheckButton("Posterior PDF") self.show_prof_like = gtk.CheckButton("Profile Likelihood") self.kde_pdf = gtk.CheckButton("KDE smoothing") self.show_best_fit.set_active(True) self.show_posterior_mean.set_active(True) self.show_posterior_median.set_active(True) self.show_posterior_mode.set_active(True) self.show_credible_regions.set_active(True) self.show_conf_intervals.set_active(True) self.show_posterior_pdf.set_active(True) self.show_prof_like.set_active(True) self.kde_pdf.set_active(False) ####################################################################### # Make plot button makeplot = gtk.Button('Make plot.') makeplot.connect("clicked", self._pmakeplot) ####################################################################### # Check boxes to control what is saved (note we only attach them to the # window after showing a plot) self.save_image = gtk.CheckButton('Save image') self.save_image.set_active(True) self.save_summary = gtk.CheckButton('Save statistics in plot') self.save_summary.set_active(True) self.save_pickle = gtk.CheckButton('Save pickle of plot') self.save_pickle.set_active(True) ####################################################################### # Layout - GTK Table self.gridbox = gtk.Table(17, 5, False) self.gridbox.attach(typetitle, 0, 1, 0, 1, xoptions=gtk.FILL) self.gridbox.attach(self.typebox, 1, 2, 0, 1, xoptions=gtk.FILL) self.gridbox.attach(xtitle, 0, 1, 1, 2, xoptions=gtk.FILL) self.gridbox.attach(self.xbox, 1, 2, 1, 2, xoptions=gtk.FILL) self.gridbox.attach(self.xtext, 1, 2, 2, 3, xoptions=gtk.FILL) self.gridbox.attach(ytitle, 0, 1, 3, 4, xoptions=gtk.FILL) self.gridbox.attach(self.ybox, 1, 2, 3, 4, xoptions=gtk.FILL) self.gridbox.attach(self.ytext, 1, 2, 4, 5, xoptions=gtk.FILL) self.gridbox.attach(ztitle, 0, 1, 5, 6, xoptions=gtk.FILL) self.gridbox.attach(self.zbox, 1, 2, 5, 6, xoptions=gtk.FILL) self.gridbox.attach(self.ztext, 1, 2, 6, 7, xoptions=gtk.FILL) self.gridbox.attach(self.logx, 0, 1, 2, 3, xoptions=gtk.FILL) self.gridbox.attach(self.logy, 0, 1, 4, 5, xoptions=gtk.FILL) self.gridbox.attach(self.logz, 0, 1, 6, 7, xoptions=gtk.FILL) self.gridbox.attach(tplottitle, 0, 1, 9, 10, xoptions=gtk.FILL) self.gridbox.attach(self.plottitle, 1, 2, 9, 10, xoptions=gtk.FILL) self.gridbox.attach(tlegtitle, 0, 1, 10, 11, xoptions=gtk.FILL) self.gridbox.attach(self.legtitle, 1, 2, 10, 11, xoptions=gtk.FILL) self.gridbox.attach(tlegpos, 0, 1, 11, 12, xoptions=gtk.FILL) self.gridbox.attach(self.legpos, 1, 2, 11, 12, xoptions=gtk.FILL) self.gridbox.attach(tbins, 0, 1, 12, 13, xoptions=gtk.FILL) self.gridbox.attach(self.bins, 1, 2, 12, 13, xoptions=gtk.FILL) self.gridbox.attach(alimits, 0, 1, 13, 14, xoptions=gtk.FILL) self.gridbox.attach(self.alimits, 1, 2, 13, 14, xoptions=gtk.FILL) self.gridbox.attach(blimits, 0, 1, 14, 15, xoptions=gtk.FILL) self.gridbox.attach(self.blimits, 1, 2, 14, 15, xoptions=gtk.FILL) self.gridbox.attach(makeplot, 0, 2, 16, 17, xoptions=gtk.FILL) ####################################################################### # Sub table to hold check boxes for toggling optional plot elements point_plot_container = gtk.Table(3, 3, True) point_plot_container.attach(self.show_conf_intervals, 0, 1, 0, 1) point_plot_container.attach(self.show_credible_regions, 0, 1, 1, 2) point_plot_container.attach(self.show_best_fit, 0, 1, 2, 3) point_plot_container.attach(self.show_posterior_mean, 1, 2, 0, 1) point_plot_container.attach(self.show_posterior_median, 1, 2, 1, 2) point_plot_container.attach(self.show_posterior_mode, 1, 2, 2, 3) point_plot_container.attach(self.show_posterior_pdf, 2, 3, 0, 1) point_plot_container.attach(self.show_prof_like, 2, 3, 1, 2) point_plot_container.attach(self.kde_pdf, 2, 3, 2, 3) self.gridbox.attach(point_plot_container, 0, 2, 15, 16, xoptions=gtk.FILL) ####################################################################### # Make main GUI window self.window = gtk.Window() self.window.maximize() self.window.set_title("SuperPlot") # Quit if cross is pressed self.window.connect('destroy', lambda w: gtk.main_quit()) # Add the table to the window and show self.window.add(self.gridbox) self.gridbox.show() self.window.show_all() return @staticmethod def _align_center(child): """ Utility method to wrap a GUI element in a centered gtk.Alignment """ alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0.0) alignment.add(child) return alignment ########################################################################### # Call-back functions. These functions are executed when buttons # are pressed/options selected. The get_active returns the index # rather than the label of the option selected. We find the data key # corresponding to that index. def _cx(self, combobox): """ Callback function for setting parameter x from combo-box and updating the text-box. :param combobox: Box with this callback function :type combobox: """ self.xindex = combobox.get_active() self.xtext.set_text(self.labels[self.xindex]) def _cy(self, combobox): """ Callback function for setting parameter y from combo-box and updating the text-box. :param combobox: Box with this callback function :type combobox: """ self.yindex = combobox.get_active() self.ytext.set_text(self.labels[self.yindex]) def _cz(self, combobox): """ Callback function for setting parameter z from combo-box and updating the text-box. :param combobox: Box with this callback function :type combobox: """ self.zindex = combobox.get_active() self.ztext.set_text(self.labels[self.zindex]) def _cxtext(self, textbox): """ Callback function for changing x label. :param textbox: Box with this callback function :type textbox: """ self.labels[self.xindex] = textbox.get_text() def _cytext(self, textbox): """ Callback function for changing y label. :param textbox: Box with this callback function :type textbox: """ self.labels[self.yindex] = textbox.get_text() def _cztext(self, textbox): """ Callback function for changing z label. :param textbox: Box with this callback function :type textbox: """ self.labels[self.zindex] = textbox.get_text() def _calimits(self, textbox): """ Callback function for setting axes limits. :param textbox: Box with this callback function :type textbox: """ # If no limits, return default if not textbox.get_text().strip(): self.plot_limits = default("plot_limits") return # Split text by commas etc self.plot_limits = re.split(r"\s*[,;]\s*", textbox.get_text()) # Convert to floats self.plot_limits = [float(i) for i in self.plot_limits if i] # Permit only two floats, if it is a one-dimensional plot if len(self.plot_limits) == 2 and "One-dimensional" in self.typebox.get_active_text(): self.plot_limits += [None, None] elif len(self.plot_limits) != 4: raise RuntimeError("Must specify four floats for axes limits") def _cblimits(self, textbox): """ Callback function for setting bin limits. :param textbox: Box with this callback function :type textbox: """ # If no limits, return default if not textbox.get_text().strip(): self.bin_limits = default("bin_limits") return # Split text by commas etc self.bin_limits = re.split(r"\s*[,;]\s*", textbox.get_text()) # Convert to floats self.bin_limits = [float(i) for i in self.bin_limits if i] # Permit only two floats, if it is a one-dimensional plot one_dim_plot = "One-dimensional" in self.typebox.get_active_text() if len(self.bin_limits) == 2 and not one_dim_plot: raise RuntimeError("Specify four floats for bin limits in 2D plot") elif len(self.bin_limits) != 2 and one_dim_plot: raise RuntimeError("Specify two floats for bin limits in 1D plot") elif len(self.bin_limits) == 4 and not one_dim_plot: # Convert to two-tuple format try: self.bin_limits = [[self.bin_limits[0], self.bin_limits[1]], [ self.bin_limits[2], self.bin_limits[3]]] except: raise IndexError("Specify four floats for bin limits in 2D plot") else: raise RuntimeError("Specify four floats for bin limits in 2D plot") def _pmakeplot(self, button): """ Callback function for pressing make plot. Main action is that it calls a ploting function that returns a figure object that is attached to our window. :param button: Button with this callback function :type button: """ # Gather up all of the plot options and put them in # a plot_options tuple args = {"xindex": self.xindex, "yindex": self.yindex, "zindex": self.zindex, "logx": self.logx.get_active(), "logy": self.logy.get_active(), "logz": self.logz.get_active(), "plot_limits": self.plot_limits, "bin_limits": self.bin_limits, "cb_limits": default("cb_limits"), "nbins": self.bins.get_value_as_int(), "xticks": default("xticks"), "yticks": default("yticks"), "cbticks": default("cbticks"), "tau": default("tau"), "alpha": default("alpha"), "xlabel": self.labels[self.xindex], "ylabel": self.labels[self.yindex], "zlabel": self.labels[self.zindex], "plot_title": self.plottitle.get_text(), "title_position": default("title_position"), "leg_title": self.legtitle.get_text(), "leg_position": self.legpos.get_active_text(), "show_best_fit": self.show_best_fit.get_active(), "show_posterior_mean": self.show_posterior_mean.get_active(), "show_posterior_median": self.show_posterior_median.get_active(), "show_posterior_mode": self.show_posterior_mode.get_active(), "show_conf_intervals": self.show_conf_intervals.get_active(), "show_credible_regions": self.show_credible_regions.get_active(), "show_posterior_pdf": self.show_posterior_pdf.get_active(), "show_prof_like": self.show_prof_like.get_active(), "kde_pdf": self.kde_pdf.get_active(), "bw_method": default("bw_method") } self.options = plot_options(**args) # Fetch the class for the selected plot type plot_class = self.plots[self.typebox.get_active_text()] # Instantiate the plot and get the figure self.fig = plot_class(self.data, self.options).figure() # Also store a handle to the plot class instance. # This is used for pickling - which needs to # re-create the figure to work correctly. self.plot = plot_class(self.data, self.options) # Put figure in plot box canvas = FigureCanvas(self.fig.figure) # A gtk.DrawingArea self.gridbox.attach(canvas, 2, 5, 0, 15) # Button to save the plot save_button = gtk.Button('Save plot.') save_button.connect("clicked", self._psave) self.gridbox.attach(save_button, 2, 5, 16, 17) # Attach the check boxes to specify what is saved self.gridbox.attach(self._align_center(self.save_image), 2, 3, 15, 16) self.gridbox.attach(self._align_center(self.save_summary), 3, 4, 15, 16) self.gridbox.attach(self._align_center(self.save_pickle), 4, 5, 15, 16) # Show new buttons etc self.window.show_all() def _psave(self, button): """ Callback function to save a plot via a dialogue box. NB differs from toolbox save, because it's figure object, rather than image in the canvas box. :param button: Button with this callback function :type button: """ save_image = self.save_image.get_active() save_summary = self.save_summary.get_active() save_pickle = self.save_pickle.get_active() if not (save_image or save_summary or save_pickle): message_dialog(gtk.MESSAGE_WARNING, "Nothing to save!") return # Get name to save to from a dialogue box. file_name = save_file_gui(set_name="Save plot as image", add_pattern=["*.pdf", "*.png", "*.eps", "*.ps"] ) if not isinstance(file_name, str): # Case in which no file is chosen return if save_image: # Re-draw figure so that size specified in style sheet is applied self.plot.figure() plots.save_plot(file_name) if save_pickle: # Need to re-draw the figure for this to work figure = self.plot.figure().figure file_prefix = os.path.splitext(file_name)[0] pickle.dump(figure, file(file_prefix + ".pkl", 'wb')) if save_summary: file_prefix = os.path.splitext(file_name)[0] with open(file_prefix + ".txt", 'w') as summary_file: summary_file.write("\n".join(self._summary())) summary_file.write("\n" + "\n".join(self.fig.summary)) def _summary(self): """ Create a generic summary (list of strings). Plot specific information can be appended to this before saving to file. :returns: List of summary strings :rtype: list """ return [ "Date: {}".format(time.strftime("%c")), "Chain file: {}".format(self.data_file), "Info file: {}".format(self.info_file), "Number of bins: {}".format(self.options.nbins), "Bin limits: {}".format(self.options.bin_limits), "Alpha: {}".format(self.options.alpha), ]
[docs]def main(): """ SuperPlot program - open relevant files and make GUI. """ data_file = open_file_gui(window_title="Select a MultiNest *.txt file", set_name="MultiNest *.txt file", add_pattern=["*.txt"], allow_no_file=False ) info_file = open_file_gui(window_title="Select an information file", set_name="Information file *.info describing " "*.txt file", add_pattern=["*.info"], allow_no_file=True ) GUIControl(data_file, info_file) gtk.main() return
if __name__ == "__main__": main()