#!/usr/bin/env ipython

import numpy as np
from v3 import v3

class v3_Pat(v3):
    '''
    v3_Pat class : subclass of v3

    Pattern Data class for the Jeol v3.0 format

    The pattern data class stores the pattern specifications and shape
    information.  This class provides methods to determine the compatibility
    of patterns with the Jeol v3.0 format and generates the binary pattern data
    block.

    The following methods are supported by the v3_Pat class:
       checkPrimitive:      Check if the pattern is compatible
       addPattern:          Adds patterns to the object
       setPatternArray:     Sets pattern array parameters
       genRecord:           Generates the binary record

    This class is constructed such that:
        1)  All patterns share the same shotrank if specified
        2)  All patterns are arrayed if specified
        3)  All patterns can be displaced by specifying the positionSet
            positionSet should only be used in a text block

    Long Chang, UH, May 2013
    '''

    def __init__(self):
        super(v3_Pat,self).__init__()
        self._blockBuffer = 100
        self._block = np.zeros(self._blockBuffer,dtype=np.uint8)
        self._blockIndex = 0
        self._blockSectionIndex = [0]
        self._nPat = 0
        self._lX = 0
        self._lY = 0
        self._nX = 1
        self._nY = 1
        self._positionSetX = -1
        self._positionSetY = -1
        self._shotRank = -1
        self._verticesBuffer = 10
        self._rectXS = np.zeros([1,4],dtype=np.uint16)
        self._rectXM = np.zeros([1,4],dtype=np.uint32)
        self._rectXL = np.zeros([1,4],dtype=np.uint32)
        self._rectYS = np.zeros([1,4],dtype=np.uint16)
        self._rectYM = np.zeros([1,4],dtype=np.uint32)
        self._rectYL = np.zeros([1,4],dtype=np.uint32)
        self._trapXS = np.zeros([1,6],dtype=np.uint32)
        self._trapXM = np.zeros([1,6],dtype=np.uint32)
        self._trapXL = np.zeros([1,6],dtype=np.uint32)
        self._trapYS = np.zeros([1,6],dtype=np.uint16)
        self._trapYM = np.zeros([1,6],dtype=np.uint32)
        self._trapYL = np.zeros([1,6],dtype=np.uint32)
        self._rectXSIndex = 0
        self._rectXMIndex = 0
        self._rectXLIndex = 0
        self._rectYSIndex = 0
        self._rectYMIndex = 0
        self._rectYLIndex = 0
        self._trapXSIndex = 0
        self._trapXMIndex = 0
        self._trapXLIndex = 0
        self._trapYSIndex = 0
        self._trapYMIndex = 0
        self._trapYLIndex = 0

        '''
        Opcodes
        -------
        0xFF##         Opcode
        X        x position (16-bit)
        XX        x position (32-bit)
        Y        y position
        W        width
        H        height
        S        shot rank
        '''
        self._cShotRank     = 0xFF05        #0xFF05 [S]
        self._cPositionSet  = 0xFFF1        #0xFFF1 [XX YY]
        self._cRecordEnd    = 0xFFF2        #0xFFF2
        self._cRectXS       = 0xFF00        #0xFF00 [X Y W H]
        self._cRectYS       = 0xFF01        #0xFF01 [X Y W H]
        self._cRectXM       = 0xFF06        #0xFF06 [X Y W H]
        self._cRectYM       = 0xFF07        #0xFF07 [X Y W H]
        self._cRectXL       = 0xFF10        #0xFF10 [XX YY WW HH]
        self._cRectYL       = 0xFF11        #0xFF11 [XX YY WW HH]
        self._cTrapXS       = 0xFF02        #0xFF02 [X1 Y1 X2 X3 X4 Y4]
        self._cTrapYS       = 0xFF03        #0xFF03 [X1 Y1 Y2 Y3 X4 Y4]
        self._cTrapXM       = 0xFF08        #0xFF08 [X1 Y1 X2 X3 X4 Y4]
        self._cTrapYM       = 0xFF09        #0xFF09 [X1 Y1 Y2 Y3 X4 Y4]
        self._cTrapXL       = 0xFF12        #0xFF12 [XX1 YY1 XX2 XX3 XX4 YY4]
        self._cTrapYL       = 0xFF13        #0xFF13 [XX1 YY1 YY2 YY3 XX4 YY4]
        self._cPatternCompactionMode8 = 0xFFE8      #0xFFE8 [Npt LLx LLy Nx Ny]
                                                    #    Npt = Number of patterns
                                                    #    LLx = Length of array along X (32-bits)
                                                    #    LLy = Length of array along Y (32-bits)
                                                    #    Nx  = Number of repeats along X
                                                    #    Ny  = Number of repeats along Y

        '''
        Parameter limitation
        '''
        self._c12 = 12                      #Identifies the shape is Small
        self._c16 = 16                      #Identifies the shape is Medium
        self._c20 = 20                      #Identifies the shape is Large

        self._r12_xy = 4095                  #Max value for a small xy position (small rectangle fom spec sheet)
        self._r12_wh = 511                   #Max value for a small wh length
        self._r16_x = 65279                 #Max value for the x position (arge rectangle from the spec sheet)
        self._r16_wyh = 65535                #Max value for the y position and wh length
        self._r20_xywh = 1048575             #This refers to the 20 bit rectangle from the spec shet
        self._t12_all = 4095
        self._t16_x1 = 65279
        self._t16_rest = 65535
        self._t20_all = 1048575

        '''
        Minimum bytes required for each opcode and parameters
            sShotRank = dShotRank + 2
            The +2 accounts for adding a cRecordEnd
        '''
        self._sShotRank = 4
        self._sPositionSet = 10
        self._sRectSM = 10
        self._sRectL = 18
        self._sTrapSM = 14
        self._sTrapL = 26
        self._dRectSM = 8
        self._dRectL = 16
        self._dTrapSM = 12
        self._dTrapL = 24
        self._sPatternCompactionMode8 = 16
        self._sMax = 28

    def __repr__(self):
        print 'shotRank:          ' , self.shotRank
        print 'nPat:              ' , self.nPat
        print 'nX:                ' , self.nX
        print 'nY:                ' , self.nY
        if self.nX < 2:
            print 'pitchX:            0'
        else:
            print 'pitchX:            ' , self.lX/(self.nX-1)
        if self.nY < 2:
            print 'pitchY:            0'
        else:
            print 'pitchY:            ' , self.lY/(self.nY-1)
        print 'rectXS:            ' , self.rectXSIndex
        print 'rectXM:            ' , self.rectXMIndex
        print 'rectXL:            ' , self.rectXLIndex
        print 'rectYS:            ' , self.rectYSIndex
        print 'rectYM:            ' , self.rectYMIndex
        print 'rectYL:            ' , self.rectYLIndex
        print 'trapXS:            ' , self.trapXSIndex
        print 'trapXM:            ' , self.trapXMIndex
        print 'trapXL:            ' , self.trapXLIndex
        print 'trapYS:            ' , self.trapYSIndex
        print 'trapYM:            ' , self.trapYMIndex
        print 'trapYL:            ' , self.trapYLIndex
        print 'numRect:           ' , self.numRect
        print 'numTrap:           ' , self.numTrap
        print 'numDecRect:        ' , self.numDecRect
        print 'numDecTrap:        ' , self.numDecTrap
        return ''

    @property
    def nPat(self):
        '''
        nPat : integer from 0 to 4095
            Number of patterns in te array
        '''
        return self._nPat

    @nPat.setter
    def nPat(self,val):
        if val < 0 or val > 4095:
            raise ValueError('v3_Pat.nPat : This parameter must range from 0 to 4095')
        self._nPat = val

    @property
    def lX(self):
        '''
        lX : integer from 0 to 1000000
            Distance between the first and last pattern in the array along X
        '''
        return self._lX

    @lX.setter
    def lX(self, val):
        if val < 0 or val >= 1000000:
            raise ValueError("v3_Pat.lX : The parameters nX*pX must range from 0 to 1000000")
        self._lX = val

    @property
    def lY(self):
        '''
        lx : integer from 0 to 1000000
            Distance between the first and last pattern in the array along Y
        '''
        return self._lY

    @lY.setter
    def lY(self, val):
        if val < 0 or val >= 1000000:
            raise ValueError("v3_Pat.lY : This parameters nY*pY must range from 0 to 1000000")
        self._lY = val

    @property
    def nX(self):
        '''
        nX : integer from 0 to 2047
            The number of repeats of the pattern in the array along X
        '''
        return self._nX

    @nX.setter
    def nX(self, val):
        if val < 0 or val >= 2047:
            raise ValueError("v3_Pat.nX : This parameter must range from 0 to 2047")
        self._nX = val

    @property
    def nY(self):
        '''
        nY : integer from 0 to 2047
            The number of repeats of the pattern in the array along Y
        '''
        return self._nY

    @nY.setter
    def nY(self, val):
        if val < 0 or val >= 2047:
            raise ValueError("v3_Pat.nY : The value for nY must be larger than 0")
        self._nY = val

    def checkPrimitive(self, vertices):
        '''
        checkPrimitives(vertices)

        Returns the type of primitive if the input shape is a Jeol v3.0 format primitive

        Parameters
        ----------
        vertices : numpy.ndarray of unsigned integers containing 4, 6 or 8 elements
                4 elements are used to specify a rectangle [x,y,w,h]
                6 elements are used to specify a triangle  [x1,y1,x2,y2,x3,y3]
                8 elements are used to specify a trapezoid [x1,y1,x2,y2,x3,y3,x4,y4]

        Returns
        -------
        cType : a string specifying the type of primitive
                ['RectXS', 'RectXM', 'RectXL', 'RectYS', 'RectYM', 'RectYL',
                'TrapXS', 'TrapXM', 'TrapXL', 'TrapYS', 'TrapYM', 'TrapYL']
        trap : a formatted version of vertices to facilitate storage and conversion
        '''
        if not all(vertices >= 0):
                raise ValueError('v3_Pat.checkPrimitive() : The vertices parameter can not be negative')

        if not isinstance(vertices,np.ndarray):
            raise TypeError('v3_Pat.checkPrimitive() : Vertices is not of type numpy.ndarray')

        if not vertices.size in [4,6,8]:
            raise ValueError('v3_Pat.checkPrimitive() : The vertices parameter must contain 4, 6, or 8 elements')

        if vertices.size == 8:
            #The elements of vertices is [X1 Y1 X2 Y2 X3 Y3 X4 Y4]

            #Determine if the base of the trapezoid is along X or Y
            tmp = np.append(vertices,vertices[0:2])
            isX = sum(np.diff(tmp[1::2]) == 0) == 2
            isY = sum(np.diff(tmp[0::2]) == 0) == 2
            tmp = vertices.reshape(4,2)

            #The trapezoid is not supported
            if not isX and not isY:
                raise ValueError('v3_Pat.checkPrimitives : The trapezoid does not have both base parallel to either the X or Y axis')

            #The trapezoid is a rectangle
            if isX and isY:
                x = min(tmp[:,0])
                y = min(tmp[:,1])
                w = max(tmp[:,0]) - x
                h = max(tmp[:,1]) - y
                trap = np.array([x,y,w,h],dtype=np.uint32)
                bitSize = self.getBitSize(trap)

                if w >= h:
                    if bitSize is self.c12:
                        cType = self.cRectXS
                    elif bitSize is self.c16:
                        cType = self.cRectXM
                    elif bitSize is self.c20:
                        cType = self.cRectXL
                else:
                    if bitSize is self.c12:
                        cType = self.cRectYS
                    elif bitSize is self.c16:
                        cType = self.cRectYM
                    elif bitSize is self.c20:
                        cType = self.cRectYL

                return cType, trap
            #The trapezoid has both base parallel to X axis
            elif isX:
                #Sort the vertices of the trapezoid
                iA = tmp[:,1] == min(tmp[:,1])
                iB = tmp[:,1] == max(tmp[:,1])
                i1 = (tmp[:,0] == min(tmp[iA,0])) * iA
                i2 = (tmp[:,0] == max(tmp[iA,0])) * iA
                i3 = (tmp[:,0] == max(tmp[iB,0])) * iB
                i4 = (tmp[:,0] == min(tmp[iB,0])) * iB
                trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                theta1 = np.arctan(abs(int(trap[0])-int(trap[6]))/float(abs(int(trap[1])-int(trap[7]))))
                theta2 = np.arctan(abs(int(trap[2])-int(trap[4]))/float(abs(int(trap[3])-int(trap[5]))))

                if theta1 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : X Trapezoid Theta1 cannot be larger than 60 degrees')
                if theta2 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : X Trapezoid Theta2 cannot be larger than 60 degrees')

                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cTrapXS
                elif bitSize is self.c16:
                    cType = self.cTrapXM
                elif bitSize is self.c20:
                    cType = self.cTrapXL

                return cType, trap[[0,1,2,4,6,7]]
            #The trapezoid has both base parallel to the Y axis
            elif isY:
                #Sort the vertices of the trapezoid
                iA = tmp[:,0] == min(tmp[:,0])
                iB = tmp[:,0] == max(tmp[:,0])
                i1 = (tmp[:,1] == min(tmp[iA,1])) * iA
                i2 = (tmp[:,1] == max(tmp[iA,1])) * iA
                i3 = (tmp[:,1] == max(tmp[iB,1])) * iB
                i4 = (tmp[:,1] == min(tmp[iB,1])) * iB
                trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                theta1 = np.arctan(abs(int(trap[7])-int(trap[1]))/float(abs(int(trap[0])-int(trap[6]))))
                theta2 = np.arctan(abs(int(trap[3])-int(trap[5]))/float(abs(int(trap[2])-int(trap[4]))))

                if theta1 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : Y Trapezoid Theta1 cannot be larger than 60 degrees')
                if theta2 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : Y Trapezoid Theta2 cannot be larger than 60 degrees')

                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cTrapYS
                elif bitSize is self.c16:
                    cType = self.cTrapYM
                elif bitSize is self.c20:
                    cType = self.cTrapYL

                return cType, trap[[0,1,3,5,6,7]]
        elif vertices.size == 6:
            #The element of vertices is [X1 Y1 X2 Y2 X3 Y3]

            #Determineof the base of the triangle is along X or Y
            tmp = np.append(vertices,vertices[0:2])
            isX = sum(np.diff(tmp[1::2]) == 0) == 1
            isY = sum(np.diff(tmp[0::2]) == 0) == 1
            tmp = vertices.reshape(3,2)

            if not isX and not isY:
                raise ValueError('v3_Pat.checkPrimitive : One edge of the triangle must be parallel to either the X or Y axis')

            #Right triangle
            if isX and isY:

                #Determine the base and height of a right triangle
                w = max(vertices[0::2])-min(vertices[0::2])
                h = max(vertices[1::2])-min(vertices[1::2])

                #X right triangle
                if h >= w:
                    #Sort the vertices
                    iA = tmp[:,1] == min(tmp[:,1])
                    iB = tmp[:,1] == max(tmp[:,1])
                    i1 = (tmp[:,0] == min(tmp[iA,0])) * iA
                    i2 = (tmp[:,0] == max(tmp[iA,0])) * iA
                    i3 = (tmp[:,0] == max(tmp[iB,0])) * iB
                    i4 = (tmp[:,0] == min(tmp[iB,0])) * iB
                    trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                    theta1 = np.arctan(abs(int(trap[0])-int(trap[6]))/float(abs(int(trap[1])-int(trap[7]))))
                    theta2 = np.arctan(abs(int(trap[2])-int(trap[4]))/float(abs(int(trap[3])-int(trap[5]))))

                    if theta1 > np.pi/3:
                        raise ValueError('v3_Pat.checkPrimitive : X Triangle Theta1 cannot be larger than 60 degrees')
                    if theta2 > np.pi/3:
                        raise ValueError('v3_Pat.checkPrimitive : X Triangle Theta2 cannot be larger than 60 degrees')

                    bitSize = self.getBitSize(trap)
                    if bitSize is self.c12:
                        cType = self.cTrapXS
                    elif bitSize is self.c16:
                        cType = self.cTrapXM
                    elif bitSize is self.c20:
                        cType = self.cTrapXL

                    return cType, trap[[0,1,2,4,6,7]]

                #Y right triangle
                else:
                    #Sort the vertices
                    iA = tmp[:,0] == min(tmp[:,0])
                    iB = tmp[:,0] == max(tmp[:,0])
                    i1 = (tmp[:,1] == min(tmp[iA,1])) * iA
                    i2 = (tmp[:,1] == max(tmp[iA,1])) * iA
                    i3 = (tmp[:,1] == max(tmp[iB,1])) * iB
                    i4 = (tmp[:,1] == min(tmp[iB,1])) * iB
                    trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                    theta1 = np.arctan(abs(int(trap[7])-int(trap[1]))/float(abs(int(trap[0])-int(trap[6]))))
                    theta2 = np.arctan(abs(int(trap[3])-int(trap[5]))/float(abs(int(trap[2])-int(trap[4]))))

                    if theta1 > np.pi/3:
                        raise ValueError('v3_Pat.checkPrimitive : Y Triangle Theta1 cannot be larger than 60 degrees')
                    if theta2 > np.pi/3:
                        raise ValueError('v3_Pat.checkPrimitive : Y Triangle Theta2 cannot be larger than 60 degrees')

                    bitSize = self.getBitSize(trap)
                    if bitSize is self.c12:
                        cType = self.cTrapYS
                    elif bitSize is self.c16:
                        cType = self.cTrapYM
                    elif bitSize is self.c20:
                        cType = self.cTrapYL

                    return cType, trap[[0,1,3,5,6,7]]

            #X triangle
            elif isX:
                #Sort the vertices
                iA = tmp[:,1] == min(tmp[:,1])
                iB = tmp[:,1] == max(tmp[:,1])
                i1 = (tmp[:,0] == min(tmp[iA,0])) * iA
                i2 = (tmp[:,0] == max(tmp[iA,0])) * iA
                i3 = (tmp[:,0] == max(tmp[iB,0])) * iB
                i4 = (tmp[:,0] == min(tmp[iB,0])) * iB
                trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                theta1 = np.arctan(abs(int(trap[6])-int(trap[0]))/float(abs(int(trap[1])-int(trap[7]))))
                theta2 = np.arctan(abs(int(trap[2])-int(trap[4]))/float(abs(int(trap[3])-int(trap[5]))))

                if theta1 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : X Triangle Theta1 cannot be larger than 60 degrees')
                if theta2 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : X Triangle Theta2 cannot be larger than 60 degrees')

                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cTrapXS
                elif bitSize is self.c16:
                    cType = self.cTrapXM
                elif bitSize is self.c20:
                    cType = self.cTrapXL

                return cType, trap[[0,1,2,4,6,7]]

            #Y triangle
            elif isY:
                #Sort the vertices
                iA = tmp[:,0] == min(tmp[:,0])
                iB = tmp[:,0] == max(tmp[:,0])
                i1 = (tmp[:,1] == min(tmp[iA,1])) * iA
                i2 = (tmp[:,1] == max(tmp[iA,1])) * iA
                i3 = (tmp[:,1] == max(tmp[iB,1])) * iB
                i4 = (tmp[:,1] == min(tmp[iB,1])) * iB
                trap = np.array([tmp[i1],tmp[i2],tmp[i3],tmp[i4]],dtype=np.uint32).ravel()

                theta1 = np.arctan(abs(int(trap[7])-int(trap[1]))/float(abs(int(trap[0])-int(trap[6]))))
                theta2 = np.arctan(abs(int(trap[3])-int(trap[5]))/float(abs(int(trap[2])-int(trap[4]))))

                if theta1 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : Y Triangle Theta1 cannot be larger than 60 degrees')
                if theta2 > np.pi/3:
                    raise ValueError('v3_Pat.checkPrimitive : Y Triangle Theta2 cannot be larger than 60 degrees')

                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cTrapYS
                elif bitSize is self.c16:
                    cType = self.cTrapYM
                elif bitSize is self.c20:
                    cType = self.cTrapYL

                return cType, trap[[0,1,3,5,6,7]]

        #Rectangle
        elif vertices.size == 4:
            #The elements of vertices is [X Y W H]

            #Determine of the longest edge is along the X or Y axis
            isX = vertices[2] >= vertices[3]
            trap = vertices.astype(np.uint32)

            #X rectangle
            if isX:
                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cRectXS
                elif bitSize is self.c16:
                    cType = self.cRectXM
                elif bitSize is self.c20:
                    cType = self.cRectXL

            #Y rectangle
            else:
                bitSize = self.getBitSize(trap)
                if bitSize is self.c12:
                    cType = self.cRectYS
                elif bitSize is self.c16:
                    cType = self.cRectYM
                elif bitSize is self.c20:
                    cType = self.cRectYL

            return cType,trap

        raise ValueError('v3_Pat.checkPrimitive: An unaccounted error occurred')

    @property
    def c12(self):
        return self._c12

    @property
    def c16(self):
        return self._c16

    @property
    def c20(self):
        return self._c20

    def getBitSize(self, vertices):
        '''
        getBitSize(vertices)

        Returns a value either 12, 16 or 20 to specify the minimum number of
        bits required to represent the vertices

        Parameters
        ----------
        vertices : a 1D numpy.ndarray

        Returns
        -------
        out : integer from the set [12,16,20]
        '''
        if any(vertices >= self._r20_xywh):
            raise ValueError("v3_Pat.getBitSize : The value for the input argument vertices cannot exceed 2^20 (1048576)")

        if vertices.size == 4:
            if not any(np.append(vertices[0:2] >= self._r12_xy,any(vertices[2:] >= self._r12_wh))):
                return self.c12
            elif not any(vertices >= self._r16_x):
                return self.c16
            else:
                return self.c20
        else:
            if not any(vertices >= self._t12_all):
                return self.c12
            elif not any(vertices >= self._t16_x1):
                return self.c16
            else:
                return self.c20

    @property
    def shotRank(self):
        '''
        shotRank : integer from -1 to 255
            The shot rank value for the patterns in this object
                -1  =   No shot rank
        '''
        return self._shotRank

    @shotRank.setter
    def shotRank(self,val):
        if val < -1 or val >= 256:
            raise ValueError("v3_Pat.shotRank : This parameter must range from -1 to 255")
        self._shotRank = val
        self.maxShotRank = self._shotRank

    @property
    def rectXS(self):
        '''
        rectXS : numpy.ndarray of Nx4 elements
            The parameters of a 12-bit X rectangle
        '''
        return self._rectXS

    @rectXS.setter
    def rectXS(self,vertices):
        if self._rectXSIndex == self._rectXS.shape[0]:
            self._rectXS = np.append(self._rectXS,np.zeros((self._verticesBuffer,4),dtype=np.uint16),axis=0)
        self._rectXS[self._rectXSIndex] = vertices
        self._rectXSIndex += 1
        self.numRect += 1

    @property
    def rectXM(self):
        '''
        rectXM : numpy.ndarray of Nx4 elements
            The parameters of a 16-bit X rectangle
        '''
        return self._rectXM

    @rectXM.setter
    def rectXM(self,vertices):
        if self._rectXMIndex == self._rectXM.shape[0]:
            self._rectXM = np.append(self._rectXM,np.zeros((self._verticesBuffer,4),dtype=np.uint16),axis=0)
        self._rectXM[self._rectXMIndex] = vertices
        self._rectXMIndex += 1
        self.numRect += 1

    @property
    def rectXL(self):
        '''
        rectXL : numpy.ndarray of Nx4 elements
            The parameters of a 20-bit X rectangle
        '''
        return self._rectXL

    @rectXL.setter
    def rectXL(self,vertices):
        if self._rectXLIndex == self._rectXL.shape[0]:
            self._rectXL = np.append(self._rectXL,np.zeros((self._verticesBuffer,4),dtype=np.uint32),axis=0)
        self._rectXL[self._rectXLIndex] = vertices
        self._rectXLIndex += 1
        self.numRect += 1

    @property
    def rectYS(self):
        '''
        rectYS : numpy.ndarray of Nx4 elements
            The parameters of a 12-bit Y rectangle
        '''
        return self._rectYS

    @rectYS.setter
    def rectYS(self,vertices):
        if self._rectYSIndex == self._rectYS.shape[0]:
            self._rectYS = np.append(self._rectYS,np.zeros((self._verticesBuffer,4),dtype=np.uint16),axis=0)
        self._rectYS[self._rectYSIndex] = vertices
        self._rectYSIndex += 1
        self.numRect += 1

    @property
    def rectYM(self):
        '''
        rectYM : numpy.ndarray of Nx4 elements
            The parameters of a 16-bit Y rectangle
        '''
        return self._rectYM

    @rectYM.setter
    def rectYM(self,vertices):
        if self._rectYMIndex == self._rectYM.shape[0]:
            self._rectYM = np.append(self._rectYM,np.zeros((self._verticesBuffer,4),dtype=np.uint16),axis=0)
        self._rectYM[self._rectYMIndex] = vertices
        self._rectYMIndex += 1
        self.numRect += 1

    @property
    def rectYL(self):
        '''
        rectYL : numpy.ndarray of Nx4 elements
            The parameters of a 20-bit Y rectangle
        '''
        return self._rectYL

    @rectYL.setter
    def rectYL(self,vertices):
        if self._rectYLIndex == self._rectYL.shape[0]:
            self._rectYL = np.append(self._rectYL,np.zeros((self._verticesBuffer,4),dtype=np.uint32),axis=0)
        self._rectYL[self._rectYLIndex] = vertices
        self._rectYLIndex += 1
        self.numRect += 1

    @property
    def trapXS(self):
        '''
        trapXS : numpy.ndarray of Nx6 elements
            The parameters of a 12-bit X trapezoid
        '''
        return self._trapXS

    @trapXS.setter
    def trapXS(self,vertices):
        if self._trapXSIndex == self._trapXS.shape[0]:
            self._trapXS = np.append(self._trapXS,np.zeros((self._verticesBuffer,6),dtype=np.uint16),axis=0)
        self._trapXS[self._trapXSIndex] = vertices
        self._trapXSIndex += 1
        self.numTrap += 1

    @property
    def trapXM(self):
        '''
        trapXM : numpy.ndarray of Nx6 elements
            The parameters of a 16-bit X trapezoid
        '''
        return self._trapXM

    @trapXM.setter
    def trapXM(self,vertices):
        if self._trapXMIndex == self._trapXM.shape[0]:
            self._trapXM = np.append(self._trapXM,np.zeros((self._verticesBuffer,6),dtype=np.uint16),axis=0)
        self._trapXM[self._trapXMIndex] = vertices
        self._trapXMIndex += 1
        self.numTrap += 1

    @property
    def trapXL(self):
        '''
        trapXL : numpy.ndarray of Nx6 elements
            The parameters of a 20-bit X trapezoid
        '''
        return self._trapXL

    @trapXL.setter
    def trapXL(self,vertices):
        if self._trapXLIndex == self._trapXL.shape[0]:
            self._trapXL = np.append(self._trapXL,np.zeros((self._verticesBuffer,6),dtype=np.uint32),axis=0)
        self._trapXL[self._trapXLIndex] = vertices
        self._trapXLIndex += 1
        self.numTrap += 1

    @property
    def trapYS(self):
        '''
        trapYS : numpy.ndarray of Nx6 elements
            The parameters of a 12-bit Y trapezoid
        '''
        return self._trapYS

    @trapYS.setter
    def trapYS(self,vertices):
        if self._trapYSIndex == self._trapYS.shape[0]:
            self._trapYS = np.append(self._trapYS,np.zeros((self._verticesBuffer,6),dtype=np.uint16),axis=0)
        self._trapYS[self._trapYSIndex] = vertices
        self._trapYSIndex += 1
        self.numTrap += 1

    @property
    def trapYM(self):
        '''
        trapYM : numpy.ndarray of Nx6 elements
            The parameters of a 16-bit Y trapezoid
        '''
        return self._trapYM

    @trapYM.setter
    def trapYM(self,vertices):
        if self._trapYMIndex == self._trapYM.shape[0]:
            self._trapYM = np.append(self._trapYM,np.zeros((self._verticesBuffer,6),dtype=np.uint16),axis=0)
        self._trapYM[self._trapYMIndex] = vertices
        self._trapYMIndex += 1
        self.numTrap += 1

    @property
    def trapYL(self):
        '''
        trapYL : numpy.ndarray of Nx6 elements
            The parameters of a 20-bit Y trapezoid
        '''
        return self._trapYL

    @trapYL.setter
    def trapYL(self,vertices):
        if self._trapYLIndex == self._trapYL.shape[0]:
            self._trapYL = np.append(self._trapYL,np.zeros((self._verticesBuffer,6),dtype=np.uint32),axis=0)
        self._trapYL[self._trapYLIndex] = vertices
        self._trapYLIndex += 1
        self.numTrap += 1

    @property
    def rectXSIndex(self):
        '''
        rectXSIndex : integer
            Tracks the number of rectXS and points to the next position in the rectXS numpy.ndarray
        '''
        return self._rectXSIndex

    @property
    def rectXMIndex(self):
        '''
        rectXMIndex : integer
            Tracks the number of rectXM and points to the next position in the rectXM numpy.ndarray
        '''
        return self._rectXMIndex

    @property
    def rectXLIndex(self):
        '''
        rectXLIndex : integer
            Tracks the number of rectXL and points to the next position in the rectXL numpy.ndarray
        '''
        return self._rectXLIndex

    @property
    def rectYSIndex(self):
        '''
        rectYSIndex : integer
            Tracks the number of rectYS and points to the next position in the rectYS numpy.ndarray
        '''
        return self._rectYSIndex

    @property
    def rectYMIndex(self):
        '''
        rectYMIndex : integer
            Tracks the number of rectYM and points to the next position in the rectYM numpy.ndarray
        '''
        return self._rectYMIndex

    @property
    def rectYLIndex(self):
        '''
        rectYLIndex : integer
            Tracks the number of rectYL and points to the next position in the rectYL numpy.ndarray
        '''
        return self._rectYLIndex

    @property
    def trapXSIndex(self):
        '''
        trapXSIndex : integer
            Tracks the number of trapXS and points to the next position in the trapXS numpy.ndarray
        '''
        return self._trapXSIndex

    @property
    def trapXMIndex(self):
        '''
        trapXMIndex : integer
            Tracks the number of trapXM and points to the next position in the trapXM numpy.ndarray
        '''
        return self._trapXMIndex

    @property
    def trapXLIndex(self):
        '''
        trapXLIndex : integer
            Tracks the number of trapXL and points to the next position in the trapXL numpy.ndarray
        '''
        return self._trapXLIndex

    @property
    def trapYSIndex(self):
        '''
        trapYSIndex : integer
            Tracks the number of trapYS and points to the next position in the trapYS numpy.ndarray
        '''
        return self._trapYSIndex

    @property
    def trapYMIndex(self):
        '''
        trapYMIndex : integer
            Tracks the number of trapYM and points to the next position in the trapYM numpy.ndarray
        '''
        return self._trapYMIndex

    @property
    def trapYLIndex(self):
        '''
        trapYLIndex : integer
            Tracks the number of trapYL and points to the next position in the trapYL numpy.ndarray
        '''
        return self._trapYLIndex

    def addPattern(self, vertices, shotRank=-1):
        '''
        addPattern(vertices)

        Adds a list of vertices to the v3_Pat object

        Parameters
        ----------
        vertices : a list of numpy.ndarray
                Each list element should contain an np.ndarray of size 4, 6 or 8 elements
        shotRank : a value from 0 to 255
                The shot rank value of all shapes in this object
                    -1  =   No shot rank

        Returns
        -------
        nothing
        '''
        if shotRank >= 0:    self.shotRank = shotRank

        for i in vertices:
            cType,trap = self.checkPrimitive(i)
            if   cType is self.cRectXS:
                self.rectXS = trap
            elif cType is self.cRectXM:
                self.rectXM = trap
            elif cType is self.cRectXL:
                self.rectXL = trap
            elif cType is self.cRectYS:
                self.rectYS = trap
            elif cType is self.cRectYM:
                self.rectYM = trap
            elif cType is self.cRectYL:
                self.rectYL = trap
            elif cType is self.cTrapXS:
                self.trapXS = trap
            elif cType is self.cTrapXM:
                self.trapXM = trap
            elif cType is self.cTrapXL:
                self.trapXL = trap
            elif cType is self.cTrapYS:
                self.trapYS = trap
            elif cType is self.cTrapYM:
                self.trapYM = trap
            elif cType is self.cTrapYL:
                self.trapYL = trap

        self.numDecRect = self.numRect*self.nX*self.nY
        self.numDecTrap = self.numTrap*self.nX*self.nY

    def setPatternArray(self, pX, pY, nX, nY):
        '''
        setPatternArray(pX,pY,nX,nY)

        Sets the parameters to array all patterns in the object

        Parameters
        ----------
        pX : integer
            The distance between neighboring patterns in the array along X
        pY : integer
            The distance between neighboring patterns in the array along Y
        nX : integer from 0 to 2047
            The number of repeats of the patterns in the array along X
        nY : integer from 0 to 2047
            The number of repeats of the patterns in the array along Y

        '''
        self.lX = pX*(nX-1)
        self.lY = pY*(nY-1)
        self.nX = nX
        self.nY = nY

        self.numDecRect = self.numRect*self.nX*self.nY
        self.numDecTrap = self.numTrap*self.nX*self.nY

    def setPatternPosition(self, x=0, y=0):
        '''
        setPatternPosition(x=0, y=0)
        '''
        self.positionSetX = x
        self.positionSetY = y

    @property
    def block(self):
        '''
        block : numpy.ndarray of type numpy.uint8
            The binary pattern data

        Description
        -----------
        The block parameter appends its set value
        The block parameter is a dynamically growing array
            The block parameter will grow by self._blockBuffer when appending
            the set value will result in overflow
        '''
        return self._block

    @block.setter
    def block(self,val):
        if self._blockIndex + val.size >= self._block.size:
            nBuffer = int(np.ceil(float(val.size)/float(self._blockBuffer)))
            self._block = np.append(self._block,np.zeros(self._blockBuffer*nBuffer,dtype=np.uint8),axis=0)
        self._block[self._blockIndex:self._blockIndex+val.size] = val
        self._blockIndex += val.size

    @property
    def blockIndex(self):
        '''
        blockIndex : integer
            A pointer that tracks the position in the block parameter

        Description
        -----------
        The blockIndex automatically points to the next available memory
        position of the block parameter.
        '''
        return self._blockIndex

    @property
    def blockSectionIndex(self):
        '''
        blockSectionIndex : list of integer
            Stores the start position of a block
        '''
        return self._blockSectionIndex

    @blockSectionIndex.setter
    def blockSectionIndex(self,val):
        self._blockSectionIndex.append(val)

    def clipBlock(self):
        '''
        clipBlock()

        Remove unused elements in the block parameter
        '''
        self._block = np.delete(self._block,np.s_[self._blockIndex::],0)

    @property
    def positionSetX(self):
        '''
        positionSetX : integer from 1 to 2,000,000
            The pattern x origin with respect to the field origin
        '''
        return self._positionSetX

    @positionSetX.setter
    def positionSetX(self,val):
        if val < 0 or val > 2000000:
            raise ValueError('v3_Pat.positionSetX : This parameter must range from 0 to 2,000,000')
        self._positionSetX = val

    @property
    def positionSetY(self):
        '''
        positionSetY : integer from 0 to 2,000,000
            The pattern y origin with respect to the field origin
        '''
        return self._positionSetY

    @positionSetY.setter
    def positionSetY(self,val):
        if val < 0 or val > 2000000:
            raise ValueError('v3_Pat.positionSetY : This parameter must range from 0 to 2,000,000')
        self._positionSetY = val

    @property
    def cShotRank(self):
        '''
        cShotRank : 0xFF05
            Opcode for shotrank
        '''
        return self._cShotRank

    @property
    def cPositionSet(self):
        '''
        cPositionSet : 0xFFF1
            Opcode for position set
        '''
        return self._cPositionSet

    @property
    def cRecordEnd(self):
        return self._cRecordEnd

    @property
    def cRectXS(self):
        return self._cRectXS

    @property
    def cRectXM(self):
        return self._cRectXM

    @property
    def cRectXL(self):
        return self._cRectXL

    @property
    def cRectYS(self):
        return self._cRectYS

    @property
    def cRectYM(self):
        return self._cRectYM

    @property
    def cRectYL(self):
        return self._cRectYL

    @property
    def cTrapXS(self):
        return self._cTrapXS

    @property
    def cTrapXM(self):
        return self._cTrapXM

    @property
    def cTrapXL(self):
        return self._cTrapXL

    @property
    def cTrapYS(self):
        return self._cTrapYS

    @property
    def cTrapYM(self):
        return self._cTrapYM

    @property
    def cTrapYL(self):
        return self._cTrapYL

    @property
    def cPatternCompactionMode8(self):
        return self._cPatternCompactionMode8

    @property
    def sShotRank(self):
        return self._sShotRank

    @property
    def sPositionSet(self):
        return self._sPositionSet

    @property
    def sRectSM(self):
        return self._sRectSM

    @property
    def sRectL(self):
        return self._sRectL

    @property
    def sTrapSM(self):
        return self._sTrapSM

    @property
    def sTrapL(self):
        return self._sTrapL

    @property
    def dRectSM(self):
        return self._dRectSM

    @property
    def dRectL(self):
        return self._dRectL

    @property
    def dTrapSM(self):
        return self._dTrapSM

    @property
    def dTrapL(self):
        return self._dTrapL

    @property
    def sPatternCompactionMode8(self):
        return self._sPatternCompactionMode8

    @property
    def sMax(self):
        return self._sMax

    def blockFracture(self,offset,cType):
        '''
        blockFracture(offset,cType)

        Determines if the block should be fractured to fit in a record

        Parameters
        ----------
        offset : integer from 0 to 4096

        cType : string

        Returns
        -------
        offset : integer from 0 to 4096
            New offset position

        reset : boolean
            Indicate if a offset was reset

        Note
        -----------
        This function could be improved to better fill a block by using the
        actual length of each opCode instead of the maximum length of all
        opCodes (sMax).
        '''

        if self.maxRecordSize < offset + self.sMax:
            self.block = self.dec2bin(self.cRecordEnd)
            self.blockSectionIndex = self.blockIndex
            if not (cType is self.cShotRank or cType is self.cPatternCompactionMode8):
                self.block = self.dec2bin(cType)
            offset = 0
            return offset, True
        return offset, False

    def genRecord(self,offset=0):
        '''
        genRecord(offset=0)

        Generates the binary pattern data

        Parameters
        ----------
        offset : integer from 0 to 4096
            The position in a record.
            Since a record has a fixed size of 2048 words, the blocks must be
            generated properly to avoid overflow.

        Results
        -------
        offset : integer from 0 to 4096
            The position in a record after adding this pattern data block

        Description
        -----------
        Generates the binary pattern data block from the pattern data.  The
        syntax for the pattern data block is:
        <Pattern Data Block> =  <Position Set?><Shot Rank?><Pattern Compaction?>
                                <RectXS?><RectXM?><RectXL?><RectYS?><RectYM?>
                                <RectYL?><TrapXS?><TrapXM?><TrapXL?><TrapYS?>
                                <TrapYM?><TrapYL?>
        A '?' means that the block is optional
        '''

        if offset < 0 or offset > self.maxRecordSize:
            raise ValueError('v3_Pat.genRecord : The offset parameter must range from 0 to 4096')

        self._block = np.zeros(self._blockBuffer,dtype=np.uint8)
        self._blockIndex = 0
        self._blockSectionIndex = [0]

        #Position Set
        if self.positionSetX >= 0 and self.positionSetY >= 0:
            offset, reset = self.blockFracture(offset,self.cPositionSet)
            self.block = self.dec2bin(self.cPositionSet)
            self.block = self.dec2bin(self.positionSetX,4)
            self.block = self.dec2bin(self.positionSetY,4)
            offset += self.sPositionSet

        #Shot rank value
        offset, reset = self.blockFracture(offset,self.cShotRank)
        if self.shotRank >= 0:
            self.block = self.dec2bin(self.cShotRank)
            self.block = self.dec2bin(self.shotRank)
            offset += self.sShotRank

        #Pattern compaction mode 8
        if self.nX*self.nY > 1:
            self.nPat = self.numRect + self.numTrap
            self.numDecRect = self.numRect*self.nX*self.nY
            self.numDecTrap = self.numTrap*self.nX*self.nY
            offset, reset = self.blockFracture(offset,self.cPatternCompactionMode8)
            self.block = self.dec2bin(self.cPatternCompactionMode8)
            self.block = self.dec2bin(self.nPat)
            self.block = self.dec2bin(self.lX,4)
            self.block = self.dec2bin(self.lY,4)
            self.block = self.dec2bin(self.nX)
            self.block = self.dec2bin(self.nY)
            offset += self.sPatternCompactionMode8

        #Shapes
        if self.rectXSIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectXS)
            if not reset:
                self.block = self.dec2bin(self.cRectXS)
            offset += 2
            for i in range(self.rectXSIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectXS)
                self.block = np.array([self.dec2bin(self.rectXS[i,0]),
                        self.dec2bin(self.rectXS[i,1]),
                        self.dec2bin(self.rectXS[i,2]),
                        self.dec2bin(self.rectXS[i,3])],dtype=np.uint8).ravel()
                offset += self.dRectSM
        if self.rectXMIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectXM)
            if not reset:
                self.block = self.dec2bin(self.cRectXM)
            offset += 2
            for i in range(self.rectXMIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectXM)
                self.block = np.array([self.dec2bin(self.rectXM[i,0]),
                        self.dec2bin(self.rectXM[i,1]),
                        self.dec2bin(self.rectXM[i,2]),
                        self.dec2bin(self.rectXM[i,3])],dtype=np.uint8).ravel()
                offset += self.dRectSM
        if self.rectXLIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectXL)
            if not reset:
                self.block = self.dec2bin(self.cRectXL)
            offset += 2
            for i in range(self.rectXLIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectXL)
                self.block = np.array([self.dec2bin(self.rectXL[i,0],4),
                        self.dec2bin(self.rectXL[i,1],4),
                        self.dec2bin(self.rectXL[i,2],4),
                        self.dec2bin(self.rectXL[i,3],4)],dtype=np.uint8).ravel()
                offset += self.dRectL
        if self.rectYSIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectYS)
            if not reset:
                self.block = self.dec2bin(self.cRectYS)
            offset += 2
            for i in range(self.rectYSIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectYS)
                self.block = np.array([self.dec2bin(self.rectYS[i,0]),
                        self.dec2bin(self.rectYS[i,1]),
                        self.dec2bin(self.rectYS[i,2]),
                        self.dec2bin(self.rectYS[i,3])],dtype=np.uint8).ravel()
                offset += self.dRectSM
        if self.rectYMIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectYM)
            if not reset:
                self.block = self.dec2bin(self.cRectYM)
            offset += 2
            for i in range(self.rectYMIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectYM)
                self.block = np.array([self.dec2bin(self.rectYM[i,0]),
                        self.dec2bin(self.rectYM[i,1]),
                        self.dec2bin(self.rectYM[i,2]),
                        self.dec2bin(self.rectYM[i,3])],dtype=np.uint8).ravel()
                offset += self.dRectSM
        if self.rectYLIndex > 0:
            offset, reset = self.blockFracture(offset,self.cRectYL)
            if not reset:
                self.block = self.dec2bin(self.cRectYL)
            offset += 2
            for i in range(self.rectYLIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cRectYL)
                self.block = np.array([self.dec2bin(self.rectYL[i,0],4),
                        self.dec2bin(self.rectYL[i,1],4),
                        self.dec2bin(self.rectYL[i,2],4),
                        self.dec2bin(self.rectYL[i,3],4)],dtype=np.uint8).ravel()
                offset += self.dRectL
        if self.trapXSIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapXS)
            if not reset:
                self.block = self.dec2bin(self.cTrapXS)
            offset += 2
            for i in range(self.trapXSIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapXS)
                self.block = np.array([self.dec2bin(self.trapXS[i,0]),
                        self.dec2bin(self.trapXS[i,1]),
                        self.dec2bin(self.trapXS[i,2]),
                        self.dec2bin(self.trapXS[i,3]),
                        self.dec2bin(self.trapXS[i,4]),
                        self.dec2bin(self.trapXS[i,5])],dtype=np.uint8).ravel()
                offset += self.dTrapSM
        if self.trapXMIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapXM)
            if not reset:
                self.block = self.dec2bin(self.cTrapXM)
            offset += 2
            for i in range(self.trapXMIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapXM)
                self.block = np.array([self.dec2bin(self.trapXM[i,0]),
                        self.dec2bin(self.trapXM[i,1]),
                        self.dec2bin(self.trapXM[i,2]),
                        self.dec2bin(self.trapXM[i,3]),
                        self.dec2bin(self.trapXM[i,4]),
                        self.dec2bin(self.trapXM[i,5])],dtype=np.uint8).ravel()
                offset += self.dTrapSM
        if self.trapXLIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapXL)
            if not reset:
                self.block = self.dec2bin(self.cTrapXL)
            offset += 2
            for i in range(self.trapXLIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapXL)
                self.block = np.array([self.dec2bin(self.trapXL[i,0],4),
                        self.dec2bin(self.trapXL[i,1],4),
                        self.dec2bin(self.trapXL[i,2],4),
                        self.dec2bin(self.trapXL[i,3],4),
                        self.dec2bin(self.trapXL[i,4],4),
                        self.dec2bin(self.trapXL[i,5],4)],dtype=np.uint8).ravel()
                offset += self.dTrapL
        if self.trapYSIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapYS)
            if not reset:
                self.block = self.dec2bin(self.cTrapYS)
            offset += 2
            for i in range(self.trapYSIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapYS)
                self.block = np.array([self.dec2bin(self.trapYS[i,0]),
                        self.dec2bin(self.trapYS[i,1]),
                        self.dec2bin(self.trapYS[i,2]),
                        self.dec2bin(self.trapYS[i,3]),
                        self.dec2bin(self.trapYS[i,4]),
                        self.dec2bin(self.trapYS[i,5])],dtype=np.uint8).ravel()
                offset += self.dTrapSM
        if self.trapYMIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapYM)
            if not reset:
                self.block = self.dec2bin(self.cTrapYM)
            offset += 2
            for i in range(self.trapYMIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapYM)
                self.block = np.array([self.dec2bin(self.trapYM[i,0]),
                        self.dec2bin(self.trapYM[i,1]),
                        self.dec2bin(self.trapYM[i,2]),
                        self.dec2bin(self.trapYM[i,3]),
                        self.dec2bin(self.trapYM[i,4]),
                        self.dec2bin(self.trapYM[i,5])],dtype=np.uint8).ravel()
                offset += self.dTrapSM
        if self.trapYLIndex > 0:
            offset, reset = self.blockFracture(offset,self.cTrapYL)
            if not reset:
                self.block = self.dec2bin(self.cTrapYL)
            offset += 2
            for i in range(self.trapYLIndex):
                if i > 0:
                    offset, reset = self.blockFracture(offset,self.cTrapYL)
                self.block = np.array([self.dec2bin(self.trapYL[i,0],4),
                        self.dec2bin(self.trapYL[i,1],4),
                        self.dec2bin(self.trapYL[i,2],4),
                        self.dec2bin(self.trapYL[i,3],4),
                        self.dec2bin(self.trapYL[i,4],4),
                        self.dec2bin(self.trapYL[i,5],4)],dtype=np.uint8).ravel()
                offset += self.dTrapL
        self.clipBlock()
        return offset

def test():
    '''
    test()

    Performs a diagnosis of the v3_Pat class
    '''
    a = v3_Pat()
    #Rectangles
    v1 = np.array([[0,0,3,3],[10,0,5,3],[0,10,3,5],[10,10,5,5]])
    #Rectangles as Trapezoids
    v2 = np.array([[0,15,0,25,20,25,20,15],[25,0,25,20,35,20,35,0]])
    #Triangles
    v3 = np.array([[5,30,10,35,15,30],[0,35,0,45,5,40],[10,45,5,50,15,50],[20,35,15,40,20,45]])
    #Right Triangles
    v4 = np.array([[6,20,6,44,10,44],[14,20,10,44,14,44],[10,36,14,40,14,36],[6,36,6,40,10,36]])
    #Trapezoids
    v5 = np.array([[25,25,30,30,35,30,40,25],[25,30,25,45,30,40,30,35],[40,30,35,35,35,40,40,45],[30,45,23,50,40,50,35,45]])

#    for i in v1:
#        print a.checkPrimitive(i)
#    for i in v2:
#        print a.checkPrimitive(i)
#    for i in v3:
#        print a.checkPrimitive(i)
#    for i in v4:
#        print a.checkPrimitive(i)
#    for i in v5:
#        print a.checkPrimitive(i)

    a.addPattern(v1)
    a.addPattern(v2)
    a.addPattern(v3)
    a.addPattern(v4)
    a.addPattern(v5)
    a.setPatternArray(100,100,1000,1000)
    a.genRecord(4040)
    print a
    print a.block[a.blockSectionIndex[0]:a.blockSectionIndex[1]]
    print a.block[a.blockSectionIndex[1]:]

if __name__ == '__main__':
    test()


# Copyright 2019, FBPIC contributors
# Authors: Remi Lehe, Manuel Kirchen
# License: 3-Clause-BSD-LBNL
"""
This file is part of the Fourier-Bessel Particle-In-Cell code (FB-PIC)

It defines the picmi Simulation interface
"""
import numpy as np
import warnings
from scipy.constants import c, e, m_e
from .particle_charge_and_mass import particle_charge, particle_mass

# Import relevant fbpic object
from fbpic.main import Simulation as FBPICSimulation
from fbpic.fields.smoothing import BinomialSmoother
from fbpic.lpa_utils.laser import add_laser_pulse, GaussianLaser
from fbpic.lpa_utils.bunch import add_particle_bunch_gaussian, add_particle_bunch
from fbpic.lpa_utils.mirrors import Mirror
from fbpic.lpa_utils.external_fields import ExternalField
from fbpic.openpmd_diag import FieldDiagnostic, ParticleDiagnostic, \
    ParticleChargeDensityDiagnostic, \
    BackTransformedFieldDiagnostic, BackTransformedParticleDiagnostic

# Import picmi base class
from picmistandard import PICMI_Simulation, PICMI_CylindricalGrid
from picmistandard import PICMI_AnalyticDistribution, PICMI_UniformDistribution, PICMI_GriddedLayout
from picmistandard import PICMI_PseudoRandomLayout, PICMI_GaussianBunchDistribution
from picmistandard import PICMI_LaserAntenna, PICMI_GaussianLaser
from picmistandard import PICMI_Species, PICMI_MultiSpecies
from picmistandard import PICMI_AnalyticAppliedField, PICMI_ConstantAppliedField, PICMI_Mirror
from picmistandard import PICMI_FieldDiagnostic, PICMI_ParticleDiagnostic, \
    PICMI_LabFrameFieldDiagnostic, PICMI_LabFrameParticleDiagnostic

# Define a new simulation object for picmi, that derives from PICMI_Simulation
class Simulation( PICMI_Simulation ):

    # Redefine the `init` method, as required by the picmi `_ClassWithInit`
    def init(self, kw):

        # Get the grid
        grid = self.solver.grid
        if not type(grid) == PICMI_CylindricalGrid:
            raise ValueError('When using fbpic with PICMI, '
                'the grid needs to be a CylindricalGrid object.')
        # Check rmin and boundary conditions
        assert grid.rmin == 0.
        assert grid.bc_zmin == grid.bc_zmax
        if grid.bc_zmin == 'reflective':
            warnings.warn(
            "FBPIC does not support reflective boundary condition in z.\n"
            "The z boundary condition was automatically converted to 'open'.")
            grid.bc_zmin = 'open'
            grid.bc_zmax = 'open'
        assert grid.bc_zmax in ['periodic', 'open']
        assert grid.bc_rmax in ['reflective', 'open']

        # Determine timestep
        if self.solver.cfl is not None:
            dz = (grid.zmax-grid.zmin)/grid.nz
            dt = self.solver.cfl * dz / c
        elif self.time_step_size is not None:
            dt = self.time_step_size
        else:
            raise ValueError(
                'You need to either set the `cfl` of the solver\n'
                'or the `timestep_size` of the `Simulation`.')

        # Convert API for the smoother
        if self.solver.source_smoother is None:
            smoother = BinomialSmoother()
        else:
            if self.solver.source_smoother.n_pass is None:
                n_passes = 1
            else:
                n_passes = {'r': self.solver.source_smoother.n_pass[0],
                            'z': self.solver.source_smoother.n_pass[1]}
            if self.solver.source_smoother.compensation is None:
                compensator = False
            else:
                compensator = all(self.solver.source_smoother.compensation)
            smoother = BinomialSmoother( n_passes=n_passes,
                                         compensator=compensator )

        # Convert verbose level:
        verbose_level = self.verbose
        if verbose_level is None:
            verbose_level = 1

        # Order of the stencil for z derivatives in the Maxwell solver
        if self.solver.stencil_order is None:
            n_order = -1
        else:
            n_order = self.solver.stencil_order[-1]

        # Number of guard cells
        if grid.guard_cells is None:
            n_guard = None
        else:
            n_guard = grid.guard_cells[-1]

        if self.solver.galilean_velocity is None:
            v_comoving = None
        else:
            v_comoving = self.solver.galilean_velocity[-1]

        # Initialize and store the FBPIC simulation object
        self.fbpic_sim = FBPICSimulation(
            Nz=int(grid.nz), zmin=grid.zmin, zmax=grid.zmax,
            Nr=int(grid.nr), rmax=grid.rmax, Nm=grid.n_azimuthal_modes,
            dt=dt, use_cuda=True, smoother=smoother, n_order=n_order,
            boundaries={'z':grid.bc_zmax, 'r':grid.bc_rmax},
            n_guard=n_guard, verbose_level=verbose_level,
            particle_shape=self.particle_shape,
            v_comoving=v_comoving,
            gamma_boost=self.gamma_boost )

        # Set the moving window
        if grid.moving_window_velocity is not None:
            self.fbpic_sim.set_moving_window(grid.moving_window_velocity[-1])

    # Redefine the method `add_laser` from the PICMI Simulation class
    def add_laser( self, laser, injection_method ):
        # Call method of parent class
        PICMI_Simulation.add_laser( self, laser, injection_method )

        # Handle injection method
        assert type(injection_method) == PICMI_LaserAntenna
        # Handle laser profile method
        if type(laser) == PICMI_GaussianLaser:
            assert laser.propagation_direction[0] == 0.
            assert laser.propagation_direction[1] == 0.
            assert (laser.zeta is None) or (laser.zeta == 0)
            assert (laser.beta is None) or (laser.beta == 0)
            phi2_chirp = laser.phi2
            if phi2_chirp is None:
                phi2_chirp = 0
            polarization_angle = np.arctan2(laser.polarization_direction[1],
                                            laser.polarization_direction[0])
            laser_profile = GaussianLaser( a0=laser.a0, waist=laser.waist,
                z0=laser.centroid_position[-1], zf=laser.focal_position[-1],
                tau=laser.duration, theta_pol=polarization_angle,
                phi2_chirp=phi2_chirp )
        else:
            raise ValueError('Unknown laser profile: %s' %type(injection_method))

        # Inject the laser
        add_laser_pulse( self.fbpic_sim, laser_profile, method='antenna',
            z0_antenna=injection_method.position[-1],
            gamma_boost=self.gamma_boost )

    # Redefine the method `add_species` from the PICMI Simulation class
    def add_species( self, species, layout, initialize_self_field=False ):
        # Call method of parent class
        PICMI_Simulation.add_species( self, species, layout,
                                      initialize_self_field )
        # Call generic method internally
        self._add_species_generic( species, layout,
            injection_plane_position=None, injection_plane_normal_vector=None,
            initialize_self_field=initialize_self_field )

    def add_species_through_plane( self, species, layout,
            injection_plane_position, injection_plane_normal_vector,
            initialize_self_field=False ):
        # Call method of parent class
        PICMI_Simulation.add_species_through_plane( self, species, layout,
            injection_plane_position, injection_plane_normal_vector,
            initialize_self_field=initialize_self_field )
        # Call generic method internally
        self._add_species_generic( species, layout,
            injection_plane_position=injection_plane_position,
            injection_plane_normal_vector=injection_plane_normal_vector,
            initialize_self_field=initialize_self_field )

    def _add_species_generic( self, species, layout, injection_plane_position,
        injection_plane_normal_vector, initialize_self_field ):

        # Extract list of species
        if type(species) == PICMI_Species:
            species_instances_list = [species]
        elif type(species) == PICMI_MultiSpecies:
            species_instances_list = species.species_instances_list
        else:
            raise ValueError('Unknown type: %s' %type(species))

        # Loop over species and create FBPIC species
        for s in species_instances_list:

            # Get their charge and mass
            if s.particle_type is not None:
                s.charge = particle_charge[s.particle_type]
                s.mass = particle_mass[s.particle_type]
            # If `charge_state` is set, redefine the charge and mass
            if s.charge_state is not None:
                s.charge = s.charge_state*e
                s.mass -= s.charge_state*m_e

            # Add the species to the FBPIC simulation
            fbpic_species = self._create_new_fbpic_species(s,
                 layout, injection_plane_position,
                 injection_plane_normal_vector, initialize_self_field)

            # Register a pointer to the FBPIC species in the PICMI species itself
            # (Useful for particle diagnostics later on)
            s.fbpic_species = fbpic_species

        # Loop over species and handle ionization
        for s in species_instances_list:
            for interaction in s.interactions:
                assert interaction[0] == 'ionization'
                assert interaction[1] == 'ADK'
                picmi_target = interaction[2]
                if not hasattr( picmi_target, 'fbpic_species' ):
                    raise RuntimeError('For ionization with PICMI+FBPIC:\n'
                        'You need to add the target species to the simulation,'
                        ' before the other species.')
                fbpic_target = picmi_target.fbpic_species
                fbpic_source = s.fbpic_species
                fbpic_source.make_ionizable( element=s.particle_type,
                                             level_start=s.charge_state,
                                             target_species=fbpic_target )

    def _create_new_fbpic_species(self, s, layout, injection_plane_position,
        injection_plane_normal_vector, initialize_self_field):

        # - For the case of a plasma/beam defined in a gridded layout
        if type(layout) == PICMI_GriddedLayout:
            # - Uniform distribution
            if type(s.initial_distribution)==PICMI_UniformDistribution:
                n0 = s.initial_distribution.density
                dens_func = None
            # - Analytic distribution
            elif type(s.initial_distribution)==PICMI_AnalyticDistribution:
                import numexpr
                density_expression = s.initial_distribution.density_expression
                if s.density_scale is not None:
                    n0 = s.density_scale
                else:
                    n0 = 1.
                def dens_func(x, y, z):
                    d = locals()
                    d.update( s.initial_distribution.user_defined_kw )
                    n = numexpr.evaluate( density_expression, local_dict=d )
                    return n
            else:
                raise ValueError('Unknown combination of layout and distribution')
            p_nr = layout.n_macroparticle_per_cell[0]
            p_nt = layout.n_macroparticle_per_cell[1]
            p_nz = layout.n_macroparticle_per_cell[2]

            if initialize_self_field or (injection_plane_position is not None):
                assert s.initial_distribution.fill_in != True

                if injection_plane_position is None:
                    z_injection_plane = None
                else:
                    z_injection_plane = injection_plane_position[-1]
                gamma0_beta0 = s.initial_distribution.directed_velocity[-1]/c
                gamma0 = ( 1 + gamma0_beta0**2 )**.5
                fbpic_species = add_particle_bunch( self.fbpic_sim,
                    q=s.charge, m=s.mass, gamma0=gamma0, n=n0,
                    dens_func=dens_func, p_nz=p_nz, p_nr=p_nr, p_nt=p_nt,
                    p_zmin=s.initial_distribution.lower_bound[-1],
                    p_zmax=s.initial_distribution.upper_bound[-1],
                    p_rmin=0,
                    p_rmax=s.initial_distribution.upper_bound[0],
                    boost=self.fbpic_sim.boost,
                    z_injection_plane=z_injection_plane,
                    initialize_self_field=initialize_self_field,
                    boost_positions_in_dens_func=True )
            else:
                fbpic_species = self.fbpic_sim.add_new_species(
                    q=s.charge, m=s.mass, n=n0,
                    dens_func=dens_func, p_nz=p_nz, p_nr=p_nr, p_nt=p_nt,
                    p_zmin=s.initial_distribution.lower_bound[-1],
                    p_zmax=s.initial_distribution.upper_bound[-1],
                    p_rmax=s.initial_distribution.upper_bound[0],
                    continuous_injection=s.initial_distribution.fill_in,
                    boost_positions_in_dens_func=True )

        # - For the case of a Gaussian beam
        elif (type(s.initial_distribution)==PICMI_GaussianBunchDistribution) \
             and (type(layout) == PICMI_PseudoRandomLayout):
            dist = s.initial_distribution
            gamma0_beta0 = dist.centroid_velocity[-1]/c
            gamma0 = ( 1 + gamma0_beta0**2 )**.5
            sig_r = dist.rms_bunch_size[0]
            sig_z = dist.rms_bunch_size[-1]
            sig_gamma = dist.rms_velocity[-1]/c
            sig_vr = dist.rms_velocity[0] / gamma0
            if sig_vr != 0:
                tf = - sig_r**2/sig_vr**2 * dist.velocity_divergence[0]
            else:
                tf = 0.
            zf = dist.centroid_position[-1] + \
                 dist.centroid_velocity[-1]/gamma0 * tf
            # Calculate size at focus and emittance
            sig_r0 = (sig_r**2 - (sig_vr*tf)**2)**0.5
            n_emit = gamma0 * sig_r0 * sig_vr/c
            # Get the number of physical particles
            n_physical_particles = dist.n_physical_particles
            if s.density_scale is not None:
                n_physical_particles *= s.density_scale
            fbpic_species = add_particle_bunch_gaussian( self.fbpic_sim,
                                q=s.charge, m=s.mass,
                                gamma0=gamma0, sig_gamma=sig_gamma,
                                sig_r=sig_r0, sig_z=sig_z, n_emit=n_emit,
                                n_physical_particles=n_physical_particles,
                                n_macroparticles=layout.n_macroparticles,
                                zf=zf, tf=tf, boost=self.fbpic_sim.boost,
                                initialize_self_field=initialize_self_field )

        # - For the case of an empty species
        elif (s.initial_distribution is None) and (layout is None):
            fbpic_species = self.fbpic_sim.add_new_species(q=s.charge, m=s.mass)

        else:
            raise ValueError('Unknown combination of layout and distribution')

        return fbpic_species

    # Redefine the method `add_diagnostic` of the parent class
    def add_diagnostic(self, diagnostic):
        # Call method of parent class
        PICMI_Simulation.add_diagnostic( self, diagnostic )

        # Handle iteration_min/max in regular diagnostic
        if type(diagnostic) in [PICMI_FieldDiagnostic, PICMI_ParticleDiagnostic]:
            if diagnostic.step_min is None:
                iteration_min = 0
            else:
                iteration_min = diagnostic.step_min
            if diagnostic.step_max is None:
                iteration_max = np.inf
            else:
                iteration_max = diagnostic.step_max

        # Register field diagnostic
        if type(diagnostic) in [PICMI_FieldDiagnostic, PICMI_LabFrameFieldDiagnostic]:
            if diagnostic.data_list is None:
                data_list = ['rho', 'E', 'B', 'J']
            else:
                data_list = set()  # Use set to avoid redundancy
                for data in diagnostic.data_list:
                    if data in ['Ex', 'Ey', 'Ez']:
                        data_list.add('E')
                    elif data in ['Bx', 'By', 'Bz']:
                        data_list.add('B')
                    elif data in ['Jx', 'Jy', 'Jz']:
                        data_list.add('J')
                    elif data == 'rho':
                        data_list.add('rho')
                data_list = list(data_list)

        if type(diagnostic) == PICMI_FieldDiagnostic:

            diag = FieldDiagnostic(
                    period=diagnostic.period,
                    fldobject=self.fbpic_sim.fld,
                    comm=self.fbpic_sim.comm,
                    fieldtypes=data_list,
                    write_dir=diagnostic.write_dir,
                    iteration_min=iteration_min,
                    iteration_max=iteration_max)

            # Register particle density diagnostic
            rho_density_list = []
            if diagnostic.data_list is not None:
                for data in diagnostic.data_list:
                    if data.startswith('rho_'):
                        # particle density diagnostics, rho_speciesname
                        rho_density_list.append(data)
            if rho_density_list:
                species_dict = {}
                for data in rho_density_list:
                    sname = data[4:]
                    for s in self.species:
                        if s.name == sname:
                            species_dict[s.name] = s.fbpic_species
                pdd_diag = ParticleChargeDensityDiagnostic(
                            period=diagnostic.period,
                            sim=self.fbpic_sim,
                            species=species_dict,
                            write_dir=diagnostic.write_dir,
                            iteration_min=iteration_min,
                            iteration_max=iteration_max)
                self.fbpic_sim.diags.append( pdd_diag )

        elif type(diagnostic) == PICMI_LabFrameFieldDiagnostic:
            diag = BackTransformedFieldDiagnostic(
                    zmin_lab=diagnostic.grid.zmin,
                    zmax_lab=diagnostic.grid.zmax,
                    v_lab=c,
                    dt_snapshots_lab=diagnostic.dt_snapshots,
                    Ntot_snapshots_lab=diagnostic.num_snapshots,
                    gamma_boost=self.gamma_boost,
                    period=100,
                    fldobject=self.fbpic_sim.fld,
                    comm=self.fbpic_sim.comm,
                    fieldtypes=diagnostic.data_list,
                    write_dir=diagnostic.write_dir)
        # Register particle diagnostic
        elif type(diagnostic) in [PICMI_ParticleDiagnostic,
                                  PICMI_LabFrameParticleDiagnostic]:
            species_dict = {}
            for s in diagnostic.species:
                if s.name is None:
                    raise ValueError('When using a species in a diagnostic, '
                                      'its name must be set.')
                species_dict[s.name] = s.fbpic_species
            if diagnostic.data_list is None:
                data_list = ['position', 'momentum', 'weighting']
            else:
                data_list = diagnostic.data_list
            if type(diagnostic) == PICMI_ParticleDiagnostic:
                diag = ParticleDiagnostic(
                    period=diagnostic.period,
                    species=species_dict,
                    comm=self.fbpic_sim.comm,
                    particle_data=data_list,
                    write_dir=diagnostic.write_dir,
                    iteration_min=iteration_min,
                    iteration_max=iteration_max)
            else:
                diag = BackTransformedParticleDiagnostic(
                    zmin_lab=diagnostic.grid.zmin,
                    zmax_lab=diagnostic.grid.zmax,
                    v_lab=c,
                    dt_snapshots_lab=diagnostic.dt_snapshots,
                    Ntot_snapshots_lab=diagnostic.num_snapshots,
                    gamma_boost=self.gamma_boost,
                    period=100,
                    fldobject=self.fbpic_sim.fld,
                    species=species_dict,
                    comm=self.fbpic_sim.comm,
                    particle_data=data_list,
                    write_dir=diagnostic.write_dir)

        # Add it to the FBPIC simulation
        self.fbpic_sim.diags.append( diag )

    # Redefine the method `add_diagnostic` of the parent class
    def add_applied_field(self, applied_field):
        # Call method of parent class
        PICMI_Simulation.add_applied_field( self, applied_field )

        if type(applied_field) == PICMI_Mirror:
            assert applied_field.z_front_location is not None
            mirror = Mirror( z_lab=applied_field.z_front_location,
                             n_cells=applied_field.number_of_cells,
                             gamma_boost=self.fbpic_sim.boost.gamma0 )
            self.fbpic_sim.mirrors.append( mirror )

        elif type(applied_field) == PICMI_ConstantAppliedField:
            # TODO: Handle bounds
            for field_name in ['Ex', 'Ey', 'Ez', 'Bx', 'By', 'Bz']:
                field_value = getattr( applied_field, field_name )
                if field_value is None:
                    continue
                def field_func( F, x, y, z, t, amplitude, length_scale ):
                    return( F + amplitude * field_value )
                # Pass it to FBPIC
                self.fbpic_sim.external_fields.append(
                    ExternalField( field_func, field_name, 1., 0.)
                )

        elif type(applied_field) == PICMI_AnalyticAppliedField:
            # TODO: Handle bounds
            for field_name in ['Ex', 'Ey', 'Ez', 'Bx', 'By', 'Bz']:
                # Extract expression and execute it inside a function definition
                expression = getattr( applied_field, field_name+'_expression' )
                if expression is None:
                    continue
                fieldfunc = None
                define_function_code = \
                """def fieldfunc( F, x, y, z, t, amplitude, length_scale ):\n    return( F + amplitude * %s )""" %expression
                # Take into account user-defined variables
                for k in applied_field.user_defined_kw:
                    define_function_code = \
                        "%s = %s\n" %(k,applied_field.user_defined_kw[k]) \
                        + define_function_code
                exec( define_function_code, globals() )
                # Pass it to FBPIC
                self.fbpic_sim.external_fields.append(
                    ExternalField( fieldfunc, field_name, 1., 0.)
                )

        else:
            raise ValueError("Unrecognized `applied_field` type.")

    # Redefine the method `step` of the parent class
    def step(self, nsteps=None):
        if nsteps is None:
            nsteps = self.max_steps
        self.fbpic_sim.step( nsteps )


from __future__ import print_function
import numpy as np
import os
import threading

from navrep.tools.rings import generate_rings
from navrep.models.rnn import reset_graph, sample_hps_params, MDNRNN, get_pi_idx, MAX_GOAL_DIST
from navrep.models.vae2d import ConvVAE

_Z = 32
_G = 2

class DreamEnv(object):
    def __init__(self, temperature=0.25,
                 initial_z_path=os.path.expanduser(
                     "~/navrep/datasets/M/ian/000_mus_logvars_robotstates_actions_rewards_dones.npz"
                 ),
                 rnn_model_path=os.path.expanduser("~/navrep/models/M/rnn.json"),
                 vae_model_path=os.path.expanduser("~/navrep/models/V/vae.json"),
                 ):
        # constants
        self.TEMPERATURE = temperature
        self.DT = 0.5  # should be the same as data rnn was trained with
        # V + M Models
        reset_graph()
        self.rnn = MDNRNN(sample_hps_params, gpu_mode=False)
        self.vae = ConvVAE(batch_size=1, is_training=False)
        self.vae.load_json(vae_model_path)
        self.rnn.load_json(rnn_model_path)
        # load initial image encoding
        arrays = np.load(initial_z_path)
        initial_mu = arrays["mus"][0]
        initial_logvar = arrays["logvars"][0]
        initial_robotstate = arrays["robotstates"][0]
        ini_lidar_z = initial_mu + np.exp(initial_logvar / 2.0) * np.random.randn(
            *(initial_mu.shape)
        )
        ini_goal_z = initial_robotstate[:2] / MAX_GOAL_DIST
        self.initial_z = np.concatenate([ini_lidar_z, ini_goal_z], axis=-1)
        # other tools
        self.rings_def = generate_rings(64, 64)
        self.viewer = None
        # environment state variables
        self.reset()
        # hot-start the rnn state
        for i in range(20):
            self.step(np.array([0,0,0]), override_next_z=self.initial_z)

    def step(self, action, override_next_z=None):
        feed = {
            self.rnn.input_z: np.reshape(self.prev_z, (1, 1, _Z+_G)),
            self.rnn.input_action: np.reshape(action, (1, 1, 3)),
            self.rnn.input_restart: np.reshape(self.prev_restart, (1, 1)),
            self.rnn.initial_state: self.rnn_state,
        }

        [logmix, mean, logstd, logrestart, next_state] = self.rnn.sess.run(
            [
                self.rnn.out_logmix,
                self.rnn.out_mean,
                self.rnn.out_logstd,
                self.rnn.out_restart_logits,
                self.rnn.final_state,
            ],
            feed,
        )
        OUTWIDTH = _Z+_G

        if self.TEMPERATURE == 0:  # deterministically pick max of MDN distribution
            mixture_idx = np.argmax(logmix, axis=-1)
            chosen_mean = mean[(range(OUTWIDTH), mixture_idx)]
            chosen_logstd = logstd[(range(OUTWIDTH), mixture_idx)]
            next_z = chosen_mean
        else:  # sample from modelled MDN distribution
            mixprob = np.copy(logmix) / self.TEMPERATURE  # adjust temperatures
            mixprob -= mixprob.max()
            mixprob = np.exp(mixprob)
            mixprob /= mixprob.sum(axis=1).reshape(OUTWIDTH, 1)

            mixture_idx = np.zeros(OUTWIDTH)
            chosen_mean = np.zeros(OUTWIDTH)
            chosen_logstd = np.zeros(OUTWIDTH)
            for j in range(OUTWIDTH):
                idx = get_pi_idx(np.random.rand(), mixprob[j])
                mixture_idx[j] = idx
                chosen_mean[j] = mean[j][idx]
                chosen_logstd[j] = logstd[j][idx]
            rand_gaussian = np.random.randn(OUTWIDTH) * np.sqrt(self.TEMPERATURE)
            next_z = chosen_mean + np.exp(chosen_logstd) * rand_gaussian
        if sample_hps_params.differential_z:
            next_z = self.prev_z + next_z

        next_restart = 0
        #         if logrestart[0] > 0:
        #             next_restart = 1

        self.prev_z = next_z
        if override_next_z is not None:
            self.prev_z = override_next_z
        self.prev_restart = next_restart
        self.rnn_state = next_state
        # logging-only vars, used for rendering
        self.prev_action = action
        self.episode_step += 1

        return next_z, None, next_restart, {}

    def reset(self):
        self.prev_z = self.initial_z
        self.prev_restart = np.array([1])
        self.rnn_state = self.rnn.sess.run(self.rnn.zero_state)
        # logging vars
        self.prev_action = np.array([0.0, 0.0, 0.0])
        self.episode_step = 0

    def render(self, mode="human", close=False):
        if close:
            if self.viewer is not None:
                self.viewer.close()
            return

        # get last z decoding
        rings_pred = (
            self.vae.decode(self.prev_z.reshape(1, _Z+_G)[:, :_Z])
            * self.rings_def["rings_to_bool"]
        )
        predicted_ranges = self.rings_def["rings_to_lidar"](rings_pred, 1080)
        goal_pred = self.prev_z.reshape((_Z+_G,))[_Z:] * MAX_GOAL_DIST

        if mode == "rgb_array":
            raise NotImplementedError
        elif mode == "human":
            # Window and viewport size
            WINDOW_W = 256
            WINDOW_H = 256
            M_PER_PX = 25.6 / WINDOW_H
            VP_W = WINDOW_W
            VP_H = WINDOW_H
            from gym.envs.classic_control import rendering
            import pyglet
            from pyglet import gl

            # Create viewer
            if self.viewer is None:
                self.viewer = rendering.Viewer(WINDOW_W, WINDOW_H)
                self.score_label = pyglet.text.Label(
                    "0000",
                    font_size=12,
                    x=20,
                    y=WINDOW_H * 2.5 / 40.00,
                    anchor_x="left",
                    anchor_y="center",
                    color=(255, 255, 255, 255),
                )
                #                 self.transform = rendering.Transform()
                self.currently_rendering_iteration = 0
                self.image_lock = threading.Lock()
            # Render in pyglet
            def make_circle(c, r, res=10):
                thetas = np.linspace(0, 2 * np.pi, res + 1)[:-1]
                verts = np.zeros((res, 2))
                verts[:, 0] = c[0] + r * np.cos(thetas)
                verts[:, 1] = c[1] + r * np.sin(thetas)
                return verts

            with self.image_lock:
                self.currently_rendering_iteration += 1
                self.viewer.draw_circle(r=10, color=(0.3, 0.3, 0.3))
                win = self.viewer.window
                win.switch_to()
                win.dispatch_events()
                win.clear()
                gl.glViewport(0, 0, VP_W, VP_H)
                # colors
                bgcolor = np.array([0.4, 0.8, 0.4])
                nosecolor = np.array([0.3, 0.3, 0.3])
                lidarcolor = np.array([1.0, 0.0, 0.0])
                # Green background
                gl.glBegin(gl.GL_QUADS)
                gl.glColor4f(bgcolor[0], bgcolor[1], bgcolor[2], 1.0)
                gl.glVertex3f(0, VP_H, 0)
                gl.glVertex3f(VP_W, VP_H, 0)
                gl.glVertex3f(VP_W, 0, 0)
                gl.glVertex3f(0, 0, 0)
                gl.glEnd()
                # LIDAR
                i = WINDOW_W / 2.0
                j = WINDOW_H / 2.0
                angle = np.pi / 2.0
                scan = np.squeeze(predicted_ranges)
                lidar_angles = np.linspace(0, 2 * np.pi, len(scan) + 1)[:-1]
                lidar_angles = lidar_angles + np.pi / 2.  # make robot face up
                i_ray_ends = i + scan / M_PER_PX * np.cos(lidar_angles)
                j_ray_ends = j + scan / M_PER_PX * np.sin(lidar_angles)
                is_in_fov = np.cos(lidar_angles - angle) >= 0.78
                for ray_idx in range(len(scan)):
                    end_i = i_ray_ends[ray_idx]
                    end_j = j_ray_ends[ray_idx]
                    gl.glBegin(gl.GL_LINE_LOOP)
                    if is_in_fov[ray_idx]:
                        gl.glColor4f(1.0, 1.0, 0.0, 0.1)
                    else:
                        gl.glColor4f(lidarcolor[0], lidarcolor[1], lidarcolor[2], 0.1)
                    gl.glVertex3f(i, j, 0)
                    gl.glVertex3f(end_i, end_j, 0)
                    gl.glEnd()
                # Agent body
                i = WINDOW_W / 2.0
                j = WINDOW_H / 2.0
                r = 0.3 / M_PER_PX
                angle = np.pi / 2.0
                poly = make_circle((i, j), r)
                gl.glBegin(gl.GL_POLYGON)
                color = np.array([1.0, 1.0, 1.0])
                gl.glColor4f(color[0], color[1], color[2], 1)
                for vert in poly:
                    gl.glVertex3f(vert[0], vert[1], 0)
                gl.glEnd()
                # Direction triangle
                inose = i + r * np.cos(angle)
                jnose = j + r * np.sin(angle)
                iright = i + 0.3 * r * -np.sin(angle)
                jright = j + 0.3 * r * np.cos(angle)
                ileft = i - 0.3 * r * -np.sin(angle)
                jleft = j - 0.3 * r * np.cos(angle)
                gl.glBegin(gl.GL_TRIANGLES)
                gl.glColor4f(nosecolor[0], nosecolor[1], nosecolor[2], 1)
                gl.glVertex3f(inose, jnose, 0)
                gl.glVertex3f(iright, jright, 0)
                gl.glVertex3f(ileft, jleft, 0)
                gl.glEnd()
                # Goal
                goalcolor = np.array([1., 1., 0.3])
                px_goal = goal_pred / M_PER_PX
                igoal = i - px_goal[1]  # rotate 90deg to face up
                jgoal = j + px_goal[0]
                # Goal line
                gl.glBegin(gl.GL_LINE_LOOP)
                gl.glColor4f(goalcolor[0], goalcolor[1], goalcolor[2], 1)
                gl.glVertex3f(i, j, 0)
                gl.glVertex3f(igoal, jgoal, 0)
                gl.glEnd()
                # Goal markers
                gl.glBegin(gl.GL_TRIANGLES)
                gl.glColor4f(goalcolor[0], goalcolor[1], goalcolor[2], 1)
                triangle = make_circle((igoal, jgoal), r/3., res=3)
                for vert in triangle:
                    gl.glVertex3f(vert[0], vert[1], 0)
                gl.glEnd()
                # Text
                self.score_label.text = "A {:.1f} {:.1f} {:.1f} S {}".format(
                    self.prev_action[0],
                    self.prev_action[1],
                    self.prev_action[2],
                    self.episode_step,
                )
                self.score_label.draw()
                win.flip()
                return self.viewer.isopen

    def close(self):
        self.render(close=True)

    def _get_dt(self):
        return self.DT

    def _get_viewer(self):
        return self.viewer


#!/usr/bin/python
#
# Batch multifit analysis.
#
# Colenso 05_30_18

import glob
import os, time, datetime
import multiprocessing
import signal
import subprocess
import sys
import threading

import numpy
import math
from subprocess import *
import datareader
import re
import arraytoimage
import i3togrid
from PIL import Image
import storm_analysis.sa_library.sa_h5py as saH5Py

import storm_analysis.sa_library.batch_run as batchRun
import storm_analysis.sa_library.datareader as datareader
import storm_analysis.Name_Correction as N_C

from .gen_bead_warp_hcam import *

def batch_fitting(expfolder, max_processes, selected_channels, debug=False):
    # set path to raw data files
    #expfolder = "Z:\\Chenghang\\chenghaz006_1.31.2019\\"
    expfolder = os.path.normpath(expfolder)
    XML_folder = expfolder + "\\XMLs\\"

    # set directory for raw STORM .DAX files
    input_directory = expfolder + "\\acquisition\\"

    #Correct the file names if there is less than 11 images taken. (might need modification if there are more than 100 images in the future)
    # h5_files =glob.glob(input_directory + "647storm_" + "*.dax")
    # test = os.path.basename(h5_files[0])
    # if len(test) == 14:
        # N_C.CorrectIt(input_directory)

    # make directory for bead registration output
    # create new directory for bead alignment files
    if not os.path.exists(input_directory + "bead_registration\\"):
        os.mkdir(input_directory + "bead_registration\\")
    bead_registration = input_directory + "bead_registration\\"

    # create new directory for bin files
    if not os.path.exists(input_directory + "bins\\"):
        os.mkdir (input_directory + "bins\\")
    bin_folder = input_directory + "bins\\"

    # If there are no previous STORM molecule lists found, start the STORM analysis
    cmd_lines = []
    channels = selected_channels

    for channel in channels:

        if len(glob.glob(bin_folder + "{}storm_*_mlist.bin".format(channel))) == 0:

            base = str(channel)
            # set output directory for analyzed molecule lists
            output_directory = input_directory + "bins\\"
            # set path to analysis parameters
            multi_xml = XML_folder + base + ".xml"
            # set minimum length of .DAX file for analysis
            minimum_length = 0
            # create list of all .DAX files in STORM directory
            dax_files =glob.glob(input_directory + base + "*.dax")
            # set path to multi-fit analysis code
            mufit_exe = "C:/Program Files/Python36/Lib/site-packages/storm_analysis/sCMOS/scmos_analysis.py"

            # start processes
            # for each STORM movie
            for movie_file in dax_files:
                # read the file information and determine if it is long enough
                movie_obj = datareader.inferReader(movie_file)
                if(movie_obj.filmSize()[2] > minimum_length):
                    # set file name parameters for jobs
                    print("Analyzing:", movie_file,"with xml file",multi_xml)
                    basename = os.path.basename(movie_file)
                    mlistname = output_directory + "/" + basename[:-4] + "_mlist.hdf5"
                    # start fitting
                    cmd_lines.append(['python', mufit_exe,
                                      "--movie", movie_file,
                                      "--bin", mlistname,
                                      "--xml", multi_xml])
        # run the fitting in a batch with the specified number of concurrent processes
        batchRun.batchRun(cmd_lines, max_processes = max_processes)

    # If there are no previous bead molecule lists found, start the bead analysis
    if not os.path.isfile(bin_folder + "Regbead_1_00_647mlist.bin"):

        # set bead fitting channels
        channel = "Regbead"

        # setup automated bead analysis
        base = str(channel)
        # set output directory for analyzed molecule lists
        output_directory = input_directory + "bins\\"
        # set path to analysis parameters
        xml = ["Visbead_647.xml","Visbead_561.xml","Visbead_488.xml","IRbead_750.xml","IRbead_647.xml"]

        # set minimum length of .DAX file for analysis
        minimum_length = 1

        # create list of all .DAX files in bead directory
        dax_files =glob.glob(input_directory + base + "*.dax")
        #print (dax_files)

        # set path to multi-fit analysis code
        mufit_exe = "C:/Program Files/Python36/Lib/site-packages/storm_analysis/sCMOS/scmos_analysis.py"

        # start processes
        cmd_lines = []
        for movie_file in dax_files:
            movie_name = os.path.basename(movie_file[:-4])
            # run analysis on Visbeads
            movie_obj = datareader.inferReader(movie_file)
            if(movie_obj.filmSize()[2] > minimum_length):

                basename = os.path.basename(movie_file)
                mlistname = output_directory + "/" + basename[:-4]
                # fit 647 portion of Visbead dax file
                cmd_lines.append(['python', mufit_exe,
                                  "--movie", movie_file,
                                  "--bin", mlistname + "_Vis647mlist.hdf5",
                                  "--xml", XML_folder + xml[0]])
                print("Analyzing:", movie_file,"with xml file",xml[0])
                # fit 561 portion of Visbead dax file
                cmd_lines.append(['python', mufit_exe,
                                  "--movie", movie_file,
                                  "--bin", mlistname + "_561mlist.hdf5",
                                  "--xml", XML_folder + xml[1]])
                print("Analyzing:", movie_file,"with xml file",xml[1])
                # fit 488 portion of Visbead dax file
                cmd_lines.append(['python', mufit_exe,
                                  "--movie", movie_file,
                                  "--bin", mlistname + "_488mlist.hdf5",
                                  "--xml", XML_folder + xml[2]])
                print("Analyzing:", movie_file,"with xml file",xml[2])

                mlistname = output_directory + "/" + basename[:-4]
                # fit 750 portion of IRbead dax file
                cmd_lines.append(['python', mufit_exe,
                                  "--movie", movie_file,
                                  "--bin", mlistname + "_750mlist.hdf5",
                                  "--xml", XML_folder + xml[3]])
                print("Analyzing:", movie_file,"with xml file",xml[3])

                mlistname = output_directory + "/" + basename[:-4]
                # fit 750 portion of IRbead dax file
                cmd_lines.append(['python', mufit_exe,
                                  "--movie", movie_file,
                                  "--bin", mlistname + "_IR647mlist.hdf5",
                                  "--xml", XML_folder + xml[4]])
                print("Analyzing:", movie_file,"with xml file",xml[4])

        batchRun.batchRun(cmd_lines, max_processes = max_processes)

    # where are your data?
    storm_folder = expfolder + "\\acquisition\\"
    input_directory = storm_folder + "bins\\"
    output_directory = storm_folder + "real_bins\\"

    # Mkdir for the output folder.
    if not os.path.exists(output_directory):
        os.mkdir(output_directory)

    # which imaging channels are you saving?
    channels = ["750storm", "647storm", "561storm","488storm"]

    # setup automated analysis
    for channel in channels:
     #for  i in range (iterations):
        base = str(channel)
     # create list of all .hdf5 files in BIN directory
        h5_files =glob.glob(input_directory + base + "*.hdf5")
     # set path to multi-fit analysis code
        hdf5_to_bin = "C:/Program Files/Python36/Lib/site-packages/storm_analysis/sa_utilities/hdf5_to_bin.py"

     # start processes
        cmd_lines = []
        for h5_file in h5_files:

            print ("Found:", h5_file)
            h5_filename = os.path.basename(h5_file)
            print("Converting:",h5_filename)
            bin_out_name = output_directory + h5_filename[:-5] + ".bin"
            cmd_lines.append(['python', hdf5_to_bin,
                                "--hdf5", h5_file,
                                "--bin", bin_out_name
                                ])
        batchRun.batchRun(cmd_lines, max_processes = max_processes)

    # Convert the .hdf5 files to .tiff images.
    storm_image_scale = int(10)
    # where do you want to save the data?
    output_directory = expfolder + "\\stormtiffs\\"
    # Mkdir for the output folder. --- 7.30.2018.
    if not os.path.exists(output_directory):
        os.mkdir(output_directory)
    # which imaging channels are you saving?
    channels = ["750storm", "647storm", "561storm","488storm"]
    # what sigma to use for rendering images in [channels]?
    sigma_channels = [1,0.5,0.5,1]
    # setup automated analysis
    for channel in channels:
     #for  i in range (iterations):
        base = str(channel)
     # create list of all .hdf5 files in BIN directory
        h5_files =glob.glob(input_directory + base + "*.hdf5")
     # set path to multi-fit analysis code
        hdf5_to_image = "C:/Program Files/Python36/Lib/site-packages/storm_analysis/sa_utilities/hdf5_to_image.py"

     # start processes
        cmd_lines = []
        for h5_file in h5_files:
            if base == "750storm":
                sigma = sigma_channels[0]
            elif base == "647storm":
                sigma = sigma_channels[1]
            elif base == "561storm":
                sigma = sigma_channels[2]
            elif base == "488storm":
                sigma = sigma_channels[3]

            print ("Found:", h5_file)
            h5_filename = os.path.basename(h5_file)
            print("Analyzing:",h5_filename,"with sigma ",str(sigma))
            tiff_out_name = output_directory + h5_filename[:-5] + ".tiff"
            cmd_lines.append(['python', hdf5_to_image,
                                "--image", tiff_out_name,
                                "--bin", h5_file,
                                "--scale", str(storm_image_scale),
                                "--sigma", str(sigma)])
        batchRun.batchRun(cmd_lines, max_processes = max_processes)

    # perform bead registration

    # MATLAB Code
    # print ("starting matlab gen_bead_warp analysis")
    # if not os.path.isfile(bead_registration + 'Cumulative_distribution_for_registration_self.tif'):
    #    matsub = ("""matlab -nosplash -nodisplay -r "arg1='""" + expfolder + """'; gen_bead_warp_hcam" """)
    #    subprocess.Popen(matsub ,shell=True)

    # while not os.path.isfile(bead_registration + 'Cumulative_distribution_for_registration_self.tif'):
    #     time.sleep(10)
    # print ("Bead alignment is finished")
    # print("The test part is skipped" + "\n" + "Done! ")

    #The following codes are for testing purpose. No need if everything works well in previous codes.
    #Note that the following code won't end as expected for a customized because the test images are saved under 'Z:\Colenso\05_25_18_sample5\acquisition\bead_registration'

        #test warping transform on beads (only if two bead passes)
    #if not os.path.isfile(bead_registration + 'Cumulative_distribution_for_registration_1_based_on_2.fig'):
    #    print ("starting matlab_test_bead_warp")
    #    matsub = ("""matlab -nosplash -nodisplay -r "arg1='""" + expfolder + """'; test_bead_warp_hcam" """)
    #    subprocess.Popen(matsub ,shell=True)

    #while not os.path.isfile(bead_registration + 'Cumulative_distribution_for_registration_1_based_on_2.fig'):
    #    time.sleep(10)
    #print ("Bead testing is finished")
if __name__ == "__main__":
    exp_folder = "C:/Users/Vatsal/QT_Projects/New_Pipeline/4color_test"
    # exp_folder = "C:/Users/Jerry Yang/Desktop/Test_bead_data"
    selected_channels = ['750storm_']
    max_processes = 50
    batch_fitting(exp_folder, max_processes, selected_channels, debug=True)