import numpy as np
import pytest
from astropy import cosmology as cosmo

from autolens import exc
from autolens.data.array import grids
from autolens.data.array import mask as msk
from autolens.model.inversion import pixelizations, regularization
from autolens.model.galaxy import galaxy as g
from autolens.model.galaxy.util import galaxy_util
from autolens.lens.util import lens_util
from autolens.lens import plane as pl
from autolens.model.profiles import light_profiles as lp, mass_profiles as mp
from test.mock.mock_inversion import MockRegularization, MockPixelization
from test.mock.mock_imaging import MockBorders

@pytest.fixture(name="grid_stack")
def make_grid_stack():
    mask = msk.Mask(np.array([[True, True, True, True],
                             [True, False, False, True],
                             [True, True, True, True]]), pixel_scale=6.0)

    grid_stack = grids.GridStack.grid_stack_from_mask_sub_grid_size_and_psf_shape(mask=mask, sub_grid_size=2,
                                                                                psf_shape=(3, 3))

    # Manually overwrite a set of cooridnates to make tests of grid_stacks and defledctions straightforward

    grid_stack.regular[0] = np.array([1.0, 1.0])
    grid_stack.regular[1] = np.array([1.0, 0.0])
    grid_stack.sub[0] = np.array([1.0, 1.0])
    grid_stack.sub[1] = np.array([1.0, 0.0])
    grid_stack.sub[2] = np.array([1.0, 1.0])
    grid_stack.sub[3] = np.array([1.0, 0.0])
    grid_stack.sub[4] = np.array([-1.0, 2.0])
    grid_stack.sub[5] = np.array([-1.0, 4.0])
    grid_stack.sub[6] = np.array([1.0, 2.0])
    grid_stack.sub[7] = np.array([1.0, 4.0])
    grid_stack.blurring[0] = np.array([1.0, 0.0])
    grid_stack.blurring[1] = np.array([-6.0, -3.0])
    grid_stack.blurring[2] = np.array([-6.0, 3.0])
    grid_stack.blurring[3] = np.array([-6.0, 9.0])
    grid_stack.blurring[4] = np.array([0.0, -9.0])
    grid_stack.blurring[5] = np.array([0.0, 9.0])
    grid_stack.blurring[6] = np.array([6.0, -9.0])
    grid_stack.blurring[7] = np.array([6.0, -3.0])
    grid_stack.blurring[8] = np.array([6.0, 3.0])
    grid_stack.blurring[9] = np.array([6.0, 9.0])

    return grid_stack

@pytest.fixture(name="padded_grid_stack")
def make_padded_grid_stack():
    mask = msk.Mask(np.array([[True, False]]), pixel_scale=3.0)
    return grids.GridStack.padded_grid_stack_from_mask_sub_grid_size_and_psf_shape(mask, 2, (3, 3))

@pytest.fixture(name='galaxy_non', scope='function')
def make_galaxy_non():
    return g.Galaxy()

@pytest.fixture(name="galaxy_light")
def make_galaxy_light():
    return g.Galaxy(light_profile=lp.EllipticalSersic(centre=(0.1, 0.1), axis_ratio=1.0, phi=0.0, intensity=1.0,
                                                      effective_radius=0.6, sersic_index=4.0))

@pytest.fixture(name="galaxy_mass")
def make_galaxy_mass():
    return g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))

@pytest.fixture(name='galaxy_mass_x2')
def make_galaxy_mass_x2():
    return g.Galaxy(sis_0=mp.SphericalIsothermal(einstein_radius=1.0),
                    sis_1=mp.SphericalIsothermal(einstein_radius=1.0))

class TestAbstractPlane(object):

    class TestRedshift:

        def test__galaxy_redshifts_gives_list_of_redshifts(self):

            g0 = g.Galaxy(redshift=1.0)
            g1 = g.Galaxy(redshift=1.0)
            g2 = g.Galaxy(redshift=1.0)

            plane = pl.AbstractPlane(galaxies=[g0, g1, g2], cosmology=cosmo.Planck15)

            assert plane.galaxy_redshifts == [1.0, 1.0, 1.0]

        def test__galaxy_has_no_redshift__cosmology_input__raises_exception(self):

            g0 = g.Galaxy()
            g1 = g.Galaxy(redshift=1.0)

            with pytest.raises(exc.RayTracingException):
                pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.LambdaCDM)

        def test__galaxies_entered_all_have_no_redshifts__no_exception_raised(self):

            g0 = g.Galaxy()
            g1 = g.Galaxy()

            pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)

        def test__galaxies_entered_all_have_same_redshifts__no_exception_raised(self):

            g0 = g.Galaxy(redshift=0.1)
            g1 = g.Galaxy(redshift=0.1)

            pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)

        def test__1_galaxy_has_redshift_other_does_not__exception_is_raised(self):

            g0 = g.Galaxy(redshift=0.1)
            g1 = g.Galaxy()

            with pytest.raises(exc.RayTracingException):
                pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)

        def test__galaxies_have_different_redshifts__exception_is_raised(self):

            g0 = g.Galaxy(redshift=0.1)
            g1 = g.Galaxy(redshift=1.0)

            with pytest.raises(exc.RayTracingException):
                pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)

        def test__galaxy_has_redshift__returns_redshift(self):

            g0 = g.Galaxy(redshift=0.1)

            plane = pl.AbstractPlane(galaxies=[g0], cosmology=cosmo.Planck15)

            assert plane.redshift == 0.1

        def test__galaxy_has_no_redshift__returns_none(self):

            g0 = g.Galaxy()

            plane = pl.AbstractPlane(galaxies=[g0], cosmology=cosmo.Planck15)

            assert plane.redshift == None

    class TestCosmology:

        def test__arcsec_to_kpc_coversion_and_anguar_diameter_distance_to_earth(self):

            g0 = g.Galaxy(redshift=0.1)
            plane = pl.AbstractPlane(galaxies=[g0], cosmology=cosmo.Planck15)
            assert plane.arcsec_per_kpc_proper == pytest.approx(0.525060, 1e-5)
            assert plane.kpc_per_arcsec_proper == pytest.approx(1.904544, 1e-5)
            assert plane.angular_diameter_distance_to_earth == pytest.approx(392840, 1e-5)

            g0 = g.Galaxy(redshift=1.0)
            plane = pl.AbstractPlane(galaxies=[g0], cosmology=cosmo.Planck15)
            assert plane.arcsec_per_kpc_proper == pytest.approx(0.1214785, 1e-5)
            assert plane.kpc_per_arcsec_proper == pytest.approx(8.231907, 1e-5)
            assert plane.angular_diameter_distance_to_earth == pytest.approx(1697952, 1e-5)

    class TestProperties:

        def test__no_galaxies__raises_exception(self):

            with pytest.raises(exc.RayTracingException):
                pl.AbstractPlane(galaxies=[], cosmology=cosmo.Planck15)

        def test__has_light_profile(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_light_profile == False

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(light_profile=lp.LightProfile())], cosmology=cosmo.Planck15)
            assert plane.has_light_profile == True

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(light_profile=lp.LightProfile()), g.Galaxy()],
                                     cosmology=cosmo.Planck15)
            assert plane.has_light_profile == True

        def test__has_mass_profile(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_mass_profile == False

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(light_profile=mp.MassProfile())], cosmology=cosmo.Planck15)
            assert plane.has_mass_profile == True

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(light_profile=mp.MassProfile()), g.Galaxy()],
                                     cosmology=cosmo.Planck15)
            assert plane.has_mass_profile == True

        def test__has_pixelization(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_pixelization == False

            galaxy_pix = g.Galaxy(pixelization=pixelizations.Pixelization(),
                                  regularization=regularization.Regularization())

            plane = pl.AbstractPlane(galaxies=[galaxy_pix], cosmology=cosmo.Planck15)
            assert plane.has_pixelization == True

            plane = pl.AbstractPlane(galaxies=[galaxy_pix, g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_pixelization == True

        def test__has_regularization(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_regularization == False

            galaxy_pix = g.Galaxy(pixelization=pixelizations.Pixelization(),
                                  regularization=regularization.Regularization())

            plane = pl.AbstractPlane(galaxies=[galaxy_pix], cosmology=cosmo.Planck15)
            assert plane.has_regularization == True

            plane = pl.AbstractPlane(galaxies=[galaxy_pix, g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_regularization == True

        def test__has_hyper_galaxy(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.has_hyper_galaxy == False

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(hyper_galaxy=g.HyperGalaxy())], cosmology=cosmo.Planck15)
            assert plane.has_hyper_galaxy == True

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(hyper_galaxy=g.HyperGalaxy()), g.Galaxy()],
                                     cosmology=cosmo.Planck15)
            assert plane.has_hyper_galaxy == True

        def test__extract_hyper_galaxies(self):

            plane = pl.AbstractPlane(galaxies=[g.Galaxy()], cosmology=cosmo.Planck15)
            assert plane.hyper_galaxies == [None]

            hyper_galaxy = g.HyperGalaxy()
            plane = pl.AbstractPlane(galaxies=[g.Galaxy(hyper_galaxy=hyper_galaxy)], cosmology=cosmo.Planck15)
            assert plane.hyper_galaxies == [hyper_galaxy]

            plane = pl.AbstractPlane(galaxies=[g.Galaxy(), g.Galaxy(hyper_galaxy=hyper_galaxy), g.Galaxy()],
                                     cosmology=cosmo.Planck15)
            assert plane.hyper_galaxies == [None, hyper_galaxy, None]

    class TestLuminosities:

        def test__within_circle__no_conversion_factor__same_as_galaxy_dimensionless_luminosities(self):

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=1.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=2.0))

            g0_luminosity = g0.luminosity_within_circle(radius=1.0)
            g1_luminosity = g1.luminosity_within_circle(radius=1.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_circles(radius=1.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=3.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=4.0))

            g0_luminosity = g0.luminosity_within_circle(radius=2.0)
            g1_luminosity = g1.luminosity_within_circle(radius=2.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_circles(radius=2.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

        def test__luminosity_within_circle__same_as_galaxy_luminosities(self):

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=1.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=2.0))

            g0_luminosity = g0.luminosity_within_circle(radius=1.0, conversion_factor=3.0)
            g1_luminosity = g1.luminosity_within_circle(radius=1.0, conversion_factor=3.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_circles(radius=1.0, conversion_factor=3.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=3.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=4.0))

            g0_luminosity = g0.luminosity_within_circle(radius=2.0, conversion_factor=6.0)
            g1_luminosity = g1.luminosity_within_circle(radius=2.0, conversion_factor=6.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_circles(radius=2.0, conversion_factor=6.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

        def test__within_ellipse__no_conversion_factor__same_as_galaxy_dimensionless_luminosities(self):

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=1.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=2.0))

            g0_luminosity = g0.luminosity_within_ellipse(major_axis=0.8)
            g1_luminosity = g1.luminosity_within_ellipse(major_axis=0.8)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_ellipses(major_axis=0.8)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=3.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=4.0))

            g0_luminosity = g0.luminosity_within_ellipse(major_axis=0.6)
            g1_luminosity = g1.luminosity_within_ellipse(major_axis=0.6)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_ellipses(major_axis=0.6)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

        def test__luminosity_within_ellipse__same_as_galaxy_luminosities(self):

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=1.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=2.0))

            g0_luminosity = g0.luminosity_within_ellipse(major_axis=0.8, conversion_factor=3.0)
            g1_luminosity = g1.luminosity_within_ellipse(major_axis=0.8, conversion_factor=3.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_ellipses(major_axis=0.8, conversion_factor=3.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

            g0 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=3.0))
            g1 = g.Galaxy(luminosity=lp.SphericalSersic(intensity=4.0))

            g0_luminosity = g0.luminosity_within_ellipse(major_axis=0.6, conversion_factor=6.0)
            g1_luminosity = g1.luminosity_within_ellipse(major_axis=0.6, conversion_factor=6.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_luminosities = plane.luminosities_of_galaxies_within_ellipses(major_axis=0.6, conversion_factor=6.0)

            assert plane_luminosities[0] == g0_luminosity
            assert plane_luminosities[1] == g1_luminosity

    class TestMasses:

        def test__within_circle__no_conversion_factor__same_as_galaxy_dimensionless_masses(self):

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_mass = g0.mass_within_circle(radius=1.0)
            g1_mass = g1.mass_within_circle(radius=1.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_circles(radius=1.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=3.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=4.0))

            g0_mass = g0.mass_within_circle(radius=2.0)
            g1_mass = g1.mass_within_circle(radius=2.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_circles(radius=2.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

        def test__mass_within_circle__same_as_galaxy_masses(self):
            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_mass = g0.mass_within_circle(radius=1.0, conversion_factor=3.0)
            g1_mass = g1.mass_within_circle(radius=1.0, conversion_factor=3.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_circles(radius=1.0, conversion_factor=3.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=3.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=4.0))

            g0_mass = g0.mass_within_circle(radius=2.0, conversion_factor=6.0)
            g1_mass = g1.mass_within_circle(radius=2.0, conversion_factor=6.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_circles(radius=2.0, conversion_factor=6.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

        def test__within_ellipse__no_conversion_factor__same_as_galaxy_dimensionless_masses(self):

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_mass = g0.mass_within_ellipse(major_axis=0.8)
            g1_mass = g1.mass_within_ellipse(major_axis=0.8)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_ellipses(major_axis=0.8)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=3.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=4.0))

            g0_mass = g0.mass_within_ellipse(major_axis=0.6)
            g1_mass = g1.mass_within_ellipse(major_axis=0.6)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_ellipses(major_axis=0.6)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

        def test__mass_within_ellipse__same_as_galaxy_masses(self):

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_mass = g0.mass_within_ellipse(major_axis=0.8, conversion_factor=3.0)
            g1_mass = g1.mass_within_ellipse(major_axis=0.8, conversion_factor=3.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_ellipses(major_axis=0.8, conversion_factor=3.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

            g0 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=3.0))
            g1 = g.Galaxy(mass=mp.SphericalIsothermal(einstein_radius=4.0))

            g0_mass = g0.mass_within_ellipse(major_axis=0.6, conversion_factor=6.0)
            g1_mass = g1.mass_within_ellipse(major_axis=0.6, conversion_factor=6.0)
            plane = pl.AbstractPlane(galaxies=[g0, g1], cosmology=cosmo.Planck15)
            plane_masses = plane.masses_of_galaxies_within_ellipses(major_axis=0.6, conversion_factor=6.0)

            assert plane_masses[0] == g0_mass
            assert plane_masses[1] == g1_mass

class TestPlane(object):

    class TestGridStackSetup:

        def test__grid_stack_setup_for_regular_sub_and_blurring__no_deflections(self, grid_stack,
                                                                                galaxy_mass):

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack, compute_deflections=False)

            assert plane.grid_stack.regular == pytest.approx(np.array([[1.0, 1.0], [1.0, 0.0]]), 1e-3)
            assert plane.grid_stack.sub == pytest.approx(np.array([[1.0, 1.0], [1.0, 0.0], [1.0, 1.0], [1.0, 0.0],
                                                                      [-1.0, 2.0], [-1.0, 4.0], [1.0, 2.0], [1.0, 4.0]]), 1e-3)
            assert plane.grid_stack.blurring == pytest.approx(np.array([[1.0, 0.0], [-6.0, -3.0], [-6.0, 3.0], [-6.0, 9.0],
                                                                           [0.0, -9.0], [0.0, 9.0],
                                                                           [6.0, -9.0], [6.0, -3.0], [6.0, 3.0], [6.0, 9.0]]), 1e-3)

            assert plane.deflection_stack == None

        def test__same_as_above_but_test_deflections(self, grid_stack, galaxy_mass):

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack, compute_deflections=True)

            sub_galaxy_deflections = galaxy_mass.deflections_from_grid(grid_stack.sub)
            blurring_galaxy_deflections = galaxy_mass.deflections_from_grid(grid_stack.blurring)

            assert plane.deflection_stack.regular == pytest.approx(np.array([[0.707, 0.707], [1.0, 0.0]]), 1e-3)
            assert (plane.deflection_stack.sub == sub_galaxy_deflections).all()
            assert (plane.deflection_stack.blurring == blurring_galaxy_deflections).all()

        def test__same_as_above__x2_galaxy_in_plane__or_galaxy_x2_sis__deflections_double(self, grid_stack,
                                                                                galaxy_mass,
                                                                                galaxy_mass_x2):

            plane = pl.Plane(galaxies=[galaxy_mass_x2], grid_stack=grid_stack, compute_deflections=True)

            sub_galaxy_deflections = galaxy_mass_x2.deflections_from_grid(grid_stack.sub)
            blurring_galaxy_deflections = galaxy_mass_x2.deflections_from_grid(grid_stack.blurring)

            assert plane.deflection_stack.regular == pytest.approx(np.array([[2.0 * 0.707, 2.0 * 0.707], [2.0, 0.0]]), 1e-3)
            assert (plane.deflection_stack.sub == sub_galaxy_deflections).all()
            assert (plane.deflection_stack.blurring == blurring_galaxy_deflections).all()

            plane = pl.Plane(galaxies=[galaxy_mass, galaxy_mass], grid_stack=grid_stack, compute_deflections=True)

            sub_galaxy_deflections = galaxy_mass.deflections_from_grid(grid_stack.sub)
            blurring_galaxy_deflections = galaxy_mass.deflections_from_grid(grid_stack.blurring)

            assert plane.deflection_stack.regular == pytest.approx(np.array([[2.0 * 0.707, 2.0 * 0.707], [2.0, 0.0]]), 1e-3)
            assert (plane.deflection_stack.sub == 2.0 * sub_galaxy_deflections).all()
            assert (plane.deflection_stack.blurring == 2.0 * blurring_galaxy_deflections).all()

    class TestProperties:

        def test__padded_grid_in__tracer_has_padded_grid_property(self, grid_stack, padded_grid_stack, galaxy_light):

            plane = pl.Plane(grid_stack=grid_stack, galaxies=[galaxy_light])
            assert plane.has_padded_grid_stack == False

            plane = pl.Plane(grid_stack=padded_grid_stack, galaxies=[galaxy_light])
            assert plane.has_padded_grid_stack == True

    class TestImage:

        def test__image_from_plane__same_as_its_light_profile_image(self, grid_stack, galaxy_light):

            lp = galaxy_light.light_profiles[0]

            lp_sub_image = lp.intensities_from_grid(grid_stack.sub)

            # Perform sub gridding average manually
            lp_image_pixel_0 = (lp_sub_image[0] + lp_sub_image[1] + lp_sub_image[2] + lp_sub_image[3]) / 4
            lp_image_pixel_1 = (lp_sub_image[4] + lp_sub_image[5] + lp_sub_image[6] + lp_sub_image[7]) / 4

            plane = pl.Plane(galaxies=[galaxy_light], grid_stack=grid_stack)

            assert (plane.image_plane_image_1d[0] == lp_image_pixel_0).all()
            assert (plane.image_plane_image_1d[1] == lp_image_pixel_1).all()
            assert (plane.image_plane_image ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.image_plane_image_1d)).all()

        def test__image_from_plane__same_as_its_galaxy_image(self, grid_stack, galaxy_light):

            galaxy_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.sub, galaxies=[galaxy_light])

            plane = pl.Plane(galaxies=[galaxy_light], grid_stack=grid_stack)

            assert plane.image_plane_image_1d == pytest.approx(galaxy_image, 1.0e-4)

            image_plane_image = grid_stack.regular.scaled_array_from_array_1d(plane.image_plane_image_1d)

            assert plane.image_plane_image == pytest.approx(image_plane_image, 1.0e-4)

        def test__image_plane_image_of_galaxies(self, grid_stack):

            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=1.0))
            g1 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=2.0))

            lp0 = g0.light_profiles[0]
            lp1 = g1.light_profiles[0]

            lp0_sub_image = lp0.intensities_from_grid(grid_stack.sub)
            lp1_sub_image = lp1.intensities_from_grid(grid_stack.sub)

            # Perform sub gridding average manually
            lp0_image_pixel_0 = (lp0_sub_image[0] + lp0_sub_image[1] + lp0_sub_image[2] + lp0_sub_image[3]) / 4
            lp0_image_pixel_1 = (lp0_sub_image[4] + lp0_sub_image[5] + lp0_sub_image[6] + lp0_sub_image[7]) / 4
            lp1_image_pixel_0 = (lp1_sub_image[0] + lp1_sub_image[1] + lp1_sub_image[2] + lp1_sub_image[3]) / 4
            lp1_image_pixel_1 = (lp1_sub_image[4] + lp1_sub_image[5] + lp1_sub_image[6] + lp1_sub_image[7]) / 4

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.image_plane_image_1d[0] == pytest.approx(lp0_image_pixel_0 + lp1_image_pixel_0, 1.0e-4)
            assert plane.image_plane_image_1d[1] == pytest.approx(lp0_image_pixel_1 + lp1_image_pixel_1, 1.0e-4)

            image_plane_image = grid_stack.regular.scaled_array_from_array_1d(plane.image_plane_image_1d)

            assert plane.image_plane_image == image_plane_image

            assert plane.image_plane_image_1d_of_galaxies[0][0] == lp0_image_pixel_0
            assert plane.image_plane_image_1d_of_galaxies[0][1] == lp0_image_pixel_1
            assert plane.image_plane_image_1d_of_galaxies[1][0] == lp1_image_pixel_0
            assert plane.image_plane_image_1d_of_galaxies[1][1] == lp1_image_pixel_1

        def test__same_as_above__use_multiple_galaxies(self, grid_stack):

            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=1.0))
            g1 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=2.0))

            g0_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.sub, galaxies=[g0])
            g1_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.sub, galaxies=[g1])

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.image_plane_image_1d == pytest.approx(g0_image + g1_image, 1.0e-4)
            assert (plane.image_plane_image ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.image_plane_image_1d)).all()

            assert (plane.image_plane_image_1d_of_galaxies[0] == g0_image).all()
            assert (plane.image_plane_image_1d_of_galaxies[1] == g1_image).all()

        def test__padded_grid_stack_in__image_plane_image_is_padded(self, padded_grid_stack, galaxy_light):

            lp = galaxy_light.light_profiles[0]

            lp_sub_image = lp.intensities_from_grid(padded_grid_stack.sub)

            # Perform sub gridding average manually
            lp_image_pixel_0 = (lp_sub_image[0] + lp_sub_image[1] + lp_sub_image[2] + lp_sub_image[3]) / 4
            lp_image_pixel_1 = (lp_sub_image[4] + lp_sub_image[5] + lp_sub_image[6] + lp_sub_image[7]) / 4
            lp_image_pixel_2 = (lp_sub_image[8] + lp_sub_image[9] + lp_sub_image[10] + lp_sub_image[11]) / 4
            lp_image_pixel_3 = (lp_sub_image[12] + lp_sub_image[13] + lp_sub_image[14] + lp_sub_image[15]) / 4
            lp_image_pixel_4 = (lp_sub_image[16] + lp_sub_image[17] + lp_sub_image[18] + lp_sub_image[19]) / 4
            lp_image_pixel_5 = (lp_sub_image[20] + lp_sub_image[21] + lp_sub_image[22] + lp_sub_image[23]) / 4
            lp_image_pixel_6 = (lp_sub_image[24] + lp_sub_image[25] + lp_sub_image[26] + lp_sub_image[27]) / 4
            lp_image_pixel_7 = (lp_sub_image[28] + lp_sub_image[29] + lp_sub_image[30] + lp_sub_image[31]) / 4
            lp_image_pixel_8 = (lp_sub_image[32] + lp_sub_image[33] + lp_sub_image[34] + lp_sub_image[35]) / 4
            lp_image_pixel_9 = (lp_sub_image[36] + lp_sub_image[37] + lp_sub_image[38] + lp_sub_image[39]) / 4
            lp_image_pixel_10 = (lp_sub_image[40] + lp_sub_image[41] + lp_sub_image[42] + lp_sub_image[43]) / 4
            lp_image_pixel_11 = (lp_sub_image[44] + lp_sub_image[45] + lp_sub_image[46] + lp_sub_image[47]) / 4

            plane = pl.Plane(galaxies=[galaxy_light], grid_stack=padded_grid_stack)

            assert plane.image_plane_image_for_simulation.shape == (3, 4)
            assert (plane.image_plane_image_for_simulation[0, 0] == lp_image_pixel_0).all()
            assert (plane.image_plane_image_for_simulation[0, 1] == lp_image_pixel_1).all()
            assert (plane.image_plane_image_for_simulation[0, 2] == lp_image_pixel_2).all()
            assert (plane.image_plane_image_for_simulation[0, 3] == lp_image_pixel_3).all()
            assert (plane.image_plane_image_for_simulation[1, 0] == lp_image_pixel_4).all()
            assert (plane.image_plane_image_for_simulation[1, 1] == lp_image_pixel_5).all()
            assert (plane.image_plane_image_for_simulation[1, 2] == lp_image_pixel_6).all()
            assert (plane.image_plane_image_for_simulation[1, 3] == lp_image_pixel_7).all()
            assert (plane.image_plane_image_for_simulation[2, 0] == lp_image_pixel_8).all()
            assert (plane.image_plane_image_for_simulation[2, 1] == lp_image_pixel_9).all()
            assert (plane.image_plane_image_for_simulation[2, 2] == lp_image_pixel_10).all()
            assert (plane.image_plane_image_for_simulation[2, 3] == lp_image_pixel_11).all()

    class TestBlurringImage:

        def test__image_from_plane__same_as_its_light_profile_image(self, grid_stack, galaxy_light):

            lp = galaxy_light.light_profiles[0]

            lp_blurring_image = lp.intensities_from_grid(grid_stack.blurring)

            plane = pl.Plane(galaxies=[galaxy_light], grid_stack=grid_stack)

            assert (plane.image_plane_blurring_image_1d == lp_blurring_image).all()

        def test__same_as_above__use_multiple_galaxies(self, grid_stack):
            # Overwrite one value so intensity in each pixel is different
            grid_stack.blurring[1] = np.array([2.0, 2.0])

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=1.0))
            g1 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=2.0))

            lp0 = g0.light_profiles[0]
            lp1 = g1.light_profiles[0]

            lp0_blurring_image = lp0.intensities_from_grid(grid_stack.blurring)
            lp1_blurring_image = lp1.intensities_from_grid(grid_stack.blurring)

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert (plane.image_plane_blurring_image_1d == lp0_blurring_image + lp1_blurring_image).all()

        def test__image_from_plane__same_as_its_galaxy_image(self, grid_stack, galaxy_light):

            galaxy_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.blurring, galaxies=[galaxy_light])

            plane = pl.Plane(galaxies=[galaxy_light], grid_stack=grid_stack)

            assert (plane.image_plane_blurring_image_1d == galaxy_image).all()

        def test__same_as_above_galaxies___use_multiple_galaxies(self, grid_stack):

            # Overwrite one value so intensity in each pixel is different
            grid_stack.blurring[1] = np.array([2.0, 2.0])

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=1.0))
            g1 = g.Galaxy(light_profile=lp.EllipticalSersic(intensity=2.0))

            g0_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.blurring, galaxies=[g0])
            g1_image = galaxy_util.intensities_of_galaxies_from_grid(grid_stack.blurring, galaxies=[g1])

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert (plane.image_plane_blurring_image_1d == g0_image + g1_image).all()

    class TestSurfaceDensity:

        def test__surface_density_from_plane__same_as_its_mass_profile(self, grid_stack, galaxy_mass):

            mp = galaxy_mass.mass_profiles[0]

            mp_sub_surface_density = mp.surface_density_from_grid(grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp_surface_density_pixel_0 = (mp_sub_surface_density[0] + mp_sub_surface_density[1] +
                                          mp_sub_surface_density[2] + mp_sub_surface_density[3]) / 4
            mp_surface_density_pixel_1 = (mp_sub_surface_density[4] + mp_sub_surface_density[5] +
                                          mp_sub_surface_density[6] + mp_sub_surface_density[7]) / 4

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.surface_density[1,1] == mp_surface_density_pixel_0).all()
            assert (plane.surface_density[1,2] == mp_surface_density_pixel_1).all()

        def test__same_as_above__use_multiple_galaxies(self, grid_stack):

            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0, centre=(1.0, 0.0)))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0, centre=(1.0, 1.0)))

            mp0 = g0.mass_profiles[0]
            mp1 = g1.mass_profiles[0]

            mp0_sub_surface_density = mp0.surface_density_from_grid(grid=grid_stack.sub.unlensed_grid)
            mp1_sub_surface_density = mp1.surface_density_from_grid(grid=grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp0_surface_density_pixel_0 = (mp0_sub_surface_density[0] + mp0_sub_surface_density[1] +
                                           mp0_sub_surface_density[2] + mp0_sub_surface_density[3]) / 4
            mp0_surface_density_pixel_1 = (mp0_sub_surface_density[4] + mp0_sub_surface_density[5] +
                                           mp0_sub_surface_density[6] + mp0_sub_surface_density[7]) / 4
            mp1_surface_density_pixel_0 = (mp1_sub_surface_density[0] + mp1_sub_surface_density[1] +
                                           mp1_sub_surface_density[2] + mp1_sub_surface_density[3]) / 4
            mp1_surface_density_pixel_1 = (mp1_sub_surface_density[4] + mp1_sub_surface_density[5] +
                                           mp1_sub_surface_density[6] + mp1_sub_surface_density[7]) / 4

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.surface_density[1,1] == pytest.approx(mp0_surface_density_pixel_0 +
                                                               mp1_surface_density_pixel_0, 1.0e-4)
            assert plane.surface_density[1,2] == pytest.approx(mp0_surface_density_pixel_1 +
                                                               mp1_surface_density_pixel_1, 1.0e-4)

        def test__surface_density__same_as_its_galaxy(self, grid_stack, galaxy_mass):

            galaxy_surface_density = galaxy_util.surface_density_of_galaxies_from_grid(grid_stack.sub.unlensed_grid,
                                                                  galaxies=[galaxy_mass])

            galaxy_surface_density = grid_stack.regular.scaled_array_from_array_1d(galaxy_surface_density)

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.surface_density == galaxy_surface_density).all()

        def test__same_as_above_galaxies___use_multiple_galaxies(self, grid_stack):

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_surface_density = galaxy_util.surface_density_of_galaxies_from_grid(grid_stack.sub.unlensed_grid,
                                                                                galaxies=[g0])
            g1_surface_density = galaxy_util.surface_density_of_galaxies_from_grid(grid_stack.sub.unlensed_grid,
                                                                                galaxies=[g1])

            g0_surface_density = grid_stack.regular.scaled_array_from_array_1d(g0_surface_density)
            g1_surface_density = grid_stack.regular.scaled_array_from_array_1d(g1_surface_density)

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert (plane.surface_density == g0_surface_density + g1_surface_density).all()

        def test__surface_density_from_plane__same_as_its_mass_profile__use_padded_grid_stack(self, padded_grid_stack,
                                                                                galaxy_mass):

            mp = galaxy_mass.mass_profiles[0]

            mp_sub_surface_density = mp.surface_density_from_grid(padded_grid_stack.sub.unlensed_grid)

            # The padded sub-grid adds 5 pixels arounds the mask from the top-left which we skip over, thus our
            # first sub-pixel index is 20.
            mp_surface_density_pixel_0 = (mp_sub_surface_density[20] + mp_sub_surface_density[21] +
                                          mp_sub_surface_density[22] + mp_sub_surface_density[23]) / 4
            mp_surface_density_pixel_1 = (mp_sub_surface_density[24] + mp_sub_surface_density[25] +
                                          mp_sub_surface_density[26] + mp_sub_surface_density[27]) / 4

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=padded_grid_stack)

            # The padded array is trimed to the same size as the original mask (1x2).
            assert plane.surface_density[0,0] == pytest.approx(mp_surface_density_pixel_0, 1.0e-4)
            assert plane.surface_density[0,1] == pytest.approx(mp_surface_density_pixel_1, 1.0e-4)

    class TestPotential:

        def test__potential_from_plane__same_as_its_mass_profile(self, grid_stack, galaxy_mass):
            mp = galaxy_mass.mass_profiles[0]

            mp_sub_potential = mp.potential_from_grid(grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp_potential_pixel_0 = (mp_sub_potential[0] + mp_sub_potential[1] + mp_sub_potential[2] + mp_sub_potential
                [3]) / 4
            mp_potential_pixel_1 = (mp_sub_potential[4] + mp_sub_potential[5] + mp_sub_potential[6] + mp_sub_potential
                [7]) / 4

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.potential[1,1] == mp_potential_pixel_0).all()
            assert (plane.potential[1,2] == mp_potential_pixel_1).all()

        def test__same_as_above__use_multiple_galaxies(self, grid_stack):

            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub.unlensed_grid[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0))

            mp0 = g0.mass_profiles[0]
            mp1 = g1.mass_profiles[0]

            mp0_sub_potential = mp0.potential_from_grid(grid_stack.sub.unlensed_grid)
            mp1_sub_potential = mp1.potential_from_grid(grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp0_potential_pixel_0 = (mp0_sub_potential[0] + mp0_sub_potential[1] +
                                     mp0_sub_potential[2] + mp0_sub_potential[3]) / 4
            mp0_potential_pixel_1 = (mp0_sub_potential[4] + mp0_sub_potential[5] +
                                     mp0_sub_potential[6] + mp0_sub_potential[7]) / 4
            mp1_potential_pixel_0 = (mp1_sub_potential[0] + mp1_sub_potential[1] +
                                     mp1_sub_potential[2] + mp1_sub_potential[3]) / 4
            mp1_potential_pixel_1 = (mp1_sub_potential[4] + mp1_sub_potential[5] +
                                     mp1_sub_potential[6] + mp1_sub_potential[7]) / 4

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.potential[1,1] == pytest.approx(mp0_potential_pixel_0 +
                                                         mp1_potential_pixel_0, 1.0e-4)
            assert plane.potential[1,2] == pytest.approx(mp0_potential_pixel_1 +
                                                         mp1_potential_pixel_1, 1.0e-4)

        def test__potential__same_as_its_galaxy(self, grid_stack, galaxy_mass):
            galaxy_potential = galaxy_util.potential_of_galaxies_from_grid(grid_stack.sub.unlensed_grid, galaxies=[galaxy_mass])

            galaxy_potential = grid_stack.regular.scaled_array_from_array_1d(galaxy_potential)

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.potential == galaxy_potential).all()

        def test__same_as_above_galaxies___use_multiple_galaxies(self, grid_stack):
            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub.unlensed_grid[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_potential = galaxy_util.potential_of_galaxies_from_grid(grid_stack.sub.unlensed_grid, galaxies=[g0])
            g1_potential = galaxy_util.potential_of_galaxies_from_grid(grid_stack.sub.unlensed_grid, galaxies=[g1])

            g0_potential = grid_stack.regular.scaled_array_from_array_1d(g0_potential)
            g1_potential = grid_stack.regular.scaled_array_from_array_1d(g1_potential)

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.potential == pytest.approx(g0_potential + g1_potential, 1.0e-4)

        def test__potential_from_plane__same_as_its_mass_profile__use_padded_grid_stack(self, padded_grid_stack,
                                                                                galaxy_mass):

            mp = galaxy_mass.mass_profiles[0]

            mp_sub_image = mp.potential_from_grid(padded_grid_stack.sub.unlensed_grid)

            # The padded sub-grid adds 5 pixels arounds the mask from the top-left which we skip over, thus our
            # first sub-pixel index is 20.
            mp_image_pixel_0 = (mp_sub_image[20] + mp_sub_image[21] + mp_sub_image[22] + mp_sub_image[23]) / 4
            mp_image_pixel_1 = (mp_sub_image[24] + mp_sub_image[25] + mp_sub_image[26] + mp_sub_image[27]) / 4

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=padded_grid_stack)

            # The padded array is trimed to the same size as the original mask (1x2).
            assert plane.potential[0,0] == pytest.approx(mp_image_pixel_0, 1.0e-4)
            assert plane.potential[0,1] == pytest.approx(mp_image_pixel_1, 1.0e-4)

    class TestDeflections:

        def test__deflections_from_plane__same_as_its_mass_profile(self, grid_stack, galaxy_mass):

            mp = galaxy_mass.mass_profiles[0]

            mp_sub_image = mp.deflections_from_grid(grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp_image_pixel_0x = (mp_sub_image[0 ,0] + mp_sub_image[1 ,0] + mp_sub_image[2 ,0] + mp_sub_image[3 ,0]) / 4
            mp_image_pixel_1x = (mp_sub_image[4 ,0] + mp_sub_image[5 ,0] + mp_sub_image[6 ,0] + mp_sub_image[7 ,0]) / 4
            mp_image_pixel_0y = (mp_sub_image[0 ,1] + mp_sub_image[1 ,1] + mp_sub_image[2 ,1] + mp_sub_image[3 ,1]) / 4
            mp_image_pixel_1y = (mp_sub_image[4 ,1] + mp_sub_image[5 ,1] + mp_sub_image[6 ,1] + mp_sub_image[7 ,1]) / 4

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.deflections_1d[0 , 0] == mp_image_pixel_0x).all()
            assert (plane.deflections_1d[0 , 1] == mp_image_pixel_0y).all()
            assert (plane.deflections_1d[1 , 0] == mp_image_pixel_1x).all()
            assert (plane.deflections_1d[1 , 1] == mp_image_pixel_1y).all()
            assert (plane.deflections_y ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 0])).all()
            assert (plane.deflections_x ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 1])).all()

        def test__same_as_above__use_multiple_galaxies(self, grid_stack):
            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub.unlensed_grid[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0))

            mp0 = g0.mass_profiles[0]
            mp1 = g1.mass_profiles[0]

            mp0_sub_image = mp0.deflections_from_grid(grid_stack.sub.unlensed_grid)
            mp1_sub_image = mp1.deflections_from_grid(grid_stack.sub.unlensed_grid)

            # Perform sub gridding average manually
            mp0_image_pixel_0x = (mp0_sub_image[0 ,0] + mp0_sub_image[1 ,0] +
                                  mp0_sub_image[2 ,0] + mp0_sub_image[3 ,0]) / 4
            mp0_image_pixel_1x = (mp0_sub_image[4 ,0] + mp0_sub_image[5 ,0] +
                                  mp0_sub_image[6 ,0] + mp0_sub_image[7 ,0]) / 4
            mp0_image_pixel_0y = (mp0_sub_image[0 ,1] + mp0_sub_image[1 ,1] +
                                  mp0_sub_image[2 ,1] + mp0_sub_image[3 ,1]) / 4
            mp0_image_pixel_1y = (mp0_sub_image[4 ,1] + mp0_sub_image[5 ,1] +
                                  mp0_sub_image[6 ,1] + mp0_sub_image[7 ,1]) / 4

            mp1_image_pixel_0x = (mp1_sub_image[0 ,0] + mp1_sub_image[1 ,0] +
                                  mp1_sub_image[2 ,0] + mp1_sub_image[3 ,0]) / 4
            mp1_image_pixel_1x = (mp1_sub_image[4 ,0] + mp1_sub_image[5 ,0] +
                                  mp1_sub_image[6 ,0] + mp1_sub_image[7 ,0]) / 4
            mp1_image_pixel_0y = (mp1_sub_image[0 ,1] + mp1_sub_image[1 ,1] +
                                  mp1_sub_image[2 ,1] + mp1_sub_image[3 ,1]) / 4
            mp1_image_pixel_1y = (mp1_sub_image[4 ,1] + mp1_sub_image[5 ,1] +
                                  mp1_sub_image[6 ,1] + mp1_sub_image[7 ,1]) / 4

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.deflections_1d[0 , 0] == pytest.approx(mp0_image_pixel_0x + mp1_image_pixel_0x, 1.0e-4)
            assert plane.deflections_1d[1 , 0] == pytest.approx(mp0_image_pixel_1x + mp1_image_pixel_1x, 1.0e-4)
            assert plane.deflections_1d[0 , 1] == pytest.approx(mp0_image_pixel_0y + mp1_image_pixel_0y, 1.0e-4)
            assert plane.deflections_1d[1 , 1] == pytest.approx(mp0_image_pixel_1y + mp1_image_pixel_1y, 1.0e-4)
            assert (plane.deflections_y ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 0])).all()
            assert (plane.deflections_x ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 1])).all()

        def test__deflections__same_as_its_galaxy(self, grid_stack, galaxy_mass):

            galaxy_deflections = galaxy_util.deflections_of_galaxies_from_grid(grid=grid_stack.sub.unlensed_grid, galaxies=[galaxy_mass])

            plane = pl.Plane(galaxies=[galaxy_mass], grid_stack=grid_stack)

            assert (plane.deflections_1d == galaxy_deflections).all()
            assert (plane.deflections_y ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 0])).all()
            assert (plane.deflections_x ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 1])).all()

        def test__same_as_above_galaxies___use_multiple_galaxies(self, grid_stack):
            # Overwrite one value so intensity in each pixel is different
            grid_stack.sub.unlensed_grid[5] = np.array([2.0, 2.0])

            g0 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=1.0))
            g1 = g.Galaxy(mass_profile=mp.SphericalIsothermal(einstein_radius=2.0))

            g0_deflections = galaxy_util.deflections_of_galaxies_from_grid(grid=grid_stack.sub.unlensed_grid, galaxies=[g0])
            g1_deflections = galaxy_util.deflections_of_galaxies_from_grid(grid=grid_stack.sub.unlensed_grid, galaxies=[g1])

            plane = pl.Plane(galaxies=[g0, g1], grid_stack=grid_stack)

            assert plane.deflections_1d == pytest.approx(g0_deflections + g1_deflections, 1.0e-4)
            assert (plane.deflections_y ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 0])).all()
            assert (plane.deflections_x ==
                    grid_stack.regular.scaled_array_from_array_1d(plane.deflections_1d[:, 1])).all()

    class TestMapper:

        def test__no_galaxies_with_pixelizations_in_plane__returns_none(self, grid_stack):
            galaxy_no_pix = g.Galaxy()

            plane = pl.Plane(galaxies=[galaxy_no_pix], grid_stack=grid_stack, border=[MockBorders()])

            assert plane.mapper is None

        def test__1_galaxy_in_plane__it_has_pixelization__returns_mapper(self, grid_stack):
            galaxy_pix = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))

            plane = pl.Plane(galaxies=[galaxy_pix], grid_stack=grid_stack, border=[MockBorders()])

            assert plane.mapper == 1

        def test__2_galaxies_in_plane__1_has_pixelization__extracts_reconstructor(self, grid_stack):
            galaxy_pix = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))
            galaxy_no_pix = g.Galaxy()

            plane = pl.Plane(galaxies=[galaxy_no_pix, galaxy_pix], grid_stack=grid_stack, border=[MockBorders()])

            assert plane.mapper == 1

        def test__plane_has_no_border__still_returns_mapper(self, grid_stack):

            galaxy_pix = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))
            galaxy_no_pix = g.Galaxy()

            plane = pl.Plane(galaxies=[galaxy_no_pix, galaxy_pix], grid_stack=grid_stack)

            assert plane.mapper == 1

        def test__2_galaxies_in_plane__both_have_pixelization__raises_error(self, grid_stack):
            galaxy_pix_0 = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))
            galaxy_pix_1 = g.Galaxy(pixelization=MockPixelization(value=2), regularization=MockRegularization(value=0))

            plane = pl.Plane(galaxies=[galaxy_pix_0, galaxy_pix_1], grid_stack=grid_stack, border=[MockBorders()])

            with pytest.raises(exc.PixelizationException):
                plane.mapper

    class TestRegularization:

        def test__no_galaxies_with_pixelizations_in_plane__returns_none(self, grid_stack):

            galaxy_no_pix = g.Galaxy()

            plane = pl.Plane(galaxies=[galaxy_no_pix], grid_stack=grid_stack, border=MockBorders())

            assert plane.regularization is None

        def test__1_galaxy_in_plane__it_has_pixelization__returns_mapper(self, grid_stack):
            galaxy_pix = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))

            plane = pl.Plane(galaxies=[galaxy_pix], grid_stack=grid_stack, border=MockBorders())

            assert plane.regularization.value == 0

        def test__2_galaxies_in_plane__1_has_pixelization__extracts_reconstructor(self, grid_stack):
            galaxy_pix = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))
            galaxy_no_pix = g.Galaxy()

            plane = pl.Plane(galaxies=[galaxy_no_pix, galaxy_pix], grid_stack=grid_stack, border=MockBorders())

            assert plane.regularization.value == 0

        def test__2_galaxies_in_plane__both_have_pixelization__raises_error(self, grid_stack):
            galaxy_pix_0 = g.Galaxy(pixelization=MockPixelization(value=1), regularization=MockRegularization(value=0))
            galaxy_pix_1 = g.Galaxy(pixelization=MockPixelization(value=2), regularization=MockRegularization(value=0))

            plane = pl.Plane(galaxies=[galaxy_pix_0, galaxy_pix_1], grid_stack=grid_stack, border=MockBorders())

            with pytest.raises(exc.PixelizationException):
                plane.regularization

    class TestPlaneImage:

        def test__3x3_grid__extracts_max_min_coordinates__ignores_other_coordinates_more_central(self, grid_stack):

            grid_stack.regular[1] = np.array([2.0, 2.0])

            galaxy = g.Galaxy(light=lp.EllipticalSersic(intensity=1.0))

            plane = pl.Plane(galaxies=[galaxy], grid_stack=grid_stack, compute_deflections=False)

            plane_image_from_func = lens_util.plane_image_of_galaxies_from_grid(shape=(3, 4),
                                                                                grid=grid_stack.regular,
                                                                                galaxies=[galaxy])

            assert (plane_image_from_func == plane.plane_image).all()

        def test__ensure_index_of_plane_image_has_negative_arcseconds_at_start(self, grid_stack):
            # The grid coordinates -2.0 -> 2.0 mean a plane of shape (5,5) has arc second coordinates running over
            # -1.6, -0.8, 0.0, 0.8, 1.6. The origin -1.6, -1.6 of the model_galaxy means its brighest pixel should be
            # index 0 of the 1D grid and (0,0) of the 2d plane datas_.

            mask = msk.Mask(array=np.full((5, 5), False), pixel_scale=1.0)

            grid_stack.regular = grids.RegularGrid(np.array([[-2.0, -2.0], [2.0, 2.0]]), mask=mask)

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(centre=(1.6, -1.6), intensity=1.0))
            plane = pl.Plane(galaxies=[g0], grid_stack=grid_stack)

            assert plane.plane_image.shape == (5, 5)
            assert np.unravel_index(plane.plane_image.argmax(), plane.plane_image.shape) == (0, 0)

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(centre=(1.6, 1.6), intensity=1.0))
            plane = pl.Plane(galaxies=[g0], grid_stack=grid_stack)
            assert np.unravel_index(plane.plane_image.argmax(), plane.plane_image.shape) == (0, 4)

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(centre=(-1.6, -1.6), intensity=1.0))
            plane = pl.Plane(galaxies=[g0], grid_stack=grid_stack)
            assert np.unravel_index(plane.plane_image.argmax(), plane.plane_image.shape) == (4, 0)

            g0 = g.Galaxy(light_profile=lp.EllipticalSersic(centre=(-1.6, 1.6), intensity=1.0))
            plane = pl.Plane(galaxies=[g0], grid_stack=grid_stack)
            assert np.unravel_index(plane.plane_image.argmax(), plane.plane_image.shape) == (4, 4)

        def test__compute_xticks_from_image_grid_correctly(self):

            plane_image = pl.PlaneImage(array=np.ones((3,3)), pixel_scales=(5.0, 1.0), grid=None)
            assert plane_image.xticks == pytest.approx(np.array([-1.5, -0.5, 0.5, 1.5]), 1e-3)

            plane_image = pl.PlaneImage(array=np.ones((3,3)), pixel_scales=(5.0, 0.5), grid=None)
            assert plane_image.xticks == pytest.approx(np.array([-0.75, -0.25, 0.25, 0.75]), 1e-3)

            plane_image = pl.PlaneImage(array=np.ones((1,6)), pixel_scales=(5.0, 1.0), grid=None)
            assert plane_image.xticks == pytest.approx(np.array([-3.0, -1.0, 1.0, 3.0]), 1e-2)

        def test__compute_yticks_from_image_grid_correctly(self):

            plane_image = pl.PlaneImage(array=np.ones((3,3)), pixel_scales=(1.0, 5.0), grid=None)
            assert plane_image.yticks == pytest.approx(np.array([-1.5, -0.5, 0.5, 1.5]), 1e-3)

            plane_image = pl.PlaneImage(array=np.ones((3,3)), pixel_scales=(0.5, 5.0), grid=None)
            assert plane_image.yticks == pytest.approx(np.array([-0.75, -0.25, 0.25, 0.75]), 1e-3)

            plane_image = pl.PlaneImage(array=np.ones((6,1)), pixel_scales=(1.0, 5.0), grid=None)
            assert plane_image.yticks == pytest.approx(np.array([-3.0, -1.0, 1.0, 3.0]), 1e-2)

class TestPlaneImage:

    def test__compute_xticks_from_regular_grid_correctly(self):

        plane_image = pl.PlaneImage(array=np.ones((3, 3)), pixel_scales=(5.0, 1.0), grid=None)
        assert plane_image.xticks == pytest.approx(np.array([-1.5, -0.5, 0.5, 1.5]), 1e-3)

        plane_image = pl.PlaneImage(array=np.ones((3, 3)), pixel_scales=(5.0, 0.5), grid=None)
        assert plane_image.xticks == pytest.approx(np.array([-0.75, -0.25, 0.25, 0.75]), 1e-3)

        plane_image = pl.PlaneImage(array=np.ones((1, 6)), pixel_scales=(5.0, 1.0), grid=None)
        assert plane_image.xticks == pytest.approx(np.array([-3.0, -1.0, 1.0, 3.0]), 1e-2)

    def test__compute_yticks_from_regular_grid_correctly(self):

        plane_image = pl.PlaneImage(array=np.ones((3, 3)), pixel_scales=(1.0, 5.0), grid=None)
        assert plane_image.yticks == pytest.approx(np.array([-1.5, -0.5, 0.5, 1.5]), 1e-3)

        plane_image = pl.PlaneImage(array=np.ones((3, 3)), pixel_scales=(0.5, 5.0), grid=None)
        assert plane_image.yticks == pytest.approx(np.array([-0.75, -0.25, 0.25, 0.75]), 1e-3)

        plane_image = pl.PlaneImage(array=np.ones((6, 1)), pixel_scales=(1.0, 5.0), grid=None)
        assert plane_image.yticks == pytest.approx(np.array([-3.0, -1.0, 1.0, 3.0]), 1e-2)


#!/bin/env/python
# -*- encoding: utf-8 -*-
"""

"""
from __future__ import division, print_function

import numpy as np
from tensorflow.keras.applications import ResNet50, MobileNet

from dkeras import dKeras
import ray

def main():
    """
    Test out using multiple models at the same time, this case is running
    ResNet50 and MobileNet
    """
    ray.init()
    n_data = 20

    model1 = dKeras(ResNet50, weights='imagenet', wait_for_workers=True, n_workers=3)
    model2 = dKeras(MobileNet, weights='imagenet', wait_for_workers=True, n_workers=3)

    test_data = np.random.uniform(-1, 1, (n_data, 224, 224, 3))

    model1.predict(test_data)
    model2.predict(test_data)

    model1.close()
    model2.close()

if __name__ == "__main__":
    main()

ray.init()
n_data = 20

model1 = dKeras(ResNet50, weights='imagenet', wait_for_workers=True, n_workers=3)
model2 = dKeras(MobileNet, weights='imagenet', wait_for_workers=True, n_workers=3)

test_data = np.random.uniform(-1, 1, (n_data, 224, 224, 3))

model1.predict(test_data)
model2.predict(test_data)

model1.close()
model2.close()


import argparse
import os
import torch
from torch.utils.data import DataLoader
from torch import optim
import numpy as np
from data.audioset import Audioset_Dataset, Audioset_AV_Classify

from model.base_model import resnet18
from model.location_model import Location_Net_stage_two

from sklearn import cluster, metrics
import numpy as np
from sklearn.preprocessing import normalize

def batch_organize(audio_data, posi_img_data, nega_img_data):
    batch_audio_data = torch.zeros(audio_data.shape[0] * 2, audio_data.shape[1], audio_data.shape[2],
                                   audio_data.shape[3])
    batch_image_data = torch.zeros(posi_img_data.shape[0] * 2, posi_img_data.shape[1], posi_img_data.shape[2],
                                   posi_img_data.shape[3])
    batch_labels = torch.zeros(audio_data.shape[0] * 2)
    for i in range(audio_data.shape[0]):
        batch_audio_data[i * 2, :] = audio_data[i, :]
        batch_audio_data[i * 2 + 1, :] = audio_data[i, :]
        batch_image_data[i * 2, :] = posi_img_data[i, :]
        batch_image_data[i * 2 + 1, :] = nega_img_data[i, :]
        batch_labels[i * 2] = 1
        batch_labels[i * 2 + 1] = 0
    return batch_audio_data, batch_image_data, batch_labels

def eva_metric(predict, gt):
    correct = (np.round(predict) == gt).sum()
    return correct / predict.shape[0]

def location_model_train(model, av_model_fix, data_loader, optimizer, criterion_location, criterion_class, epoch, obj_rep):
    model.train()
    av_model_fix.eval()
    accs = 0
    count = 0
    losses_g = 0
    losses_l = 0
    obj_rep = np.tile(obj_rep, (8,1))
    obj_rep_cuda = torch.from_numpy(obj_rep).type(torch.FloatTensor).cuda()
    for i, data in enumerate(data_loader, 0):
        if i % 200 == 0:
            print('location batch:%d' % i)
        audio_data, posi_img_data, nega_img_data, _,_,_,_ = data
        audio_data, image_data, av_labels = batch_organize(audio_data, posi_img_data, nega_img_data)

        audio_data, image_data, av_labels = audio_data.type(torch.FloatTensor).cuda(), \
                                            image_data.type(torch.FloatTensor).cuda(), \
                                            av_labels.type(torch.FloatTensor).cuda()

        model.zero_grad()
        av_outputs, _, _, sounding_objects, sounding_object_local = model(image_data, audio_data, obj_rep_cuda)
        loss_global = criterion_location(av_outputs, av_labels)

        with torch.no_grad():
            _, _, audio_class, _, _ = av_model_fix(image_data, audio_data, obj_rep_cuda)

        audio_class = audio_class[::2]
        sounding_objects = sounding_objects[::2]

        pseudo_label = torch.zeros_like(audio_class)
        idx = torch.argsort(audio_class, axis=1)
        pseudo_label[np.arange(audio_class.shape[0]), idx[:, -2]] = 1
        pseudo_label[np.arange(audio_class.shape[0]), idx[:, -1]] = 1
        pseudo_label = pseudo_label.cuda()

        #sounding_objects = torch.nn.functional.log_softmax(sounding_objects/5, dim=1)
        audio_class = torch.nn.functional.softmax(audio_class.detach()/0.7, dim=1)

        loss_local = criterion_class(sounding_objects, audio_class)

        if epoch<50:
            losses = loss_local
            losses.backward()
            optimizer.step()
        else:
            losses = loss_local + loss_global
            losses.backward()
            optimizer.step()

        losses_g += loss_global.detach().cpu().numpy()
        losses_l += loss_local.detach().cpu().numpy()

        acc = eva_metric(av_outputs.detach().cpu().numpy(), av_labels.cpu().numpy())
        accs += acc
        count += 1

    print('Global matching loss is %.3f, local matching loss is %.3f ' % ((losses_g/count), (losses_l/count)))

    return accs / count

def main():
    parser = argparse.ArgumentParser(description='AID_PRETRAIN')
    parser.add_argument('--data_list_dir', type=str,
                        default='/mnt/home/hudi/location_sound/audioset-exp/data/audioset_multi')
    parser.add_argument('--data_dir', type=str, default='/mnt/scratch/hudi/audioset-instrument/')
    parser.add_argument('--mode', type=str, default='train', help='train/val/test')
    parser.add_argument('--use_class_task', type=int, default=1, help='whether to use class task')
    parser.add_argument('--init_num', type=int, default=0, help='epoch number for initializing the location model')
    parser.add_argument('--use_pretrain', type=int, default=1, help='whether to init from ckpt')
    parser.add_argument('--class_iter', type=int, default=3, help='training iteration for classification model')
    parser.add_argument('--ckpt_file', type=str, default='location_cluster_net_010_0.672.pth', help='pretrained model name')
    parser.add_argument('--enable_img_augmentation', type=int, default=1, help='whether to augment input image')
    parser.add_argument('--enable_audio_augmentation', type=int, default=1, help='whether to augment input audio')
    parser.add_argument('--batch_size', type=int, default=32, help='training batch size')
    parser.add_argument('--learning_rate', type=float, default=1e-4, help='training batch size')
    parser.add_argument('--epoch', type=int, default=2000, help='training epoch')
    parser.add_argument('--gpu_ids', type=str, default='[0,1,2,3]', help='USING GPU IDS e.g.\'[0,4]\'')
    parser.add_argument('--num_threads', type=int, default=12, help='number of threads')
    parser.add_argument('--seed', type=int, default=10)
    args = parser.parse_args()

    train_dataset = Audioset_Dataset(args.data_dir, 'multi_train_data.pkl', args)
    val_dataset = Audioset_Dataset(args.data_dir, 'multi_val_data.pkl', args)

    train_dataloader = DataLoader(dataset=train_dataset, batch_size=args.batch_size, shuffle=True,
                                  num_workers=args.num_threads)
    val_dataloader = DataLoader(dataset=val_dataset, batch_size=args.batch_size, shuffle=False,
                                num_workers=args.num_threads)

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # net setup
    visual_backbone = resnet18(modal='vision')
    audio_backbone = resnet18(modal='audio')
    av_model = Location_Net_stage_two(visual_net=visual_backbone, audio_net=audio_backbone)
    av_model_cuda = torch.nn.DataParallel(av_model)
    av_model_cuda.to(device)

    if args.use_pretrain:
        PATH = os.path.join('ckpt/stage_one/', args.ckpt_file)
        state = torch.load(PATH)
        av_model_cuda.load_state_dict(state, strict=False)

    # fixed model
    visual_backbone_fix = resnet18(modal='vision')
    audio_backbone_fix = resnet18(modal='audio')
    av_model_fix = Location_Net_stage_two(visual_net=visual_backbone_fix, audio_net=audio_backbone_fix)
    av_model_fix_cuda = torch.nn.DataParallel(av_model_fix)
    av_model_fix_cuda.to(device)

    if args.use_pretrain:
        PATH = os.path.join('ckpt/stage_one/', args.ckpt_file)
        state = torch.load(PATH)
        av_model_fix_cuda.load_state_dict(state, strict=False)
        print('loaded weights')

    # loading the object representation for stage_one
    obj_rep = np.load('obj_feature_softmax_avg_fc.npy')

    loss_func_BCE_location = torch.nn.BCELoss(reduce=True)
    loss_func_BCE_class = torch.nn.BCELoss(reduce=True)

    optimizer = optim.Adam(params=av_model_cuda.parameters(), lr=args.learning_rate, betas=(0.9, 0.999),
                           weight_decay=0.0001)

    init_num = 0
    for e in range(args.epoch):
        print('Epoch is %03d' % e)

        train_location_acc = location_model_train(av_model_cuda, av_model_fix_cuda, train_dataloader, optimizer,
                                                  loss_func_BCE_location, loss_func_BCE_class, e, obj_rep)

        if e % 5 == 0:
            PATH = 'ckpt/stage_two/audioset_pretrain_%03d_%.3f.pth' % (e, train_location_acc)
            torch.save(av_model_cuda.state_dict(), PATH)

if __name__ == '__main__':
    main()


import Vehicles
import numpy as np
import math
import cv2
import random
import time
import matplotlib.pyplot as plt
from PIL import Image
import scipy.misc
from functools import wraps
import copy
import os

# gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

class GameV1:
    ## 5000 is total length of highway
    ## class attributes
    front  = 20
    back = 20

    # front = 30
    # back = 20
    def __init__(self):
        #eachunit is 5.2 feet
        #car will be 4 units long
        #

        self.lanes = 5
        self.Game = []
        self.maxUnits = 500
        self.playercarposition = 0
        self.playerlanes = self.lanes-1
        # self.imagearray = np.zeros(shape=(500,600))
        # self.imagearray = np.zeros(shape = (200,300))
        self.imagearray = np.zeros(shape = (200,400))
        self.gameplayerindexvert =0
        self.gameplayerindexhorz =0
        self.give_img = False
        self.temprestore = []
        self.timer = 0
        self.colorImage = None
        self.our_car = []

    @classmethod
    def change_climate(cls, climate = "sunny"):
        if climate == "sunny":
            cls.front = 30
            cls.back =  20
        else:
            cls.front = 20
            cls.back =  10

    def draw_image(self):
        if (self.give_img == False):
            return
        flag = False
        plt.ion()
        if (not flag):
## checkout some exiciting colors https://matplotlib.org/tutorials/colors/colormaps.html
            ## Paired, Blues, Set1
            figure = plt.imshow(self.imagearray, cmap="Accent")
            flag=True

        else:
            figure.set_data(self.imagearray)
        plt.draw()
        # time.sleep(12)
        plt.waitforbuttonpress(0.01)

    def print_array(self, arr):
        for row in arr:
            for elem in row:
                print(elem, end=' ')
            print()

## prints the positions in the playercarlane

    def visualize_positions(original_func):

        def wrapper(self,*args,**kwargs):
            arr = original_func(self)
            for j in range(len(arr[self.playerlanes])):
                print(arr[self.playerlanes][j], end=" ")
            return arr
        return wrapper

    # def print_positions_array(self):
    #     all_posn =self.give_me_all_positions()
    #     for j in range(len(all_posn[self.playerlanes])):
    #         print(all_posn[self.playerlanes][j], end=" ")

    # @visualize_positions
    def give_me_all_positions(self):
        all_positions_array = []
        ## for each row in the game array
        for i in range(len(self.Game)):
            all_positions_array.append([])
        for i in range(len(self.Game)):
            for j in range(len(self.Game[i])):
                all_positions_array[i].append(self.Game[i][j].GetPos())
            #     print(self.Game[i][j].GetPos(), end = " ")
            # print()
        return all_positions_array

## collision detection based on positions
    def collision_detection_player(self):
        all_positions_array = self.give_me_all_positions()
        self.searchforPlayerCar()
        player_position  = self.Game[self.playerlanes][self.playercarposition].GetPos()

        length = len(all_positions_array[self.playerlanes])
        for j  in range(0,length):
            if  (j != self.playercarposition):
                pos = all_positions_array[self.playerlanes][j]
                if abs(int(pos-player_position))<4:
                    # print("pos_that_creates_a_crash_followed_by_player_position",pos, player_position)
                    # print("disable for now")
                    return True
        return False

    def populateGameArray(self):
        self.Game=[]
        self.timer = 0
        # import time
        random.seed(int(time.time()))
        for i in range(self.lanes):
            self.Game.append([])
        for i in range(self.lanes):
            currentposition = 0
            while(currentposition<self.maxUnits):
                ### velocity and position randomized
                if (i==3):
                    currentposition += 50 ## change the freq from 30 to 40
                    # self.Game[i].append(Vehicles.Truck(currentposition, np.random.normal(1+(0.3*(self.lanes-i-1)), 0.3)))
                    self.Game[i].append(Vehicles.Truck(currentposition, 1))

                    continue
                    # continue

                self.Game[i].append(Vehicles.Car(currentposition, 2 * (self.lanes-i)))

                # self.Game[i].append(Vehicles.Car(currentposition, np.random.normal(2+(1+0.3*(self.lanes-i-1)), 0.3)))

                # currentposition += random.randrange(8,20)## any number between 8 and 20
                # self.Game[i].append(Vehicles.Car(currentposition,2))
                currentposition += 50 ##  change freq from 20 to 30

        self.Game[self.lanes-1][0] = Vehicles.PlayerCar(0, self.Game[self.lanes-1][1].velocity)

    def updateGameArray(self, action):
        self.timer +=1
        self.tempstore = self.Game

        self.searchforPlayerCar()## will set the self.playerlanes and self.carposition
        # print("before",self.playerlanes,self.playercarposition)

        reward = self.updatePlayerCar(action)
        # print("reward",reward)
        self.searchforPlayerCar()## will set the self.playerlanes and self.carposition
        # print("after",self.playerlanes,self.playercarposition)

        for i in range(len(self.Game)):
            for j in range(len(self.Game[i])):
                updatingcar = self.Game[i][j] ## every car object

                if (isinstance(updatingcar, Vehicles.PlayerCar) == False):
                #     ## if its other car
                #     # if the next car to it is some car probably also the last car in that lane
                #     ## which means updating car should not be the last car
                #     if (j+1 <= len(self.Game[i]) -1):
                #         # if the difference between position of this car and next car is less than 0.3
                #         # if (updatingcar.GetVel() -self.Game[i][j+1].GetVel() <= 4): ## change it to 4 from 0.3
                #             ## give the same velocity as the next adjacent car
                #             updatingcar.updateVeloc(self.Game[i][j+1].GetVel())
                #     ## if the previous car to updating car is the not the first car
                #     if (j-1 != 0):
                #         ## chance of collision
                #         if updatingcar.GetVel() < self.Game[i][j-1].GetVel():
                #             # if self.timer%10 == 0:
                #                 ##
                #                 # updatingcar.updateVeloc(np.random.normal(2+(1+0.3*(self.lanes-i-1)), 0.15))
                #                 updatingcar.updateVeloc(self.Game[i][j-1].GetVel())
                    ## we have to update the position in every case but velocities only for middle cars
                    ## below  is valid for both start and end car updatepos()

                    ## prev car exist
                    # if (j-1>=0):
                    #     if (updatingcar.GetVel()>self.Game[i][j-1].GetVel()):
                    #         updatingcar.updateVeloc(self.Game[i][j-1].GetVel())
                    # updatingcar.updatePos()
                    updatingcar.updatePos()

                    ## if the position is greater than 500
                    if updatingcar.GetPos() >= self.maxUnits:
                        self.Game[i].pop(j) ## remove that jth car object
                        self.Game[i].insert(0, updatingcar)## insert in position 0
                        updatingcar.position = 0
        self.searchforPlayerCar() ## this will fill playerlanes and playercarposition
        playercar = self.Game[self.playerlanes][self.playercarposition]
        playercar.updatePos()## update the player car this will update the game array position and above

        #if action ==2 or action ==3:
        #    if self.playercarposition != len(self.Game[self.playerlanes]) -1:
        #        updatingcar.velocity = self.Game[self.playerlanes][self.playercarposition+1].GetVel()
        # print('lanes: {}, action: {}, velocity: {}'.format(self.playerlanes, action, playercar.GetVel()))

        # self.createImage(self.createImageList(), self.gameplayerindexvert, self.gameplayerindexhorz)
        self.image_from_visible_objects()

        # self.render_image()
        # plt.plot(self.imagearray)
        # plt.show()

        # img = Image.fromarray(self.imagearray, 'L')
        # img.show()

        # for i in range(200):
        #     for j in range(300):
        #         print(self.imagearray[i][j], end =" ")
        #     print()

        self.searchforPlayerCar()
        if self.checkColission():
        # if self.collision_detection_player():
            # print("crash")
            # plt.imshow(self.imagearray, cmap="Blues")
            # plt.show()

        #    commented for now #
            self.draw_image()

            return -1, 0
        elif playercar.GetPos() >=self.maxUnits:
            # print("win")
            # plt.imshow(self.imagearray, cmap="Blues")
            # plt.show()

        #    commented for now #
            self.draw_image()
            return 1, reward
        else:
            # print("no_crash")
            # plt.imshow(self.imagearray, cmap="Blues")
            # plt.show()
        #    commented for now #
            # print("something")
            self.draw_image()

            return 0, reward

    def searchforPlayerCar(self):
        for i in range(len(self.Game)):
            for j in range(len(self.Game[i])):
                if isinstance(self.Game[i][j], Vehicles.PlayerCar):
                    self.playerlanes = i
                    self.playercarposition =j
                    break

    ## return reward function
    def updatePlayerCar(self, action):
        #action 0 = ACC : Adaptive cruise control follow front car
        #action 1 = velocity +=2 accelerate in same lane
        #action 2 = left lane change
        #action 3 = right lane change

        playercar = self.Game[self.playerlanes][self.playercarposition]

        if (action == 0):

            if self.playercarposition != len(self.Game[self.playerlanes]) -1:
                if (self.Game[self.playerlanes][self.playercarposition+1].GetPos()> playercar.GetPos()):
                    playercar.updateVeloc(self.Game[self.playerlanes][self.playercarposition+1].GetVel())
                #if playercar.velchange > 0:
                #    return playercar.velchange
                #if playercar.velchange < 0:
                #    return playercar.velchange

                return 0
            else:

                return 0
        elif (action == 1):
            #self.searchforPlayerCar()
            playercar.updateVeloc(playercar.GetVel() + 0.5)
            return 0

        elif (action == 2):

            if self.playerlanes != 0:

                for i in range(len(self.Game[self.playerlanes -1])):

                    if self.Game[self.playerlanes-1][i].GetPos() >= playercar.GetPos():

                        self.Game[self.playerlanes].pop(self.playercarposition)
                        self.Game[self.playerlanes-1].insert(i, playercar)
                        return self.Game[self.playerlanes-1][i+1].GetVel() - playercar.GetVel()
            else:

                if self.playercarposition != len(self.Game[self.playerlanes]) - 1:
                    playercar.updateVeloc(self.Game[self.playerlanes][self.playercarposition + 1].GetVel())
                    return 0
        elif (action == 3):

            if self.playerlanes != self.lanes-1:

                for j in range(len(self.Game[self.playerlanes+1])):
                    if self.Game[self.playerlanes+1][j].GetPos() >= playercar.GetPos():
                        self.Game[self.playerlanes].pop(self.playercarposition)
                        self.Game[self.playerlanes+1].insert(j, playercar)
                        return self.Game[self.playerlanes+1][j+1].GetVel() - playercar.GetVel()
            else:
                if self.playercarposition != len(self.Game[self.playerlanes]) -1:
                    playercar.updateVeloc(self.Game[self.playerlanes][self.playercarposition+1].GetVel())
                    return 0
        # else:
        #     return 0

    ## if this returns true meaning it has collided
    def checkColission(self):
        playercar = self.Game[self.playerlanes][self.playercarposition]
        if self.checkBack(playercar):
            # print("1")
            return True
        elif self.checkFront(playercar):
            # print("2")
            return True
        elif self.checkFast(playercar):
            # print("3")
            return True
        # elif self.checkCurrent(playercar):
        #     # print("4")
        #     return True
        # elif self.checkTotal(playercar):
            # print("5")
            # return True
        # if self.collision_detection_player():
        #     print("6")
        #     return True
        else:
            return False

    # def checkTotal(self,playercar):
    #     self.searchforPlayerCar()
    #     player_position = playercar.GetPos()
    #     for obj in self.Game[self.playerlanes]:
    #         print(obj.GetPos(),end = " ")
    #         if abs(obj.GetPos() - player_position<=4):

    #             return True

    #     return False

    # def checkCurrent(self,playercar):
    #     player_position = playercar.GetPos()
    #     posn_b_player =self.Game[self.playerlanes][self.playercarposition-1].GetPos()
    #     posn_a_player = self.Game[self.playerlanes][self.playercarposition+1].GetPos()

    #     if abs(player_position - posn_b_player)<4:
    #         return True
    #     if abs(player_position- posn_a_player)<4:
    #         return True
    #     return False

    def checkBack(self, playercar):
        if self.playercarposition !=0:
            if playercar.CollisionBox()[1][0] <= self.Game[self.playerlanes][self.playercarposition-1].CollisionBox()[1][1]:
                # print(playercar.CollisionBox()[1][0])
                #print("playercar")
                #print(self.Game[self.playerlanes][self.playercarposition-1].CollisionBox()[1][1])
                #print("car behind")
                return True
        return False

    def checkFront(self, playercar):
        self.searchforPlayerCar()
        if self.playercarposition < (len(self.Game[self.playerlanes])-1):

            if playercar.CollisionBox()[1][1] >= self.Game[self.playerlanes][self.playercarposition+1].CollisionBox()[1][0]:
                # print(playercar.CollisionBox()[1][1])
                # print(self.Game[self.playerlanes][self.playercarposition+1].CollisionBox()[1][0])
                return True

        return False

    ## go thorugh in two time stamps
    def checkFast(self,playercar):
        if (self.playercarposition!=0):

            if playercar.CollisionBox()[0][1] <= self.Game[self.playerlanes][self.playercarposition-1].CollisionBox()[0][0]:

                if playercar.CollisionBox()[1][0] >= self.Game[self.playerlanes][self.playercarposition-1].CollisionBox()[1][1]:
                    #print("2 fast")
                    return True

        return False

    # def checkLeft(self,playercar):
    #     if (self.playercarposition!=0):

    ## decorator
    # def print_any_array(org_func):
    #     def print_wrapper(*args,**kwargs):
    #         arr = org_func(*args,**kwargs)
    #         for row in arr:
    #             for elem in row:
    #                 print(elem, end=' ')
    #             print()
    #         return org_func()

    #     return print_wrapper

    ##visibility array
    ## self.createImagelist() will fill visibility_array positions
    # @print_any_array
    # def createImageList(self):
    #     playercar = self.Game[self.playerlanes][self.playercarposition]

    #     visibility_array = []

    #     # front = 20
    #     # back=20
    #     ## for each row in the game array
    #     for i in range(len(self.Game)):
    #         visibility_array.append([])
    #     for i in range(len(self.Game)):

    #         for j in range(len(self.Game[i])):

    #             ## behind car visibility by 10
    #             if self.Game[i][j].GetPos() >= playercar.GetPos() - self.back:
    #                 ## if car at i,j is in range p-10 to p
    #                 ## possible values are {0,1,2,3,4,5,6,7,8,9}
    #                 if self.Game[i][j].GetPos() <= playercar.GetPos():
    #                     ## visibility array will have {100,110,120,....190,200}
    #                     visibility_array[i].append((self.Game[i][j].GetPos() - playercar.GetPos()) * 10 + 10*self.back ))
    #                 if self.Game[i][j].GetPos() == playercar.GetPos():

    #                     self.gameplayerindexvert = i
    #                     ## our car will be put in the visibility_array recently
    #                     ## therefore last element of subarray
    #                     self.gameplayerindexhorz = len(visibility_array[i]) - 1

    #             if self.Game[i][j].GetPos() <= playercar.GetPos() + self.front:
    #                 if self.Game[i][j].GetPos() > playercar.GetPos():
    #                     visibility_array[i].append((self.Game[i][j].GetPos() - playercar.GetPos()) * 10 + 10*self.back)
    #     return visibility_array

## create Imagelist outputs visibility array on vehicle  objects that are visible
## its only array of position and does not have vehicle objects in it

##  create Image
## shade where is final painting

## will fill imagearray
    # def createImage(self, visibility_array, playervert, playerhorz):
    #     self.imagearray = np.zeros(shape=(np.shape(self.imagearray)[0],np.shape(self.imagearray)[1])) ## display image array
    #     self.imagearray.fill(0.1)
    #     for i in range(len(visibility_array)):
    #         for j in range(len(visibility_array[i])):
    #             if i == playervert:
    #                 if j == playerhorz:
    #                     ## for our car spotted
    #                     self.shadewhere(visibility_array[i][j], i, 0.5)
    #                 else:
    #                      ## car in same row but not our car
    #                     self.shadewhere(visibility_array[i][j], i, 1)
    #             else:
    #                 ## car in different row
    #                 self.shadewhere(visibility_array[i][j], i, 1)

    ## fill the imagearray
    ## the position is for display only not the real position as
    def shadewhere(self, position, lanenumber, value):
        ## 4 units for the car
        leftmax = int(math.floor(position - 20))
        rightmax = int(math.floor(position + 20))
        if leftmax < 0:
            leftmax = 0
        ## cannot exceed maximum limits
        if rightmax >np.shape(self.imagearray)[1]:
            rightmax = np.shape(self.imagearray)[1]
        ## car is 20 by 40
        ## 200/5 = 40 pixels in vertical per lane

        diff_rows = np.shape(self.imagearray)[0]//self.lanes

        # total_visibility = 30

        for i in range(20):
            for j in range(rightmax-leftmax):

                self.imagearray[i+10 + lanenumber* diff_rows][leftmax+j] = value

    def shadewhere_truck(self, position, lanenumber, value):
        ## 4 units for the car
        leftmax = int(math.floor(position - 50))
        rightmax = int(math.floor(position + 50))
        if leftmax < 0:
            leftmax = 0
        ## cannot exceed maximum limits
        if rightmax >np.shape(self.imagearray)[1]:
            rightmax = np.shape(self.imagearray)[1]
        ## car is 20 by 40
        ## 200/5 = 40 pixels in vertical per lane

        diff_rows = np.shape(self.imagearray)[0]//self.lanes

        # total_visibility = 30

        for i in range(20):
            for j in range(rightmax-leftmax):

                self.imagearray[i+10 + lanenumber* diff_rows][leftmax+j] = value

    def visualize_objects(original_func):
        def wrapper(self,*args,**kwargs):
            arr = original_func(self)
            for i in range(len(arr)):
                for j in range(len(arr[i])):
                    print(arr, end = " ")
                print()
            return arr
        return wrapper

    # @visualize_objects
    def visible_game_objects(self):

        self.searchforPlayerCar() ## fill playerlanses and  playercarposition
        playercar = self.Game[self.playerlanes][self.playercarposition]
        visible_game_objects = []

        for i in range(len(self.Game)):
            ## 5 rows
            visible_game_objects.append([])
        for i in range(len(self.Game)):

            for j in range(len(self.Game[i])):

                if self.Game[i][j].GetPos() >= playercar.GetPos() - self.back:
                    ## inside visibility range

                    if self.Game[i][j].GetPos() <= playercar.GetPos():

                        visible_game_objects[i].append(self.Game[i][j])

                    if (self.Game[i][j].GetPos() == playercar.GetPos()\
                         and isinstance(self.Game[i][j],Vehicles.PlayerCar)) :

                        self.gameplayerindexvert = i ## index of player car in visibility game objects

                        self.gameplayerindexhorz = len(visible_game_objects[i]) - 1 ## most impt
                        visible_game_objects[i].append(self.Game[i][j])

                if self.Game[i][j].GetPos() <= playercar.GetPos() + self.front:
                    if self.Game[i][j].GetPos() > playercar.GetPos():
                        visible_game_objects[i].append(self.Game[i][j])

        return visible_game_objects

    def image_from_visible_objects(self):
        self.imagearray = np.zeros(shape=(np.shape(self.imagearray)[0],np.shape(self.imagearray)[1])) ## display image array
        # self.imagearray.fill(0.01)
        # print("fun_call")

        vgo = self.visible_game_objects() #visible_game_objects
        #player_car_index_in_visible_game_obj
        pcivgo = vgo[self.gameplayerindexvert][self.gameplayerindexhorz]

        for i in range(len(vgo)):
            for j in range(len(vgo[i])):

                if isinstance(vgo[i][j],Vehicles.Truck):
                        # print("truck",vgo[i][j].GetVel())
                        posn = (vgo[i][j].GetPos() - pcivgo.GetPos())* 10 + self.back * 10
                        # self.shadewhere_truck(posn,i,2.75)
                        self.shadewhere_truck(posn,i,vgo[i][j].GetVel())
                        continue

                if isinstance(vgo[i][j],Vehicles.PlayerCar):
                    posn =  self.back * 10
                    # print("player_car",self.gameplayerindexvert," ",vgo[i][j].GetVel()," ", vgo[i][j].GetPos())
                    self.shadewhere(posn,self.gameplayerindexvert,vgo[i][j].GetVel())
                    continue

                if isinstance (vgo[i][j], Vehicles.Car):
                    ## check if left or right
                        posn = (vgo[i][j].GetPos() - pcivgo.GetPos())* 10 + self.back * 10
                        # print("other_cars",vgo[i][j].GetVel())
                        # print(vgo[i][j].GetVel())
                        self.shadewhere(posn,i,vgo[i][j].GetVel())

    def runGame(self, action, greedy):

        #if self.i == 0:
         #   temp = [0,0]
        #else:
        # temp = [0,0]
        ## temp is 0,-1 or 1
        temp = self.updateGameArray(action)
        # if greedy == True and temp[0] != 0:
        #     self.Game=self.temprestore
        #     return "REDO", 0, 0, 0

        ## convert 2d numpy image into 3 channel image
        # instead of returning self.imagearray  as observations return stacked_image
        # stacked_img = np.stack((self.imagearray,)*3, axis=-1)

        # print("temp", temp)
        if temp[0] == 0:
            ##  imagearray , gamestatus : running: 0 , crashed: -1, win: 1, reward, done status: T OR F, ego_velocity
            return self.imagearray, temp[0], temp[1], False,self.Game[self.playerlanes][self.playercarposition].GetVel()
            # return  self.imagearray, temp[0], temp[1], False
            #self.i = 1
        else:
            ## bot has crashed or bot has successfully cross 500 restart game
            #self.i=0
            # print("cancelled for now restart_simulation")
            tempcar = self.Game[self.playerlanes][self.playercarposition].GetVel()
            self.populateGameArray()
            # observation, reward, smallreward, done, velocity
            ## negative velocity is the reward

            return self.imagearray, temp[0], 0, True, temp[0] * tempcar
            # return self.imagearray,temp[0],0,True

def print_array(arr):
    count  = 0
    for i in range(len(arr)):
        for j in range(len(arr[0])):
            if arr[i][j] == 2.75:
                print(arr[i][j], end = " ")

# def main():
#     # display = True
#     game = GameV1()
#     # game.overlay_image()
#     game.populateGameArray() ## 1 time

#     # for i in range(len(game .Game)):
#     #     for j in range(len(game.Game[i])):
#     #         print(game.Game[i][j]," ")
#     #     print(" ")

#     gameover = False
#     # cv2.namedWindow("game images")
#     # game.change_climate
#     # test = 0;
#     # game.give_img = True
#     # count  = 0
#     # accelerate = 1
#     count = 0
#     while True:
#         action = int(input("please_input_action"))
#         if (action == "end"):
#             break
#         count += 1
#         print("count", count)

#         # action  = 0
#         # if (count % 40):
#         #     action = 1
#         game.give_img = False
#         a,b,c,d,e = game.runGame(action, False) ## for now no populate game array

#         # data = 255 * data # Now scale by 255
#         # img = data.astype(np.uint8)

#         # if ((b ==1) or (b == -1)):
#         #     break

#         img = a

#         # print_array(game.imagearray)
#         cv2.imshow("window",img)
#         # cv2.imshow("window",a)
#         key = cv2.waitKey(100)

#         # a,b,c,d,e = game.runGame(0,False)

#         print(type(b))
#         print(type(c))
#         print(a.dtype)
        # accelerate+=1
        # if accelerate%13 == 0:
            # print("accelerate")
            # a,b,c,d,e = game.runGame(1,False)

        # if (b==1):
        #     count+=1
        #     print("winning" + str(count))
        # if (b==-1):
        #     cv2.imshow(" ",game.imagearray)
        #     key = cv2.waitKey(3000)
        #     file_path = ''
        #     file_name = '\\test_collision\\'+ time.strftime('%Y_%m_%d_%H_%M_%S') + '.png'

        #     s = cv2.imwrite('F:\sim\Simulator_Presentation' + '\\test_collision\\'+ time.strftime('%Y_%m_%d_%H_%M_%S') + '.png', game.imagearray)
        #     print(s, os.path.join(file_path, file_name))
        #     game.populateGameArray()

            # print("ssss",s )

            # print("losing")

        # key = cv2.waitKey(3000)#pauses for 3 seconds before fetching next image
        # if key == 27:#if ESC is pressed, exit loop
        # cv2.destroyAllWindows()

        # print("visibility_positions")
        # game.print_array(game.createImageList())

        # game.playercarposition();
        # print("true position playercar",game.Game[game.playerlanes][game.playercarposition])

        # print(game.Game[game.playerlanes][game.playercarposition].GetPos())
        # game.print_positions_array()
        # game.give_me_all_positions() ## in player lane

    # while gameover == False:
    #    temp = game.updateGameArray(0)
    #    if temp != None:
    #        gameover = True
    #        print(temp)
    # cv2.destroyAllWindows()

# if __name__ == "__main__":
#     main()

# g1 = GameV1(True)
# g1.overlay_image()
# g1.populateGameArray()
# g1.Get_Vehicle_Objects()
# g1.
# sum = 0;
# for i in range(0,5):
#     sum+=len(g1.Game[i])
# print(sum)

# for i in range(5):
#     g1.updateGameArray(0)
#     g1.updateGameArray(0)
#     g1.updateGameArray(0)
#     g1.updateGameArray(0)
#     g1.updateGameArray(0)

# ## proof that local variables point to the same element in function
# arr = []
# for i in range(13):
#     arr.append(Vehicles.Car(0,2))

# update = arr[0]
# print(arr[0].GetPos())
# update.updatePos()
# update.updatePos()
# update.updatePos()
# update.updatePos()
# update.updatePos()
# print(arr[0].GetPos())

# subarray = []
# g1 = GameV1(True)
# g1.populateGameArray()
# g1.updateGameArray(0)
# subarray=g1.createImageList()

# print("subarray")
# for i in range(len(subarray)):
#     for j in range(len(subarray[i])):
#         print(subarray[i][j], end = " ")
#     print(" ")
# print(" ")
# print("len_subarray ", len(subarray))
# print("len_subarray[0] ", len(subarray[0]))