import os
import cv2
import json
import math
import time
import numpy as np
from tqdm import tqdm
from datetime import datetime
from collections import Counter
from scipy.spatial import distance as dist

class OpticalTrack():
    previewWindow = False
    savevid = False

    inited = False  # if first tracking feature points is found
    index = 0       # frame id
    fps = 25        # frame FPS

    # params for Shi-Tomasi corner detection
    shitomasi_params = {"qualityLevel": 0.1,"minDistance": 7,"blockSize": 7}

    # params for Lucas-Kanade optical flow
    criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
    LK_params = {"winSize": (15,15),"maxLevel": 2,"criteria": criteria}

    # params for visualization
    color = np.random.randint(0,255,(100,3))
    trailThickness = 8  # thickness of the trail to draw behind the target
    trailFade = 4       # the intensity at which the trail fades
    pointSize = 15      # pixel radius of the circle to draw over tracked points

    orient_num = 100    # the record number of frame's orient in the past time

    _UP      = -1   # ↑, up state
    _STOP    = 0    # -, stop state
    _DOWN    = 1    # ↓, down state
    _UNSURE  = 2    # unknown state

    def __init__(self,numPts=30,trailLength=50,conf_thresh=0.9,roi=None,roiJsonFile=None,partRate=0.5) -> None:
        self.numPts = numPts            # max number of points to track
        self.trailLength = trailLength  # numeber of past time frames to keep
        self.conf_thresh = conf_thresh  # confidence threshold
        self.roiJsonFile = roiJsonFile  # seg result json file from whitch the roi will be get
        self.part_rate = partRate       # step roi area to be part

        self.orients_previous = np.zeros((1,2))     # overall oriention vector of the previous time
        self.orient_history = [0 for i in range(self.orient_num)] # orientation history of past frames
        self.trail_states = np.zeros((numPts,2))    # whether a point is still being tracked
        self.trail_history = [[[(0,0), (0,0)] for j in range(trailLength)] for i in range(numPts)]

        if roi is None and isinstance(roiJsonFile,str):
            self.roi = roi_from_json(roiJsonFile,part_rate=partRate,rect=False)
        else:
            self.roi = roi

    def opticalTrack_old(self,camera_id,roi):
        start_time = time.time()

        # SETUP -----------------------------------------------------------------------
        # read the video file into memory
        cap = cv2.VideoCapture(camera_id)
        fps = cap.get(cv2.CAP_PROP_FPS)
        num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        pbar = tqdm(desc='FPS:0',total=num_frames)
        count = 1
        print("FPS: ",fps)

        # get the first frame
        _, old_frame = cap.read()
        old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

        # create crosshair mask
        crosshairmask = np.zeros_like(old_gray)
        crosshairmask = cv2.fillConvexPoly(crosshairmask,roi,color=255)
        visualmask = cv2.fillConvexPoly(np.zeros_like(old_frame),roi,color=(255,255,255))

        # create roi mask
        mask = cv2.fillConvexPoly(np.zeros_like(old_gray),roi,color=255)

        # # get trail and orient info
        # orients_previous = self.orients_previous
        # orient_history = self.orient_history
        # trail_states = self.trail_states
        # trail_history = self.trail_history

        # get features from first frame
        print(f"\nRunning Optical Flow on: {camera_id}")
        old_points = cv2.goodFeaturesToTrack(old_gray, maxCorners=self.numPts, mask=crosshairmask, **self.shitomasi_params)
        points_id = np.arange(len(old_points),dtype=np.int32).reshape(len(old_points),1)  # feature point id in trail_history

        # if saving video
        if self.savevid:
            # path to save output video
            savepath = camera_id.split('.')[-2] + '_LK_FLOW' + '.mp4'
            print(f"Saving Output video to: {savepath}")

            # get shape of video frames
            height, width, _ = old_frame.shape

            # setup videowriter object
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            videoOut = cv2.VideoWriter(savepath, fourcc, fps, (width, height))

        # PROCESS VIDEO ---------------------------------------------------------------
        while(True):
            start = time.time()
            # get next frame and convert to grayscale
            stillGoing, new_frame = cap.read()

            # if video is over, quit
            if not stillGoing:
                break
            else:
                count += 1

            # convert to grayscale
            new_frame_gray = cv2.cvtColor(new_frame, cv2.COLOR_BGR2GRAY)

            # calculate optical flow
            new_points, st, err = cv2.calcOpticalFlowPyrLK(old_gray, new_frame_gray, old_points, None, **self.LK_params)

            # select good points
            if old_points is not None:
                good_new = new_points[st==1]
                good_old = old_points[st==1]
                good_id = points_id[st==1]

            # create trail mask to add to image
            trailMask = np.zeros_like(old_frame)

            # calculate motion lines and points, and check points according to history trail
            valid_ids = []
            for i,(new,old) in enumerate(zip(good_new, good_old)):
                # flatten coords
                a,b = new.ravel()
                c,d = old.ravel()
                k = good_id[i]

                # list of the prev and current points converted to int
                linepts = [(int(a),int(b)), (int(c),int(d))]

                # add points to the trail history
                self.trail_history[k].insert(0, linepts)

                # check valid of trail
                isvalid = self.check_flow(self.trail_history[k])
                if isvalid:
                    valid_ids.append(i)
                    # points update times
                    self.trail_states[k,1] += 1

                # add trail lines to image
                if self.previewWindow and isvalid:
                    self.draw_trail(trailMask,self.trail_history,k,new_frame)

                # get rid of the trail segment
                self.trail_history[k].pop()

            # set trail states, 0 for missed, 1 for still being tracked
            self.trail_states[:, 0] = 0
            self.trail_states[good_id[valid_ids], 0] = 1

            # filter invalid flow
            self.trail_history = self.filter_invalid_flow(self.trail_history,lateral_thresh=50)

            # Calculation direction
            orient = self.get_orientation(self.trail_history,self.trail_states,self.orients_previous,count)
            orient = self.update_orient(self.orient_history,orient)
            if orient==1:
                cv2.putText(new_frame,'down',(20,50),cv2.LINE_AA,2,(255,0,0),2)
            elif orient==-1:
                cv2.putText(new_frame,'up',(20,50),cv2.LINE_AA,2,(255,0,0),2)
            else:
                cv2.putText(new_frame,'-',(20,50),cv2.LINE_AA,2,(255,0,0),2)

            # add trail to frame
            new_frame = cv2.addWeighted(new_frame,0.7,visualmask,0.3,0)
            img = cv2.add(new_frame, trailMask)

            # show the frames
            if self.previewWindow:
                cv2.imshow('optical flow', img)

            # write frames to new output video
            if self.savevid:
                videoOut.write(img)

            # kill window if ESC is pressed
            rest = max(1/fps - (time.time()-start),1)
            k = cv2.waitKey(rest) & 0xff
            if k == 27:
                break

            # update previous frame and previous points
            old_gray = new_frame_gray.copy()
            old_points = good_new[valid_ids,...].reshape(-1,1,2)
            points_id = good_id[valid_ids,...].reshape(-1,1)

            # if old_points < numPts, get new points
            old_points,points_id = self.update_points(old_gray,old_points,points_id,mask,crosshairmask)
            # trail_states = self.trail_states
            # trail_history = self.trail_history

            FPS = 1/(time.time()-start)
            AVG_FPS = count/(time.time()-start_time)
            pbar.set_description_str('FPS:{:.0f} AVG_FPS:{:.2f} orient:{}'.format(FPS,AVG_FPS,orient))
            pbar.update()
            assert len(points_id) == len(old_points)
        # after video is finished
        pbar.close()
        print('\nComplete!\n')

    def opticalTrack(self,t_frame):
        assert self.roi is not None, f'ROI area is not set! Please use roi_from_json() to get roi firstly.'
        orient = None
        if t_frame is None:
            print("t_frame is None")
            return orient
        self.index += 1
        if not self.inited:
            self.old_frame = t_frame.copy()
            #self.roi = roi
            self.old_gray = cv2.cvtColor(self.old_frame, cv2.COLOR_BGR2GRAY)
            if self.old_gray is None:
                print("old_gray is None")
                return orient
            # create crosshair mask
            self.crosshairmask = np.zeros_like(self.old_gray)
            self.crosshairmask = cv2.fillConvexPoly(self.crosshairmask,self.roi,color=255)
            self.visualmask = cv2.fillConvexPoly(np.zeros_like(self.old_frame),self.roi,color=(255,255,255))

            # create roi mask
            self.mask = cv2.fillConvexPoly(np.zeros_like(self.old_gray),self.roi,color=255)

            # get features from first frame
            self.old_points = cv2.goodFeaturesToTrack(self.old_gray, maxCorners=self.numPts, mask=self.crosshairmask, **self.shitomasi_params)
            if self.old_points is None:
                print("self.old_points is None,Init Fail")
                return orient
            else:
                print("Init Success")
                self.inited = True
                self.start_time = time.time()
            self.points_id = np.arange(len(self.old_points),dtype=np.int32).reshape(len(self.old_points),1)  # feature point id in trail_history

            # if saving video
            if self.savevid:
                # path to save output video
                savepath = "data/" + datetime.now().strftime("%Y-%m-%d %H%M%S") + '_LK_FLOW' + '.mp4'
                print(f"Saving Output video to: {savepath}")

                # get shape of video frames
                height, width, _ = self.old_frame.shape

                # setup videowriter object
                fourcc = cv2.VideoWriter_fourcc(*'mp4v')
                self.videoOut = cv2.VideoWriter(savepath, fourcc, self.fps, (width, height))
        else:
            new_frame = t_frame.copy()
            # convert to grayscale
            new_frame_gray = cv2.cvtColor(new_frame, cv2.COLOR_BGR2GRAY)
            # PROCESS VIDEO ---------------------------------------------------------------
            # calculate optical flow
            new_points, st, err = cv2.calcOpticalFlowPyrLK(self.old_gray, new_frame_gray, self.old_points, None, **self.LK_params)
            # select good points
            if self.old_points is not None:
                good_new = new_points[st==1]
                good_old = self.old_points[st==1]
                good_id = self.points_id[st==1]
            # create trail mask to add to image
            trailMask = np.zeros_like(self.old_frame)
            # calculate motion lines and points, and check points according to history trail
            valid_ids = []
            for i,(new,old) in enumerate(zip(good_new, good_old)):
                # flatten coords
                a,b = new.ravel()
                c,d = old.ravel()
                k = good_id[i]

                # list of the prev and current points converted to int
                linepts = [(int(a),int(b)), (int(c),int(d))]

                # add points to the trail history
                self.trail_history[k].insert(0, linepts)

                # check valid of trail
                isvalid = self.check_flow(self.trail_history[k])
                if isvalid:
                    valid_ids.append(i)
                    # points update times
                    self.trail_states[k,1] += 1

                # add trail lines to image
                if self.previewWindow and isvalid:
                    self.draw_trail(trailMask,self.trail_history,k,new_frame)

                # get rid of the trail segment
                self.trail_history[k].pop()

            # set trail states, 0 for missed, 1 for still being tracked
            self.trail_states[:, 0] = 0
            self.trail_states[good_id[valid_ids], 0] = 1

            # filter invalid flow
            self.trail_history = self.filter_invalid_flow(self.trail_history,lateral_thresh=50)

            # Calculation direction
            orient_one = self.get_orientation(self.trail_history,self.trail_states,self.orients_previous,self.index)
            orient,conf,count_info = self.update_orient(self.orient_history, orient_one, self.conf_thresh)
            if self.index<self.orient_num:
                orient = None

            if orient==1:
                cv2.putText(new_frame,'down' + str(self.index),(20,50),cv2.LINE_AA,2,(255,0,0),2)
            elif orient==-1:
                cv2.putText(new_frame,'up' + str(self.index),(20,50),cv2.LINE_AA,2,(255,0,0),2)
            elif orient==0:
                cv2.putText(new_frame,'-' + str(self.index),(20,50),cv2.LINE_AA,2,(255,0,0),2)
            else:
                cv2.putText(new_frame,'unsure' + str(self.index),(20,50),cv2.LINE_AA,2,(255,255,0),2)

            # add trail to frame
            new_frame = cv2.addWeighted(new_frame,0.7,self.visualmask,0.3,0)
            img = cv2.add(new_frame, trailMask)
            if self.previewWindow:
                avgFS = self.index / (time.time()-self.start_time)
                print( 'orient={}, FIndex={:04d}, avgFS={:2.2f}, conf={:.2f}'.format(orient, self.index, avgFS, conf) )

            # write frames to new output video
            if self.savevid:
                self.videoOut.write(img)
            # show the frames
            if self.previewWindow:
                cv2.imshow('optical flow {}'.format(self), img)
                # kill window if ESC is pressed
                # rest = max(1/self.fps - (time.time()-self.start_time),1)
                # k = cv2.waitKey(rest) & 0xff
                # if k == 27:
                #     return
            # update previous frame and previous points
            self.old_gray = new_frame_gray.copy()
            self.old_points = good_new[valid_ids,...].reshape(-1,1,2)
            self.points_id = good_id[valid_ids,...].reshape(-1,1)

            # if old_points < numPts, get new points
            self.old_points,self.points_id = self.update_points(self.old_gray,self.old_points,self.points_id,self.mask,self.crosshairmask)
            # trail_states = self.trail_states
            # trail_history = self.trail_history
            assert len(self.points_id) == len(self.old_points)
        return orient

    def draw_trail(self,trailMask,trail,k,frame):
        fade = self.trailFade
        color = self.color[k].tolist() # get color for this point
        thickness = self.trailThickness
        pointSize = self.pointSize
        # draw trail
        for j in range(len(trail[k])):
            pt1,pt2 = tuple(trail[k][j][0]),tuple(trail[k][j][1])
            trailColor = [int( color[0]-(fade*j) ), int( color[1]-(fade*j) ), int( color[2]-(fade*j) )] # fading colors
            trailMask = cv2.line(trailMask, pt1, pt2, trailColor, thickness=thickness, lineType=cv2.LINE_AA)
        # add circle over the point
        cv2.circle(frame, tuple(trail[k][0][0]), pointSize, color, -1)

    def filter_invalid_flow(self,multi_pts,lateral_thresh=30):
        '''filter flow with large lateral displacement\n
        Arguments:
            multi_points: trail history
            lateral_thresh: threshold of lateral diplacement, pixel distance
        Return:
            filterd multi_points
        '''
        multi_pts = np.array(multi_pts)
        zeros_pts = np.zeros_like(multi_pts)
        displacement = multi_pts[:,:,0,:]-multi_pts[:,:,1,:]
        multi_pts = np.where(displacement[...,0][...,None,None]>lateral_thresh,zeros_pts,multi_pts).astype(np.int32)
        return multi_pts.tolist()

    def get_orientation(self,trail_history,states,orients_previous=None,penalty=5,update_weight=0.7,stop_thresh=10):
        '''calculate orientation according to trail history\n
        Arguments:
            trail_history: history coordinates of feature points, shape: numPts x trailLength x 2 x 2
            states: states of feature points, wheather it has been tracking and update times, shape: numPts x 2
            orients_previsous: previous overall direction of escalator, shape: 1 x 2
            penalty: attenuated weight of inactive points trail history when calculate direction
            update_weigth: weight of new dispalcement, orient = update_weigth*orients + (1-update_weigths)*orients_previous
            stop_thresh: when the overall dispalcement is lower than this threshold, the direction will be judged as stop
        Return:
            orient: current direction of escalator
        '''

        multi_pts = np.array(trail_history)
        active_ids = states[:,0]==1
        active_updates = states[active_ids,1]
        active_pts = multi_pts[active_ids,...]
        inactive_ids = states[:,0]==0
        inactive_updates = states[inactive_ids,1]
        inactive_pts = multi_pts[inactive_ids,...]
        for i,state in enumerate(inactive_ids):
            if state:
                # attenuate the displacement of feature points that are not continuously tracked
                trail_history[i]=(np.array(trail_history[i])/10).astype(np.int32).tolist()

        active_disp = active_pts[:,:,0,:]-active_pts[:,:,1,:]
        active_nums = np.sum(active_updates)
        active_orients = np.sum(active_disp,axis=(0,1)) if active_nums!=0 else np.zeros(2,dtype=np.int32)

        inactive_disp = inactive_pts[:,:,0,:]-inactive_pts[:,:,1,:]
        inactive_nums = np.sum(inactive_updates)
        inactive_orients = np.sum(inactive_disp,axis=(0,1))/penalty if inactive_nums!=0 else np.zeros(2,dtype=np.int32)

        orients_previous = np.zeros((1,2)) if orients_previous is None else orients_previous
        orients = update_weight*(active_orients+inactive_orients)[None,:] + orients_previous*(1-update_weight)

        angle = np.arctan2(orients[:,1],orients[:,0])/np.pi*180
        angle = np.nanmean(angle)
        magnitude = np.sqrt(np.square(orients).sum())

        orients_previous[0,0] = orients[0,0]
        orients_previous[0,1] = orients[0,1]

        if -150<angle<-30 and orients[0,1]<-stop_thresh:
            orient = self._UP       # ↑
        elif 30<angle<150 and orients[0,1]>stop_thresh:
            orient = self._DOWN     # ↓
        elif magnitude<1.414*stop_thresh or ((-30<=angle<=30 or 150<=angle<=180 or -180<=angle<=-150) and abs(orients[0,1])<=stop_thresh):
            orient = self._STOP     # -
        else:
            orient = self._UNSURE   # unsure
        # print(orients)
        return orient

    def get_default_pts(self,roi,num_pts=60):
        '''get random feature points when opencv function can't get good feature\n
        Arguments:
            roi: roi area of escalator
            num_pts: number of feature points needed
        Return:
            pts: defaulf feature points in roi area
        '''
        left = np.min(roi[:,0])
        right = np.max(roi[:,0])
        top = np.min(roi[:,1])
        down = np.max(roi[:,1])
        pts_x = np.random.randint(left,right,num_pts)
        pts_y = np.random.randint(top,down,num_pts)
        pts = np.stack([pts_x,pts_y],axis=-1)
        pts = pts[:,None,:].astype(np.float32)
        return pts

    def get_FeaturesToTrack_deprecated(self,image,maxCorners,mask,roi):
        qualityLevel = self.shitomasi_params["qualityLevel"]
        minDistance = self.shitomasi_params["minDistance"]
        blockSize = self.shitomasi_params["blockSize"]
        feat_pts = None
        try:
            qualityLevel_loose = max(qualityLevel-0.1,0.1)
            feat_pts = cv2.goodFeaturesToTrack(image,maxCorners,qualityLevel_loose,minDistance,mask=mask,blockSize=blockSize)
            assert feat_pts is not None,'\nNone features points have been tracked, try to loose quality level'
        except:
            qualityLevel_loose = max(qualityLevel-0.2,0.1)
            feat_pts = cv2.goodFeaturesToTrack(image,maxCorners,qualityLevel_loose,minDistance,mask=mask,blockSize=blockSize)
            print(f'\nquality level has been loosed from {qualityLevel} to {qualityLevel_loose:.1f}')
        finally:
            if feat_pts is None:
                feat_pts = self.get_default_pts(roi,maxCorners)
                print('\nNone features points have been tracked, set default features points')

        return feat_pts

    def get_FeaturesToTrack(self,image,maxCorners,mask,roi):
        '''get new feature points\n
        Arguments:
            image: gray image of in which feature points to get
            maxCorners: number of feature points needed
            mask: roi mask
            roi: roi area
        Return:
            feat_pts: feature points to be tracked
        '''
        qualityLevel = self.shitomasi_params["qualityLevel"]
        minDistance = self.shitomasi_params["minDistance"]
        blockSize = self.shitomasi_params["blockSize"]
        feat_pts = None
        try:
            feat_pts = cv2.goodFeaturesToTrack(image,maxCorners,qualityLevel,minDistance,mask=mask,blockSize=blockSize)
        finally:
            if feat_pts is None:
                feat_pts = self.get_default_pts(roi,maxCorners)
                print('\nNone features points have been tracked, set default features points')
        return feat_pts

    def check_flow(self,flow):
        '''filter points whose direction are changing strangely, somtimes up, somtimes down.
        '''
        flow = np.array(flow)
        flow = flow[::5,...]
        disp = flow[:-1,0,1]-flow[1:,0,1]
        cur_orient = 0
        reverse_times = 0
        for i in range(len(disp)):
            if disp[i]>5:
                orient = 1
            elif disp[i]<-5:
                orient = -1
            else:
                orient = 0
            if abs(cur_orient-orient)>1:
                reverse_times+=1
            cur_orient = orient if orient!=0 else cur_orient
        return False if reverse_times>2 else True

    def update_orient(self,orient_history,orient_one,conf_thresh=0.9):
        '''update orient alccording to the orient appeared most times in history orient
        '''
        orient_history.insert(0,orient_one)
        orient_history.pop()
        counter = Counter(orient_history)
        orient_dict = Counter(orient_history).most_common(1)[0]
        orient, count = orient_dict
        conf = count/len(orient_history) # conf = mostly appeared orient / num of total orient
        if conf < conf_thresh:
            orient = self._UNSURE        # if conf < conf_thresh, set state to unsure
        # print(counter)
        count_dict = list(counter.items())
        return orient,conf,count_dict

    def update_points(self,frame_gray,points,points_id,roi_mask,crosshairmask):
        '''determine whether the feature points need to be updated
        '''
        numPts = self.numPts
        trailLength = self.trailLength

        # filter feature points out of image
        img_h,img_w = roi_mask.shape[:2]

        coord_x = np.array(points[:,0,0], dtype=np.int64)
        valid_idx = np.where(np.bitwise_and(0<=coord_x,coord_x<img_w),True,False)   # x limit
        points = points[valid_idx]
        points_id = points_id[valid_idx]

        coord_y = np.array(points[:,0,1], dtype=np.int64)
        valid_idy = np.where(np.bitwise_and(0<=coord_y,coord_y<img_h),True,False)   # y limit
        points = points[valid_idy]
        points_id = points_id[valid_idy]

        # filter feature points out of ROI
        coord_x = np.array(points[:,0,0], dtype=np.int64)
        coord_y = np.array(points[:,0,1], dtype=np.int64)
        valid_indices = np.where(roi_mask[coord_y,coord_x]==255,True,False)
        points = points[valid_indices]
        points_id = points_id[valid_indices]

        # if old_points < numPts, get new points
        if (numPts - len(points)) > 10 :
            # reset trail staet and trail history
            self.trail_states = np.zeros((numPts,2))
            self.trail_history = [[[(0,0), (0,0)] for j in range(trailLength)] for i in range(numPts)]
            # update points and points id
            points = self.get_FeaturesToTrack(frame_gray, maxCorners=numPts, mask=crosshairmask, roi=self.roi)
            points_id = np.arange(len(points),dtype=np.int32).reshape(len(points),1)

        return points,points_id

    def reset(self):
        self.index = 0
        self.inited = False
        self.orients_previous = np.zeros((1,2))         # overall oriention vector of the previous time
        self.orient_history = [0 for i in range(25)]    # orientation history of past frames
        self.trail_states = np.zeros((self.numPts,2))   # whether a point is still being tracked
        self.trail_history = [[[(0,0), (0,0)] for j in range(self.trailLength)] for i in range(self.numPts)]

class ROI_GETER():
    '''ROI for direction judge
    '''
    class Line:
        def __init__(self,pt1,pt2,id=0) -> None:
            self.pt1 = pt1
            self.pt2 = pt2
            self.id = id
            self.x_low = min(pt1[0],pt2[0])
            self.x_up  = max(pt1[0],pt2[0])
            self.y_low = min(pt1[1],pt2[1])
            self.y_up  = max(pt1[1],pt2[1])
            self.k, self.b = self._get_kb(pt1,pt2)

        def _get_kb(self,pt1,pt2):
            # 获取线段的斜率和截距
            x1,y1 = pt1
            x2,y2 = pt2
            k = None if x1 == x2 else (y1-y2)/(x1-x2)
            b = None if k is None else y1-k*x1
            return k,b

        def is_inrange(self,x=None,y=None):
            x_inrange = True
            y_inrange = True
            if x is not None:
                x_inrange = self.x_low <= x <= self.x_up
            if y is not None:
                y_inrange = self.y_low <= y <= self.y_up
            return x_inrange and y_inrange

        def __call__(self, x=None,y=None, *args, **kwds):
            '''输入x坐标, 则计算对应的y坐标; 输入y坐标, 则计算对应的x坐标\n
            '''
            assert (x is None) ^ (y is None ),'x and y should not be specified or None at the same time'
            result = 0
            if x is not None:
                if self.k is None:
                    result = None       # 此时线段与y轴平行，给定x坐标无法求出y坐标
                else:
                    result = self.k*x + self.b
            if y is not None:
                if self.k is None:
                    result = self.x_low # 此时线段与y轴平行，x坐标为固定值
                elif self.k == 0:
                    result = None       # 此时线段与y轴平行，给定y坐标无法求出x坐标
                else:
                    result = (y-self.b)/self.k
            return result

    def __init__(self,contour,img_shape) -> None:
        self.contour = contour
        self.lines = self._parse_contour(contour)
        self.img_shape = img_shape

    def _parse_contour(self,contour):
        # 获取轮廓每个线段对应的直线
        lines = []
        for i,(pt1,pt2) in enumerate(zip(contour[:-1,:],contour[1:,:])):
            lines.append(self.Line(pt1,pt2,i))
        first_pt,last_pt = contour[0,:],contour[-1,:]
        lines.append(self.Line(first_pt,last_pt,i+1))
        return lines

    def _order_points(self,pts):
        # 排序box的四个顶点, 使其输出顺序为: [top left, top right, bottom right, bottom left]
        # sort the points based on their x-coordinates
        xSorted = pts[np.argsort(pts[:, 0]), :]

        # grab the left-most and right-most points from the sorted
        # x-roodinate points
        leftMost = xSorted[:2, :]
        rightMost = xSorted[2:, :]

        # now, sort the left-most coordinates according to their
        # y-coordinates so we can grab the top-left and bottom-left
        # points, respectively
        leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
        (tl, bl) = leftMost

        # now that we have the top-left coordinate, use it as an
        # anchor to calculate the Euclidean distance between the
        # top-left and right-most points; by the Pythagorean
        # theorem, the point with the largest distance will be
        # our bottom-right point
        D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
        (br, tr) = rightMost[np.argsort(D)[::-1], :]

        # return the coordinates in top-left, top-right,
        # bottom-right, and bottom-left order
        return np.array([tl, tr, br, bl], dtype="float32")

    def _get_min_outer_rect(self,contour):
        # 获取轮廓最下外接矩形
        rect = cv2.minAreaRect(contour)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        box = self._order_points(box)
        return box

    def _get_max_inner_rect(self, cont, cX, cY, x_min, x_max, y_min, y_max, img=None):
            """中心延展法获取轮廓最大内接正矩形
            cont: 轮廓
            cX,cY: 中心点
            x_min, x_max, y_min, y_max: 轮廓的最大外接矩形的四个顶点
            """
            # img 对应的是原图, 四个极值坐标对应的是最大外接矩形的四个顶点
            c = cont  # 单个轮廓
            # print(c)
            range_x, range_y = x_max - x_min, y_max - y_min   # 轮廓的X，Y的范围
            x1, x2, y1, y2 = cX, cX, cY, cY     # 中心扩散矩形的四个顶点x,y
            cnt_range, radio = 0, 0
            shape_flag = 1                      # 1：轮廓X轴方向比Y长；0：轮廓Y轴方向比X长
            if range_x > range_y:                     # 判断轮廓 X方向更长
                radio, shape_flag = int(range_x / range_y), 1
                range_x_left = cX - x_min
                range_x_right = x_max - cX
                if range_x_left >= range_x_right:   # 取轴更长范围作for循环
                    cnt_range = int(range_x_left)
                if range_x_left < range_x_right:
                    cnt_range = int(range_x_right)
            else:                                   # 判断轮廓 Y方向更长
                radio, shape_flag = int(range_y / range_x), 0
                range_y_top = cY - y_min
                range_y_bottom = y_max - cY
                if range_y_top >= range_y_bottom:   # 取轴更长范围作for循环
                    cnt_range = int(range_y_top)
                if range_y_top < range_y_bottom:
                    cnt_range = int(range_y_bottom)
            print("X radio Y: %d " % radio)
            print("---------new drawing range: %d-------------------------------------" % cnt_range)
            flag_x1, flag_x2, flag_y1, flag_y2 = False, False, False, False
            radio = 5       # 暂时设5，统一比例X:Y=5:1 因为发现某些会出现X:Y=4:1, 某些会出现X:Y=5:1
            if shape_flag == 1:
                radio_x = radio - 1
                radio_y = 1
            else:
                radio_x = 1
                radio_y = radio - 1
            for ix in range(1, cnt_range, 1):      # X方向延展，假设X:Y=3:1，那延展步进值X:Y=3:1
                # 第二象限延展
                if flag_y1 == False:
                    y1 -= 1 * radio_y       # 假设X:Y=1:1，轮廓XY方向长度接近，可理解为延展步进X:Y=1:1
                    p_x1y1 = cv2.pointPolygonTest(c, (x1, y1), False)
                    p_x2y1 = cv2.pointPolygonTest(c, (x2, y1), False)
                    if p_x1y1 <= 0 or y1 <= y_min or p_x2y1 <= 0:  # 在轮廓外，只进行y运算，说明y超出范围
                        for count in range(0, radio_y - 1, 1):    # 最长返回步进延展
                            y1 += 1     # y超出, 步进返回
                            p_x1y1 = cv2.pointPolygonTest(c, (x1, y1), False)
                            if p_x1y1 <= 0 or y1 <= y_min or p_x2y1 <= 0:
                                pass
                            else:
                                break
                        # print("y1 = %d, P=%d" % (y1, p_x1y1))
                        flag_y1 = True

                if flag_x1 == False:
                    x1 -= 1 * radio_x
                    p_x1y1 = cv2.pointPolygonTest(c, (x1, y1), False)    # 满足第二象限的要求，像素都在轮廓内
                    p_x1y2 = cv2.pointPolygonTest(c, (x1, y2), False)    # 满足第三象限的要求，像素都在轮廓内
                    if p_x1y1 <= 0 or x1 <= x_min or p_x1y2 <= 0:       # 若X超出轮廓范围
                        # x1 += 1  # x超出, 返回原点
                        for count in range(0, radio_x-1, 1):       #
                            x1 += 1         # x超出, 步进返回
                            p_x1y1 = cv2.pointPolygonTest(c, (x1, y1), False)  # 满足第二象限的要求，像素都在轮廓内
                            p_x1y2 = cv2.pointPolygonTest(c, (x1, y2), False)  # 满足第三象限的要求，像素都在轮廓内
                            if p_x1y1 <= 0 or x1 <= x_min or p_x1y2 <= 0:
                                pass
                            else:
                                break
                        # print("x1 = %d, P=%d" % (x1, p_x1y1))
                        flag_x1 = True              # X轴像左延展达到轮廓边界，标志=True
                # 第三象限延展
                if flag_y2 == False:
                    y2 += 1 * radio_y
                    p_x1y2 = cv2.pointPolygonTest(c, (x1, y2), False)
                    p_x2y2 = cv2.pointPolygonTest(c, (x2, y2), False)
                    if p_x1y2 <= 0 or y2 >= y_max or p_x2y2 <= 0:  # 在轮廓外，只进行y运算，说明y超出范围
                        for count in range(0, radio_y - 1, 1):  # 最长返回步进延展
                            y2 -= 1     # y超出, 返回原点
                            p_x1y2 = cv2.pointPolygonTest(c, (x1, y2), False)
                            if p_x1y2 <= 0 or y2 >= y_max or p_x2y2 <= 0:  # 在轮廓外，只进行y运算，说明y超出范围
                                pass
                            else:
                                break
                        # print("y2 = %d, P=%d" % (y2, p_x1y2))
                        flag_y2 = True              # Y轴像左延展达到轮廓边界，标志=True
                # 第一象限延展
                if flag_x2 == False:
                    x2 += 1 * radio_x
                    p_x2y1 = cv2.pointPolygonTest(c, (x2, y1), False)    # 满足第一象限的要求，像素都在轮廓内
                    p_x2y2 = cv2.pointPolygonTest(c, (x2, y2), False)    # 满足第四象限的要求，像素都在轮廓内
                    if p_x2y1 <= 0 or x2 >= x_max or p_x2y2 <= 0:
                        for count in range(0, radio_x - 1, 1):  # 最长返回步进延展
                            x2 -= 1     # x超出, 返回原点
                            p_x2y1 = cv2.pointPolygonTest(c, (x2, y1), False)  # 满足第一象限的要求，像素都在轮廓内
                            p_x2y2 = cv2.pointPolygonTest(c, (x2, y2), False)  # 满足第四象限的要求，像素都在轮廓内
                            if p_x2y1 <= 0 or x2 >= x_max or p_x2y2 <= 0:
                                pass
                            elif p_x2y2 > 0:
                                break
                        # print("x2 = %d, P=%d" % (x2, p_x2y1))
                        flag_x2 = True
                if flag_y1 and flag_x1 and flag_y2 and flag_x2:
                    print("(x1,y1)=(%d,%d)" % (x1, y1))
                    print("(x2,y2)=(%d,%d)" % (x2, y2))
                    break
            x1, x2, y1, y2 = int(x1),int(x2),int(y1),int(y2)
            # cv.line(img, (x1,y1), (x2,y1), (255, 0, 0))
            # cv2.rectangle(img, (x1, y1), (x2, y2), (255, 255, 255), 1, 8)

            return x1, x2, y1, y2

    def shrink_contour(self,contour,img_shape=None,scale=0.99,pixel=None):
        '''缩小轮廓区域
            Arguments:
                contour: 扶梯梯路区域轮廓
                img_shape: 轮廓对应的图片的形状[H,W]
                scale: 缩小比例, list|tuple|int, e.g. [0.85,0.8], 对于x方向和y方向
                pixel: 缩小的像素, 同scale, 区别是单位为像素级别, 如[10,10]表示x,y都缩小10像素
            Return:
                approx: 缩小后的轮廓
        '''
        assert 0.5<scale[0]<1.2 and 0.5<scale[1]<1.2 if isinstance(scale,(tuple,list)) else \
            0.5<scale<1.2, f'Scale range should be [0.7,1.2]'
        if img_shape is None:
            img_shape = self.img_shape
        mask = np.zeros(img_shape).astype(np.uint8)
        mask_fill = cv2.fillConvexPoly(mask.copy(),contour,color=255)
        # m = cv2.moments(contour)
        # cX = int(m["m10"] / m["m00"])
        # cY = int(m["m01"] / m["m00"])
        x,y,w,h = cv2.boundingRect(contour)
        cX = int(x + w/2)
        cY = int(y + h/2)
        area = cv2.contourArea(contour)
        max_area = 1.5**2 * area
        min_area = 0.5**2 * area

        if pixel is not None:
            if isinstance(pixel,(tuple,list)):
                scalex = 1-pixel[0]/w
                scaley = 1-pixel[0]/h
            else:
                scalex = scaley= 1-pixel/h
        else:
            if isinstance(scale,(tuple,list)):
                scalex,scaley = scale
            else:
                scalex = scaley = scale

        crop = mask_fill[y:y+h,x:x+w]
        crop = cv2.resize(crop,dsize=(0,0),fx=scalex,fy=scaley)
        nh,nw = crop.shape[:2]
        y1 = int(cY-nh/2)
        y2 = int(cY+nh/2)
        x1 = int(cX-nw/2)
        x2 = int(cX+nw/2)
        mask[y1:y2,x1:x2] = crop
        mask = mask.astype(np.uint8)
        # cv2.imshow('test',mask)
        # cv2.waitKey(0)
        # exit()
        new_contours,hierarchy = cv2.findContours(mask,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
        if new_contours:
            areas = [cv2.contourArea(cnt) for cnt in new_contours]
            areas_ids = np.array([(j,area) for j,area in enumerate(areas) if min_area<area<max_area]) # filter
            areas = areas_ids[:,1]
            indexes = areas_ids[:,0]
            idx = int(indexes[np.argmax(areas)])
            new_contour = new_contours[idx] # select contour with max area
            epsilon = 0.005 * cv2.arcLength(new_contour, True)
            approx = cv2.approxPolyDP(new_contour,epsilon,True) # smoothing
            # approx = cv2.convexHull(contour)
            approx = approx.reshape(-1,2)
        else:
            raise AssertionError("No contour has been found")
        return approx

    def part_contour(self,contour,rate=0.5,rect=False):
        '''按比例获取轮廓的下半部分\n
            Arguments:
                contour: 扶梯梯路区域轮廓
                rate: 轮廓截取比例, 0~1, 1表示保留全部轮廓, 从下往上截取
                rect: 是否返回矩形轮廓, 该矩形轮廓为截取后的轮廓的最大内接矩形
            Return:
                new_contour: 截取后的轮廓
        '''
        box = self._get_min_outer_rect(contour)
        tl, tr, br, bl = box
        height = bl[1]-tl[1]
        y_clip = bl[1]-int(height*rate)
        lines = [line for line in self.lines if line.is_inrange(y=y_clip)]
        contour_list = contour.tolist()
        insert_ids = []
        for line in lines:
            x = int(line(y=y_clip))
            if x is not None:
                extra = len(np.array(insert_ids)>=line.id+1)
                contour_list.insert(line.id+1+extra,[x,y_clip])
                insert_ids.append(line.id+1)

        new_contour = []
        for pt in contour_list:
            if pt[1]>=y_clip:
                new_contour.append(pt)
        new_contour = np.array(new_contour)

        if rect:
            m = cv2.moments(new_contour)
            cX = int(m["m10"] / m["m00"])
            cY = int(m["m01"] / m["m00"])
            tl, tr, br, bl = self._get_min_outer_rect(new_contour)
            x_min,x_max,y_min,y_max = tl[0],br[0],tl[1],br[1]
            x1, x2, y1, y2 = self._get_max_inner_rect(new_contour,cX,cY,x_min,x_max,y_min,y_max)
            new_contour = np.array([(x1,y1),(x2,y1),(x2,y2),(x1,y2)])

        return new_contour

    def part_contour_simple(self,contour,rate=0.5,rect=False):
        '''按比例获取轮廓的下半部分\n
            Arguments:
                contour: 扶梯梯路区域轮廓
                rate: 轮廓截取比例, 0~1, 1表示保留全部轮廓, 从下往上截取
                rect: 是否返回矩形轮廓, 该矩形轮廓为截取后的轮廓的最大内接矩形
            Return:
                new_contour: 截取后的轮廓
        '''
        area = cv2.contourArea(contour)
        max_area = (rate+0.2) * area
        min_area = (rate-0.2) * area

        box = self._get_min_outer_rect(contour)
        tl, tr, br, bl = box
        height = br[1]-tl[1]
        y_clip = br[1]-int(height*rate)

        mask_zero = np.zeros(self.img_shape).astype(np.uint8)
        mask_fill = cv2.fillConvexPoly(mask_zero.copy(),contour,255)
        nx = np.arange(self.img_shape[1])
        ny = np.arange(self.img_shape[0])
        grid_x,grid_y = np.meshgrid(nx,ny)
        mask = np.where(grid_y>=y_clip,mask_fill,mask_zero)

        # vis_img = mask_zero+200
        # self.draw_point(vis_img,box,vis_ptid=True)
        # cv2.imshow('test',vis_img)
        # cv2.waitKey(0)

        new_contours,hierarchy = cv2.findContours(mask,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
        if new_contours:
            areas = [cv2.contourArea(cnt) for cnt in new_contours]
            areas_ids = np.array([(j,area) for j,area in enumerate(areas) if min_area<area<max_area]) # filter
            areas = areas_ids[:,1]
            indexes = areas_ids[:,0]
            idx = int(indexes[np.argmax(areas)])
            new_contour = new_contours[idx] # select contour with max area
            epsilon = 0.005 * cv2.arcLength(new_contour, True)
            new_contour = cv2.approxPolyDP(new_contour,epsilon,True) # smoothing
            # approx = cv2.convexHull(contour)
            new_contour = new_contour.reshape(-1,2)
        else:
            raise AssertionError("No contour has been found")

        if rect:
            m = cv2.moments(new_contour)
            cX = int(m["m10"] / m["m00"])
            cY = int(m["m01"] / m["m00"])
            tl, tr, br, bl = self._get_min_outer_rect(new_contour)
            x_min,x_max,y_min,y_max = tl[0],br[0],tl[1],br[1]
            x1, x2, y1, y2 = self._get_max_inner_rect(new_contour,cX,cY,x_min,x_max,y_min,y_max)
            new_contour = np.array([(x1,y1),(x2,y1),(x2,y2),(x1,y2)])

        # cv2.imshow('test',mask_fill)
        # cv2.waitKey(0)
        # cv2.imshow('test',mask)
        # cv2.waitKey(0)

        return new_contour

    def draw_point(self,img,points,vis_ptid=True):
        for i,pt in enumerate(points):
            cv2.circle(img,(int(pt[0]),int(pt[1])),5,(0,255,255),-1)
            cv2.putText(img,str(i),(int(pt[0])+10,int(pt[1])+10),cv2.LINE_AA,0.75,(55,55,55))

def roi_from_json(file_path, part_rate=0.7, reduce_scale=[0.85,0.8], rect=False):
    '''从分割算法保存的json文件中获取光流所需ROI区域
        Arguments:
            file_path: json 文件路径
            part_rate: 轮廓截取比例
            reduce_scale: 截取后的轮廓内缩比例(不做轮廓缩小可能会影响光流特征点的获取)
            rect: 是否返回矩形的ROI区域, default: False
        Return:
            part_contour: 光流法所需取的ROI区域
    '''
    assert os.path.exists(file_path),f'file not found: `{file_path}`'
    with open(file_path,'r',encoding='utf-8') as f:
        data = json.load(f)
    assert 'step' in data, 'step contours not found'
    assert 'imgHeight' in data and 'imgWidth' in data, 'img shape info not found'
    step_contour = np.array(data['step'])
    height = data['imgHeight']
    width = data['imgWidth']

    roi_get = ROI_GETER(step_contour,(height,width))
    part_contour = roi_get.part_contour_simple(step_contour,rate=part_rate,rect=rect)
    if reduce_scale is not None:
        part_contour = roi_get.shrink_contour(part_contour,(height,width),scale=reduce_scale)

    return part_contour

def sample1():
    track = OpticalTrack()
    camera_id = "data/test.mp4"
    # roi = np.array([[665,447],[845,447],[845,510],[665,510]])
    roi = roi_from_json('config/seg_result.json',part_rate=0.5,rect=False)
    print(roi)
    track.opticalTrack(camera_id,roi)

def sample2():
    # 测试用例
    camera_id = './data/test.mp4'
    seg_json  = 'config/seg_result.json'
    print("camera_id = {}, seg_json = {}".format(camera_id, seg_json))
    track = OpticalTrack(roiJsonFile=seg_json, conf_thresh=0.9)
    track.previewWindow = True
    track.savevid = False
    cap = cv2.VideoCapture(camera_id)

    stream_continue, t_frame = cap.read()
    while stream_continue:
        orient = track.opticalTrack(t_frame)
        stream_continue, t_frame = cap.read()
        k = cv2.waitKey(1) & 0xff
        if k == 27:
            return
    print("End of Stream")

    ## 说明：如果self.opticalTrack()前后两次调用时间间隔较大，需要先调用 self.reset() 函数
    ##      清空历史信息，否者会影响第二次的方向判断
    ## 比如，第一次方向判断时不断送入图片调用 self.opticalTrack(t_frame), 在获取到确定的方
    ## 向后停止了运行, 然后过了一段时间, 想再次调用 self.opticalTrack(t_frame) 来判断目前的
    ## 扶梯运行方向如何, 此时需要先调用 self.reset() 重置状态，清空历史帧和历史方向信息，然后
    ## 再不断送图片到 self.self.opticalTrack(t_frame) 获取方向。

if __name__ == "__main__":
    sample2()


import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import urlparse, urljoin

R = '\033[31m'  # red
G = '\033[32m'  # green
C = '\033[36m'  # cyan
W = '\033[0m'   # white
Y = '\033[33m'  # yellow

def perform_web_recon(website_url):
    # Set timeout values in seconds
    timeout = 10

    # Send a GET request to the website
    response = requests.get(website_url, timeout=timeout)

    # Check if the request was successful
    if response.status_code == 200:
        # Parse the HTML content using BeautifulSoup
        soup = BeautifulSoup(response.content, 'html.parser')

        # Lists to store URLs of different types of files
        js_files = []
        css_files = []
        html_files = []
        php_files = []
        image_files = []
        internal_links = set()
        external_links = set()

        # Regular expression pattern to match file extensions
        file_extension_pattern = r'\.([a-zA-Z0-9]+)$'

        # Find all <script> tags and extract src URLs
        for script_tag in soup.find_all('script', src=True):
            src = script_tag['src']
            js_files.append(src)

        # Find all <link> tags with rel="stylesheet" and extract href URLs
        for link_tag in soup.find_all('link', rel='stylesheet', href=True):
            href = link_tag['href']
            css_files.append(href)

        # Find all <a> tags and extract href URLs
        for a_tag in soup.find_all('a', href=True):
            href = a_tag['href']
            if re.search(file_extension_pattern, href):
                extension = re.search(file_extension_pattern, href).group(1)
                if extension == 'html':
                    html_files.append(href)
                elif extension == 'php':
                    php_files.append(href)
                else:
                    image_files.append(href)
            else:
                if href.startswith('#'):
                    continue
                full_url = urljoin(website_url, href)
                parsed_url = urlparse(full_url)
                if parsed_url.netloc == urlparse(website_url).netloc:
                    internal_links.add(full_url)
                else:
                    external_links.add(full_url)

        # Print the URLs
        print(f"{G}╰➤ {C}JS Files:{W}")
        for js_file in js_files:
            print(js_file)

        print(f"\n{G}╰➤ {C}CSS Files:{W}")
        for css_file in css_files:
            print(css_file)

        print(f"\n{G}╰➤ {C}HTML Files:{W}")
        for html_file in html_files:
            print(html_file)

        print(f"\n{G}╰➤ {C}PHP Files:{W}")
        for php_file in php_files:
            print(php_file)

        print(f"\n{G}╰➤ {C}Image Files:{W}")
        for image_file in image_files:
            print(image_file)

        print(f"\n{G}╰➤ {C}Internal Links:{W}")
        for internal_link in internal_links:
            print(internal_link)

        print(f"\n{G}╰➤ {C}External Links:{W}")
        for external_link in external_links:
            print(external_link)

        # Directory search and print details
        directory_search = website_url + "/directory"  # Replace with the directory you want to search
        directory_response = requests.get(directory_search, timeout=timeout)

        if directory_response.status_code == 200:
            directory_soup = BeautifulSoup(directory_response.content, 'html.parser')
            directory_links = []

            # Find all <a> tags in the directory page
            for a_tag in directory_soup.find_all('a', href=True):
                href = a_tag['href']
                full_url = urljoin(directory_search, href)
                directory_links.append(full_url)

            print("\nDirectory Links:")
            for directory_link in directory_links:
                print(directory_link)

        else:
            print("\nFailed to fetch directory. Status code:", directory_response.status_code)

        # Print the counts
        print(f"\n{G}╰➤ {C}Total JS Files:{W}", len(js_files))
        print(f"{G}╰➤ {C}Total CSS Files:{W}", len(css_files))
        print(f"{G}╰➤ {C}Total HTML Files:{W}", len(html_files))
        print(f"{G}╰➤ {C}Total PHP Files:{W}", len(php_files))
        print(f"{G}╰➤ {C}Total Image Files:{W}", len(image_files))
        print(f"{G}╰➤ {C}Total Internal Links:{W}", len(internal_links))
        print(f"{G}╰➤ {C}Total External Links:{W}", len(external_links))

    else:
        print(f"{R}Failed to fetch the website. Status code:", response.status_code)

if __name__ == "__main__":
    target_website = "https://spyboy.blog"  # Replace with your desired URL
    perform_web_recon(target_website)


"""Routes configuration

The more specific and detailed routes should be defined first so they
may take precedent over the more generic routes. For more information
refer to the routes manual at http://routes.groovie.org/docs/
"""
from pylons import config
from routes import Mapper

def make_map():
    """Create, configure and return the routes Mapper"""
    map = Mapper(directory=config['pylons.paths']['controllers'],
        always_scan=config['debug'])
    map.minimization = False

    # The ErrorController route (handles 404/500 error pages); it should
    # likely stay at the top, ensuring it can always be resolved
    map.connect('/error/{action}', controller='error')
    map.connect('/error/{action}/{id}', controller='error')

    # CUSTOM ROUTES HERE
    map.connect('/signout', controller='account', action='signin')
    map.connect('/signin', controller='account', action='signin')
    map.connect('/', controller='marketing', action='homepage')
    map.connect('/handler/{controller}', action='handler')
    map.connect('/dashboard', controller='dashboard', action='index')
    map.connect('/users', controller='user', action='index')
    map.connect('/users/{propertyId}', controller='user', action='index')
    map.connect('/property', controller='property', action='index')
    map.connect('/reports', controller='reports', action='transactions')
    map.connect('/contacts', controller='contacts', action='index')
    map.connect('/contacts/json/{type}', controller='contacts', action='json')
    map.connect('/activate/{id}', controller='account', action='activate')
    map.connect('/newPassword/{id}', controller='account', action='newPassword')
    map.connect('/account', controller='account', action='manage')
    map.connect('/company', controller='company', action='manage')
    map.connect('/search', controller='search', action='index')
    map.connect('/help', controller='help', action='index')
    map.connect('/autocomplete', controller='search', action='autocomplete')
    map.connect('/{controller}/{action}')
    map.connect('/{controller}/{action}/{id}')
    map.connect('/{controller}/{action}/{id}/{unitId}', controller='property', action='setup')

    return map


import arcade
SCREEN_WIDTH=800
SCREEN_HEIGHT=600
def suelo():
    """Dibuja el suelo"""
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)
def cabeza(x,y):
    """Dibuja la cabeza"""
    arcade.draw_circle_filled(300+x, 200+y, 10, arcade.color.YELLOW)
    arcade.draw_circle_filled(296+x, 205+y, 3, arcade.color.BLACK)
    arcade.draw_circle_filled(304+x, 205+y, 3, arcade.color.BLACK)
def on_draw(delta_time):
    arcade.start_render()
    suelo()
    cabeza(on_draw.coordenada_x, on_draw.coordenada_y)
    cabeza(-20, -20)
    on_draw.coordenada_x +=1
    on_draw.coordenada_y +=2
on_draw.coordenada_x=0
on_draw.coordenada_y=0
def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)

    arcade.schedule(on_draw,1/60)
    arcade.run()

main()


"""
Vues de l'application "Research", permettant de traiter toutes
les requêtes liées à la recherche de produits
"""
from django.shortcuts import render, redirect, HttpResponseRedirect, get_object_or_404, HttpResponse
from django.core.paginator import Paginator
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.db import IntegrityError
from .models import Product, Substitute, Rating
from .manager import manager_display_products
from .forms import RatingForm

@login_required
def display_substitutes(request):
    """
    Fonction permettant d'afficher les substituts
    enregistrés d'un utilisateur.
    La balise @login_required permet de n'afficher
    cette page que si l'utilisateur est connecté.
    """
    substitutes = Substitute.objects.filter(user=request.user)
    products_paginator = Paginator(substitutes, 6)
    page_number = request.GET.get("page")
    page = products_paginator.get_page(page_number)
    if User.is_authenticated:
        return render(request, "research/substitutes.html", {'count':products_paginator.count, "page":page})
    else:
        return redirect("frontpage")

def display_products(request):
    """
    Fonction permettant d'afficher les substituts
    enregistrés d'un utilisateur.
    La balise @login_required permet de n'afficher
    cette page que si l'utilisateur est connecté.
    """
    if request.method == "GET":
        try:
            search = request.GET.get("search")
            page_number = request.GET.get("page")
            data = manager_display_products(search, page_number)
            return render(request, "research/products.html", data)
        except ValueError:
            return redirect("frontpage")

def product_details(request, product_pk):
    """
    Fonction permettant d'afficher les détails
    d'un produit sur une page dédiée.
    """
    try:
        user = request.user
    except TypeError:
        pass

    product = get_object_or_404(Product, pk=product_pk)
    values = Rating.objects.filter(product=product).values('note')
    users = Rating.objects.filter(product=product).values('user_id')

    try:
        exists = Rating.objects.get(product=product, user=user)
        hasVoted = True
    except Rating.DoesNotExist:
        hasVoted = False
    except TypeError:
        hasVoted = False

    form = RatingForm()
    if values:
        final_note = 0
        count = 0
        for value in values:
            final_note += value["note"]
            count += 1
        _rating = final_note / count

        return render(request, "research/product.html", {"product":product, "rating":_rating, "hasVoted":hasVoted, "form":form})
    else:
        return render(request, "research/product.html", {"product":product, "hasVoted":hasVoted, "form":form})

@login_required
def save_product(request, product_pk):
    """
    Fonction permettant d'enregistrer un produit
    en substitut pour un utilisateur.
    La balise @login_required permet de n'afficher
    cette page que si l'utilisateur est connecté.
    """
    if request.method == "POST":
        product = get_object_or_404(Product, pk=product_pk)
        user = request.user
        substitute = Substitute(product=product, user=user)
        substitute.save()
        return redirect("substitutes")
    else:
        return redirect("frontpage")

@login_required
def delete_product(request, substitute_pk):
    """
    Fonction permettant de supprimer un substituts
    des enregistrés d'un utilisateur.
    La balise @login_required permet de n'afficher
    cette page que si l'utilisateur est connecté.
    """
    if request.method == "POST":
        substitute = get_object_or_404(Substitute, pk=substitute_pk)
        user = request.user
        substitute.delete()
        return redirect("substitutes")
    else:
        return redirect("frontpage")

def rate_product(request, product_pk):
    if request.method == "POST":
        form = RatingForm(request.POST)
        if form.is_valid():
            note = form.cleaned_data.get('rating')

        product = get_object_or_404(Product, pk=product_pk)
        user = request.user
        rating = Rating.objects.create(product=product, user=user, note=note)

        values = Rating.objects.filter(product=product).values('note')
        users = Rating.objects.filter(product=product).values('user_id')
        try:
            exists = Rating.objects.get(product=product, user=user)
            hasVoted = True
        except Rating.DoesNotExist:
            hasVoted = False

        if values:
            final_note = 0
            count = 0
            for value in values:
                final_note += value["note"]
                count += 1
            _rating = final_note / count
        return render(request, "research/product.html", {"product":product, "rating":_rating, "hasVoted":hasVoted, "form":form})


from collections import deque
import pyaudio

def is_silent(data):
    SILENCE_THRESHOLD = 500
    """Returns 'True' if below the 'silent' threshold"""
    return max(data) < SILENCE_THRESHOLD

def test_mic():
    # Parameters for pyaudio stream
    CHUNK = 1024
    FORMAT = pyaudio.paInt16
    CHANNELS = 1
    RATE = 44100
    SILENCE_DURATION = (
        5  # How many seconds of silence before we consider the conversation over
    )

    audio = pyaudio.PyAudio()

    stream = audio.open(
        format=FORMAT,
        channels=CHANNELS,
        rate=RATE,
        input=True,
        output=True,
        frames_per_buffer=CHUNK,
    )

    print("Testing microphone... Press Ctrl+C to stop.")
    # Initialize the buffer and silence tracking
    audio_buffer = deque(maxlen=int(RATE / CHUNK * SILENCE_DURATION))
    silent_time = 0

    while True:
        # Read a chunk of data
        data = stream.read(CHUNK, exception_on_overflow=False)
        stream.write(data)

        if not data:
            print("No data read from the microphone.")
        else:
            # Check for silence
            if is_silent(data):
                silent_time += CHUNK / RATE
                if silent_time > 5:  # 5 seconds of silence
                    print("Silence detected. Stopping listening.")
                    audio_buffer.append(data)
                    break
            else:
                silent_time = 0  # Reset the silence timer

        # Append data to the buffer
        audio_buffer.append(data)

    print("Recording stopped, playing back...")

    for data in audio_buffer:
        stream.write(data)

    # Stop and close the playback stream
    stream.stop_stream()
    stream.close()

    print("Playback finished.")

    # try:
    #     while True:
    #         data = stream.read(CHUNK)
    #         stream.write(data)  # Playback the captured audio in real-time
    # except KeyboardInterrupt:
    #     print("\nStopping microphone test...")
    #     stream.stop_stream()
    #     stream.close()
    #     audio.terminate()

if __name__ == "__main__":
    test_mic()


import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
from boto.s3.connection import S3Connection
from flask import Flask, render_template, url_for, request, redirect, flash

env = "dev"

def send_mail(receiver, link, name):

    api_key = os.environ.get('Forgot Password')
    sender = "no-reply@vidveda.com"
    message = Mail(
        from_email=(sender,"VID VEDA") ,
        to_emails=receiver,
        subject='TEST MAIL',
        html_content=f'<strong>Hi {name}! The link is {link} </strong>')
    try:
        sg = SendGridAPIClient(api_key)
        response = sg.send(message)

    except Exception as e:
        flash('Sorry, but the email linked to this account is invalid')
        return redirect('/forgot')


import logging
from string import Template

from tg import expose, request, config, session, redirect, flash
from tg.decorators import with_trailing_slash
from tg import tmpl_context as c
from tg import response
from tg import TGController
from webob import exc

from allura.app import SitemapEntry
from allura.lib import plugin
from allura.controllers.error import ErrorController
from allura.controllers.project import NeighborhoodController
from allura import model as M
from allura.lib.widgets import project_list as plw
from allura.ext.personal_dashboard.dashboard_main import DashboardController
from .auth import AuthController
from .trovecategories import TroveCategoryController
from .search import SearchController, ProjectBrowseController
from .newforge import NewForgeController
from .site_admin import SiteAdminController
from .rest import RestController

__all__ = ['RootController']

log = logging.getLogger(__name__)

flash.static_template = Template("$$('#messages').notify('$message', {status: '$status'});")

class W:
    project_summary = plw.ProjectSummary()

class RootController(TGController):

    """
    The root controller for the allura application.

    All the other controllers and WSGI applications should be mounted on this
    controller. For example::

        panel = ControlPanelController()
        another_app = AnotherWSGIApplication()

    Keep in mind that WSGI applications shouldn't be mounted directly: They
    must be wrapped around with :class:`tg.controllers.WSGIAppController`.

    When testing the root, BasetestProjectRootController should be considered as the root controller

    """

    auth = AuthController()
    error = ErrorController()
    nf = NewForgeController()
    search = SearchController()
    rest = RestController()
    categories = TroveCategoryController()
    dashboard = DashboardController()
    browse = ProjectBrowseController()

    def __init__(self):
        super().__init__()
        self.nf.admin = SiteAdminController()

    @expose()
    def _lookup(self, nbhd_mount, *remainder):
        n_url_prefix = '/%s/' % nbhd_mount
        n = self._lookup_neighborhood(n_url_prefix)
        if n and not n.url_prefix.startswith('//'):
            return NeighborhoodController(n), remainder
        else:
            raise exc.HTTPNotFound

    def _lookup_neighborhood(self, url_prefix):
        n = M.Neighborhood.query.get(url_prefix=url_prefix)
        return n

    def _check_security(self):
        c.project = c.app = None
        c.user = plugin.AuthenticationProvider.get(request).authenticate_request()
        assert c.user is not None, ('c.user should always be at least User.anonymous(). '
                                    'Did you run `paster setup-app` to create the database?')
        if not c.user.is_anonymous():
            c.user.track_active(request)

            # Make sure the page really isn't cached (not accessible by back button, etc)
            # pylons.configuration defaults to "no-cache" only.
            # See also http://blog.55minutes.com/2011/10/how-to-defeat-the-browser-back-button-cache/ and
            # https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en#defining_optimal_cache-control_policy
            response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'

    @expose()
    @with_trailing_slash
    def index(self, **kw):
        """Handle the front-page."""
        if not c.user.is_anonymous():
            redirect('/dashboard')
        else:
            redirect('/neighborhood')

    @expose('jinja:allura:templates/neighborhood_list.html')
    def neighborhood(self, **kw):
        neighborhoods = M.Neighborhood.query.find().sort('name')
        categories = M.ProjectCategory.query.find(
            {'parent_id': None}).sort('name').all()
        c.custom_sidebar_menu = [
            SitemapEntry(cat.label, '/browse/' + cat.name) for cat in categories
        ]
        return dict(neighborhoods=neighborhoods, title="All Neighborhoods")


from src.scripts.financial_statement_converter import FinancialStatementConverter
from src.dao.file_storage_dao import FileStorageDAO
from src.business_objects.company_report import CompanyReport
from src.business_objects.income_statement import IncomeStatement
from src.business_objects.balance_sheet import BalanceSheet
from src.business_objects.cash_flow_statement import CashFlowStatement
from src.business_objects.key_ratios import KeyRatios
from src.business_objects.company_key_metrics_ttm import CompanyKeyMetricsTTM
from src.business_objects.company_quote import CompanyQuote
from scripts.utilities.utils import Utils
import time
import datetime
import json

class ReportGenerator:

    @staticmethod
    def generate_reports(tickers, time_to_live=0):
        for ticker in tickers:
            ReportGenerator.generate_report(ticker, time_to_live)

    @staticmethod
    def generate_report(ticker, time_to_live=0):
        try:
            info = FileStorageDAO.get_company_report(ticker)
            if time.time() - float(info.get('last_updated_date', 0)) < time_to_live:
                return
        except (FileNotFoundError, json.decoder.JSONDecodeError):
            # continue on
            pass
        except BaseException as e:
            print("Unexpected exception when trying to generate report: " + str(e))
            return

        income_statements = FinancialStatementConverter.convert_income_statement_data(ticker)
        balance_sheets = FinancialStatementConverter.convert_balance_statement_data(ticker)
        cash_flow_statements = FinancialStatementConverter.convert_cash_flow_statement_data(ticker)
        key_ratios = FinancialStatementConverter.convert_key_ratio_data(ticker)
        company_key_metrics_ttm = FinancialStatementConverter.convert_company_key_metrics_ttm_data(ticker)
        company_quote = FinancialStatementConverter.convert_company_quote_data(ticker)

        # Part 1 - Growth rates
        equity_growth = ReportGenerator.get_growth_list(balance_sheets, BalanceSheet.SHAREHOLDERS_EQUITY)
        revenue_growth = ReportGenerator.get_growth_list(income_statements, IncomeStatement.REVENUE)
        earnings_growth = ReportGenerator.get_growth_list(income_statements, IncomeStatement.NET_INCOME)
        operating_cash_growth = ReportGenerator.get_growth_list(cash_flow_statements, CashFlowStatement.OPERATING_CASH_FLOW)

        try:
            relevant_growth_rates = [equity_growth[2], equity_growth[9],
                                     revenue_growth[2], revenue_growth[9],
                                     earnings_growth[2], earnings_growth[9],
                                     operating_cash_growth[2], operating_cash_growth[9]]

            # filter out any None values
            lowest_growth_rate = min(rate for rate in relevant_growth_rates if rate is not None)
            conservative_growth_rate = min(.15, lowest_growth_rate)
        except:
            lowest_growth_rate = 0
            conservative_growth_rate = 0

        # Part 2 - P/E and EPS
        pe_ratios = ReportGenerator.get_list(key_ratios, KeyRatios.PE_RATIO)

        positive_pe_ratios = Utils.remove_negative_numbers(pe_ratios)

        estimated_future_pe = max(min(lowest_growth_rate * 2 * 100, Utils.average(positive_pe_ratios)), 0)
        conservative_future_pe = 15

        if len(income_statements) > 0:
            eps = income_statements[0].get(IncomeStatement.EPS)
            eps_diluted = income_statements[0].get(IncomeStatement.EPS_DILUTED)
        else:
            eps = 0
            eps_diluted = 0

        if company_key_metrics_ttm is None:
            eps_ttm = 0
        else:
            eps_ttm = company_key_metrics_ttm.get(CompanyKeyMetricsTTM.EPS)

        if company_quote is None:
            shares_outstanding = 0
        else:
            shares_outstanding = company_quote.get(CompanyQuote.SHARES_OUTSTANDING)

        # Part 3 - Intrinsic value calculation
        intrinsic_value = Utils.calculate_intrinsic_value(eps, lowest_growth_rate, estimated_future_pe)
        conservative_intrinsic_value = Utils.calculate_intrinsic_value(eps_diluted, conservative_growth_rate, conservative_future_pe)

        intrinsic_value_ttm = Utils.calculate_intrinsic_value(eps_ttm, lowest_growth_rate, estimated_future_pe)
        conservative_intrinsic_value_ttm = Utils.calculate_intrinsic_value(eps_ttm, conservative_growth_rate, conservative_future_pe)

        # Part 4 - Create and populate the company report
        company_report = CompanyReport()
        company_report.set_attr(CompanyReport.DATES, ReportGenerator.get_list(income_statements, IncomeStatement.DATE))

        # Set growth rates
        company_report.set_attr(CompanyReport.EQUITY_GROWTH, equity_growth)
        company_report.set_attr(CompanyReport.REVENUE_GROWTH, revenue_growth)
        company_report.set_attr(CompanyReport.EARNINGS_GROWTH, earnings_growth)
        company_report.set_attr(CompanyReport.OPERATING_CASH_GROWTH, operating_cash_growth)

        # Debt, Revenue, Earnings, Equity, ROIC, Return on Equity, Debt to Earnings, Shares Outstanding
        company_report.set_attr(CompanyReport.RETURN_ON_INVESTED_CAPITAL, ReportGenerator.get_list(key_ratios, KeyRatios.ROIC))
        company_report.set_attr(CompanyReport.RETURN_ON_EQUITY, ReportGenerator.get_list(key_ratios, KeyRatios.ROE))
        company_report.set_attr(CompanyReport.TOTAL_DEBT, ReportGenerator.get_list(balance_sheets, BalanceSheet.TOTAL_DEBT))
        company_report.set_attr(CompanyReport.REVENUE, ReportGenerator.get_list(income_statements, IncomeStatement.REVENUE))
        company_report.set_attr(CompanyReport.EARNINGS, ReportGenerator.get_list(income_statements, IncomeStatement.NET_INCOME))
        company_report.set_attr(CompanyReport.EPS_TTM, eps_ttm)
        company_report.set_attr(CompanyReport.EQUITY, ReportGenerator.get_list(balance_sheets, BalanceSheet.SHAREHOLDERS_EQUITY))

        # other
        company_report.set_attr(CompanyReport.TICKER, ticker)
        company_report.set_attr(CompanyReport.NUM_INCOME_STATEMENTS, len(income_statements))
        company_report.set_attr(CompanyReport.NUM_BALANCE_SHEETS, len(balance_sheets))
        company_report.set_attr(CompanyReport.NUM_CASH_FLOW_STATEMENTS, len(cash_flow_statements))

        company_report.set_attr(CompanyReport.EPS, eps)
        company_report.set_attr(CompanyReport.EPS_DILUTED, eps_diluted)
        company_report.set_attr(CompanyReport.PE_RATIOS, pe_ratios)
        company_report.set_attr(CompanyReport.AVERAGE_PE_RATIO, estimated_future_pe)
        company_report.set_attr(CompanyReport.SHARES_OUTSTANDING, shares_outstanding)
        company_report.set_attr(CompanyReport.DEBT_TO_EARNINGS, ReportGenerator.get_list(key_ratios, KeyRatios.DEBT_TO_EARNINGS))

        company_report.set_attr(CompanyReport.INTRINSIC_VALUE, intrinsic_value)
        company_report.set_attr(CompanyReport.INTRINSIC_VALUE_GROWTH_RATE, lowest_growth_rate)
        company_report.set_attr(CompanyReport.CONSERVATIVE_INTRINSIC_VALUE, conservative_intrinsic_value)
        company_report.set_attr(CompanyReport.CONSERVATIVE_INTRINSIC_VALUE_GROWTH_RATE, conservative_growth_rate)

        company_report.set_attr(CompanyReport.INTRINSIC_VALUE_USING_TTM_EPS, intrinsic_value_ttm)
        company_report.set_attr(CompanyReport.CONSERVATIVE_INTRINSIC_VALUE_USING_TTM_EPS, conservative_intrinsic_value_ttm)

        FileStorageDAO.save_report(company_report)

        # Part 5 - create and save the json version of the company report
        company_report_json = {
            CompanyReport.DATES: ReportGenerator.get_list(income_statements, IncomeStatement.DATE),
            CompanyReport.EQUITY_GROWTH: equity_growth,
            CompanyReport.REVENUE_GROWTH: revenue_growth,
            CompanyReport.EARNINGS_GROWTH: earnings_growth,
            CompanyReport.OPERATING_CASH_GROWTH: operating_cash_growth,
            CompanyReport.RETURN_ON_INVESTED_CAPITAL: ReportGenerator.get_list(key_ratios, KeyRatios.ROIC),
            CompanyReport.RETURN_ON_EQUITY: ReportGenerator.get_list(key_ratios, KeyRatios.ROE),
            CompanyReport.TOTAL_DEBT: ReportGenerator.get_list(balance_sheets, BalanceSheet.TOTAL_DEBT),
            CompanyReport.REVENUE: ReportGenerator.get_list(income_statements, IncomeStatement.REVENUE),
            CompanyReport.EARNINGS: ReportGenerator.get_list(income_statements, IncomeStatement.NET_INCOME),
            CompanyReport.EPS_TTM: eps_ttm,
            CompanyReport.EQUITY: ReportGenerator.get_list(balance_sheets, BalanceSheet.SHAREHOLDERS_EQUITY),
            CompanyReport.TICKER: ticker,
            CompanyReport.NUM_INCOME_STATEMENTS: len(income_statements),
            CompanyReport.NUM_BALANCE_SHEETS: len(balance_sheets),
            CompanyReport.NUM_CASH_FLOW_STATEMENTS: len(cash_flow_statements),
            CompanyReport.EPS: eps,
            CompanyReport.EPS_DILUTED: eps_diluted,
            CompanyReport.PE_RATIOS: pe_ratios,
            CompanyReport.AVERAGE_PE_RATIO: estimated_future_pe,
            CompanyReport.SHARES_OUTSTANDING: shares_outstanding,
            CompanyReport.DEBT_TO_EARNINGS: ReportGenerator.get_list(key_ratios, KeyRatios.DEBT_TO_EARNINGS),
            CompanyReport.INTRINSIC_VALUE: intrinsic_value,
            CompanyReport.INTRINSIC_VALUE_GROWTH_RATE: lowest_growth_rate,
            CompanyReport.CONSERVATIVE_INTRINSIC_VALUE: conservative_intrinsic_value,
            CompanyReport.CONSERVATIVE_INTRINSIC_VALUE_GROWTH_RATE: conservative_growth_rate,
            CompanyReport.INTRINSIC_VALUE_USING_TTM_EPS: intrinsic_value_ttm,
            CompanyReport.CONSERVATIVE_INTRINSIC_VALUE_USING_TTM_EPS: conservative_intrinsic_value_ttm,
            'last_updated_date': time.time(),
            'last_updated_date_human': str(datetime.datetime.now())
        }

        FileStorageDAO.save_company_report_json(ticker, company_report_json)

    @staticmethod
    def get_growth(statements, num_years, attr):
        if len(statements) < num_years or len(statements) == 0:
            return 'no data'
        try:
            starting_value = float(statements[num_years].get(attr))
            ending_value = float(statements[0].get(attr))
            return Utils.calculate_yoy_return(starting_value, ending_value, num_years)
        except:
            return 'error'

    @staticmethod
    def get_value(statements, num_years, attr):
        if len(statements) < num_years:
            return 'no data'
        return float(statements[num_years].get(attr))

    @staticmethod
    def get_list(statements, attr):
        """
        :param statements: a list of financial statements
        :param attr: the attribute you want
        :return: a list containing all the values of that particular attribute
        """
        ret = []
        for i in range(len(statements)):
            ret.append(statements[i].get(attr))
        return ret

    @staticmethod
    def get_growth_list(statements, attr):
        ret = []

        # start from 1 since growth requires more than 0 years
        for i in range(1, len(statements)):
            ret.append(ReportGenerator.get_growth(statements, i, attr))
        return ret

# print(Utils.calculate_payback_time(3185, 501000000, .25, 17377000000))
# FileStorageDAO._make_directories()
# ReportGenerator.generate_report('F')
# print(Utils.calculate_intrinsic_value(34.8,.25,50))


###############################################################################
#
# Done by: Leandro Jaimes Soria
# Contact: https://www.linkedin.com/in/ljaimessoria/
#
# Program Description:
#
# Software in python that capture video, store in NAS and compress to MP4
# The hardware used was: Rasberry pi 4 + Camera Module v1
#
# Execution
# python  app.py
###############################################################################

import asyncio
import logging
import yaml
import logging.config
import os
import time
from queue import Queue
from gpiozero import CPUTemperature
from time import sleep, strftime, time
import subprocess,shlex
#from pythonping import ping

NAS = '192.168.0.135:/Users/Leo/Documents/Raspi'
folder = '/mnt/nfs'
cpu = CPUTemperature(min_temp=30, max_temp=90)
logger = logging.getLogger(__name__)

class TempRoutines:
    async def temp_logger (self,period_s):
        while True:
            try:
                # log temperature
                logger.info("temp: %i",cpu.temperature)
                await asyncio.sleep(period_s)
            except Exception as e:
                logger.error('Reading temperature failed')
                logger.error(str(e))

class VideoRoutines:
    def __init__(self,symbol=None):
        self.Stp_time_s = 2

    async def record_video_m (self,folder,duration_m,video_queue):
        while True:
            try:
                #Capture Video
                file_name= folder + strftime("/1080p25-%Y%m%d-%H%M%S")
                logger.info("Recording: %s",file_name)
                command= shlex.split('raspivid -t '+ str(duration_m*60000) +
                    ' -w 1436 -h 1080 -a 12 -drc high -fps 25 -b 17000000 -o '
                    + file_name +'.h264')
                p3 = subprocess.Popen(command)
                await asyncio.sleep(duration_m*60+self.Stp_time_s)
                await video_queue.put(file_name)
            except Exception as e:
                logger.error('Recording failed')
                logger.error(str(e))
                p3.terminate()

    async def wrap_video (self,video_queue):
        del_h264 = False
        prev_file_name = ''
        while True:
            try:
                #convert video to mp4
                file_name = await video_queue.get()
                if del_h264:
                    del_h264 = False
                    logger.debug("Delete: %s.h264",prev_file_name)
                    command= shlex.split('sudo rm '+ prev_file_name +'.h264 ')
                    p2 = subprocess.Popen(command)

                logger.debug("Wrap to MP4: %s",file_name)
                command= shlex.split('MP4Box -fps 25 -add '
                        +file_name+ '.h264 '+ file_name +'.mp4')
                p = subprocess.Popen(command)
                del_h264 = True
                prev_file_name = file_name
            except Exception as e:
                logger.error('warping failed')
                logger.error(str(e))
                p.terminate()
                p2.terminate()

def setup_logging(
   default_path='logging.yaml',
   default_level=logging.DEBUG,
   env_key='LOG_CFG'
):
   """Setup logging configuration"""
   path = default_path
   value = os.getenv(env_key, None)
   if value:
       path = value
   if os.path.exists(path):
       with open(path, 'rt') as f:
           config = yaml.safe_load(f.read())
       logging.config.dictConfig(config)
   else:
       logging.basicConfig(level=default_level)

def main():
    setup_logging()
    logger.info('Running. Press CTRL-C to exit.')
    temp_routine = TempRoutines()
    video_routine = VideoRoutines()
    video_queue =  asyncio.Queue()
    loop = asyncio.get_event_loop()

    try:
#       response = ping('192.168.0.135', size=40, count=10)
#       while not response.success:
#       sleep(60)
#       response = ping('192.168.0.135', size=40, count=10)
#       r = pyping.ping(NAS)

        #Mount NAS
        logger.info('waiting NAS')
        sleep(60)
        command = 'sudo mount -t nfs '+ NAS +' '+folder
        os.system(command)
        logger.debug('NAS mounted')
        #Threads
        asyncio.ensure_future(
            video_routine.record_video_m(folder,1,video_queue))  #coroutine for video recording
        asyncio.ensure_future(
            temp_routine.temp_logger(1))    #coroutine for temp log
        asyncio.ensure_future(
            video_routine.wrap_video(video_queue))  #coroutine for video recording
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        logger.debug("Received exit signal")

        command = 'sudo umount /mnt/nfs'
        os.system(command)
        logger.debug('NAS umounted')

        logger.debug('closing thread')
        for task in asyncio.Task.all_tasks():
            task.cancel()
        loop.stop()

if __name__ == '__main__':
   main()


# -*- coding: utf-8 -*-
# @Time    : 2022/8/28 22:20
# @Author  : CMM
# @Site    :
# @File    : cgan.py
# @Software: PyCharm
import torch
import torch.nn as nn
import config

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.label_emb = nn.Embedding(config.N_CLASSES, config.N_CLASSES)

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *block(config.N_Z + config.N_CLASSES, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            nn.Linear(1024, config.N_C * config.IMAGE_SIZE * config.IMAGE_SIZE),
            nn.Tanh()
        )

    def forward(self, noise, label):
        gen_input = torch.cat((self.label_emb(label), noise), -1)
        img = self.model(gen_input)
        img = img.view(img.size(0), config.N_C, config.IMAGE_SIZE, config.IMAGE_SIZE)
        return img

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.label_embedding = nn.Embedding(config.N_CLASSES, config.N_CLASSES)
        self.model = nn.Sequential(
            nn.Linear(config.N_CLASSES + config.N_C * config.IMAGE_SIZE * config.IMAGE_SIZE, 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 512),
            nn.Dropout(0.4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 512),
            nn.Dropout(0.4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 1)
        )

    def forward(self, img, labels):
        disc_input = torch.cat((img.view(img.size(0), -1), self.label_embedding(labels)), -1)
        validity = self.model(disc_input)
        return validity


#random generalo modul importalasa
import random

# lista generalasa kulonobozo N-ekkel: N =[10, 30, 100, 300, ...]
N=sorted([10**i for i in range(1,6)]+[3*10**i for i in range(1,6)])

#sample number
ns=10

#ezekben fogom tarolni a mean-t es median-t
mean_range   = []
median_range = []

#vegigmegyek az N-eken
for i in range(len(N)):
    #ezekben lesznek az atlagok es medianok (ns darab)
    means   = []
    medians = []
    #adott N-re ns db. sample-t generalok
    for k in range(ns):
        #legeneralom az N db random szamot
        rnds = sorted([random.random() for j in range(N[i])])
        #kiszamolom az atlagot
        means.append(sum(rnds)/N[i])
        #kiszamolom a mediant
        if N[i]%2==0:
            medians.append((rnds[int(N[i]/2)]+rnds[int(N[i]/2)+1])/2)
        else:
            medians.append(rnds[int(N[i]/2)])
    # mean and median range
    mean_range.append(max(means)-min(means))
    median_range.append(max(medians)-min(medians))

#matplotlib importalasa a plotolashoz
import matplotlib.pyplot as plt

#plotolas
plt.loglog()
plt.plot(N, mean_range, 'o', label="Mean range")
plt.plot(N, median_range, 'x', label="Median range")
plt.xlabel("N")
plt.legend(loc=3)
plt.savefig("fig.pdf")


import tensorflow as tf
import numpy as np
import shfl

from sklearn.metrics import classification_report

class ClassifierModel(shfl.model.DeepLearningModel):

    def __init__(self, dict_labels, batch_size, epochs, finetune):

        #dict labels: letra -> bin
        self._dict_labels = dict_labels

        if finetune:
            resnet50 = tf.keras.applications.ResNet50(include_top = False, weights = 'imagenet', pooling = 'avg', input_tensor=tf.keras.layers.Input(shape=(224, 224, 3)))
        else:
            resnet50 = tf.keras.applications.ResNet50(include_top = False, weights = None, pooling = 'avg', input_tensor=tf.keras.layers.Input(shape=(224, 224, 3)))

        # Add last layers
        x = resnet50.output
        x = tf.keras.layers.Dense(512, activation = 'relu')(x)
        predictions = tf.keras.layers.Dense(4, activation = 'softmax')(x)

        self._model = tf.keras.Model(inputs = resnet50.input, outputs = predictions)
        self._data_shape = self._model.layers[0].get_input_shape_at(0)[1:]
        self._labels_shape = self._model.layers[-1].get_output_shape_at(0)[1:]

        self._criterion = tf.keras.losses.CategoricalCrossentropy()
        self._optimizer = tf.keras.optimizers.SGD(lr = 1e-3, decay = 1e-6, momentum = 0.9, nesterov = True)
        self._metrics = [tf.keras.metrics.categorical_accuracy]
        #self._metrics = [tf.keras.metrics.Accuracy()]

        self._batch_size = batch_size
        self._epochs = epochs

        self._model.compile(optimizer=self._optimizer, loss=self._criterion, metrics=self._metrics)

    def train(self, data, labels):

        train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
                                            preprocessing_function = tf.keras.applications.resnet50.preprocess_input,
                                            #rotation_range = 5,
                                            #width_shift_range = 0.5,
                                            #height_shift_range = 0.5,
                                            #horizontal_flip = True,
                                            validation_split=0.1
                                          )
        train_generator = train_datagen.flow(data, labels, batch_size=self._batch_size, subset='training', shuffle=True)
        validation_generator = train_datagen.flow(data, labels, batch_size=1, subset='validation', shuffle=False)

        #early_stopping = tf.keras.callbacks.EarlyStopping(monitor = 'val_loss', patience = 10, verbose=1, restore_best_weights = True)
        early_stopping = tf.keras.callbacks.EarlyStopping(monitor = 'val_categorical_accuracy', patience = 10, verbose=1, restore_best_weights = True)
        self._model.fit(
            x=train_generator,
            steps_per_epoch= int(len(data)*0.9) // self._batch_size,
            validation_data = validation_generator,
            validation_steps = int(len(data)*0.1),
            epochs=self._epochs,
            callbacks = [early_stopping]
        )

    def predict(self, data):

        #probs = self._model.predict(data, batch_size=self._batch_size)

        preds = []
        preds_4 = []
        no_concuerda = 0
        for i in range(int(len(data)/2)):

            tn = data[2*i]
            tp = data[2*i+1]

            tn = np.expand_dims(tn, axis = 0)
            tp = np.expand_dims(tp, axis = 0)
            tp = tf.keras.applications.resnet50.preprocess_input(tp)
            tn = tf.keras.applications.resnet50.preprocess_input(tn)

            prob_tn = self._model.predict(tn)[0]
            prob_tp = self._model.predict(tp)[0]

            pred_tp = np.argmax(prob_tp)
            pred_tn = np.argmax(prob_tn)

            preds_4.append(pred_tn)
            preds_4.append(pred_tp)

            if pred_tp == self._dict_labels['NTP'] and pred_tn == self._dict_labels['NTN']:
                pred = self._dict_labels['N']
            elif pred_tp == self._dict_labels['PTP'] and pred_tn == self._dict_labels['PTN']:
                pred = self._dict_labels['P']
            else:
                no_concuerda = no_concuerda + 1
                # prob_p = prob_tp[0][dict['PTP']] + prob_tp[0][dict['PTN']] + prob_tn[0][dict['PTP']] + prob_tn[0][dict['PTN']]
                # prob_n = prob_tp[0][dict['NTP']] + prob_tp[0][dict['NTN']] + prob_tn[0][dict['NTP']] + prob_tn[0][dict['NTN']]
                prob_p = max(prob_tp[self._dict_labels['PTP']], prob_tp[self._dict_labels['PTN']], prob_tn[self._dict_labels['PTP']], prob_tn[self._dict_labels['PTN']])
                prob_n = max(prob_tp[self._dict_labels['NTP']], prob_tp[self._dict_labels['NTN']], prob_tn[self._dict_labels['NTP']], prob_tn[self._dict_labels['NTN']])
                if prob_p >= prob_n:
                    pred = self._dict_labels['P']
                else:
                    pred = self._dict_labels['N']

            preds.append(pred)

        preds = np.array(preds)
        preds_4 = np.array(preds_4)

        return preds, preds_4, no_concuerda

    def evaluate(self, data, labels):
        preds, preds_4, no_concuerda, = self.predict(data)

        labels_4 = np.array([np.argmax(l) for l in labels])
        labels_2 = []

        for i in range(int(len(labels)/2)):
            lab = np.argmax(labels[2*i])
            if lab == self._dict_labels['PTP'] or lab == self._dict_labels['PTN']:
                etiq = 'P'
            else:
                etiq = 'N'

            labels_2.append(self._dict_labels[etiq])

        labels_2 = np.array(labels_2)

        acc_4 = sum(labels_4 == preds_4)/len(labels_4)

        acc = sum(labels_2 == preds)/len(labels_2)

        cr = classification_report(labels_2, preds, digits = 5, output_dict = True)

        metrics = [acc, acc_4, no_concuerda, cr]

        return metrics

    def save(self, path):
        self._model.save(path + "SDNET_C.h5")

    def load(self, path):
        self._model = tf.keras.models.load_model(path + "SDNET_C.h5")


#!/usr/bin/env python3

import glob
import json
import pathlib

from elasticsearch import Elasticsearch
from elasticsearch.exceptions import TransportError
from elasticsearch.helpers import bulk, streaming_bulk

YEAR = "2017"

def read_judgments_jsons(files):
    for path in files:
        yield json.loads(pathlib.Path(path).read_text())

def items_from_judgments_jsons(judgments):
    for judgment in judgments:
        for item in judgment["items"]:
            yield item

def is_correct_judgment_year(year):
    return lambda item: item["judgmentDate"][:4] == year

def items_in_given_year(files):
    judgments_jsons = read_judgments_jsons(files)
    items = items_from_judgments_jsons(judgments_jsons)
    for item in filter(is_correct_judgment_year(YEAR), items):
        yield item

def create_judgment_index(client):
    create_index_body = {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 0,

        "analysis": {
          "analyzer": {
            "polish_analyzer": {
              "type": "custom",
              "tokenizer": "standard",
              "filter": ["morfologik_stem"]
            }
          }
        }
      },
      "mappings": {
        "doc": {
          "properties": {
            "id": {"type": "keyword"},
            "date": {"type": "date"},
            "text_content": {"type": "text", "analyzer": "polish_analyzer"},
            "judges": {"type": "keyword"}
          }
        }
      }
    }
    client.indices.create(
        index="judgment",
        body=create_index_body
    )

def items_to_es_doc(items):
    for item in items:
        yield {
            "text_content": item["textContent"],
            "date": item["judgmentDate"],
            "id": item["id"],
            "judges": [judge["name"] for judge in item["judges"]]
        }

def push_judgments_to_es(client, items_in_es_format):
    for ok, result in streaming_bulk(
            client,
            items_in_es_format,
            index="judgment",
            doc_type="doc",
            max_retries=10,
            chunk_size=400,
            ):
        action, result = result.popitem()
        doc_id = "/{}/doc/{}".format("judgment", result['_id'])

        if not ok:
            print("Failed {} document {}: {}".format(action, doc_id, result))
        else:
            print(doc_id)

if __name__ == "__main__":
    es = Elasticsearch("localhost:9200")
    try:
        create_judgment_index(es)
    except Exception as e:
        print(e)

    files = glob.glob("data/json/judgments*.json")
    items = items_in_given_year(files)
    push_judgments_to_es(es, items_to_es_doc(items))

    es.indices.refresh(index="judgment")


import sys

import bdatos
import configparser
import os
import errno
import time
from ping3 import ping as pineador

# ---------------------VARIABLES----------------------------
ANCHO = 2
LARGO = 2
RUTA = "dataConfig"
ARCHIVO = RUTA + "/configuracion.ini"

# ----------------------------------------------------------

# ---------------BASE DE DATOS-----------------
def obtener_servidores():
    resultado = ()
    for servidor in bdatos.cargar_servidores():
        resultado = resultado + servidor
    return resultado

# ----------------------------------------------------------

# ---------------CONFIGURACION------------------------------
def existe_configuracion(objeto=None):
    if not os.path.exists(ARCHIVO):
        crear_configuracion()
        bdatos.existe_bd()
    elif objeto == None:
        guardar_configuracion_ini("servidor_actual", "localhost")
    if objeto != None:
        objeto.read(ARCHIVO)

def crear_configuracion():
    try:
        os.mkdir(RUTA)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise
    configuracion = configparser.ConfigParser()
    try:
        configuracion.add_section("CONFIGURACION")
    except configparser.DuplicateSectionError:
        pass
    configuracion.set("CONFIGURACION", "servidor_actual", "localhost")
    configuracion.set("CONFIGURACION", "regular_ping", "300")
    configuracion.set("CONFIGURACION", "mal_ping", "1000")
    configuracion.set("CONFIGURACION", "cantidad_pings", "60")
    with open(ARCHIVO, "w") as archivo_de_configuracion:
        configuracion.write(archivo_de_configuracion)

def guardar_configuracion_ini(key, valor):
    configuracion = configparser.ConfigParser()
    existe_configuracion(objeto=configuracion)
    try:
        configuracion.set("CONFIGURACION", key, valor)
        with open(ARCHIVO, "w") as archivo_de_configuracion:
            configuracion.write(archivo_de_configuracion)
    except:
        crear_configuracion()

def crear_obcion(key):
    configuracion = configparser.ConfigParser()
    existe_configuracion(objeto=configuracion)
    try:
        configuracion.set("CONFIGURACION", key, "")
        with open(ARCHIVO, "w") as archivo_de_configuracion:
            configuracion.write(archivo_de_configuracion)
    except configparser.NoSectionError:
        crear_configuracion()

def leer_configuracion(key):
    configuracion = configparser.ConfigParser()
    existe_configuracion(objeto=configuracion)
    try:
        value = configuracion.get("CONFIGURACION", key)
        return value
    except configparser.NoSectionError:
        crear_configuracion()
        return leer_configuracion(key)
    except configparser.NoOptionError:
        crear_obcion(key)
        return leer_configuracion(key)

def servidor_actual():
    return leer_configuracion("servidor_actual")

def regular_ping():
    return leer_configuracion("regular_ping")

def mal_ping():
    return leer_configuracion("mal_ping")

def cantidad_pings():
    return leer_configuracion("cantidad_pings")

# ----------------------------------------------------------

def cerrar_aplicacion(aplicacion):
    aplicacion.hilo = False
    aplicacion.destroy()

def redondear_o_Nulear(dato):
    if dato == None:
        return "---"
    else:
        return round(dato)

def reiniciar_pings(raiz, e):
    raiz.L_maximoping["text"] = "MAX: ---"
    raiz.L_minimoping["text"] = "MIN: ---"
    raiz.L_promedioping["text"] = "PROM: ---"
    raiz.L_perdidaping["text"] = "LOST: ---"
    raiz.F_cobertura.configure(style="Red.TFrame")
    raiz.nueva_direccion = True

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

def pinear2(raiz, id):
    if raiz.tv_servidores.condicional:
        raiz.tv_servidores.set(id, 'MAX', str(maximo_ping(raiz.tv_servidores.hilos[id]["pings"])) + "ms")
        raiz.tv_servidores.set(id, 'MIN', str(minimo_ping(raiz.tv_servidores.hilos[id]["pings"])) + "ms")
        raiz.tv_servidores.set(id, 'PROM', str(promedio_ping(raiz.tv_servidores.hilos[id]["pings"])) + "ms")
        raiz.tv_servidores.set(id, 'LOST', str(perdida_ping(raiz.tv_servidores.hilos[id]["pings"])) + "%")

def calcular_ping2(raiz, id, nombre):
    while raiz.tv_servidores.condicional:
        direccion = bdatos.obtener_direccion(nombre)
        tiempo_de_espera = ping_en_profundidad(raiz.tv_servidores.hilos[id]["pings"], direccion)
        pinear2(raiz, id)
        eliminar_pings_vencidos(raiz.tv_servidores.hilos[id]["pings"])
        esperar(tiempo_de_espera)

def maximo_ping(list):
    MAX = None
    for pin in list:
        if pin != None:
            if MAX == None or MAX < pin:
                MAX = pin
    return redondear_o_Nulear(MAX)

def minimo_ping(list):
    MIN = None
    for pin in list:
        if pin != None:
            if MIN == None or MIN > pin:
                MIN = pin
    return redondear_o_Nulear(MIN)

def promedio_ping(list):
    suma_total = None
    conteo = 0
    for pin in list:
        if pin != None:
            if suma_total == None:
                suma_total = pin
            else:
                suma_total += pin
            conteo += 1
    if suma_total == None:
        return redondear_o_Nulear(None)
    else:
        return redondear_o_Nulear(suma_total / conteo)

def perdida_ping(list):
    suma_total = 0
    if len(list) != 0:
        for pin in list:
            if pin == None:
                suma_total += 1
        return redondear_o_Nulear(suma_total / len(list) * 100)
    return "---"

def ping_en_profundidad(list, direccion):
    inicio = time.time()
    try:
        ping = pineador(direccion, timeout=1)
    except:
        ping = False
    if type(ping) in (float, int):
        ping = ping * 1000
        lantencia = round(ping) / 1000
        tiempo_de_espera = 1 - lantencia
        list.append(ping)
    else:
        list.append(None)
        final = time.time()
        tiempo_de_espera = (1000 - (final - inicio)) / 1000
    return tiempo_de_espera

def eliminar_pings_vencidos(list):
    if len(list) > int(leer_configuracion("cantidad_pings")):
        list.pop(0)

def esperar(tiempo_de_espera):
    if tiempo_de_espera > 0:
        time.sleep(tiempo_de_espera)