Drag and Drop image doesn't work in qgraphicview
-
I'm new to Qt (first post here), and honestly this journey has been exciting and frustrating at the same time, but learning a lot of new stuff. I'm developing an app to view and analyze thermal images and want to add drag and drop feature on viewer, which is based on QGraphicsView. Prior the viewer was based on a Qlabel and it was working perfectly, but as I want to add zoom, pan, ROis I have found in my study that using QGraphicsview was the right choice, so I switched the viewer. Now, the drag and drop image feature is not working, tho the mouse events are being registered when printed. The image does not update (nothing happens), it might be some detail I'm missing, so if anyone can figure it out I would be grateful.
As I depend on some matrix to do all thermal calculations and processing when I subclass some widget I probably will have to use attributes or methods from on class to another. In this case, I had to create a instance on Qgraphicsview to use a method (set_image()) and this is really where I'm struggling (however it looks it working according to the printed events).
Here is the entire code, I also accept suggestion on design and code management, because I fear this may end up being a monster that will be hard to update and mantain:
import sys import os from PyQt6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QPixmap, QImage, QMouseEvent, QPainter, QColor, QAction, QIcon from PyQt6.QtWidgets import (QWidget, QApplication, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QGroupBox, QFileDialog, QLineEdit, QMessageBox, QErrorMessage, QGraphicsScene, QToolBar, QGraphicsView, QGraphicsPixmapItem, QMainWindow) from PyQt6.QtCore import Qt, QEvent, QSize from superqt import QLabeledDoubleRangeSlider from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas import matplotlib.pyplot as plt import matplotlib as mpl from matplotlib.pylab import * import cv2 import numpy as np from functions import * os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' class LeftPanel(QWidget): def __init__(self): super().__init__() ##ATRIBUTES---------------------------------------------------------------------------------------------------------------------------------------------- self.setAcceptDrops(True) self.exiftool_path = "exiftool" self.filename = None #will hold image address location self.tmp = None #will hold temporary image for display self.pixmapitem = QGraphicsPixmapItem() self.max_img_width = 640 self.max_img_height = 480 self.flir_raw = None self.pdkta_raw = None self.thermal_matrix = None self.thermal_matrix_width = None self.thermal_matrix_height = None self.processed_frame = None self.thermal_frame_resized = None self.colorized_frame = None self.cliped_img = None self.vmax = 35 self.vmin = 20 self.max_limit = None self.min_limit = None self.min_limit_init = 20.0 self.max_limit_init = 40.0 self.clipped_min = 0 self.clipped_max = 0 self.slider_values = 0 self.pallete_folder = "palettes" self.palette_file = "palettes/1_rain.pal" self.cmap_mat = create_colormap_mat(self.palette_file) #set initial palette for colorbar self.cmap_cv = None #set initial palette for img self.palette_list = self.populate_pallete() #populate palette list on combo box ##-------------------------------------------------------------------------------------------------------------------------------------------------- ##LAYOUT------------------------------------------------------------------------------------------------------------------------------------------------------- #Set top_bar layout #self.top_bar_groupbox = QGroupBox() self.top_layout = QHBoxLayout() self.toolbar = QToolBar() self.toolbar.setStyleSheet("QToolBar { border-bottom: 1px solid #DCDCDC; border-top: 1px solid #DCDCDC; padding: 3px;}") self.toolbar.setIconSize(QSize(26, 26)) self.top_layout.addWidget(self.toolbar) #Open self.openaction = QAction("Open File", self) self.openaction.setIcon(QIcon("assets/file-open.svg")) self.openaction.triggered.connect(self.choose_img) self.toolbar.addAction(self.openaction) #Save self.saveaction = QAction("Save File", self) self.saveaction.setIcon(QIcon("assets/file-save.svg")) self.saveaction.triggered.connect(self.save_image) self.toolbar.addAction(self.saveaction) #self.top_bar_groupbox.setLayout(self.top_layout) #Save CSV self.savecsvaction = QAction("Save CSV", self) self.savecsvaction.setIcon(QIcon("assets/csv-vector-icon.png")) self.savecsvaction.triggered.connect(self.export_csv) self.toolbar.addAction(self.savecsvaction) self.toolbar.addSeparator() #set horizontal layout for viewer/colorbar self.h_layout = QHBoxLayout() self.h_layout.setSpacing(3) self.h_layout.setContentsMargins(0, 0, 0, 0) #set Image Container self.viewer_groupbox = QGroupBox() # self.label_groupbox.setMaximumWidth(self.max_img_width+50) # self.label_groupbox.setMaximumHeight(self.max_img_height+50) self.viewer_layout = QVBoxLayout() self.scene = QGraphicsScene() self.viewer = ImageViewer() self.pixmapitem.setPixmap(QPixmap("assets/placeholder.jpg")) self.scene.addItem(self.pixmapitem) self.viewer.setScene(self.scene) #Add Label to layout self.viewer_layout.addWidget(self.viewer) self.viewer_groupbox.setLayout(self.viewer_layout) #set ColorBar self.cb_groupbox = QGroupBox() self.cb_groupbox.setFixedWidth(90) self.cb_layout = QVBoxLayout() self.colorbar = FigureCanvas(self.create_colorbar()) self.colorbar.setFixedHeight(self.max_img_height) # self.colorbar.setFixedWidth(70) #Add CB to layout self.cb_layout.addWidget(self.colorbar) self.cb_groupbox.setLayout(self.cb_layout) #Buttons Container self.buttons_groupbox = QGroupBox() self.buttons_layout = QHBoxLayout(self.buttons_groupbox) self.button1 = QPushButton("Open") self.button1.clicked.connect(self.choose_img) self.button2 = QPushButton("PDKTA") self.button2.clicked.connect(self.pdkta_process) self.button3 = QPushButton("FLIR") self.button3.clicked.connect(self.flir_process) self.button4 = QPushButton("Save") self.button4.clicked.connect(self.save_image) self.button5 = QPushButton("Export CSV") self.button5.clicked.connect(self.export_csv) #Set Combo Box self.combo1 = QComboBox() for i in range (len(self.palette_list)): self.combo1.addItem(self.palette_list[i]) self.combo1.setPlaceholderText("--Set Pallete--") self.combo1.setCurrentIndex(-1) self.combo1.currentTextChanged.connect(self.handleComboBoxEvent) #Add Buttons to Slider Widget self.buttons_layout.addWidget(self.button1) self.buttons_layout.addWidget(self.button2) self.buttons_layout.addWidget(self.button3) self.buttons_layout.addWidget(self.button4) self.buttons_layout.addWidget(self.button5) self.buttons_layout.addWidget(self.combo1) #Set Slider Container self.slider_groupbox = QGroupBox() self.slider_groupbox.setObjectName("slider_container") #self.slider_groupbox.setMaximumWidth(self.max_img_width-100) self.slider_layout = QVBoxLayout(self.slider_groupbox) self.slider1 = QLabeledDoubleRangeSlider(Qt.Orientation.Horizontal, self) self.slider1.setEdgeLabelMode(self.slider1.EdgeLabelMode.NoLabel) #self.slider1.setHandleLabelPosition(self.slider1.LabelPosition.NoLabel) self.slider1.valueChanged.connect(self.update_slider) self.slider1.mousePressEvent = lambda event: self.on_double_click_slider() self.slider1.setValue((self.min_limit_init, self.max_limit_init)) self.slider1.setRange(15, 45) self.slider1.setSingleStep(0.01) self.slider1.setStyleSheet(open("qss/styles.qss", 'r').read()) self.slider_layout.addWidget(self.slider1) #Create Main Layout self.main_layout = QVBoxLayout() self.main_layout.setSpacing(3) self.main_layout.setContentsMargins(0,0,0,0) #Add top Bar to main layout self.main_layout.addLayout(self.top_layout) #Add Horizontal Widgets to HLayout self.h_layout.addWidget(self.viewer_groupbox, alignment=Qt.AlignmentFlag.AlignLeft) self.h_layout.addWidget(self.cb_groupbox, alignment=Qt.AlignmentFlag.AlignLeft) #Add Horizontal Widgets to Main Layout self.main_layout.addLayout(self.h_layout) #Add Vertical Widgets to Main Layout self.main_layout.addWidget(self.buttons_groupbox) self.main_layout.addWidget(self.slider_groupbox) #Set Main Layout self.setLayout(self.main_layout) ##METHODS------------------------------------------------------------------------------------------------------------------------------------------------------- #this function open dialog and allows to choose Predikta/FLIR .PNG .JPG files def choose_img(self): self.dialog = QFileDialog(self) # self.dialog.setNameFilter("Images (*.png *.jpg)") self.dialog.setDirectory(r"C:\Users\desuo\Documents\Codes\Projetos_PDKTA\ImageEditor\examples") self.filename = self.dialog.getOpenFileName(filter="Image (*.PNG *.JPG)")[0] if not self.filename: return try: self.image_data = cv2.imread(self.filename, -1) self.tmp = self.image_data image_data_resized = resize_image(self.tmp, self.max_img_width, self.max_img_height) self.pixmapitem.setPixmap(set_pixmap_original(image_data_resized)) self.processed_frame = None self.cmap_cv = None self.combo1.blockSignals(True) self.combo1.setCurrentIndex(-1) self.combo1.blockSignals(False) self.min_limit = None self.max_limit = None self.thermal_frame_resized = None self.cliped_img = None self.processed_frame = None self.colorized_frame = None self.pdkta_raw = None self.flir_raw = None self.slider1.setValue((self.min_limit_init, self.max_limit_init)) except Exception as e: QMessageBox.warning(self, 'Error', f'O seguinte erro ocorreu:\n{type(e)}: {e}') return #This function sets the initial value of slider after the image being processed def adjust_initial_slider(self): self.slider1.setValue((self.min_limit, self.max_limit)) return #This functions processes the selected image using PREDIKTA framework def pdkta_process(self): if self.tmp is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Nenhuma imagem selecionada!') error_dialog.exec() elif self.filename.endswith((".jpg")): error_dialog = QErrorMessage(self) error_dialog.showMessage('Esta não é uma imagem PDKTA, por favor, escolha uma imagem compatível!') error_dialog.exec() else: self.cmap_cv = create_colormap_cv("palettes/1_rain.pal") self.pdkta_raw = pdkta_frame2raw(self.tmp) self.thermal_matrix = pdkta_raw2temp(self.pdkta_raw) self.thermal_matrix_height = self.thermal_matrix.shape[0] self.thermal_matrix_width = self.thermal_matrix.shape[1] self.thermal_frame_resized = resize_image(self.thermal_matrix, self.max_img_width, self.max_img_height) self.processed_frame = frame_processor(self.thermal_frame_resized) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.combo1.setCurrentText("1_rain.pal") self.max_limit = np.max(self.thermal_matrix) self.min_limit = np.min(self.thermal_matrix) self.adjust_initial_slider() return #This functions processes the selected image using FLIR framework def flir_process(self): if self.tmp is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Nenhuma imagem selecionada!') error_dialog.exec() elif self.filename.endswith((".png")): error_dialog = QErrorMessage(self) error_dialog.showMessage('Esta não é uma imagem FLIR, por favor, escolha uma imagem compatível!') error_dialog.exec() else: get_meta(self.filename) self.flir_raw = flir2raw(self.filename) flir2temp_vec = vectorize_function(flir2temp) self.thermal_matrix = flir2temp_vec(self.flir_raw) self.thermal_matrix_height = self.thermal_matrix.shape[0] self.thermal_matrix_width = self.thermal_matrix.shape[1] self.thermal_frame_resized = resize_image(self.thermal_matrix, self.max_img_width, self.max_img_height) self.processed_frame = frame_processor(self.thermal_frame_resized) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.combo1.setCurrentText("1_rain.pal") self.max_limit = np.max(self.thermal_matrix) self.min_limit = np.min(self.thermal_matrix) self.adjust_initial_slider() return #This function will populate palettes from folder def populate_pallete(self): folder_path = "palettes" if folder_path: self.file_names = [file for file in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, file))] return self.file_names #this function will create a matplotlib colorbar def create_colorbar(self): fig, ax = plt.subplots(figsize=(1, 6), layout='constrained') fig.set_facecolor('#F0F0F0') cmap = self.cmap_mat norm = mpl.colors.Normalize(vmin=self.vmin, vmax=self.vmax) fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='vertical', label='', format=lambda x, _: f"{x:.1f}") ax.tick_params(labelsize = 8) ax.minorticks_on() plt.close() return fig #this function will clear past colorbar and update with a new one with new parameters def update_colorbar(self): for i in reversed(range(self.cb_layout.count())): widgetToRemove = self.cb_layout.itemAt(i).widget() if widgetToRemove is not None: self.cb_layout.removeWidget(widgetToRemove) widgetToRemove.deleteLater() self.colorbar = FigureCanvas(self.create_colorbar()) self.cb_layout.addWidget(self.colorbar) self.cb_groupbox.setLayout(self.cb_layout) self.colorbar.setFixedHeight(self.max_img_height) # self.colorbar.setFixedWidth(70) return #this function will update palette drop menu def handleComboBoxEvent(self): if self.processed_frame is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Por favor, processe a imagem antes de aplicar o palette!') error_dialog.exec() else: self.palette_file = os.path.join(self.pallete_folder, self.combo1.currentText()) self.cmap_cv = create_colormap_cv(self.palette_file) self.cmap_mat = create_colormap_mat(self.palette_file) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.update_colorbar() #this function updates range slider values def update_slider(self, values): self.slider_values = values self.update_image_threshold() return values #this funcrion thresholds the image according to slider handle values def update_image_threshold(self): if self.thermal_frame_resized is not None: self.clipped_min = self.slider_values[0] self.clipped_max = self.slider_values[1] A = self.thermal_frame_resized B = np.where(A == np.max(A), np.max(A)+20, (np.where(A == np.min(A), np.min(A)-20, A))) self.cliped_img = np.clip(B, self.clipped_min, self.clipped_max) self.processed_frame = frame_processor(self.cliped_img) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.vmax = self.clipped_max self.vmin = self.clipped_min self.update_colorbar() else: pass return #This function will reset the slider and viewer to initial min/max image values def on_double_click_slider(self): if self.processed_frame is not None: event = QMouseEvent self.adjust_initial_slider() self.update_image_threshold() else: pass return def save_image(self): self.filename = QFileDialog.getSaveFileName(filter="JPG(*.jpg);;PNG(*.png)")[0] # self.imagevar = QImage(self.colorbar.size(), QImage.Format.Format_ARGB32) # self.imagevar = self.pixmap2array(self.imagevar) # saved_img = np.concatenate((self.colorized_frame, self.imagevar), axis=1) # cv2.imwrite(self.filename, saved_img) if not self.filename: return imagevar = self.colorbar.grab(self.colorbar.rect()) imagevar.save("examples/colorbar.png") img = cv2.imread("examples/colorbar.png") width = int(img.shape[1]) height = self.colorized_frame.shape[0] dim = (width, height) img = cv2.resize(img, dim, interpolation=None) saved_img = np.concatenate((self.colorized_frame, img), axis=1) cv2.imwrite(self.filename, saved_img) def pixmap2array(self, pixmap): channels_count = 4 size = pixmap.size() h = size.width() w = size.height() image = pixmap.toImage() s = image.bits().asstring(w * h * channels_count) arr = np.fromstring(s, dtype=np.uint8).reshape((h, w, channels_count)) return arr def export_csv(self): if self.thermal_matrix is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('No há matrix térmica processada!') error_dialog.exec() else: self.filename = QFileDialog.getSaveFileName(self, "Export CSV", "exports", filter="CSV(*.csv)")[0] if not self.filename: return csv_matrix = self.thermal_matrix export_csv(csv_matrix, self.filename) print(self.filename) def set_image(self): #load image----- self.image_data = cv2.imread(self.filename, -1) self.tmp = self.image_data image_data_resized = resize_image(self.tmp, self.max_img_width, self.max_img_height) self.pixmapitem.setPixmap(set_pixmap_original(image_data_resized)) self.scene.addItem(self.pixmapitem) self.viewer.setScene(self.scene) #configurations---- self.processed_frame = None self.cmap_cv = None self.combo1.blockSignals(True) self.combo1.setCurrentIndex(-1) self.combo1.blockSignals(False) self.min_limit = None self.max_limit = None self.thermal_frame_resized = None self.cliped_img = None self.processed_frame = None self.colorized_frame = None self.slider1.setValue((self.min_limit_init, self.max_limit_init)) class ImageViewer(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) # self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setAcceptDrops(True) #This is the drag and drop event -------------------------------------------------------- def dragEnterEvent(self, event): if event.mimeData().hasImage: event.accept() print("accepted") else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasImage: event.accept() print("accepted2") else: event.ignore() def dropEvent(self, event): if event.mimeData().hasImage: try: event.setDropAction(Qt.CopyAction) self.main_window_instance = LeftPanel() self.main_window_instance.filename = event.mimeData().urls()[0].toLocalFile() self.main_window_instance.set_image() event.accept() print(self.main_window_instance.pixmapitem) except Exception as e: QMessageBox.warning(self, 'Error', f'O seguinte erro ocorreu:\n{type(e)}: {e}') returnhere is the main:
import sys import os from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QHBoxLayout, QScrollArea from PyQt6.QtGui import QIcon from testesdesignqview import LeftPanel class MainWindowGV(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Predikta Thermal Analyzer") self.setWindowIcon(QIcon("assets/logo.png")) layout = QHBoxLayout() layout.addWidget(LeftPanel(), alignment=Qt.AlignmentFlag.AlignLeft) #layout.addWidget(right_panel(), alignment=Qt.AlignmentFlag.AlignRight) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindowGV() window.show() app.exec()here the printed event of drag and drop:
accepted accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 <PyQt6.QtWidgets.QGraphicsPixmapItem object at 0x00000250EDEF4310>``` and finally how the app looks as of now:  Thanks in advance and sorry for the big post. -
I'm new to Qt (first post here), and honestly this journey has been exciting and frustrating at the same time, but learning a lot of new stuff. I'm developing an app to view and analyze thermal images and want to add drag and drop feature on viewer, which is based on QGraphicsView. Prior the viewer was based on a Qlabel and it was working perfectly, but as I want to add zoom, pan, ROis I have found in my study that using QGraphicsview was the right choice, so I switched the viewer. Now, the drag and drop image feature is not working, tho the mouse events are being registered when printed. The image does not update (nothing happens), it might be some detail I'm missing, so if anyone can figure it out I would be grateful.
As I depend on some matrix to do all thermal calculations and processing when I subclass some widget I probably will have to use attributes or methods from on class to another. In this case, I had to create a instance on Qgraphicsview to use a method (set_image()) and this is really where I'm struggling (however it looks it working according to the printed events).
Here is the entire code, I also accept suggestion on design and code management, because I fear this may end up being a monster that will be hard to update and mantain:
import sys import os from PyQt6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QPixmap, QImage, QMouseEvent, QPainter, QColor, QAction, QIcon from PyQt6.QtWidgets import (QWidget, QApplication, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QGroupBox, QFileDialog, QLineEdit, QMessageBox, QErrorMessage, QGraphicsScene, QToolBar, QGraphicsView, QGraphicsPixmapItem, QMainWindow) from PyQt6.QtCore import Qt, QEvent, QSize from superqt import QLabeledDoubleRangeSlider from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas import matplotlib.pyplot as plt import matplotlib as mpl from matplotlib.pylab import * import cv2 import numpy as np from functions import * os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' class LeftPanel(QWidget): def __init__(self): super().__init__() ##ATRIBUTES---------------------------------------------------------------------------------------------------------------------------------------------- self.setAcceptDrops(True) self.exiftool_path = "exiftool" self.filename = None #will hold image address location self.tmp = None #will hold temporary image for display self.pixmapitem = QGraphicsPixmapItem() self.max_img_width = 640 self.max_img_height = 480 self.flir_raw = None self.pdkta_raw = None self.thermal_matrix = None self.thermal_matrix_width = None self.thermal_matrix_height = None self.processed_frame = None self.thermal_frame_resized = None self.colorized_frame = None self.cliped_img = None self.vmax = 35 self.vmin = 20 self.max_limit = None self.min_limit = None self.min_limit_init = 20.0 self.max_limit_init = 40.0 self.clipped_min = 0 self.clipped_max = 0 self.slider_values = 0 self.pallete_folder = "palettes" self.palette_file = "palettes/1_rain.pal" self.cmap_mat = create_colormap_mat(self.palette_file) #set initial palette for colorbar self.cmap_cv = None #set initial palette for img self.palette_list = self.populate_pallete() #populate palette list on combo box ##-------------------------------------------------------------------------------------------------------------------------------------------------- ##LAYOUT------------------------------------------------------------------------------------------------------------------------------------------------------- #Set top_bar layout #self.top_bar_groupbox = QGroupBox() self.top_layout = QHBoxLayout() self.toolbar = QToolBar() self.toolbar.setStyleSheet("QToolBar { border-bottom: 1px solid #DCDCDC; border-top: 1px solid #DCDCDC; padding: 3px;}") self.toolbar.setIconSize(QSize(26, 26)) self.top_layout.addWidget(self.toolbar) #Open self.openaction = QAction("Open File", self) self.openaction.setIcon(QIcon("assets/file-open.svg")) self.openaction.triggered.connect(self.choose_img) self.toolbar.addAction(self.openaction) #Save self.saveaction = QAction("Save File", self) self.saveaction.setIcon(QIcon("assets/file-save.svg")) self.saveaction.triggered.connect(self.save_image) self.toolbar.addAction(self.saveaction) #self.top_bar_groupbox.setLayout(self.top_layout) #Save CSV self.savecsvaction = QAction("Save CSV", self) self.savecsvaction.setIcon(QIcon("assets/csv-vector-icon.png")) self.savecsvaction.triggered.connect(self.export_csv) self.toolbar.addAction(self.savecsvaction) self.toolbar.addSeparator() #set horizontal layout for viewer/colorbar self.h_layout = QHBoxLayout() self.h_layout.setSpacing(3) self.h_layout.setContentsMargins(0, 0, 0, 0) #set Image Container self.viewer_groupbox = QGroupBox() # self.label_groupbox.setMaximumWidth(self.max_img_width+50) # self.label_groupbox.setMaximumHeight(self.max_img_height+50) self.viewer_layout = QVBoxLayout() self.scene = QGraphicsScene() self.viewer = ImageViewer() self.pixmapitem.setPixmap(QPixmap("assets/placeholder.jpg")) self.scene.addItem(self.pixmapitem) self.viewer.setScene(self.scene) #Add Label to layout self.viewer_layout.addWidget(self.viewer) self.viewer_groupbox.setLayout(self.viewer_layout) #set ColorBar self.cb_groupbox = QGroupBox() self.cb_groupbox.setFixedWidth(90) self.cb_layout = QVBoxLayout() self.colorbar = FigureCanvas(self.create_colorbar()) self.colorbar.setFixedHeight(self.max_img_height) # self.colorbar.setFixedWidth(70) #Add CB to layout self.cb_layout.addWidget(self.colorbar) self.cb_groupbox.setLayout(self.cb_layout) #Buttons Container self.buttons_groupbox = QGroupBox() self.buttons_layout = QHBoxLayout(self.buttons_groupbox) self.button1 = QPushButton("Open") self.button1.clicked.connect(self.choose_img) self.button2 = QPushButton("PDKTA") self.button2.clicked.connect(self.pdkta_process) self.button3 = QPushButton("FLIR") self.button3.clicked.connect(self.flir_process) self.button4 = QPushButton("Save") self.button4.clicked.connect(self.save_image) self.button5 = QPushButton("Export CSV") self.button5.clicked.connect(self.export_csv) #Set Combo Box self.combo1 = QComboBox() for i in range (len(self.palette_list)): self.combo1.addItem(self.palette_list[i]) self.combo1.setPlaceholderText("--Set Pallete--") self.combo1.setCurrentIndex(-1) self.combo1.currentTextChanged.connect(self.handleComboBoxEvent) #Add Buttons to Slider Widget self.buttons_layout.addWidget(self.button1) self.buttons_layout.addWidget(self.button2) self.buttons_layout.addWidget(self.button3) self.buttons_layout.addWidget(self.button4) self.buttons_layout.addWidget(self.button5) self.buttons_layout.addWidget(self.combo1) #Set Slider Container self.slider_groupbox = QGroupBox() self.slider_groupbox.setObjectName("slider_container") #self.slider_groupbox.setMaximumWidth(self.max_img_width-100) self.slider_layout = QVBoxLayout(self.slider_groupbox) self.slider1 = QLabeledDoubleRangeSlider(Qt.Orientation.Horizontal, self) self.slider1.setEdgeLabelMode(self.slider1.EdgeLabelMode.NoLabel) #self.slider1.setHandleLabelPosition(self.slider1.LabelPosition.NoLabel) self.slider1.valueChanged.connect(self.update_slider) self.slider1.mousePressEvent = lambda event: self.on_double_click_slider() self.slider1.setValue((self.min_limit_init, self.max_limit_init)) self.slider1.setRange(15, 45) self.slider1.setSingleStep(0.01) self.slider1.setStyleSheet(open("qss/styles.qss", 'r').read()) self.slider_layout.addWidget(self.slider1) #Create Main Layout self.main_layout = QVBoxLayout() self.main_layout.setSpacing(3) self.main_layout.setContentsMargins(0,0,0,0) #Add top Bar to main layout self.main_layout.addLayout(self.top_layout) #Add Horizontal Widgets to HLayout self.h_layout.addWidget(self.viewer_groupbox, alignment=Qt.AlignmentFlag.AlignLeft) self.h_layout.addWidget(self.cb_groupbox, alignment=Qt.AlignmentFlag.AlignLeft) #Add Horizontal Widgets to Main Layout self.main_layout.addLayout(self.h_layout) #Add Vertical Widgets to Main Layout self.main_layout.addWidget(self.buttons_groupbox) self.main_layout.addWidget(self.slider_groupbox) #Set Main Layout self.setLayout(self.main_layout) ##METHODS------------------------------------------------------------------------------------------------------------------------------------------------------- #this function open dialog and allows to choose Predikta/FLIR .PNG .JPG files def choose_img(self): self.dialog = QFileDialog(self) # self.dialog.setNameFilter("Images (*.png *.jpg)") self.dialog.setDirectory(r"C:\Users\desuo\Documents\Codes\Projetos_PDKTA\ImageEditor\examples") self.filename = self.dialog.getOpenFileName(filter="Image (*.PNG *.JPG)")[0] if not self.filename: return try: self.image_data = cv2.imread(self.filename, -1) self.tmp = self.image_data image_data_resized = resize_image(self.tmp, self.max_img_width, self.max_img_height) self.pixmapitem.setPixmap(set_pixmap_original(image_data_resized)) self.processed_frame = None self.cmap_cv = None self.combo1.blockSignals(True) self.combo1.setCurrentIndex(-1) self.combo1.blockSignals(False) self.min_limit = None self.max_limit = None self.thermal_frame_resized = None self.cliped_img = None self.processed_frame = None self.colorized_frame = None self.pdkta_raw = None self.flir_raw = None self.slider1.setValue((self.min_limit_init, self.max_limit_init)) except Exception as e: QMessageBox.warning(self, 'Error', f'O seguinte erro ocorreu:\n{type(e)}: {e}') return #This function sets the initial value of slider after the image being processed def adjust_initial_slider(self): self.slider1.setValue((self.min_limit, self.max_limit)) return #This functions processes the selected image using PREDIKTA framework def pdkta_process(self): if self.tmp is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Nenhuma imagem selecionada!') error_dialog.exec() elif self.filename.endswith((".jpg")): error_dialog = QErrorMessage(self) error_dialog.showMessage('Esta não é uma imagem PDKTA, por favor, escolha uma imagem compatível!') error_dialog.exec() else: self.cmap_cv = create_colormap_cv("palettes/1_rain.pal") self.pdkta_raw = pdkta_frame2raw(self.tmp) self.thermal_matrix = pdkta_raw2temp(self.pdkta_raw) self.thermal_matrix_height = self.thermal_matrix.shape[0] self.thermal_matrix_width = self.thermal_matrix.shape[1] self.thermal_frame_resized = resize_image(self.thermal_matrix, self.max_img_width, self.max_img_height) self.processed_frame = frame_processor(self.thermal_frame_resized) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.combo1.setCurrentText("1_rain.pal") self.max_limit = np.max(self.thermal_matrix) self.min_limit = np.min(self.thermal_matrix) self.adjust_initial_slider() return #This functions processes the selected image using FLIR framework def flir_process(self): if self.tmp is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Nenhuma imagem selecionada!') error_dialog.exec() elif self.filename.endswith((".png")): error_dialog = QErrorMessage(self) error_dialog.showMessage('Esta não é uma imagem FLIR, por favor, escolha uma imagem compatível!') error_dialog.exec() else: get_meta(self.filename) self.flir_raw = flir2raw(self.filename) flir2temp_vec = vectorize_function(flir2temp) self.thermal_matrix = flir2temp_vec(self.flir_raw) self.thermal_matrix_height = self.thermal_matrix.shape[0] self.thermal_matrix_width = self.thermal_matrix.shape[1] self.thermal_frame_resized = resize_image(self.thermal_matrix, self.max_img_width, self.max_img_height) self.processed_frame = frame_processor(self.thermal_frame_resized) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.combo1.setCurrentText("1_rain.pal") self.max_limit = np.max(self.thermal_matrix) self.min_limit = np.min(self.thermal_matrix) self.adjust_initial_slider() return #This function will populate palettes from folder def populate_pallete(self): folder_path = "palettes" if folder_path: self.file_names = [file for file in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, file))] return self.file_names #this function will create a matplotlib colorbar def create_colorbar(self): fig, ax = plt.subplots(figsize=(1, 6), layout='constrained') fig.set_facecolor('#F0F0F0') cmap = self.cmap_mat norm = mpl.colors.Normalize(vmin=self.vmin, vmax=self.vmax) fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='vertical', label='', format=lambda x, _: f"{x:.1f}") ax.tick_params(labelsize = 8) ax.minorticks_on() plt.close() return fig #this function will clear past colorbar and update with a new one with new parameters def update_colorbar(self): for i in reversed(range(self.cb_layout.count())): widgetToRemove = self.cb_layout.itemAt(i).widget() if widgetToRemove is not None: self.cb_layout.removeWidget(widgetToRemove) widgetToRemove.deleteLater() self.colorbar = FigureCanvas(self.create_colorbar()) self.cb_layout.addWidget(self.colorbar) self.cb_groupbox.setLayout(self.cb_layout) self.colorbar.setFixedHeight(self.max_img_height) # self.colorbar.setFixedWidth(70) return #this function will update palette drop menu def handleComboBoxEvent(self): if self.processed_frame is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('Por favor, processe a imagem antes de aplicar o palette!') error_dialog.exec() else: self.palette_file = os.path.join(self.pallete_folder, self.combo1.currentText()) self.cmap_cv = create_colormap_cv(self.palette_file) self.cmap_mat = create_colormap_mat(self.palette_file) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.update_colorbar() #this function updates range slider values def update_slider(self, values): self.slider_values = values self.update_image_threshold() return values #this funcrion thresholds the image according to slider handle values def update_image_threshold(self): if self.thermal_frame_resized is not None: self.clipped_min = self.slider_values[0] self.clipped_max = self.slider_values[1] A = self.thermal_frame_resized B = np.where(A == np.max(A), np.max(A)+20, (np.where(A == np.min(A), np.min(A)-20, A))) self.cliped_img = np.clip(B, self.clipped_min, self.clipped_max) self.processed_frame = frame_processor(self.cliped_img) self.colorized_frame = BGR2RGB(cv2.applyColorMap(self.processed_frame, self.cmap_cv)) self.pixmapitem.setPixmap(set_pixmap_thermal(self.colorized_frame)) self.vmax = self.clipped_max self.vmin = self.clipped_min self.update_colorbar() else: pass return #This function will reset the slider and viewer to initial min/max image values def on_double_click_slider(self): if self.processed_frame is not None: event = QMouseEvent self.adjust_initial_slider() self.update_image_threshold() else: pass return def save_image(self): self.filename = QFileDialog.getSaveFileName(filter="JPG(*.jpg);;PNG(*.png)")[0] # self.imagevar = QImage(self.colorbar.size(), QImage.Format.Format_ARGB32) # self.imagevar = self.pixmap2array(self.imagevar) # saved_img = np.concatenate((self.colorized_frame, self.imagevar), axis=1) # cv2.imwrite(self.filename, saved_img) if not self.filename: return imagevar = self.colorbar.grab(self.colorbar.rect()) imagevar.save("examples/colorbar.png") img = cv2.imread("examples/colorbar.png") width = int(img.shape[1]) height = self.colorized_frame.shape[0] dim = (width, height) img = cv2.resize(img, dim, interpolation=None) saved_img = np.concatenate((self.colorized_frame, img), axis=1) cv2.imwrite(self.filename, saved_img) def pixmap2array(self, pixmap): channels_count = 4 size = pixmap.size() h = size.width() w = size.height() image = pixmap.toImage() s = image.bits().asstring(w * h * channels_count) arr = np.fromstring(s, dtype=np.uint8).reshape((h, w, channels_count)) return arr def export_csv(self): if self.thermal_matrix is None: error_dialog = QErrorMessage(self) error_dialog.showMessage('No há matrix térmica processada!') error_dialog.exec() else: self.filename = QFileDialog.getSaveFileName(self, "Export CSV", "exports", filter="CSV(*.csv)")[0] if not self.filename: return csv_matrix = self.thermal_matrix export_csv(csv_matrix, self.filename) print(self.filename) def set_image(self): #load image----- self.image_data = cv2.imread(self.filename, -1) self.tmp = self.image_data image_data_resized = resize_image(self.tmp, self.max_img_width, self.max_img_height) self.pixmapitem.setPixmap(set_pixmap_original(image_data_resized)) self.scene.addItem(self.pixmapitem) self.viewer.setScene(self.scene) #configurations---- self.processed_frame = None self.cmap_cv = None self.combo1.blockSignals(True) self.combo1.setCurrentIndex(-1) self.combo1.blockSignals(False) self.min_limit = None self.max_limit = None self.thermal_frame_resized = None self.cliped_img = None self.processed_frame = None self.colorized_frame = None self.slider1.setValue((self.min_limit_init, self.max_limit_init)) class ImageViewer(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) # self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setAcceptDrops(True) #This is the drag and drop event -------------------------------------------------------- def dragEnterEvent(self, event): if event.mimeData().hasImage: event.accept() print("accepted") else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasImage: event.accept() print("accepted2") else: event.ignore() def dropEvent(self, event): if event.mimeData().hasImage: try: event.setDropAction(Qt.CopyAction) self.main_window_instance = LeftPanel() self.main_window_instance.filename = event.mimeData().urls()[0].toLocalFile() self.main_window_instance.set_image() event.accept() print(self.main_window_instance.pixmapitem) except Exception as e: QMessageBox.warning(self, 'Error', f'O seguinte erro ocorreu:\n{type(e)}: {e}') returnhere is the main:
import sys import os from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QHBoxLayout, QScrollArea from PyQt6.QtGui import QIcon from testesdesignqview import LeftPanel class MainWindowGV(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Predikta Thermal Analyzer") self.setWindowIcon(QIcon("assets/logo.png")) layout = QHBoxLayout() layout.addWidget(LeftPanel(), alignment=Qt.AlignmentFlag.AlignLeft) #layout.addWidget(right_panel(), alignment=Qt.AlignmentFlag.AlignRight) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindowGV() window.show() app.exec()here the printed event of drag and drop:
accepted accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 accepted2 <PyQt6.QtWidgets.QGraphicsPixmapItem object at 0x00000250EDEF4310>``` and finally how the app looks as of now:  Thanks in advance and sorry for the big post.@IvanDesuo Ok, I got it worked. First I suclassed QGraphicSene and added the mouse events:
class Scene(QGraphicsScene): def __init__(self, parent=None): super().__init__(parent) def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, event): event.accept() def dragMoveEvent(self, e): e.acceptProposedAction()then added self.show() after the function I was calling:
def set_image(self): #load image----- self.image_data = cv2.imread(self.filename, -1) self.tmp = self.image_data image_data_resized = resize_image(self.tmp, self.max_img_width, self.max_img_height) self.pixmapitem.setPixmap(set_pixmap_original(image_data_resized)) self.show()Finally changed the class instance I was using once it was calling left panel everytime I and dropped the image to:
def dropEvent(self, event): if event.mimeData().hasImage: try: event.setDropAction(Qt.CopyAction) self.main_window_instance = self.parent().parent() self.main_window_instance.filename = event.mimeData().urls()[0].toLocalFile() self.main_window_instance.set_image() event.accept() print(self.main_window_instance.pixmapitem) except Exception as e: QMessageBox.warning(self, 'Error', f'O seguinte erro ocorreu:\n{type(e)}: {e}') returnIn the end of the day it is working, but still confused about class inheritance and why it worked.
-
I IvanDesuo has marked this topic as solved on