Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Drag and Drop image doesn't work in qgraphicview
Qt 6.11 is out! See what's new in the release blog

Drag and Drop image doesn't work in qgraphicview

Scheduled Pinned Locked Moved Solved Qt for Python
2 Posts 1 Posters 1.4k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • I Offline
    I Offline
    IvanDesuo
    wrote on last edited by IvanDesuo
    #1

    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}')
                    
                    return
    

    here 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:
    
    ![app.png](https://ddgobkiprc33d.cloudfront.net/f928ed86-0853-4260-975f-1de34633caea.png) 
    
    Thanks in advance and sorry for the big post.
    I 1 Reply Last reply
    0
    • I IvanDesuo

      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}')
                      
                      return
      

      here 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:
      
      ![app.png](https://ddgobkiprc33d.cloudfront.net/f928ed86-0853-4260-975f-1de34633caea.png) 
      
      Thanks in advance and sorry for the big post.
      I Offline
      I Offline
      IvanDesuo
      wrote on last edited by
      #2

      @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}')
                      
                      return
      

      In the end of the day it is working, but still confused about class inheritance and why it worked.

      1 Reply Last reply
      0
      • I IvanDesuo has marked this topic as solved on

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved