Esempio n. 1
0
class VideoWidget(QVideoWidget):

    def __init__(self, parent=None):
        ''' Constructor '''
        super().__init__(parent)
        self.surface = VideoWidgetSurface(self)
        self.setAttribute(Qt.WA_OpaquePaintEvent)

        self.Tracking_Video_RubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.Censure_RubberBand = QRubberBand(QRubberBand.Rectangle, self)

        color_blue = QColor(Qt.blue)
        color_black = QColor(Qt.black)
        color_amber = QColor(252, 215, 108)

        pal_blue = QPalette()
        pal_blue.setBrush(QPalette.Highlight, QBrush(color_blue))
        self.Tracking_Video_RubberBand.setPalette(pal_blue)

        pal_black = QPalette()
        pal_black.setBrush(QPalette.Highlight, QBrush(color_black))
        self.Censure_RubberBand.setPalette(pal_black)

        self._interaction = InteractionState()
        self._filterSatate = FilterState()

        self._isinit = False
        self._MGRS = False
        self.gt = None

        self.drawCesure = []
        self.poly_coordinates, self.drawPtPos, self.drawLines, self.drawMeasureDistance, self.drawMeasureArea, self.drawPolygon = [], [], [], [], [], []
        # Draw Polygon Canvas Rubberband
        self.poly_Canvas_RubberBand = QgsRubberBand(
            iface.mapCanvas(), True)  # Polygon type
        # set rubber band style
        self.poly_Canvas_RubberBand.setColor(color_amber)
        self.poly_Canvas_RubberBand.setWidth(3)

        # Tracking Canvas Rubberband
        self.Track_Canvas_RubberBand = QgsRubberBand(
            iface.mapCanvas(), QgsWkbTypes.LineGeometry)
        # set rubber band style
        self.Track_Canvas_RubberBand.setColor(color_blue)
        self.Track_Canvas_RubberBand.setWidth(5)

        # Cursor Canvas Rubberband
        self.Cursor_Canvas_RubberBand = QgsRubberBand(
            iface.mapCanvas(), QgsWkbTypes.PointGeometry)
        self.Cursor_Canvas_RubberBand.setWidth(4)
        self.Cursor_Canvas_RubberBand.setColor(QColor(255, 100, 100, 250))
        self.Cursor_Canvas_RubberBand.setIcon(QgsRubberBand.ICON_FULL_DIAMOND)

        self.parent = parent.parent()

        palette = self.palette()
        palette.setColor(QPalette.Background, Qt.transparent)
        self.setPalette(palette)

        self.origin, self.dragPos = QPoint(), QPoint()
        self.tapTimer = QBasicTimer()
        self.brush = QBrush(color_black)
        self.blue_Pen = QPen(color_blue, 3)

    def removeLastLine(self):
        ''' Remove Last Line Objects '''
        if self.drawLines:
            try:
                if self.drawLines[-1][3] == "mouseMoveEvent":
                    del self.drawLines[-1]  # Remove mouseMoveEvent element
            except Exception:
                None
            for pt in range(len(self.drawLines) - 1, -1, -1):
                del self.drawLines[pt]
                try:
                    if self.drawLines[pt - 1][0] is None:
                        break
                except Exception:
                    None
            self.UpdateSurface()
            AddDrawLineOnMap(self.drawLines)
        return

    def removeLastSegmentLine(self):
        ''' Remove Last Segment Line Objects '''
        try:
            if self.drawLines[-1][3] == "mouseMoveEvent":
                del self.drawLines[-1]  # Remove mouseMoveEvent element
        except Exception:
            None
        if self.drawLines:
            if self.drawLines[-1][0] is None:
                del self.drawLines[-1]

            del self.drawLines[-1]
            self.UpdateSurface()
            AddDrawLineOnMap(self.drawLines)
        return

    def removeAllLines(self):
        ''' Resets Line List '''
        if self.drawLines:
            self.drawLines = []
            self.UpdateSurface()
            # Clear all Layer
            RemoveAllDrawLineOnMap()

    def ResetDrawMeasureDistance(self):
        ''' Resets Measure Distance List '''
        self.drawMeasureDistance = []

    def ResetDrawMeasureArea(self):
        ''' Resets Measure Area List '''
        self.drawMeasureArea = []

    def removeAllCensure(self):
        ''' Remove All Censure Objects '''
        if self.drawCesure:
            self.drawCesure = []
            self.UpdateSurface()

    def removeLastCensured(self):
        ''' Remove Last Censure Objects '''
        if self.drawCesure:
            del self.drawCesure[-1]
            self.UpdateSurface()

    def removeLastPoint(self):
        ''' Remove All Point Drawer Objects '''
        if self.drawPtPos:
            del self.drawPtPos[-1]
            self.UpdateSurface()
            RemoveLastDrawPointOnMap()
        return

    def removeAllPoint(self):
        ''' Remove All Point Drawer Objects '''
        if self.drawPtPos:
            self.drawPtPos = []
            self.UpdateSurface()
            # Clear all Layer
            RemoveAllDrawPointOnMap()
        return

    def removeAllPolygon(self):
        ''' Remove All Polygon Drawer Objects '''
        if self.drawPolygon:
            self.drawPolygon = []
            self.UpdateSurface()
            # Clear all Layer
            RemoveAllDrawPolygonOnMap()

    def removeLastPolygon(self):
        ''' Remove Last Polygon Drawer Objects '''
        if self.drawPolygon:
            try:
                if self.drawPolygon[-1][3] == "mouseMoveEvent":
                    del self.drawPolygon[-1]  # Remove mouseMoveEvent element
            except Exception:
                None
            for pt in range(len(self.drawPolygon) - 1, -1, -1):
                del self.drawPolygon[pt]
                try:
                    if self.drawPolygon[pt - 1][0] is None:
                        break
                except Exception:
                    None

            self.UpdateSurface()
            # remove last index layer
            RemoveLastDrawPolygonOnMap()

    def keyPressEvent(self, event):
        '''Exit fullscreen
        :type event: QKeyEvent
        :param event:
        :return:
        '''
        if event.key() == Qt.Key_Escape and self.isFullScreen():
            self.setFullScreen(False)
            event.accept()
        elif event.key() == Qt.Key_Enter and event.modifiers() & Qt.Key_Alt:
            self.setFullScreen(not self.isFullScreen())
            event.accept()
        else:
            super().keyPressEvent(event)

    def mouseDoubleClickEvent(self, event):
        """
         Mouse double click event
        :type event: QMouseEvent
        :param event:
        :return:
        """
        if GetImageHeight() == 0:
            return

        if(not vut.IsPointOnScreen(event.x(), event.y(), self.surface)):
            return

        if self.gt is not None and self._interaction.lineDrawer:
            self.drawLines.append([None, None, None])
            return

        if self.gt is not None and self._interaction.measureDistance:
            self.drawMeasureDistance.append([None, None, None])
            return

        if self.gt is not None and self._interaction.measureArea:
            self.drawMeasureArea.append([None, None, None])
            return

        if self.gt is not None and self._interaction.polygonDrawer:

            ok = AddDrawPolygonOnMap(self.poly_coordinates)
            # Prevent invalid geometry (Polygon with 2 points)
            if not ok:
                return

            self.drawPolygon.append([None, None, None])

            # Empty RubberBand
            for _ in range(self.poly_Canvas_RubberBand.numberOfVertices()):
                self.poly_Canvas_RubberBand.removeLastPoint()
            # Empty List
            self.poly_coordinates = []
            return

        self.UpdateSurface()
        self.setFullScreen(not self.isFullScreen())
        event.accept()

    def videoSurface(self):
        ''' Return video Surface '''
        return self.surface

    def UpdateSurface(self):
        ''' Update Video Surface only is is stopped or paused '''
        if self.parent.playerState in (QMediaPlayer.StoppedState,
                                       QMediaPlayer.PausedState):
            self.update()
        QApplication.processEvents()

    def sizeHint(self):
        ''' This property holds the recommended size for the widget '''
        return self.surface.surfaceFormat().sizeHint()

    def currentFrame(self):
        ''' Return current frame QImage '''
        return self.surface.image

    def SetInvertColor(self, value):
        '''Set Invert color filter
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.invertColorFilter = value

    def SetObjectTracking(self, value):
        '''Set Object Tracking
        @type value: bool
        @param value:
        @return:
        '''
        self._interaction.objectTracking = value

    def SetMeasureDistance(self, value):
        '''Set measure Distance
        @type value: bool
        @param value:
        @return:
        '''
        self._interaction.measureDistance = value

    def SetMeasureArea(self, value):
        '''Set measure Area
        @type value: bool
        @param value:
        @return:
        '''
        self._interaction.measureArea = value

    def SetHandDraw(self, value):
        '''Set Hand Draw
        @type value: bool
        @param value:
        @return:
        '''
        self._interaction.HandDraw = value

    def SetCensure(self, value):
        '''Set Censure Video Parts
        @type value: bool
        @param value:
        @return:
        '''
        self._interaction.censure = value

    def SetMGRS(self, value):
        '''Set MGRS Cursor Coordinates
        @type value: bool
        @param value:
        @return:
        '''
        self._MGRS = value

    def SetGray(self, value):
        '''Set gray scale
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.grayColorFilter = value

    def SetMirrorH(self, value):
        '''Set Horizontal Mirror
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.MirroredHFilter = value

    def SetNDVI(self, value):
        '''Set NDVI
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.NDVI = value

    def SetEdgeDetection(self, value):
        '''Set Canny Edge filter
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.edgeDetectionFilter = value

    def SetAutoContrastFilter(self, value):
        '''Set Automatic Contrast filter
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.contrastFilter = value

    def SetMonoFilter(self, value):
        '''Set mono filter
        @type value: bool
        @param value:
        @return:
        '''
        self._filterSatate.monoFilter = value

    def RestoreFilters(self):
        ''' Remove and restore all video filters '''
        self._filterSatate.clear()

    def RestoreDrawer(self):
        ''' Remove and restore all Drawer Options '''
        self._interaction.clear()
        # Magnifier Glass
        self.dragPos = QPoint()
        self.tapTimer.stop()

    def RemoveCanvasRubberbands(self):
        ''' Remove Canvas Rubberbands '''
        self.poly_Canvas_RubberBand.reset()
        self.Track_Canvas_RubberBand.reset(QgsWkbTypes.LineGeometry)
        self.Cursor_Canvas_RubberBand.reset(QgsWkbTypes.PointGeometry)

    def paintEvent(self, event):
        """
        @type event: QPaintEvent
        @param event:
        @return:
        """
        if not self.surface.isActive():
            return

        self.painter = QPainter(self)
        self.painter.setRenderHint(QPainter.HighQualityAntialiasing)

        region = event.region()
        self.painter.fillRect(region.boundingRect(), self.brush)  # Background painter color

        try:
            self.surface.paint(self.painter)
            SetImageSize(self.currentFrame().width(),
                         self.currentFrame().height())
        except Exception:
            None

        # Prevent draw on video if not started or finished
        if self.parent.player.position() == 0:
            self.painter.end()
            return

        self.gt = GetGCPGeoTransform()

        # Draw On Video
        draw.drawOnVideo(self.drawPtPos, self.drawLines, self.drawPolygon,
                         self.drawMeasureDistance, self.drawMeasureArea, self.drawCesure, self.painter, self.surface, self.gt)

        # Draw On Video Object tracking test
        if self._interaction.objectTracking and self._isinit:
            frame = convertQImageToMat(self.currentFrame())
            offset = self.surface.videoRect()
            # Update tracker
            result = resize(frame, (offset.width(), offset.height()))
            ok, bbox = self.tracker.update(result)
            # Draw bounding box
            if ok:
                # check negative values
                x = bbox[0] + offset.x()
                y = bbox[1] + offset.y()
                if vut.IsPointOnScreen(x, y, self.surface):
                    self.painter.setPen(self.blue_Pen)
                    self.painter.drawRect(x, y, bbox[2], bbox[3])

                    # Get Track object center
                    xc = x + (bbox[2] / 2)
                    yc = y + (bbox[3] / 2)
                    p = QPoint(xc, yc)
                    Longitude, Latitude, _ = vut.GetPointCommonCoords(
                        p, self.surface)
                    # Draw Rubber Band on canvas
                    self.Track_Canvas_RubberBand.addPoint(QgsPointXY(Longitude, Latitude))

            else:
                self._isinit = False
                del self.tracker

        # Magnifier Glass
        if self._interaction.magnifier and not self.dragPos.isNull():
            draw.drawMagnifierOnVideo(self, self.dragPos, self.currentFrame(), self.painter)

        # Stamp On Video
        if self._interaction.stamp:
            draw.drawStampOnVideo(self, self.painter)

        self.painter.end()
        return

    def resizeEvent(self, _):
        """
        @type _: QMouseEvent
        @param _:
        @return:
        """
        self.surface.updateVideoRect()
        self.update()
        # Magnifier Glass
        if self._interaction.magnifier and not self.dragPos.isNull():
            draw.drawMagnifierOnVideo(self, self.dragPos, self.currentFrame(), self.painter)
        # QApplication.processEvents()

    def AddMoveEventValue(self, values, Longitude, Latitude, Altitude):
        """
        Remove and Add move value for fluid drawing

        @type values: list
        @param values: Points list

        @type Longitude: float
        @param Longitude: Longitude value

        @type Latitude: float
        @param Latitude: Latitude value

        @type Altitude: float
        @param Altitude: Altitude value

        """
        for idx, pt in enumerate(values):
            if pt[-1] == "mouseMoveEvent":
                del values[idx]
        values.append([Longitude, Latitude, Altitude, "mouseMoveEvent"])

        self.UpdateSurface()

    def mouseMoveEvent(self, event):
        """
        @type event: QMouseEvent
        @param event:
        @return:
        """
        # Magnifier mouseMoveEvent
        # Magnifier can move on black screen for show image borders
        if self._interaction.magnifier:
            if event.buttons():
                self.dragPos = event.pos()
                self.UpdateSurface()

        # check if the point  is on picture (not in black borders)
        if(not vut.IsPointOnScreen(event.x(), event.y(), self.surface)):
            self.setCursor(QCursor(Qt.ArrowCursor))
            self.Cursor_Canvas_RubberBand.reset(QgsWkbTypes.PointGeometry)
            return

        # Prevent draw on video if not started or finished
        if self.parent.player.position() == 0:
            return

        # Mouser cursor drawing
        if self._interaction.pointDrawer or self._interaction.polygonDrawer or self._interaction.lineDrawer or self._interaction.measureDistance or self._interaction.measureArea or self._interaction.censure or self._interaction.objectTracking:
            self.setCursor(QCursor(Qt.CrossCursor))

        # Cursor Coordinates
        if self.gt is not None:

            Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                event, self.surface)

            tr = QgsCoordinateTransform( QgsCoordinateReferenceSystem( 'EPSG:4326' ), iface.mapCanvas().mapSettings().destinationCrs(), QgsProject.instance().transformContext() )
            mapPt = tr.transform( QgsPointXY(Longitude, Latitude) )
            
            vertices = self.Cursor_Canvas_RubberBand.numberOfVertices()
            if vertices > 0:
                self.Cursor_Canvas_RubberBand.removePoint(0, True, 0)
                self.Cursor_Canvas_RubberBand.movePoint( mapPt, 0)
            else:
                self.Cursor_Canvas_RubberBand.addPoint( mapPt )

            if self._MGRS:
                try:
                    mgrsCoords = mgrs.toMgrs(Latitude, Longitude)
                except Exception:
                    mgrsCoords = ""

                txt = "<span style='font-size:9pt; font-weight:normal;'>" + \
                    ("%s" % mgrsCoords) + "</span>"

            else:

                txt = "<span style='font-size:10pt; font-weight:bold;'>Lon : </span>"
                txt += "<span style='font-size:9pt; font-weight:normal;'>" + \
                    ("%.3f" % Longitude) + "</span>"
                txt += "<span style='font-size:10pt; font-weight:bold;'> Lat : </span>"
                txt += "<span style='font-size:9pt; font-weight:normal;'>" + \
                    ("%.3f" % Latitude) + "</span>"

                if hasElevationModel():
                    txt += "<span style='font-size:10pt; font-weight:bold;'> Alt : </span>"
                    txt += "<span style='font-size:9pt; font-weight:normal;'>" + \
                        ("%.0f" % Altitude) + "</span>"
                else:
                    txt += "<span style='font-size:10pt; font-weight:bold;'> Alt : </span>"
                    txt += "<span style='font-size:9pt; font-weight:normal;'>-</span>"

            self.parent.lb_cursor_coord.setText(txt)

            # Polygon drawer mouseMoveEvent
            if self._interaction.polygonDrawer:
                self.AddMoveEventValue(self.drawPolygon, Longitude, Latitude, Altitude)

            # Line drawer mouseMoveEvent
            if self._interaction.lineDrawer:
                self.AddMoveEventValue(self.drawLines, Longitude, Latitude, Altitude)

            # Measure Distance drawer mouseMoveEvent
            if self._interaction.measureDistance and self.drawMeasureDistance:
                self.AddMoveEventValue(self.drawMeasureDistance, Longitude, Latitude, Altitude)

            # Measure Area drawer mouseMoveEvent
            if self._interaction.measureArea and self.drawMeasureArea:
                self.AddMoveEventValue(self.drawMeasureArea, Longitude, Latitude, Altitude)

        else:
            self.parent.lb_cursor_coord.setText("<span style='font-size:10pt; font-weight:bold;'>Lon :</span>" +
                                                "<span style='font-size:9pt; font-weight:normal;'>-</span>" +
                                                "<span style='font-size:10pt; font-weight:bold;'> Lat :</span>" +
                                                "<span style='font-size:9pt; font-weight:normal;'>-</span>" +
                                                "<span style='font-size:10pt; font-weight:bold;'> Alt :</span>" +
                                                "<span style='font-size:9pt; font-weight:normal;'>-</span>")

        if not event.buttons():
            return

        # Object tracking rubberband
        if not self.Tracking_Video_RubberBand.isHidden():
            self.Tracking_Video_RubberBand.setGeometry(
                QRect(self.origin, event.pos()).normalized())

        # Censure rubberband
        if not self.Censure_RubberBand.isHidden():
            self.Censure_RubberBand.setGeometry(
                QRect(self.origin, event.pos()).normalized())

    def timerEvent(self, _):
        """ Time Event (Magnifier method)"""
        if not self._interaction.magnifier:
            self.activateMagnifier()

    def mousePressEvent(self, event):
        """
        @type event: QMouseEvent
        @param event:
        @return:
        """
        if GetImageHeight() == 0:
            return

        # Prevent draw on video if not started or finished
        if self.parent.player.position() == 0:
            return

        if event.button() == Qt.LeftButton:

            # Magnifier Glass
            if self._interaction.magnifier:
                self.dragPos = event.pos()
                self.tapTimer.stop()
                self.tapTimer.start(10, self)

            if(not vut.IsPointOnScreen(event.x(), event.y(), self.surface)):
                return

            # point drawer
            if self.gt is not None and self._interaction.pointDrawer:
                Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                    event, self.surface)

                pointIndex = len(self.drawPtPos) + 1
                AddDrawPointOnMap(pointIndex, Longitude,
                                  Latitude, Altitude)

                self.drawPtPos.append([Longitude, Latitude, Altitude])

            # polygon drawer
            if self.gt is not None and self._interaction.polygonDrawer:
                Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                    event, self.surface)
                self.poly_Canvas_RubberBand.addPoint(QgsPointXY(Longitude, Latitude))
                self.poly_coordinates.extend(QgsPointXY(Longitude, Latitude))
                self.drawPolygon.append([Longitude, Latitude, Altitude])

            # line drawer
            if self.gt is not None and self._interaction.lineDrawer:
                Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                    event, self.surface)

                self.drawLines.append([Longitude, Latitude, Altitude])

                AddDrawLineOnMap(self.drawLines)

            self.origin = event.pos()
            # Object Tracking Interaction
            if self._interaction.objectTracking:
                self.Tracking_Video_RubberBand.setGeometry(
                    QRect(self.origin, QSize()))
                self.Tracking_Video_RubberBand.show()

            # Censure Interaction
            if self._interaction.censure:
                self.Censure_RubberBand.setGeometry(
                    QRect(self.origin, QSize()))
                self.Censure_RubberBand.show()

            # Measure Distance drawer
            if self.gt is not None and self._interaction.measureDistance:
                Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                    event, self.surface)
                self.drawMeasureDistance.append([Longitude, Latitude, Altitude])

            # Measure Distance drawer
            if self.gt is not None and self._interaction.measureArea:
                Longitude, Latitude, Altitude = vut.GetPointCommonCoords(
                    event, self.surface)
                self.drawMeasureArea.append([Longitude, Latitude, Altitude])

            # if not called, the paint event is not triggered.
            self.UpdateSurface()

    def activateMagnifier(self):
        """ Activate Magnifier Glass """
        self.tapTimer.stop()
        self.UpdateSurface()

    def SetMagnifier(self, value):
        """Set Magnifier Glass
        @type value: bool
        @param value:
        """
        self._interaction.magnifier = value
        # We avoid that the second time we activate the tool, save the previous position.
        # Always keep the same behavior of the tool
        if not value:
            self.dragPos = QPoint()
            self.tapTimer.stop()

    def SetStamp(self, value):
        """Set Stamp
        @type value: bool
        @param value:
        """
        self._interaction.stamp = value

    def SetPointDrawer(self, value):
        """Set Point Drawer
        @type value: bool
        @param value:
        """
        self._interaction.pointDrawer = value

    def SetLineDrawer(self, value):
        """Set Line Drawer
        @type value: bool
        @param value:
        """
        self._interaction.lineDrawer = value

    def SetPolygonDrawer(self, value):
        """Set Polygon Drawer
        @type value: bool
        @param value:
        """
        self._interaction.polygonDrawer = value

    def mouseReleaseEvent(self, _):
        """
        @type event: QMouseEvent
        @param event:
        @return:
        """
        # Prevent draw on video if not started or finished
        if self.parent.player.position() == 0:
            return

        # Censure Draw Interaction
        if self._interaction.censure:
            geom = self.Censure_RubberBand.geometry()
            self.Censure_RubberBand.hide()
            self.drawCesure.append([geom])

        # Object Tracking Interaction
        if self._interaction.objectTracking:
            geom = self.Tracking_Video_RubberBand.geometry()
            offset = self.surface.videoRect()
            bbox = (geom.x() - offset.x(), geom.y() - offset.y(), geom.width(), geom.height())
            img = self.currentFrame()
            frame = convertQImageToMat(img)
            # Remo rubberband on canvas and video
            self.Tracking_Video_RubberBand.hide()
            self.Track_Canvas_RubberBand.reset()

            self.tracker = TrackerMOSSE_create()
            result = resize(frame, (offset.width(), offset.height()))

            try:
                ok = self.tracker.init(result, bbox)
            except Exception:
                return
            if ok:
                self._isinit = True
                # Get Traker center
                xc = bbox[0] + (geom.width() / 2)
                yc = bbox[1] + (geom.height() / 2)
                p = QPoint(xc, yc)
                Longitude, Latitude, _ = vut.GetPointCommonCoords(
                    p, self.surface)
                # Draw Rubber Band on canvas
                self.Track_Canvas_RubberBand.addPoint(QgsPointXY(Longitude, Latitude))
            else:
                self._isinit = False

    def leaveEvent(self, _):
        """
        @type _: QEvent
        @param _:
        @return:
        """
        # Remove coordinates label value
        self.parent.lb_cursor_coord.setText("")
        # Change cursor
        self.setCursor(QCursor(Qt.ArrowCursor))
        # Reset mouse rubberband
        self.Cursor_Canvas_RubberBand.reset(QgsWkbTypes.PointGeometry)
class AgknowDockWidgetTimeSlider(QtWidgets.QDockWidget, FORM_CLASS):
    """
     Class for the timeslider dockwidget of the agknow plugin.
    """
    closingPlugin = pyqtSignal()
    productChanged = pyqtSignal(str)

    def __init__(self, parent=None):
        """Constructor."""
        super(AgknowDockWidgetTimeSlider, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        # GUI Events
        # lambda keyword allows the button itself to be passed
        #https://www.tutorialspoint.com/pyqt/pyqt_qradiobutton_widget.htm
        self.rdBtnVisible.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnVisible))
        self.rdBtnVitality.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnVitality))
        self.rdBtnVariations.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnVariations))
        self.rdBtnNDRE1.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnNDRE1))
        self.rdBtnNDRE2.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnNDRE2))
        self.rdBtnNDWI.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnNDWI))
        self.rdBtnSAVI.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnSAVI))
        self.rdBtnEVI2.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnEVI2))
        self.rdBtnCIRE.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnCIRE))
        self.rdBtnNDVI.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnNDVI))
        self.rdBtnRefl.toggled.connect(
            lambda: self.rdBtnProductState_toggled(self.rdBtnRefl))

        self.sliderTime.valueChanged.connect(self.sliderValue_changed)
        self.btnTimePlay.clicked.connect(self.btnTimePlay_clicked)
        self.btnTimeBackward.clicked.connect(self.btnTimeBackward_clicked)
        self.btnTimeForward.clicked.connect(self.btnTimeForward_clicked)

        self.product = "vitality"
        self.rasters = [
        ]  # it's a list here - a dict in agknow_qgis_dockwidget.py
        self.data_source = "sentinel2"
        self.current_parcel_id = None

        # timeslider
        self.timer = QBasicTimer()
        self.step = 0

        self.iface = qgis.utils.iface

    def set_data_source(self, data_source):
        """
         Setter for the data_source (landsat-8 or sentinel-2).
        """
        self.data_source = data_source

        self.toggle_products_data_source_compatibility()

    def set_product(self, product):
        """
         Setter for the product (vitality, variations, visible, etc).
        """
        self.product = product

        # notify slots of change
        self.productChanged.emit(product)

    def set_current_parcel_id(self, parcel_id):
        """
         Setter for the current parcel_id.
        """
        self.current_parcel_id = parcel_id

    def timerEvent(self, e):
        """
         Event handler for the timer.
        :param e:
        """
        print("timerEvent()")

        if self.step == self.sliderTime.maximum():
            self.timer.stop()
            self.step = 0  #reset step
            return
        else:
            self.step = self.step + 1

            self.sliderTime.setValue(self.sliderTime.value() + 1)

    def rdBtnProductState_toggled(self, btn):
        """
         Handles the toggled event of the given radio button.

        :param btn: radio button (QRadioButton)
        """
        # trigger update
        # if radio button is checked at the end
        if btn.isChecked():

            if btn.text().lower() == "refl.":
                self.set_product("reflectances")
            else:
                self.set_product(btn.text().lower())

            parcel_id = self.current_parcel_id

            if len(self.rasters) > 0:

                # find subgroup
                root = QgsProject.instance().layerTreeRoot()
                parcel_group = root.findGroup(
                    "parcel id: {0}".format(parcel_id))

                # select active layer for identity e.g.
                self.set_layer_active_toc(parcel_id)

                if parcel_group is not None:
                    self.toggle_products(parcel_group)

    def set_layer_active_toc(self, parcel_id):
        """
         Sets the group layer of the given parcel ID in the TOC to active.

        :param parcel_id: parcel's ID (integer)
        """
        # take the first layer of the group "parcel id: - source - product"
        data_source_group = self.find_data_source_group(
            parcel_id, self.product, self.data_source)

        # layer name
        date = self.rasters[0]["date"]
        raster_id = self.rasters[0]["raster_id"]
        activeLyrName = date + " - " + str(raster_id)

        if data_source_group is not None:

            for lyr in data_source_group.findLayers():

                #print("searching for {0}..".format(activeLyrName))

                # QGIS >= 2.18.1 : name(), QGIS 2.18.0 : layerName()
                if lyr.name() == activeLyrName:

                    # QgsLayerTreeLayer to QgsMapLayer per .layer()
                    #print("setting lyr {0} active".format(lyr.layerName()))
                    self.iface.setActiveLayer(lyr.layer())

        else:
            pass
            #print("data_source_group not found: {0} - {1} - {2}".format(parcel_id, self.product, self.data_source))

    def sliderValue_changed(self):
        """
         Handles the value changed event of the timeslider.
        """

        if len(self.rasters) > 0:
            #print(self.sliderTime.value())

            idx = self.sliderTime.value()

            #set current raster to visible - the rest invisible

            # get data from position in self.rasters
            product_id = self.product + " "  #whitespace!
            data_source = self.rasters[idx]["source"]
            parcel_id = self.rasters[idx]["parcel_id"]
            raster_id = self.rasters[idx]["raster_id"]
            date = self.rasters[idx]["date"]

            #print(product_id, data_source, parcel_id, raster_id, date)

            activeLyrName = "{0}|{1}|{2}|{3}".format(
                product_id.strip(), date, raster_id,
                data_source)  #date + " - " + str(raster_id)

            #print(activeLyrName)

            self.toggle_image_layer(activeLyrName, data_source, parcel_id,
                                    product_id)

        else:
            QgsMessageLog.logMessage(
                "AgknowDockWidgetTimeSlider - sliderValue_changed() - rasters are not set!",
                "agknow", Qgis.Critical)

    def toggle_image_layer(self, activeLyrName, data_source, parcel_id,
                           product_id):
        """
         Toggles all other image layers off except the given activeLayer.

        :param activeLyrName: active layer group (string)
        :param data_source: landsat-8 or sentinel-2 (string)
        :param parcel_id: parcel's ID (integer)
        :param product_id: product id: visible, vitality, variations, etc. (string)
        """
        # find subgroup
        data_source_group = self.find_data_source_group(
            parcel_id, product_id, data_source)

        if data_source_group is not None:

            for lyr in data_source_group.findLayers():
                # QGIS 2.18.1 : name(), QGIS 2.18.0 : layerName()
                if lyr.name() == activeLyrName:
                    # set active layer visible
                    lyr.setItemVisibilityChecked(Qt.Checked)
                    # select active layer for identity e.g.
                    # QgsLayerTreeLayer to QgsMapLayer per .layer()
                    self.iface.setActiveLayer(lyr.layer())
                else:
                    lyr.setItemVisibilityChecked(Qt.Unchecked)

    def find_data_source_group(self, parcel_id, product_id, data_source):
        """
         Returns the data source group layer of the TOC for the given parcel id, product and data source.

        :param parcel_id: parcel's ID (integer)
        :param product_id: product id: visible, vitality, variations, etc. (string)
        :param data_source: landsat-8 or sentinel-2 (string)

        :return: data_source_group (QgsLayerTreeGroup)
        """
        root = QgsProject.instance().layerTreeRoot()
        parcel_group = root.findGroup("parcel id: {0}".format(parcel_id))

        if parcel_group is not None:
            #print("parcel group found")
            product_group = parcel_group.findGroup(product_id)
            #print(product_group)

            if product_group is not None:
                #print("product group found")
                data_source_group = product_group.findGroup(data_source)

                return data_source_group

    def toggle_products(self, parcel_group):
        """
         Toggle all other product groups off except the given parcel_group.

        :param parcel_group: (QgsLayerTreeGroup)
        """
        data_source_list = ["landsat8", "sentinel2"]
        matrix = {
            "landsat8": ["visible", "vitality", "variations"],
            "sentinel2": [
                "visible", "vitality", "variations", "reflectances", "ndvi",
                "ndre1", "ndre2", "ndre3", "ndwi", "savi", "evi2", "cire"
            ]
        }

        # turn off other data_source groups
        for ds in data_source_list:
            #print("checking {0}..".format(ds))
            for p in matrix[ds]:
                #print("checking product {0}..".format(p))
                pg = parcel_group.findGroup(p + " ")

                if pg is not None:
                    #print("group {0} found!".format(p))
                    g = pg.findGroup(ds)

                    if g is not None:
                        if p == self.product and ds == self.data_source:
                            #print("group {0} visible".format(ds))
                            g.setItemVisibilityChecked(Qt.Checked)
                        else:
                            #print("group {0} invisible!".format(ds))
                            g.setItemVisibilityChecked(Qt.Unchecked)

    def toggle_products_data_source_compatibility(self):
        """
         Handles radio buttons for compatibility of products & data_source
        """
        print("toggle_products_data_source_compatibility()")
        matrix = {
            "landsat8": ["Visible", "Vitality", "Variations"],
            "sentinel2": [
                "Visible", "Vitality", "Variations", "Refl.", "NDVI", "NDRE1",
                "NDRE2", "NDRE3", "NDWI", "SAVI", "EVI2", "CIRE"
            ]
        }
        radioBtns = [
            self.rdBtnVisible, self.rdBtnVitality, self.rdBtnVariations,
            self.rdBtnNDRE1, self.rdBtnNDRE2, self.rdBtnNDWI, self.rdBtnSAVI,
            self.rdBtnEVI2, self.rdBtnCIRE, self.rdBtnNDVI, self.rdBtnRefl
        ]

        for rdBtn in radioBtns:
            # disable all other radio buttons
            if rdBtn.text() in matrix[self.data_source]:
                rdBtn.setEnabled(True)
            else:
                rdBtn.setEnabled(False)

        #TODO: if data source disables product - select another product
        # leads to segmentation faults at the moment because several threads are started then
        #for rdBtn in radioBtns:
        #    # check if selected radio button is disabled
        #    if rdBtn.isChecked() and not rdBtn.isEnabled():
        #        # if so take the first of possible radio buttons
        #        rdBtnToCheckName = matrix[self.data_source][0]
        #        print(rdBtnToCheckName)
        #        for r in radioBtns:
        #            if r.text() == rdBtnToCheckName:
        #                r.setChecked(Qt.Checked)

    def btnTimePlay_clicked(self):
        """
         Handles the click event of the Button btnTimePlay.
        """
        self.btnTimePlay.setText(" || ")
        interval = 1000  #ms

        # stop timer if it is already active
        if self.timer.isActive():
            self.btnTimePlay.setText(" > ")
            self.timer.stop()
            # reset timer
            self.step = 0
            self.sliderTime.setValue(0)

        # start timer
        else:
            self.timer.start(interval, self)

    def btnTimeForward_clicked(self):
        """
         Handles the click event of the Button btnTimeForward.
        """
        self.sliderTime.setValue(self.sliderTime.value() + 1)

    def btnTimeBackward_clicked(self):
        """
         Handles the click event of the Button btnTimeBackward.
        """
        self.sliderTime.setValue(self.sliderTime.value() - 1)

    def reload_images(self, rasters):
        """
         Reloads images for the given rasters.
         :param rasters: ()
        """
        #print("reload_images({0})".format(rasters))

        if rasters is not None:
            self.sliderTime.setRange(0, len(rasters) - 1)
            self.sliderTime.setTickInterval = 1
            self.rasters = rasters

            if len(self.rasters) > 0:
                #print(self.rasters)
                parcel_id = self.rasters[0]["parcel_id"]

                QgsMessageLog.logMessage(
                    "AgknowDockWidgetTimeSlider - activating parcel {0}..".
                    format(parcel_id), "agknow", Qgis.Info)
                # select first image layer active in toc for identity e.g.
                self.set_layer_active_toc(parcel_id)

        else:
            QgsMessageLog.logMessage(
                "AgknowDockWidgetTimeSlider - rasters is None!", "agknow",
                Qgis.Warning)

    def closeEvent(self, event):
        """
         Handles the close event of the class.
        :param event:
        """
        self.closingPlugin.emit()
        event.accept()