Beispiel #1
0
    def default_traits_view(self):
        no_endlabel = DefaultOverride(low_label='',
                                      high_label='',
                                      mode='logslider')
        no_endlabel_linear = DefaultOverride(low_label='',
                                             high_label='',
                                             mode='slider')

        return View(Group(Item('reconstruction', editor=ComponentEditor()),
                          show_labels=False,
                          show_left=False),
                    HGroup(Item('pulses_used', style='readonly'),
                           Item('absolute_sum'), Item('amplitudes_one'),
                           Item('replace'), Item('subtract')),
                    Item('amplitude_threshold_min',
                         editor=no_endlabel,
                         label='Minimum absolute amplitude'),
                    Item('amplitude_threshold_max', editor=no_endlabel),
                    Item('area_threshold_min', editor=no_endlabel),
                    Item('area_threshold_max', editor=no_endlabel),
                    Item('volume_threshold_min', editor=no_endlabel),
                    Item('volume_threshold_max', editor=no_endlabel),
                    Item('circularity_min'),
                    Item('circularity_max'),
                    Item('lifetime_min', editor=no_endlabel_linear),
                    Item('lifetime_max', editor=no_endlabel_linear),
                    Item('output_threshold', editor=no_endlabel_linear),
                    HGroup(Item('save_file', show_label=False),
                           Item('save_button', show_label=False)),
                    width=800,
                    height=600,
                    resizable=True,
                    title='DPT 2D reconstruction')
    def readonly_editor(self, ui, object, name, description, parent):
        return ("readonly_editor", self, ui, object, name, description, parent)


class NewInt(Int):
    def create_editor(self):
        return DummyEditor()


class Dummy(HasTraits):
    x = NewInt()


dummy_object = Dummy()
do = DefaultOverride(x=15, y=25, format_str="%r")


class TestDefaultOverride(unittest.TestCase):

    def test_simple_override(self):
        editor_name, editor, ui, obj, name, description, parent = \
            do.simple_editor("ui", dummy_object, "x", "description", "parent")
        self.assertEqual(editor_name, "simple_editor")
        self.assertEqual(editor.x, 15)
        self.assertEqual(editor.y, 25)
        self.assertEqual(obj, dummy_object)
        self.assertEqual(name, "x")
        self.assertEqual(description, "description")
        self.assertEqual(parent, "parent")
Beispiel #3
0
from traits.api import (HasTraits, Enum, Bool,
        Instance, Delegate, on_trait_change)

from traitsui.api import (View, Item, UItem,
        HGroup, VGroup, DefaultOverride)

from chaco.api import (Plot, ArrayPlotData, color_map_name_dict,
        GridPlotContainer, VPlotContainer, PlotLabel)
from chaco.tools.api import (ZoomTool, SaveTool, ImageInspectorTool,
        ImageInspectorOverlay, PanTool)

from enable.component_editor import ComponentEditor

from .process import Process

slider_editor = DefaultOverride(mode="slider")


class Bullseye(HasTraits):
    plots = Instance(GridPlotContainer)
    abplots = Instance(VPlotContainer)
    screen = Instance(Plot)
    horiz = Instance(Plot)
    vert = Instance(Plot)
    asum = Instance(Plot)
    bsum = Instance(Plot)

    process = Instance(Process)

    colormap = Enum("gray", "jet", "hot", "prism")
    invert = Bool(True)
    def readonly_editor(self, ui, object, name, description, parent):
        return ('readonly_editor', self, ui, object, name, description, parent)


class NewInt(Int):

    def create_editor(self):
        return DummyEditor()


class Dummy(HasTraits):
    x = NewInt()


dummy_object = Dummy()
do = DefaultOverride(x=15, y=25, format_str='%r')


def test_simple_override():
    editor_name, editor, ui, obj, name, description, parent = do.simple_editor(
        'ui', dummy_object, 'x', 'description', 'parent')
    assert_equals(editor_name, 'simple_editor')
    assert_equals(editor.x, 15)
    assert_equals(editor.y, 25)
    assert_equals(obj, dummy_object)
    assert_equals(name, 'x')
    assert_equals(description, 'description')
    assert_equals(parent, 'parent')


def test_text_override():
Beispiel #5
0
class MplPlot(BasePlot, HasTraitsGroup):
    figure = Instance(Figure, ())
    _draw_pending = Bool(False)

    scale = Enum('linear', 'log', 'sqrt')('linear')
    scale_values = [
        'linear', 'log', 'sqrt'
    ]  # There's probably a way to exract this from the Enum trait but I don't know how
    azimuth = Range(-90, 90, -70)
    elevation = Range(0, 90, 30)
    quality = Range(1, MAX_QUALITY, 1)
    flip_order = Bool(False)
    x_lower = Float(0.0)
    x_upper = Float
    x_label = Str('Angle (2$\Theta$)')
    y_label = Str('Dataset')
    z_lower = Float(0.0)
    z_upper = Float
    z_label = Str
    z_labels = {}  # A dictionary to hold edited labels for each scaling type

    group = VGroup(
        HGroup(
            VGroup(
                Item('azimuth',
                     editor=DefaultOverride(mode='slider',
                                            auto_set=False,
                                            enter_set=True)),
                Item('elevation',
                     editor=DefaultOverride(mode='slider',
                                            auto_set=False,
                                            enter_set=True)),
                Item('quality'),
                Item('flip_order'),
            ),
            VGroup(
                HGroup(
                    Item('x_label',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                    Item('x_lower',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                    Item('x_upper',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                ),
                HGroup(Item('y_label'), ),
                HGroup(
                    Item('z_label',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                    Item('z_lower',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                    Item('z_upper',
                         editor=DefaultOverride(auto_set=False,
                                                enter_set=True)),
                ),
            ),
        ),
        UItem('figure', editor=MPLFigureEditor()),
    )

    def __init__(self, callback_obj=None, *args, **kws):
        super(MplPlot, self).__init__(*args, **kws)
        self.figure = plt.figure()
        self.figure.subplots_adjust(bottom=0.05, left=0, top=1, right=0.95)
        self.ax = None
        for s in self.scale_values:
            self.z_labels[s] = 'Intensity - ' + get_value_scale_label(s,
                                                                      mpl=True)
        # This must be a weak reference, otherwise the entire app will
        # hang on exit.
        from weakref import proxy
        if callback_obj:
            self._callback_object = proxy(callback_obj)
        else:
            self._callback_object = lambda *args, **kw: None

    def close(self):
        del self._callback_object
        plt.close()

    def __del__(self):
        plt.close()

    @on_trait_change('azimuth, elevation')
    def _perspective_changed(self):
        if self.ax:
            self.ax.view_init(azim=self.azimuth, elev=self.elevation)
            self.redraw()

    def _quality_changed(self):
        self.redraw(replot=True)

    @on_trait_change(
        'x_label, y_label, x_lower, x_upper, z_lower, z_upper, flip_order')
    def _trigger_redraw(self):
        self.quality = 1
        self.redraw(replot=True)

    def _z_label_changed(self):
        self.z_labels[self.scale] = self.z_label
        self._trigger_redraw()

    def redraw(self, replot=False, now=False):
        if not now and self._draw_pending:
            self._redraw_timer.Restart()
            return
        #import wx
        canvas = self.figure.canvas
        if canvas is None:
            return

        def _draw():
            self._callback_object._on_redraw(drawing=True)
            if replot:
                self._plot(self.x, self.y, self.z, self.scale)
            else:
                canvas.draw()
            self._draw_pending = False
            self._callback_object._on_redraw(drawing=False)

        if now:
            _draw()
        else:
            _draw()
            #self._redraw_timer = wx.CallLater(250, _draw)
            #self._draw_pending = True
            #self._redraw_timer.Start()

#    def _prepare_data(self, datasets):

    def _prepare_data(self, stack):
        #        stack = stack_datasets(datasets)

        x = stack[:, :, 0]
        z = stack[:, :, 1]
        #        y = array([ [i]*z.shape[1] for i in range(1, len(datasets) + 1) ])
        y = array([[i] * z.shape[1] for i in range(1, stack.shape[1] + 1)])

        if x[0, 0] < x[0, -1]:
            self.x_lower = x[0, 0]
            self.x_upper = x[0, -1]
        else:
            self.x_lower = x[0, -1]
            self.x_upper = x[0, 0]
        self.z_upper = z.max()
        return x, y, z

    def _plot(self, x, y, z, scale='linear'):
        self.x, self.y, self.z = x, y, z
        x, y, z = x.copy(), y.copy(), z.copy()

        if self.flip_order:
            z = z[::-1]
        self.scale = scale
        self.figure.clear()
        self.figure.set_facecolor('white')
        ax = self.ax = self.figure.add_subplot(111, projection='3d')
        ax.set_xlabel(self.x_label)
        ax.set_ylabel(self.y_label)
        self.z_label = self.z_labels[self.scale]
        ax.set_zlabel(self.z_label)

        y_rows = z.shape[0]
        ax.locator_params(axis='y', nbins=10, integer=True)
        ax.view_init(azim=self.azimuth, elev=self.elevation)

        if self.quality != MAX_QUALITY:
            # map quality from 1->5 to 0.05->0.5 to approx. no. of samples
            samples = int(z.shape[1] * ((self.quality - 1) * (0.5 - 0.05) /
                                        (5 - 1) + 0.05))
            z, truncate_at, bins = rebin_preserving_peaks(z, samples / 2)
            # Take the x's from the original x's to maintain visual x-spacing
            # We need to calculate the x's for the rebinned data
            x0_row = x[0, :truncate_at]
            old_xs = np.linspace(x0_row.min(), x0_row.max(), bins * 2)
            new_xs = np.interp(
                old_xs, np.linspace(x0_row.min(), x0_row.max(), len(x0_row)),
                x0_row)
            x = np.tile(new_xs, (y.shape[0], 1))

        # Set values to inf to avoid rendering by matplotlib
        x[(x < self.x_lower) | (x > self.x_upper)] = np.inf
        z[(z < self.z_lower) | (z > self.z_upper)] = np.inf

        # separate series with open lines
        ys = y[:, 0]
        points = []
        for x_row, z_row in zip(x, z):
            points.append(zip(x_row, z_row))
        lines = LineCollection(points)
        ax.add_collection3d(lines, zs=ys, zdir='y')
        ax.set_xlim3d(self.x_lower, self.x_upper)
        ax.set_ylim3d(1, y_rows)
        ax.set_zlim3d(self.z_lower, self.z_upper)
        self.figure.canvas.draw()
        return None

    def copy_to_clipboard(self):
        self.figure.canvas.Copy_to_Clipboard()

    def save_as(self, filename):
        self.figure.canvas.print_figure(filename)
        logger.logger.info('Saved plot {}'.format(filename))

    def _reset_view(self):
        self.azimuth = -70
        self.elevation = 30
Beispiel #6
0
class MainApp(HasTraits):
    container = Instance(OverlayPlotContainer)

    file_paths = List(Str)
    # Button group above tabbed area
    open_files = Button("Open files...")
    edit_datasets = Button("Edit datasets...")
    generate_plot = Button("Generate plot...")
    help_button = Button("Help...")

    # View tab
    scale = Enum('linear', 'log', 'sqrt')
    options = List
    reset_button = Button("Reset view")
    copy_to_clipboard = Button("Copy to clipboard")
    save_as_image = Button("Save as image...")

    # Process tab
    merge_positions = Enum('all', 'p1+p2', 'p3+p4', 'p12+p34')('p1+p2')
    load_partners = Button
    splice = Bool(True)
    merge = Bool(False)
    merge_regrid = Bool(False)
    normalise = Bool(True)
    # See comment in class Global() for an explanation of the following traits
    g = Instance(Global, ())
    file_list = DelegatesTo('g')
    normalisation_source_filenames = Enum(values='file_list')

    def _g_default(self):
        return g

    correction = Float(0.0)
    align_positions = Bool(False)
    bt_start_peak_select = Button
    bt_end_peak_select = Button
    peak_selecting = Bool(False)

    what_to_plot = Enum('Plot new', 'Plot old and new')('Plot old and new')

    bt_process = Button("Apply")
    bt_undo_processing = Button("Undo")
    bt_save = Button("Save...")

    # Background removal tab
    bt_manually_define_background = Button("Define")
    polynomial_order = Range(1, 20)(7)
    bt_poly_fit = Button("Poly fit")
    bt_load_background = Button("Load...")

    # theta/d/Q tab
    filename_field = Str("d")
    bt_convertscale_abscissa = Button("Convert/scale abscissa...")

    raw_data_plot = Instance(RawDataPlot)

    #-------------------------------------------------------------------------------------
    # MVC View
    view_group = VGroup(
        Label('Scale:'),
        UItem('scale', enabled_when='object._has_data()'),
        UItem('options',
              editor=CheckListEditor(name='_options'),
              style='custom',
              enabled_when='object._has_data()'),
        UItem('reset_button', enabled_when='object._has_data()'),
        spring,
        '_',
        spring,
        UItem('copy_to_clipboard', enabled_when='object._has_data()'),
        UItem('save_as_image', enabled_when='object._has_data()'),
        label='View',
        springy=False,
    )

    process_group = VGroup(
        VGroup(
            Label('Positions to process:'),
            UItem(name='merge_positions',
                  style='custom',
                  editor=EnumEditor(values={
                      'p1+p2': '1: p1+p2',
                      'p3+p4': '2: p3+p4',
                      'p12+p34': '3: p12+p34',
                      'all': '4: all',
                  },
                                    cols=2),
                  enabled_when='object._has_data()'),
            UItem('load_partners',
                  enabled_when=
                  'object._has_data() and (object.merge_positions != "all")'),
            show_border=True,
        ),
        VGroup(
            HGroup(Item('align_positions'),
                   enabled_when=
                   'object._has_data() and (object.merge_positions != "all")'),
            HGroup(
                UItem(
                    'bt_start_peak_select',
                    label='Select peak',
                    enabled_when=
                    'object.align_positions and not object.peak_selecting and (object.merge_positions != "all")'
                ),
                UItem(
                    'bt_end_peak_select',
                    label='Align',
                    enabled_when=
                    'object.peak_selecting and (object.merge_positions != "all")'
                ),
            ),
            Item('correction',
                 label='Zero correction:',
                 enabled_when='object._has_data()'),
            show_border=True,
        ),
        VGroup(
            HGroup(Item('splice'),
                   Item('merge',
                        enabled_when='object.merge_positions != "p12+p34"'),
                   enabled_when=
                   'object._has_data() and (object.merge_positions != "all")'),
            HGroup(
                Item(
                    'normalise',
                    label='Normalise',
                    enabled_when=
                    'object._has_data() and (object.merge_positions != "p12+p34")'
                ),
                Item('merge_regrid',
                     label='Grid',
                     enabled_when='object._has_data()'),
            ),
            VGroup(
                Label('Normalise to:'),
                UItem('normalisation_source_filenames',
                      style='simple',
                      enabled_when='object.normalise and object._has_data()'),
            ),
            show_border=True,
        ),
        spring,
        UItem('what_to_plot',
              editor=DefaultOverride(cols=2),
              style='custom',
              enabled_when='object._has_data()'),
        spring,
        UItem('bt_process', enabled_when='object._has_data()'),
        UItem('bt_undo_processing',
              enabled_when='object.undo_state is not None'),
        UItem('bt_save', enabled_when='object._has_data()'),
        label='Process',
        springy=False,
    )

    background_removal_group = VGroup(
        VGroup(
            Label('Manually define:'),
            UItem('bt_manually_define_background',
                  enabled_when='object._has_data()'),
            show_border=True,
        ),
        VGroup(
            Label('Fit polynomial:'),
            HGroup(
                Item('polynomial_order',
                     label='order',
                     enabled_when='object._has_data()'), ),
            UItem('bt_poly_fit', enabled_when='object._has_data()'),
            show_border=True,
        ),
        VGroup(
            Label('Load from file:'),
            UItem('bt_load_background', enabled_when='object._has_data()'),
            show_border=True,
        ),
        label='Backgrnd',
        springy=False,
    )

    convert_xscale_group = VGroup(
        Label('Filename label (prefix_<label>_nnnn.xye):'),
        UItem('filename_field', enabled_when='object._has_data()'),
        UItem(
            'bt_convertscale_abscissa',
            label='Convert/scale abscissa...',
            enabled_when='object._has_data()',
        ),
        label=ur'\u0398 d Q',
        springy=True,
    )

    traits_view = View(
        HGroup(
            VGroup(
                UItem('open_files'),
                UItem('edit_datasets', enabled_when='object._has_data()'),
                UItem('generate_plot', enabled_when='object._has_data()'),
                UItem('help_button'),
                spring,
                spring,
                Tabbed(
                    view_group,
                    process_group,
                    # background_removal_group,
                    convert_xscale_group,
                    springy=False,
                ),
                show_border=False,
            ),
            UItem('container', editor=ComponentEditor(bgcolor='white')),
            show_border=False,
        ),
        resizable=True,
        title=title,
        width=size[0],
        height=size[1])

    #-------------------------------------------------------------------------------------
    # MVC Control

    def _has_data(self):
        return len(self.datasets) != 0

    def __init__(self, *args, **kws):
        """
        self.datasets = [ <XYEDataset>, ..., <XYEDataset> ]
        self.dataset_pairs = set([ (<XYEDataset-p1>, <XYEDataset-p2>),
                                   ...,
                                   (<XYEDataset-p1>, <XYEDataset-p2>) ])
        """
        super(MainApp, self).__init__(*args, **kws)
        self.datasets = []
        self.dataset_pairs = set()
        self.undo_state = None
        self.raw_data_plot = RawDataPlot()
        self.plot = self.raw_data_plot.get_plot()
        self.container = OverlayPlotContainer(self.plot,
                                              bgcolor="white",
                                              use_backbuffer=True,
                                              border_visible=False)
        self.pan_tool = None
        # The list of all options.
        self._options = ['Show legend', 'Show gridlines', 'Show crosslines']
        # The list of currently set options, updated by the UI.
        self.options = self._options
        self.file_paths = []

    def _open_files_changed(self):
        file_list = get_file_list_from_dialog()
        if file_list:
            self.file_paths = file_list

    def _options_changed(self, opts):
        # opts just contains the keys that are true.
        # Create a dict all_options that has True/False for each item.
        all_options = dict.fromkeys(self._options, False)
        true_options = dict.fromkeys(opts, True)
        all_options.update(true_options)
        self.raw_data_plot.show_legend(all_options['Show legend'])
        self.raw_data_plot.show_grids(all_options['Show gridlines'])
        self.raw_data_plot.show_crosslines(all_options['Show crosslines'])
        self.container.request_redraw()

    def _bt_start_peak_select_changed(self):
        self.raw_data_plot.start_range_select()
        self.peak_selecting = True

    def _bt_end_peak_select_changed(self):
        self.peak_selecting = False
        selection_range = self.raw_data_plot.end_range_select()
        if not selection_range:
            return

        range_low, range_high = selection_range
        # fit the peak in all loaded dataseries
        self._get_partners()
        for datapair in self._get_dataset_pairs():
            processing.fit_peaks_for_a_dataset_pair(range_low, range_high,
                                                    datapair, self.normalise)
        editor = PeakFitWindow(dataset_pairs=self._get_dataset_pairs(),
                               range=selection_range)
        editor.edit_traits()

    def _get_dataset_pairs(self):
        datasets_dict = dict([(d.name, d) for d in self.datasets])
        return [ (datasets_dict[file1], datasets_dict[file2]) \
                    for file1, file2 in self.dataset_pairs ]

    def _bt_process_changed(self):
        '''
        Button click event handler for processing. 
        '''
        # Save the unprocessed data series at this point for later undoing
        processed_datasets = []
        processor = DatasetProcessor(self.normalise, self.correction,
                                     self.align_positions, self.splice,
                                     self.merge, self.merge_regrid,
                                     self.normalisation_source_filenames,
                                     self.datasets)
        # Processing at this point depends on the "Positions to process:" radiobutton
        # selection:
        # If Splice==True, get all pairs and splice them
        # If Merge==True, get all pairs and merge them
        # If Normalise==True, always normalise
        # If Grid===True, output gridded and ungridded
        # The following processing code sould really be placed into a processor.process()
        # method, but I only worked out how to pass required stuff late in the day, so
        # I do this stuff here.
        if self.merge_positions == 'p12+p34':
            self._get_partners(
            )  # pair up datasets corresponding to the radiobutton selection
            for dataset_pair in self._get_dataset_pairs():
                datasets = processor.splice_overlapping_datasets(dataset_pair)
                for dataset in datasets:
                    dataset.metadata['ui'].name = dataset.name + ' (processed)'
                    dataset.metadata['ui'].color = None
                processed_datasets.extend(datasets)
        elif self.merge_positions == 'all':
            # Handle "all" selection for regrid and normalise
            for d in self.datasets:
                dataset = processor.normalise_me(d)
                if dataset is not None:
                    processed_datasets.extend([dataset])
                    dataset.metadata['ui'].name = dataset.name + ' (processed)'
                    dataset.metadata['ui'].color = None
                    d = dataset

                dataset = processor.regrid_me(d)
                if dataset is not None:
                    processed_datasets.extend([dataset])
                    dataset.metadata['ui'].name = dataset.name + ' (processed)'
                    dataset.metadata['ui'].color = None
        else:
            self._get_partners(
            )  # pair up datasets corresponding to the radiobutton selection
            for dataset_pair in self._get_dataset_pairs():
                datasets = processor.process_dataset_pair(dataset_pair)
                for dataset in datasets:
                    dataset.metadata['ui'].name = dataset.name + ' (processed)'
                    dataset.metadata['ui'].color = None
                processed_datasets.extend(datasets)

        self.processed_datasets = processed_datasets
        self._plot_processed_datasets()

    def _plot_processed_datasets(self):
        self._save_state()
        self.dataset_pairs = set(
        )  # TODO: Check whether this line should be removed
        if 'old' not in self.what_to_plot:
            self.datasets = []
        if 'new' in self.what_to_plot:
            self.datasets.extend(self.processed_datasets)
        self._plot_datasets()

    def _save_state(self):
        self.undo_state = (self.datasets[:], self.dataset_pairs.copy())

    def _restore_state(self):
        if self.undo_state is not None:
            self.datasets, self.dataset_pairs = self.undo_state
            self.undo_state = None

    def _bt_undo_processing_changed(self):
        self._restore_state()
        self._plot_datasets()

    def _bt_save_changed(self):
        wildcard = 'All files (*.*)|*.*'
        default_filename = 'prefix_'
        dlg = FileDialog(title='Save results',
                         action='save as',
                         default_filename=default_filename,
                         wildcard=wildcard)
        if dlg.open() == OK:
            for dataset in self.processed_datasets:
                filename = os.path.join(dlg.directory,
                                        dlg.filename + dataset.name)
                dataset.save(filename)
            open_file_dir_with_default_handler(dlg.path)

    def _save_as_image_changed(self):
        if len(self.datasets) == 0:
            return
        filename = get_save_as_filename()
        if filename:
            PlotOutput.save_as_image(self.container, filename)
            open_file_dir_with_default_handler(filename)

    def _copy_to_clipboard_changed(self):
        if self.datasets:
            PlotOutput.copy_to_clipboard(self.container)

    def _scale_changed(self):
        self._plot_datasets()

    def _get_partner(self, position_index):
        # return index of partner; i.e., 2=>1, 1=>2, 3=>4, 4=>3, 12=>34, 34=>12
        if position_index in [1, 2, 3, 4]:
            partner = ((position_index - 1) ^ 1) + 1
        elif position_index == 12:
            partner = 34
        elif position_index == 34:
            partner = 12
        else:
            raise 'unparsable position'
        return partner

    def _get_position(self, filename):
        m = re.search('_p([0-9]*)_', filename)
        try:
            return int(m.group(1))
        except (AttributeError, ValueError):
            return None

    def _add_dataset_pair(self, filename):
        current_directory, filebase = os.path.split(filename)
        position_index = self._get_position(filename)
        if position_index is None:
            return

        # base filename for the associated position.
        other_filebase = re.sub(
            '_p{}_'.format(position_index),
            '_p{}_'.format(self._get_partner(position_index)), filebase)
        other_filename = os.path.join(current_directory, other_filebase)
        if not os.path.exists(other_filename):
            return

        # OK, we've got the names and paths, now add the actual data and references.
        if other_filename not in self.file_paths:
            self._add_xye_dataset(other_filename)
            # We also need to append the new path to the file_paths List trait which is
            # already populated by the files selected using the file selection dialog
            self.file_paths.append(other_filename)
        self._refresh_normalise_to_list()

    def _get_partners(self):
        """
        Populates the self.dataset_pairs list with all dataset partners in
        self.file_paths corresponding to the merge_positions radiobutton selection.
        """
        matching_re = {
            'all': '',
            'p1+p2': '_p[12]_',
            'p3+p4': '_p[34]_',
            'p12+p34': '_p(?:12|34)_',
        }
        basenames = [os.path.basename(f) for f in self.file_paths]
        filtered_paths = [
            f for f in basenames
            if re.search(matching_re[self.merge_positions], f) is not None
        ]
        self.dataset_pairs = set()
        for filebase in filtered_paths:
            # base filename for the first position.
            position_index = self._get_position(filebase)
            if position_index is None:
                return
            other_filebase = re.sub(
                '_p{}_'.format(position_index),
                '_p{}_'.format(self._get_partner(position_index)), filebase)
            if filebase in basenames and other_filebase in basenames:
                if position_index != 12 and (position_index & 1) == 0:
                    self.dataset_pairs.add((other_filebase, filebase))
                else:
                    self.dataset_pairs.add((filebase, other_filebase))
        return self.dataset_pairs

    def _refresh_normalise_to_list(self):
        g.populate_list(self.file_paths)

    def _file_paths_changed(self, new):
        """
        When the file dialog box is closed with a selection of filenames,
        just generate a list of all the filenames
        """
        self.datasets = []
        # self.file_paths is modified by _add_dataset_pair() so iterate over a copy of it.
        for filename in self.file_paths[:]:
            self._add_xye_dataset(filename)
        self._plot_datasets()
        self.datasets.sort(key=lambda d: d.name)
        self._refresh_normalise_to_list()

    def _load_partners_changed(self):
        for filename in self.file_paths[:]:
            self._add_dataset_pair(filename)
        self._plot_datasets()
        self.datasets.sort(key=lambda d: d.name)

    def _plot_datasets(self, reset_view=True):
        self.raw_data_plot.plot_datasets(self.datasets,
                                         scale=self.scale,
                                         reset_view=reset_view)
        self._options_changed(self.options)
        self.container.request_redraw()

    def _edit_datasets_changed(self):
        editor = DatasetEditor(datasets=self.datasets)
        editor.edit_traits()
        self._plot_datasets(reset_view=False)

    def _generate_plot_changed(self):
        if self.datasets:
            generator = PlotGenerator(datasets=self.datasets)
            generator.show()

    def _help_button_changed(self):
        help_box = HelpBox()
        help_box.edit_traits()

    def _reset_button_changed(self):
        self.raw_data_plot.reset_view()

    def _add_xye_dataset(self, file_path):
        try:
            dataset = XYEDataset.from_file(file_path)
        except IOError:
            return
        self.datasets.append(dataset)
        create_datasetui(dataset)

    def _bt_convertscale_abscissa_changed(self):
        editor = WavelengthEditor(datasets=self.datasets,
                                  filename_field=self.filename_field)
        editor.edit_traits()
        self._plot_datasets(reset_view=False)