import numpy as np

import logging
logging.basicConfig(format='[%(asctime)s]-[%(name)-1s-%(levelname)2s]: %(message)s')
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.DEBUG)

def gaussian_pdf(x, mu, sigma):
    # x vector should be in column, but numpy treat 1-d array as vector so can save the extra step
    n = len(x)
    return (2*np.pi)**(-n/2) * (np.linalg.det(sigma) ** (-1/2) ) * np.exp(-1/2*((x-mu).T@np.linalg.inv(sigma)@(x-mu)))

def component_pdfs(x, mus, sigmas):
    """
    The component pdf p_k(x; mu_k, sigma_k)
        :param mus: Kxn array (n be the dimension of x vector and K be the num of components), mean of k n-dim gaussian distr.
        :param sigmas: Knxn array covariance of k n-dim gaussian distr.
        :return: K-dim array contain probability of each component pdf
    """
    n_components = mus.shape[0]
    return np.array([gaussian_pdf(x, mus[k,:], sigmas[k, :, :]) for k in range(n_components)])

def likelihood_function(X, taus, mus, sigmas):
    """
    The component pdf p_k(x; mu_k, sigma_k)
        :param taus: K-dim array contains the weight (or prior of hidden var) of each gaussian component
        :param mus: Kxn array (n be the dimension of x vector and K be the num of components), mean of k n-dim gaussian distr.
        :param sigmas: Knxn array covariance of k n-dim gaussian distr.
        :return: numeric between 0 and 1, the likelihood function value
    """
    N = X.shape[0] # number of data points
    get_component_prob = lambda x: component_pdfs(x, mus, sigmas)
    T = np.apply_along_axis(arr=X, func1d=get_component_prob, axis=1) # gaussian component probabilities in row format (NxK)
    taus_rep = np.tile(taus, reps=(N, 1)) # repeat tau along N-axis so elementwise product can work

    return np.sum(T*taus_rep, axis=1)

def e_step(X, taus, mus, sigmas):
    """
        E step of the EM algorithm, caculates the posterior T_{k, i}=P(z_i=k|x_i)
        it returns T_{k,i} in the form of a KxN T matrix where each element is T_{k, i}
        :param X: Nxn matrix represents N number of n-dim data points
        :param taus: K-dim vector, the weight of each component, or the prior of the hidden stats z
        :param mus: Kxn matrix (n be the dimension of x vector and K be the num of components), mean of k n-dim gaussian distr.
        :param sigmas: Kxnxn matrix covariance of k n-dim gaussian distr.
        :return: T_{k,i} in the form of a KxN T matrix where each element is T_{k, i}
    """
    K, N = mus.shape[0], X.shape[0] # dimensions, K: num of hidden component, N: number of data points
    get_component_prob = lambda x: component_pdfs(x, mus, sigmas)
    T = np.apply_along_axis(arr=X, func1d=get_component_prob, axis=1) # gaussian component probabilities in row format (NxK)
    taus_rep = np.tile(taus, reps=(N, 1)) # repeat tau along N-axis so elementwise product can work

    norm_const = np.sum(T*taus_rep, axis=1) # the normalisation factor \sum_{k=1}^K p_k * tau_k， and is currently estimated likelihood
    norm_const_rep = np.tile(norm_const, reps=(K, 1)).T # repeat normalisation constant along K-axis

    T = T*taus_rep/norm_const_rep # calculate the posterior
    return T.T #return the transposed matrix so that the index is matched

def m_step(X, T):
    """
        M step of the EM algorithm, caculates the MLE of taus, mus and sigmas
        :param X: Nxn matrix, the dataset, N number of n-dim data points
        :param T: KxN matrix, the T matrix is the posterior matrix where the i, j th component is the T_{k, i}
        :return: a 3-tuple:
            - taus: K-dim array, the estimated prior probability for each hidden variable z
            - mus: Kxn matrix, the estimated mean of the n-dim gaussian component, for each of the k component
            - sigmas: Kxnxn matrix, the covariance matrix of the n-dim gaussian component, for each of the k component
    """
    def get_sigma(X, muk, Tk):
        """
            function that calculate the covariance of the k-th component
            :param muk: n-dim vector, the k-th component's mean
            :param Tk: N-dim vector, the k-th component posterior of hidden state z_i, for each x_i
        """
        X_centred = X - muk
        X_weighted = X_centred * np.tile(Tk, reps=(X.shape[1],1)).T # repeat Tk in N-direction to match X's shape and weigh it
        return X_weighted.T@X_centred/np.sum(Tk) # weighted and centred are exchangable: we only need to weigh it by T_k once

    N, n = X.shape #  N: number of data points, n: dimension of the data point
    K = T.shape[0] # num of hidden component
    T_sum = np.sum(T, axis=1) # caculate the common term sum of T_{k, i} over all i, this is a k-dim vector

    taus = T_sum / N # average over i  for T_{k, i} gives MLE for all tau_k

    T_sum_rep = np.tile(T_sum, reps=(n, 1)).T # repeat T_sum n times in column
    mus = T@X/T_sum_rep # T@X gives a Kxn matrix with it's k, i th component be \sum_{i=1}^NT_{k, i}x_i then each row is divided by T_sum, gives MLE for all mu_k

    sigmas = np.array([get_sigma(X, mus[k, :], T[k, :]) for k in range(K)])
    return taus, mus, sigmas

class GaussianMixture():
    def __init__(self, n_hidden=2, max_iter=100, seed=None):
        """
            :param n_hidden: number of hidden variables (or the number of components)
            :param max_iter: maximum EM iteration allowed, default to 100
            :param seed: the random seed for initialisation
        """
        self.n_hidden = n_hidden
        self.max_iter = max_iter
        self.seed = seed
        self.taus, self.mus, self.sigmas = None, None, None

    def fit(self, X):
        """
            :param X: Nxn matrix (N be the num of data points and n be the dimension of each data point)
        """
        n_hid = self.n_hidden
        n_var = X.shape[1]

        np.random.seed(self.seed) # setup seed, if None means no seed
        mus = np.random.randn(n_hid, n_var)*10 # initialise means of k components
        np.random.seed(self.seed)
        # initialise  sigmas of k components with identity
        sigmas = np.array([np.eye(n_var) for _ in range(n_hid)])
        taus = np.ones(n_hid)/n_hid # assume uninformative prior

        for i in range(self.max_iter):
            _logger.debug(f"iter: {i}, objective: {np.sum(np.log(likelihood_function(X, taus, mus, sigmas)))}") # only show this in debug mode to reduce unnecessary evaluation
            T = e_step(X, taus, mus, sigmas)
            sigmas_prev = sigmas
            taus, mus, sigmas = m_step(X, T)
            if np.min(abs(sigmas - sigmas_prev) < 0.1):
                _logger.debug(f"break after iteration {i+1}")
                break
        self.taus, self.mus, self.sigmas = taus, mus, sigmas
        return self

    def predict_proba(self, X):
        T = e_step(X, self.taus, self.mus, self.sigmas)
        return T.T # transpose, so it's 1st dimension matches the X's dimension N.

    def predict(self, X):
        T = e_step(X, self.taus, self.mus, self.sigmas)
        return np.argmax(T.T, axis=1)

if __name__ == "__main__":

    # generate a dataset
    mean1, mean2 = np.array([0, 0]), np.array([10, 20])
    sigma1, sigma2 = np.array([[1, 0], [0, 1]]), np.array([[5, -5], [-5, 10]])

    np.random.seed(42)
    X1 = np.random.multivariate_normal(mean1, sigma1, 1000)
    np.random.seed(42)
    X2 = np.random.multivariate_normal(mean2, sigma2, 200)
    X = np.vstack([X1, X2])

    print("="*20, "data info", "="*20)
    print(f"cluster 1: mean={np.mean(X1, axis=0)}\nsigma={np.cov(X1.T)}")
    print('-'*8)
    print(f"cluster 2: mean={np.mean(X2, axis=0)}\nsigma={np.cov(X2.T)}")
    print("="*50)
    print("\n\n")

    n_components = 2
    gmm = GaussianMixture(n_hidden=n_components, seed=0)
    print("="*20, f"fitting Gaussian mixture model with {n_components} components", "="*20)
    gmm.fit(X)

    print(f"{'='*20}finished fitting{'='*20}")
    print(f"{'-'*5}estimated taus:\n{gmm.taus}")
    print(f"{'-'*5}estimated mus:\n{gmm.mus}")
    print(f"{'-'*5}estimated sigmas:\n{gmm.sigmas}")

    print("\n\n")
    print(f"{'='*20}test predictive power{'='*20}")
    # construct a new dataset for test, but with the same distribution
    np.random.seed(0)
    X1 = np.random.multivariate_normal(mean1, sigma1, 1000)
    np.random.seed(0)
    X2 = np.random.multivariate_normal(mean2, sigma2, 200)
    X_test = np.vstack([X1, X2])
    y_true = np.hstack([np.zeros(1000), np.ones(200)])

    print("-"*20, "test data info", "-"*20)
    print(f"cluster 1: mean={np.mean(X1, axis=0)}\nsigma={np.cov(X1.T)}")
    print('-'*8)
    print(f"cluster 2: mean={np.mean(X2, axis=0)}\nsigma={np.cov(X2.T)}")
    print("-"*8)

    # predict use the model trained
    y_pred = gmm.predict(X_test)
    accuracy = np.mean(y_true==y_pred)
    print(f"accuracy of the model on test is {accuracy}")


# 2D shape analysis
#
#Written by Matthew J. Gastinger
#
#Aug 2020 - Imaris 9.6.0

# <CustomTools>
#     <Menu>
#         <Submenu name="Surfaces Functions">
#             <Item name="2D Shape Analysis9" icon="Python3">
#                 <Command>Python3XT::XT_MJG_Shape_Analysis9(%i)</Command>
#             </Item>
#         </Submenu>
#     </Menu>
#     <SurpassTab>
#         <SurpassComponent name="bpSurfaces">
#             <Item name="2D Shape Analysis9" icon="Python3">
#                 <Command>Python3XT::XT_MJG_Shape_Analysis9(%i)</Command>
#             </Item>
#         </SurpassComponent>
#     </SurpassTab>
# </CustomTools>
#
#Description:
#This XTension will calculate a variety of 2D statistics.  If isosurface is 2D,
#the calculations are done on mask of the surface on the slice.  If the isosurface
#if 3D, the closest slice to the center of homegeneous mass of the surface is used

#
#Ring mask generated using   ndimage.filters.generic_filter(vSliceData, np.std, size=2)

#
#All statistics are based on a masked surface at the midline of the 3D surface,
#or in a 2D slice.
#Definite of New Statistics:
# 1.Perimeter midline
#     1)Border Perimeter midline (convexhull)
#           Measurement of the length of the perimeter of the calculated 2D convexhull
#
#     2)Border Perimeter midline - Contour vertices
            #Measurement of the contour vertices and walking distances between vertices (better)
# 2.2D cross-sectional area (at midline)
#     1) 2DArea -- Quantification of voxels inside the mask multiplied by size of one pixel
#     2) 2DArea convexhull -- of the Convexhull created from the border pixels

# 4. Compactness
#       -A measure of roundness or circularity (area to perimeter ratio) which includes local irregularities
#           defined as the ratio of the area of an object to the area of a circle with the actual perimeter
# 5. Circularity
#       -A measure of roundness or circularity (area to perimeter ratio) which excludes local irregularities
#           can be obtained as the ratio of the area of an object to the area of a circle with the same convex perimeter
# 6. Convexity
#       -Relative amount that an object differs from a convex object.
#       -A measure of convexity can be obtained by # forming the ratio of the
#        -perimeter of an object’s convex hull to the perimeter of the object
# 6. Solidity
#        Solidity is the ratio of contour area to its convex hull area.
# 7.Diameter of Equivalent Circle (Compactness)
#     -Diameter of circle based on the actual perimeter=circle circumference
#      -Defined as the ratio of the area of an object to the area of a circle with
#           the same perimeter.
# 8.FeretDiameter Max
#     -BoundingBoxOO Length C - for the surfaces
# 9.Feret Diameter Max90
#     -BoundingBoxOO Length B - surfaces
#     -the Feret diameter measured at an angle of 90 degrees to that of the
#       maximum Feret diameter.
# 10. Intensity of the border ring at midline (measured without physically making ring)
#     -IntensityMean
#     -IntensityMedian
#     -IntensityMax
# 10a. Expanded border for intensity statistics only.  Will NOT affect any other stat.

#NOTE: If Imaris Ring surfaces are not made:
#            FeretDiameterMax = BoundingBoxOOLengthC (for original surface)
#            FeretDiameterMax90 = BoundingBoxOOLengthB (for original surface)

import time
import numpy as np
from scipy.spatial import ConvexHull#, convex_hull_plot_2d
from scipy.spatial.distance import euclidean
from scipy.spatial.distance import cdist
import scipy.ndimage as ndimage
# import matplotlib.pyplot as plt
from operator import itemgetter
import operator
import matplotlib.pyplot as plt
import cv2
from statistics import mean
from statistics import median
# from sklearn.neighbors import NearestNeighbors
# # import networkx as nx
# from skimage.measure import perimeter
# from shapely.geometry import LineString
from itertools import chain

# GUI imports
import tkinter as tk
from tkinter import *
from tkinter import messagebox
from tkinter import simpledialog
from tkinter import ttk
from tkinter.ttk import *

import ImarisLib

aImarisId=0
def XT_MJG_Shape_Analysis9(aImarisId):
    # Create an ImarisLib object
    vImarisLib = ImarisLib.ImarisLib()
    # Get an imaris object with id aImarisId
    vImarisApplication = vImarisLib.GetApplication(aImarisId)
    # Get the factory
    vFactory = vImarisApplication.GetFactory()
    # Get the currently loaded dataset
    vImage = vImarisApplication.GetDataSet()
    # Get the Surpass scene
    vSurpassScene = vImarisApplication.GetSurpassScene()

    ############################################################################
    ############################################################################

    #Dialog window
    ############################################################################
    qInputBox = Tk()
    qInputBox.title('2D Shape Analysis')
    #window.geometry('75x100')
    qInputBox.attributes("-topmost", True)

    ##################################################################
    #Set input in center on screen
    # Gets the requested values of the height and widht.
    windowWidth = qInputBox.winfo_reqwidth()
    windowHeight = qInputBox.winfo_reqheight()
    # Gets both half the screen width/height and window width/height
    positionRight = int(qInputBox.winfo_screenwidth()/2 - windowWidth/2)
    positionDown = int(qInputBox.winfo_screenheight()/2 - windowHeight/2)
    # Positions the window in the center of the page.
    qInputBox.geometry("+{}+{}".format(positionRight, positionDown))
    ##################################################################
    def ShapeAnalysis_Options():
        global vOptionMakeRings, vOptionMeasureIntensity,vOptionExpandBorderIntensity
        global zDilateRing, vOptionMakeDisc
        vOptionMakeRings=var1.get()
        vOptionMeasureIntensity=var2.get()
        vOptionExpandBorderIntensity=var3.get()
        vOptionMakeDisc=var4.get()
        zDilateRing=0
        if vOptionExpandBorderIntensity==1:
            zDilateRing=4
        qInputBox.destroy()

    var1 = tk.IntVar(value=0)
    var2 = tk.IntVar(value=0)
    var3 = tk.IntVar(value=0)
    var4 = tk.IntVar(value=0)

    Label(qInputBox, font="bold", text='Extra Features').grid(row=0,column=0)
    Checkbutton(qInputBox, text='Create midline Surface (Longer Processing Time)',
                    variable=var1, onvalue=1, offvalue=0).grid(row=1, column=0, padx=40,sticky=W)
    Checkbutton(qInputBox, text='Create Disc',
                    variable=var4, onvalue=1, offvalue=0).grid(row=2, column=0, padx=80,sticky=W)
    Checkbutton(qInputBox, text='Calculate Border Intensity at midline',
                    variable=var2, onvalue=1, offvalue=0).grid(row=3, column=0, padx=40,sticky=W)
    Checkbutton(qInputBox, text='Expand Border',
                    variable=var3, onvalue=1, offvalue=0).grid(row=4, column=0, padx=85,sticky=W)

    btn = Button(qInputBox, text="Analyze Surfaces", command=ShapeAnalysis_Options)
    btn.grid(column=0, row=5, sticky=W, padx=100)

    # tk.Label(window, font="bold", text='Statistics Calculated:\n').grid(row=5,column=0,sticky=E)
    # tk.Label(window, text='Perimeter midline, 2DArea cross section (midline)\n'
    #                      'Compactness, Circularity, Convexity\n'
    #                      'Diameter Equivalent Circle\n'
    #                      'Feret Diameter, Chord Max Length\n'
    #                      'Intensity border ring').grid(row=6,column=0,sticky=E)

    qInputBox.mainloop()

    ############################################################################
    ############################################################################
    #testing surface masking
    vDataMin = [vImage.GetExtendMinX(),vImage.GetExtendMinY(),vImage.GetExtendMinZ()]
    vDataMax = [vImage.GetExtendMaxX(),vImage.GetExtendMaxY(),vImage.GetExtendMaxZ()]
    vDataSize = [vImage.GetSizeX(),vImage.GetSizeY(),vImage.GetSizeZ()]
    vSizeT = vImage.GetSizeT()
    vSizeC = vImage.GetSizeC()
    aXvoxelSpacing= (vDataMax[0]-vDataMin[0])/vDataSize[0]
    aYvoxelSpacing= (vDataMax[1]-vDataMin[1])/vDataSize[1]
    aZvoxelSpacing = round((vDataMax[2]-vDataMin[2])/vDataSize[2],3)
    vSmoothingFactor=aXvoxelSpacing*2

    #get all surfaces
    vSurfaces = vFactory.ToSurfaces(vImarisApplication.GetSurpassSelection())
    vNumberOfSurfaces = vSurfaces.GetNumberOfSurfaces()
    if vOptionMakeRings==1:
        vPerimeterRings = vImarisApplication.GetFactory().CreateSurfaces()
        vPerimeterRingsWorking = vImarisApplication.GetFactory().CreateSurfaces()

    #Define slice# and Z position
    vZSlicePosition=[vDataMin[2]]
    for vSliceIndex in range (vDataSize[2]):
        vZSlicePosition.append(vZSlicePosition[vSliceIndex]+aZvoxelSpacing)

    #add additional channel
    if vOptionMeasureIntensity==1 or vOptionMakeRings==1:
        #clone Dataset
        vImarisDataSet = vImage.Clone()

        vImarisDataSet.SetSizeC(vSizeC + 1)
        TotalNumberofChannels=vSizeC+1
        vLastChannel=TotalNumberofChannels-1

    #make Imaris invisible for faster running
    # if vNumberOfSurfaces>100:
    #     vImarisApplication.SetVisible(~vImarisApplication.GetVisible)

    #Generate postions from center of mass
    vPositionFinal=[]
    for SurfaceIndex  in range (vNumberOfSurfaces):
        vPositionFinal.extend(vSurfaces.GetCenterOfMass(SurfaceIndex))

    #############################
    #############################
    vNewStatConvexhullPerimeter=[]
    vNewStatPerimeterBinaryPixels=[]
    vNewStatPerimeterContourVertices=[]
    vNewStat_2D_Perimeter_Contours=[]
    vAllTimeIndices=[]
    vNewStatMaxChordLength=[]
    vNewStatDiameterEquivCircle=[]
    vNewStat_2D_Area_CrossSectionalPixels=[]
    vNewStat_2D_Area_CrossSectionalConvexhull=[]
    vNewStat_2D_Area_Contours=[]
    vNewStatCircularity=[]
    vNewStatCompactness=[]
    vNewStatConvexity=[]
    vNewStatSolidity=[]
    ########################
    ########################

    # Create the master object
    master = tk.Tk()
    # Create a progressbar widget
    progress_bar = ttk.Progressbar(master, orient="horizontal",
                                  mode="determinate", maximum=100, value=0)
    # And a label for it
    label_1 = tk.Label(master, text="2D shape - Progress Bar")
    # Use the grid manager
    label_1.grid(row=0, column=0,pady=10)
    progress_bar.grid(row=0, column=1)
    master.geometry('270x50')
    master.attributes("-topmost", True)

    #################################################################
    #Set input in center on screen
    # Gets the requested values of the height and widht.
    windowWidth = master.winfo_reqwidth()
    windowHeight = master.winfo_reqheight()
    # Gets both half the screen width/height and window width/height
    positionRight = int(master.winfo_screenwidth()/2 - windowWidth/2)
    positionDown = int(master.winfo_screenheight()/2 - windowHeight/2)
    # Positions the window in the center of the page.
    master.geometry("+{}+{}".format(positionRight, positionDown))
    ##################################################################

    # Necessary, as the master object needs to draw the progressbar widget
    # Otherwise, it will not be visible on the screen
    master.update()
    progress_bar['value'] = 0
    master.update()
    zRingIntensityMean=[]
    zRingIntensityMedian=[]
    zRingIntensityMax=[]
    qIsBadSurface=False
    qIsPerimeterFail=False
    wNewStatCount=0
    wNewCountPerimeterFail=0

    ###############################################################################
    ###############################################################################
    vAllSurfaceStatistics = vSurfaces.GetStatistics()
    vSurfacesStatNames = vAllSurfaceStatistics.mNames
    vAllvSurfacesIds = vAllSurfaceStatistics.mIds
    vAllvSurfaceIdsSorted=sorted((e,i) for i,e in enumerate(vAllvSurfacesIds))

    vSurfacesStatValues = vAllSurfaceStatistics.mValues
    vSurfaceVolumeIndex=[i for i,val in enumerate(vSurfacesStatNames)
                                  if val==('Volume')]
    vSurfaceTimeIndex=[i for i,val in enumerate(vSurfacesStatNames)
                                  if val==('Time Index')]
    vSurfaceBoundingBoxAALengthZIndex=[i for i,val in enumerate(vSurfacesStatNames)
                                  if val==('BoundingBoxAA Length Z')]
    if len(vSurfaceVolumeIndex) > 1:
        vSurfacesVolume=list(itemgetter(*vSurfaceVolumeIndex)(vSurfacesStatValues))
        vSurfacesTimeIndex=list(itemgetter(*vSurfaceTimeIndex)(vSurfacesStatValues))
        vSurfacesBoundingBoxAALengthZ=list(itemgetter(*vSurfaceBoundingBoxAALengthZIndex)(vSurfacesStatValues))
    else:
        vSurfacesVolume=[x[1] for x in enumerate(vSurfacesStatValues)
                          if x[0] in vSurfaceVolumeIndex]
        vSurfacesTimeIndex=[x[1] for x in enumerate(vSurfacesStatValues)
                          if x[0] in vSurfaceTimeIndex]
        vSurfacesBoundingBoxAALengthZ=[x[1] for x in enumerate(vSurfacesStatValues)
                          if x[0] in vSurfaceBoundingBoxAALengthZIndex]
    ###############################################################################
    ###############################################################################

    #cycle thru random color mask indices
    for vSurfaceIndex in range (vNumberOfSurfaces):
        vPositionXYZworking = vSurfaces.GetCenterOfMass(vSurfaceIndex)
        vAllTimeIndices.append(vSurfaces.GetTimeIndex(vSurfaceIndex))
    #Find slice closest to the center of mass
        if vDataSize[2]!=1:
            vSliceIndexZ=(list(map(abs, [i-vPositionXYZworking[0][2] for i in vZSlicePosition])))
            vSurfaceIndexMiddle=vSliceIndexZ.index(min(vSliceIndexZ))
        else:
            vSurfaceIndexMiddle=0

        zMaskSingleSurface = vSurfaces.GetSingleMask(vSurfaceIndex,
                                                 vDataMin[0],
                                                 vDataMin[1],
                                                 vDataMin[2],
                                                 vDataMax[0],
                                                 vDataMax[1],
                                                 vDataMax[2],
                                                 vDataSize[0],
                                                 vDataSize[1],
                                                 vDataSize[2])

        #Generate slice to find center of Surfacemask
        if vSurfaceIndexMiddle!=0:
            vSlice = zMaskSingleSurface.GetDataSliceShorts(vSurfaceIndexMiddle-1, 0, 0)
        else:
            vSlice = zMaskSingleSurface.GetDataSliceShorts(vSurfaceIndexMiddle, 0, 0)

        zMaskTest=int(np.amax(vSlice))#Maybe better to have an any

        if zMaskTest==0:
            qIsBadSurface=True
            if vOptionMeasureIntensity==1:
                zRingIntensityMean.append(999994)
                zRingIntensityMax.append(999994)
                zRingIntensityMedian.append(999994)
        #pad stat result for "bad surface masking"
            vNewStatConvexhullPerimeter.append(999994)
            vNewStatPerimeterContourVertices.append(999994)
            vNewStat_2D_Area_CrossSectionalPixels.append(999994)
            vNewStatDiameterEquivCircle.append(999994)
            vNewStat_2D_Area_CrossSectionalConvexhull.append(999994)
            vNewStat_2D_Area_Contours.append(999994)
            vNewStatCircularity.append(999994)
            vNewStatCompactness.append(999994)
            vNewStatConvexity.append(999994)
            vNewStatSolidity.append(999994)
            # vNewStatMaxChordLength.append(999994)

            continue

        vSliceNumpy=np.array(vSlice)
        vSliceNumpyNew=ndimage.binary_fill_holes(vSliceNumpy).astype(float)
        #vSliceNumpyNew=ndimage.grey_erosion(vSliceNumpyNew, size=(2,1))

        if vOptionMeasureIntensity==1 or vOptionMakeRings==1:
        ## find the non-zero min-max coords of canny

            pts = np.argwhere(vSliceNumpy>0)
            y1,x1 = pts.min(axis=0)
            y2,x2 = pts.max(axis=0)

            #test if mask share edge of slice border
            y1CropAdj=0
            y2CropAdj=0
            x1CropAdj=0
            x2CropAdj=0

            if y1==0:
                y1CropAdj=2+zDilateRing
            if y2==0:
                y2CropAdj=2+zDilateRing
            if x1==0:
                x1CropAdj=2+zDilateRing
            if x2==0:
                x2CropAdj=2+zDilateRing

            ## crop the region
            vSlicecropped = vSliceNumpy[y1-2+y1CropAdj-zDilateRing:y2+2-y2CropAdj+zDilateRing,
                                        x1-2+x1CropAdj-zDilateRing:x2+2-x2CropAdj+zDilateRing]
            vSlicecropped=ndimage.binary_fill_holes(vSlicecropped).astype(float)
            vSlicecropped=ndimage.grey_erosion(vSlicecropped, size=(2,1))

            #Run Variance filter thru scipy to find edges of the binary
            if vOptionExpandBorderIntensity==1:
                vVarianceFilterResult=ndimage.filters.generic_filter(vSlicecropped, np.std, size=4)
            else:
                vVarianceFilterResult=ndimage.filters.generic_filter(vSlicecropped, np.std, size=2)

            # imshow(vSlicecroppedfilled)
            # imshow(vSlicecropped)
            # imshow(vSlice)
            # plt.imshow(vSliceNumpy)
            # imshow(vVarianceFilterResult)
            # imshow(vSlicecroppedDenoised)

        ############################################################################
            #need to set the ring back into the original frame
            zNumColRight=(vSliceNumpy.shape[1]-x2-2-zDilateRing) # 12 col on right
            zNumColLeft=x1-2-zDilateRing    #110 col left
            zNumRowBottom=(vSliceNumpy.shape[0]-y2-2-zDilateRing) #30 row bottom
            zNumRowTop=y1-2-zDilateRing   #46 row top
            #add columns to right - test if on border
            if zNumColRight>0:
                vVarianceFilterResult = np.column_stack( [ vVarianceFilterResult , [[0]*zNumColRight]*vVarianceFilterResult.shape[0] ] )
            #add columns to left - test if on border
            if zNumColLeft>0:
                vVarianceFilterResult = np.column_stack( [ [[0]*zNumColLeft]*vVarianceFilterResult.shape[0], vVarianceFilterResult ] )
            #add rows to bottom - test if on border
            if zNumRowBottom>0:
                vVarianceFilterResult = np.row_stack( [ vVarianceFilterResult , [[0]*vSliceNumpy.shape[1]]*zNumRowBottom ] )
            #add rows to top - test if on border
            if zNumRowTop >0:
                vVarianceFilterResult = np.row_stack( [ [[0]*vSliceNumpy.shape[1]]*zNumRowTop, vVarianceFilterResult ] )

    ############################################################################
    #Get intensity in all channels for new Statistic
        if vOptionMeasureIntensity==1:
            for cIndex in range (vSizeC):
                if vSurfaceIndexMiddle!=0:
                    vSlice = vImarisDataSet.GetDataSliceFloats(vSurfaceIndexMiddle-1,cIndex,vAllTimeIndices[vSurfaceIndex])
                else:
                    vSlice = vImarisDataSet.GetDataSliceFloats(vSurfaceIndexMiddle,cIndex,vAllTimeIndices[vSurfaceIndex])
        #Compare vSlice and ring slice - find all values insvSlice that are in ring
                zRingIntensityMean.append(mean(np.array(vSlice)[vVarianceFilterResult > 0]))
                zRingIntensityMax.append(max(np.array(vSlice)[vVarianceFilterResult > 0]))
                zRingIntensityMedian.append(median(np.array(vSlice)[vVarianceFilterResult > 0]))

    ############################################################################
    #Set Donut FinalMask (donut) to new channel
        if vOptionMakeRings==1:
            if vOptionMakeDisc==1:
                vSliceNumpy[vSliceNumpy==1]=10
                if vSurfaceIndexMiddle!=0:
                    vImarisDataSet.SetDataSliceFloats(vSliceNumpy.tolist(),vSurfaceIndexMiddle-1,vSizeC,vAllTimeIndices[vSurfaceIndex])
                else:
                    vImarisDataSet.SetDataSliceFloats(vSliceNumpy.tolist(),vSurfaceIndexMiddle,vSizeC,vAllTimeIndices[vSurfaceIndex])
            else:
                vVarianceFilterResult[vVarianceFilterResult > 0] = 10#if pixel >0 set to 0 numpy solution
                vFinalMaskToList = vVarianceFilterResult.tolist()
                if vSurfaceIndexMiddle!=0:
                    vImarisDataSet.SetDataSliceFloats(vFinalMaskToList,vSurfaceIndexMiddle-1,vSizeC,vAllTimeIndices[vSurfaceIndex])
                else:
                    vImarisDataSet.SetDataSliceFloats(vFinalMaskToList,vSurfaceIndexMiddle,vSizeC,vAllTimeIndices[vSurfaceIndex])

    #############################################
        vSliceNumpyNew = vSliceNumpyNew.astype('float')
    ###############################################
    #Calculate contour perimeter and convexhull
        ret,zThresh = cv2.threshold(vSliceNumpyNew.astype(float),0,255,0)
        zThreshBinary=zThresh.astype(np.uint8)
        zContours,zHierarchy = cv2.findContours(zThreshBinary, 1, 2)
    # # create hull array for convex hull vertices
        hull = []
        # calculate points for each contour
        for i in range(0,len(zContours)):
            vertices = zContours[i]
        hull=cv2.convexHull(vertices)
    #Calculate ChordLength
        for i in range (len(vertices)):
            vertices[i]
    ##Find MaxChord Length
        if len(vertices)>2:
            #Calculate Max chord length from contour vertices
            wContourVertices = list(chain.from_iterable(vertices.tolist()))
            vDistanceArray=cdist(wContourVertices,wContourVertices)
            # vNewStatMaxChordLength.append(np.max(vDistanceArray)*aXvoxelSpacing)
    ###############################################
        #Calculated 2D Cross section area from contour vertices
            vNewStat_2D_Area_Contours.append(cv2.contourArea(vertices)*aXvoxelSpacing*aYvoxelSpacing)
        #Find 2D area of convex hull
            vNewStat_2D_Area_CrossSectionalConvexhull.append(cv2.contourArea(hull)*aXvoxelSpacing*aYvoxelSpacing)
    ##############################################
        #Calcualte Perimeters
        #Contour perimeter
            vNewStatPerimeterContourVertices.append(cv2.arcLength(vertices,True)*aXvoxelSpacing)
        #Convexhull perimeter
            vNewStatConvexhullPerimeter.append(cv2.arcLength(hull,True)*aXvoxelSpacing)
        #Find diameter from equivalent circle
            vNewStatDiameterEquivCircle.append(vNewStatPerimeterContourVertices[vSurfaceIndex]/3.1415926)
        #Compactness (alternative)
        #Objects which have an elliptical shape, or a boundary that is irregular rather than smooth, will decrease the measure.
            vNewStatCompactness.append(4*3.1415926*vNewStat_2D_Area_Contours[vSurfaceIndex]/(vNewStatPerimeterContourVertices[vSurfaceIndex]**2))
        #Convexity is the relative amount that an object differs from a convex object
            vNewStatConvexity.append(vNewStatConvexhullPerimeter[vSurfaceIndex]/vNewStatPerimeterContourVertices[vSurfaceIndex])
        #Circularity= 4*pi*Area/(P*P) Excludes local irregularities
            vNewStatCircularity.append(4*3.1415926*vNewStat_2D_Area_Contours[vSurfaceIndex]/(vNewStatConvexhullPerimeter[vSurfaceIndex]**2))
        # #Solidity -- Solidity is the ratio of contour area to its convex hull area.
            vNewStatSolidity.append(vNewStat_2D_Area_Contours[vSurfaceIndex]/vNewStat_2D_Area_CrossSectionalConvexhull[vSurfaceIndex])

        else:
            qIsPerimeterFail=True
            # vNewStatMaxChordLength.append(9999944)
            vNewStat_2D_Area_Contours.append(9999944)
            vNewStat_2D_Area_CrossSectionalConvexhull.append(9999944)
            vNewStatPerimeterContourVertices.append(9999944)
            vNewStatConvexhullPerimeter.append(9999944)
            vNewStatDiameterEquivCircle.append(9999944)
            vNewStatCompactness.append(9999944)
            vNewStatConvexity.append(9999944)
            vNewStatCircularity.append(9999944)
            vNewStatSolidity.append(9999944)
            wNewCountPerimeterFail=wNewCountPerimeterFail+1
    #############################################
        #Quantify cross-sectional area by voxel count
        vNewStat_2D_Area_CrossSectionalPixels.append(np.count_nonzero(vSliceNumpyNew)*aXvoxelSpacing*aYvoxelSpacing)
    ###############################################################################
    #Set Donut result to new channel - If the option is checked
        if vOptionMakeRings==1:
            ip = vImarisApplication.GetImageProcessing()
        #Make single Surface from Donut channel using ROI. no smoothing
            vLowerThreshold=10
            vPerimeterRingsWorking = ip.DetectSurfacesWithUpperThreshold(vImarisDataSet,
                                                                    [[0,
                                                                      0,
                                                                      vSurfaceIndexMiddle-1,
                                                                      vAllTimeIndices[vSurfaceIndex],
                                                                      vDataSize[0],
                                                                      vDataSize[1],
                                                                      vSurfaceIndexMiddle-1,
                                                                      vAllTimeIndices[vSurfaceIndex]]],
                                                                      vLastChannel, 0, 0, True,False,
                                                                      vLowerThreshold-0.5,True, False,
                                                                      vLowerThreshold,'')
            if vPerimeterRingsWorking.GetNumberOfSurfaces()==1:
                vNewPerimeterRingsIndex=[0]
                vPerimeterRingsWorking.CopySurfacesToSurfaces(vNewPerimeterRingsIndex, vPerimeterRings)

        progress_bar['value'] = int((vSurfaceIndex+1)/vNumberOfSurfaces*100)
        master.update()
    master.destroy()
    master.mainloop()

    #Create a new folder object for new ring surfaces - if option is checked
    if vOptionMakeRings==1:
        result = vFactory.CreateDataContainer()
        result.SetName('2D Shape Analysis -- ' + vSurfaces.GetName())
        vPerimeterRings.SetName('2D Perimeter Rings -- ' + vSurfaces.GetName())
        result.AddChild(vPerimeterRings, -1)
        vImarisApplication.GetSurpassScene().AddChild(result, -1)

    ####################################################
    vAllvSurfacesStatistics = vSurfaces.GetStatistics()
    vAllvSurfacesIds = vAllvSurfacesStatistics.mIds
    vAllvSurfaceIdsSorted=sorted((e,i) for i,e in enumerate(vAllvSurfacesIds))
    vAllvSurfacesStatNames = vAllvSurfacesStatistics.mNames
    vAllvSurfacesStatValues = vAllvSurfacesStatistics.mValues
    vNewStatFeretDiameterMaxIndex=[i for i,val in enumerate(vAllvSurfacesStatNames)
                                   if val==('BoundingBoxOO Length C')]
    vNewStatFeretDiameterMax90Index=[i for i,val in enumerate(vAllvSurfacesStatNames)
                                   if val==('BoundingBoxOO Length B')]
    if len(vNewStatFeretDiameterMaxIndex)>1:
        vNewStatFeretDiameterMax=list(itemgetter(*vNewStatFeretDiameterMaxIndex)(vAllvSurfacesStatValues))
        vNewStatFeretDiameterMax90=list(itemgetter(*vNewStatFeretDiameterMax90Index)(vAllvSurfacesStatValues))
    else:
        vNewStatFeretDiameterMax=[x[1] for x in enumerate(vAllvSurfacesStatValues)
                          if x[0] in vNewStatFeretDiameterMaxIndex]
        vNewStatFeretDiameterMax90=[x[1] for x in enumerate(vAllvSurfacesStatValues)
                          if x[0] in vNewStatFeretDiameterMax90Index]

    ####################################################
    #Remove the working channels
    if vOptionMeasureIntensity==1 or vOptionMakeRings==1:
        vImarisApplication.GetDataSet().SetSizeC(vSizeC)

    ####################################################
    ####################################################
    #Generate Surface stat
        #number of new stat values
    vNumberOfNewStats=len(vNewStatConvexhullPerimeter)

    ####################################################
    vSurfaceStatvIds=list(range(vNumberOfNewStats))
    vSurfaceIDs=vSurfaces.GetIds()

    if qIsBadSurface==True:
        #Find index of marked stat values "999994"
        vBadSurfaceIdIndex=[i for i,val in enumerate(vNewStatConvexhullPerimeter)
                                    if val==999994]
        #remove bad surfaceIds and stats
        for ele in sorted(vBadSurfaceIdIndex, reverse = True):
            del vSurfaceStatvIds[ele]
            del vSurfaceIDs[ele]
            if vOptionMeasureIntensity==1:
                del zRingIntensityMean[ele]
                del zRingIntensityMax[ele]
                del zRingIntensityMedian[ele]
            del vNewStatConvexhullPerimeter[ele]
            del vNewStatPerimeterContourVertices[ele]
            del vNewStat_2D_Area_CrossSectionalPixels[ele]
            del vNewStatDiameterEquivCircle[ele]
            del vNewStat_2D_Area_CrossSectionalConvexhull[ele]
            del vNewStat_2D_Area_Contours[ele]
            del vNewStatCircularity[ele]
            del vNewStatCompactness[ele]
            del vNewStatConvexity[ele]
            del vNewStatSolidity[ele]
            del vNewStatFeretDiameterMax90[ele]
            del vNewStatFeretDiameterMax[ele]
            # del vNewStatMaxChordLength[ele]

        vNumberOfNewStats=len(vNewStatConvexhullPerimeter)

    if qIsPerimeterFail==True:
        vSurfaceIDsPerimeterFail=vSurfaceIDs
        vSurfaceIDsPerimeterFailRing=vSurfaceStatvIds
        #Find index of marked stat values "999994"
        vBadSurfaceIdPerimeterFailIndex=[i for i,val in enumerate(vNewStatPerimeterContourVertices)
                                    if val==9999944]
        for ele in sorted(vBadSurfaceIdPerimeterFailIndex, reverse = True):
            del vNewStatPerimeterContourVertices[ele]
            del vNewStatDiameterEquivCircle[ele]
            del vNewStatCompactness[ele]
            del vNewStatConvexity[ele]
            del vNewStatSolidity[ele]
            # del vNewStatMaxChordLength[ele]
            del vNewStat_2D_Area_Contours[ele]
            del vNewStat_2D_Area_CrossSectionalConvexhull[ele]
            del vNewStatConvexhullPerimeter[ele]
            del vNewStatCircularity[ele]
            del vNewStatFeretDiameterMax90[ele]
            del vNewStatFeretDiameterMax[ele]
            del vNewStat_2D_Area_CrossSectionalPixels[ele]

            del vSurfaceIDsPerimeterFail[ele]
            del vSurfaceIDsPerimeterFailRing[ele]
        vNumberOfNewStatsPerimeterFail=len(vNewStatPerimeterContourVertices)
        vNumberOfNewStats=len(vNewStatPerimeterContourVertices)
    ####################################################
    vSurfaceStatUnits=['um']*vNumberOfNewStats
    #Create Tuple list for each surface in time
    vSurfaceStatFactors=(['Surface']*vNumberOfNewStats,
                          [str(e) for e in [i+1 for i in vAllTimeIndices]])
    vSurfaceStatFactorName=['Category','Time']
    ####################################################
    if vOptionMakeRings==1:

        vSurfaceStatNames=[' Feret Diameter Max']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatFeretDiameterMax,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Ferret Diameter Max90']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatFeretDiameterMax90,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Area2D (from #pixels']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_CrossSectionalPixels,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)

    ####################################################
        if  qIsPerimeterFail==True:
            vSurfaceStatvIds=vSurfaceIDsPerimeterFailRing
            vNumberOfNewStats=vNumberOfNewStatsPerimeterFail
            vSurfaceStatFactors=(['Surface']*vNumberOfNewStatsPerimeterFail,
                              [str(e) for e in [i+1 for i in vAllTimeIndices]])
            vSurfaceStatUnits=['um']*vNumberOfNewStatsPerimeterFail
    ####################################################
    ####################################################
        vSurfaceStatNames=[' Border Perimeter midline - Contour vertices']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatPerimeterContourVertices,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Diameter of Equvialent Circle']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatDiameterEquivCircle,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Compactness']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatCompactness,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
        ####################################################
        vSurfaceStatNames=[' Convexity']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatConvexity,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Circularity']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatCircularity,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Solidity']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatSolidity,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)

    ####################################################
        vSurfaceStatNames=[' Border Perimeter midline (convexhull)']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatConvexhullPerimeter,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
    # ####################################################
    #     vSurfaceStatNames=[' Chord Max length']*vNumberOfNewStats
    #     vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStatMaxChordLength,
    #                                   vSurfaceStatUnits, vSurfaceStatFactors,
    #                                   vSurfaceStatFactorName, vSurfaceStatvIds)
    ####################################################
        vSurfaceStatNames=[' Area2D (contour)']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_Contours,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)
        ####################################################
        vSurfaceStatNames=[' Area2D (Convexhull)']*vNumberOfNewStats
        vPerimeterRings.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_CrossSectionalConvexhull,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceStatvIds)

    ####################################################
    ####################################################
    ####################################################
    ####################################################
    vSurfaceStatUnits=['um']*vNumberOfNewStats
    #Create Tuple list for each surface in time
    vSurfaceStatFactors=(['Surface']*vNumberOfNewStats,
                          [str(e) for e in [i+1 for i in vAllTimeIndices]])
    ###############################################
    ####################################################
    vSurfaceStatNames=[' Area 2D #Pixels']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_CrossSectionalPixels,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Feret Diameter Max']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatFeretDiameterMax,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Ferret Diameter Max90']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatFeretDiameterMax90,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceIDs)
    #######################################################
    #Intensity of the ring
    if vOptionMeasureIntensity==1:
        zCompleteRingIntensityMean = [[] for _ in range(vSizeC)]
        for index, item in enumerate(zRingIntensityMean):
            zCompleteRingIntensityMean[index % vSizeC].append(item)
        zCompleteRingIntensityMedian = [[] for _ in range(vSizeC)]
        for index, item in enumerate(zRingIntensityMedian):
            zCompleteRingIntensityMedian[index % vSizeC].append(item)
        zCompleteRingIntensityMax = [[] for _ in range(vSizeC)]
        for index, item in enumerate(zRingIntensityMax):
            zCompleteRingIntensityMax[index % vSizeC].append(item)

        for c in range (vSizeC):
            vSurfaceStatNames=[' IntensityMean Cell_Border ch' + str(c+1)]*vNumberOfNewStats
            vSurfaces.AddStatistics(vSurfaceStatNames, zCompleteRingIntensityMean[c],
                                          vSurfaceStatUnits, vSurfaceStatFactors,
                                          vSurfaceStatFactorName, vSurfaceIDs)
            vSurfaceStatNames=[' IntensityMedian Cell_Border ch' + str(c+1)]*vNumberOfNewStats
            vSurfaces.AddStatistics(vSurfaceStatNames, zCompleteRingIntensityMedian[c],
                                          vSurfaceStatUnits, vSurfaceStatFactors,
                                          vSurfaceStatFactorName, vSurfaceIDs)
            vSurfaceStatNames=[' IntensityMax Cell_Border ch' + str(c+1)]*vNumberOfNewStats
            vSurfaces.AddStatistics(vSurfaceStatNames, zCompleteRingIntensityMax[c],
                                          vSurfaceStatUnits, vSurfaceStatFactors,
                                          vSurfaceStatFactorName, vSurfaceIDs)
    ###########################################################
    ####################################################
    if  qIsPerimeterFail==True:
        vSurfaceIDs=vSurfaceIDsPerimeterFail
        vSurfaceStatFactors=(['Surface']*vNumberOfNewStatsPerimeterFail,
                              [str(e) for e in [i+1 for i in vAllTimeIndices]])
        vSurfaceStatUnits=['um']*vNumberOfNewStatsPerimeterFail
    ####################################################
    vSurfaceStatNames=[' Border Perimeter midline - Contour vertices']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatPerimeterContourVertices,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Diameter of Equvialent Circle']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatDiameterEquivCircle,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Compactness']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatCompactness,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Convexity']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatConvexity,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Border Perimeter midline (convexhull)']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatConvexhullPerimeter,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    # ####################################################
    # vSurfaceStatNames=[' Chord Max length']*vNumberOfNewStats
    # vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatMaxChordLength,
    #                               vSurfaceStatUnits, vSurfaceStatFactors,
    #                               vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Area 2D (contour)']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_Contours,
                                      vSurfaceStatUnits, vSurfaceStatFactors,
                                      vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Area 2D - ConvexHull']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStat_2D_Area_CrossSectionalConvexhull,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Circularity']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatCircularity,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)
    ####################################################
    vSurfaceStatNames=[' Solidity']*vNumberOfNewStats
    vSurfaces.AddStatistics(vSurfaceStatNames, vNewStatSolidity,
                                  vSurfaceStatUnits, vSurfaceStatFactors,
                                  vSurfaceStatFactorName, vSurfaceIDs)

    vSurfaces.SetName(vSurfaces.GetName()+' - 2D Shape Analyzed')
    vImarisApplication.GetSurpassScene().AddChild(vSurfaces, -1)

    if qIsBadSurface==True:
        myError = tk.Tk()
        messagebox.showerror(title='2D shape Analysis ',
                              message='Z voxel scaling is not great\n'
                                  'Some surfaces can not be masked!\n'
                                  'They will not have statistics!')
    #######################################################
        myError.destroy()


'''

  There are a total of n levels to be played in the Game.
  A player can unlock a level by first completing easier levels.
  completing one level will unlock other level(s).
  The map of level complexity is defined as [B, A],
  where you need to finish level A to play level 1 8 Given hierarchy of game levels and a level X,
  output which all levels you need to finish to unlock level X. e

  For example:

  I)
  [Y,X]
  There are a total of 2 levels to complete.
  1) To choose level Y you should have finished level X.
  2) So the correct sequence order is[X,Y]

  II)
  [[X,W],[Y,W],[Z,X],[Z,Y],[U,V],[V,T]]
  There are a total of 4 levels to complete.
  To play level Z you should have finished both levels X and Y.
  Both levels X and Y should be completed after you finished level W.
  So one correct course order is [W,X,Y,Z]

  Please give hit to solve this question

        W     T
       / \    |
      X   Y   V
       \ /    |
        Z     U
'''

from collections import Counter, defaultdict, deque

class Solution:
  def unlockLevels(self, prereqs, target):
    childToParents, parentToChildren, inDegrees, sources = self.buildGraphs(prereqs)
    relevantCourses = self.getRelevantCourses(childToParents, target)

    queue = deque(list(sources))
    pathToTarget = []

    while queue:
      node = queue.popleft()
      if node not in relevantCourses: continue
      pathToTarget.append(node)

      for child in parentToChildren[node]:
        inDegrees[child] -= 1
        if inDegrees[child] == 0:
          queue.append(child)

    return pathToTarget

  def getRelevantCourses(self, graph, target):
    relevantCourses = set()
    queue = deque([target])

    while queue:
      node = queue.popleft()
      relevantCourses.add(node)
      for parent in graph[node]:
        queue.append(parent)

    return relevantCourses

  def buildGraphs(self, prereqs):
    childToParents = defaultdict(list)
    parentToChildren = defaultdict(list)
    inDegrees = defaultdict(int)
    sources = set()

    for child, parent in prereqs:
      childToParents[child].append(parent)
      parentToChildren[parent].append(child)
      inDegrees[child] += 1
      sources.add(parent)

    for parent in list(sources):
      if parent in childToParents: sources.discard(parent)

    return childToParents, parentToChildren, inDegrees, sources

def runSolution():
  solution = Solution()
  print(solution.unlockLevels([
    ['Y','X']
  ], 'Y'))
  print(solution.unlockLevels([
    ['X','W'],['Y','W'],['Z','X'],['Z','Y'],['U','V'],['V','T']
  ], 'Z'))
  pass
runSolution()


import requests
import sys
import argparse
import os
from dotenv import dotenv_values
import datetime

config = dotenv_values(".env")
current_year = datetime.datetime.now().year

def create_directory_structure(day, year):
    if not os.path.exists(f'{year}'):
        os.mkdir(str(year))
    path = f"{year}/Day{day}/"
    try:
        os.mkdir(path)
    except FileExistsError:
        pass

def create_standard_example_file(day, year):
    path = f"{year}/Day{day}/example.txt"
    with open(path, "w") as f:
        f.write("")

def copy_boilerplate_py_file(day, name, year):
    path = f"{year}/Day{day}/{name}.py"
    os.system(f"cp boilerplate.py {path}")

def save_data(day, year):
    out_path = f"{year}/Day{day}/input.txt"
    url = f"https://adventofcode.com/{year}/day/{day}/input"
    id = config.get("MY_ID")
    if not id:
        print("MY_ID not found in .env file")
        sys.exit(1)
    cookies = {"session": id}
    r = requests.get(url, cookies=cookies)
    with open(out_path, "w") as f:
        f.write(r.text)

def create_template_files(day, name, year):
    create_directory_structure(day, year)
    create_standard_example_file(day, year)
    copy_boilerplate_py_file(day, name, year)
    save_data(day, year)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        prog="setup.py",
        description="Create new folder and template files for a new day of Advent of Code",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument("day", type=int, help="Advent of Code day")
    parser.add_argument("-y", "--year", type=int, default=current_year,
                        help="Year of the challenge")
    parser.add_argument("-n", "--name", type=str, default='day_{day}', help="Name of the file")
    args = parser.parse_args()

    if args.name == "day_{day}":
        args.name = f"day_{args.day}"

    create_template_files(args.day, args.name, args.year)


import torch
import numpy as np

def with_tensor(func):
    def wrapper(*args, **kwargs):
        found_tensor = False

        new_args = []
        for arg in args:
            if isinstance(arg, np.ndarray):
                t = torch.tensor(arg)
                if arg.dtype in [np.float32, np.float64]:
                    t = t.float()
                new_args.append(t)
            elif isinstance(arg, torch.Tensor):
                found_tensor = True
                new_args.append(arg)
            else:
                new_args.append(arg)

        new_kwargs = {}
        for key, value in kwargs.items():
            if isinstance(value, np.ndarray):
                t = torch.tensor(value)
                if arg.dtype in [np.float32, np.float64]:
                    t = t.float()
                new_kwargs[key] = t
            elif isinstance(value, torch.Tensor):
                found_tensor = True
                new_kwargs[key] = value
            else:
                new_kwargs[key] = value

        out = func(*new_args, **new_kwargs)

        # if at least one torch.tensor is in the input,
        # return the output in torch.tensor type
        if found_tensor:
            return out

        # if there are no tensors in the input but are only numpy arrays,
        # convert all tensors to numpy arrays
        if isinstance(out, tuple):
            return tuple([x.numpy() if isinstance(x, torch.Tensor) else x for x in out])
        elif isinstance(out, list):
            return list([x.numpy() if isinstance(x, torch.Tensor) else x for x in out])
        elif isinstance(out, dict):
            return {k: v.numpy() if isinstance(v, torch.Tensor) else v for k, v in out.items()}
        elif isinstance(out, torch.Tensor):
            return out.numpy()

    return wrapper


from dataclasses import dataclass, field
from enum import Enum
from aws_lambda_powertools import Logger

logger = Logger()

class ApiEventTypes(Enum):
    CORS_PREFLIGHT = "OPTIONS"
    GET = "GET"
    POST = "POST"

class ApiEvent(Enum):
    A_WEEK = "week"
    THIS_WEEK = "thisweek"
    LAST_WEEK = "last_week"

@dataclass
class IncomingEvent:
    event: dict
    path: str = field(init=False)
    api_event: ApiEvent = field(init=False)
    week_number: int = field(init=False)
    total_payout: float = field(init=False)
    path_parts: list = field(init=False)
    IS_OPTIONS: bool = field(init=False)
    IS_GET: bool = field(init=False)
    IS_POST: bool = field(init=False)
    api_type: str = field(init=False)

    def __post_init__(self):
        self.path = self.event.get("resource", "")
        self.determine_event()
        self.determine_type()
        self._is_allowed_method()

    def determine_event(self):

        dispatch = {
            "/v1/week/{proxy+}": ApiEvent.A_WEEK,
            "/v1/lastweek/{proxy+}": ApiEvent.LAST_WEEK,
            "/v1/thisweek/{proxy+}": ApiEvent.THIS_WEEK,
            "/v1/lastweek": ApiEvent.LAST_WEEK,
            "/v1/thisweek": ApiEvent.THIS_WEEK
        }

        self.api_event = dispatch[self.path]

        self._parse_path_parts()

    def determine_type(self):
        """
        determines the type of the api call
        """

        self.method = self.event.get("httpMethod")

        self.IS_GET = self.method == ApiEventTypes.GET.value
        self.IS_OPTIONS = self.method == ApiEventTypes.CORS_PREFLIGHT.value
        self.IS_POST = self.method == ApiEventTypes.POST.value

    def _is_allowed_method(self):
        """
        GetEntity only allows Options and Get method calls.
        """
        if self.IS_OPTIONS:
            self.api_type = ApiEventTypes.CORS_PREFLIGHT

        elif self.IS_GET:
            self.api_type = ApiEventTypes.GET

        else:
            raise TypeError("Not a valid Type for GetEntity - must be GET or OPTIONS")

    def _parse_path_parts(self):
        logger.info(f"Parsing Proxy paths {self.event}")
        try:
            self.path_parts = self.event.get("pathParameters", {}).get("proxy", "")

            parts = self.path_parts.split("/")
            number_of_proxy_paths = len(parts)
            self.total_payout = 0

            if self.api_event == ApiEvent.A_WEEK:
                self.week_number = parts[0]
                if number_of_proxy_paths == 2:
                    self.total_payout=parts[1]

            elif number_of_proxy_paths == 1:
                self.total_payout = parts[0]

        except:
            logger.info("No Proxy parameters found)")
            self.path_parts = ""
            self.total_payout = 0
            self.week_number = self.api_event.value


import os.path
import unittest

import hydra
import pkg_resources
import torch
from hydra import initialize_config_dir, compose
from hydra.core.global_hydra import GlobalHydra

from super_gradients.training.models.detection_models.csp_resnet import CSPResNet
from super_gradients.training.utils.hydra_utils import normalize_path

class PPYoloETests(unittest.TestCase):
    def get_model_arch_params(self, config_name):
        GlobalHydra.instance().clear()
        sg_recipes_dir = pkg_resources.resource_filename("super_gradients.recipes", "")
        with initialize_config_dir(config_dir=normalize_path(sg_recipes_dir), version_base="1.2"):
            cfg = compose(config_name=normalize_path(config_name))
            cfg = hydra.utils.instantiate(cfg)
            arch_params = cfg.arch_params

        return arch_params

    def _test_csp_resnet_variant(self, variant):
        arch_params = self.get_model_arch_params(os.path.join("arch_params", variant))

        ppyoloe = CSPResNet(**arch_params)
        dummy_input = torch.randn(1, 3, 320, 320)
        with torch.no_grad():
            feature_maps = ppyoloe(dummy_input)
            self.assertEqual(len(feature_maps), 3)

    def test_csp_resnet_s(self):
        self._test_csp_resnet_variant("csp_resnet_l_arch_params")

    def test_csp_resnet_m(self):
        self._test_csp_resnet_variant("csp_resnet_m_arch_params")

    def test_csp_resnet_l(self):
        self._test_csp_resnet_variant("csp_resnet_l_arch_params")

    def test_csp_resnet_x(self):
        self._test_csp_resnet_variant("csp_resnet_x_arch_params")

if __name__ == "__main__":
    unittest.main()


#!/usr/bin/env python3

import re
import shutil

import requests
from bs4 import BeautifulSoup

base_url = "https://downloads.openwrt.org/"

def get_rel(url, version):
    res = requests.get(url)
    if not res.status_code == 200:
        return
    c = res.content
    soup = BeautifulSoup(c, "lxml")
    links = soup.find_all("a")
    for l in links:
        filename = l.string.strip()
        if not re.search('combined-ext4.img.gz', filename):
            #print("ignoring {}".format(filename))
            continue
        if re.search('[^0-9][0-9]{2}\.[0-9]{2}[^0-9]', filename):
            local_filename = filename
        else:
            local_filename = re.sub('^openwrt-', 'openwrt-{}-'.format(version), filename)
        file_url = "{}{}".format(url, filename)
        print("Downloading {} -> {}".format(file_url, local_filename))
        r = requests.get(file_url, stream=True)
        with open(local_filename, 'wb') as f:
            shutil.copyfileobj(r.raw, f)

def main():
    res = requests.get("https://downloads.openwrt.org/")
    if not res.status_code == 200:
        return
    c = res.content
    soup = BeautifulSoup(c, "lxml")
    links = soup.find_all("a")
    for l in links:
        rel_url = "{}{}x86/kvm_guest/".format(base_url, l.attrs['href'])
        m = re.search('[^0-9]([0-9]{2}\.[0-9]{2})[^0-9]', l.attrs['href'])
        if not m:
            continue
        print(l.string.strip(), l.attrs['href'], rel_url)
        get_rel(rel_url, m.group(1))

main()


#thanks:
# https://medium.freecodecamp.org/how-to-build-a-web-application-using-flask-and-deploy-it-to-the-cloud-3551c985e492
#https://towardsdatascience.com/develop-a-nlp-model-in-python-deploy-it-with-flask-step-by-step-744f3bdd7776
#https://medium.com/datadriveninvestor/deploy-your-pytorch-model-to-production-f69460192217?FGa=true
# http://flask.pocoo.org/docs/1.0/patterns/fileuploads/#uploading-files

import os
from pathlib import Path

"""importing AI App"""
from check_flower import check_flower

"""importing the Flask module
and creating a Flask web server from the Flask module"""
from flask import Flask, render_template, url_for, request, flash, redirect, send_from_directory

"""there is that principle called “never trust user input”.
This is also true for the filename of an uploaded file.
All submitted form data can be forged, and filenames can be dangerous."""
from werkzeug.utils import secure_filename

"""The UPLOAD_FOLDER is where we will store the uploaded files"""
UPLOAD_FOLDER = Path('C:/Users/Justyna/GIT/flask/uploads/')

"""The ALLOWED_EXTENSIONS is the set of allowed file extensions"""
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

""" creating an instance of the Flask class and calling it app.
Here we are creating a new web application. __name__  means
this current file. In this case, it is main.py.
This current file will represent a web application"""
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
    return '.' in filename and \
            filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

"""It represents the default page. For example,
if we go to a website such as “google.com/” with nothing after
the slash. Then this will be the default page of Google."""
@app.route("/")
#If the user goes to my website and they go to the default page (nothing after the slash), then this function will get activated:
def home():
    return render_template("home.html")

@app.route("/about")
def about():
    return render_template("about.html")

@app.route("/result",methods=['GET', 'POST'])
def result():
    if request.method == 'POST':
        #checks if the post request has the file part
        if 'image' not in request.files:
            flash('No file part')
            return redirect(request.url)
        image = request.files['image'] #.get('image')
        #if user does not select file, browser also
        #submit an empty part without filename
        if image.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if image and allowed_file(image.filename):
            imagename = secure_filename(image.filename) #always we need to use that
                                             #function to secure a filename before storing it
                                             # directly on the filesystem.
            image.save(os.path.join(app.config['UPLOAD_FOLDER'], imagename)) #uses
                #the save() method of the file to save the file permanently on the
                #file system

            # image_path = redirect(url_for('uploaded_file', filename=imagename))
            fname, fprob, fclass = check_flower(image)
            fname = fname.upper()
            fprob = '{:05.2f}'.format(fprob * 100)
            fclass = str(fclass)
            ipath = 'images/'+fclass+'/image.jpg'
            # ipath = 'images/11/image.jpg'

            return render_template('result.html', image_name=imagename,
                                        p_name=fname, prob=fprob, ipath=ipath)
    return

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    # Returns the file of the name <filename>
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

"""When we run main.py, it will change its name to __main__
and only then will that if statement activate."""
if __name__ == "__main__":
    app.run(debug=True) #runs the application
"""Having debug=True allows possible Python errors to appear
on the web page. This will help us trace the errors."""


import json
import unittest
import unittest.mock as mock
from tests.unittest_utils import ForsetiTestCase
from tests.unittest_utils import get_datafile_path
import yaml

from google.cloud.forseti.common.gcp_type import bucket_access_controls
from google.cloud.forseti.common.util import file_loader
from google.cloud.forseti.scanner.audit import base_rules_engine as bre
from google.cloud.forseti.scanner.audit import buckets_rules_engine as bre
from google.cloud.forseti.scanner.audit.errors import InvalidRulesSchemaError

# TODO: Define more tests
class BucketsRulesEngineTest(ForsetiTestCase):
    """Tests for the BucketsRulesEngine."""

    def setUp(self):
        """Set up."""
        self.rule_index = 0
        self.bre = bre
        self.bre.LOGGER = mock.MagicMock()

    def test_build_rule_book_from_local_yaml_file_works(self):
        """Test that a RuleBook is built correctly with a yaml file."""
        rules_local_path = get_datafile_path(__file__,
                                             'buckets_test_rules_1.yaml')
        rules_engine = bre.BucketsRulesEngine(rules_file_path=rules_local_path)
        rules_engine.build_rule_book()
        self.assertEqual(2, len(rules_engine.rule_book.resource_rules_map))

    @mock.patch.object(file_loader,
                       '_read_file_from_gcs', autospec=True)
    def test_build_rule_book_from_gcs_works(self, mock_load_rules_from_gcs):
        """Test that a RuleBook is built correctly with a mocked gcs file.

        Setup:
            * Create a mocked GCS object from a test yaml file.
            * Get the yaml file content.

        Expected results:
            There are 4 resources that have rules, in the rule book.
        """
        bucket_name = 'bucket-name'
        rules_path = 'input/buckets_test_rules_1.yaml'
        full_rules_path = 'gs://{}/{}'.format(bucket_name, rules_path)
        rules_engine = bre.BucketsRulesEngine(rules_file_path=full_rules_path)

        # Read in the rules file
        file_content = None
        with open(get_datafile_path(__file__, 'buckets_test_rules_1.yaml'),
                  'r') as rules_local_file:
            try:
                file_content = yaml.safe_load(rules_local_file)
            except yaml.YAMLError:
                raise

        mock_load_rules_from_gcs.return_value = file_content

        rules_engine.build_rule_book()
        self.assertEqual(2, len(rules_engine.rule_book.resource_rules_map))

    def test_build_rule_book_no_resource_type_fails(self):
        """Test that a rule without a resource cannot be created."""
        rules_local_path = get_datafile_path(__file__,
                                             'buckets_test_rules_2.yaml')
        rules_engine = bre.BucketsRulesEngine(rules_file_path=rules_local_path)
        with self.assertRaises(InvalidRulesSchemaError):
            rules_engine.build_rule_book()

    def test_find_violation_for_publicly_exposed_acls(self):

        rules_local_path = get_datafile_path(__file__,
                                             'buckets_test_rules_1.yaml')
        rules_engine = bre.BucketsRulesEngine(rules_file_path=rules_local_path)
        rules_engine.build_rule_book()
        rules_map = rules_engine.rule_book.resource_rules_map
        all_users_rule = rules_map[0]
        all_authenticated_users_rule = rules_map[1]

        # Everything is allowed.
        acl_dict = json.loads(
            BUCKET_ACL_TEMPLATE.format(entity='project-owners-123456'))
        acl = bucket_access_controls.BucketAccessControls.from_dict(
            'test-project', 'fake_inventory_data', acl_dict)
        violation = all_users_rule.find_violations(acl)
        self.assertEqual(0, len(list(violation)))

        # Exposed to everyone in the world.
        acl_dict = json.loads(
            BUCKET_ACL_TEMPLATE.format(entity='allUsers'))
        acl = bucket_access_controls.BucketAccessControls.from_dict(
            'test-project', 'fake_inventory_data', acl_dict)
        violation = all_users_rule.find_violations(acl)
        self.assertEqual(1, len(list(violation)))

        # Exposed to all google-authenticated users in the world.
        acl_dict = json.loads(
            BUCKET_ACL_TEMPLATE.format(entity='allAuthenticatedUsers'))
        acl = bucket_access_controls.BucketAccessControls.from_dict(
            'test-project', 'fake_inventory_data', acl_dict)
        violation = all_authenticated_users_rule.find_violations(acl)
        self.assertEqual(1, len(list(violation)))

BUCKET_ACL_TEMPLATE = """
{{
 "kind": "storage#bucketAccessControl",
 "id": "test-bucket/{entity}",
 "selfLink": "https://www.googleapis.com/storage/v1/b/test-bucket/acl/{entity}",
 "bucket": "test-bucket",
 "entity": "{entity}",
 "role": "OWNER",
 "projectTeam": {{
  "projectNumber": "123456",
  "team": "owners"
 }},
 "etag": "CAE="
}}
"""

if __name__ == '__main__':
    unittest.main()