class BioInfo(estimhab_GUI.StatModUseful): """ This class contains the tab with the biological information (the curves of preference). It inherites from StatModUseful. StatModuseful is a QWidget, with some practical signal (send_log and show_fig) and some functions to find path_im and path_bio (the path where to save image) and to manage lists. """ get_list_merge = pyqtSignal() """ A Pyqtsignal which indicates to chronice_GUI.py that the merge list should be changed. In Main_Windows.py, the new list of merge file is found and send to the ChonicleGui class. """ def __init__(self, path_prj, name_prj, lang='French'): super().__init__() self.lang = lang self.path_prj = path_prj self.name_prj = name_prj self.imfish = '' self.keep_data = None self.path_im_bio = 'biology/figure_pref/' # self.path_bio is defined in StatModUseful. self.data_fish = [] # all data concerning the fish # attribute from the xml which the user can search the database # the name should refect the xml attribute or bio_info.load_xml_name() should be changed # can be changed but with caution # coorect here for new language by adding an attribute in the form "langue"_common_name # stage have to be the first attribute ! self.attribute_acc = [ 'Stage', 'French_common_name', 'English_common_name', 'Code_ONEMA', 'Code_Sandre', 'LatinName', 'CdBiologicalModel' ] self.all_run_choice = [ self.tr('Coarser Substrate'), self.tr('Dominant Substrate'), self.tr('By Percentage'), self.tr('Neglect Substrate') ] self.hdf5_merge = [ ] # the list with the name and path of the hdf5 file self.text_ini = [] # the text with the tooltip #self.name_database = 'pref_bio.db' self.timer = QTimer() self.timer.setInterval(1000) self.running_time = 0 self.timer.timeout.connect(self.show_image_hab) self.plot_new = False self.tooltip = [ ] # the list with tooltip of merge file (useful for chronicle_GUI.py) self.ind_current = None self.init_iu() def init_iu(self): """ Used in the initialization by __init__() """ # the available merged data l0 = QLabel(self.tr('<b> Substrate and hydraulic data </b>')) self.m_all = QComboBox() # create lists with the possible fishes # right buttons for both QListWidget managed in the MainWindows class l1 = QLabel(self.tr('<b> Available Fish and Guild </b>')) l2 = QLabel(self.tr('<b> Selected Fish and Guild </b>')) self.list_f.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list_s.setSelectionMode(QAbstractItemView.ExtendedSelection) # show information about the fish self.list_f.itemClicked.connect(self.show_info_fish_avai) self.list_s.itemClicked.connect(self.show_info_fish_sel) self.list_f.itemActivated.connect(self.show_info_fish_avai) self.list_s.itemActivated.connect(self.show_info_fish_sel) # shwo info if movement of the arrow key self.list_f.itemSelectionChanged.connect(self.show_info_fish) self.list_s.itemSelectionChanged.connect( lambda: self.show_info_fish(True)) self.list_f.setMinimumWidth(280) # run habitat value self.l9 = QLabel(' <b> Options for the computation </b>') self.l9.setAlignment(Qt.AlignBottom) self.choice_run = QComboBox() self.choice_run.addItems(self.all_run_choice) self.runhab = QPushButton(self.tr('Compute Habitat Value')) self.runhab.setStyleSheet( "background-color: #47B5E6; color: white; font: bold") self.runhab.clicked.connect(self.run_habitat_value) self.butfig = QPushButton(self.tr("Create figure again")) self.butfig.clicked.connect(self.recreate_fig) if not self.keep_data: self.butfig.setDisabled(True) # find the path bio try: try: docxml = ET.parse( os.path.join(self.path_prj, self.name_prj + '.xml')) root = docxml.getroot() except IOError: # self.send_log.emit("Warning: the xml p file does not exist \n") return except ET.ParseError: self.send_log.emit("Warning: the xml file is not well-formed.\n") return pathbio_child = root.find(".//Path_Bio") if pathbio_child is not None: if os.path.isdir(pathbio_child.text): self.path_bio = pathbio_child.text # info on preference curve l4 = QLabel( self. tr('<b> Information on the suitability curve</b> (Right click on fish name)' )) l5 = QLabel(self.tr('Latin Name: ')) self.com_name = QLabel() l7 = QLabel(self.tr('ONEMA fish code: ')) self.fish_code = QLabel('') l8 = QLabel(self.tr('Description:')) self.descr = QLabel() self.pref_curve = QPushButton(self.tr('Show suitability curve')) self.pref_curve.clicked.connect(self.show_pref) # get a scollable area for the decription which might be long self.scroll = QScrollArea() self.scroll.setFrameStyle(QFrame.NoFrame) self.vbar = self.scroll.verticalScrollBar() self.descr.setWordWrap(True) self.descr.setMaximumSize(200, 210) self.descr.setAlignment(Qt.AlignTop) self.descr.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.descr.setTextFormat(Qt.RichText) self.scroll.setWidget(self.descr) # to have the Qlabel at the right size self.scroll.setWidgetResizable(True) self.scroll.setStyleSheet('background-color: white') self.vbar.setStyleSheet('background-color: lightGrey') # insist on white background color (for linux, mac) self.setAutoFillBackground(True) p = self.palette() p.setColor(self.backgroundRole(), Qt.white) self.setPalette(p) # image fish self.pic = QLabel() # hydrosignature self.hs = QPushButton( self.tr('Show Measurement Conditions (Hydrosignature)')) self.hs.clicked.connect(self.show_hydrosignature) # fill in list of fish sys.stdout = self.mystdout = StringIO() self.data_fish = bio_info.load_xml_name(self.path_bio, self.attribute_acc) sys.stdout = sys.__stdout__ self.send_err_log() # order data fish by alphabetical order on the first column ind = self.data_fish[:, 0].argsort() self.data_fish = self.data_fish[ind, :] self.list_f.addItems(self.data_fish[:, 0]) # erase fish selection self.butdel = QPushButton(self.tr("Erase All Selection")) self.butdel.clicked.connect(self.remove_all_fish) # fish selected fish self.add_sel_fish() if self.list_s.count() == 0: self.runhab.setDisabled(True) self.runhab.setStyleSheet("background-color: #47B5E6") else: self.runhab.setStyleSheet( "background-color: #47B5E6; color: white; font: bold") # search possibility l3 = QLabel(self.tr('<b> Search biological models </b>')) self.keys = QComboBox() self.keys.addItems(self.attribute_acc[:-1]) self.keys.currentIndexChanged.connect(self.get_autocompletion) l02 = QLabel('is equal to') l02.setAlignment(Qt.AlignCenter) self.cond1 = QLineEdit() self.cond1.returnPressed.connect(self.next_completion) #self.cond1.returnPressed.connect(self.select_fish) self.bs = QPushButton(self.tr('Select suitability curve')) self.bs.clicked.connect(self.select_fish) # add auto-completion self.completer = QCompleter() self.model = QStringListModel() self.completer.setModel(self.model) self.cond1.setCompleter(self.completer) self.get_autocompletion() # fill hdf5 list self.update_merge_list() # layout self.layout4 = QGridLayout() self.layout4.addWidget(l0, 0, 0) self.layout4.addWidget(self.m_all, 0, 1, 1, 2) self.layout4.addWidget(l1, 2, 0) self.layout4.addWidget(l2, 2, 2) self.layout4.addWidget(self.list_f, 3, 0, 3, 2) self.layout4.addWidget(self.list_s, 3, 2, 3, 1) self.layout4.addWidget(l4, 6, 0, 1, 3) self.layout4.addWidget(l5, 7, 0) self.layout4.addWidget(self.com_name, 7, 1) self.layout4.addWidget(l7, 8, 0) self.layout4.addWidget(self.fish_code, 8, 1) self.layout4.addWidget(l8, 9, 0) self.layout4.addWidget(self.scroll, 9, 1, 3, 2) # in fact self.descr is in self.scoll self.layout4.addWidget(self.pic, 11, 0) self.layout4.addWidget(self.l9, 3, 3) self.layout4.addWidget(self.choice_run, 4, 3) self.layout4.addWidget(self.runhab, 5, 3) self.layout4.addWidget(self.butfig, 6, 3) self.layout4.addWidget(self.pref_curve, 9, 3) self.layout4.addWidget(self.hs, 10, 3) self.layout4.addWidget(self.butdel, 2, 3) self.layout4.addWidget(l3, 12, 0) self.layout4.addWidget(self.keys, 13, 0) self.layout4.addWidget(l02, 13, 1) self.layout4.addWidget(self.cond1, 13, 2) self.layout4.addWidget(self.bs, 13, 3) # self.layout4.addItem(spacer1, 0, 2) # self.layout4.addItem(spacer2, 3, 3) self.setLayout(self.layout4) def next_completion(self): """ A small function to use the enter key to select the fish with auto-completion. Adapted from https://stackoverflow.com/questions/9044001/qcompleter-and-tab-key It would be nice to make it work with tab also but it is quite complcated because the tab key is already used by PyQt to go from one windows to the next. """ index = self.completer.currentIndex() self.completer.popup().setCurrentIndex(index) start = self.completer.currentRow() if not self.completer.setCurrentRow(start + 1): self.completer.setCurrentRow(0) self.select_fish() def get_autocompletion(self): """ This function updates the auto-complexton model as a function of the QComboxBox next to it with support for upper and lower case. """ ind = self.keys.currentIndex() if ind == 0: string_all = list( set( list(self.data_fish[:, 1]) + list([item.lower() for item in self.data_fish[:, 1]]) + list([item.upper() for item in self.data_fish[:, 1]]))) elif ind == 1: string_all = list( set( list(self.data_fish[:, 3]) + [item.lower() for item in self.data_fish[:, 3]] + [item.upper() for item in self.data_fish[:, 3]])) elif ind == 2: string_all = list( set( list(self.data_fish[:, 4]) + [item.lower() for item in self.data_fish[:, 4]] + [item.upper() for item in self.data_fish[:, 4]])) elif ind == 3: string_all = list( set( list(self.data_fish[:, 5]) + [item.lower() for item in self.data_fish[:, 5]] + [item.upper() for item in self.data_fish[:, 5]])) elif ind == 4: string_all = list( set( list(self.data_fish[:, 6]) + [item.lower() for item in self.data_fish[:, 6]] + [item.upper() for item in self.data_fish[:, 6]])) elif ind == 5: string_all = list( set( list(self.data_fish[:, 7]) + [item.lower() for item in self.data_fish[:, 7]] + [item.upper() for item in self.data_fish[:, 7]])) else: string_all = '' self.model.setStringList(string_all) def show_info_fish(self, select=False): """ This function shows the useful information concerning the selected fish on the GUI. :param select:If False, the selected items comes from the QListWidgetcontaining the available fish. If True, the items comes the QListWidget with the selected fish """ # get the file if not select: i1 = self.list_f.currentItem( ) # show the info concerning the one selected fish if not i1: return self.ind_current = self.list_f.currentRow() else: found_it = False i1 = self.list_s.currentItem() if not i1: return for m in range(0, self.list_f.count()): self.list_f.setCurrentRow(m) if i1.text() == self.list_f.currentItem().text(): self.ind_current = m found_it = True break if not found_it: self.ind_current = None if i1 is None: return name_fish = i1.text() name_fish = name_fish.split(':')[0] i = np.where(self.data_fish[:, 7] == name_fish)[0] if len(i) > 0: xmlfile = os.path.join(self.path_bio, self.data_fish[i[0], 2]) else: return # open the file try: try: docxml = ET.parse(xmlfile) root = docxml.getroot() except IOError: print("Warning: the xml file does not exist \n") return except ET.ParseError: print("Warning: the xml file is not well-formed.\n") return # get the data code ONEMA # for the moment only one code alternativ possible data = root.find('.//CdAlternative') if data is not None: if data.attrib['OrgCdAlternative']: if data.attrib['OrgCdAlternative'] == 'ONEMA': self.fish_code.setText(data.text) # get the latin name data = root.find('.//LatinName') if data is not None: self.com_name.setText(data.text) # get the description data = root.findall('.//Description') if len(data) > 0: found = False for d in data: if d.attrib['Language'] == self.lang: self.descr.setText(d.text) found = True if not found: self.descr.setText(data[0].text) # get the image fish data = root.find('.//Image') if data is not None: self.imfish = os.path.join(os.getcwd(), self.path_im_bio, data.text) name_imhere = os.path.join(os.getcwd(), self.path_im_bio, data.text) if os.path.isfile(name_imhere): # use full ABSOLUTE path to the image, not relative self.pic.setPixmap( QPixmap(name_imhere).scaled(200, 90, Qt.KeepAspectRatio)) # 800 500 else: self.pic.clear() else: self.pic.clear() def show_info_fish_sel(self): """ This function shows the useful information concerning the already selected fish on the GUI and remove fish from the list of selected fish. This is what happens when the user click on the second QListWidget (the one called selected fish and guild). """ self.show_info_fish(True) self.remove_fish() # Enable the button if self.list_s.count() > 0: self.runhab.setEnabled(True) else: self.runhab.setEnabled(False) def show_info_fish_avai(self): """ This function shows the useful information concerning the available fish on the GUI and add the fish to the selected fish This is what happens when the user click on the first QListWidget (the one called available fish). """ self.show_info_fish(False) self.add_fish() if self.list_s.count() > 0: self.runhab.setEnabled(True) else: self.runhab.setEnabled(False) def show_hydrosignature(self): """ This function make the link with function in bio_info.py which allows to load and plot the data related to the hydrosignature. """ # get the file i = self.list_f.currentRow() xmlfile = os.path.join(self.path_bio, self.data_fish[i, 2]) # do the plot sys.stdout = self.mystdout = StringIO() bio_info.plot_hydrosignature(xmlfile) sys.stdout = sys.__stdout__ self.send_err_log() # show the plot self.show_fig.emit() def select_fish(self): """ This function selects the fish which corresponds at the chosen criteria by the user. The type of criteria is given in the list self.keys and the criteria is given in self.cond1. The condition should exactly match the criteria. Signs such as * do not work. """ # get item s to be selected i = self.keys.currentIndex() # item type cond = self.cond1.text() if i == 0: i = -1 # i +2=1 for the key called 'stage' which is on the second colum of self.data_type data_fish_here = [] for f in self.data_fish[:, i + 2]: data_fish_here.append(f.lower()) data_fish_here = np.array(data_fish_here) if cond.lower() in data_fish_here: inds = np.where(data_fish_here == cond.lower())[0] self.runhab.setEnabled(True) else: self.send_log.emit( self. tr('Warning: No suitability curve found for the last selection. \n' )) return # get the new selection for ind in inds: for i in range(0, self.list_f.count()): item = self.list_f.item(i) if item.text() == self.data_fish[ind, 0]: break self.list_f.setCurrentRow(i) # add the fish to the QListView self.add_fish() def update_merge_list(self): """ This function goes in the projet xml file and gets all available merged data. Usually, it is called by Substrate() (when finished to merge some data) or at the start of HABBY. We add a "tooltip" which indicates the orginal hydraulic and substrate files. """ xmlfile = os.path.join(self.path_prj, self.name_prj + '.xml') # open the file try: try: docxml = ET.parse(xmlfile) root = docxml.getroot() except IOError: self.send_log.emit( "Warning: the xml project file does not exist \n") return except ET.ParseError: self.send_log.emit( "Warning: the xml project file is not well-formed.\n") return self.m_all.clear() self.tooltip = [] self.hdf5_merge = [] # get filename files = root.findall('.//hdf5_mergedata') files = reversed(files) # get the newest first path_hdf5 = self.find_path_hdf5_est() # add it to the list if files is not None: for idx, f in enumerate(files): if os.path.isfile(os.path.join(path_hdf5, f.text)): [sub_ini, hydro_ini ] = load_hdf5.get_initial_files(path_hdf5, f.text) hydro_ini = os.path.basename(hydro_ini) textini = 'Hydraulic: ' + hydro_ini + '\nSubstrate :' + sub_ini if len(f.text) < 55: self.m_all.addItem(f.text) else: blob = f.text[:55] + '...' self.m_all.addItem(blob) self.m_all.setItemData(idx, textini, Qt.ToolTipRole) self.tooltip.append(textini) name = f.text self.hdf5_merge.append(name) else: self.send_log.emit( "Warning: One merge hdf5 file was not found by calc_hab \n" ) # a signal to indicates to Chronicle_GUI.py to update the merge file self.get_list_merge.emit() def show_pref(self): """ This function shows the image of the preference curve of the selected xml file. For this it calls, the functions read_pref and figure_pref of bio_info.py. Hence, this function justs makes the link between the GUI and the functions effectively doing the image. """ if self.ind_current is None: self.send_log.emit( "Warning: No fish selected to create suitability curves \n") return # get the file i = self.ind_current # show the info concerning the one selected fish xmlfile = os.path.join(self.path_bio, self.data_fish[i, 2]) # open the pref sys.stdout = self.mystdout = StringIO() [h_all, vel_all, sub_all, code_fish, name_fish, stages] = bio_info.read_pref(xmlfile) sys.stdout = sys.__stdout__ self.send_err_log() # plot the pref fig_dict = output_fig_GUI.load_fig_option(self.path_prj, self.name_prj) bio_info.figure_pref(h_all, vel_all, sub_all, code_fish, name_fish, stages, fig_opt=fig_dict) # show the image self.show_fig.emit() def run_habitat_value(self): """ This function runs HABBY to get the habitat value based on the data in a "merged" hdf5 file and the chosen preference files. We should not add a comma in the name of the selected fish. """ # disable the button self.runhab.setDisabled(True) self.send_log.emit(" Calculating habitat value... \n") # get the figure options and the type of output to be created fig_dict = output_fig_GUI.load_fig_option(self.path_prj, self.name_prj) # erase the memory of the data for the figure self.keep_data = None # get the name of the xml biological file of the selected fish and the stages to be analyzed pref_list = [] stages_chosen = [] name_fish = [] name_fish_sh = [ ] # because max 10 characters in attribute table of shapefile name_fish_sel = '' # for the xml project file xmlfiles = [] for i in range(0, self.list_s.count()): fish_item = self.list_s.item(i) for j in range(0, self.list_f.count()): if self.data_fish[j][0] == fish_item.text(): pref_list.append(self.data_fish[j][2]) stages_chosen.append(self.data_fish[j][1]) if int(fig_dict['fish_name_type']) == 0: # latin name name_fish.append(self.data_fish[j][7]) elif int(fig_dict['fish_name_type'] ) == 1: # french common name name_fish.append(self.data_fish[j][3]) elif int(fig_dict['fish_name_type'] ) == 2: # english common name name_fish.append(self.data_fish[j][4]) elif int(fig_dict['fish_name_type']) == 3: # code onema name_fish.append(self.data_fish[j][5]) else: name_fish.append(self.data_fish[j][5]) name_fish_sh.append(self.data_fish[j][5][:3] + self.data_fish[j][1][:3]) name_fish_sel += fish_item.text() + ',' xmlfiles.append(self.data_fish[j][2]) # save the selected fish in the xml project file try: try: filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.xml') docxml = ET.parse(filename_path_pro) root = docxml.getroot() except IOError: print("Warning: the xml project file does not exist \n") return except ET.ParseError: print("Warning: the xml project file is not well-formed.\n") return hab_child = root.find(".//Habitat") if hab_child is None: blob = ET.SubElement(root, "Habitat") hab_child = root.find(".//Habitat") fish_child = root.find(".//Habitat/Fish_Selected") if fish_child is None: blob = ET.SubElement(hab_child, "Fish_Selected") fish_child = root.find(".//Habitat/Fish_Selected") fish_child.text = name_fish_sel[:-1] # last comma docxml.write(filename_path_pro) # get the name of the merged file path_hdf5 = self.find_path_hdf5_est() ind = self.m_all.currentIndex() if len(self.hdf5_merge) > 0: hdf5_file = self.hdf5_merge[ind] else: self.runhab.setDisabled(False) self.send_log.emit('Error: No merged hydraulic files available \n') return # get the path where to save the different outputs (function in estimhab_GUI.py) path_txt = self.find_path_text_est() path_im = self.find_path_im_est() path_shp = self.find_path_output_est("Path_Shape") path_para = self.find_path_output_est("Path_Paraview") # get the type of option choosen for the habitat calculation run_choice = self.choice_run.currentIndex() # only useful if we want to also show the 2d figure in the GUI self.hdf5_file = hdf5_file self.path_hdf5 = path_hdf5 path_im_bioa = os.path.join(os.getcwd(), self.path_im_bio) # send the calculation of habitat and the creation of output self.timer.start(1000) # to know when to show image self.q4 = Queue() self.p4 = Process(target=calcul_hab.calc_hab_and_output, args=(hdf5_file, path_hdf5, pref_list, stages_chosen, name_fish, name_fish_sh, run_choice, self.path_bio, path_txt, path_shp, path_para, path_im, self.q4, False, fig_dict, path_im_bioa, xmlfiles)) self.p4.start() # log self.send_log.emit("py file1='" + hdf5_file + "'") self.send_log.emit( "py path1= os.path.join(path_prj, 'fichier_hdf5')") self.send_log.emit("py pref_list= ['" + "', '".join(pref_list) + "']") self.send_log.emit("py stages= ['" + "', '".join(stages_chosen) + "']") self.send_log.emit("py type=" + str(run_choice)) self.send_log.emit("py name_fish1 = ['" + "', '".join(name_fish) + "']") self.send_log.emit("py name_fish2 = ['" + "', '".join(name_fish_sh) + "']") self.send_log.emit( "py calcul_hab.calc_hab_and_output(file1, path1 ,pref_list, stages, name_fish1, name_fish2, type, " "path_bio, path_prj, path_prj, path_prj, path_prj, [], True, [])") self.send_log.emit("restart RUN_HABITAT") self.send_log.emit("restart file1: " + hdf5_file) self.send_log.emit("restart list of preference file: " + ",".join(pref_list)) self.send_log.emit("restart stages chosen: " + ",".join(stages_chosen)) self.send_log.emit("restart type of calculation: " + str(run_choice)) def recreate_fig(self): """ This function use show_image_hab() to recreate the habitat figures shown before """ self.plot_new = True self.show_image_hab() def show_image_hab(self): """ This function is linked with the timer started in run_habitat_value. It is run regulary and check if the function on the second thread have finised created the figures. If yes, this function create the 1d figure for the HABBY GUI. """ # say in the Stauts bar that the processus is alive if self.p4.is_alive(): self.running_time += 1 # this is useful for GUI to update the running, should be logical with self.Timer() # get the langugage fig_dict = output_fig_GUI.load_fig_option(self.path_prj, self.name_prj) # send the message if fig_dict['language'] == str(1): # it is necssary to start this string with Process to see it in the Statusbar self.send_log.emit("Processus 'Habitat' fonctionne depuis " + str(self.running_time) + " sec") else: # it is necssary to start this string with Process to see it in the Statusbar self.send_log.emit( "Process 'Habitat' is alive and run since " + str(self.running_time) + " sec") # when the loading is finished if not self.q4.empty() or (self.keep_data is not None and self.plot_new): if self.keep_data is None: self.timer.stop() data_second = self.q4.get() self.mystdout = data_second[0] area_all = data_second[1] spu_all = data_second[2] name_fish = data_second[3] name_base = data_second[4] vh_all_t_sp = data_second[5] self.send_err_log() self.keep_data = data_second else: self.timer.stop() area_all = self.keep_data[1] spu_all = self.keep_data[2] name_fish = self.keep_data[3] name_base = self.keep_data[4] vh_all_t_sp = self.keep_data[5] # give the possibility of sending a new simulation self.runhab.setDisabled(False) if len(name_fish) > 0: if isinstance(name_fish[0], int): self.running_time = 0 self.send_log.emit("clear status bar") return # show one image (relatively quick to create) #sys.stdout = self.mystdout = StringIO() path_im = self.find_path_im_est() fig_dict = output_fig_GUI.load_fig_option(self.path_prj, self.name_prj) sim_name = load_hdf5.load_timestep_name(self.hdf5_file, self.path_hdf5) if fig_dict['erase_id'] == 'True': erase_id = True else: erase_id = False calcul_hab.save_hab_fig_spu(area_all, spu_all, name_fish, path_im, name_base, fig_dict, sim_name, erase_id) for t in fig_dict['time_step']: # if print last and first time step and one time step only, only print it once if t == -1 and len( vh_all_t_sp[0]) == 2 and 1 in fig_dict['time_step']: pass else: calcul_hab.save_vh_fig_2d(self.hdf5_file, self.path_hdf5, [vh_all_t_sp[0]], path_im, name_fish, name_base, fig_dict, [t], save_fig=False) # sys.stdout = sys.__stdout__ #self.send_err_log() # show figure self.show_fig.emit() # enable the button to call this function directly again to redo the figure self.butfig.setEnabled(True) # put the timer back to zero and clear status bar self.running_time = 0 self.send_log.emit("clear status bar") self.plot_new = False if not self.p4.is_alive(): # enable the button to call this functin directly again self.butfig.setEnabled(True) self.timer.stop() # put the timer back to zero self.running_time = 0 self.send_log.emit("clear status bar")