#%% modules

import copy
import numpy as np
import numpy.ma as ma
import pandas as pd
import cv2
import os
import pprint
import sys

from math import inf

from phenopype import settings
from phenopype import utils_lowlevel
from phenopype.core import preprocessing
from phenopype.core import segmentation
from phenopype.core import visualization

#%% classes


class motion_tracker(object):
    """
    Initialize motion tracker class; extract information (length, fps, codec, 
    etc.) from input video and pass to other tracking methods.

    Parameters
    ----------
    video_path : str
        path to video
    at_frame : int, optional
        frame index to be used to extract the video information
        
    """

    def __init__(self, video_path, at_frame=1):

        ## extract frame
        if os.path.isfile(video_path):
            capture = cv2.VideoCapture(video_path)
            idx = 0
            while capture.isOpened():
                idx += 1
                if idx == at_frame:
                    ret, frame = capture.read()
                    break
                else:
                    capture.grab()
                if cv2.waitKey(1) & 0xFF == 27:
                    break
        else:
            print("No compatible video file found under provided path")
            return

        ## properties
        self.path = video_path
        self.name = os.path.basename(self.path)
        self.nframes = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
        self.fps = capture.get(cv2.CAP_PROP_FPS)
        self.fourcc_str = utils_lowlevel._decode_fourcc(
            capture.get(cv2.CAP_PROP_FOURCC)
        )
        self.length = str(
            str(int((self.nframes / self.fps) / 60)).zfill(2)
            + ":"
            + str(
                int(
                    (
                        ((self.nframes / self.fps) / 60)
                        - int((self.nframes / self.fps) / 60)
                    )
                    * 60
                )
            ).zfill(2)
        )
        self.dimensions = tuple(reversed(frame.shape[0:2]))
        if frame.shape[2] == 3:
            self.is_colour = True

        ## for masks
        self.image = frame
        self.df_image_data = pd.DataFrame(
            {
                "filename": self.name,
                "filepath": self.path,
                "filetype": self.fourcc_str,
                "width": self.dimensions[0],
                "height": self.dimensions[1],
            },
            index=[0],
        )

        ## default
        self.flag_save_video = False

        ## release capture
        capture.release()

        ## feeddback
        print("\n")
        print("--------------------------------------------------------------")
        print('Input video properties - "' + self.name + '":\n')
        print(
            "Frames per second: "
            + str(self.fps)
            + "\nN frames: "
            + str(self.nframes)
            + "\nLength: "
            + str(self.length)
            + " (mm:ss)"
            + "\nDimensions: "
            + str(self.dimensions)
            + "\nColour video: "
            + str(self.is_colour)
            + "\nFourCC code: "
            + str(self.fourcc_str)
        )
        print("--------------------------------------------------------------")

    def video_output(
        self,
        video_format=None,
        save_suffix="out",
        dirpath=None,
        fps=None,
        save_colour=None,
        dimensions=None,
        resize=1,
    ):
        """
        Set properties of output video file. Most settings can be left blank, 
        so that settings from the input video will be applied
        
        Parameters
        ----------
        video_format: str, optional
            format of the output video file. needs to be a fourcc-string
            https://www.fourcc.org/codecs.php
        save_suffix: str, optional
            name for the output video file (defaut: "input-filename" + _out.avi)
        dirpath: str, optional
            save directory for the output video file. will be created if not existing
        fps: int, optional
            frames per second of the output video file
        dimensions: tuple, optional
            dimensions (width, length) of the output video file
        resize: int, optional
            factor by which to resize the output video dimensions
        save_colour: bool, optional
            should the saved video frames be in colour 
        """

        self.flag_save_video = True

        ## name and path
        name = (
            os.path.splitext(self.name)[0]
            + "_"
            + save_suffix
            + os.path.splitext(self.name)[1]
        )
        if dirpath.__class__.__name__ == "NoneType":
            dirpath = os.path.dirname(self.path)
        else:
            if not os.path.isdir(dirpath):
                q = input("Save folder {} does not exist - create?.".format(dirpath))
                if q in ["True", "true", "y", "yes"]:
                    os.makedirs(dirpath)
                else:
                    print("Directory not created - aborting")
                    return
        path_out = os.path.join(dirpath, name)

        ## video properties
        if video_format.__class__.__name__ == "NoneType":
            fourcc_str = self.fourcc_str
        else:
            fourcc_str = video_format
        fourcc_out = cv2.VideoWriter_fourcc(*fourcc_str)
        if fps.__class__.__name__ == "NoneType":
            fps_out = self.fps
        if dimensions.__class__.__name__ == "NoneType":
            dimensions_out = self.dimensions
        if save_colour.__class__.__name__ == "NoneType":
            self.flag_save_colour = self.is_colour
        else:
            self.flag_save_colour = save_colour

        self.resize_factor = resize
        if self.resize_factor < 1:
            res_msg = " (original resized by factor " + str(self.resize_factor) + ")"
        else:
            res_msg = ""
        dimensions_out = (
            int(dimensions_out[0] * self.resize_factor),
            int(dimensions_out[1] * self.resize_factor),
        )

        ## start video-writer
        self.writer = cv2.VideoWriter(
            path_out, fourcc_out, fps_out, dimensions_out, self.flag_save_colour
        )

        print("\n")
        print("--------------------------------------------------------------")
        print('Output video settings - "' + self.name + '":\n')
        print(
            "Save name: "
            + name
            + "\nSave dir: "
            + os.path.abspath(dirpath)
            + "\nFrames per second: "
            + str(fps_out)
            + "\nDimensions: "
            + str(dimensions_out)
            + res_msg
            + "\nColour video: "
            + str(self.flag_save_colour)
            + "\nFormat (FourCC code): "
            + fourcc_str
        )
        print("--------------------------------------------------------------")

    def detection_settings(
        self,
        skip=5,
        warmup=0,
        start_after=0,
        finish_after=0,
        history=60,
        threshold=10,
        detect_shadows=True,
        mode="MOG",
        methods=None,
        masks=None,
        c_mask=False,
        c_mask_shape="rect",
        c_mask_size=50,
    ):

        """
        Set properties of output video file. Most settings can be left at their 
        default value.
        
        Parameters
        ----------
        skip: int, optional
            how many frames to skip between each capture
        warmup: int, optional
            warmup period in seconds for the background subtractor
        start_after: int, optional
            start after X seconds
        finish_after: int, optional
            finish after X seconds
        history: int, optional
            how many frames to use for fg-bg subtraction algorithm
        threshold: int, optional
            sensitivity-level for fg-bg subtraction algorithm (lower = more 
            sensitive)
        detect_shadows: bool, optional
            attempt to detect shadows - will be returned as gray pixels
        mode: {"MOG", "KNN"} str, optional
            type of fg-bg subtraction algorithm
        methods: method or list of methods, optional
            list with tracking_method objects
        c_mask: bool, optional 
            consecutive masking. if multiple methods are defined, the objects 
            detected first will mask the objects detected in subsequent methods
        c_mask_shape: {"rect", "ellipse", "contour"} str, optional
            which shape should the consecutive mask have
        c_mask_size: int, optional
            area in pixels that is added around the mask
            
        """
        ## kwargs
        self.skip = skip
        self.warmup = warmup
        self.start = start_after
        self.finish = finish_after
        self.flag_detect_shadows = detect_shadows
        self.flag_consecutive = c_mask
        self.consecutive_shape = c_mask_shape
        self.consecutive_size = c_mask_size
        self.masks = masks

        ## select background subtractor
        if mode == "MOG":
            self.fgbg_subtractor = cv2.createBackgroundSubtractorMOG2(
                int(history * (self.fps / self.skip)),
                threshold,
                self.flag_detect_shadows,
            )
        elif mode == "KNN":
            self.fgbg_subtractor = cv2.createBackgroundSubtractorKNN(
                int(history * (self.fps / self.skip)),
                threshold,
                self.flag_detect_shadows,
            )

        ## return settings of methods
        if not methods.__class__.__name__ == "NoneType":
            if methods.__class__.__name__ == "tracking_method":
                methods = [methods]

            self.methods = methods
            for m in self.methods:
                print("-----------------------------")
                print("Detection method \"{}\":\n".format(m.label))
                m._print_settings()
                print("-----------------------------")
                print("\n")

        print("\n")
        print("--------------------------------------------------------------")
        print('Motion detection settings - "' + self.name + '":\n')
        print(
            "Background-subtractor: "
            + str(mode)
            + "\nHistory: "
            + str(history)
            + " seconds"
            + "\nSensitivity: "
            + str(threshold)
            + "\nRead every nth frame: "
            + str(self.skip)
            + "\nDetect shadows: "
            + str(self.flag_detect_shadows)
            + "\nStart after n seconds: "
            + str(self.start)
            + "\nFinish after n seconds: "
            + str(self.finish if self.finish > 0 else " - ")
        )
        print("--------------------------------------------------------------")

    def run_tracking(self, feedback=True, canvas="overlay", overlay_weight=0.5, **kwargs):
        """
        Start motion tracking procedure. Enable or disable video feedback, 
        output and select canvas (overlay of detected objects, foreground mask,
        input video).
        
        Parameters
        ----------
        feedback: bool, optional
            show output of tracking
        canvas: {"overlay","fgmask","input"} str, optional
            the background for the ouput video
        overlay_weight: float (default: 0.5)
            if canvas="overlay", how transparent should the overlay should be

        """

        ## kwargs
        flag_feedback = feedback

        ## check if settings have been called
        if not hasattr(self, "fgbg_subtractor"):
            self.detection_settings()

        ## initialize
        self.df = pd.DataFrame()
        self.idx1, self.idx2 = (0, 0)
        self.capture = cv2.VideoCapture(self.path)
        self.start_frame = int(self.start * self.fps)
        if self.finish > 0:
            self.finish_frame = int(self.finish * self.fps)
        else:
            self.finish_frame = self.nframes

        if all(hasattr(self, attr) for attr in ["methods", "masks"]):
            for m in self.methods:
                m._apply_masks(frame=self.image, masks=self.masks)

        ## loop thrpugh frames
        while self.capture.isOpened():

            ## frame indexing
            self.idx1, self.idx2 = (self.idx1 + 1, self.idx2 + 1)
            if self.idx2 == self.skip:
                self.idx2 = 0

            ## time conversion
            mins = str(int((self.idx1 / self.fps) / 60)).zfill(2)
            secs = str(int((((self.idx1 / self.fps) / 60) - int(mins)) * 60)).zfill(2)
            self.time_stamp = (
                "Time: "
                + mins
                + ":"
                + secs
                + "/"
                + self.length
                + " - Frames: "
                + str(self.idx1)
                + "/"
                + str(int(self.nframes))
            )

            ## end-of-frames-control
            if self.idx1 == self.finish_frame - 1:
                self.capture.release()
                if self.flag_save_video:
                    self.writer.release()
                break

            ## capture frame
            if (
                self.idx1 > self.start_frame - int(self.warmup * self.fps)
                and self.idx2 == 0
            ):
                self.ret, self.frame = self.capture.read()

                ## skip empty frames
                if self.ret == False:
                    continue
                else:
                    capture_frame = True
                    if (
                        self.idx1 < self.start_frame
                        and self.idx1 > self.start_frame - int(self.warmup * self.fps)
                    ):
                        print(self.time_stamp + " - warmup")
                    else:
                        print(self.time_stamp + " - captured")
            else:
                self.capture.grab()
                print(self.time_stamp)
                continue

            ## if captured, apply masks > apply methods > write to output
            if capture_frame == True:

                # initiate tracking
                fgmask = self.fgbg_subtractor.apply(self.frame)
                fgmask_copy = copy.deepcopy(fgmask)
                self.frame_overlay = self.frame

                # apply methods
                if "methods" in vars(self):
                    idx = 0
                    for m in self.methods:
                        fgmask_copy = copy.deepcopy(fgmask)
                        (
                            self.fgmask_mod,
                            self.overlay,
                            self.method_contours,
                            self.frame_df,
                        ) = m._run(frame=self.frame, fgmask=fgmask_copy)
                        idx += 1

                        # shadowing of methods
                        if self.flag_consecutive and idx < len(self.methods):
                            self.method_mask = np.zeros_like(fgmask_copy)
                            for contour in self.method_contours:
                                if self.consecutive_shape == "contour":
                                    self.method_mask = cv2.drawContours(
                                        self.method_mask,
                                        [contour],
                                        0,
                                        utils_lowlevel._get_bgr("white"),
                                        -1,
                                    )  # Draw filled contour in mask
                                elif self.consecutive_shape == "ellipse":
                                    self.method_mask = cv2.ellipse(
                                        self.method_mask,
                                        cv2.fitEllipse(contour),
                                        utils_lowlevel._get_bgr("white"),
                                        -1,
                                    )
                                elif self.consecutive_shape in ["rect", "rectangle"]:
                                    rx, ry, rw, rh = cv2.boundingRect(contour)
                                    cv2.rectangle(
                                        self.method_mask,
                                        (int(rx), int(ry)),
                                        (int(rx + rw), int(ry + rh)),
                                        utils_lowlevel._get_bgr("white"),
                                        -1,
                                    )
                                kernel = cv2.getStructuringElement(
                                    cv2.MORPH_RECT,
                                    (self.consecutive_size, self.consecutive_size),
                                )
                                self.method_mask = cv2.dilate(
                                    self.method_mask, kernel, iterations=1
                                )
                            fgmask = cv2.subtract(fgmask, self.method_mask)

                        # create overlay for each method
                        self.frame_overlay = cv2.addWeighted(
                            self.frame_overlay, 1, self.overlay, overlay_weight, 0
                        )

                        # make data.frame
                        self.frame_df.insert(0, "frame_abs", self.idx1)
                        self.frame_df.insert(
                            1, "frame", int((self.idx1 - self.start_frame) / self.skip)
                        )
                        self.frame_df.insert(2, "mins", mins)
                        self.frame_df.insert(3, "secs", secs)
                        # self.df = self.df.append(
                        #     self.frame_df, ignore_index=True, sort=False
                        # )
                        self.df = pd.concat([self.df, self.frame_df], ignore_index=True, sort=False)


                ## select canvas
                if "methods" in vars(self):
                    if canvas == "overlay":
                        self.canvas = self.frame_overlay
                    elif canvas == "fgmask":
                        self.canvas = fgmask_copy
                    elif canvas == "fgmask_mod":
                        self.canvas = self.fgmask_mod
                    else:
                        self.canvas = self.frame
                else:
                    self.canvas = fgmask_copy

                ## resize
                if "resize_factor" in vars(self):
                    self.canvas = cv2.resize(
                        self.canvas,
                        (0, 0),
                        fx=self.resize_factor,
                        fy=self.resize_factor,
                    )

                ## convert to colour
                if len(self.canvas.shape) < 3:
                    self.canvas = cv2.cvtColor(self.canvas, cv2.COLOR_GRAY2BGR)

                ## draw masks
                if not self.masks.__class__.__name__ == "NoneType":
                    for key, value in self.masks[settings._mask_type].items():
                        self.canvas = visualization.draw_mask(
                            self.canvas, {settings._mask_type: {key: value}}, label=True
                        )

                ## feedback
                if flag_feedback == True:
                    cv2.namedWindow("phenopype", cv2.WINDOW_AUTOSIZE)
                    cv2.imshow("phenopype", self.canvas)

                # save output
                if self.flag_save_video == True:
                    if self.flag_save_colour and len(self.canvas.shape) < 3:
                        self.canvas = cv2.cvtColor(self.canvas, cv2.COLOR_GRAY2BGR)
                    self.writer.write(self.canvas)

            ## keep stream open
            if cv2.waitKey(1) & 0xFF == 27:
                self.capture.release()
                self.writer.release()
                break

        ## cleanup
        self.capture.release()
        if "writer" in vars(self):
            self.writer.release()
        cv2.destroyAllWindows()

        ## return DataFrame
        debug =  kwargs.get("debug", False)
        
        if debug:
            return self
        else:
            return self.df


class tracking_method:

    """
    Constructs a tracking method that can be supplied to the motion_tracker 
    class.
    
    Parameters
    ----------
    label: str, optional
        label for all objects detected by this method
    blur: int, optional
        blurring of fgbg-mask (kernel size)
    threshold: int, optional
        binarization of fgbg-mask after blurring (threshold value)
    remove_shadows: bool, optional
        if motion_tracker has detect_shadows=True, they can be removed here
    min_area : int, optional
        minimum contour area in pixels to be included
    max_area : int, optional
        maximum contour area in pixels to be included
    min_length : int, optional
        minimum diameter of boundary circle to be included
    max_length : int, optional
        maximum diameter of boundary circle to be included
    mode: {"single", "multiple"} str, optional
        track "multiple", or "single" (biggest by diameter) objects
    remove_shadows: bool, optional
        remove shadows if shadow-detection is actived in MOG-algorithm
    overlay_colour: {"red", "green", "blue", "black", "white"} str, optional
        which colour should tracked objects have
    operations: list (default: ["length", "area"])
        determines the type of operations to be performed on the detected objects:
            - "diameter" of the bounding circle of our object
            - "area" within the contour of our object
            - "grayscale" mean and standard deviation of grayscale pixel values 
              inside the object contours
            - "grayscale_background" background within boundingbox of contour
            - "bgr" mean and standard deviation of blue, green and red pixel 
              values inside the object contours
    """

    def __init__(
        self,
        label="m1",
        blur=5,
        threshold=127,
        remove_shadows=True,
        min_length=0,
        max_length=inf,
        min_area=0,
        max_area=inf,
        mode="multiple",
        overlay_colour="red",
        operations=[],
    ):

        ## kwargs
        self.blur_kernel = blur
        self.label = label
        self.overlay_colour = utils_lowlevel._get_bgr(overlay_colour)
        self.min_length, self.max_length = min_length, max_length
        self.min_area, self.max_area = min_area, max_area
        self.mode = mode
        self.operations = operations
        self.threshold_value = threshold
        self.remove_shadows = remove_shadows

    def _apply_masks(self, frame, masks):
        """
        Applies masks drawn using the motion_tracker.
        
        Internal reference - don't call this directly. 
        """
        if masks.__class__.__name__ == "dict":
            if settings._mask_type in masks:
                self.mask_bool = {}
                for key, value in masks[settings._mask_type].items():
                    polygons = masks[settings._mask_type][key]["data"][
                        settings._mask_type
                    ]
                    label = masks[settings._mask_type][key]["data"]["label"]
                    for coords in polygons:
                        mask_bool = utils_lowlevel._create_mask_bool(frame, coords)
                        self.mask_bool[label] = mask_bool

    def _print_settings(self, width=30, indent=1, compact=True):
        """
        Prints the settings of the tracking method. 
        
        Internal reference - don't call this directly. 
        """

        print_dict = {}
        for key, value in self.__dict__.items():
            if type(value) in [str, float, int, tuple, bool]:
                print_dict[key] = value
                
        pretty = pprint.PrettyPrinter(width=width, compact=compact, indent=indent)
        pretty.pprint(print_dict)

    def _run(self, frame, fgmask):
        """
        Run tracking method on current frame. 
        
        Internal reference - don't call this directly.      
        """

        ## initialize
        self.overlay = np.zeros_like(frame)
        self.overlay_bin = np.zeros(frame.shape[0:2], dtype=np.uint8)
        self.frame_df = pd.DataFrame()

        if self.remove_shadows == True:
            ret, fgmask = cv2.threshold(fgmask, 128, 255, cv2.THRESH_BINARY)

        ## blur
        fgmask = preprocessing.blur(fgmask, self.blur_kernel)

        # ## threshold
        fgmask = segmentation.threshold(
            fgmask, method="binary", invert=True, value=self.threshold_value
        )

        ## find contours
        contours, hierarchy = cv2.findContours(
            fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )

        ## perform operations on contours
        if len(contours) > 0:
            list_contours, list_area, list_length, list_center_coordinates = (
                [],
                [],
                [],
                [],
            )
            df_list, df_column_names = [], []

            # check if contour matches min/max length provided
            for contour in contours:
                if contour.shape[0] > 4:
                    center, radius = cv2.minEnclosingCircle(contour)
                    length = int(radius * 2)
                    area = int(cv2.contourArea(contour))
                    if all(
                        [
                            length > self.min_length and length < self.max_length,
                            area > self.min_area and area < self.max_area,
                        ]
                    ):
                        list_length.append(length)
                        list_area.append(area)
                        list_contours.append(contour)
                        list_center_coordinates.append(center)

            if len(list_contours) > 0:
                # if single biggest contour:
                if self.mode == "single":
                    if len(contours) == 1:
                        pass
                    elif len(contours) > 1:
                        max_idx = np.argmax(list_length)
                        list_contours = [list_contours[max_idx]]
                        list_length = [list_length[max_idx]]
                        list_area = [list_area[max_idx]]
                        list_center_coordinates = [list_center_coordinates[max_idx]]

                list_x, list_y = [], []
                list_grayscale, list_grayscale_background = [], []
                list_b, list_g, list_r = [], [], []
                list_mask_check = []

                for contour, center in zip(list_contours, list_center_coordinates):

                    # operations
                    x = int(center[0])
                    y = int(center[1])
                    list_x.append(x)
                    list_y.append(y)

                    if "mask_bool" in vars(self):
                        temp_list = []
                        for key, val in self.mask_bool.items():
                            temp_list.append(val[y, x])
                        list_mask_check.append(temp_list)

                    rx, ry, rw, rh = cv2.boundingRect(contour)
                    frame_roi = frame[ry : ry + rh, rx : rx + rw]
                    frame_roi_gray = cv2.cvtColor(frame_roi, cv2.COLOR_BGR2GRAY)
                    mask_roi = fgmask[ry : ry + rh, rx : rx + rw]

                    if any("grayscale" in o for o in self.operations):
                        grayscale = ma.array(
                            data=frame_roi_gray, mask=np.logical_not(mask_roi)
                        )
                        list_grayscale.append(int(np.mean(grayscale)))

                    if any("grayscale_background" in o for o in self.operations):
                        grayscale_background = ma.array(
                            data=frame_roi_gray, mask=mask_roi
                        )
                        if not grayscale_background.mask.all():
                            list_grayscale_background.append(
                                int(np.mean(grayscale_background))
                            )
                        else:
                            list_grayscale_background.append(9999)

                    if any("bgr" in o for o in self.operations):
                        b = ma.array(
                            data=frame_roi[:, :, 0], mask=np.logical_not(mask_roi)
                        )
                        list_b.append(int(np.mean(b)))
                        g = ma.array(
                            data=frame_roi[:, :, 1], mask=np.logical_not(mask_roi)
                        )
                        list_g.append(int(np.mean(g)))
                        r = ma.array(
                            data=frame_roi[:, :, 2], mask=np.logical_not(mask_roi)
                        )
                        list_r.append(int(np.mean(r)))

                    # drawing
                    self.overlay = cv2.drawContours(
                        self.overlay, [contour], 0, self.overlay_colour, -1
                    )  # Draw filled contour in mask
                    self.overlay = cv2.putText(
                        self.overlay,
                        self.label,
                        (x, y),
                        cv2.FONT_HERSHEY_SIMPLEX,
                        1,
                        self.overlay_colour,
                        1,
                        cv2.LINE_AA,
                    )
                    self.overlay = cv2.rectangle(
                        self.overlay,
                        (rx, ry),
                        (int(rx + rw), int(ry + rh)),
                        self.overlay_colour,
                        2,
                    )

                df_list = df_list + [list_x]
                df_list = df_list + [list_y]
                df_column_names = df_column_names + ["x", "y"]

                if any("diameter" in o for o in self.operations):
                    df_list = df_list + [list_length]
                    df_column_names.append("diameter")

                if any("area" in o for o in self.operations):
                    df_list = df_list + [list_area]
                    df_column_names.append("area")

                if any("grayscale" in o for o in self.operations):
                    df_list = df_list + [list_grayscale]
                    df_column_names.append("grayscale")

                if any("grayscale_background" in o for o in self.operations):
                    df_list = df_list + [list_grayscale_background]
                    df_column_names.append("grayscale_background")

                if any("bgr" in o for o in self.operations):
                    df_list = df_list + [list_b]
                    df_list = df_list + [list_g]
                    df_list = df_list + [list_r]
                    df_column_names = df_column_names + ["b", "g", "r"]

                frame_df = pd.DataFrame(data=df_list)
                frame_df = frame_df.transpose()
                frame_df.columns = df_column_names
                frame_df["label"] = self.label

                if "mask_bool" in vars(self):

                    mask_df = pd.DataFrame(list_mask_check, columns=[*self.mask_bool])
                    self.frame_df = pd.concat(
                        [frame_df.reset_index(drop=True), mask_df], axis=1
                    )

                else:
                    self.frame_df = frame_df

                self.contours = list_contours

                return fgmask, self.overlay, self.contours, self.frame_df

            else:
                frame_df = pd.DataFrame()
                return fgmask, self.overlay, [], self.frame_df

        else:
            frame_df = pd.DataFrame()
            return fgmask, self.overlay, [], self.frame_df
