Exemplo n.º 1
0
class ElemDetectSettingsDialog(QDialog, Ui_Dialog):
    """
    Dialog that allow the selection of segmentation, parameter measurement
    and  classification  methods that would be used to  process a  segment.
    It's a factory interface of detectors, parameter measurers and classifiers
    """

    # region SIGNALS

    # signal raised when the user try to modify the parameters to measure
    # that functionality is delegated in the segmentation window through signal
    modifyParametersMeasurement = pyqtSignal()

    # endregion

    # region Initialize

    def __init__(self, parent, signal, segment_manager=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.widget.signal = small_signal(signal)

        self._detector = None
        self._classifier = None

        # the factory adapters for segmentation and classification
        self._segmentationAdapterFactory = SegmentationAdapterFactory()
        self._classificationAdapterFactory = ClassificationAdapterFactory()

        # the widget to visualize the segmentation and classification methods
        self.segmentation_classification_tree = None

        self.segmentation_classification_tree_widget = DuettoParameterTree()
        self.segmentation_classification_tree_widget.setAutoScroll(True)
        self.segmentation_classification_tree_widget.setHeaderHidden(True)

        layout = QtGui.QVBoxLayout()
        layout.setMargin(0)

        layout.addWidget(self.parameter_bttn)
        layout.addWidget(self.segmentation_classification_tree_widget)
        self.segmentation_classification_settings.setLayout(layout)

        # to manage the parameter selection and configuration
        self.parameter_bttn.clicked.connect(lambda: self.modifyParametersMeasurement.emit())

        self.create_segmentation_classification_param_tree()

        if segment_manager is not None:
            self.restore_previous_state(segment_manager)

        self.detect()

    def create_segmentation_classification_param_tree(self):
        # get the adapters for segmentation methods
        segmentation_adapters = self.segmentation_adapter_factory.adapters_names()
        segmentation_adapters = [(self.tr(unicode(t)), t) for t in segmentation_adapters]

        # get the adapters for classification methods
        classification_adapters = self.classification_adapter_factory.adapters_names()
        classification_adapters = [(self.tr(t), t) for t in classification_adapters]

        # the list of segmentation adapters check boxes to select method (radio buttons unavailable)
        list_param = [{u'name': unicode(x[1]), u'type': u'bool', u'value': False, u'default': False}
                      for x in segmentation_adapters]

        # the method settings
        list_param.append({u'name': unicode(self.tr(u'Method Settings')), u'type': u'group', u'children': []})

        # the list of classification adapters check boxes to select method (radio buttons unavailable)
        list_classification = [{u'name': unicode(x[1]), u'type': u'bool', u'value': False, u'default': False}
                               for x in classification_adapters]

        # the method settings
        list_classification.append({u'name': unicode(self.tr(u'Method Settings')), u'type': u'group', u'children': []})

        params = [{u'name': unicode(self.tr(u'Segmentation')), u'type': u'group', u'children': list_param},
                  {u'name': unicode(self.tr(u'Classification')), u'type': u'group', u'children': list_classification}]

        self.segmentation_classification_tree = Parameter.create(name=unicode(self.tr(u'Settings')),
                                                                 type=u'group', children=params)

        self.connect_segmentation_classification_changed_events()

    def connect_segmentation_classification_changed_events(self):

        self.segmentation_classification_tree.param(unicode(self.tr(u'Segmentation'))). \
            sigTreeStateChanged.connect(lambda param, changes:
                                        self.segmentation_classification_changed(param, changes, u'Segmentation',
                                                                                 self.segmentation_adapter_factory))

        self.segmentation_classification_tree.param(unicode(self.tr(u'Classification'))). \
            sigTreeStateChanged.connect(lambda param, changes:
                                        self.segmentation_classification_changed(param, changes, u'Classification',
                                                                                 self.classification_adapter_factory))

        # create and set initial properties
        self.segmentation_classification_tree_widget.setParameters(self.segmentation_classification_tree)

        # the only way of segmentation and classification on Sound Lab Lite version is manual
        # self.segmentation_classification_tree_widget.setEnabled(False)

        # lite_licence_restriction = u"The only way of segmentation and classification \n " \
        #                            u"on Sound Lab Lite version is the manual. \n " \
        #                            u"The others methods are available at \n the Professional Version of the software"
        #
        # self.segmentation_classification_tree_widget.setToolTip(self.tr(lite_licence_restriction))

    def segmentation_classification_changed(self, param, changes, param_tree_name, adapter_factory):
        """
        Process a change into the parameter tree of segmentation and classification
        :param param:
        :param changes:
        :return:
        """

        # block signals because there is changes that involve tree updates
        # (select a segmentation method with settings that must be added into the tree by example)
        self.segmentation_classification_tree.blockSignals(True)

        for parameter, _, value in changes:

            # if the value is bool then is the selection of the segmentation or classification method
            # if the change came from the segmentation or classification method settings
            # then continue (each method adapter would take care about it's settings)
            if isinstance(value, bool) and value and \
               parameter not in self.segmentation_classification_tree.param(unicode(self.tr(param_tree_name))). \
               param(unicode(self.tr(u'Method Settings'))).children():

                try:
                    # the parameter changed has the method name
                    adapter = adapter_factory.get_adapter(parameter.name())

                    # change the method settings if any (Parameter tree interface of adapter)
                    param_settings = self.segmentation_classification_tree.param(
                        unicode(self.tr(param_tree_name))).param(unicode(self.tr(u'Method Settings')))

                    param_settings.clearChildren()

                    method_settings = adapter.get_settings()
                    if method_settings:
                        param_settings.addChild(method_settings)

                except Exception as ex:
                    print(ex.message)

                params_to_update = self.segmentation_classification_tree.param(
                    unicode(self.tr(param_tree_name))).children()

                params_to_update = [p for p in params_to_update if p.type() == u"bool" and
                                    p.name() != parameter.name()]

                # set to false the others segmentation methods (radio button behavior, only select one method)
                for p in params_to_update:
                    p.setValue(False)

        self.segmentation_classification_tree.blockSignals(False)
        self.detect()

    # endregion

    # region Restore Previous Status

    def restore_previous_state(self, segment_manager):
        """
        Restore the dialog previous selected data to avoid lose of previous selected information
        :return:
        """

        # segmentation method restoration
        self._restore_method(segment_manager.detector_adapter, self.segmentation_adapter_factory, u'Segmentation')

        # classification method restoration
        self._restore_method(segment_manager.classifier_adapter, self.classification_adapter_factory, u'Classification')

    def _restore_method(self, adapter, adapter_factory, method_name):
        """
        Restore the segmentation or classification adapter previously used
        (user friendly restore of previous values of the dialog)
        :param adapter: the adapter (segmentation or classification adapter)
        :param method_name: the method name (one of 'Segmentation', 'Classification')
        :return:
        """

        for parameter in self.segmentation_classification_tree.param(unicode(self.tr(method_name))).children():
            if parameter.type() == u"bool":
                adapter_name = parameter.name()

                method_adapter = adapter_factory.get_adapter(adapter_name)

                if type(adapter) == type(method_adapter):
                    method_adapter.restore_settings(adapter, self.widget.signal)
                    parameter.setValue(True)

    # endregion

    # region  Factory Adapters Properties

    @property
    def segmentation_adapter_factory(self):
        return self._segmentationAdapterFactory

    @property
    def classification_adapter_factory(self):
        return self._classificationAdapterFactory

    @property
    def detector(self):
        """
        :return: The selected detector adapter to perform segmentation
        """
        self._detector = self._get_adapter(self.segmentation_adapter_factory)
        return self._detector

    @property
    def classifier(self):
        self._classifier = self._get_adapter(self.classification_adapter_factory)
        return self._classifier

    # endregion

    # region Detector, Parameter Measurers and Classifier

    def _get_adapter(self, adapter_factory):
        """
        Gets the adapter of the supplied adapter factory
        :param adapter_factory: the adapter factory to find the adapter in.
        :return:
        """
        param_tree_name = u'Classification' if adapter_factory == self.classification_adapter_factory else u'Segmentation'

        default_adapter_class = ManualDetectorAdapter

        if adapter_factory == self.classification_adapter_factory:
            default_adapter_class = ManualClassifierAdapter

        try:
            name = ""
            parameters = self.segmentation_classification_tree.param(unicode(self.tr(param_tree_name))).children()

            for parameter in parameters:
                if parameter.type() == u"bool" and parameter.value():
                    name = parameter.name()
                    break

            adapter = adapter_factory.get_adapter(name)

        except Exception as e:
            print("Fail to get the adapter instance. In " + param_tree_name + " " + e.message)
            adapter = default_adapter_class()

        return adapter

    # endregion

    def load_workspace(self, workspace):
        """
        Method that loads the workspace to update visual options from main window.
        :param workspace:
        """
        self.widget.load_workspace(workspace)

    def detect(self):
        """
        Perform the detection on the small signal to visualize an
        approximation of the detection algorithm
        :return:
        """
        # get the detector to perform segmentation
        detector = self.detector.get_instance(self.widget.signal)

        self.widget.elements = detector.detect()

        # update the visual items of the segmentation process
        self.widget.add_segmentation_items(detector.get_visual_items())

        self.widget.graph()
Exemplo n.º 2
0
class ParametersWindow(QtGui.QDialog, Ui_Dialog):
    """
    Window that visualize a parameter manager to change its configurations.
    Contains a tab widget with all the types of parameters to measure.
    """

    # region CONSTANTS

    MEASUREMENT_TEMPLATE_FOLDER = os.path.join("utils",
                                               "measurement_templates")

    PARAMETER_VISUALIZATION_SIGNAL_PATH = os.path.join(
        os.path.join("utils", "measurement_templates"),
        "measurement_params_example.wav")

    # endregion

    # region SIGNALS

    # signal raised when the window has finished to interact with parameters
    parameterChangeFinished = pyqtSignal(object)

    # endregion

    # region Initialize

    def __init__(self,
                 parent=None,
                 measurement_template=None,
                 signal=None,
                 workspace=None):
        """
        :type specgram_data: dict with the options of spectrogram creation on main window
        options are NFFT and overlap
        """
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)

        # the list of templates names
        self.templates_paths = []
        self.load_templates()

        # if accepted or cancel anyway raise the changes
        self.buttonBox.clicked.connect(
            lambda bttn: self.parameterChangeFinished.emit(
                self.measurement_template))

        self.remove_measurement_template_bttn.clicked.connect(
            self.delete_template)
        self.save_measurement_template_as_bttn.clicked.connect(
            self.save_template_as)
        self.measurement_template_cbox.currentIndexChanged.connect(
            self.select_template)

        # configuration of parameters and location trees user interface
        self.parameter_tree_widget = DuettoParameterTree()
        self.parameter_tree_widget.setAutoScroll(True)
        self.parameter_tree_widget.setHeaderHidden(True)

        self.location_tree_widget = DuettoParameterTree()
        self.location_tree_widget.setAutoScroll(True)
        self.location_tree_widget.setHeaderHidden(True)

        # create and set the layout for the parameter and location settings widget
        self.create_layout_for_settings_widget()

        self.param_measurement_tree = None
        self.location_measurement_tree = None

        # create the parameter trees for parameter settings and location settings
        self.param_measurement_tree = Parameter.create(name=unicode(
            self.tr(u'Parameter')),
                                                       type=u'group')
        self.parameter_tree_widget.setParameters(self.param_measurement_tree)

        self.location_measurement_tree = Parameter.create(name=unicode(
            self.tr(u'Location')),
                                                          type=u'group')
        self.location_tree_widget.setParameters(self.location_measurement_tree)

        # tables are just for selection (of parameter-location) not for edit elements.
        self.parameter_locations_table.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.wave_parameter_table.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.time_parameter_table.setEditTriggers(
            QAbstractItemView.NoEditTriggers)

        self.workspace = workspace
        self.signal = signal

        self._measurement_template = None
        self.measurement_template = measurement_template if measurement_template is not None else MeasurementTemplate(
        )

        # pre visualization signal
        try:
            signal = openSignal(self.PARAMETER_VISUALIZATION_SIGNAL_PATH)

        except Exception as e:
            signal = small_signal(signal)

        self.configure_advanced_mode_items(signal)

        self.segmentManager = SegmentManager()

        self.segmentManager.signal = self.widget.signal
        self.segmentManager.segmentVisualItemAdded.connect(
            self.widget.add_parameter_visual_items)
        self.segmentManager.measurementsFinished.connect(
            self.widget.draw_elements)
        self.try_load_signal_segment()

    def configure_advanced_mode_items(self, signal):
        """
        Configure the setting of the advanced mode window.
        :param signal: the signal to graph with the segment to dynamically
        measure the parameters and display items
        :return:
        """
        self.widget.signal = signal
        self.widget.visibleSpectrogram = True
        self.widget.visibleOscilogram = False

        if self.workspace is not None:
            self.widget.load_workspace(self.workspace)

        self.visible_oscilogram_cbox.stateChanged.connect(
            self.update_widget_graphs_visibility)
        self.visible_spectrogram_cbox.stateChanged.connect(
            self.update_widget_graphs_visibility)
        self.visible_spectrogram_cbox.setChecked(True)
        self.visible_oscilogram_cbox.setChecked(False)
        self.widget.setSelectedTool(Tools.NoTool)

        def visibility_function():
            visibility = self.advanced_mode_visibility_cbox.isChecked()
            self.dock_widget_advanced_mode.setVisible(visibility)
            if visibility:
                self.update_parameter_pre_visualization()

        self.advanced_mode_visibility_cbox.stateChanged.connect(
            visibility_function)
        self.dock_widget_advanced_mode.setVisible(False)
        self.finished.connect(
            lambda _: self.dock_widget_advanced_mode.setVisible(False))

    def create_layout_for_settings_widget(self):
        label = QtGui.QLabel("<center><h4>" + self.tr(u"Settings") +
                             "</h4></center>")
        layout = QtGui.QVBoxLayout()
        layout.setMargin(0)
        layout.addWidget(label)
        layout.addWidget(self.parameter_tree_widget)
        layout.addWidget(self.location_tree_widget)
        self.settings_widget.setLayout(layout)

    def try_load_signal_segment(self):
        """
        Restore (if any) the previous session with this file.
        That means detected elements, measured parameters etc that are saved on the signal
        extra data.
        :return:
        """
        segments = self.widget.get_signal_segmentation_data()

        if len(segments) == 0:
            return

        for i, e in enumerate(segments):
            self.segmentManager.add_element(i, e[0], e[1])

        self.widget.elements = self.segmentManager.elements
        self.widget.graph()

    def update_widget_graphs_visibility(self):
        osc_visibility = self.visible_oscilogram_cbox.isChecked()
        spec_visibility = self.visible_spectrogram_cbox.isChecked()

        if self.widget.visibleOscilogram != osc_visibility:
            self.widget.visibleOscilogram = osc_visibility

        if self.widget.visibleSpectrogram != spec_visibility:
            self.widget.visibleSpectrogram = spec_visibility

        self.widget.setVisible(osc_visibility or spec_visibility)

        self.widget.graph()

    # endregion

    # region Measurement Template Actions

    def select_template(self, index):
        """
        Select the template at index supplied.
        :param index:
        :return:
        """
        if index == 0:
            # the --new-- or blank measurement template
            self.measurement_template = MeasurementTemplate()
            return

        try:
            self.measurement_template.load_state(
                deserialize(self.templates_paths[index - 1]))
            self.load_parameters()
            self.update_parameter_pre_visualization()

        except Exception as e:
            print(e.message)
            self.select_template(0)

    def load_templates(self):
        """
        Load all the available templates from disc
        (if any) and fills the combo box with them.
        :return:
        """
        self.measurement_template_cbox.clear()

        # the 0 index of the combo has the new template option for unsaved ones
        self.measurement_template_cbox.addItem("--new--")

        self.templates_paths = folder_files(self.MEASUREMENT_TEMPLATE_FOLDER,
                                            extensions=[".dmt"])

        templates = []

        for path in self.templates_paths:
            try:
                templates.append(deserialize(path)["name"])

            except Exception as e:
                templates.append(path)

        self.measurement_template_cbox.addItems(templates)

    def save_template_as(self):
        """
        Save the current template as a new one in the system.
        :return:
        """
        new_template_name = unicode(self.new_template_name_linedit.text())
        template_names = [
            unicode(self.measurement_template_cbox.itemText(i))
            for i in range(self.measurement_template_cbox.count())
        ]

        if not new_template_name:
            QtGui.QMessageBox.warning(
                QtGui.QMessageBox(), self.tr(u"Error"),
                self.tr(
                    u"You have to write a name for the measurement template."))
            return

        elif new_template_name in template_names:
            error_msg = self.tr(u"There is already a template with that name.")
            QtGui.QMessageBox.warning(QtGui.QMessageBox(), self.tr(u"Error"),
                                      error_msg)
            return

        # save the template as new one
        self.measurement_template.name = new_template_name

        try:
            new_template_path = os.path.join(
                self.MEASUREMENT_TEMPLATE_FOLDER,
                self.measurement_template.name + ".dmt")

            serialize(new_template_path, self.measurement_template.get_state())

            self.templates_paths.append(new_template_path)
            self.measurement_template_cbox.addItem(new_template_name)

            # the first index is for the --new-- template
            self.measurement_template_cbox.setCurrentIndex(
                len(self.templates_paths))

        except Exception as e:
            print e.message
            error_msg = self.tr(
                u"An error occurs when the template was been saved. Try it again"
            )
            QtGui.QMessageBox.warning(QtGui.QMessageBox(), self.tr(u"Error"),
                                      error_msg)

    def delete_template(self):
        """
        removes (if possible) the current template from the list of templates.
        :return:
        """

        text = self.tr(
            u"The current measurement template is protected and could not be deleted."
        )

        if not self.measurement_template.editable:
            QtGui.QMessageBox.warning(QtGui.QMessageBox(), self.tr(u"Error"),
                                      text)
            return

        current_template_index = self.measurement_template_cbox.currentIndex()

        if current_template_index == 0:
            return

        # delete the file
        try:
            os.remove(
                os.path.join(self.MEASUREMENT_TEMPLATE_FOLDER,
                             self.measurement_template.name + ".dmt"))

            self.measurement_template_cbox.removeItem(current_template_index)
            del self.templates_paths[current_template_index - 1]

        except Exception as e:
            pass

        # select the --new-- template at 0 index in the combo box
        self.measurement_template_cbox.setCurrentIndex(0)

    # endregion

    # region Properties

    @property
    def measurement_template(self):
        return self._measurement_template

    @measurement_template.setter
    def measurement_template(self, parameter):
        if not isinstance(parameter, MeasurementTemplate):
            raise Exception(
                "Invalid type of argument. Must be of type MeasurementTemplate"
            )

        self._measurement_template = parameter
        self.config_measurement_template()

        for adapter in self.measurement_template.get_data_changing_adapters():
            adapter.dataChanged.connect(
                self.update_parameter_pre_visualization)

        # when change the parameter manager updates its values on the tables
        self.load_parameters()

    # endregion

    # region Load Parameters

    def config_measurement_template(self):
        """
        sets the configuration if any of the values in the measurement template
        accord to the current signal in process by the system
        :return:
        """

        if self.signal is not None:
            self.measurement_template.update_adapters_data(self.signal)

        if self.workspace is not None:
            NFFT = self.workspace.spectrogramWorkspace.FFTSize
            overlap = self.workspace.spectrogramWorkspace.FFTOverlap

            # the overlap on the workspace is between 0 and 1
            # and is -1 if automatic overlap selected by system
            overlap = 50 if overlap < 0 else overlap * 100

            spectrogram_data = dict(NFFT=NFFT, overlap=overlap)
            self.measurement_template.update_locations_data(spectrogram_data)

    def load_parameters(self):
        """
        Updates the parameter configuration on the window with the ones in the parameter template.
        :return:
        """

        if self.measurement_template is None:
            return

        self.load_time_based_parameters()

        table = self.wave_parameter_table
        adapters = self.measurement_template.wave_parameters_adapters
        locations_adapters = self.measurement_template.wave_locations_adapters
        self.load_parameters_locations(
            table, locations_adapters, adapters,
            self.measurement_template.wave_location_parameters)

        table = self.parameter_locations_table
        locations_adapters = self.measurement_template.spectral_time_locations_adapters
        adapters = self.measurement_template.spectral_parameters_adapters
        self.load_parameters_locations(
            table, locations_adapters, adapters,
            self.measurement_template.spectral_location_parameters)

        # clear the parameter-location settings tree
        self.param_measurement_tree.clearChildren()
        self.location_measurement_tree.clearChildren()

    def load_time_based_parameters(self):
        """
        Load into the time parameter table widget the data of the time parameter adapters from the manager.
        :return:
        """

        # commodity variable for table
        table = self.time_parameter_table

        adapters = self.measurement_template.time_parameters_adapters

        # first row for the 'select all' option
        table.setRowCount(1 + len(adapters))
        table.setVerticalHeaderLabels([self.tr(u"Select All")] +
                                      [x.name for x in adapters])

        table.setColumnCount(1)
        table.setHorizontalHeaderLabels([self.tr(u"Measure")])
        all_selected = True

        # the first row (0 index) is for the select all option
        for i in xrange(table.rowCount()):
            item = QtGui.QTableWidgetItem("")

            state = Qt.Unchecked if i == 0 or not self.measurement_template.time_location_parameters[
                i - 1] else Qt.Checked

            all_selected = all_selected and (i == 0 or state == Qt.Checked)

            item.setCheckState(state)

            table.setItem(i, 0, item)

        if all_selected:
            table.item(0, 0).setCheckState(Qt.Checked)

        def time_selection_function(row, col):
            state = Qt.Unchecked if table.item(
                row, col).checkState() == Qt.Checked else Qt.Checked
            table.item(row, col).setCheckState(state)

        table.cellPressed.connect(time_selection_function)
        table.cellClicked.connect(
            lambda row, col: self.parameter_time_selected(row, col, adapters))

        # fit the contents of the parameters names on the cells
        table.resizeColumnsToContents()
        table.resizeRowsToContents()

    def load_parameters_locations(self, table, time_locations_adapters,
                                  param_adapters, selection_matrix):
        """
        Load into the spectral tab widget the data of the current parameter manager
        :return:
        """
        # one extra row and column for the 'select all' option
        table.setRowCount(1 + len(param_adapters))
        table.setColumnCount(1 + len(time_locations_adapters))

        row_names = [self.tr(u"All Params")] + [x.name for x in param_adapters]
        column_names = [self.tr(u"All Locations")
                        ] + [x.name for x in time_locations_adapters]

        # load spectral params and locations
        for i in xrange(table.rowCount()):
            for j in xrange(table.columnCount()):
                item = QtGui.QTableWidgetItem("")
                table.setItem(i, j, item)

                state = Qt.Unchecked
                if i > 0 and j > 0 and selection_matrix[i - 1, j - 1]:
                    state = Qt.Checked

                item.setCheckState(state)

        all_selected = True

        # set the state for the select all options items
        for i in xrange(1, table.rowCount()):
            state = Qt.Checked if selection_matrix[
                i - 1, :].all() else Qt.Unchecked
            table.item(i, 0).setCheckState(state)

        for i in xrange(1, table.columnCount()):
            state = Qt.Checked if selection_matrix[:, i -
                                                   1].all() else Qt.Unchecked
            table.item(0, i).setCheckState(state)
            all_selected = all_selected and (state == Qt.Checked)

        table.item(
            0, 0).setCheckState(Qt.Checked if all_selected else Qt.Unchecked)

        table.setVerticalHeaderLabels(row_names)
        table.setHorizontalHeaderLabels(column_names)

        def selection_function(x, y):
            state = Qt.Unchecked if table.item(
                x, y).checkState() == Qt.Checked else Qt.Checked
            table.item(x, y).setCheckState(state)

        table.cellPressed.connect(selection_function)
        table.cellClicked.connect(
            lambda x, y: self.parameter_spectral_selected(
                table, x, y, param_adapters, selection_matrix,
                time_locations_adapters))

        table.resizeColumnsToContents()
        table.resizeRowsToContents()

    # endregion

    def parameter_spectral_selected(self,
                                    table,
                                    row,
                                    col,
                                    param_adapters,
                                    selection_matrix,
                                    time_locations_adapters=None,
                                    select_all_parameters=False):

        # select all params all locations
        if row == 0 and col == 0:
            # update the columns 'select all' values
            for i in xrange(1, table.columnCount()):
                table.item(row,
                           i).setCheckState(table.item(row, col).checkState())

            for i in xrange(1, table.rowCount()):
                table.item(i, col).setCheckState(
                    table.item(row, col).checkState())
                self.parameter_spectral_selected(table, i, col, param_adapters,
                                                 selection_matrix,
                                                 time_locations_adapters, True)

        elif row == 0:
            for i in xrange(1, table.rowCount()):
                table.item(i, col).setCheckState(
                    table.item(row, col).checkState())
                selection_matrix[i - 1, col - 1] = table.item(
                    row, col).checkState() == Qt.Checked

        elif col == 0:
            for i in xrange(1, table.columnCount()):
                table.item(row,
                           i).setCheckState(table.item(row, col).checkState())
                selection_matrix[row - 1, i - 1] = table.item(
                    row, col).checkState() == Qt.Checked
        else:
            selection_matrix[row - 1, col - 1] = table.item(
                row, col).checkState() == Qt.Checked

            # boolean to avoid multiple computation with recursive calls
            if not select_all_parameters:
                self.update_parameter_and_locations_settings(
                    row - 1, col - 1, param_adapters, time_locations_adapters)

        if not select_all_parameters:
            table.repaint()
            self.settings_widget.repaint()
            self.update_parameter_pre_visualization()

    def parameter_time_selected(self, row, col, param_adapters):

        table = self.time_parameter_table if self.tab_time_parameters.isVisible(
        ) else self.wave_parameter_table

        if row == 0:
            for i in xrange(1, table.rowCount()):
                table.item(i, col).setCheckState(
                    table.item(row, col).checkState())

            self.measurement_template.time_location_parameters[:] = table.item(
                row, col).checkState() == Qt.Checked
            return

        self.measurement_template.time_location_parameters[
            row - 1] = table.item(row, col).checkState() == Qt.Checked
        self.update_parameter_and_locations_settings(
            row - 1, col, param_adapters,
            self.measurement_template.spectral_time_locations_adapters)

        # make a fast visible the change on the checkbox to prevent multiple clicks
        table.repaint()

        self.update_parameter_pre_visualization()

    def update_parameter_and_locations_settings(self,
                                                row,
                                                col,
                                                param_adapters,
                                                time_locations_adapters=None):
        # update tree of parameter settings and locations
        self.param_measurement_tree.clearChildren()
        self.location_measurement_tree.clearChildren()
        if row < 0 or col < 0:
            return

        try:
            parameter_adapter = param_adapters[row]
            param_settings = parameter_adapter.get_settings()
            self.param_measurement_tree.addChild(param_settings)

            if time_locations_adapters is not None:
                time_location_adapter = time_locations_adapters[col]
                location_settings = time_location_adapter.get_settings()
                self.location_measurement_tree.addChild(location_settings)

            # spectral locations reserved for future versions
            # if isinstance(parameter_adapter, SpectralParameterAdapter):
            #     spectral_location_adapter = self.measurement_template.spectral_locations_adapters[row, col]
            #     spectral_location_settings = spectral_location_adapter.get_settings()
            #     self.location_measurement_tree.addChild(spectral_location_settings)

        except Exception as ex:
            print("updating settings " + ex.message)

    def update_parameter_pre_visualization(self):
        if self.widget.visibleSpectrogram or self.widget.visibleOscilogram:
            self.segmentManager.parameters = self.measurement_template.parameter_list(
            )

            self.widget.elements = self.segmentManager.elements

            self.segmentManager.measure_parameters_and_classify()
Exemplo n.º 3
0
class OneDimensionalAnalysisWindow(QtGui.QMainWindow, Ui_OneDimensionalWindow):
    """
    Window that allow to create and visualize one dimensional transforms on signals
    """

    # region CONSTANTS
    DOCK_OPTIONS_WIDTH = 350
    WIDGET_MINIMUM_HEIGHT = 350
    WIDGET_MINIMUM_WIDTH = 1.5 * DOCK_OPTIONS_WIDTH

    # endregion
    def __init__(self, parent=None, signal=None):
        super(OneDimensionalAnalysisWindow, self).__init__(parent)
        self.setupUi(self)
        self.show()
        self.widget.setMinimumWidth(self.WIDGET_MINIMUM_WIDTH)
        self.widget.setMinimumHeight(self.WIDGET_MINIMUM_HEIGHT)

        self._transforms_handler = OneDimensionalGeneralHandler(self)

        # connect the tool detected data to show the status bar message
        self.statusbar = self.statusBar()
        self.statusbar.setSizeGripEnabled(False)
        self.widget.toolDataDetected.connect(self.updateStatusBar)

        self._indexTo = -1
        self._indexFrom = 0

        if signal is None:
            raise Exception("Signal can't be None.")

        # set a default one dim one_dim_transform to the widget
        self.widget.signal = signal

        # self.signal = signal
        self.widget.one_dim_transform = InstantFrequencies(signal)

        # Parameter Tree Settings
        self.__createParameterTree()

        # self._transforms_handler.dataChanged.connect(self.widget.graph)

    def load_workspace(self, workspace):
        """
        Load the supplied theme into the widget inside.
        :param theme: The theme with the visual options
        :return:
        """
        self.widget.load_workspace(workspace)

    # region Properties IndexTo IndexFrom

    @property
    def indexTo(self):
        return self._indexTo

    @indexTo.setter
    def indexTo(self, value):
        self._indexTo = value

    @property
    def indexFrom(self):
        return self._indexFrom

    @indexFrom.setter
    def indexFrom(self, value):
        self._indexFrom = value

    # endregion

    # region Graph

    def graph(self, indexFrom=0, indexTo=-1):
        """
        Update the graph of the one dimensional
        selected transformation in the signal interval supplied.
        Apply the transformation to the signal interval and plot it.
        :param indexFrom: start of the interval in signal data indexes
        :param indexTo: end of the interval in signal data indexes
        :return:
        """
        indexTo = indexTo if indexTo >= 0 else self.widget.signal.length

        if indexTo != self.indexTo:
            self.indexTo = indexTo

        if indexFrom != self.indexFrom:
            self.indexFrom = indexFrom

        labels = self._transforms_handler.get_axis_labels(
            self.widget.one_dim_transform)
        self.widget.graph(indexFrom, indexTo, labels)

    # endregion

    # region Parameter Tree Options

    def __createParameterTree(self):
        """
        Create the ParameterTree with the options of the window.
        The ParameterTree contains the combo box of
        the active one dimensional transforms to select.
        :return:
        """
        transforms = self._transforms_handler.get_all_transforms_names()
        transforms = [(unicode(self.tr(unicode(t))), t) for t in transforms]
        params = [{
            u'name': unicode(self.tr(u'Select')),
            u'type': u'list',
            u'value': transforms[0][1],
            u'default': transforms[0][1],
            u'values': transforms
        }, {
            u'name': unicode(self.tr(u'Settings')),
            u'type': u'group'
        }]

        self.ParamTree = Parameter.create(name=u'One Dimensional Transform',
                                          type=u'group',
                                          children=params)

        # create and set initial properties
        self.parameterTree = DuettoParameterTree()
        self.parameterTree.setAutoScroll(True)
        self.parameterTree.setHeaderHidden(True)
        self.parameterTree.setParameters(self.ParamTree)

        # connect the signals to react when a change of one_dim_transform is made
        self.ParamTree.param(unicode(
            self.tr(u'Select'))).sigValueChanged.connect(self.changeTransform)

        # reload the new widgets one_dim_transform options
        self._transform_paramTree = None
        self.reloadOptionsWidget(self.widget.one_dim_transform)

    def reloadOptionsWidget(self, one_dim_transform):
        """
        Refresh the parameter tree and the layout
        of the options windows when a change of  transformation is made.
        removes the old transformation options and set the parameter tree
        of the new one.
        :param one_dim_transform: the new one_dim_transform.
        :return:
        """
        options_window_layout = QtGui.QVBoxLayout()
        options_window_layout.setMargin(0)
        options_window_layout.addWidget(self.parameterTree)

        # add the parameter tree of the one_dim_transform if exists
        if one_dim_transform is not None:

            # clear the settings options of the parameter tree
            if self._transform_paramTree is not None:
                self.ParamTree.param(u'Settings').clearChildren()
            params = self._transforms_handler.get_settings(one_dim_transform)
            self._transform_paramTree = Parameter.create(name=u'Parameters',
                                                         type=u'group',
                                                         children=params)

            if params:
                self.ParamTree.param(u'Settings').addChild(
                    self._transform_paramTree)

            # getting transform graph information by the general handler
            labels = self._transforms_handler.get_axis_labels(
                one_dim_transform)
            limits = self._transforms_handler.get_y_limits(one_dim_transform)
            default_limits = self._transforms_handler.get_y_default(
                one_dim_transform)

            # setting the default Y range values
            self.widget.minY = default_limits[0]
            self.widget.maxY = default_limits[1]

            # adding the range params to the settings
            rangeParams = [{
                u'name': unicode(self.tr(u'Min')),
                u'type': u'int',
                u'limits': limits,
                u'value': self.widget.minY
            }, {
                u'name': unicode(self.tr(u'Max')),
                u'type': u'int',
                u'limits': limits,
                u'value': self.widget.maxY
            }]

            self._yRange_paramTree = Parameter.create(name=labels[u'Y'],
                                                      type=u'group',
                                                      children=rangeParams)
            self.ParamTree.param(u'Settings').addChild(self._yRange_paramTree)

            # setting the connecting lines option on settings with default transform value
            self.widget.lines = self._transforms_handler.get_default_lines(
                one_dim_transform)

            lines = {
                u'name': unicode(self.tr(u'Connect points')),
                u'type': u'bool',
                u'value': self.widget.lines,
                u'default': self.widget.lines
            }

            self.ParamTree.param(u'Settings').addChild(lines)

            # connecting the signals to the change handler interval_function
            self.ParamTree.param(u'Settings').param(
                u'Connect points').sigTreeStateChanged.connect(
                    self.connectLinesChanged)
            self._transform_paramTree.sigTreeStateChanged.connect(
                self.changeTransformSettings)
            self._yRange_paramTree.sigTreeStateChanged.connect(
                self.changeYRangeSettings)

        # removing the old layout from the dock widget
        self.dock_settings_contents = QtGui.QWidget()
        self.dock_settings_contents.setLayout(options_window_layout)
        self.dockSettings.setWidget(self.dock_settings_contents)
        self.dockSettings.setVisible(True)
        self.dockSettings.setMinimumWidth(self.DOCK_OPTIONS_WIDTH)

    def changeTransform(self, parameter):
        """
        Method invoked when a new one_dim_transform is selected.
        Change the one_dim_transform in the widget and update the options window
        :param parameter: the parameter tree node that change
        :return:
        """

        transform_name = parameter.value()

        self.widget.one_dim_transform = self._transforms_handler.get_transform(
            transform_name)

        self.reloadOptionsWidget(self.widget.one_dim_transform)

        self.update()

        self.graph(indexFrom=self.indexFrom, indexTo=self.indexTo)

    def connectLinesChanged(self, param, changes):

        param, change, data = changes[0]
        self.widget.lines = data

        self.graph(indexFrom=self.indexFrom, indexTo=self.indexTo)

    def changeTransformSettings(self, param, changes):

        for param, change, data in changes:
            path = self._transform_paramTree.childPath(param)
            if path is not None:
                childName = '.'.join(path)
            else:
                childName = param.name()

            self._transforms_handler.apply_settings_change(
                self.widget.one_dim_transform, (childName, change, data))

        self.graph(indexFrom=self.indexFrom, indexTo=self.indexTo)

    def changeYRangeSettings(self, param, changes):

        labels = self._transforms_handler.get_axis_labels(
            self.widget.one_dim_transform)
        for param, change, data in changes:
            path = self._transform_paramTree.childPath(param)
            if path is not None:
                childName = '.'.join(path)
            else:
                childName = param.name()

            if childName == u'Min':
                self.widget.minY = data

            if childName == u'Max':
                self.widget.maxY = data

        self.graph(indexFrom=self.indexFrom, indexTo=self.indexTo)

    # endregion

    def updateStatusBar(self, line):
        """
        Update the status bar window message.
        :param line: The (string) to show as message
        :return: None
        """
        self.statusbar.showMessage(line)
Exemplo n.º 4
0
class TwoDimensionalAnalisysWindow(QtGui.QMainWindow, Ui_TwoDimensionalWindow):
    """
    Window that provide an interface to create two dimensional
    graphs.
    """

    # region SIGNALS

    # Signal raised when an element is selected in the graph.
    # Raise the index of the selected element
    elementSelected = QtCore.Signal(int)

    # Signal raised when a selection of elements are manually classified.
    # raise the classification indexes of the elements as a list and
    # the ClassificationData for each one
    elementsClassified = QtCore.Signal(list, object)

    # endregion

    # region CONSTANTS

    # the width by default of the dock window with the options of the graph
    DOCK_WINDOW_WIDTH = 200

    # the color of the selected element brush
    SELECTED_ELEMENT_COLOR = "FFF"

    # endregion

    # region Initialize

    def __init__(self, parent, segmentManager):
        """
        Create a new window for two dimensional graphs
        :param parent: parent window if any
        :param columns: the columns of measured parameters
        :param data: Matrix of columns*number of elements.
        In data[i,j] is the medition of the parameter columns[i] in the j detected element
        :param classificationData:  the clasification data for the clasification
        :return:
        """
        super(TwoDimensionalAnalisysWindow, self).__init__(parent)
        self.setupUi(self)

        # initialization settings for the plot widget
        self.configure_widget()

        self.segmentManager = segmentManager

        # set the layout for the parameter tree widget
        lay1 = QtGui.QVBoxLayout()
        lay1.setMargin(0)

        self.ParamTree = None
        self.parameterTree = DuettoParameterTree()
        lay1.addWidget(self.parameterTree)

        self.dockWidgetContents.setLayout(lay1)
        self.parameterTree.setAutoScroll(True)
        self.parameterTree.setHeaderHidden(True)

        self.dockWidgetContents.setStyleSheet("background-color:#DDF")
        self.parameterTree.setMinimumWidth(self.DOCK_WINDOW_WIDTH)

        # scatter plot to graphs the elements
        self.scatter_plot = None

        # font to use in the axis of the graph
        self.font = QtGui.QFont()

        # index of the element currently selected in the widget if any
        # if no selection element then -1
        self.selectedElementIndex = -1

        self.create_parameter_tree(self.segmentManager.parameterColumnNames)

        self.show()

    def configure_widget(self):
        """
        Set a group of initial configuration on the visualization widget
        :return:
        """
        self.widget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
        self.widget.getPlotItem().showGrid(x=True, y=True)
        self.widget.setMouseEnabled(x=False, y=False)
        self.widget.getPlotItem().hideButtons()
        self.widget.setMenuEnabled(False)
        self.widget.enableAutoRange()

        self.widget.addAction(self.actionMark_Selected_Elements_As)
        self.widget.addAction(self.actionHide_Show_Settings)
        self.widget.addAction(self.actionSaveGraphImage)

    def create_parameter_tree(self, parameterColumnNames):
        """
        Create the parameter tree with the visual options according to the
        measured parameters.
        :param parameterColumnNames: the names of the measured parameters
        :return:
        """
        if len(parameterColumnNames) == 0:
            return

        # the X axis possible params names
        x_axis = [unicode(x) for x in parameterColumnNames]

        # get two initial random parameters to visualize in x and y axis
        x, y = random.randint(0,
                              len(x_axis) / 2), random.randint(
                                  len(x_axis) / 2,
                                  len(x_axis) - 1)

        params = [
            {
                u'name':
                unicode(self.tr(u'X Axis Parameter Settings')),
                u'type':
                u'group',
                u'children': [{
                    u'name':
                    unicode(self.tr(u'X Axis')),
                    u'type':
                    u'list',
                    u'value':
                    x,
                    # the possible values to select for graph in the X axis (name,index)
                    u'default':
                    x,
                    u'values': [(name, i) for i, name in enumerate(x_axis)]
                }]
            },
            {
                u'name':
                unicode(self.tr(u'Y Axis Parameter Settings')),
                u'type':
                u'group',
                u'children': [{
                    u'name':
                    unicode(self.tr(u'Y Axis')),
                    u'type':
                    u'list',
                    u'value':
                    y,
                    # the possible values to select for graph in the Y axis (name,index)
                    u'default':
                    y,
                    u'values': [(name, i) for i, name in enumerate(x_axis)]
                }]
            },
            {
                u'name': unicode(self.tr(u'Color')),
                u'type': u'color',
                u'value': "00F"
            },
            {
                u'name': unicode(self.tr(u'Figures Size')),
                u'type': u'int',
                u'value': 15
            },
            {
                u'name':
                unicode(self.tr(u'Figures Shape')),
                u'type':
                u'list',
                u'value':
                "o",
                u'default':
                "o",
                u'values': [("Circle", "o"), ("Square", "s"),
                            ("Triangle", "t"), ("Diamond", "d"), ("Plus", "+")]
            },
            {
                u'name': unicode(self.tr(u'Change Font')),
                u'type': u'action'
            },
            {
                u'name': unicode(self.tr(u'Save Graph as Image')),
                u'type': u'action'
            }
        ]

        self.ParamTree = Parameter.create(name=u'params',
                                          type=u'group',
                                          children=params)
        self.parameterTree.setParameters(self.ParamTree, showTop=False)

        self.ParamTree.sigTreeStateChanged.connect(self.plot)
        self.ParamTree.param(unicode(
            self.tr(u'Save Graph as Image'))).sigActivated.connect(
                self.on_actionSaveGraphImage_triggered)
        self.ParamTree.param(unicode(
            self.tr(u'Change Font'))).sigActivated.connect(self.changeFont)

        # visualize the changes
        self.plot()

    # endregion

    # region Graph Managing

    def load_data(self, segment_manager):
        """
        Load a new detection data. Update the graph and the internal variables
        from the segment manager.
        :return:
        """
        self.deselect_element()

        # removes the old combo with the old parameters
        # the x axis old parameters
        self.ParamTree.param(unicode(
            self.tr(u'X Axis Parameter Settings'))).removeChild(
                self.ParamTree.param(
                    unicode(self.tr(u'X Axis Parameter Settings'))).param(
                        unicode(self.tr(u'X Axis'))))

        # the y axis old parameters
        self.ParamTree.param(unicode(
            self.tr(u'Y Axis Parameter Settings'))).removeChild(
                self.ParamTree.param(
                    unicode(self.tr(u'Y Axis Parameter Settings'))).param(
                        unicode(self.tr(u'Y Axis'))))

        # create the new combo for the new parameters
        # the x axis new parameters
        self.ParamTree.param(unicode(
            self.tr(u'X Axis Parameter Settings'))).addChild(
                Parameter.create(
                    **{
                        u'name':
                        unicode(self.tr(u'X Axis')),
                        u'type':
                        u'list',
                        u'value':
                        0,
                        u'default':
                        0,
                        u'values': [(name, i) for i, name in enumerate(
                            segment_manager.parameterColumnNames)]
                    }))

        # the y axis new parameters
        self.ParamTree.param(unicode(
            self.tr(u'Y Axis Parameter Settings'))).addChild(
                Parameter.create(
                    **{
                        u'name':
                        unicode(self.tr(u'Y Axis')),
                        u'type':
                        u'list',
                        u'value':
                        0,
                        u'default':
                        0,
                        u'values': [(name, i) for i, name in enumerate(
                            segment_manager.parameterColumnNames)]
                    }))

        self.plot()

    def update_data(self, segmentManager):
        """
        Update the data from the segment manager where a change is made
        on the amount of elements(someone(s) is(are) deleted(added)) but the parameters measured
        remains equals.
        (If the change is made on parameters must call loadData)
        :param segmentManager:
        :return:
        """
        self.segmentManager = segmentManager
        self.plot()

    def changeFont(self):
        """
        Change the font of the axis in the plot widget.
        """
        self.font, ok = QtGui.QFontDialog.getFont(self.font)
        if ok:
            self.widget.setAxisFont("bottom", self.font)
            self.widget.setAxisFont("left", self.font)

    def plot(self):
        """
        Plot the two dimensional graph with the options settings defined by user.
        :return:
        """
        self.widget.clear()

        # get the indexes of the two params X and Y for each axis values to graph
        x_axis_index = self.ParamTree.param(
            unicode(self.tr(u'X Axis Parameter Settings'))).param(
                unicode(self.tr(u'X Axis'))).value()

        y_axis_index = self.ParamTree.param(
            unicode(self.tr(u'Y Axis Parameter Settings'))).param(
                unicode(self.tr(u'Y Axis'))).value()

        # get the visual options of the graph
        color = self.ParamTree.param(unicode(self.tr(u'Color'))).value()
        shape = self.ParamTree.param(unicode(
            self.tr(u'Figures Shape'))).value()
        fig_size = self.ParamTree.param(unicode(
            self.tr(u'Figures Size'))).value()

        # get the values x,y of each element according to the measured parameter selected in each axis
        x_cords = [
            e[x_axis_index] for e in self.segmentManager.measuredParameters
        ]
        y_cords = [
            e[y_axis_index] for e in self.segmentManager.measuredParameters
        ]

        xmin, xmax = min(x_cords), max(x_cords)
        ymin, ymax = min(y_cords), max(y_cords)

        # space in x and y axis for center the visible elements and set the
        # visible range a little more bigger than just the area that enclose them
        xshift = (xmax - xmin) * 0.15  # 15 % for every side left and rigth
        yshift = (ymax - ymin) * 0.15  # 15 % up and down

        # create the scatter plot with the values
        self.scatter_plot = pg.ScatterPlotItem(x=x_cords,
                                               y=y_cords,
                                               data=numpy.arange(len(x_cords)),
                                               size=fig_size,
                                               symbol=shape,
                                               brush=(pg.mkBrush(color)))

        # connect the signals for selection item on the plot
        self.scatter_plot.sigClicked.connect(self.elementFigureClicked)

        elems = self.scatter_plot.points()

        # draw the selected element with a different brush
        if 0 <= self.selectedElementIndex < len(elems):
            elems[self.selectedElementIndex].setBrush(
                pg.mkBrush(self.SELECTED_ELEMENT_COLOR))

        # update font size in axis labels
        text_size = {
            'color': '#FFF',
            'font-size': unicode(self.font.pointSize()) + 'pt'
        }
        self.widget.setAxisLabel(
            u"bottom",
            unicode(self.segmentManager.parameterColumnNames[x_axis_index]),
            **text_size)

        self.widget.setAxisLabel(
            u"left",
            unicode(self.segmentManager.parameterColumnNames[y_axis_index]),
            **text_size)

        # add the plot to the widget
        self.widget.addItem(self.scatter_plot)

        # set range to the visible region of values
        self.widget.setRange(xRange=(xmin - xshift, xmax + xshift),
                             yRange=(ymin - yshift, ymax + yshift))

        # clear the selection rectangle
        self.widget.removeSelectionRect()
        self.widget.addSelectionRect()

    # endregion

    # region Elements Selection

    def select_element(self, index):
        """
        Select the element at index 'index' in the graph.
        If index is outside of the elements count range nothing is do it.
        :param index: The index of the element to select.
        :return:
        """
        if self.scatter_plot is None or self.selectedElementIndex == index:
            return

        # get the current elements on the graph
        elems = self.scatter_plot.points()
        if not 0 <= index < len(elems):
            return

        # get the color of the not selected elements
        color = self.ParamTree.param(unicode(self.tr(u'Color'))).value()

        element_to_select = elems[index]
        element_to_select.setBrush(pg.mkBrush(self.SELECTED_ELEMENT_COLOR))

        # update the old selected element to unselected (if any)
        if self.selectedElementIndex != -1:
            elems[self.selectedElementIndex].setBrush(pg.mkBrush(color))

        # update the state variable of last selected element
        self.selectedElementIndex = index

    def deselect_element(self):
        """
        Deselect the element currently selected (if any) on the graph.
        :return:
        """
        if self.scatter_plot is None or self.selectedElementIndex < 0:
            return

        # get the color of the normal element figures on the graph
        color = self.ParamTree.param(unicode(self.tr(u'Color'))).value()

        # set the normal brush color to the element selected
        self.scatter_plot.points()[self.selectedElementIndex].setBrush(
            pg.mkBrush(color))

        self.selectedElementIndex = -1

    def elementFigureClicked(self, x, y):
        """
        Method that listen to the event of click an element on the scatter plot graph.
        :param x:
        :param y:
        :return:
        """
        self.select_element(y[0].data())
        self.elementSelected.emit(y[0].data())

    # endregion

    def load_workspace(self, workspace):
        """
        Update the visual theme of the window with the values from
        the application.
        :param theme: The visual theme currently used in the application.
        :return:
        """
        self.widget.setBackground(
            workspace.workTheme.oscillogramTheme.background_color)

        self.widget.showGrid(x=workspace.workTheme.oscillogramTheme.gridX,
                             y=workspace.workTheme.oscillogramTheme.gridY)

    @pyqtSlot()
    def on_actionHide_Show_Settings_triggered(self):
        """
        Switch the visibility of the settings window
        :return:
        """
        visibility = self.dockGraphsOptions.isVisible()
        self.dockGraphsOptions.setVisible(not visibility)

        if not visibility:
            # if was previously invisible
            self.dockGraphsOptions.setFloating(False)

    @pyqtSlot()
    def on_actionSaveGraphImage_triggered(self):
        """
        Save the widget graph as image
        :return:
        """
        fname = unicode(
            QFileDialog.getSaveFileName(
                self, self.tr(u"Save two dimensional graph as an Image"),
                u"two-dim-graph-Duetto-Image", u"*.jpg"))
        if fname:
            save_image(self.widget, fname)

    @pyqtSlot()
    def on_actionMark_Selected_Elements_As_triggered(self):
        """
        Make the manual classification of elements on the graph.
        @return: the indexes of the manually classified selected elements in the graph.
        """
        if self.scatter_plot is None:
            return []

        rect = self.widget.ElementSelectionRect.rect()

        # get the rect bounds
        x1, y1 = rect.x(), rect.y()
        x2, y2 = x1 + rect.width(), y1 + rect.height()

        # the width and height could be negatives
        x1, x2, y1, y2 = min(x1, x2), max(x1, x2), min(y1, y2), max(y1, y2)

        # get the elements that are on the range marked by the rectangle
        selected_elements = [
            x.data() for x in self.scatter_plot.points()
            if x1 <= x.pos().x() <= x2 and y1 <= x.pos().y() <= y2
        ]

        if len(selected_elements) == 0:
            QtGui.QMessageBox.warning(
                QtGui.QMessageBox(), self.tr(u"Warning"),
                self.tr(u"There is no element selected."))
            return

        # get the taxonomic identification
        classification_dialog = ManualClassificationDialog()
        classification_dialog.exec_()

        self.elementsClassified.emit(
            selected_elements, classification_dialog.get_classification())