예제 #1
0
 def set_node_colors(self):
     if not self.graph: return
     self.lastColorColumn = self.colorCombo.currentText()
     attribute = self.colorCombo.itemData(self.colorCombo.currentIndex())
     assert not attribute or isinstance(attribute, Orange.data.Variable)
     if not attribute:
         for node in self.view.nodes:
             node.setColor(None)
         return
     table = self.graph.items()
     if not table: return
     if attribute in table.domain.class_vars:
         values = table[:, attribute].Y
         if values.ndim > 1:
             values = values.T
     elif attribute in table.domain.metas:
         values = table[:, attribute].metas[:, 0]
     elif attribute in table.domain.attributes:
         values = table[:, attribute].X[:, 0]
     else:
         raise RuntimeError("Shouldn't be able to select this column")
     if attribute.is_continuous:
         colors = CONTINUOUS_PALETTE[scale(values)]
     elif attribute.is_discrete:
         DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values))
         colors = DISCRETE_PALETTE[values]
     for node, color in zip(self.view.nodes, colors):
         node.setColor(color)
예제 #2
0
 def set_node_colors(self):
     if not self.graph: return
     self.lastColorColumn = self.colorCombo.currentText()
     attribute = self.colorCombo.itemData(self.colorCombo.currentIndex())
     assert not attribute or isinstance(attribute, Orange.data.Variable)
     if not attribute:
         for node in self.view.nodes:
             node.setColor(None)
         return
     table = self.graph.items()
     if not table: return
     if attribute in table.domain.class_vars:
         values = table[:, attribute].Y
         if values.ndim > 1:
             values = values.T
     elif attribute in table.domain.metas:
         values = table[:, attribute].metas[:, 0]
     elif attribute in table.domain.attributes:
         values = table[:, attribute].X[:, 0]
     else: raise RuntimeError("Shouldn't be able to select this column")
     if attribute.is_continuous:
         colors = CONTINUOUS_PALETTE[scale(values)]
     elif attribute.is_discrete:
         DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values))
         colors = DISCRETE_PALETTE[values]
     for node, color in zip(self.view.nodes, colors):
         node.setColor(color)
예제 #3
0
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.clear_messages()
            if data is not None:
                if not data.domain.attributes:
                    self.Warning.no_input_variables()
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.Information.use_first_two()
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.Warning.continuous_target()
            self.input_classes = ["C1"]
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
예제 #4
0
    def set_node_colors(self, attribute=None, replot=True):
        assert not attribute or isinstance(attribute, data.Variable)

        if not attribute:
            self.kwargs.pop('brush', None)
            if replot:
                self.replot()
            return

        table = self.graph.items()
        if not table:
            return
        if table.domain.class_var == attribute:
            values = table[:, attribute].Y
        else:
            values = table[:, attribute].X[:, 0]
        if attribute.is_continuous:
            colors = CONTINUOUS_PALETTE[scale(values)]
        elif attribute.is_discrete:
            DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values))
            colors = DISCRETE_PALETTE[values]
        brushes = [QBrush(qcolor) for qcolor in colors]
        self.kwargs['brush'] = brushes
        if replot:
            self.replot()
예제 #5
0
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.clear_messages()
            if data is not None:
                if not data.domain.attributes:
                    self.Warning.no_input_variables()
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.Information.use_first_two()
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.Warning.continuous_target()
            self.input_classes = ["C1"]
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
예제 #6
0
    def update_edges(self):
        if not self.scatterplot_item \
                or self.simplify & self.Simplifications.NoEdges:
            return
        x, y = self.scatterplot_item.getData()
        edges = self.master.get_edges()
        srcs, dests, weights = edges.row, edges.col, edges.data
        if self.edge_curve is None:
            self.pair_indices = np.empty((2 * len(srcs),), dtype=int)
            self.pair_indices[::2] = srcs
            self.pair_indices[1::2] = dests

        data = dict(x=x[self.pair_indices], y=y[self.pair_indices],
                    pen=self._edge_curve_pen(), antialias=True,
                    size=self.scatterplot_item.data["size"][self.pair_indices] / 2)
        if self.relative_edge_widths and len(set(weights)) > 1:
            data['widths'] = \
                scale(weights, .7, 8) * np.log2(self.edge_width / 4 + 1)
        else:
            data['widths'] = None

        if self.edge_curve is None:
            self.edge_curve = PlotVarWidthCurveItem(
                self.master.is_directed(), **data)
            self.edge_curve.setZValue(-10)
            self.plot_widget.addItem(self.edge_curve)
        else:
            self.edge_curve.setData(**data)
        self.update_edge_labels()
예제 #7
0
 def set_edge_sizes(self):
     if not self.graph: return
     if self.relativeEdgeWidths:
         widths = [self.graph.edge[u][v].get('weight', 1)
                   for u, v in self.graph.edges()]
         widths = scale(widths, .7, 8)
     else:
         widths = (.7 for i in range(self.graph.number_of_edges()))
     for edge, width in zip(self.view.edges, widths):
         edge.setSize(width)
예제 #8
0
 def set_edge_sizes(self):
     if not self.graph: return
     if self.relativeEdgeWidths:
         widths = [
             self.graph.edge[u][v].get('weight', 1)
             for u, v in self.graph.edges()
         ]
         widths = scale(widths, .7, 8)
     else:
         widths = (.7 for i in range(self.graph.number_of_edges()))
     for edge, width in zip(self.view.edges, widths):
         edge.setSize(width)
예제 #9
0
 def set_edge_colors(self, attribute):
     colors = []
     if attribute:
         assert self.graph.links()
         values = self.graph.links()[:, attribute].X[:, 0]
         if attribute.is_continuous:
             colors = (tuple(i) for i in CONTINUOUS_PALETTE.getRGB(scale(values)))
         elif attribute.is_discrete:
             DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values))
             colors = (DISCRETE_PALETTE[i] for i in values)
             colors = (tuple(c.red(), c.green(), c.blue()) for c in colors)
     self.edgeColors = colors
     self.set_edge_sizes()
예제 #10
0
    def set_data(self, data):
        if data:
            try:
                y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
            except StopIteration:
                y = np.ones(X.shape[0])
            else:
                y = data[:, y].Y
            while len(self.class_model) < numpy.unique(y).size:
                self.add_new_class_label(undoable=False)

            X = numpy.array([scale(vals) for vals in data.X[:, :2].T]).T
            self.data = numpy.column_stack((X, y))
            self._replot()
예제 #11
0
파일: owpaintdata.py 프로젝트: rbax/orange3
    def set_data(self, data):
        if data:
            try:
                y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
            except StopIteration:
                y = np.ones(X.shape[0])
            else:
                y = data[:, y].Y
            while len(self.class_model) < np.unique(y).size:
                self.add_new_class_label(undoable=False)

            X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
            self.data = np.column_stack((X, y))
            self._replot()
 def set_node_colors(self):
     if not self.graph: return
     attribute = self.attr_color
     assert not attribute or isinstance(attribute, Orange.data.Variable)
     if self.view.legend is not None:
         self.view.scene().removeItem(self.view.legend)
         self.view.legend.clear()
     else:
         self.view.legend = LegendItem()
         self.view.legend.set_parent(self.view)
     if not attribute:
         for node in self.view.nodes:
             node.setColor(None)
         return
     table = self.graph.items()
     if not table: return
     if attribute in table.domain.class_vars:
         values = table[:, attribute].Y
         if values.ndim > 1:
             values = values.T
     elif attribute in table.domain.metas:
         values = table[:, attribute].metas[:, 0]
     elif attribute in table.domain.attributes:
         values = table[:, attribute].X[:, 0]
     else:
         raise RuntimeError("Shouldn't be able to select this column")
     if attribute.is_continuous:
         colors = CONTINUOUS_PALETTE[scale(values)]
         label = PaletteItemSample(
             CONTINUOUS_PALETTE,
             DiscretizedScale(np.nanmin(values), np.nanmax(values)))
         self.view.legend.addItem(label, "")
         self.view.legend.setGeometry(label.boundingRect())
     elif attribute.is_discrete:
         DISCRETE_PALETTE = ColorPaletteGenerator(len(attribute.values))
         colors = DISCRETE_PALETTE[values]
         for value, color in zip(attribute.values, DISCRETE_PALETTE):
             self.view.legend.addItem(
                 ScatterPlotItem(pen=Node.Pen.DEFAULT,
                                 brush=QBrush(QColor(color)),
                                 size=10,
                                 symbol="o"), escape(value))
     for node, color in zip(self.view.nodes, colors):
         node.setColor(color)
     self.view.scene().addItem(self.view.legend)
     self.view.legend.geometry_changed()
예제 #13
0
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.clear_messages()
            if data and data.is_sparse():
                self.Warning.sparse_not_supported()
                return False
            if data:
                if not data.domain.attributes:
                    self.Warning.no_input_variables()
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.Information.use_first_two()
                self.info.set_input_summary(len(data),
                                            format_summary_details(data))
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return bool(data)

        if not _check_and_set_data(data):
            self.info.set_input_summary(self.info.NoInput)
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.Warning.continuous_target()
            self.input_classes = ["C1"]
            self.input_colors = None
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            self.input_colors = y.palette

            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
        self.unconditional_commit()
예제 #14
0
    def set_edge_sizes(self, replot=True):
        self.kwargs['pen'] = DEFAULT_EDGE_PEN
        G = self.graph
        if self.relative_edge_widths or self.edgeColors:
            if self.relative_edge_widths:
                weights = [G.edge[u][v].get('weight', 1) for u, v in G.edges()]
            else:
                weights = [1] * G.number_of_edges()
            if self.edgeColors:
                colors = self.edgeColors
            else:
                colors = [(0xb4, 0xb4, 0xb4)] * G.number_of_edges()

            self.kwargs['pen'] = np.array([c + (0xff, w) for c, w in zip(colors, weights)],
                                          dtype=[('red',   int),
                                                 ('green', int),
                                                 ('blue',  int),
                                                 ('alpha', int),
                                                 ('width', float)])
            self.kwargs['pen']['width'] = 5 * scale(self.kwargs['pen']['width'], .1, 1)
        if replot:
            self.replot()
예제 #15
0
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.warning()
            self.information()
            if data is not None:
                if not data.domain.attributes:
                    self.warning("Input data has no variables")
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.information(
                        "Paint Data uses data from the first two attributes.")
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.warning("Target value is continuous and can not be used.")
            self.input_classes = ["C1"]
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
예제 #16
0
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.warning()
            self.information()
            if data is not None:
                if not data.domain.attributes:
                    self.warning("Input data has no variables")
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.information(
                        "Paint Data uses data from the first two attributes.")
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.warning("Target value is continuous and can not be used.")
            self.input_classes = ["C1"]
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
예제 #17
0
 def test_scale(self):
     self.assertTrue(np.all(scale([0, 1, 2], -1, 1) == [-1, 0, 1]))
     self.assertTrue(np.all(scale([3, 3, 3]) == [1, 1, 1]))
     self.assertTrue(not np.all(np.isnan(scale([.5, np.nan]))))
예제 #18
0
class OWDistanceTransformation(widget.OWWidget):
    name = "Distance Transformation"
    description = "Transform distances according to selected criteria."
    icon = "icons/DistancesTransformation.svg"

    inputs = [("Distances", DistMatrix, "set_data")]
    outputs = [("Distances", DistMatrix)]

    want_main_area = False
    resizing_enabled = False

    normalization_method = settings.Setting(0)
    inversion_method = settings.Setting(0)
    autocommit = settings.Setting(False)

    normalization_options = (
        ("No normalization", lambda x: x),
        ("To interval [0, 1]", lambda x: scale(x, min=0, max=1)),
        ("To interval [-1, 1]", lambda x: scale(x, min=-1, max=1)),
        ("Sigmoid function: 1/(1+exp(-X))", lambda x: 1/(1+np.exp(-x))),
    )

    inversion_options = (
        ("No inversion", lambda x: x),
        ("-X", lambda x: -x),
        ("1 - X", lambda x: 1-x),
        ("max(X) - X", lambda x: np.max(x) - x),
        ("1/X", lambda x: 1/x),
    )

    def __init__(self):
        super().__init__()

        self.data = None

        box = gui.widgetBox(self.controlArea, "Normalization")
        gui.radioButtons(box, self, "normalization_method",
                         btnLabels=[x[0] for x in self.normalization_options],
                         callback=self._invalidate)

        box = gui.widgetBox(self.controlArea, "Inversion")
        gui.radioButtons(box, self, "inversion_method",
                         btnLabels=[x[0] for x in self.inversion_options],
                         callback=self._invalidate)

        gui.auto_commit(self.controlArea, self, "autocommit", "Apply",
                        checkbox_label="Apply on any change")

    def set_data(self, data):
        self.data = data
        self.unconditional_commit()

    def commit(self):
        distances = self.data
        if distances is not None:
            # normalize
            norm = self.normalization_options[self.normalization_method][1]
            distances = norm(distances)

            # invert
            inv = self.inversion_options[self.inversion_method][1]
            distances = inv(distances)
        self.send("Distances", distances)

    def _invalidate(self):
        self.commit()
예제 #19
0
class OWDistanceTransformation(widget.OWWidget):
    name = "距离变换(Distance Transformation)"
    description = "根据所选标准变换距离。"
    icon = "icons/DistancesTransformation.svg"
    keywords = []

    class Inputs:
        distances = Input("距离(Distances)", DistMatrix, replaces=['Distances'])

    class Outputs:
        distances = Output("距离(Distances)",
                           DistMatrix,
                           dynamic=False,
                           replaces=['Distances'])

    want_main_area = False
    resizing_enabled = False
    buttons_area_orientation = Qt.Vertical

    normalization_method = settings.Setting(0)
    inversion_method = settings.Setting(0)
    autocommit = settings.Setting(True)

    normalization_options = (
        ("No normalization", lambda x: x),
        ("To interval [0, 1]", lambda x: scale(x, min=0, max=1)),
        ("To interval [-1, 1]", lambda x: scale(x, min=-1, max=1)),
        ("Sigmoid function: 1/(1+exp(-X))", lambda x: 1 / (1 + np.exp(-x))),
    )
    Chinese_normalization_options = (
        ("无归一化", lambda x: x),
        ("到间隔[0,1]", lambda x: scale(x, min=0, max=1)),
        ("到间隔[-1,1]", lambda x: scale(x, min=-1, max=1)),
        ("Sigmoid函数: 1/(1+exp(-X))", lambda x: 1 / (1 + np.exp(-x))),
    )

    inversion_options = (
        ("No inversion", lambda x: x),
        ("-X", lambda x: -x),
        ("1 - X", lambda x: 1 - x),
        ("max(X) - X", lambda x: np.max(x) - x),
        ("1/X", lambda x: 1 / x),
    )
    Chinese_inversion_options = (
        ("无反转", lambda x: x),
        ("-X", lambda x: -x),
        ("1 - X", lambda x: 1 - x),
        ("最大值(X) - X", lambda x: np.max(x) - x),
        ("1/X", lambda x: 1 / x),
    )

    def __init__(self):
        super().__init__()

        self.data = None

        gui.radioButtons(
            self.controlArea,
            self,
            "normalization_method",
            box="归一化",
            btnLabels=[x[0] for x in self.Chinese_normalization_options],
            callback=self._invalidate)

        gui.radioButtons(
            self.controlArea,
            self,
            "inversion_method",
            box="反转",
            btnLabels=[x[0] for x in self.Chinese_inversion_options],
            callback=self._invalidate)

        gui.auto_apply(self.controlArea, self, "autocommit")

    @Inputs.distances
    def set_data(self, data):
        self.data = data
        self.unconditional_commit()

    def commit(self):
        distances = self.data
        if distances is not None:
            # normalize
            norm = self.normalization_options[self.normalization_method][1]
            distances = norm(distances)

            # invert
            inv = self.inversion_options[self.inversion_method][1]
            distances = inv(distances)
        self.Outputs.distances.send(distances)

    def send_report(self):
        norm, normopt = self.normalization_method, self.normalization_options
        inv, invopt = self.inversion_method, self.inversion_options
        parts = []
        if inv:
            parts.append('inversion ({})'.format(invopt[inv][0]))
        if norm:
            parts.append('normalization ({})'.format(normopt[norm][0]))
        self.report_items(
            'Model parameters',
            {'Transformation': ', '.join(parts).capitalize() or 'None'})

    def _invalidate(self):
        self.commit()
예제 #20
0
class OWDistanceTransformation(widget.OWWidget):
    name = "Distance Transformation"
    description = "Transform distances according to selected criteria."
    icon = "icons/DistancesTransformation.svg"
    keywords = []

    class Inputs:
        distances = Input("Distances", DistMatrix)

    class Outputs:
        distances = Output("Distances", DistMatrix, dynamic=False)

    want_main_area = False
    resizing_enabled = False

    normalization_method = settings.Setting(0)
    inversion_method = settings.Setting(0)
    autocommit = settings.Setting(True)

    normalization_options = (
        ("No normalization", lambda x: x),
        ("To interval [0, 1]", lambda x: scale(x, min=0, max=1)),
        ("To interval [-1, 1]", lambda x: scale(x, min=-1, max=1)),
        ("Sigmoid function: 1/(1+exp(-X))", lambda x: 1 / (1 + np.exp(-x))),
    )

    inversion_options = (
        ("No inversion", lambda x: x),
        ("-X", lambda x: -x),
        ("1 - X", lambda x: 1 - x),
        ("max(X) - X", lambda x: np.max(x) - x),
        ("1/X", lambda x: 1 / x),
    )

    def __init__(self):
        super().__init__()

        self.data = None

        gui.radioButtons(self.controlArea,
                         self,
                         "normalization_method",
                         box="Normalization",
                         btnLabels=[x[0] for x in self.normalization_options],
                         callback=self._invalidate)

        gui.radioButtons(self.controlArea,
                         self,
                         "inversion_method",
                         box="Inversion",
                         btnLabels=[x[0] for x in self.inversion_options],
                         callback=self._invalidate)

        gui.auto_apply(self.buttonsArea, self, "autocommit")

    @Inputs.distances
    def set_data(self, data):
        self.data = data
        self.commit.now()

    @gui.deferred
    def commit(self):
        distances = self.data
        if distances is not None:
            # normalize
            norm = self.normalization_options[self.normalization_method][1]
            distances = norm(distances)

            # invert
            inv = self.inversion_options[self.inversion_method][1]
            distances = inv(distances)
        self.Outputs.distances.send(distances)

    def send_report(self):
        norm, normopt = self.normalization_method, self.normalization_options
        inv, invopt = self.inversion_method, self.inversion_options
        parts = []
        if inv:
            parts.append('inversion ({})'.format(invopt[inv][0]))
        if norm:
            parts.append('normalization ({})'.format(normopt[norm][0]))
        self.report_items(
            'Model parameters',
            {'Transformation': ', '.join(parts).capitalize() or 'None'})

    def _invalidate(self):
        self.commit.deferred()
예제 #21
0
class OWDistanceTransformation(widget.OWWidget):
    name = "Distance Transformation"
    description = "Transform distances according to selected criteria."
    icon = "icons/DistancesTransformation.svg"

    inputs = [("Distances", DistMatrix, "set_data")]
    outputs = [("Distances", DistMatrix)]

    want_main_area = False
    resizing_enabled = False
    buttons_area_orientation = Qt.Vertical

    normalization_method = settings.Setting(0)
    inversion_method = settings.Setting(0)
    autocommit = settings.Setting(True)

    normalization_options = (
        ("No normalization", lambda x: x),
        ("To interval [0, 1]", lambda x: scale(x, min=0, max=1)),
        ("To interval [-1, 1]", lambda x: scale(x, min=-1, max=1)),
        ("Sigmoid function: 1/(1+exp(-X))", lambda x: 1 / (1 + np.exp(-x))),
    )

    inversion_options = (
        ("No inversion", lambda x: x),
        ("-X", lambda x: -x),
        ("1 - X", lambda x: 1 - x),
        ("max(X) - X", lambda x: np.max(x) - x),
        ("1/X", lambda x: 1 / x),
    )

    def __init__(self):
        super().__init__()

        self.data = None

        gui.radioButtons(self.controlArea,
                         self,
                         "normalization_method",
                         box="Normalization",
                         btnLabels=[x[0] for x in self.normalization_options],
                         callback=self._invalidate)

        gui.radioButtons(self.controlArea,
                         self,
                         "inversion_method",
                         box="Inversion",
                         btnLabels=[x[0] for x in self.inversion_options],
                         callback=self._invalidate)

        box = gui.auto_commit(self.buttonsArea,
                              self,
                              "autocommit",
                              "Apply",
                              checkbox_label="Apply automatically",
                              box=None)
        box.layout().insertWidget(0, self.report_button)
        box.layout().insertSpacing(1, 8)

    def set_data(self, data):
        self.data = data
        self.unconditional_commit()

    def commit(self):
        distances = self.data
        if distances is not None:
            # normalize
            norm = self.normalization_options[self.normalization_method][1]
            distances = norm(distances)

            # invert
            inv = self.inversion_options[self.inversion_method][1]
            distances = inv(distances)
        self.send("Distances", distances)

    def send_report(self):
        norm, normopt = self.normalization_method, self.normalization_options
        inv, invopt = self.inversion_method, self.inversion_options
        parts = []
        if inv:
            parts.append('inversion ({})'.format(invopt[inv][0]))
        if norm:
            parts.append('normalization ({})'.format(normopt[norm][0]))
        self.report_items(
            'Model parameters',
            {'Transformation': ', '.join(parts).capitalize() or 'None'})

    def _invalidate(self):
        self.commit()