class AudioPedantik(QWidget): def __init__(self, parent=None): super().__init__(parent) self.title = "AudioPedantik" self.top = 100 self.left = 100 self.width = 700 self.height = 500 self.config = configparser.ConfigParser() self.config.read("config.ini") self.search_dir = "" self.dest_dir = "" self.audio_id3 = None self.results = [] self.artwork_bytes = None self.init_ui() def init_ui(self): # Main widgets declaration search_dir_button = QPushButton("Select search directory") dest_dir_button = QPushButton("Select destination directory") self.search_dir_edit = QLineEdit() self.search_dir_edit.setText(self.config["Last"]["search_dir"]) self.search_dir = self.config["Last"]["search_dir"] self.search_dir_edit.setReadOnly(True) self.dest_dir_edit = QLineEdit() self.dest_dir_edit.setText(self.config["Last"]["dest_dir"]) self.dest_dir = self.config["Last"]["dest_dir"] self.dest_dir_edit.setReadOnly(True) self.listbox = QListWidget() self.listbox.setMaximumWidth(250) self.combobox = QComboBox() # ID3 group widgets declarations file_name_label = QLabel("File name: ") self.artwork_id3_label = QLabel() self.artwork_id3_label.setPixmap(QPixmap("default.jpg")) artist_id3_label = QLabel("Artist: ") title_id3_label = QLabel("Title: ") genre_id3_label = QLabel("Genre: ") album_id3_label = QLabel("Album: ") release_year_id3_label = QLabel("Release year: ") track_no_id3_label = QLabel("Track number: ") self.file_name_edit = QLineEdit() self.artist_id3_edit = QLineEdit() self.artist_id3_edit.setReadOnly(True) self.title_id3_edit = QLineEdit() self.title_id3_edit.setReadOnly(True) self.genre_id3_edit = QLineEdit() self.genre_id3_edit.setReadOnly(True) self.album_id3_edit = QLineEdit() self.album_id3_edit.setReadOnly(True) self.release_year_id3_edit = QLineEdit() self.release_year_id3_edit.setReadOnly(True) self.track_no_id3_edit = QLineEdit() self.track_no_id3_edit.setReadOnly(True) # iTunes group widgets declarations search_phrase_label = QLabel("Search phrase: ") self.checkbox = QCheckBox("Don't overwrite artwork") self.artwork_itunes_label = QLabel() self.artwork_itunes_label.setPixmap(QPixmap("default.jpg")) artist_itunes_label = QLabel("Artist: ") title_itunes_label = QLabel("Title: ") genre_itunes_label = QLabel("Genre: ") genre_itunes_label = QLabel("Genre: ") album_itunes_label = QLabel("Album: ") release_year_itunes_label = QLabel("Release year: ") track_no_itunes_label = QLabel("Track number: ") self.search_phrase_edit = QLineEdit() self.artist_itunes_edit = QLineEdit() self.title_itunes_edit = QLineEdit() self.genre_itunes_edit = QLineEdit() self.album_itunes_edit = QLineEdit() self.release_year_itunes_edit = QLineEdit() self.track_no_itunes_edit = QLineEdit() load_artwork_button = QPushButton("Load custom artwork") self.save_button = QPushButton("Save") self.save_button.setMaximumWidth(50) self.save_button.setDisabled(True) # Layout main_layout = QGridLayout() main_layout.addWidget(search_dir_button, 0, 0) main_layout.addWidget(dest_dir_button, 1, 0) main_layout.addWidget(self.search_dir_edit, 0, 1) main_layout.addWidget(self.dest_dir_edit, 1, 1) main_layout.addWidget(self.listbox, 2, 0, 3, 1) main_layout.addWidget(self.combobox, 2, 1) # Group box for mp3 data id3_data_group_box = QGroupBox("MP3 data: ") id3_data_group_layout = QGridLayout() id3_data_group_layout.addWidget(file_name_label, 0, 0) id3_data_group_layout.addWidget(self.artwork_id3_label, 1, 0, 5, 1) id3_data_group_layout.addWidget(artist_id3_label, 1, 2) id3_data_group_layout.addWidget(title_id3_label, 2, 2) id3_data_group_layout.addWidget(genre_id3_label, 3, 2) id3_data_group_layout.addWidget(album_id3_label, 4, 2) id3_data_group_layout.addWidget(release_year_id3_label, 5, 2) id3_data_group_layout.addWidget(track_no_id3_label, 6, 2) id3_data_group_layout.addWidget(self.file_name_edit, 0, 2, 1, 2) id3_data_group_layout.addWidget(self.artist_id3_edit, 1, 3) id3_data_group_layout.addWidget(self.title_id3_edit, 2, 3) id3_data_group_layout.addWidget(self.genre_id3_edit, 3, 3) id3_data_group_layout.addWidget(self.album_id3_edit, 4, 3) id3_data_group_layout.addWidget(self.release_year_id3_edit, 5, 3) id3_data_group_layout.addWidget(self.track_no_id3_edit, 6, 3) id3_data_group_box.setLayout(id3_data_group_layout) main_layout.addWidget(id3_data_group_box, 3, 1) # Group box for iTunes data itunes_data_group_box = QGroupBox("iTunes data: ") itunes_data_group_layout = QGridLayout() itunes_data_group_layout.addWidget(search_phrase_label, 0, 0) itunes_data_group_layout.addWidget(self.artwork_itunes_label, 1, 0, 5, 1) itunes_data_group_layout.addWidget(self.checkbox, 6, 0) itunes_data_group_layout.addWidget(artist_itunes_label, 1, 2) itunes_data_group_layout.addWidget(title_itunes_label, 2, 2) itunes_data_group_layout.addWidget(genre_itunes_label, 3, 2) itunes_data_group_layout.addWidget(album_itunes_label, 4, 2) itunes_data_group_layout.addWidget(release_year_itunes_label, 5, 2) itunes_data_group_layout.addWidget(track_no_itunes_label, 6, 2) itunes_data_group_layout.addWidget(track_no_itunes_label, 6, 2) itunes_data_group_layout.addWidget(self.search_phrase_edit, 0, 2, 1, 2) itunes_data_group_layout.addWidget(self.artist_itunes_edit, 1, 3) itunes_data_group_layout.addWidget(self.title_itunes_edit, 2, 3) itunes_data_group_layout.addWidget(self.genre_itunes_edit, 3, 3) itunes_data_group_layout.addWidget(self.album_itunes_edit, 4, 3) itunes_data_group_layout.addWidget(self.release_year_itunes_edit, 5, 3) itunes_data_group_layout.addWidget(self.track_no_itunes_edit, 6, 3) itunes_data_group_layout.addWidget(load_artwork_button, 7, 0) itunes_data_group_layout.addWidget(self.save_button, 7, 3) itunes_data_group_box.setLayout(itunes_data_group_layout) main_layout.addWidget(itunes_data_group_box, 4, 1) self.setLayout(main_layout) # Bindings dest_dir_button.pressed.connect(self.choose_dest_dir) search_dir_button.pressed.connect(self.choose_search_dir) load_artwork_button.pressed.connect(self.load_custom_artwork) self.save_button.pressed.connect(self.save) self.search_phrase_edit.editingFinished.connect(self.search_itunes) self.combobox.currentIndexChanged.connect(self.combobox_selected) # Window settings self.setGeometry(self.top, self.left, self.width, self.height) self.setWindowTitle(self.title) self.show() self.refresh_listbox() self.save_button.setDisabled(False) def refresh_listbox(self): self.listbox.disconnect() self.listbox.clear() try: self.listbox.addItems([ file for file in os.listdir(self.search_dir) if file.endswith(".mp3") ]) except FileNotFoundError: # FIXME: pass self.listbox.currentItemChanged.connect(self.listbox_selected) def choose_search_dir(self): self.search_dir = QFileDialog.getExistingDirectory( self, "Select Search Directory") self.search_dir_edit.setText(self.search_dir) self.config["Last"]["search_dir"] = self.search_dir with open("config.ini", "w") as configfile: self.config.write(configfile) self.refresh_listbox() def choose_dest_dir(self): self.dest_dir = QFileDialog.getExistingDirectory( self, "Select destination directory") self.dest_dir_edit.setText(self.dest_dir) self.config["Last"]["dest_dir"] = self.dest_dir with open("config.ini", "w") as configfile: self.config.write(configfile) self.save_button.setDisabled(False) def listbox_selected(self): file_name = self.listbox.currentItem().text() self.search_phrase_edit.setText(file_name[:-4]) self.file_name_edit.setText(file_name) self.get_id3_tags(file_name) self.search_itunes() def get_id3_tags(self, file_name): try: self.audio_id3 = ID3(self.search_dir + "/" + file_name, v2_version=3) except _util.ID3NoHeaderError: file = File(self.search_dir + "/" + file_name) file.add_tags() file.save() self.audio_id3 = ID3(self.search_dir + "/" + file_name, v2_version=3) for tag in self.audio_id3: if tag.startswith("APIC") and (PictureType.COVER_FRONT == 3): image = QImage() image.loadFromData(self.audio_id3[tag].data) self.artwork_id3_label.setPixmap( QPixmap(image).scaled(150, 150)) break else: #TODO: default image should be stored as raw data self.artwork_id3_label.setPixmap(QPixmap("default.jpg")) if "TPE1" in self.audio_id3: self.artist_id3_edit.setText(str(self.audio_id3["TPE1"])) else: self.artist_id3_edit.clear() if "TIT2" in self.audio_id3: self.title_id3_edit.setText(str(self.audio_id3["TIT2"])) else: self.title_id3_edit.clear() if "TCON" in self.audio_id3: self.genre_id3_edit.setText(str(self.audio_id3["TCON"])) else: self.genre_id3_edit.clear() if "TALB" in self.audio_id3: self.album_id3_edit.setText(str(self.audio_id3["TALB"])) else: self.album_id3_edit.clear() if "TYER" in self.audio_id3: self.release_year_id3_edit.setText(str(self.audio_id3["TYER"])) else: self.release_year_id3_edit.clear() if "TRCK" in self.audio_id3: self.track_no_id3_edit.setText(str(self.audio_id3["TRCK"])) else: self.track_no_id3_edit.clear() def search_itunes(self): params = { "term": self.search_phrase_edit.text(), "media": "music", "limit": 10 } json_data = json.loads( urlopen("https://itunes.apple.com/search?" + urlencode(params)).read().decode('utf8')) self.combobox.clear() self.combobox.disconnect() self.results = [] for result in json_data["results"]: # Prepare icon image_data = urlopen(result["artworkUrl30"]).read() image = QPixmap() image.loadFromData(image_data) self.combobox.addItem( QIcon(image), result["artistName"] + " - " + result["trackName"]) self.results.append(result) self.combobox.currentIndexChanged.connect(self.combobox_selected) if len(self.results) > 0: self.combobox.setCurrentIndex(0) self.combobox_selected() else: self.artwork_itunes_label.setPixmap(QPixmap("default.jpg")) self.artwork_bytes = None self.artist_itunes_edit.clear() self.title_itunes_edit.clear() self.genre_itunes_edit.clear() self.album_itunes_edit.clear() self.release_year_itunes_edit.clear() def combobox_selected(self): current_combobox_index = self.combobox.currentIndex() artwork_url = self.results[current_combobox_index][ "artworkUrl30"].split("/") artwork_url[-1] = "600x600bb.jpg" artwork_url = "/".join(artwork_url) self.artwork_bytes = urlopen(artwork_url).read() artwork = QPixmap() artwork.loadFromData(self.artwork_bytes) self.artwork_itunes_label.setPixmap(artwork.scaled(150, 150)) self.artist_itunes_edit.setText( self.results[current_combobox_index]["artistName"]) self.title_itunes_edit.setText( self.results[current_combobox_index]["trackName"]) self.genre_itunes_edit.setText( self.results[current_combobox_index]["primaryGenreName"]) if "collectionName" in self.results[current_combobox_index]: self.album_itunes_edit.setText( self.results[current_combobox_index]["collectionName"]) self.release_year_itunes_edit.setText( self.results[current_combobox_index]["releaseDate"][:4]) if "trackNumber" in self.results[current_combobox_index]: self.track_no_itunes_edit.setText( str(self.results[current_combobox_index]["trackNumber"])) self.file_name_edit.setText( self.results[current_combobox_index]["artistName"] + " - " + self.results[current_combobox_index]["trackName"] + ".mp3") def load_custom_artwork(self): image_file_name, _ = QFileDialog.getOpenFileName( self, "Select Artwork File", None, "Images (*.png *.jpg)") with open(image_file_name, 'rb') as f: self.artwork_bytes = f.read() artwork = QPixmap() artwork.loadFromData(self.artwork_bytes) self.artwork_itunes_label.setPixmap(artwork.scaled(150, 150)) def save(self): audio_path = self.search_dir + "/" + self.listbox.currentItem().text() audio_save_path = self.dest_dir + "/" + self.file_name_edit.text() if not self.artwork_bytes == None: if not self.checkbox.isChecked(): found = 0 for tag in self.audio_id3: if tag.startswith("APIC") and (PictureType.COVER_FRONT == 3): self.audio_id3[tag].data = self.artwork_bytes break if not found: self.audio_id3.add( APIC(encoding=3, mime='image/jpeg', type=3, data=self.artwork_bytes)) self.audio_id3.add( TPE1(encoding=3, text=self.artist_itunes_edit.text())) self.audio_id3.add(TIT2(encoding=3, text=self.title_itunes_edit.text())) self.audio_id3.add(TCON(encoding=3, text=self.genre_itunes_edit.text())) self.audio_id3.add(TALB(encoding=3, text=self.album_itunes_edit.text())) self.audio_id3.add( TYER(encoding=3, text=self.release_year_itunes_edit.text())) self.audio_id3.add( TRCK(encoding=3, text=self.track_no_itunes_edit.text())) self.audio_id3.save(v2_version=3) shutil.move(audio_path, audio_save_path) self.refresh_listbox() self.listbox.setCurrentRow(0)
class AnalysisViewer(AnalysisPlotter, QWidget): """This class is a window that provides convenient viewing of a pws acquisition, analysis, and related images. It expands upon the functionality of `BigPlot` which handles ROIs but not analysis images.""" def __init__(self, metadata: pwsdt.Acquisition, analysisLoader: AnalysisPlotter.AnalysisResultsComboType, title: str, roiManager: ROIManager, parent=None, initialField=AnalysisPlotter.PlotFields.Thumbnail, flags=None): if flags is not None: QWidget.__init__(self, parent=parent, flags=flags) else: QWidget.__init__(self, parent=parent) AnalysisPlotter.__init__(self, metadata, analysisLoader, initialField=initialField) self.roiPlot = RoiPlot(metadata, metadata.getThumbnail(), roiManager=roiManager, parent=self) self.setWindowTitle(title) self.analysisCombo = QComboBox(self) self._populateFields() self.roiPlot.layout().itemAt(0).insertWidget( 0, self.analysisCombo) # This is sketchy, oh well. l = QVBoxLayout() l.setContentsMargins(0, 0, 0, 0) l.addWidget(self.roiPlot) self.setLayout(l) self.changeData(self.analysisField) def _populateFields(self): currField = self.analysisCombo.currentText() try: self.analysisCombo.disconnect() except: pass #Sometimes there is nothing to disconnect self.analysisCombo.clear() _ = self.PlotFields # Just doing this to make for less typing later. items = [_.Thumbnail] for i in [ _.MeanReflectance, _.RMS, _.AutoCorrelationSlope, _.RSquared, _.Ld ]: try: if hasattr( self.analysis.pws, i.value[1] ): # This will raise a key error if the analysis object exists but the requested item is not found items.append(i) except KeyError: pass for i in [_.RMS_t_squared, _.Diffusion, _.DynamicsReflectance]: try: if hasattr( self.analysis.dyn, i.value[1] ): # This will raise a key error if the analysis object exists but the requested item is not found items.append(i) except KeyError: pass if self.analysis.pws is not None: if 'reflectance' in self.analysis.pws.file.keys( ): #This is the normalized 3d data cube. needed to generate the opd. items.append(_.OpdPeak) items.append(_.SingleWavelength) for i in range(len(self.acq.fluorescence)): items.append(self._fluorescencePlotFields[i]) self.analysisCombo.addItems([i.name for i in items]) if currField in [i.name for i in items]: self.analysisCombo.setCurrentText( currField) #Maintain the same field if possible. self.analysisCombo.currentTextChanged.connect( self.changeDataByName ) # If this line comes before the analysisCombo.addItems line then it will get triggered when adding items. def changeDataByName(self, field: str): field = [ enumField for enumField in self.PlotFields if enumField.name == field ][0] #This function recieves the name of the Enum item. we want to get the enum item itself. self.changeData(field) def changeData(self, field: AnalysisPlotter.PlotFields): """Change which image associated with the PWS acquisition we want to view.""" super().changeData(field) if self.analysisCombo.currentText() != field.name: self.analysisCombo.setCurrentText(field.name) self.roiPlot.setImageData(self.data) def setMetadata(self, md: Acquisition, analysis: Optional[ConglomerateAnalysisResults] = None): """Change this widget to display data for a different acquisition and optionally an analysis.""" try: super().setMetadata(md, analysis) except ValueError: # Trying to set new metadata may result in an error if the new analysis/metadata can't plot the currently set analysisField self.changeData( self.PlotFields.Thumbnail ) # revert back to thumbnail which should always be possible super().setMetadata(md, analysis) self.roiPlot.setMetadata(md) self._populateFields()
class PlotControlWidget(QWidget): """Creates a simple about dialog. The about dialog contains general information about the application and shows the copyright notice. That's why the class has no attributes or return values. """ # signals chartViewChanged = pyqtSignal(int) lengthEnabled = pyqtSignal(bool) countEnabled = pyqtSignal(bool) lengthChanged = pyqtSignal(int) countChanged = pyqtSignal(int) lengthFinished = pyqtSignal() def __init__(self): super().__init__() self._COMBOBOX_ITEM_LIST = (QtCore.QT_TRANSLATE_NOOP("PlotControlWidget", "Area chart"), QtCore.QT_TRANSLATE_NOOP("PlotControlWidget", "Line chart")) # create labels and input fields self.viewLabel = QLabel() self.viewInput = QComboBox(self) self.lengthCheckBox = QCheckBox() self.lengthCheckBox.setCheckState(Qt.Checked) self.lengthCheckBox.setDisabled(True) self.lengthInput = QSpinBox(self, maximum=99999) self.lengthInput.setSuffix(" Lfm.") self.lengthInput.setDisabled(True) self.countCheckBox = QCheckBox() self.countCheckBox.setCheckState(Qt.Checked) self.countCheckBox.setDisabled(True) self.countInput = QSpinBox(self, maximum=99999) self.countInput.setSuffix(" St.") self.countInput.setDisabled(True) self.countCalculator = QPushButton() self.countCalculator.setDisabled(True) lineFrame = QFrame(frameShadow=QFrame.Sunken, frameShape=QFrame.VLine) # create layout layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.viewLabel) layout.addWidget(self.viewInput) layout.addStretch() layout.addWidget(self.lengthCheckBox) layout.addWidget(self.lengthInput) layout.addWidget(lineFrame) layout.addWidget(self.countCheckBox) layout.addWidget(self.countInput) layout.addWidget(self.countCalculator) # connect signals self.viewInput.currentIndexChanged.connect(self.chartViewChanged) self.lengthCheckBox.stateChanged.connect(self.enableLengthInput) self.countCheckBox.stateChanged.connect(self.enableCountInput) self.lengthInput.valueChanged.connect(self.lengthChanged) self.lengthInput.editingFinished.connect(self.lengthFinished) self.countInput.valueChanged.connect(self.countChanged) self.countCalculator.clicked.connect(self.countCalculation) # translate the graphical user interface self.retranslateUi() def countCalculation(self): dialog = PlantCountDialog() if dialog.exec() == QDialog.Accepted: self.countInput.setValue(dialog.value) def enableLengthInput(self, state): if state == Qt.Checked: self.lengthInput.setEnabled(True) self.lengthEnabled.emit(True) else: self.lengthInput.setDisabled(True) self.lengthEnabled.emit(False) def enableCountInput(self, state): if state == Qt.Checked: self.countInput.setEnabled(True) self.countCalculator.setEnabled(True) self.countEnabled.emit(True) else: self.countInput.setDisabled(True) self.countCalculator.setDisabled(True) self.countEnabled.emit(False) def setLength(self, length): self.lengthCheckBox.setEnabled(True) self.lengthInput.setEnabled(True) self.lengthInput.setValue(length) def setCount(self, count): self.countCheckBox.setEnabled(True) self.countInput.setEnabled(True) self.countCalculator.setEnabled(True) self.countInput.setValue(count) def reset(self): # reset the view input field self.viewInput.disconnect() self.viewInput.setCurrentIndex(0) #self.viewInput.setDisabled(True) # reset length input field self.lengthCheckBox.setCheckState(Qt.Checked) #self.lengthCheckBox.setDisabled(True) self.lengthInput.setValue(0) #self.lengthInput.setDisabled(True) # reset count check box and count input field self.countCheckBox.setCheckState(Qt.Checked) #self.countCheckBox.setDisabled(True) self.countInput.setValue(0) #self.countInput.setDisabled(True) #self.countCalculator.setDisabled(True) def retranslateUi(self): # input fields self.viewLabel.setText(QApplication.translate("PlotControlWidget", "Chart view") + ":") # Diagrammansicht self.viewInput.clear() for item in self._COMBOBOX_ITEM_LIST: self.viewInput.addItem(QApplication.translate("PlotControlWidget", item)) self.lengthCheckBox.setText(QApplication.translate("PlotControlWidget", "Fence length") + ":") # Zaunlänge self.countCheckBox.setText(QApplication.translate("PlotControlWidget", "Number of plants") + ":") # Anzahl der Pflanzen self.countCalculator.setText(QApplication.translate("PlotControlWidget", "Calculation help")) # Umrechnungshilfe
class ModelInfoGroup(QGroupBox): """ This class is a subclass of class QGroupBox. """ send_log = pyqtSignal(str, name='send_log') drop_hydro = pyqtSignal() drop_merge = pyqtSignal() def __init__(self, path_prj, name_prj, send_log): super().__init__() self.path_prj = path_prj self.name_prj = name_prj self.send_log = send_log self.path_last_file_loaded = self.path_prj self.hydraulic_model_information = HydraulicModelInformation() self.p = Process(target=None) self.pathfile = None self.namefile = None self.name_hdf5 = None self.model_index = None self.drop_hydro.connect(lambda: self.name_last_hdf5(self.model_type)) self.init_ui() def init_ui(self): self.result_file_title_label = QLabel(self.tr('Result file')) self.input_file_combobox = QComboBox() self.select_file_button = QPushButton("...") self.select_file_button.setToolTip(self.tr("Select file(s)")) self.select_file_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) widget_height = self.input_file_combobox.minimumSizeHint().height() self.select_file_button.setFixedHeight(widget_height) self.select_file_button.setFixedWidth(widget_height) self.select_file_button.clicked.connect( self.select_file_and_show_informations_dialog) # selection_layout self.selection_layout = QHBoxLayout() self.selection_layout.addWidget(self.input_file_combobox) self.selection_layout.addWidget(self.select_file_button) # reach reach_name_title_label = QLabel(self.tr('Reach name')) self.reach_name_combobox = QComboBox() # unit list unit_title_label = QLabel(self.tr('Unit name')) self.units_QListWidget = QListWidgetClipboard() self.units_QListWidget.setSelectionMode( QAbstractItemView.ExtendedSelection) # unit type units_name_title_label = QLabel(self.tr('Type')) self.units_name_label = QLabel(self.tr('unknown')) # unit number units_number_title_label = QLabel(self.tr('Number')) self.unit_number_label = QLabel(self.tr('unknown')) # unit_layout unit_layout = QGridLayout() unit_layout.addWidget(self.units_QListWidget, 0, 0, 4, 1) unit_layout.addWidget(units_name_title_label, 0, 1, Qt.AlignBottom) unit_layout.addWidget(self.units_name_label, 1, 1, Qt.AlignTop) unit_layout.addWidget(units_number_title_label, 2, 1, Qt.AlignBottom) unit_layout.addWidget(self.unit_number_label, 3, 1, Qt.AlignTop) # usefull_mesh_variables usefull_mesh_variable_label_title = QLabel(self.tr('Mesh data')) # usefull_mesh_variable_label_title.setFixedHeight(widget_height) self.usefull_mesh_variable_label = QLabel(self.tr('unknown')) # usefull_node_variables usefull_node_variable_label_title = QLabel(self.tr('Node data')) # usefull_node_variable_label_title.setFixedHeight(widget_height) self.usefull_node_variable_label = QLabel(self.tr('unknown')) # LAMMI substrate sub_radio_group = QButtonGroup(self) classification_code_title_label = QLabel( self.tr('Sub classification code')) classification_code_title_label.setToolTip( self.tr("LAMMI data substrate classification code")) self.sub_classification_code_edf_radio = QRadioButton("EDF") sub_radio_group.addButton(self.sub_classification_code_edf_radio) self.sub_classification_code_edf_radio.setToolTip( self.tr("8 EDF classes")) self.sub_classification_code_cemagref_radio = QRadioButton("Cemagref") sub_radio_group.addButton(self.sub_classification_code_cemagref_radio) self.sub_classification_code_cemagref_radio.setToolTip( self.tr("8 Cemagref classes")) if user_preferences.data["lammi_sub_classification_code"] == "EDF": self.sub_classification_code_edf_radio.setChecked(True) elif user_preferences.data[ "lammi_sub_classification_code"] == "Cemagref": self.sub_classification_code_cemagref_radio.setChecked(True) else: self.send_log.emit( self. tr("Warning: lammi_sub_classification_code not recognized in user preferences." )) print( "Warning: lammi_sub_classification_code not recognized in user preferences." ) self.sub_classification_code_cemagref_radio.setChecked(True) self.sub_classification_code_edf_radio.toggled.connect( self.lammi_choice_changed) sub_radio_layout = QHBoxLayout() sub_radio_layout.addWidget(self.sub_classification_code_edf_radio) sub_radio_layout.addWidget(self.sub_classification_code_cemagref_radio) sub_radio_layout.addStretch() # LAMMI equation equation_radio_group = QButtonGroup(self) equation_title_label = QLabel(self.tr('Calculation method')) equation_title_label.setToolTip( self.tr("LAMMI hydraulic data calculation method")) self.equation_fe_radio = QRadioButton(self.tr("Finite Element Method")) equation_radio_group.addButton(self.equation_fe_radio) self.equation_fe_radio.setToolTip( self.tr("Vertical 1D hydraulic profile data set to node.")) self.equation_fv_radio = QRadioButton(self.tr("Finite Volume Method")) equation_radio_group.addButton(self.equation_fv_radio) self.equation_fv_radio.setToolTip( self.tr("Vertical 1D hydraulic profile data set to mesh.")) if user_preferences.data["lammi_calculation_method"] == "FEM": self.equation_fe_radio.setChecked(True) elif user_preferences.data["lammi_calculation_method"] == "FVM": self.equation_fv_radio.setChecked(True) else: self.send_log.emit( self. tr("Warning: lammi_calculation_method not recognized in user preferences." )) print( "Warning: lammi_calculation_method not recognized in user preferences." ) self.equation_fe_radio.setChecked(True) self.equation_fe_radio.toggled.connect(self.lammi_choice_changed) equation_radio_layout = QHBoxLayout() equation_radio_layout.addWidget(self.equation_fe_radio) equation_radio_layout.addWidget(self.equation_fv_radio) equation_radio_layout.addStretch() # epsg epsg_title_label = QLabel(self.tr('EPSG code')) self.epsg_label = QLineEdit(self.tr('unknown')) self.epsg_label.returnPressed.connect(self.load_hydraulic_create_hdf5) # hdf5 name self.hdf5_name_title_label = QLabel(".hyd " + self.tr('file name')) self.hdf5_name_lineedit = QLineEdit() self.hdf5_name_lineedit.returnPressed.connect( self.load_hydraulic_create_hdf5) # last_hydraulic_file_label self.last_hydraulic_file_label = QLabel(self.tr('Last file created')) self.last_hydraulic_file_name_label = QLabel(self.tr('no file')) # progress_layout self.progress_layout = ProcessProgLayout( self.load_hydraulic_create_hdf5, send_log=self.send_log, process_type="hyd", send_refresh_filenames=self.drop_hydro) # layout self.hydrau_layout = QGridLayout() self.hydrau_layout.addWidget(self.result_file_title_label, 0, 0) self.hydrau_layout.addLayout(self.selection_layout, 0, 1) self.hydrau_layout.addWidget(reach_name_title_label, 1, 0) self.hydrau_layout.addWidget(self.reach_name_combobox, 1, 1) self.hydrau_layout.addWidget(unit_title_label, 3, 0) self.hydrau_layout.addLayout(unit_layout, 3, 1) self.hydrau_layout.addWidget(usefull_mesh_variable_label_title, 5, 0) self.hydrau_layout.addWidget( self.usefull_mesh_variable_label, 5, 1) # from row, from column, nb row, nb column self.hydrau_layout.addWidget(usefull_node_variable_label_title, 6, 0) self.hydrau_layout.addWidget( self.usefull_node_variable_label, 6, 1) # from row, from column, nb row, nb column self.hydrau_layout.addWidget(classification_code_title_label, 7, 0) self.hydrau_layout.addItem(sub_radio_layout, 7, 1) self.hydrau_layout.addWidget(equation_title_label, 8, 0) self.hydrau_layout.addItem(equation_radio_layout, 8, 1) self.hydrau_layout.addWidget(epsg_title_label, 9, 0) self.hydrau_layout.addWidget(self.epsg_label, 9, 1) self.hydrau_layout.addWidget(self.hdf5_name_title_label, 10, 0) self.hydrau_layout.addWidget(self.hdf5_name_lineedit, 10, 1) self.hydrau_layout.addLayout(self.progress_layout, 11, 0, 1, 2) self.hydrau_layout.addWidget(self.last_hydraulic_file_label, 12, 0) self.hydrau_layout.addWidget(self.last_hydraulic_file_name_label, 12, 1) self.setLayout(self.hydrau_layout) def update_for_lammi(self, on=False): # hide/show lammi widgets self.hydrau_layout.itemAtPosition(7, 0).widget().setVisible(on) for widget_ind in range( 0, self.hydrau_layout.itemAtPosition(7, 1).count()): widget_temp = self.hydrau_layout.itemAtPosition( 7, 1).itemAt(widget_ind).widget() if widget_temp: widget_temp.setVisible(on) self.hydrau_layout.itemAtPosition(8, 0).widget().setVisible(on) for widget_ind in range( 0, self.hydrau_layout.itemAtPosition(8, 1).count()): widget_temp = self.hydrau_layout.itemAtPosition( 8, 1).itemAt(widget_ind).widget() if widget_temp: widget_temp.setVisible(on) # change labels if on: self.hdf5_name_title_label.setText(".hab " + self.tr('file name')) self.progress_layout.run_stop_button.setText( self.tr("Create .hab file")) else: self.hdf5_name_title_label.setText(".hyd " + self.tr('file name')) self.progress_layout.run_stop_button.setText( self.tr("Create .hyd file")) def lammi_choice_changed(self): # sub if self.sub_classification_code_edf_radio.isChecked(): user_preferences.data["lammi_sub_classification_code"] = "EDF" elif self.sub_classification_code_cemagref_radio.isChecked(): user_preferences.data["lammi_sub_classification_code"] = "Cemagref" # equ if self.equation_fe_radio.isChecked(): user_preferences.data["lammi_calculation_method"] = "FEM" elif self.equation_fv_radio.isChecked(): user_preferences.data["lammi_calculation_method"] = "FVM" # update variable position if self.namefile and (self.sender() == self.equation_fe_radio or self.sender() == self.equation_fv_radio): hsr = HydraulicSimulationResults(self.namefile, self.pathfile, self.model_type, self.path_prj) width_char = 120 mesh_list = ", ".join( hsr.hvum.software_detected_list.meshs().names_gui()) if len(mesh_list) > width_char: self.usefull_mesh_variable_label.setText( mesh_list[:width_char] + "...") self.usefull_mesh_variable_label.setToolTip(mesh_list) else: self.usefull_mesh_variable_label.setText(mesh_list) node_list = ", ".join( hsr.hvum.software_detected_list.nodes().names_gui()) if len(node_list) > width_char: self.usefull_node_variable_label.setText( node_list[:width_char] + "...") self.usefull_node_variable_label.setToolTip(node_list) else: self.usefull_node_variable_label.setText(node_list) # save user_preferences.save_user_preferences_json() def read_attribute_xml(self, att_here): """ A function to read the text of an attribute in the xml project file. :param att_here: the attribute name (string). """ data = '' filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') if os.path.isfile(filename_path_pro): if att_here == "path_last_file_loaded": data = load_project_properties(self.path_prj)[att_here] else: data = load_project_properties(self.path_prj)[att_here]["path"] else: pass return data def save_xml(self): """ A function to save the loaded data in the xml file. This function adds the name and the path of the newly chosen hydrological data to the xml project file. First, it open the xml project file (and send an error if the project is not saved, or if it cannot find the project file). Then, it opens the xml file and add the path and name of the file to this xml file. If the model data was already loaded, it adds the new name without erasing the old name IF the switch append_name is True. Otherwise, it erase the old name and replace it by a new name. The variable “i” has the same role than in select_file_and_show_informations_dialog. :param i: a int for the case where there is more than one file to load :param append_name: A boolean. If True, the name found will be append to the existing name in the xml file, instead of remplacing the old name by the new name. """ filename_path_file = self.pathfile filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') # save the name and the path in the xml .prj file if not os.path.isfile(filename_path_pro): self.end_log.emit( 'Error: The project is not saved. ' 'Save the project in the General tab before saving hydrological data. \n' ) else: # change path_last_file_loaded, model_type (path) project_properties = load_project_properties( self.path_prj) # load_project_properties project_properties[ "path_last_file_loaded"] = filename_path_file # change value project_properties[ self.model_type]["path"] = filename_path_file # change value save_project_properties( self.path_prj, project_properties) # save_project_properties def name_last_hdf5(self, type): """ This function opens the xml project file to find the name of the last hdf5 merge file and to add it to the GUI on the QLabel self.lm2. It also add a QToolTip with the name of substrate and hydraulic files used to create this merge file. If there is no file found, this function do nothing. """ filename_path_pro = os.path.join(self.path_prj, self.name_prj + '.habby') name = QCoreApplication.translate("SubHydroW", 'no file') # save the name and the path in the xml .prj file if not os.path.isfile(filename_path_pro): self.send_log.emit('Error: ' + QCoreApplication.translate( "SubHydroW", 'The project is not saved. ' 'Save the project in the General tab before saving hydraulic data. \n' )) else: project_properties = load_project_properties(self.path_prj) if project_properties[type]["hdf5"]: name = project_properties[type]["hdf5"][-1] self.last_hydraulic_file_name_label.setText(name) def clean_gui(self): self.input_file_combobox.clear() self.reach_name_combobox.clear() self.units_name_label.setText("unknown") # kind of unit self.unit_number_label.setText("unknown") # number units self.units_QListWidget.clear() self.epsg_label.clear() self.hdf5_name_lineedit.setText("") # hdf5 name self.progress_layout.run_stop_button.setText( self.tr("Create .hyd file")) def select_file_and_show_informations_dialog(self): """ A function to obtain the name of the file chosen by the user. This method open a dialog so that the user select a file. This file is NOT loaded here. The name and path to this file is saved in an attribute. This attribute is then used to loaded the file in other function, which are different for each children class. Based on the name of the chosen file, a name is proposed for the hdf5 file. :param i: an int for the case where there is more than one file to load """ # get minimum water height as we might neglect very low water height self.project_properties = load_project_properties(self.path_prj) # prepare the filter to show only useful files if len(self.extension.split(", ")) <= 4: filter2 = "File (" for e in self.extension.split(", "): filter2 += '*' + e + ' ' filter2 = filter2[:-1] filter2 += ')' + ";; All File (*.*)" else: filter2 = '' # get last path if self.read_attribute_xml( self.model_type) != self.path_prj and self.read_attribute_xml( self.model_type) != "": model_path = self.read_attribute_xml(self.model_type) # path spe elif self.read_attribute_xml( "path_last_file_loaded" ) != self.path_prj and self.read_attribute_xml( "path_last_file_loaded") != "": model_path = self.read_attribute_xml( "path_last_file_loaded") # path last else: model_path = self.path_prj # path proj # find the filename based on user choice if self.extension: filename_list = QFileDialog().getOpenFileNames( self, self.tr("Select file(s)"), model_path, filter2) else: filename_list = QFileDialog().getExistingDirectory( self, self.tr("Select directory"), model_path) filename_list = [filename_list] # if file has been selected if filename_list[0]: # disconnect function for multiple file cases try: self.input_file_combobox.disconnect() except: pass try: self.reach_name_combobox.disconnect() except: pass try: self.units_QListWidget.disconnect() except: pass # init self.hydrau_case = "unknown" self.multi_hdf5 = False self.multi_reach = False self.index_hydrau_presence = False # get_hydrau_description_from_source hsra_value = HydraulicSimulationResultsAnalyzer( filename_list[0], self.path_prj, self.model_type) # warnings if hsra_value.warning_list: for warn in hsra_value.warning_list: self.send_log.emit(warn) if "Error:" in warn: self.clean_gui() return # error if type(hsra_value.hydrau_description_list) == str: self.clean_gui() self.send_log.emit(hsra_value.hydrau_description_list) return # set to attribute self.hydrau_description_list = hsra_value.hydrau_description_list # display first hydrau_description_list self.hydrau_case = self.hydrau_description_list[0]["hydrau_case"] # change suffix if not self.project_properties[ "cut_mesh_partialy_dry"] and self.hydrau_description_list[ 0]["model_dimension"] == "2": for telemac_description_num in range( len(self.hydrau_description_list)): namehdf5_old = os.path.splitext( self.hydrau_description_list[telemac_description_num] ["hdf5_name"])[0] exthdf5_old = os.path.splitext( self.hydrau_description_list[telemac_description_num] ["hdf5_name"])[1] self.hydrau_description_list[telemac_description_num][ "hdf5_name"] = namehdf5_old + "_no_cut" + exthdf5_old # save last path self.pathfile = self.hydrau_description_list[0][ "path_filename_source"] # source file path self.namefile = self.hydrau_description_list[0][ "filename_source"] # source file name self.name_hdf5 = self.hydrau_description_list[0]["hdf5_name"] self.save_xml() # multi if len(self.hydrau_description_list) > 1: self.multi_hdf5 = True # multi if len(self.hydrau_description_list[0]["reach_list"]) > 1: self.multi_reach = True # get names names = [ description["filename_source"] for description in self.hydrau_description_list ] # clean GUI self.clean_gui() self.input_file_combobox.addItems(names) self.update_reach_from_input_file() self.input_file_combobox.currentIndexChanged.connect( self.update_reach_from_input_file) self.reach_name_combobox.currentIndexChanged.connect( self.update_unit_from_reach) self.units_QListWidget.itemSelectionChanged.connect( self.unit_counter) self.hdf5_name_lineedit.setFocus() def update_reach_from_input_file(self): self.reach_name_combobox.blockSignals(True) self.reach_name_combobox.clear() self.reach_name_combobox.addItems(self.hydrau_description_list[ self.input_file_combobox.currentIndex()]["reach_list"]) width_char = 120 mesh_list = ", ".join( self.hydrau_description_list[self.input_file_combobox.currentIndex( )]["variable_name_unit_dict"].meshs().names_gui()) if len(mesh_list) > width_char: self.usefull_mesh_variable_label.setText(mesh_list[:width_char] + "...") self.usefull_mesh_variable_label.setToolTip(mesh_list) else: self.usefull_mesh_variable_label.setText(mesh_list) node_list = ", ".join( self.hydrau_description_list[self.input_file_combobox.currentIndex( )]["variable_name_unit_dict"].nodes().names_gui()) if len(node_list) > width_char: self.usefull_node_variable_label.setText(node_list[:width_char] + "...") self.usefull_node_variable_label.setToolTip(node_list) else: self.usefull_node_variable_label.setText(node_list) self.units_name_label.setText( self.hydrau_description_list[self.input_file_combobox.currentIndex( )]["unit_type"]) # kind of unit self.update_unit_from_reach() self.epsg_label.setText(self.hydrau_description_list[ self.input_file_combobox.currentIndex()]["epsg_code"]) self.hdf5_name_lineedit.setText(self.hydrau_description_list[ self.input_file_combobox.currentIndex()]["hdf5_name"]) # hdf5 name extension = "hyd" if self.hydrau_description_list[ self.input_file_combobox.currentIndex()]["sub"]: extension = "hab" text_load_button = self.tr("Create ") + str( len(self.hydrau_description_list)) + self.tr( " file ") + "." + extension if len(self.hydrau_description_list) > 1: text_load_button = self.tr("Create ") + str( len(self.hydrau_description_list)) + self.tr( " files ") + "." + extension self.progress_layout.run_stop_button.setText(text_load_button) self.progress_layout.progress_bar.setValue(0.0) self.progress_layout.progress_label.setText("{0:.0f}/{1:.0f}".format( 0.0, len(self.hydrau_description_list))) self.reach_name_combobox.blockSignals(False) def update_unit_from_reach(self): self.units_QListWidget.blockSignals(True) self.units_QListWidget.clear() self.units_QListWidget.addItems( self.hydrau_description_list[self.input_file_combobox.currentIndex( )]["unit_list_full"][self.reach_name_combobox.currentIndex()]) if all(self.hydrau_description_list[ self.input_file_combobox.currentIndex()]["unit_list_tf"][ self.reach_name_combobox.currentIndex()]): self.units_QListWidget.selectAll() else: for i in range( len(self.hydrau_description_list[ self.input_file_combobox.currentIndex()] ["unit_list_full"][ self.reach_name_combobox.currentIndex()])): self.units_QListWidget.item(i).setSelected( self.hydrau_description_list[ self.input_file_combobox.currentIndex()] ["unit_list_tf"][ self.reach_name_combobox.currentIndex()][i]) self.units_QListWidget.item(i).setTextAlignment(Qt.AlignLeft) self.units_QListWidget.blockSignals(False) self.unit_counter() def unit_counter(self): hyd_desc_index = self.input_file_combobox.currentIndex() reach_index = self.reach_name_combobox.currentIndex() # count total number items (units) total = self.units_QListWidget.count() # count total number items selected selected = len(self.units_QListWidget.selectedItems()) # refresh telemac dictonnary unit_list = [] unit_list_full = [] unit_list_tf = [] for i in range(total): text = self.units_QListWidget.item(i).text() if self.units_QListWidget.item(i).isSelected(): unit_list.append(text) unit_list_full.append(text) unit_list_tf.append(self.units_QListWidget.item(i).isSelected()) # save multi self.hydrau_description_list[hyd_desc_index]["unit_list"][ reach_index] = list(unit_list) self.hydrau_description_list[hyd_desc_index]["unit_list_full"][ reach_index] = unit_list_full self.hydrau_description_list[hyd_desc_index]["unit_list_tf"][ reach_index] = unit_list_tf self.hydrau_description_list[hyd_desc_index]["unit_number"] = str( selected) if self.hydrau_case == '2.a' or self.hydrau_case == '2.b': # preset name hdf5 filename_source_list = self.hydrau_description_list[ hyd_desc_index]["filename_source"].split(", ") new_names_list = [] for file_num, file in enumerate(filename_source_list): if self.hydrau_description_list[hyd_desc_index][ "unit_list_tf"][reach_index][file_num]: new_names_list.append(os.path.splitext(file)[0]) self.hydrau_description_list[hyd_desc_index][ "hdf5_name"] = "_".join(new_names_list) + ".hyd" if len(filename_source_list) == len(new_names_list) and len( self.hydrau_description_list[hyd_desc_index] ["hdf5_name"]) > 25: self.hydrau_description_list[hyd_desc_index]["hdf5_name"] = new_names_list[0].replace(".", "_") \ + "_to_" + \ new_names_list[-1].replace(".", "_") + ".hyd" if not load_specific_properties( self.path_prj, ["cut_mesh_partialy_dry"])[0] and self.hydrau_description_list[ hyd_desc_index]["model_dimension"] == "2": namehdf5_old = \ os.path.splitext(self.hydrau_description_list[hyd_desc_index]["hdf5_name"])[0] exthdf5_old = \ os.path.splitext(self.hydrau_description_list[hyd_desc_index]["hdf5_name"])[1] if not "no_cut" in namehdf5_old: self.hydrau_description_list[hyd_desc_index][ "hdf5_name"] = namehdf5_old + "_no_cut" + exthdf5_old self.hdf5_name_lineedit.setText( self.hydrau_description_list[hyd_desc_index] ["hdf5_name"]) # hdf5 name # set text text = str(selected) + "/" + str(total) self.unit_number_label.setText(text) # number units self.progress_layout.run_stop_button.setEnabled(True) def load_hydraulic_create_hdf5(self): """ The function which call the function which load telemac and save the name of files in the project file """ """ The function which call the function which load hec_ras2d and save the name of files in the project file """ if self.progress_layout.run_stop_button.isEnabled(): # get minimum water height as we might neglect very low water height self.project_properties = load_project_properties(self.path_prj) # get timestep and epsg selected for i in range(len(self.hydrau_description_list)): for reach_number in range( int(self.hydrau_description_list[i]["reach_number"])): if not any(self.hydrau_description_list[i]["unit_list_tf"] [reach_number]): self.send_log.emit( "Error: " + self.tr("No units selected for : ") + self.hydrau_description_list[i]["filename_source"] + "\n") return # check if extension is set by user (one hdf5 case) self.name_hdf5 = self.hdf5_name_lineedit.text() self.hydrau_description_list[self.input_file_combobox.currentIndex( )]["hdf5_name"] = self.name_hdf5 if self.name_hdf5 == "": self.send_log.emit('Error: ' + self.tr( '.hyd output filename is empty. Please specify it.')) return # check if extension is set by user (multi hdf5 case) hydrau_description_multiple = deepcopy( self.hydrau_description_list ) # create copy to not erase inital choices for hdf5_num in range(len(hydrau_description_multiple)): if not os.path.splitext( hydrau_description_multiple[hdf5_num]["hdf5_name"])[1]: hydrau_description_multiple[hdf5_num][ "hdf5_name"] = hydrau_description_multiple[hdf5_num][ "hdf5_name"] + ".hyd" # refresh filename_source if self.hydrau_case == '2.a' or self.hydrau_case == '2.b': filename_source_list = hydrau_description_multiple[ hdf5_num]["filename_source"].split(", ") new_filename_source_list = [] for reach_number in range( len(hydrau_description_multiple[hdf5_num] ["unit_list_tf"])): for file_num, file in enumerate(filename_source_list): if hydrau_description_multiple[hdf5_num][ "unit_list_tf"][reach_number][file_num]: new_filename_source_list.append( filename_source_list[file_num]) hydrau_description_multiple[hdf5_num][ "filename_source"] = ", ".join( new_filename_source_list) # process_manager self.progress_layout.process_manager.set_hyd_mode( self.path_prj, hydrau_description_multiple, self.project_properties) # process_prog_show self.progress_layout.start_process() # script self.create_script(hydrau_description_multiple) def create_script(self, hydrau_description_multiple): # path_prj path_prj_script = self.path_prj + "_restarted" # cli if sys.argv[0][-3:] == ".py": exe_cmd = '"' + sys.executable + '" "' + sys.argv[0] + '"' else: exe_cmd = '"' + sys.executable + '"' script_function_name = "CREATE_HYD" cmd_str = exe_cmd + ' ' + script_function_name + \ ' model="' + self.model_type + '"' + \ ' inputfile="' + os.path.join(self.path_prj, "input", self.name_hdf5.split(".")[0], "indexHYDRAU.txt") + '"' + \ ' unit_list=' + str(self.hydrau_description_list[self.input_file_combobox.currentIndex()]['unit_list'][0]).replace("\'", "'").replace(' ', '') + \ ' cut=' + str(self.project_properties['cut_mesh_partialy_dry']) + \ ' outputfilename="' + self.name_hdf5 + '"' + \ ' path_prj="' + path_prj_script + '"' self.send_log.emit("script" + cmd_str) # py cmd_str = F"\t# CREATE_HYD\n" \ F"\tfrom src.hydraulic_process_mod import HydraulicSimulationResultsAnalyzer, load_hydraulic_cut_to_hdf5\n\n" cmd_str = cmd_str + F'\thsra_value = HydraulicSimulationResultsAnalyzer(filename_path_list=[{repr(os.path.join(self.path_prj, "input", self.name_hdf5.split(".")[0], "indexHYDRAU.txt"))}], ' \ F"\tpath_prj={repr(path_prj_script)}, " \ F"\tmodel_type={repr(self.model_type)}, " \ F"\tnb_dim={repr(str(self.nb_dim))})\n" cmd_str = cmd_str + F"\tfor hdf5_file_index in range(0, len(hsra_value.hydrau_description_list)):\n" \ F"\t\tprogress_value = Value('d', 0.0)\n" \ F"\t\tq = Queue()\n" \ F"\t\tload_hydraulic_cut_to_hdf5(hydrau_description=hsra_value.hydrau_description_list[hdf5_file_index], " \ F"\tprogress_value=progress_value, " \ F"\tq=q, " \ F"\tprint_cmd=True, " \ F"\tproject_properties=load_project_properties({repr(path_prj_script)}))" + "\n" self.send_log.emit("py" + cmd_str)
class PictureEditor(QFrame): picture_data: Picture pic_path: str='./' def __init__(self, parent, picture_parameter: dict): QFrame.__init__(self, parent, flags=Qt.FramelessWindowHint) self.buffer = parent.buffer self.picture_parameter = picture_parameter self.picture_label = QLabel() self.resize_multiple = 1 scroll = QScrollArea(self) scroll.setWidget(self.picture_label) scroll.setObjectName('picture') self.name_combo = QComboBox(self) self.index_combo = QComboBox(self) self.palette_combo = QComboBox(self) self.resize_combo = QComboBox(self) self.name_combo.setFixedWidth(120) self.index_combo.setFixedWidth(120) self.palette_combo.setFixedWidth(120) self.resize_combo.setFixedWidth(120) self.name_combo.setItemDelegate(QStyledItemDelegate()) self.index_combo.setItemDelegate(QStyledItemDelegate()) self.palette_combo.setItemDelegate(QStyledItemDelegate()) self.resize_combo.setItemDelegate(QStyledItemDelegate()) self.resize_combo.addItems([f' × {i + 1}' for i in range(4)]) self.name_combo.currentTextChanged[str].connect(self.name_change) self.index_combo.currentIndexChanged.connect(self.refresh_data) self.palette_combo.currentIndexChanged.connect(self.refresh_data) self.resize_combo.currentIndexChanged.connect(self.refresh_data) self.name_combo.addItems(picture_parameter) output_button = QPushButton('導出圖片') input_button = QPushButton('導入圖片') output_button.clicked.connect(self.output_picture) input_button.clicked.connect(self.input_picture) control_layout = QVBoxLayout() control_layout.addWidget(QLabel('選擇圖片'), alignment=Qt.AlignLeft) control_layout.addWidget(self.name_combo, alignment=Qt.AlignRight) control_layout.addWidget(QLabel('選擇編號'), alignment=Qt.AlignLeft) control_layout.addWidget(self.index_combo, alignment=Qt.AlignRight) control_layout.addWidget(QLabel('選擇色板'), alignment=Qt.AlignLeft) control_layout.addWidget(self.palette_combo, alignment=Qt.AlignRight) control_layout.addWidget(QLabel('缩放比例'), alignment=Qt.AlignLeft) control_layout.addWidget(self.resize_combo, alignment=Qt.AlignRight) control_layout.addWidget(output_button, alignment=Qt.AlignRight) control_layout.addWidget(input_button, alignment=Qt.AlignRight) control_layout.addStretch() layout = QHBoxLayout() layout.addLayout(control_layout) layout.addWidget(scroll) self.setLayout(layout) def name_change(self, name: str): picture_setting = DATA_PARAMETER.get('圖片_' + name) self.index_combo.disconnect() self.index_combo.clear() self.index_combo.addItems([f'{i+1:>13d}' for i in range(picture_setting['quantity']['normal_quantity'])]) self.index_combo.currentIndexChanged.connect(self.refresh_data) if self.picture_parameter.get(name): palette_setting = PALETTE_PARAMETER.get('色板_' + self.picture_parameter.get(name)) else: palette_setting = () self.palette_combo.disconnect() self.palette_combo.clear() if palette_setting: self.palette_combo.addItems([f'0x{address:08X}' for address in palette_setting]) self.palette_combo.setEnabled(True) else: self.palette_combo.setEnabled(False) self.palette_combo.currentIndexChanged.connect(self.refresh_data) self.refresh_data() def refresh_data(self): picture_setting = DATA_PARAMETER.get('圖片_' + self.name_combo.currentText()) self.picture_data = Picture(self, **picture_setting) if self.palette_combo.isEnabled(): self.picture_data.palette.set_normal_offset(int(self.palette_combo.currentText(), 16)) self.resize_multiple = self.resize_combo.currentIndex() + 1 self.set_picture() def set_picture(self): picture = self.picture_data.get_data(self.index_combo.currentIndex()) width = self.picture_data.width * self.resize_multiple height = self.picture_data.height * self.resize_multiple self.picture_label.setFixedSize(width, height) self.picture_label.setPixmap(picture.resize((width, height)).toqpixmap()) def output_picture(self): filename = QFileDialog.getSaveFileName(self, '导出图片', self.pic_path, 'BMP图像(*.bmp)')[0] if filename: self.pic_path = filename[0: filename.rfind('/') + 1] if self.index_combo.isEnabled(): picture = self.picture_data.get_data(self.index_combo.currentIndex()) else: picture = self.picture_data.get_data(0) picture.save(filename) def input_picture(self): filename = QFileDialog.getOpenFileName(self, '导入图片', self.pic_path, '*.bmp;;*.png;;*.gif;;*.tif')[0] if filename: self.pic_path = filename[0: filename.rfind('/') + 1] width = self.picture_data.width * self.resize_multiple height = self.picture_data.height * self.resize_multiple picture = Image.open(filename).resize((width, height)) self.picture_data.set_data(self.index_combo.currentIndex(), picture) self.set_picture()
class ImportFrame(QFrame): conn: sqlite3.Connection = None path: str = './' setting_file_path: str = None import_wb: Workbook = None import_ws: Worksheet = None model = ImportModel([]) type_delegate = TypeDelegate() # noinspection PyArgumentList def __init__(self, *args): super(ImportFrame, self).__init__(*args) file_group = QGroupBox('Excel File') self.file_line = QLineEdit() self.file_line.setReadOnly(True) file_button = QPushButton('Load') file_button.setFixedWidth(100) file_layout = QGridLayout() file_layout.addWidget(self.file_line, 0, 0) file_layout.addWidget(file_button, 0, 1) file_group.setLayout(file_layout) file_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) import_group = QGroupBox('Import Settings') self.sheet_combo = QComboBox() self.header_spin = QSpinBox() self.header_spin.setRange(0, 0) self.name_line = QLineEdit() self.exists_combo = QComboBox() self.exists_combo.addItems(['append', 'replace', 'fail']) self.exists_combo.setStatusTip( '''if table also in database\nappend: insert new records\nreplace: replace data\nfail: abort import''') form_layout = QFormLayout() form_layout.addRow('Select Sheet', self.sheet_combo) form_layout.addRow('Select Title Row', self.header_spin) form_layout.addRow('Table Name', self.name_line) form_layout.addRow('If Table Exists', self.exists_combo) self.column_view = QTableView() save_button = QPushButton('&Save') save_button.setFixedWidth(100) load_button = QPushButton('&Load') load_button.setFixedWidth(100) fast_button = QPushButton('&FastLoad') fast_button.setFixedWidth(100) import_button = QPushButton('&Import') import_button.setFixedWidth(100) button_layout = QHBoxLayout() button_layout.addStretch() button_layout.addWidget(save_button) button_layout.addWidget(load_button) button_layout.addWidget(fast_button) button_layout.addWidget(import_button) setting_layout = QGridLayout() setting_layout.addLayout(form_layout, 0, 0) setting_layout.addWidget(self.column_view, 1, 0) setting_layout.addLayout(button_layout, 2, 0) import_group.setLayout(setting_layout) main_layout = QGridLayout() main_layout.addWidget(file_group, 0, 0) main_layout.addWidget(import_group, 1, 0) self.setLayout(main_layout) file_button.clicked.connect(self.load_file) self.header_spin.valueChanged.connect(self.load_column) save_button.clicked.connect(self.save_setting) load_button.clicked.connect(self.load_setting) fast_button.clicked.connect(self.auto_load) import_button.clicked.connect(self.import_data) def load_file(self): file_path: str = QFileDialog.getOpenFileName(self.parent(), 'Load File', self.path, 'Excel File(*.xlsx)', options=QFileDialog.DontConfirmOverwrite)[0] if file_path: self.file_line.setText(file_path) self.path = '/'.join(file_path.split('/')[:-1]) self.import_wb = load_workbook(file_path, data_only=True) self.sheet_combo.disconnect() self.sheet_combo.clear() self.sheet_combo.addItems(self.import_wb.sheetnames) self.load_sheet() self.sheet_combo.currentIndexChanged.connect(self.load_sheet) def load_sheet(self): self.import_ws = self.import_wb[self.sheet_combo.currentText()] self.name_line.setText(self.sheet_combo.currentText().replace(' ', '_', 10)) self.header_spin.setRange(1, self.import_ws.max_row) self.load_column() def load_column(self): self.model = ImportModel([cell.value for cell in list(self.import_ws.rows)[self.header_spin.value() - 1]]) self.column_view.setModel(self.model) self.column_view.setItemDelegateForColumn(1, self.type_delegate) self.column_view.setColumnWidth(0, 500) self.column_view.setColumnWidth(1, 150) def import_data(self): if not self.file_line.text(): return self.sender().setEnabled(False) df = pd.read_excel(self.file_line.text(), self.sheet_combo.currentText(), header=self.header_spin.value() - 1, usecols=self.model.usecols, dtype=self.model.dtype, parse_dates=self.model.parse_dates, date_parser=lambda x: pd.to_datetime(x).strftime('%Y-%m-%d')) for k, v in self.model.dtype.items(): if v == str: df[k] = df[k].replace('nan', np.nan) df.to_sql(self.name_line.text(), self.conn, if_exists=self.exists_combo.currentText()) self.sender().setEnabled(True) def save_setting(self): file_path: str = QFileDialog.getSaveFileName(self.parent(), 'Save Setting', './setting/', 'Setting File(*.stg)', options=QFileDialog.DontConfirmOverwrite)[0] if file_path: save_settings = [self.name_line.text(), self.header_spin.value(), self.model.table_settings] file = open(file_path, 'wb') pickle.dump(save_settings, file) file.close() def load_setting(self): file_path: str = QFileDialog.getOpenFileName(self.parent(), 'Load Setting', './setting/', 'Setting File(*.stg)', options=QFileDialog.DontConfirmOverwrite)[0] if file_path: self.setting_file_path = file_path file = open(file_path, 'rb') load_settings = pickle.load(file) self.name_line.setText(load_settings[0]) self.header_spin.setValue(load_settings[1]) self.model.table_settings = load_settings[2] self.column_view.reset() def auto_load(self): if self.setting_file_path: file = open(self.setting_file_path, 'rb') load_settings = pickle.load(file) self.name_line.setText(load_settings[0]) self.header_spin.setValue(load_settings[1]) self.model.table_settings = load_settings[2] self.column_view.reset()
class DAT_GUI(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.initUI() def initUI(self): screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() # should fit in 1024x768 (old computer screens) window_width = 900 window_height = 700 self.setGeometry( QtCore.QRect( centerPoint.x() - int(window_width / 2), centerPoint.y() - int(window_height / 2), window_width, window_height)) # should I rather center on the screen # zoom parameters self.scale = 1.0 self.min_scaling_factor = 0.1 self.max_scaling_factor = 20 self.zoom_increment = 0.05 self.setWindowTitle(__NAME__ + ' v' + str(__VERSION__)) self.paint = Createpaintwidget() # initiate 2D image for 2D display self.img = None self.list = QListWidget( self) # a list that contains files to read or play with self.list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list.selectionModel().selectionChanged.connect( self.selectionChanged) # connect it to sel change self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.paint) self.paint.scrollArea = self.scrollArea self.table_widget = QWidget() table_widget_layout = QVBoxLayout() # Initialize tab screen self.tabs = QTabWidget(self) self.tab1 = QWidget() self.tab2 = QWidget() self.tab3 = QWidget() # Add tabs self.tabs.addTab(self.tab1, "Mask neuron") self.tabs.addTab(self.tab2, "Mask cell body") self.tabs.addTab(self.tab3, "Segment dendrites") # listen to tab changes self.tabs.currentChanged.connect(self._onTabChange) # Create first tab self.tab1.layout = QGridLayout() self.tab1.layout.setAlignment(Qt.AlignTop) self.tab1.layout.setHorizontalSpacing(3) self.tab1.layout.setVerticalSpacing(3) self.tab1.layout.setContentsMargins(0, 0, 0, 0) label1_tab1 = QLabel('Step 1:') self.tab1.layout.addWidget(label1_tab1, 0, 0) self.local_threshold = QPushButton("Local threshold") self.local_threshold.clicked.connect(self.run_threshold_neuron) self.tab1.layout.addWidget(self.local_threshold, 0, 1) self.global_threshold = QPushButton("Global threshold") self.global_threshold.clicked.connect(self.run_threshold_neuron) self.tab1.layout.addWidget(self.global_threshold, 0, 2) self.local_n_global_threshold = QPushButton( "Local AND Global threshold") self.local_n_global_threshold.clicked.connect( self.run_threshold_neuron) self.tab1.layout.addWidget(self.local_n_global_threshold, 0, 3) self.extra_value_for_threshold = QSpinBox() self.extra_value_for_threshold.setSingleStep(1) self.extra_value_for_threshold.setRange(0, 1_000_000) self.extra_value_for_threshold.setValue(6) self.tab1.layout.addWidget(self.extra_value_for_threshold, 0, 4) self.threshold_method = QComboBox() self.threshold_method.addItem('Mean') self.threshold_method.addItem('Median') self.tab1.layout.addWidget(self.threshold_method, 0, 5) label2_tab1 = QLabel('Step 2 (optional):') self.tab1.layout.addWidget(label2_tab1, 1, 0) self.remove_pixel_blobs_smaller_or_equal = QPushButton( "Remove pixel blobs smaller or equal to") self.remove_pixel_blobs_smaller_or_equal.clicked.connect( self.remove_blobs) self.tab1.layout.addWidget(self.remove_pixel_blobs_smaller_or_equal, 1, 1) self.remove_blobs_size = QSpinBox() self.remove_blobs_size.setSingleStep(1) self.remove_blobs_size.setRange(0, 1_000_000) self.remove_blobs_size.setValue(1) self.tab1.layout.addWidget(self.remove_blobs_size, 1, 2) label3_tab1 = QLabel('Step 3: Save') self.tab1.layout.addWidget(label3_tab1, 2, 0) self.tab1.setLayout(self.tab1.layout) self.tab2.layout = QGridLayout() self.tab2.layout.setAlignment(Qt.AlignTop) self.tab2.layout.setHorizontalSpacing(3) self.tab2.layout.setVerticalSpacing(3) self.tab2.layout.setContentsMargins(0, 0, 0, 0) label1_tab2 = QLabel('Step 1:') self.tab2.layout.addWidget(label1_tab2, 0, 0) self.detect_cell_body = QPushButton("Detect cell body") self.detect_cell_body.clicked.connect(self.detect_neuronal_cell_body) self.tab2.layout.addWidget(self.detect_cell_body, 0, 1) self.extraCutOff_cell_body = QSpinBox() self.extraCutOff_cell_body.setSingleStep(1) self.extraCutOff_cell_body.setRange(0, 1_000_000) self.extraCutOff_cell_body.setValue(5) self.tab2.layout.addWidget(self.extraCutOff_cell_body, 0, 2) erosion_label = QLabel('erosion rounds') self.tab2.layout.addWidget(erosion_label, 0, 3) self.nb_erosion_cellbody = QSpinBox() self.nb_erosion_cellbody.setSingleStep(1) self.nb_erosion_cellbody.setRange(0, 1_000_000) self.nb_erosion_cellbody.setValue(2) self.tab2.layout.addWidget(self.nb_erosion_cellbody, 0, 4) min_object_size_label = QLabel('minimum object size') self.tab2.layout.addWidget(min_object_size_label, 0, 5) self.min_obj_size_px = QSpinBox() self.min_obj_size_px.setSingleStep(1) self.min_obj_size_px.setRange(0, 1_000_000) self.min_obj_size_px.setValue(600) self.tab2.layout.addWidget(self.min_obj_size_px, 0, 6) fill_label = QLabel('fill up to') self.tab2.layout.addWidget(fill_label, 0, 7) self.fill_holes_up_to = QSpinBox() self.fill_holes_up_to.setSingleStep(1) self.fill_holes_up_to.setRange(0, 1_000_000) self.fill_holes_up_to.setValue(600) self.tab2.layout.addWidget(self.fill_holes_up_to, 0, 8) nb_dilation_cell_body_label = QLabel('nb dilation cell body') self.tab2.layout.addWidget(nb_dilation_cell_body_label, 0, 9) self.nb_dilation_cellbody = QSpinBox() self.nb_dilation_cellbody.setSingleStep(1) self.nb_dilation_cellbody.setRange(0, 1_000_000) self.nb_dilation_cellbody.setValue(2) self.tab2.layout.addWidget(self.nb_dilation_cellbody, 0, 10) label2_tab2 = QLabel('Step 2: Save') self.tab2.layout.addWidget(label2_tab2, 6, 0) self.tab2.setLayout(self.tab2.layout) self.tab3.layout = QGridLayout() self.tab3.layout.setAlignment(Qt.AlignTop) self.tab3.layout.setHorizontalSpacing(3) self.tab3.layout.setVerticalSpacing(3) self.tab3.layout.setContentsMargins(0, 0, 0, 0) label1_tab3 = QLabel('Step 1:') self.tab3.layout.addWidget(label1_tab3, 0, 0) self.wshed = QPushButton("Watershed") self.wshed.clicked.connect(self.watershed_segment_the_neuron) self.tab3.layout.addWidget(self.wshed, 0, 1) self.whsed_big_blur = QDoubleSpinBox() self.whsed_big_blur.setSingleStep(0.1) self.whsed_big_blur.setRange(0, 100) self.whsed_big_blur.setValue(2.1) self.tab3.layout.addWidget(self.whsed_big_blur, 0, 2) self.whsed_small_blur = QDoubleSpinBox() self.whsed_small_blur.setSingleStep(0.1) self.whsed_small_blur.setRange(0, 100) self.whsed_small_blur.setValue(1.4) self.tab3.layout.addWidget(self.whsed_small_blur, 0, 3) self.wshed_rm_small_cells = QSpinBox() self.wshed_rm_small_cells.setSingleStep(1) self.wshed_rm_small_cells.setRange(0, 1_000_000) self.wshed_rm_small_cells.setValue(10) self.tab3.layout.addWidget(self.wshed_rm_small_cells, 0, 4) self.jSpinner11 = QSpinBox() self.jSpinner11.setSingleStep(1) self.jSpinner11.setRange(0, 1_000_000) self.jSpinner11.setValue(10) self.tab3.layout.addWidget(self.jSpinner11, 0, 5) label1_bis_tab3 = QLabel('Alternative Step 1:') self.tab3.layout.addWidget(label1_bis_tab3, 1, 0) self.skel = QPushButton("Skeletonize") self.skel.clicked.connect(self.skeletonize_mask) self.tab3.layout.addWidget(self.skel, 1, 1) label2_tab3 = QLabel('Step 2:') self.tab3.layout.addWidget(label2_tab3, 2, 0) self.apply_cell_body = QPushButton("Apply cell body") self.apply_cell_body.clicked.connect( self.apply_cell_body_to_skeletonized_mask) self.tab3.layout.addWidget(self.apply_cell_body, 2, 1) label3_tab3 = QLabel('Step 3 (Optional):') self.tab3.layout.addWidget(label3_tab3, 3, 0) self.prune = QPushButton("Prune") self.prune.clicked.connect(self.prune_dendrites) self.tab3.layout.addWidget(self.prune, 3, 1) self.prune_length = QSpinBox() self.prune_length.setSingleStep(1) self.prune_length.setRange(0, 1_000_000) self.prune_length.setValue(3) self.tab3.layout.addWidget(self.prune_length, 3, 2) label4_tab3 = QLabel('Step 4 (Optional):') self.tab3.layout.addWidget(label4_tab3, 4, 0) self.find_neurons = QPushButton("Find neurons") self.find_neurons.clicked.connect(self.find_neurons_in_mask) self.tab3.layout.addWidget(self.find_neurons, 4, 1) self.find_neurons_min_size = QSpinBox() self.find_neurons_min_size.setSingleStep(1) self.find_neurons_min_size.setRange(0, 1_000_000) self.find_neurons_min_size.setValue(45) self.tab3.layout.addWidget(self.find_neurons_min_size, 4, 2) self.prune_unconnected_segments = QPushButton( "Prune unconnected segments (run 'Find neurons' first)") self.prune_unconnected_segments.clicked.connect( self.prune_neuron_unconnected_segments) self.tab3.layout.addWidget(self.prune_unconnected_segments, 4, 3) label6_tab3 = QLabel('Step 5: Save') self.tab3.layout.addWidget(label6_tab3, 5, 0) label5_tab3 = QLabel('Step 6:') self.tab3.layout.addWidget(label5_tab3, 6, 0) self.create_n_save_bonds = QPushButton("Segment dendrites") self.create_n_save_bonds.clicked.connect(self.save_segmented_bonds) self.tab3.layout.addWidget(self.create_n_save_bonds, 6, 1) self.tab3.setLayout(self.tab3.layout) # Add tabs to widget table_widget_layout.addWidget(self.tabs) self.table_widget.setLayout(table_widget_layout) self.Stack = QStackedWidget(self) self.Stack.addWidget(self.scrollArea) # create a grid that will contain all the GUI interface self.grid = QGridLayout() self.grid.addWidget(self.Stack, 0, 0) self.grid.addWidget(self.list, 0, 1) # The first parameter of the rowStretch method is the row number, the second is the stretch factor. So you need two calls to rowStretch, like this: --> below the first row is occupying 80% and the second 20% self.grid.setRowStretch(0, 75) self.grid.setRowStretch(2, 25) # first col 75% second col 25% of total width self.grid.setColumnStretch(0, 75) self.grid.setColumnStretch(1, 25) # void QGridLayout::addLayout(QLayout * layout, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment = 0) self.grid.addWidget(self.table_widget, 2, 0, 1, 2) # spans over one row and 2 columns # BEGIN TOOLBAR # pen spin box and connect self.penSize = QSpinBox() self.penSize.setSingleStep(1) self.penSize.setRange(1, 256) self.penSize.setValue(3) self.penSize.valueChanged.connect(self.penSizechange) self.channels = QComboBox() self.channels.addItem("merge") self.channels.currentIndexChanged.connect(self.channelChange) tb_drawing_pane = QToolBar() save_button = QToolButton() save_button.setText("Save") save_button.clicked.connect(self.save_current_mask) tb_drawing_pane.addWidget(save_button) tb_drawing_pane.addWidget(QLabel("Channels")) tb_drawing_pane.addWidget(self.channels) # tb.addAction("Save") # tb_drawing_pane.addWidget(QLabel("Pen size")) tb_drawing_pane.addWidget(self.penSize) self.grid.addWidget(tb_drawing_pane, 1, 0) # END toolbar # toolbar for the list tb_list = QToolBar() del_button = QToolButton() del_button.setText("Delete selection from list") del_button.clicked.connect(self.delete_from_list) tb_list.addWidget(del_button) self.grid.addWidget(tb_list, 1, 1) # self.setCentralWidget(self.scrollArea) self.setCentralWidget(QFrame()) self.centralWidget().setLayout(self.grid) # self.statusBar().showMessage('Ready') statusBar = self.statusBar( ) # sets an empty status bar --> then can add messages in it self.paint.statusBar = statusBar # add progress bar to status bar self.progress = QProgressBar(self) self.progress.setGeometry(200, 80, 250, 20) statusBar.addWidget(self.progress) # Set up menu bar self.mainMenu = self.menuBar() self.zoomInAct = QAction( "Zoom &In (25%)", self, # shortcut="Ctrl++", enabled=True, triggered=self.zoomIn) self.zoomOutAct = QAction( "Zoom &Out (25%)", self, # shortcut="Ctrl+-", enabled=True, triggered=self.zoomOut) self.normalSizeAct = QAction( "&Normal Size", self, # shortcut="Ctrl+S", enabled=True, triggered=self.defaultSize) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.menuBar().addMenu(self.viewMenu) self.setMenuBar(self.mainMenu) # set drawing window fullscreen fullScreenShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_F), self) fullScreenShortcut.activated.connect(self.fullScreen) fullScreenShortcut.setContext(QtCore.Qt.ApplicationShortcut) escapeShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) escapeShortcut.activated.connect(self.escape) escapeShortcut.setContext(QtCore.Qt.ApplicationShortcut) # Show/Hide the mask escapeShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_H), self) escapeShortcut.activated.connect(self.showHideMask) escapeShortcut.setContext(QtCore.Qt.ApplicationShortcut) zoomPlus = QtWidgets.QShortcut("Ctrl+Shift+=", self) zoomPlus.activated.connect(self.zoomIn) zoomPlus.setContext(QtCore.Qt.ApplicationShortcut) zoomPlus2 = QtWidgets.QShortcut("Ctrl++", self) zoomPlus2.activated.connect(self.zoomIn) zoomPlus2.setContext(QtCore.Qt.ApplicationShortcut) zoomMinus = QtWidgets.QShortcut("Ctrl+Shift+-", self) zoomMinus.activated.connect(self.zoomOut) zoomMinus.setContext(QtCore.Qt.ApplicationShortcut) zoomMinus2 = QtWidgets.QShortcut("Ctrl+-", self) zoomMinus2.activated.connect(self.zoomOut) zoomMinus2.setContext(QtCore.Qt.ApplicationShortcut) spaceShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Space), self) spaceShortcut.activated.connect(self.nextFrame) spaceShortcut.setContext(QtCore.Qt.ApplicationShortcut) backspaceShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Backspace), self) backspaceShortcut.activated.connect(self.prevFrame) backspaceShortcut.setContext(QtCore.Qt.ApplicationShortcut) # connect enter keys to edit dendrites enterShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Return), self) enterShortcut.activated.connect(self.runSkel) enterShortcut.setContext(QtCore.Qt.ApplicationShortcut) enter2Shortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Enter), self) enter2Shortcut.activated.connect(self.runSkel) enter2Shortcut.setContext(QtCore.Qt.ApplicationShortcut) #Qt.Key_Enter is the Enter located on the keypad: #Qt::Key_Return 0x01000004 #Qt::Key_Enter 0x01000005 Typically located on the keypad. self.setAcceptDrops(True) # KEEP IMPORTANT def __get_mask_img_from_overlay(self): if self.paint.imageDraw: channels_count = 4 s = self.paint.imageDraw.bits().asstring( self.img.shape[0] * self.img.shape[1] * channels_count) arr = np.frombuffer(s, dtype=np.uint8).reshape( (self.img.shape[0], self.img.shape[1], channels_count)) return Img(arr[..., 2].copy(), dimensions='hw') else: return None def __get_output_folder(self): selected_items = self.list.selectedItems() if selected_items: filename = selected_items[0].toolTip() filename0_without_ext = os.path.splitext(filename)[0] return filename0_without_ext else: return None def delete_from_list(self): list_items = self.list.selectedItems() # empty list --> nothing to do if not list_items: return for item in list_items: self.list.takeItem(self.list.row(item)) def save_current_mask(self): output_folder = self.__get_output_folder() if output_folder is None: logger.error('No image is selected --> nothing to save') return mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to save') return if self.tabs.currentIndex() == 0: print('saving', os.path.join(output_folder, 'mask.tif')) mask.save(os.path.join(output_folder, 'mask.tif')) elif self.tabs.currentIndex() == 1: print('saving', os.path.join(output_folder, 'cellBodyMask.tif')) mask.save(os.path.join(output_folder, 'cellBodyMask.tif')) else: print('saving', os.path.join(output_folder, 'handCorrection.tif')) mask.save(os.path.join(output_folder, 'handCorrection.tif')) def detect_neuronal_cell_body(self): try: # get image and detect cell body mask = detect_cell_body( self.img, fillHoles=self.fill_holes_up_to.value(), denoise=self.min_obj_size_px.value(), nbOfErosions=self.nb_erosion_cellbody.value(), nbOfDilatations=self.nb_dilation_cellbody.value(), extraCutOff=self.extraCutOff_cell_body.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error('Cell body could not be detected') except: traceback.print_exc() def __get_neuronal_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'mask.tif')): # NB should I check the nb of channels --> no I don't want to handle externally created files and want people to rely fully on my stuff that has return Img(os.path.join(output_folder, 'mask.tif')) else: if warn: logger.error( 'Neuronal mask not found --> Please create one in the "Mask neuron" tab first' ) return None def __get_corrected_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'handCorrection.tif')): return Img(os.path.join(output_folder, 'handCorrection.tif')) elif os.path.exists(os.path.join(output_folder, 'mask.tif')) and not warn: return Img(os.path.join(output_folder, 'mask.tif')) return None def __get_cellbody_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'cellBodyMask.tif')): # NB should I check the nb of channels --> no I don't want to handle externally created files and want people to rely fully on my stuff that has return Img(os.path.join(output_folder, 'cellBodyMask.tif')) else: if warn: logger.error( 'Cell body mask not found --> Please create one in the "Mask cell body" tab first' ) return None # seems ok for now def watershed_segment_the_neuron(self): try: # get raw image and segment it using the watershed algorithm # make it load the neuronal mask neuronal_mask = self.__get_neuronal_mask() if neuronal_mask is None: return # TODO should I add autoskel or not mask = watershed_segment_neuron( self.img, neuronal_mask, fillSize=self.jSpinner11.value(), autoSkel=True, first_blur=self.whsed_big_blur.value(), second_blur=self.whsed_small_blur.value(), min_size=self.wshed_rm_small_cells.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error( 'Something went wrong, the neuron could not be segmented, sorry...' ) except: traceback.print_exc() def save_segmented_bonds(self): output_folder = self.__get_output_folder() if output_folder is None: logger.error('No image is selected --> nothing to save') return # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask = detect_cell_bonds(mask) if mask is None: logger.error( 'Could not find dendrites, are you sure a mask is overlayed over the neuron' ) return # code for conversion of 24 bits numpy array to an RGB one --> keep and store in Img at some point cause useful # convert 24 bits array to RGB RGB_mask = np.zeros(shape=(*mask.shape, 3), dtype=np.uint8) RGB_mask[..., 2] = mask & 255 RGB_mask[..., 1] = (mask >> 8) & 255 RGB_mask[..., 0] = (mask >> 16) & 255 Img(RGB_mask, dimensions='hwc').save(os.path.join(output_folder, 'bonds.tif')) def prune_neuron_unconnected_segments(self): # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask = find_neurons( mask, neuron_minimum_size_threshold=self.find_neurons_min_size.value(), return_unconnected=True) if mask is None: logger.error( 'Could not find neurons, are you sure a mask is overlayed over the neuron' ) return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def apply_cell_body_to_skeletonized_mask(self): mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return cell_body_mask = self.__get_cellbody_mask() if cell_body_mask is None: return cell_body_outline = get_cell_body_outline(cell_body_mask, mask) if cell_body_outline is None: logger.error( 'Error could not add cell body outline to the neuronal mask...' ) return self.paint.imageDraw = Img(self.createRGBA(cell_body_outline), dimensions='hwc').getQimage() self.paint.update() def find_neurons_in_mask(self): # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask_copy = mask.copy() mask = find_neurons( mask, neuron_minimum_size_threshold=self.find_neurons_min_size.value()) if mask is None: logger.error( 'Could not find neurons, are you sure a mask is overlayed over the neuron' ) return # we set the red channel, the blue one, the alpha transparency (channel 4) and finally we only allow alpha channel in the two masks regions to keep the rest of the stuff final_overlay = np.zeros(shape=(*mask_copy.shape, 4), dtype=np.uint8) final_overlay[..., 0] = np.logical_xor(mask, mask_copy).astype( np.uint8) * 255 # blue channel final_overlay[mask == 0, 0] = 0 final_overlay[..., 1] = final_overlay[ ..., 0] # green channel # copy the channel to make the stuff appear cyan final_overlay[..., 2] = mask_copy # red channel final_overlay[np.logical_or(mask, mask_copy) != 0, 3] = 255 # --> need set alpha transparency of the image self.paint.imageDraw = Img(final_overlay, dimensions='hwc').getQimage() self.paint.update() def prune_dendrites(self): prune_lgth = self.prune_length.value() if prune_lgth <= 0: logger.info('prune length is 0 --> nothing to do') return # get the mask from displayed image mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return # see how to get the stuff ???? mask = prune_dendrites(mask, prune_below=prune_lgth) if mask is None: logger.error( 'Could not prune dendrites, are you sure there is a mask ovrlayed on the neuron' ) return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def skeletonize_mask(self): # get mask then skeletonize it then return it --> see exactly try: # get raw image and segment it using the skeletonize algorithm # make it load the neuronal mask neuronal_mask = self.__get_neuronal_mask() if neuronal_mask is None: return mask = skel_segment_neuronal_mask(neuronal_mask) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error( 'Something went wrong, the neuron could not be sekeletonized, sorry...' ) except: traceback.print_exc() def _onTabChange(self): # if tab is changed --> do stuff # load files or warn... if self.tabs.currentIndex() == 0: mask = self.__get_neuronal_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() elif self.tabs.currentIndex() == 1: mask = self.__get_cellbody_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() elif self.tabs.currentIndex() == 2: mask = self.__get_corrected_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def run_threshold_neuron(self): try: local_or_global = 'global' if self.sender() == self.local_threshold: local_or_global = 'local' elif self.sender() == self.local_n_global_threshold: local_or_global = 'local+global' mask = threshold_neuron( self.img, mode=local_or_global, blur_method=self.threshold_method.currentText(), spin_value=self.extra_value_for_threshold.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() except: traceback.print_exc() def channelChange(self, i): if self.Stack.currentIndex() == 0: if i == 0: self.paint.setImage(self.img) else: channel_img = self.img.imCopy(c=i - 1) self.paint.setImage(channel_img) self.paint.update() def penSizechange(self): self.paint.brushSize = self.penSize.value() def selectionChanged(self): self.paint.maskVisible = True selected_items = self.list.selectedItems() if selected_items: start = timer() if self.img is not None: # make sure we don't load the image twice if selected_items[0].toolTip() != self.img.metadata['path']: self.img = Img(selected_items[0].toolTip()) logger.debug("took " + str(timer() - start) + " secs to load image") else: logger.debug("image already loaded --> ignoring") else: self.img = Img(selected_items[0].toolTip()) logger.debug("took " + str(timer() - start) + " secs to load image") if self.img is not None: selection = self.channels.currentIndex() self.channels.disconnect() self.channels.clear() comboData = ['merge'] if self.img.has_c(): for i in range(self.img.get_dimension('c')): comboData.append(str(i)) logger.debug('channels found ' + str(comboData)) self.channels.addItems(comboData) if selection != -1 and selection < self.channels.count(): self.channels.setCurrentIndex(selection) else: self.channels.setCurrentIndex(0) self.channels.currentIndexChanged.connect(self.channelChange) if selected_items: self.statusBar().showMessage('Loading ' + selected_items[0].toolTip()) selection = self.channels.currentIndex() if selection == 0: self.paint.setImage(self.img) else: self.paint.setImage(self.img.imCopy(c=selection - 1)) self.scaleImage(0) self.update() self.paint.update() if self.list.currentItem() and self.list.currentItem().icon( ).isNull(): logger.debug('Updating icon') icon = QIcon(QPixmap.fromImage(self.paint.image)) pixmap = icon.pixmap(24, 24) icon = QIcon(pixmap) self.list.currentItem().setIcon(icon) else: logger.debug("Empty selection") self.paint.image = None self.scaleImage(0) self.update() self.paint.update() self.img = None # try update also the masks if they are available try: self._onTabChange() except: pass def clearlayout(self, layout): for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) def showHideMask(self): self.paint.maskVisible = not self.paint.maskVisible self.paint.update() def escape(self): if self.Stack.isFullScreen(): self.fullScreen() def fullScreen(self): if not self.Stack.isFullScreen(): self.Stack.setWindowFlags( QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | # QtCore.Qt.WindowTitleHint | # QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint) self.Stack.showFullScreen() else: self.Stack.setWindowFlags(QtCore.Qt.Widget) self.grid.addWidget(self.Stack, 0, 0) # dirty hack to make it repaint properly --> obviously not all lines below are required but some are --> need test, the last line is key though self.grid.update() self.Stack.update() self.Stack.show() self.centralWidget().setLayout(self.grid) self.centralWidget().update() self.update() self.show() self.repaint() self.Stack.update() self.Stack.repaint() self.centralWidget().repaint() def nextFrame(self): idx = self.list.model().index(self.list.currentRow() + 1, 0) if idx.isValid(): self.list.selectionModel().setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect) # SelectCurrent def remove_blobs(self): blob_size = self.remove_blobs_size.value() if blob_size <= 0: logger.info('blob size is 0 --> nothing to do') return # get the mask from displayed image mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to save') return mask = remove_small_objects(mask.astype(np.bool), min_size=blob_size, connectivity=2, in_place=False) # then place back pixels in the mask # now set the mask back # plt.imshow(mask) # plt.show() self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def runSkel(self): # only allow that for tab 3 if self.tabs.currentIndex() == 2: mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return # just skeletonize the image mask = skel_segment_neuronal_mask( mask, fill_holes=0) # should I put it to 0 or other things ??? if mask is None: logger.error('Could not skeletonize user edited mask...') return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def createRGBA(self, handCorrection): # use pen color to display the mask # in fact I need to put the real color RGBA = np.zeros((handCorrection.shape[0], handCorrection.shape[1], 4), dtype=np.uint8) red = self.paint.drawColor.red() green = self.paint.drawColor.green() blue = self.paint.drawColor.blue() # bug somewhere --> fix it some day --> due to bgra instead of RGBA RGBA[handCorrection != 0, 0] = blue # b RGBA[handCorrection != 0, 1] = green # g RGBA[handCorrection != 0, 2] = red # r RGBA[..., 3] = 255 # alpha --> indeed alpha RGBA[handCorrection == 0, 3] = 0 # very complex fix some day return RGBA def prevFrame(self): idx = self.list.model().index(self.list.currentRow() - 1, 0) if idx.isValid(): self.list.selectionModel().setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect) def zoomIn(self): self.statusBar().showMessage('Zooming in', msecs=200) if self.Stack.currentIndex() == 0: self.scaleImage(self.zoom_increment) def zoomOut(self): self.statusBar().showMessage('Zooming out', msecs=200) if self.Stack.currentIndex() == 0: self.scaleImage(-self.zoom_increment) def defaultSize(self): self.paint.adjustSize() self.scale = 1.0 self.scaleImage(0) def scaleImage(self, factor): self.scale += factor if self.paint.image is not None: self.paint.resize(self.scale * self.paint.image.size()) else: # no image set size to 0, 0 --> scroll pane will auto adjust self.paint.resize(QSize(0, 0)) self.scale -= factor # reset zoom self.paint.scale = self.scale # self.paint.vdp.scale = self.scale self.zoomInAct.setEnabled(self.scale < self.max_scaling_factor) self.zoomOutAct.setEnabled(self.scale > self.min_scaling_factor) # allow DND def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() else: event.ignore() # handle DND on drop def dropEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() urls = [] for url in event.mimeData().urls(): urls.append(url.toLocalFile()) for url in urls: import os item = QListWidgetItem(os.path.basename(url), self.list) item.setToolTip(url) self.list.addItem(item) else: event.ignore()