import pygame
import math
import copy
from queue import Queue
from numpy import arange
from .shared_objects import SharedObjects
from .constants import FPS
from .helpers import circle_transform

class Animatable:
    def __init__(self, surface, centerx=0, centery=0, hidden=True, chain_movements=False):
        """
        Initializes an Surface for animation.
        Parameters:
        -----------
        surface: a pygame.Surface
        centerx: x-position of center of rectangle
        centery: y-position of center of rectangle
        hidden: whether or not the surface is displayed on screen
        chain_movements: if True, movements will be queued, otherwise, new
            movement requests will interrupt the current movement
        """
        self.surface = surface.copy()
        self.original_surface = surface.copy()
        self.rect = surface.get_rect()

        self.rect.centerx = centerx  # x position for top left corner
        self.rect.centery = centery  # y position for top left corner

        self.hidden = hidden

        self.chain_movements = chain_movements

        self.position_animation_queue = Queue()

        self.position_queue = Queue()

        self.rotozoom_generators = []
        self.color_generators = []

        self.current_scale = 1

    def get_frame(self):
        """
        Returns a blit pair indication what the surface is for this Animatable
        and where the surface should be displayed.
        """
        # 1. Apply transformations in scale or tilt (rotation/zoom)
        if len(self.rotozoom_generators):
            for generator in self.rotozoom_generators:
                did_trans = next(generator, False)
                if not did_trans:
                    self.rotozoom_generators = [
                        g for g in self.rotozoom_generators if not generator]

        # 2. Apply transformations of color
        if len(self.color_generators):
            for generator in self.color_generators:
                did_trans = next(generator, False)
                if not did_trans:
                    self.color_generators = [
                        g for g in self.color_generators if not generator]

        # 3. Apply changes in position
        if not self.position_queue.empty():
            self.rect.center = self.position_queue.get()
        elif not self.position_animation_queue.empty():
            args, function = self.position_animation_queue.get()
            items = function(*args)
            for item in items:
                self.position_queue.put(item)

        if not self.hidden:
            return (self.surface, self.rect)
        else:
            return None

    def hide(self):
        """
        Makes the card invisible on the screen.
        """
        self.hidden = True

    def show(self):
        """
        Makes the card visible on the screen.
        """
        self.hidden = False

    def instant_scale(self, scale):
        """
        Instantly changes the scale of the surface (no animation)
        Parameters:
        scale: proportion of original surface the surface should now be
        """
        x, y = self.rect.center

        orig_w = self.original_surface.get_rect().w
        orig_h = self.original_surface.get_rect().h
        w, h = (int(round(orig_w * scale)), int(round(orig_h * scale)))
        self.surface = pygame.transform.smoothscale(
            self.original_surface, (w, h))
        self.rect = self.surface.get_rect()
        self.instant_move(x, y)

        self.current_scale = scale

    def instant_move(self, x, y):
        """
        Instantly move the animatable to the given position.
        """
        self.rect.center = (x, y)

    def instant_color(self, RGB):
        """
        Instantly changes the color of the surface to a solid color.
        Parameters:
        -----------
        RGB: (r, g, b, a) tuple representing the color of the background (a is
            the alpha value)
        """
        original_surf = self.surface
        orig_rect = original_surf.get_rect()
        flash_surf = pygame.Surface((orig_rect.w, orig_rect.h))
        flash_surf = flash_surf.convert_alpha()
        r, g, b, a = RGB
        flash_surf.fill((r, g, b, a))
        self.surface.blit(flash_surf, (0, 0))

    def instant_rotate(self, angle):
        """
        Instantly rotates the surface by the given degree
        Parameters:
        -----------
        angle: degrees to rotate
        """
        x, y = self.rect.center
        self.surface = pygame.transform.rotate(self.surface, angle)
        self.rect = self.surface.get_rect()
        self.instant_move(x, y)

    ###########################################################################
    # Rotozoom Transformation Functions
    ###########################################################################

    def rotate(self, angle, duration):
        """
        Rotates the animatable by a number of degrees over a duration.

        This function will supercede rotate. If you want to rotate and zoom
        use the rotozoom function.

        Parameters:
        -----------
        angle: integer value; positive will rotate counter-clockwise and
            negative will rotate clockwise
        duration: how long it seconds before the rotation completes
        """
        def generator(surface, angles):
            """
            Generator function that alters this animatable's tilt.
            """
            original_surf = surface.copy()
            for angle in angles:
                x, y = self.rect.center
                self.surface = pygame.transform.rotate(original_surf, angle)
                self.instant_move(x, y)
                yield True

        angles = [step / duration *
                  angle for step in arange(0, duration, 1 / FPS)]
        angles.append(angle)

        self.rotozoom_generators.append(generator(self.surface, angles))

    def scale(self, from_scale, to_scale, duration=0.5, pulse=False):
        """
        The animatable shrinks/grows to the given scale from a given scale

        This function will supercede rotate. If you want to rotate and zoom
        use the rotozoom function.

        Parameters:s
        -----------
        from_scale: what proportional of original surface to start scaling from
        to_scale: what proportional of original surface to scale to
        duration: how long it seconds before the rotation completes
        pulse: if True, the surface will scale to target and then scale back to
            the starting scale
        """
        def generator(surface, dimensions):
            """
            Generator function that alters this animatable's size.
            """
            original_surf = surface.copy()
            for dimension in dimensions:
                x, y = self.rect.center
                self.surface = pygame.transform.smoothscale(
                    original_surf, dimension)
                self.rect = self.surface.get_rect()
                self.instant_move(x, y)
                yield True

        if pulse:
            def scale_fun(x): return (4*from_scale - 4 *
                                      to_scale)*(x - 0.5)**2 + to_scale
        else:
            def scale_fun(x): return (from_scale - to_scale) * \
                (x - 1)**2 + to_scale

        step_size = 1 / (duration * FPS)
        steps = [
            scale_fun(x) for x in arange(0, 1, step_size)
        ]

        rect = self.original_surface.get_rect()
        w, h = (rect.w, rect.h)
        dimensions = []
        for step in steps:
            dimensions.append(
                (int(round(w * step)), int(round(h * step)))
            )
        if pulse:
            dimensions.append((int(round(w * from_scale)),
                               int(round(h * from_scale))))
        else:
            dimensions.append(
                (int(round(w * to_scale)), int(round(h * to_scale))))

        self.rotozoom_generators = []
        self.rotozoom_generators.append(
            generator(self.original_surface, dimensions))

        self.current_scale = to_scale

    def rotoscale(self, from_scale, to_scale, angle, duration):
        """
        Rotates and zooms the animatable at the same time.

        Parameters:
        -----------
        from_scale: what proportional of original surface to start scaling from
        to_scale: what proportional of original surface to scale to
        angle: integer value; positive will rotate counter-clockwise and
            negative will rotate clockwise
        duration: how long it seconds before the rotation completes
        """
        def generator(surface, args):
            """
            Generator function that alters this animatable's size.
            """
            original_surf = surface.copy()
            for angle, scale in args:
                x, y = self.rect.center
                self.surface = pygame.transform.rotozoom(
                    original_surf, angle, scale)
                self.rect = self.surface.get_rect()
                self.instant_move(x, y)
                yield True

        angles = [step / duration *
                  angle for step in arange(0, duration, 1 / FPS)]
        angles.append(angle)

        def scale_fun(x): return (from_scale - to_scale) * \
            (x - 1)**2 + to_scale

        step_size = 1 / (duration * FPS)
        scales = [
            scale_fun(x) for x in arange(0, 1, step_size)
        ]
        scales.append(to_scale)

        args = zip(angles, scales)

        self.rotozoom_generators.append(generator(self.surface, args))

        self.current_scale = to_scale

    ###########################################################################
    # Color Transformation Functions
    ###########################################################################

    def flash(self, RGB, duration, intensity):
        """
        Flashes a surfaces, drawing the user's eye.

        This function will only work correctly if the animatable is not
        currently undergoing a rototation/zoom and if the animatable is
        a rectangle positions at a 90-degree angle.

        Parameters:
        -----------
        duration: how long in seconds the flash lasts
        intensity: how bright the surface gets; 0 - no flash, 100 - total white
        """

        def generator(surface, intensities):
            """
            Generator function that alters this animatable's surface.
            """
            original_surf = surface.copy()
            orig_rect = original_surf.get_rect()
            flash_surf = pygame.Surface((orig_rect.w, orig_rect.h))
            flash_surf = flash_surf.convert_alpha()
            r, g, b = RGB

            for intensity in intensities:
                surface.blit(original_surf, (0, 0))
                flash_surf.fill((r, g, b, intensity))
                surface.blit(flash_surf, (0, 0))
                yield True

        def flash_fun(x): return -(2*x - 1)**2 + 1

        step_size = 1 / (duration * FPS)
        steps = [
            flash_fun(x) for x in arange(0, 1, step_size)
        ]

        intensities = []
        for step in steps:
            intensities.append(intensity*step)
        intensities.append(0)

        self.color_generators.append(generator(self.surface, intensities))

    def fade_to_color(self, RGB, from_alpha, to_alpha, duration):
        """
        Fades the surface to the given RGB color tuple. The surface is not
        restored to its original color after the animation, but the original
        surface is maintained in the original_surface variable.
        Parameters:
        -----------
        RGB: (r, g, b, a) tuple indicating the color (a is an alpha value)
        duration: how long it should take for the fade to complete
        """

        def generator(alpha_vals):
            """
            Generator function that alters this animatable's surface.
            """
            original_surf = self.surface
            r, g, b = RGB
            original_surf.fill((r, g, b))
            for val in alpha_vals:
                original_surf.set_alpha(val)
                yield True

        step_size = 1 / (duration * FPS)

        if from_alpha < to_alpha:
            minimum = from_alpha
            maximum = to_alpha
        else:
            minimum = to_alpha
            maximum = from_alpha

        steps = [
            (x/1) * 255 for x in arange(minimum/255, maximum/255, step_size)
        ]

        if minimum == to_alpha:
            steps.reverse()

        alpha_vals = []
        for step in steps:
            alpha_vals.append(step)
        alpha_vals.append(to_alpha)

        self.color_generators.append(generator(alpha_vals))

    ###########################################################################
    # Position-Related Animation Functions
    ###########################################################################

    def move(self, new_centerx, new_centery, duration=0.5, steady=False):
        """
        Moves a card from one position to another.
        Parameters:
        -----------
        new_x: x-coordinate of new card position (top left corner)
        new_y: y-coordinate of new card position (top left corner)
        duration: indicates how long in seconds that the
            animation should last.
        """
        rect = self.rect
        if new_centerx == rect.centerx and new_centery == rect.centery:
            # No movement required
            return None

        if not self.chain_movements:
            self.position_animation_queue = Queue()

        args = (self.surface, new_centerx, new_centery, duration, steady)
        self.position_animation_queue.put(
            (args, self._calculate_move_positions)
        )

    def _calculate_move_positions(self, surface, end_centerx, end_centery, duration, steady):
        """
        Calculates a list of positions (x-, y-coordinates) which a surface
        should take to get from one point to another.
        Parameters:
        -----------
        surface: the pygame.Surface to move
        end_centerx: the ending x-coordinate
        end_centery: the ending y-coordinate
        duration: how long in seconds that the animation should take
        """
        rect = self.rect
        start_centerx, start_centery = rect.center
        x_diff, y_diff = (start_centerx - end_centerx,
                          start_centery - end_centery)
        if x_diff == 0 and y_diff == 0:
            return []

        # Distance between the two points (start and end)
        magnitude = math.sqrt(x_diff**2 + y_diff**2)
        # Unit vector
        x_unit, y_unit = (x_diff/magnitude, y_diff/magnitude)

        # Function to determine velocity of moving surface
        if steady:
            def mvt_fun(x): return 1 - x
        else:
            def mvt_fun(x): return -x**2 + 1

        step_size = 1 / (duration * FPS)
        steps = [
            mvt_fun(x) for x in arange(0, 1 - step_size, step_size)
        ][::-1]

        positions = []
        for step in steps:
            step_x = start_centerx + (-x_unit * magnitude * step)
            step_y = start_centery + (-y_unit * magnitude * step)
            positions.append((step_x, step_y))

        return positions

    def circle(self, center_x, center_y, angle, duration=0.25):
        """
        Moves a card from one position to another.
        Parameters:
        -----------
        center_x: x-coordinate of the center of circle being rotated around
        center_x: y-coordinate of the center of circle being rotated around
        radius: how far away from the circle to rotate
        angle: how many degrees to rotate around circle
        duration: how long the animation should take
        """
        args = (center_x, center_y, angle, duration)

        if not self.chain_movements:
            self.position_animation_queue = Queue()

        self.position_animation_queue.put(
            (args, self._calculate_circle_positions)
        )

    def _calculate_circle_positions(self, center_x, center_y, angle, duration):
        """
        Calculates a list of positions (x-, y-coordinates) which a surface
        should take to move around circle.
        Parameters:
        -----------
        center_x: x-coordinate of the center of circle being rotated around
        center_x: y-coordinate of the center of circle being rotated around
        radius: how far away from the circle to rotate
        angle: how many degrees to rotate around circle
        duration: how long the animation should take
        """
        rect = self.rect
        point_x, point_y = rect.center

        angles = []
        angle_delta = (angle / duration) / FPS
        for a in arange(0, angle, angle_delta):
            angles.append(a)
        angles.append(angle)

        positions = []
        for a in angles:
            position = circle_transform(
                point_x, point_y, center_x, center_y, a)
            positions.append(position)

        return positions

    def freeze(self, duration=0.5):
            """
            Card stays in place. Useful for chained animations.
            Parameters:
            -----------
            duration: indicates how long in seconds that the
                animation should last.
            """
            if duration == 0:
                return

            if not self.chain_movements:
                self.position_animation_queue = Queue()

            args = (self.surface, duration)
            self.position_animation_queue.put(
                (args, self._calc_freeze_positions)
            )

    def _calc_freeze_positions(self, surface, duration):
        """
        Calculates a list of positions (x-, y-coordinates).
        """
        rect = self.rect

        step_size = 1 / (duration * FPS)

        x, y = rect.center

        positions = [
            (x, y) for i in arange(0, 1 - step_size, step_size)
        ]

        return positions


from __future__ import print_function

import glob
import os
import sys
import time

# Set plot backend. Must happen before importing pylab.
import matplotlib
matplotlib.use('Agg')

pandeia_version_file = os.path.join(os.environ["pandeia_refdata"], "VERSION_PSF")
with open(pandeia_version_file, 'r') as inf:
    pandeia_version_info = inf.readline().strip()
print("Pandeia Version: {}".format(pandeia_version_info))

file_dir = os.path.basename(os.path.abspath(__file__))
stips_dir = os.path.abspath(os.path.join(file_dir, "..", ".."))
sys.path.append(stips_dir)

import numpy as np
import synphot as syn
import stsynphot as stsyn
from stips.utilities import InstrumentList
from stips import __version__ as stips_version_info
from astropy.io import fits as pyfits
from astropy import units as u

print("STIPS Version: {}".format(stips_version_info))

modes = {
            'nircamshort':  ['sw_imaging'],
            'nircamlong':   ['lw_imaging'],
            'miri':         ['imaging'],
            'wfc3ir':       ['imaging'],
            'wfi':          ['imaging']
        }
filters = {
            'nircamshort':  {'sw_imaging': ["f070w", "f090w", "f115w", "f140m", "f150w", "f162m", "f164n", "f182m", "f187n", "f200w", "f210m", "f212n"]},
            'nircamlong':   {'lw_imaging': ["f250m", "f277w", "f300m", "f323n", "f335m", "f356w", "f360m", "f405n", "f410m", "f430m", "f444w", "f460m", "f466n", "f470n",
                                            "f480m"
                                            ]
                            },
            'miri':         {'imaging': ["f560w", "f770w", "f1000w", "f1130w", "f1280w", "f1500w", "f1800w", "f2100w", "f2550w"]},
            'wfc3ir':       {'imaging': ["f110w", "f160w"]},
            'wfi':          {'imaging': ['f062', 'f087', 'f106', 'f129', 'f146', 'f149', 'f158', 'f184']}
          }

apertures = {
                'nircamshort':  {'sw_imaging':  "sw"},
                'nircamlong':   {'lw_imaging':  "lw"},
                'miri':         {'imaging':     "imager"},
                'wfc3ir':       {'imaging':     "default"},
                'wfi':          {'imaging':     "any"}
            }
area =  {
            'nircamlong':   253260.0,
            'nircamshort':  253260.0,
            'miri':         253260.0,
            'wfc3ir':       45238.93416,
            'wfi':          45238.93416
        }

def get_grid_points():
    grid_file = os.path.abspath(os.path.join(os.environ['PYSYN_CDBS'], "grid", "phoenix", "catalog.fits"))
    teff, Z, logg = np.array(()), np.array(()), np.array(())
    with pyfits.open(grid_file) as inf:
        indices = inf[1].data.field('INDEX')
        for row in indices:
            items = row.split(",")
            teff = np.append(teff, float(items[0]))
            Z = np.append(Z, float(items[1]))
            logg = np.append(logg, float(items[2]))
    return np.array((np.unique(Z), np.unique(logg), np.unique(teff), np.arange(-5.5, 16.0)))

if __name__ == '__main__':
    norm_bandpass = syn.SpectralElement.from_filter('johnson_i')
    coords = get_grid_points()
    print(coords)
    bandpasses = {}
    result_arrays = {}
    grid_path = os.path.join(os.getcwd(), "grid")
    if not os.path.exists(grid_path):
        os.makedirs(grid_path)

    instruments = InstrumentList()
    print("{}: Making Bandpasses...".format(time.ctime()))
    for instrument in instruments:
        my_instrument = instruments[instrument](log_level="WARNING")
        bandpasses[instrument.lower()] = {}
        result_arrays[instrument.lower()] = {}
        for mode in modes[instrument.lower()]:
            for filter in filters[instrument.lower()][mode]:
                print("\t{}: {},{},{},{}".format(time.ctime(), instrument, mode, filter, apertures[instrument.lower()][mode]))
                my_instrument.reset(0., 0., 0., filter.upper(), 0., psf=False, detectors=False)
                bandpasses[instrument.lower()][filter] = my_instrument.bandpass
                result_arrays[instrument.lower()][filter] = np.empty((len(coords[0]), len(coords[1]), len(coords[2]), len(coords[3])))
    print("Done\n")

    total = len(coords[0]) * len(coords[1]) * len(coords[2]) * len(coords[3])
    n = 0
    for i, Z in enumerate(coords[0]):
        print("{}: Starting Z = {}".format(time.ctime(), Z))
        for j, logg in enumerate(coords[1]):
            print("\t{}: Starting log(g) = {}".format(time.ctime(), logg))
            for k, teff in enumerate(coords[2]):
                print("\t\t{}: Starting Teff = {}".format(time.ctime(), teff))
                try:
                    spec = stsyn.grid_to_spec('phoenix', teff, Z, logg)
                    counts = False
                    if np.sum(spec(spec.waveset)) > 0.:
                        counts = True
                except stsyn.exceptions.ParameterOutOfBounds:
                    counts = False
                for l, mag in enumerate(coords[3]):
                    msg = "\t\t\t{:6d} of {}: {}: Starting Z = {}, log(g) = {}, Teff = {:7.1f}, Mabs = {:>4}"
                    print(msg.format(n+1, total, time.ctime(), Z, logg, teff, mag), end='')
                    if counts:
                        norm_value = mag*u.ABmag
                        spec_norm = spec.normalize(norm_value, norm_bandpass)
                    for instrument in instruments:
                        for mode in modes[instrument.lower()]:
                            for filter in filters[instrument.lower()][mode]:
                                if counts:
                                    obs = syn.Observation(spec_norm, bandpasses[instrument.lower()][filter], binset=spec_norm.waveset)
                                    result_arrays[instrument.lower()][filter][i,j,k,l] = obs.countrate(area[instrument.lower()]).value
                                    print(".", end='')
                                else:
                                    result_arrays[instrument.lower()][filter][i,j,k,l] = 0.
                                    print("x", end='')
                    print("")
                    n += 1

    print("{}: Saving files...".format(time.ctime()), end='')
    with open(os.path.join(os.getcwd(), "grid", "VERSION.txt"), "wt") as outf:
        outf.write("Pandeia: {}\n".format(pandeia_version_info))
        outf.write("STIPS: {}\n".format(stips_version_info))
    np.save(os.path.join(os.getcwd(), 'grid', 'input.npy'), coords)
    for instrument in instruments:
        for mode in modes[instrument.lower()]:
            for filter in filters[instrument.lower()][mode]:
                np.save(os.path.join(os.getcwd(), 'grid', 'result_{}_{}.npy'.format(instrument.lower(), filter)),
                        result_arrays[instrument.lower()][filter])
    print("done")


#!/usr/bin/env python3

import copy, glob, argparse, random, pkg_resources, pathlib

import numpy, yaml, pyfastaq, pandas
import gumpy

import gpas_covid_synthetic_reads as gcsr

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument("--variant_definitions",default=False,help="the path to the variant_definitions repository/folder from phe-genomics ")
    parser.add_argument("--pango_definitions",default=False,help="the path to the constellations repository/folder from cov-lineages ")
    parser.add_argument("--output",required=False,help="the stem of the output file")
    parser.add_argument("--variant_name",default='Reference',help="the name of the variant, default is Reference")
    parser.add_argument("--reference",required=False,default=pkg_resources.resource_filename("gpas_covid_synthetic_reads", 'data/MN908947.3.gbk'),help="the GenBank file of the covid reference (if not specified, the MN908947.3.gbk reference will be used)")
    parser.add_argument("--tech",required=True,help="whether to generate illumina (paired) or nanopore (unpaired) reads")
    parser.add_argument("--primers",nargs='+',default=['articv3'],help="the name of the primer schema, must be on of articv3, articv4, midnight1200, ampliseq")
    parser.add_argument("--read_length",default=None,type=int,help="if specified, the read length in bases, otherwise defaults to the whole amplicon")
    parser.add_argument("--read_stddev",default=0,type=int,help="the standard deviation in the read lengths (default value is 0)")
    parser.add_argument("--depth",nargs='+',default=[500],type=int,help="the depth (default value is 500)")
    parser.add_argument("--depth_stddev",default=0,type=int,help="the standard deviation of the depth distribution (default value is 0)")
    parser.add_argument("--snps",nargs='+',default=[0],type=int,help="the number of snps to randomly introduce into the sequence")
    parser.add_argument("--repeats",default=1,type=int,help="how many repeats to create")
    parser.add_argument("--error_rate",nargs='+',default=[0.0],type=float,help="the percentage base error rate (default value is 0.0)")
    parser.add_argument("--drop_amplicons", nargs='+', type=int, required=False,help="the number (int) of one or more amplicons to drop i.e. have no reads.")
    parser.add_argument("--write_fasta", action="store_true", help="whether to write out the FASTA file for the variant")
    parser.add_argument("--bias_amplicons", nargs='+', type=int, default=None, help="whether to introduce an incorrect SNP in one or more specified amplicons")
    parser.add_argument("--bias_primers", nargs='+', type=int, default=None, help="whether to introduce an incorrect SNP in both primers of an amplicon")
    parser.add_argument("--drop_forward_amplicons", nargs='+', type=int, required=False,help="the names of one or more amplicons where there will be no reads mapping to the forward strand.")
    options = parser.parse_args()

    # load in the covid reference using gumpy
    covid_reference=gumpy.Genome(options.reference)

    error_rates=numpy.array(options.error_rate)/100.

    bases = {'a','t','c','g'}

    assert options.tech in ['illumina', 'nanopore']

    assert not (options.variant_definitions and options.pango_definitions), 'cannot specify both variant_definitions and pango_definitions'

    if options.drop_amplicons is not None:
        assert len(options.primers)==1, 'can only specify dropped amplicons for a single primer scheme!'

    for primer_name in options.primers:
        assert primer_name in ['articv3','articv4','midnight1200', 'ampliseq']

        if primer_name=='articv3':
            amplicon_file = pkg_resources.resource_filename("gpas_covid_synthetic_reads", 'data/covid-artic-v3.amplicons.csv')
        elif primer_name=='articv4':
            amplicon_file = pkg_resources.resource_filename("gpas_covid_synthetic_reads", 'data/covid-artic-v4.amplicons.csv')
        elif primer_name=='midnight1200':
            amplicon_file = pkg_resources.resource_filename("gpas_covid_synthetic_reads", 'data/covid-midnight-1200.amplicons.csv')
        elif primer_name=='ampliseq':
            amplicon_file = pkg_resources.resource_filename("gpas_covid_synthetic_reads", 'data/covid-ampliseq-v1.amplicons.csv')

        # load the definitions of the amplicons
        amplicons = pandas.read_csv(amplicon_file,\
                                    dtype = {'start': int, 'end': int, 'length': int, 'number': int,
                                             'start_left': int, 'end_left': int,
                                             'start_right': int, 'end_right': int,
                                             'start_amplicon': int, 'end_amplicon': int})

        if options.drop_amplicons is not None:
            for i in options.drop_amplicons:
                assert i in list(amplicons['number']), 'amplicon '+i+' not found in '+primer_name+' scheme'

        if options.drop_forward_amplicons is not None and options.drop_amplicons is not None:
            for i in options.drop_forward_amplicons:
                assert i not in options.drop_amplicons, 'cannot both drop an amplicon andd drop its forward strand'
                assert i in list(amplicons['number']), 'amplicon '+i+' not found in '+primer_name+' scheme'

        if options.bias_amplicons is not None and options.drop_amplicons is not None:
            for i in options.bias_amplicons:
                assert i not in options.drop_amplicons, 'cannot both bias and drop an amplicon'
                assert i in list(amplicons['number']), 'amplicon '+i+' not found in '+primer_name+' scheme'

        if options.bias_primers is not None and options.drop_amplicons is not None:
            for i in options.bias_primers:
                assert i not in options.drop_amplicons, 'cannot both bias and drop an amplicon'
                assert i in list(amplicons['number']), 'amplicon '+i+' not found in '+primer_name+' scheme'

        if options.variant_definitions:

            variant_definitions = gcsr.load_variant_definitions(options.variant_definitions)

            # check that the variant that has been specified has a YAML file!
            if options.variant_name != 'reference':
                assert options.variant_name in variant_definitions.keys(), "specified variant not defined here "+options.variant_definitions

            variant = gcsr.VariantGenome(covid_reference,
                                         variant_definitions,
                                         options.variant_name)

            variant_source = 'phe'

        elif options.pango_definitions:

            variant_definitions = gcsr.load_pango_definitions(options.pango_definitions)

            # check that the variant that has been specified has a JSON file!
            if options.variant_name != 'reference':
                assert options.variant_name in variant_definitions.keys(), "specified variant not defined here "+options.variant_name

            # check that this isn't a variant which is layered on top of a parent
            if 'parent_lineage' in variant_definitions[options.variant_name]['variant']:

                parent = 'c' + variant_definitions[options.variant_name]['variant']['parent_lineage']

                print("Building {} from {}".format(options.variant_name, parent))

                prevariant = gcsr.PangoGenome(covid_reference,
                                           variant_definitions,
                                           parent)

                variant = gcsr.PangoGenome(prevariant.expected,
                                           variant_definitions,
                                           options.variant_name,
                                           indels=prevariant.indels)
            else:

                variant = gcsr.PangoGenome(covid_reference,
                                           variant_definitions,
                                           options.variant_name)

            variant_source = 'cov'

        else:
            raise ValueError('must specify either --variant_definitions or --pango_definitions')

        description = options.variant_name
        #+"_"+options.primer_definition.split('.')[0]+"_readlength_"+str(options.read_length)+"_depth_"+str(options.depth)

        # index_lookup=variant.index_lookup

        # drop any amplicons specified
        if options.drop_amplicons is not None:

            for chosen_amplicon in options.drop_amplicons:

                row = amplicons[amplicons.number == chosen_amplicon]

                mask = (covid_reference.nucleotide_index >= int(row['start_amplicon'])) & (covid_reference.nucleotide_index <= int(row['end_amplicon']))

                variant.expected.nucleotide_sequence[mask] = 'n'

        if options.drop_forward_amplicons is not None:

            for chosen_amplicon in options.drop_forward_amplicons:

                row = amplicons[amplicons.number == chosen_amplicon]

                mask = (covid_reference.nucleotide_index >= int(row['start_left'])) & (covid_reference.nucleotide_index <= int(row['end_right']))

                variant.expected.nucleotide_sequence[mask] = 'n'

        if options.bias_amplicons is not None:

            for chosen_amplicon in options.bias_amplicons:

                row = amplicons[amplicons.number == chosen_amplicon]

                idx = numpy.random.choice(numpy.arange(int(row['start_amplicon']),int(row['end_amplicon'])))
                mask = covid_reference.nucleotide_index == idx
                ref_base = covid_reference.nucleotide_sequence[mask]
                new_bases = bases ^ set(ref_base)
                new_base = random.choice(list(new_bases))

                variant.input1.nucleotide_sequence[mask] = new_base
                variant.input2.nucleotide_sequence[mask] = ref_base
                variant.expected.nucleotide_sequence[mask] = 'n'

        if options.bias_primers is not None:

            for chosen_amplicon in options.bias_primers:

                row = amplicons[amplicons.number == chosen_amplicon]

                idx = numpy.random.choice(numpy.arange(int(row['start_left']),int(row['end_left'])))
                mask = covid_reference.nucleotide_index == idx
                ref_base = covid_reference.nucleotide_sequence[mask]
                new_bases = bases ^ set(ref_base)
                new_base = random.choice(list(new_bases))
                variant.input1.nucleotide_sequence[mask] = new_base
                variant.input2.nucleotide_sequence[mask] = ref_base
                variant.expected.nucleotide_sequence[mask] = 'n'

                idx = numpy.random.choice(numpy.arange(int(row['start_right']),int(row['end_right'])))
                mask = covid_reference.nucleotide_index == idx
                ref_base = covid_reference.nucleotide_sequence[mask]
                new_bases = bases ^ set(ref_base)
                new_base = random.choice(list(new_bases))
                variant.input1.nucleotide_sequence[mask] = new_base
                variant.input2.nucleotide_sequence[mask] = ref_base
                variant.expected.nucleotide_sequence[mask] = 'n'

        def tweak_amplicons(row, idx, indel_length, start, end):
            # check if indel lies in the region being considered
            if idx >= row[start] and idx < row[end]:
                return pandas.Series([row[start], row[end] + indel_length])
            elif row[start] > idx:
                return pandas.Series([row[start] + indel_length, row[end] + indel_length])
            else:
                return pandas.Series([row[start], row[end]])

        for (idx,indel_length) in variant.indels:
            amplicons[['start','end']] = amplicons.apply(tweak_amplicons, args=(idx,indel_length,'start','end'), axis=1)
            amplicons[['start_left','end_left']] = amplicons.apply(tweak_amplicons, args=(idx,indel_length,'start_left','end_left'), axis=1)
            amplicons[['start_right','end_right']] = amplicons.apply(tweak_amplicons, args=(idx,indel_length,'start_right','end_right'), axis=1)
            amplicons[['start_amplicon','end_amplicon']] = amplicons.apply(tweak_amplicons, args=(idx,indel_length,'start_amplicon','end_amplicon'), axis=1)

        for snps in options.snps:

            current_variant = copy.deepcopy(variant)

            if snps > 0:

                snp_indices = numpy.random.choice(current_variant.expected.nucleotide_index,size=snps,replace=False)

                for i in snp_indices:
                    mask = current_variant.expected.nucleotide_index == i
                    current_base = current_variant.expected.nucleotide_sequence[mask]

                    # don't mutate an N
                    if current_base == 'n':
                        continue

                    new_bases = bases ^ set(current_base)
                    new_base = random.choice(list(new_bases))
                    current_variant.expected.nucleotide_sequence[mask] = new_base
                    current_variant.input1.nucleotide_sequence[mask] = new_base
                    current_variant.input2.nucleotide_sequence[mask] = new_base

            input1_genome = current_variant.input1.build_genome_string(fixed_length=False)
            input1_genome = pyfastaq.sequences.Fasta(id_in=current_variant.name,
                                                     seq_in=input1_genome.upper())
            input1_genome = input1_genome.to_Fastq([40] * len(input1_genome))

            input2_genome = current_variant.input2.build_genome_string(fixed_length=False)
            input2_genome = pyfastaq.sequences.Fasta(id_in=current_variant.name,
                                                     seq_in=input2_genome.upper())
            input2_genome = input2_genome.to_Fastq([40] * len(input2_genome))

            expected_genome = current_variant.expected.build_genome_string(fixed_length=False)
            expected_genome = pyfastaq.sequences.Fasta(id_in=current_variant.name,
                                                    seq_in=expected_genome.upper())
            expected_genome = expected_genome.to_Fastq([40] * len(expected_genome))

            for depth in options.depth:

                for error_rate in error_rates:

                    assert 100>error_rate>=0

                    for repeat in range(options.repeats):

                        # build an automatic output string if no output is specified or it is a pure directory
                        if options.output is None or pathlib.Path(options.output).is_dir():
                            outputstem = options.tech + '-' + primer_name.lower() + '-' + options.variant_name + '-' +\
                                         variant_source + '-' + str(snps) + 'snps-' + str(depth) + 'd-' +\
                                         str(error_rate) + 'e-'
                            if options.drop_amplicons is not None:
                                outputstem += ''.join('a' + str(i) for i in options.drop_amplicons)
                                outputstem += 'da-'
                            if options.drop_forward_amplicons is not None:
                                outputstem += ''.join('a' + str(i) for i in options.drop_forward_amplicons)
                                outputstem += 'df-'
                            if options.bias_amplicons is not None:
                                outputstem += ''.join('a' + str(i) for i in options.bias_amplicons)
                                outputstem += 'ba-'
                            if options.bias_primers is not None:
                                outputstem += ''.join('a' + str(i) for i in options.bias_primers)
                                outputstem += 'bp-'
                            outputstem += str(repeat)
                        else:
                            outputstem = options.output

                        if options.write_fasta:

                            current_variant.expected.save_fasta(outputstem+".fasta",\
                                                        fixed_length=False,\
                                                        overwrite_existing=True,\
                                                        description=variant.name)

                        if options.tech=='illumina':

                            with open(outputstem+"_1.fastq", "w") as f1, open(outputstem+"_2.fastq", "w") as f2:

                                for idx,row in amplicons.iterrows():

                                    amplicon_number = row['number']

                                    if options.drop_amplicons is not None and amplicon_number in options.drop_amplicons:
                                        continue
                                    # We'll make a read pair that looks like this:
                                    #
                                    #  |------ read1 ------------->
                                    #                      <--------- read2 -----|
                                    #  |-------------- amplicon -----------------|
                                    if options.read_length is not None:
                                        assert options.read_length < row["end"] - row["start"] + 1 < 2 * options.read_length

                                    # PWF variable
                                    # start = numpy.where(index_lookup == row["start"])[0][0]
                                    # end = numpy.where(index_lookup == row["end"])[0][0]
                                    start = row["start"]-1
                                    end = row["end"]-1

                                    if options.depth_stddev == 0:
                                        actual_depth = depth
                                    else:
                                        actual_depth = int(numpy.random.normal(depth, options.depth_stddev))

                                    for i in range(0, int(actual_depth / 2)):

                                        if options.read_length is None:
                                            length = end - start
                                        else:
                                            length = int(numpy.random.normal(options.read_length, options.read_stddev))

                                        if options.bias_amplicons is not None or options.bias_primers is not None:
                                            if (amplicon_number in options.bias_amplicons) or (amplicon_number in options.bias_primers):
                                                read1 = input2_genome.subseq(start, start + length)
                                                read2 = input2_genome.subseq(end - length, end)
                                            else:
                                                read1 = input1_genome.subseq(start, start + length)
                                                read2 = input1_genome.subseq(end - length, end)
                                        elif options.drop_forward_amplicons is not None:
                                            read1 = input1_genome.subseq(start, start + length)
                                            read2 = input1_genome.subseq(end - length, end)
                                        else:
                                            read1 = expected_genome.subseq(start, start + length)
                                            read2 = expected_genome.subseq(end - length, end)

                                        # reverse complement the second read
                                        read2.revcomp()

                                        if error_rate > 0:
                                            read1.seq = gcsr.mutate_read(read1.seq, error_rate=error_rate)
                                            read2.seq = gcsr.mutate_read(read2.seq, error_rate=error_rate)

                                        read1.id = f"{amplicon_number}.{i} /1"
                                        read2.id = f"{amplicon_number}.{i} /2"

                                        if options.drop_forward_amplicons is not None:
                                             if amplicon_number not in options.drop_forward_amplicons:
                                                 print(read1, file=f1)
                                        else:
                                            print(read1, file=f1)
                                        print(read2, file=f2)

                                        read1.id = f"{amplicon_number}.{i}.2 /2"
                                        read2.id = f"{amplicon_number}.{i}.2 /1"
                                        print(read1, file=f2)
                                        if options.drop_forward_amplicons is not None:
                                            if amplicon_number not in options.drop_forward_amplicons:
                                                print(read2, file=f1)
                                        else:
                                                print(read2, file=f1)

                        elif options.tech == 'nanopore':

                            with open(outputstem+".fastq", "w") as f1:

                                for idx, row in amplicons.iterrows():

                                    amplicon_number = row['number']

                                    if options.drop_amplicons is not None and amplicon_number in options.drop_amplicons:
                                        continue

                                    # PWF variable
                                    # start = numpy.where(index_lookup == row["start"])[0][0]
                                    # end = numpy.where(index_lookup == row["end"])[0][0]
                                    start = row["start"]-1
                                    end = row["end"]-1

                                    if options.depth_stddev == 0:
                                        actual_depth = depth
                                    else:
                                        actual_depth = int(numpy.random.normal(depth, options.depth_stddev))

                                    for i in range(0, int(actual_depth / 2)):

                                        if options.drop_forward_amplicons is not None and amplicon_number in options.drop_forward_amplicons:
                                            continue

                                        if options.read_length is None:
                                            length = end - start
                                        else:
                                            length = int(numpy.random.normal(options.read_length, options.read_stddev))

                                        if options.bias_amplicons is not None or options.bias_primers is not None:
                                            if (amplicon_number in options.bias_amplicons) or (amplicon_number in options.bias_primers):
                                                read1 = input2_genome.subseq(start, start + length)
                                            else:
                                                read1 = input1_genome.subseq(start, start + length)
                                        else:
                                            read1 = expected_genome.subseq(start, start + length)

                                        if error_rate > 0:
                                            read1.seq = gcsr.mutate_read(read1.seq, error_rate=error_rate)

                                        read1.id = f"{amplicon_number}.{i} forward"
                                        print(read1, file=f1)

                                    for i in range(0, int(actual_depth / 2)):

                                        if options.read_length is None:
                                            length = end - start
                                        else:
                                            length = int(numpy.random.normal(options.read_length, options.read_stddev))

                                        if options.bias_amplicons is not None or options.bias_primers is not None:
                                            if (amplicon_number in options.bias_amplicons) or (amplicon_number in options.bias_primers):
                                                read2 = input2_genome.subseq(end - length, end)
                                            else:
                                                read2 = input1_genome.subseq(end - length, end)
                                        elif options.drop_forward_amplicons is not None:
                                            read2 = input1_genome.subseq(end - length, end)
                                        else:
                                            read2 = expected_genome.subseq(end - length, end)

                                        read2.revcomp()

                                        if error_rate > 0:
                                            read2.seq = gcsr.mutate_read(read2.seq, error_rate=error_rate)

                                        read2.id = f"{amplicon_number}.{i}.2 reverse"
                                        print(read2, file=f1)

                        else:
                            raise ValueError("--tech must be one of illumina or nanopore!")


from keras import backend as K
from keras.models import load_model
from keras.preprocessing import image
from keras.optimizers import Adam
#from imageio import imread
import numpy as np
import random
import cv2
#from matplotlib import pyplot as plt

from models.keras_ssd300 import ssd_300
from keras_loss_function.keras_ssd_loss import SSDLoss
from keras_layers.keras_layer_AnchorBoxes import AnchorBoxes
from keras_layers.keras_layer_DecodeDetections import DecodeDetections
from keras_layers.keras_layer_DecodeDetectionsFast import DecodeDetectionsFast
from keras_layers.keras_layer_L2Normalization import L2Normalization

from ssd_encoder_decoder.ssd_output_decoder import decode_detections, decode_detections_fast

from data_generator.object_detection_2d_data_generator import DataGenerator
from data_generator.object_detection_2d_photometric_ops import ConvertTo3Channels
from data_generator.object_detection_2d_geometric_ops import Resize
from data_generator.object_detection_2d_misc_utils import apply_inverse_transforms

from threading import Thread
from datetime import datetime
import time

# Set the image size.
img_height = 300
img_width = 300

classes = ['background',
            'Vehicle plate registration']
colors = ( (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) for i in range(len(classes)) )

orig_images = [] # Store the images here.
input_images = [] # Store resized versions of the images here.

# We'll only load one image in this example.
#img_path = '/home/bende/Datasets/OpenImages_face_plate/validation/Vehicle registration plate/0c756c9366a8cb10.jpg'
#img_path = '../ssd_keras_files/voiturefeu.jpg'
video_path = '../ssd_keras_files/UK_Dash_Cam_IDIOT_DRIVERS.mp4'
confidence_threshold = 0.5

# TODO: Set the path to the `.h5` file of the model to be loaded.
#model_path = '../ssd_keras_files/ssd300_OID_plates_epoch-97_loss-2.9400_val_loss-2.4888.h5'
model_path = '../ssd_keras_files/ssd300_OID_plates_MODEL.h5'

class CountsPerSec:
    """
    Class that tracks the number of occurrences ("counts") of an
    arbitrary event and returns the frequency in occurrences
    (counts) per second. The caller must increment the count.
    """

    def __init__(self):
        self._start_time = None
        self._num_occurrences = 0

    def start(self):
        self._start_time = datetime.now()
        return self

    def increment(self):
        self._num_occurrences += 1

    def countsPerSec(self):
        elapsed_time = (datetime.now() - self._start_time).total_seconds()
        return self._num_occurrences / elapsed_time

class VideoGet:
    """
    Class that continuously gets frames from a VideoCapture object
    with a dedicated thread.
    """

    def __init__(self, src=0):
        self.stream = cv2.VideoCapture(src)
        (self.grabbed, self.frame) = self.stream.read()
        self.stopped = False

    def start(self):
        Thread(target=self.update, args=()).start()
        return self

    def update(self):
        # keep looping infinitely until the thread is stopped
        while True:
          # if the thread indicator variable is set, stop the thread
          if self.stopped:
            return

          # otherwise, read the next frame from the stream
          (self.grabbed, self.frame) = self.stream.read()

    def read(self):
        # return the frame most recently read
        return self.frame

    def stop(self):
        self.stopped = True

class VideoShow:
    """
    Class that continuously shows a frame using a dedicated thread.
    """

    def __init__(self, frame=None):
        self.frame = frame
        self.stopped = False

    def start(self):
        Thread(target=self.show, args=()).start()
        return self

    def show(self):
        while not self.stopped:
            cv2.imshow("Video", self.frame)
            if cv2.waitKey(1) == ord("q"):
                self.stopped = True

    def stop(self):
        self.stopped = True

def putIterationsPerSec(frame, iterations_per_sec):
    """
    Add iterations per second text to lower-left corner of a frame.
    """

    cv2.putText(frame, "{:.0f} iterations/sec".format(iterations_per_sec),
        (10, 450), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255))
    return frame

def predict_on_image(read_image, model):
    start = time.time()

    img = image.img_to_array(cv2.resize(read_image, (img_height, img_width)))

    y_pred = model.predict(np.array([img]))
    ####TIME####
    timebreak = time.time()
    seconds = timebreak - start
    print("Time taken to make predictions: {0} seconds".format(seconds))
    ############

    y_pred_thresh = [y_pred[k][y_pred[k,:,1] > confidence_threshold] for k in range(y_pred.shape[0])]

    if y_pred_thresh[0]:
        np.set_printoptions(precision=2, suppress=True, linewidth=90)
        print("Predicted boxes:\n")
        print('   class   conf xmin   ymin   xmax   ymax')
        print(y_pred_thresh[0])

    return y_pred_thresh

# Display the image and draw the predicted boxes onto it.
def display_pediction_image(read_image, y_pred_thresh):
    # Set the colors for the bounding boxes
    #colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()

    """
    plt.figure(figsize=(20,12))
    plt.imshow(orig_images[0])

    current_axis = plt.gca()

    for box in y_pred_thresh[0]:
        # Transform the predicted bounding boxes for the 300x300 image to the original image dimensions.
        xmin = box[2] * orig_images[0].shape[1] / img_width
        ymin = box[3] * orig_images[0].shape[0] / img_height
        xmax = box[4] * orig_images[0].shape[1] / img_width
        ymax = box[5] * orig_images[0].shape[0] / img_height
        color = colors[int(box[0])]
        label = '{}: {:.2f}'.format(classes[int(box[0])], box[1])
        current_axis.add_patch(plt.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, color=color, fill=False, linewidth=2))
        current_axis.text(xmin, ymin, label, size='x-large', color='white', bbox={'facecolor':color, 'alpha':1.0})

    plt.show()
    """
    # Visualize detected bounding boxes.
    for box in y_pred_thresh[0]:
        classId = int(box[0])
        score = box[1]
        color = colors[int(box[0])]
        xmin = int(box[2] * read_image.shape[1] / img_width)
        ymin = int(box[3] * read_image.shape[0] / img_height)
        xmax = int(box[4] * read_image.shape[1] / img_width)
        ymax = int(box[5] * read_image.shape[0] / img_height)
        cv2.blur(src=read_image[ymin:ymax, xmin:xmax],dst=read_image[ymin:ymax, xmin:xmax], ksize=(20,10))
        cv2.rectangle(read_image, (xmin, ymin), (xmax, ymax), color, thickness=2)
        label = '{}: {:.2f}'.format(classes[int(box[0])], box[1])
        cv2.putText(read_image, label, (xmin, ymin), cv2.FONT_HERSHEY_PLAIN, 1.0, (255,255,255), 1)

# 1: Build the Keras model
K.clear_session() # Clear previous models from memory.
"""
model = ssd_300(image_size=(img_height, img_width, 3),
                n_classes=1,
                mode='inference',
                l2_regularization=0.005,
                scales=[0.1, 0.2, 0.37, 0.54, 0.71, 0.88, 1.05], # The scales for MS COCO are [0.07, 0.15, 0.33, 0.51, 0.69, 0.87, 1.05]
                aspect_ratios_per_layer=[[1.0, 2.0, 0.5],
                                        [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                        [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                        [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                        [1.0, 2.0, 0.5],
                                        [1.0, 2.0, 0.5]],
                two_boxes_for_ar1=True,
                steps=[8, 16, 32, 64, 100, 300],
                offsets=[0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
                clip_boxes=False,
                variances=[0.1, 0.1, 0.2, 0.2],
                normalize_coords=True,
                subtract_mean=[123, 117, 104],
                swap_channels=[2, 1, 0],
                confidence_thresh=confidence_threshold,
                iou_threshold=0.45,
                top_k=200,
                nms_max_output_size=400)

# 2: Load the trained weights into the model.

model.load_weights(model_path, by_name=True)

# 3: Compile the model so that Keras won't complain the next time you load it.

adam = Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)

ssd_loss = SSDLoss(neg_pos_ratio=3, alpha=1.0)

model.compile(optimizer=adam, loss=ssd_loss.compute_loss)
"""

# We need to create an SSDLoss object in order to pass that to the model loader.
ssd_loss = SSDLoss(neg_pos_ratio=3, n_neg_min=0, alpha=1.0)

model = load_model(model_path, custom_objects={'AnchorBoxes': AnchorBoxes,
                                               'L2Normalization': L2Normalization,
                                               'DecodeDetections': DecodeDetections,
                                               'compute_loss': ssd_loss.compute_loss})

"""
read_image = cv2.imread(img_path)
y_pred_thresh = predict_on_image(read_image, model)
display_pedicted_image(read_image, y_pred_thresh)
cv2.waitKey()
"""
"""
# Create a VideoCapture object and read from input file
# If the input is the camera, pass 0 instead of the video file name
cap = cv2.VideoCapture(video_path)

# Check if camera opened successfully
if (cap.isOpened()== False):
  print("Error opening video stream or file")

fps = cap.get(cv2.CAP_PROP_FPS)
print("Frames per second using video.get(cv2.CAP_PROP_FPS) : {0}".format(fps))
#frames_to_skip = fps/5
frames_to_skip = 1
i=1
# Read until video is completed
while(cap.isOpened()):
  # Capture frame-by-frame
  ret, frame = cap.read()
  if ret == True:

    if i == frames_to_skip:
        y_pred_thresh = predict_on_image(frame, model)
        display_pediction_image(frame, y_pred_thresh)
        cv2.imshow('SSD Keras Plates recognition', frame)

        # Press Q on keyboard to  exit
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break
        i=0
    i+=1

  # Break the loop
  else:
    break

# When everything done, release the video capture object
cap.release()

# Closes all the frames
cv2.destroyAllWindows()

"""

cap = cv2.VideoCapture(video_path)
cps = CountsPerSec().start()

while True:
    (grabbed, frame) = cap.read()
    if not grabbed or cv2.waitKey(1) == ord("q"):
        break

    frame = putIterationsPerSec(frame, cps.countsPerSec())
    y_pred_thresh = predict_on_image(frame, model)
    display_pediction_image(frame, y_pred_thresh)
    cv2.imshow("Video", frame)
    cps.increment()

"""

video_getter = VideoGet(video_path).start()
cps = CountsPerSec().start()
while True:
        if (cv2.waitKey(1) == ord("q")) or video_getter.stopped:
            video_getter.stop()
            break

        frame = video_getter.read()
        frame = putIterationsPerSec(frame, cps.countsPerSec())
        y_pred_thresh = predict_on_image(frame, model)
        display_pediction_image(frame, y_pred_thresh)
        cv2.imshow('SSD Keras Plates recognition', frame)
        cps.increment()
"""

"""
video_getter = VideoGet(video_path).start()
video_shower = VideoShow(video_getter.frame).start()
cps = CountsPerSec().start()

while True:
    if video_getter.stopped or video_shower.stopped:
        video_shower.stop()
        video_getter.stop()
        break

    frame = video_getter.frame
    frame = putIterationsPerSec(frame, cps.countsPerSec())
    y_pred_thresh = predict_on_image(frame, model)
    display_pediction_image(frame, y_pred_thresh)
    video_shower.frame = frame
    cps.increment()
"""