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()
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()
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)
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())