#!/usr/bin/env python3

import cv2
import math
import numpy as np
import os
import pandas as pd
from shapely.geometry import Polygon

color =	{
  "leaf": (0,255,0),
  "pepper": (0,0,255),
  "pot": (255,255,255),
  "stem": (100,0,100),
  "flower": (255,0,0),
  "peduncle": (100, 100, 0)
}

# thresholds
MIN_RATIO = 0.6 # percentage of smallest bboxes to be removed
IOU = 0.2 # threshold for intersection over union

DB = 'segmentation_data_png' # database folder

# input data
IMAGE = os.path.join(DB, 'image_bg')
BBOXES_CSV = os.path.join(DB, 'bboxes_csv')
DISTANCE = os.path.join(DB, 'distance')

# output data
BBOXES_LABELED = os.path.join(DB, 'bboxes_edited_labeled')
BBOXES_EDITED_CSV = os.path.join(DB, 'bboxes_edited_csv')

def calculate_iou(box_1, box_2):
    poly_1 = Polygon(box_1)
    poly_2 = Polygon(box_2)
    iou = poly_1.intersection(poly_2).area / poly_1.union(poly_2).area
    return iou

csv_edited = os.listdir(BBOXES_EDITED_CSV)
for csv in os.listdir(BBOXES_CSV):
    if csv not in csv_edited:
        base_name = csv.split('.')[0]
        print(base_name)
        img_name = base_name + '.jpg'
        img = cv2.imread(os.path.join(IMAGE, img_name))

        distance = []
        df_distance = pd.read_csv(os.path.join(DISTANCE, csv), header=None)
        for index, row in df_distance.iterrows():
            distance.append(row[1])

        area = []
        bboxes = []
        df = pd.read_csv(os.path.join(BBOXES_CSV, csv))
        df_final = pd.DataFrame(columns=['filename', 'width', 'height', 'class', 'id', 'xmin', 'ymin', 'xmax', 'ymax'])
        for index, row in df.iterrows():
            if(row['class'] == 'leaf'):
                id = row['id']
                xmin = row['xmin']
                ymin = row['ymin']
                xmax = row['xmax']
                ymax = row['ymax']
                bbox_curr = [[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymin]]
                area_curr = (xmax-xmin)*(ymax-ymin)
                area.append(area_curr)
                bboxes.append((id, area_curr, bbox_curr, xmin, ymin, xmax, ymax, row))
            else:
                df_final = df_final.append(row)
                # TODO nacrtaj taj rectangle na slici?

        # remove MIN_RATIO% of the smallest bboxes
        area_sorted = sorted(area)
        remove_num = math.floor(MIN_RATIO*len(area))

        # if there are any leaves in the img
        bboxes_final = []
        if(remove_num):
            print('Discarding ' + str(remove_num) + ' smallest bboxes.')
            min_area = area_sorted[remove_num]
            bboxes_wo_min = []
            for bbox in bboxes:
                if bbox[1] > min_area:
                    bboxes_wo_min.append(bbox)

            # remove bboxes based on IoU ratio
            counter = 0
            if(len(bboxes_wo_min)):
                bboxes_final.append(bboxes_wo_min[0])
                for i in range (1, len(bboxes_wo_min)):
                    valid = True
                    for j in range(i):
                        iou_curr = calculate_iou(bboxes_wo_min[i][2], bboxes_wo_min[j][2])
                        if iou_curr > IOU:
                            dist_bbox_i = distance[bboxes_wo_min[i][0]]
                            dist_bbox_j = distance[bboxes_wo_min[j][0]]
                            if dist_bbox_j > dist_bbox_i:
                                if bboxes_wo_min[j] in bboxes_final:
                                    bboxes_final.remove(bboxes_wo_min[j])
                                    counter += 1
                            else:
                                if valid:
                                    valid = False
                                    counter += 1

                    if valid:
                        bboxes_final.append(bboxes_wo_min[i])

            print('Removed ' + str(counter) + ' bboxes based on IoU ratio.')

        # generate final csv and visualize result
        for bbox in bboxes_final:
            df_final = df_final.append(bbox[7])
            cv2.rectangle(img, (bbox[3],bbox[4]),(bbox[5],bbox[6]),(0,255,0),2)

        cv2.namedWindow('bw', cv2.WINDOW_NORMAL)
        cv2.resizeWindow('bw', 600,600)
        cv2.imshow('bw', img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

        cv2.imwrite(os.path.join(BBOXES_LABELED, (base_name + '_' + str(MIN_RATIO) + '_' + str(IOU) + '.jpg')), img)

        df_final.to_csv(os.path.join(BBOXES_EDITED_CSV, csv), index = False, header = True)

        print('Done')


from multiprocessing import Process, Queue
import serial
import time
import timeit
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy import optimize as opt
import numpy as np
from math import pi
import itertools
from functools import partial

WRITE = False
offsets = offsets = [0, -4, -4.5, -4.5]
amplitude_weights = [1, 1, 1, 1]

f = 40000
c = 340
wavelength = c/f
k = 2*pi/wavelength
pi_over_180 = pi / 180

spacing = 0.0105
spacing_over_c = spacing / c

def convertToInt(array):
    # return [int.from_bytes(array[2*i:2*i+1], byteorder='little') \
            # for i in range((int)(bytes_per_array/2))]
    new_array = []
    for a in zip(array[::2], array[1::2]):
        new_array.append(int.from_bytes(a, byteorder='little'))
    return new_array

def max_rolling1(a, window,axis =1):
        shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
        strides = a.strides + (a.strides[-1],)
        rolling = np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
        return np.max(rolling,axis=axis)

def modulated_sine(x, *pars):
    try:
        if len(pars) == 1:
            pars = pars[0]
        f, phi, p0, p1, p2, offset = pars
    except ValueError:
        print(pars)
        raise
    local_aplitude = (p0 + p1*x + p2*x**2)
    return local_aplitude*np.sin(2*pi*f*x + phi) + offset

def fit_data(x, y, window_size):
    t1 = x
    sensor = y
    fit_window = window_size
    fit_sensor = lambda x: 0
    r = 0
    par, _ = opt.curve_fit(
        modulated_sine,
        t1[0:2*fit_window] - 0.5*fit_window*4,
        sensor[0:2*fit_window],
        [40000*1e-6, 0, 11, 0.1, 0.1, 1700],
        maxfev = 1000
    )
    temp = lambda x, pars=par, r=r, fit_window=fit_window: ((r)*fit_window*4 < x) * (x < (r+1)*fit_window*4) * modulated_sine(x-(r+0.5)*fit_window*4, pars)
    temp2 = lambda x, f=fit_sensor, t=temp: f(x) + t(x)
    fit_sensor = temp2

    for r in range(1, int(len(x)/fit_window)-1):
        try:
            par, _ = opt.curve_fit(
                modulated_sine,
                t1[int((r-0.7)*fit_window):int((r+1.7)*fit_window)] - (r+0.5)*fit_window*4,
                sensor[int((r-0.7)*fit_window):int((r+1.7)*fit_window)],
                [40000*1e-6, 0.1, 1, 0.1, 0.01, 1700],
                maxfev=1000
            )
        except RuntimeError:
            par = [0, 0, 0, 0, 0, 0]
        if r == int(1600/fit_window):
            print(par[0])
        temp = lambda x, pars=par, r=r, fit_window=fit_window: ((r)*fit_window*4 < x) * (x < (r+1)*fit_window*4) * modulated_sine(x-(r+0.5)*fit_window*4, pars)
        temp2 = lambda x, f=fit_sensor, t=temp: f(x) + t(x)
        fit_sensor = temp2
    return fit_sensor

def plot_2d(waveforms, delta_theta=60):
    # sin(m_angle*pi/180.)*k*m_spacing
    NUM_RECEIVERS = len(waveforms)
    num_samples = len(waveforms[0])

    times = [np.linspace(i, num_samples*4 + i, num_samples) for i in range(NUM_RECEIVERS)]

    fit_window = 23

    start_time = timeit.default_timer()

    fit_sensors = []

    for t, w in zip(times, waveforms):
        fit_sensors.append(fit_data(t[50:-500], w[50:-500], fit_window))

    elapsed = timeit.default_timer() - start_time
    print("########\nFits took {} ms".format(elapsed*1e3))
    start_time = timeit.default_timer()

    theta = np.arange(-delta_theta/2, delta_theta/2, 0.8)
    rho   = np.arange(0.23, 0.37, 0.001)

    timeDiff = np.sin(theta*pi_over_180)*spacing_over_c

    window_size = 17 # sollte ungerade sein
    rho_plot = rho[int(window_size/2):-int(window_size/2)]
    timeDistance = 2e6 * rho / 340

    z_plot = []
    for t_ in timeDiff:
        # timeDiff = np.sin(t_*pi_over_180)*spacing_over_c
        values = np.zeros(len(timeDistance))
        for i in range(NUM_RECEIVERS):
            values += amplitude_weights[i]*fit_sensors[i](offsets[i]+timeDistance-i*1e6*t_)

        values = np.abs(values)
        z_plot.append(max_rolling1(values, window_size))

    z_plot = np.array(z_plot)
    z_min = z_plot.min()
    z_max = z_plot.max()
    z_plot -= z_min
    z_plot /= z_max
    z_plot = np.exp(z_plot)

    elapsed = timeit.default_timer() - start_time
    print("Digital scan took {} ms".format(elapsed*1e3))

    return theta, rho_plot, z_plot

def handle_arduino_output(queue, delta_theta=60):
    with serial.Serial(port='COM4', baudrate=115200, timeout=5.0) as arduino:
        filesWritten = 0
        while True:

            # align to starting pattern
            count = 0
            pattern = [1, 200, 31, 41]
            patternPosition = 0
            while True:
                value = arduino.read(1)[0]
                if value == pattern[patternPosition]:
                    count += 1
                else:
                    count = 0
                    patternPosition = 0
                if count > 24:
                    # print("Found {} {} in a row".format(count, pattern[patternPosition]))
                    patternPosition += 1
                    count = 0
                    if patternPosition == len(pattern):
                        break

            try:
                message = arduino.readline()
                print(message.decode("utf-8"))

                bytes_per_array = int.from_bytes(arduino.read(4), byteorder='little')
                num_arrays      = int.from_bytes(arduino.read(4), byteorder='little')
                print("Bytes per array: {}".format(bytes_per_array))
                start_time = timeit.default_timer()
                arrays = []
                for i in range(num_arrays):
                    arrays.append(arduino.read(bytes_per_array))
                elapsed = timeit.default_timer() - start_time

                print("Timed the serial read. Elapsed time: {:2.3f} ms".format(elapsed * 1000))

                start_time = time.perf_counter_ns()
                for i in range(num_arrays):
                    arrays[i] = convertToInt(arrays[i])

                elapsed = time.perf_counter_ns() - start_time

                if filesWritten < 5 and WRITE:
                    with open('waveforms/0deg_waveform_{}'.format(filesWritten), 'w') as f:
                        for i in range(len(arrays[0])):
                            for j in range(len(arrays)):
                                f.write("{} ".format(arrays[j][i]))
                            f.write("\n")
                    filesWritten += 1

                theta, rho_plot, z_plot = plot_2d(arrays, delta_theta)
                # queue.put((arrays[0][:-1000:2], theta, rho_plot, z_plot,))
                queue.put((0, theta, rho_plot, z_plot,))
                # queue.put((arrays[0][:-1500], 0, 0, 0,))

                message = arduino.readline()
                print(message.decode("utf-8"))
                print("\n")

            except UnicodeDecodeError:
                pass

def make_frame(delta_theta, queue, ax1, ax2):
    array, theta, rho_plot, z_plot = queue.get(timeout=5.0)

    colormap = plt.get_cmap('inferno')
    z_min = z_plot.min()
    z_max = z_plot.max()
    norm = mpl.colors.Normalize(z_min, z_max)
    ax1.clear()
    ax1.set_thetamin(-delta_theta/2)
    ax1.set_thetamax(delta_theta/2)
    ax1.set_rmin(0.2)
    ax1.set_theta_offset(pi/2)

    for t, values in zip(theta, z_plot):
        ax1.scatter([-t*pi/180]*len(rho_plot), rho_plot, c=values, s=6, cmap=colormap, norm=norm, linewidths=0)

    # xarr = range(len(array))
    # ax2.clear()
    # ax2.plot(xarr, array)

def main():
    delta_theta = 60

    queue = Queue()
    arduino_process = Process(target=handle_arduino_output, args=(queue,delta_theta,))
    arduino_process.daemon = True
    arduino_process.start()

    fig = plt.figure()
    ax1 = plt.subplot(1, 1, 1, polar=True)
    # ax2 = plt.subplot(1, 2, 2)
    next_frame = partial(make_frame, queue=queue, ax1=ax1, ax2=None)

    ani = animation.FuncAnimation(fig, next_frame, itertools.repeat(60), interval=10, save_count=1)

    plt.tight_layout()
    plt.show()

if __name__ == '__main__':
    main()


import numpy as np
from sklearn.metrics import confusion_matrix, mean_squared_error
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import itertools
import code
from sklearn import svm, datasets
from sklearn.metrics import roc_curve, auc, precision_recall_fscore_support
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
from sklearn.decomposition import PCA
from scipy import interp
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, StratifiedKFold, train_test_split
from itertools import cycle
import math
import numpy as Math
from numpy import genfromtxt

def plotHistogram(x,xlabel,ylabel,title):
    plt.hist(x, normed=False, bins=10)
    mean = round(np.mean(x))
    min = np.min(x)
    max = np.max(x)
    std = np.round(np.std(x),3)
    plt.text(2000,50, "Mean="+str(mean) + "\n" + "Min="+str(min) + "\n" + "Max="+str(max) + "\n" + "Std="+str(std))
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.title(title)
    plt.show()

def plot2d(X, y, title):
	fig = plt.figure(figsize=(6,6))
	fig.suptitle(title, fontsize=20)
	ax = fig.add_subplot(111)
	ax.set_xlabel("n1",fontsize=12)
	ax.set_ylabel("n2",fontsize=12)
	ax.grid(True,linestyle='-',color='0.75')
	colors = ['r','g','b','black','white','c','m','y','#CCB974','#77BEDB']

	for i in np.unique(y):
		ax.scatter(X[np.where([y==i])[1], 0], X[np.where([y==i])[1], 1], c=colors[i-1], marker='o')

	ax.set_xlabel('n1')
	ax.set_ylabel('n2')

	plt.show()

def load_csv(file_path, delimiter):
    X = genfromtxt(file_path, delimiter=delimiter)
    return X

def round_keep_sum(cm, decimals=2):
	rcm = np.round(cm, decimals)
	for i in range(rcm.shape[0]):
		column = rcm[i,:]
		error = 1 - np.sum(column)
		sr = 10**(-decimals)
		n = int(round(error / sr))
		for _,j in sorted(((cm[i,j] - rcm[i,j], j) for j in range(cm.shape[1])), reverse=n>0)[:abs(n)]:
			rcm[i,j] += math.copysign(0.01, n)
	return rcm

# def plotNiceConfusionMatrix(y_test, y_pred, class_names):
# 	# Compute confusion matrix
# 	cnf_matrix = confusion_matrix(y_test, y_pred)
# 	np.set_printoptions(precision=2)
#
# 	# Plot non-normalized confusion matrix
# 	fig = plt.figure()
# 	conf_M(cnf_matrix, classes=class_names, title='Confusion matrix, without normalization')
# 	fig.set_tight_layout(True)
#
# 	plt.show()

def plotBothConfusionMatrices(y_test, y_pred, class_names):
	# Compute confusion matrix
	cnf_matrix = confusion_matrix(y_test, y_pred)
	# Plot non-normalized confusion matrix
	fig = plt.figure()
	conf_M2(cnf_matrix, classes=class_names, title='Confusion matrix')
	fig.set_tight_layout(True)
	# plt.show()
	return fig

# This function loads the data into X and y,
# outputs the feature names, the label dictionary, m and n

def loadnumpy(filename):
	array = np.load(filename)
	return array

def addOffset(X):
	X = np.c_[np.ones((X.shape[0],1)), X]
	return X

def normalize(DATA):
	for i in range(DATA.shape[1]):
		DATA[:,i] = (DATA[:,i] - np.mean(DATA[:,i]))/(np.max(DATA[:,i])-np.min(DATA[:,i])+.001)
	return DATA

def rms(y_true, y_pred):
	return mean_squared_error(y_true, y_pred, sample_weight=None, multioutput='uniform_average')

# def plotConfusionMatrix(y, pred):
# 	n_labels = len(np.unique(y))
# 	matrix = np.zeros([n_labels+1,n_labels+1])
# 	matrix[0,1:] = np.unique(y)
# 	matrix[1:,0] = np.unique(y)
# 	matrix[1:,1:] = confusion_matrix(y.astype(int), pred.astype(int))
# 	print(matrix)

def round_keep_sum(cm, decimals=2):
	rcm = np.round(cm, decimals)
	for i in range(rcm.shape[0]):
		column = rcm[i,:]
		error = 1 - np.sum(column)
		sr = 10**(-decimals)
		n = int(round(error / sr))
		for _,j in sorted(((cm[i,j] - rcm[i,j], j) for j in range(cm.shape[1])), reverse=n>0)[:abs(n)]:
			rcm[i,j] += math.copysign(0.01, n)
	return rcm

def conf_M(cm):
    cmap=plt.cm.Blues
    classes = ["mytotic", "healthy"]
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)
    print('Confusion matrix')
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j], horizontalalignment="center", color="black")
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
    plt.show()


# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
#

import os
import sys
import torch
import numpy as np
import cv2
from argparse import Namespace
sys.path.append( os.path.join(os.getcwd(), 'RAFT-Stereo'))
sys.path.append( os.path.join(os.getcwd(), 'RAFT-Stereo', 'core'))
from raft_stereo import RAFTStereo
from utils.utils import InputPadder
from collections import OrderedDict

# Source code adapted from https://github.com/princeton-vl/RAFT-Stereo
class RAFTStereoWrapper:

    def __init__(self, model_path):
        super(RAFTStereoWrapper, self).__init__()

        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.valid_iters = 7

        # set torch options
        torch.backends.cudnn.enabled = True
        torch.backends.cudnn.benchmark = True

        # Initialize RAFT-Stereo model parameters
        args = Namespace(
            mixed_precision=True,
            hidden_dims=[128]*3,
            corr_implementation="reg_cuda",
            shared_backbone=True,
            context_norm="batch",
            corr_levels=4,
            slow_fast_gru=True,
            corr_radius=4,
            n_downsample=3,
            n_gru_layers=2
        )

        # Create model
        model = RAFTStereo(args)

        # RAFT-Stereo checkpoints are saved as nn.DataParallel.
        # So we create a new OrderedDict that does not contain the 'module.' prefix
        # Code from:
        # https://discuss.pytorch.org/t/solved-keyerror-unexpected-key-module-encoder-embedding-weight-in-state-dict/1686/2
        state_dict = torch.load(model_path)
        new_state_dict = OrderedDict()
        for k, v in state_dict.items():
            name = k[7:] # remove `module.`
            new_state_dict[name] = v
        model.load_state_dict(new_state_dict)
        self.model = model.to(self.device)
        self.model.eval()

    def __call__( self, left, right ):
        """
        Estimate stereo depth for the given left/rigth RGB input image pair.
        The input image values should be in the range [0, 1].
        """
        with torch.no_grad():
            left = (left * 255.).to(self.device).unsqueeze(0)
            right = (right * 255.).to(self.device).unsqueeze(0)

            padder = InputPadder(left.shape, divis_by=32)
            left, right = padder.pad(left, right)

            _, prediction = self.model(left, right, iters=self.valid_iters, test_mode=True)
            prediction = padder.unpad(prediction)

        return prediction


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Sep 27 11:51:14 2019

@author: apple
"""
import pandas as pd
import numpy as np
import math
from gurobipy import Model,GRB,quicksum
import csv

def optModel(roomdata, S, E, A, B, G, SR=None):
    roomsIndex = list(range(0,len(roomdata.iloc[:,0])))
    m = Model('Rooms')
    x = m.addVars(roomsIndex,name="x",vtype=GRB.BINARY)
    d = m.addVar(0, name="d", vtype=GRB.CONTINUOUS)
    DearDaniel = 7
    m.setParam("TIME_LIMIT", 45)

    #E = data.iloc[3,10]
    #A = data.iloc[4,10]
    #B = data.iloc[5,10]
    #G = data.iloc[6,10]
    #S = data.iloc[2,10]
    if SR is None:
        SR = 1800
    #Available rooms
    """x<= enddate & x>=startdate"""
    BoolInd = np.array([x>=float(startdate) for x in [float(a) for a in roomdata.columns.tolist()[11:]]])
    BoolInd2 = np.array([x<=float(enddate) for x in [float(a) for a in roomdata.columns.tolist()[11:]]])
    DateHeaders = [i+11 for i, x in enumerate(BoolInd&BoolInd2) if x]
    #print(IntInd)

    OccupiedRooms = roomdata.iloc[:, DateHeaders]
    OccupiedRoomsBool = np.sum(OccupiedRooms, axis=1)==1
    print(OccupiedRoomsBool)
    #print(OccupiedRooms)

    Orooms = roomdata.loc[OccupiedRoomsBool, :].index.values.tolist()
    Arooms = roomdata.loc[[not(a) for a in OccupiedRoomsBool],:]
    AroomsIndex = Arooms.index.values.tolist()
    print(Orooms)

    #Available rooms

    e = [0,1,2,40,41,42,43,44,106,107, 108, 109, 143, 23, 24,87]
    a = [39, 122, 123]
    b = [102,103,104,105,137,138,139]
    g = np.setdiff1d(roomsIndex,e+a+b)

    m.setObjective((quicksum(x[i] * roomdata.iloc[i,2] for i in roomsIndex))+ DearDaniel*d ,GRB.MINIMIZE)
    #space requirment
    #m.addConstr(quicksum(x[i] * data.iloc[i,1] for i in roomsIndex)>= S)
    #no occupied room
    m.addConstr(sum(x[i] for i in Orooms) == 0)
    #Exhibit hall is provided while requested
    m.addConstr(sum(x[i] for i in e)>= E)
    #Auditorium is provided while requested
    m.addConstr(sum(x[i] for i in a)>= A)
    #Ballroom is provided while requested
    m.addConstr(sum(x[i] for i in b)>= B)
    #Meeting Rooms
    m.addConstr(sum(x[i] for i in g)>= G)
    #if E is selected, Meeting room in that can't be selected
    m.addConstr((x[2]*(x[4]+x[5]+x[3]))==0)
    m.addConstr((x[40]*(x[46]+x[47]+x[45]))==0)
    m.addConstr((x[106]*(x[112]+x[113]+x[110]+x[111]))==0)
    m.addConstr(x[142]*(x[13]+x[14])==0)
    m.addConstr(x[78]*(x[79]+x[80])==0)
    m.addConstr(x[94]*(x[95]+x[96])==0)
    #Min SQFT
    m.addConstrs(x[i] == 0 for i in roomsIndex if roomdata.iloc[i,1] < SR)
    #distance stuff
    pRooms = [(Arooms.iloc[i,7],Arooms.iloc[i,8],Arooms.iloc[i,9],Arooms.iloc[i,10]) for i in range(len(AroomsIndex))]
    Sum = 0
    distMat = np.empty([len(Arooms), len(Arooms)])
    i = 0
    while i < len(Arooms):
        room1 = pRooms[i]
        j = i
        while j < len(Arooms):
            room2 = pRooms[j]
            adiff = [((room1[k] - room2[k])**2) for k in range(4)]
            dist = math.sqrt(sum(adiff))
            distMat[i][j] = dist
            distMat[j][i] = dist
            Sum += dist
            j+=1
        i+=1

    #distance calculation
    m.addConstr(d == sum([((x[i]*x[j]) * distMat[i][j]) for i in range(len(AroomsIndex)) for j in range(len(AroomsIndex))]))
    m.optimize()

    if m.status == GRB.Status.OPTIMAL or m.status == GRB.Status.TIME_LIMIT:
        x_sol = m.getAttr('x', x)
        chosenroom = []
        chosenbuild = []
        sumsqft = 0
        sumcost=0
        for i in roomsIndex:
            if round(x_sol[i])>= 1:
                chosenroom.append(roomdata.iloc[i,0])
                chosenbuild.append(roomdata.iloc[i,3])
                sumsqft = sumsqft + roomdata.iloc[i,1]
                sumcost = sumcost + roomdata.iloc[i,2]
#        #Chosen is the list of room selected
#
        for i in range(len(chosenbuild)):
            if chosenbuild[i] == 0:
               chosenbuild[i] = "A"
            elif chosenbuild[i]== 200:
               chosenbuild[i] = "B"
            else:
               chosenbuild[i] = "C"
        chosenbuild = set(chosenbuild)
    else:
    	sumcost = 0
    	chosenroom = []
#        print(chosenbuild)
#       if len(chosenroom) == 1:
#            print('{} is selected.'.format(chosenroom[0]))
#        else :
#           print('{} are selected.'.format(tuple(chosenroom)))
#        print('the sqft is {}'.format(sumsqft))
#        print('The room cost is {}'.format(sumcost))
#        print('The total cost is {}'.format(m.objVal))
    m.reset()
    return [sumcost, chosenroom]

if __name__ == "__main__":
	eventdata = pd.read_csv('TestData_v2.csv', index_col=0, header=0)
	roomdata = pd.read_csv('RoomInfo.csv', index_col=0, header=0)
	roomdata = roomdata[:143]
	csvOut = [['Cost', 'Rooms']]
	for i in range(0, len(eventdata)):
		startdate = eventdata.iloc[i, 3]
		enddate = eventdata.iloc[i, 4]
		optOut = optModel(roomdata, eventdata.iloc[i, 5], eventdata.iloc[i, 6], eventdata.iloc[i, 7], eventdata.iloc[i, 8], eventdata.iloc[i, 9], eventdata.iloc[i, 10])
		csvOut = csvOut + [optOut]

		BoolInd = np.array([x>=float(startdate) for x in [float(a) for a in roomdata.columns.tolist()[11:]]])
		BoolInd2 = np.array([x<=float(enddate) for x in [float(a) for a in roomdata.columns.tolist()[11:]]])
		IntInd = [i+11 for i, x in enumerate(BoolInd&BoolInd2) if x]
		print(IntInd)
		for a in optOut[1]:
			val = [a is b for b in roomdata.iloc[:, 0]]
			roomInd = [a for a, i in enumerate(val) if i]
			roomdata.iloc[roomInd, IntInd] = 1

	roomdata.to_csv('justforme.csv')
	with open('NewDataRoomsSelected.csv','w', newline="") as out:
		csv_out = csv.writer(out)
		for a in csvOut:
			csv_out.writerow(a)

    #if S> upper bound, return all the room


import os
from time import sleep

from bs4 import BeautifulSoup
from tqdm import tqdm

from utilities import login

def get_eeditions_urls(logged_in_session, page_number=1):
    r = logged_in_session.get(
        f'https://www.cortlandstandard.com/eeditions/?page_size=18&sub_type=eeditions&page={page_number}')
    sleep(5)
    soup = BeautifulSoup(r.content, 'html.parser')
    eeditions_container = soup.find('div', {'class': 'content-list row'})
    urls = []
    eeditions_title_containers = eeditions_container.find_all('h3')
    for eeditions_title_container in eeditions_title_containers:
        urls.append('https://www.cortlandstandard.com' + eeditions_title_container.find('a')['href'])

    return urls

def get_pdf_url(logged_in_session, eedition_url):
    print(eedition_url)
    pdf_url = None
    r = logged_in_session.get(eedition_url)
    soup = BeautifulSoup(r.content, 'html.parser')
    if 'This item is available in full to subscribers.' in soup.text:
        raise AttributeError(f'Not logged in.')
    links = soup.find_all('a')
    for link in links:
        url = link.get('href')
        if url:
            if 'cortland/files' in url:
                pdf_url = url
                break

    if not pdf_url:
        print(soup.prettify())
        raise AttributeError(f'pdf_url not found for {eedition_url}')
    return pdf_url

def main():
    logged_in_session = login()
    page_number = 0
    while True:
        page_number += 1
        print(f'page_number: {page_number}')
        urls = get_eeditions_urls(logged_in_session, page_number=page_number)
        if len(urls) == 0:
            break
        for url in tqdm(urls):
            pdf_title = url.split('/')[-1].split(',')[0]
            if pdf_title.startswith('saturday-'):
                pdf_title = pdf_title.replace('saturday-', '')
            if pdf_title.startswith('december6'):
                pdf_title = pdf_title.replace('december6', 'december-6')
            if pdf_title.startswith('november23'):
                pdf_title = pdf_title.replace('november23', 'november-23')
            try:
                month, day, year = pdf_title.split('-')
            except ValueError as e:
                print(pdf_title)
                raise e
            if not os.path.exists(f'pdfs/{year}'):
                os.mkdir(f'pdfs/{year}')
            if not os.path.exists(f'pdfs/{year}/{month}'):
                os.mkdir(f'pdfs/{year}/{month}')
            if not os.path.exists(f'pdfs/{year}/{month}/{day}'):
                os.mkdir(f'pdfs/{year}/{month}/{day}')
            # if pdf_title not already in pdfs/ folder, download the pdf
            pdf_path = f'pdfs/{year}/{month}/{day}/{pdf_title}.pdf'
            #if os.path.exists(pdf_path):
            #    continue
            sleep(5)
            try:
                pdf_url = get_pdf_url(logged_in_session, url)
                print('pdf_url:', pdf_url)
            except AttributeError:
                raise AttributeError(f'pdf_url not found for {url}')
                continue
            r = logged_in_session.get(pdf_url)
            with open(f'{pdf_path}', 'wb') as f:
                f.write(r.content)

if __name__ == '__main__':
    main()


from moviepy import editor, video
from random import randint
import os

def videfy(i):
    clip = editor.VideoFileClip("./vid.mp4")
    clip.resize(height = 1920, width = 1080)
    clip = clip.subclip(randint(15.00, 35.00), randint(clip.start, 600.00))
    a0 = editor.AudioFileClip("./audio0.mp3")
    a2 = editor.AudioFileClip("./audio1.mp3")
    a3 = editor.AudioFileClip("./audio2.mp3")
    a4 = editor.AudioFileClip("./audio3.mp3")
    a5 = editor.AudioFileClip("./audio4.mp3")
    outro = editor.AudioFileClip("./outro.mp3")
   # clip = clip_.subclip(randint(15.00, 600.00), randint(15.00, 600.00))

    mixed = editor.CompositeAudioClip([a0, a2.set_start(a0.end + 0.50),
                                        a3.set_start(a0.end + 0.50 + a2.end + 0.50),
                                        a4.set_start(a0.end + 0.50 + a2.end + 0.50 + a3.end + 0.50),
                                        a5.set_start(a0.end + 0.50 + a2.end + 0.50 + a3.end + a4.end + 0.50),
                                        outro.set_start(a0.end + 0.50 + a2.end + 0.50 + a3.end + a4.end + 0.50 + a5.end + 1.50)])

    subclip = clip.set_duration(mixed.duration + 3)
    subclip.audio = mixed
    subclip.fps = 24

    subclip.write_videofile("f" + str(i) + ".mp4")
    os.system("ffmpeg -i f" + str(i) + ".mp4 -vf scale=1080:1920 nm" + str(i) + ".mp4")


import os
import pytest

from scripts import data_processor

@pytest.fixture(scope="module")
def city_list_location():
    return 'tests/resources/cities/'

@pytest.fixture(scope="module")
def process_data(city_list_location):
    files = os.listdir(city_list_location)

    def _specify_type(file_name_or_type):
        for f in files:
            if file_name_or_type in f:
                if file_name_or_type != '.json':
                    data = data_processor.csv_reader(city_list_location + f)
                else:
                    data = data_processor.json_reader(city_list_location + f)
        return data

    yield _specify_type

def test_csv_reader_header_fields(process_data):
    """
    Happy Path test to make sure the processed data
    contains the right header fields
    """
    data = process_data(file_name_or_type="clean_map.csv")
    header_fields = list(data[0].keys())
    assert header_fields == [
            'Country',
            'City',
            'State_Or_Province',
            'Lat',
            'Long',
            'Altitude'
            ]

def test_csv_reader_data_contents(process_data):
    """
    Happy Path Test to examine that each row
    had the appropriate data type per field
    """
    data = process_data(file_name_or_type="clean_map.csv")

    # Check row types
    for row in data:
        assert(isinstance(row['Country'], str))
        assert(isinstance(row['City'], str))
        assert(isinstance(row['State_Or_Province'], str))
        assert(isinstance(row['Lat'], float))
        assert(isinstance(row['Long'], float))
        assert(isinstance(row['Altitude'], float))

    # Basic data checks
    assert len(data) == 180  # We have collected 180 rows
    assert data[0]['Country'] == 'Andorra'
    assert data[106]['Country'] == 'Japan'

def test_csv_reader_malformed_data_contents(process_data):
    """
    Sad Path Test
    """
    with pytest.raises(ValueError) as exp:
        process_data(file_name_or_type="malformed_map.csv")
    assert str(exp.value) == "could not convert string to float: 'not_an_altitude'"


"""
Simulation of Vehicle Support Center operations (24hr).

Requirements:
    - Must be able to simulate 24 hours
    - Must be able to simulate the calls coming in on a definable distribution
    - Must be able to simulate the shifts starting at defined times
    - Must be able to simulate the whole week
    - ASR calculation must be an average of each interaction, NOT the average
        of the day to day ASR

Open issues:
    - wait times from previous hour are not carrying over to next hour
"""

import random
import simpy
import numpy as np
import pandas as pd
import datetime
import csv
import math

""" Global vars
All of the time related variables are in seconds, and outputs converted to
minutes later when data is recorded/displayed.
"""
# agent starts per day
AGENT_STARTS = 10
# tracks the agents currently working
#   key is int tracking the agent that is starting
AGENT_NO = 0
AGENTS_WORKING = {}
# tracks the number of agents that are available to work the rest of the day
BENCH = -1
# stats about the interactions that VSC handless in a day (calculate based on
# stat analysis)
INTERACTIONS_MEAN = 872
INTERACTIONS_STDEV = 40
# this is set for the day by the setter function
INTERACTIONS_TODAY = 0
# in seconds (480 sec = 8 min) 10.6min = 640 sec, effective handle time,
#   based on 45 interaction per agent. This was the average as of 2/8/23 with
#   our heaviest volumes
HANDLE_TIME = int(np.random.normal(579, 5, 1))
# Customer interaction intervals are on a normal dist based on trailing 30 day
#   data
# CUSTOMER_INTERVAL = 33 # how often customer interactions flow in, cases/
#   calls come in every 33 seconds in this case, corresponds to about 872 interactions
CUSTOMER_INTERVAL = 0
HOUR_INTERVAL = 0

# 60 seconds * 60 minutes = 1 hour
SIM_TIME = 60 * 60
CURRENT_HOUR = 0
WAIT_TIMES = []
CUSTOMERS_HANDLED = 0
CURRENT_HOUR = 0
# number of customers waiting when a given execution of the sim starts
CUSTOMERS_WAITING = []
# the wait times for the customers that did not get processed last hour
RESIDUAL_WAIT_TIMES = []
# dictionary for setting the proportion of customer interactions that come
#   in for each hour
WORK_PORTIONS = {
    '0': .002, '1': .002, '2': .004, '3': .007, '4': .026, '5': .053,
    '6': .080, '7': .099, '8': .101, '9': .102, '10': .099, '11': .103,
    '12': .099, '13': .073, '14': .046, '15': .033, '16': .024, '17': .015,
    '18': .010, '19': .007, '20': .004, '21': .003, '22': .003, '23': .003
}

AGENT_PORTIONS = {
    '0': .04, '1': .04, '2': .04, '3': .09, '4': .18, '5': .4,
    '6': .57, '7': .62, '8': .74, '9': .79, '10': .84, '11': .88,
    '12': .84, '13': .75, '14': .57, '15': .4, '16': .31, '17': .22,
    '18': .22, '19': .13, '20': .09, '21': .09, '22': .04, '23': .04
}

class CallCenter:
    """
    Represents a call center or customer service center that takes calls or cases
    """

    def __init__(self, env, num_employees, handle_time):
        self.env = env
        self.staff = simpy.Resource(env, num_employees)
        self.support_time = handle_time

    def support(self, customer):
        # time it takes to handle a call
        random_time = max(1, np.random.normal(self.support_time, 4)) # np.random.normal args (mean, standard dev)
        yield self.env.timeout(random_time)
        print(f"Support finished for {customer} at {self.env.now:.2f}")

def set_agents_working(hour=12):
    """
    Setter for AGENTS_WORKING
    This will be used to determine how many agents are working, each time the
        simulation simulates an hour.

    TODO: make the number of employees returned dynamic, based on the number
            of total agent starts and the time of day.
    """
    global CURRENT_HOUR, AGENT_NO, AGENTS_WORKING, BENCH
    # simplified version
    # AGENTS_WORKING = 18

    # filling the bench
    if BENCH == -1:
        # subtracting 1 to account for the night agent
        BENCH = AGENT_STARTS -1

    # setting up AGENTS_WORKING for off hours
    # if it's earler than 3 am, night agent from previous day will be working
    if CURRENT_HOUR < 3:
        if CURRENT_HOUR == 0:
            add_agent(4, -1)
        else:
            decrement_agent_hours_left()

    # if it's later than 9 pm
    elif CURRENT_HOUR > 21:
        # if it's 10 pm, there is one agent and they have 2 hours left
        if CURRENT_HOUR == 22:
            AGENTS_WORKING = {-1: 2}
        else:
            decrement_agent_hours_left()

    # hours between 3 am and 9 pm inclusive
    else:
        previous_agent_count = get_agents_working_count()
        decrement_agent_hours_left()
        ideal_agents_working = int(AGENT_STARTS * AGENT_PORTIONS[str(CURRENT_HOUR)])
        print("Ideal number of agents working:", ideal_agents_working)
        ideal_agents_added = ideal_agents_working - previous_agent_count

        # case where staff and caseload are ramping up
        if ideal_agents_working > get_agents_working_count():
            # case where there are enough agents on the bench to fill the needed workcload
            if ideal_agents_added <= BENCH:
                for i in range(ideal_agents_added):
                    add_agent()

            # case where there are not enough on the bench for ideal workload
            elif ideal_agents_added > BENCH:
                for i in range(BENCH):
                    add_agent()

    print("On bench:", BENCH)
    # for agent in AGENTS_WORKING:
    #     print("Agent", agent, "has", AGENTS_WORKING[agent], "hours left." )

def add_agent(hours_left = 8, this_agent = 0):
    """
    Adds one agent to the dict of currently working agents.
        if this_agnet variable is left default it means that this is the first
        agent of the day (technically started yesterday), which is why the
        AGENT_NO does not get incremented.
    """
    global AGENT_NO
    global AGENTS_WORKING
    global BENCH
    # default adds an agent to AGENTS_WORKING
    if this_agent == 0:
        print("Added agent", AGENT_NO, "to AGENTS_WORKING, with", hours_left, "hours left.")
        AGENT_NO += 1
        AGENTS_WORKING[AGENT_NO] = hours_left
        BENCH -= 1
    # adds specific agent
    else:
        print("Added agent", this_agent, "to AGENTS_WORKING, with", hours_left, "hours left.")
        AGENTS_WORKING[this_agent] = hours_left

def decrement_agent_hours_left():
    """
    Subtracts an hour from the time each agent has left to work
    if the time they have left is 0, it removes them.
    """
    global AGENTS_WORKING

    for i in tuple(AGENTS_WORKING):
        try:
            if AGENTS_WORKING[i] == 0:
                del AGENTS_WORKING[i]
            else:
                AGENTS_WORKING[i] -=1
        except:
            print("Error: Out of bounds", i, "for AGENTS_WORKING dict.")
            continue

def get_agents_working_count():
    """
    Getter for AGENTS WORKING
    """
    return len(AGENTS_WORKING)

def day_customer_interval(hour=12):
    """
    Provides the interval upon which the work comes in for a given sim
        execution.

    Returns: int representing the number of seconds between customer
        interactions coming in overall for an entire day.
    """
    global INTERACTIONS_MEAN, INTERACTIONS_STDEV, CUSTOMER_INTERVAL

    interactions = int(
        np.random.normal(INTERACTIONS_MEAN, INTERACTIONS_STDEV, 1))
    day_seconds = 60 * 60 * 12
    CUSTOMER_INTERVAL = int(day_seconds / interactions)

    return CUSTOMER_INTERVAL

def hour_customer_interval(hour=12):
    """
    Provides the interval upon which the work comes in for a given hour.
    Assumes:
        INTERACTIONS_TODAY has been set.
        CURRENT_HOUR has been set.

    Returns: int representing the number of seconds between customer
        interactions coming in.
    """
    # global HOUR_INTERVAL
    # # portion of total interactions that are expected to come in this hour
    # portion = WORK_PORTIONS[str(hour)]
    # print("portion of work", portion)
    # day_interactions = int(
    #     np.random.normal(INTERACTIONS_MEAN, INTERACTIONS_STDEV, 1))
    # print("day interactions:", day_interactions)
    # hour_interactions = portion * day_interactions
    # HOUR_INTERVAL = int(60 * 60 * 24 / hour_interactions)

    global HOUR_INTERVAL

    interactions_this_hour = int(INTERACTIONS_TODAY * WORK_PORTIONS[str(CURRENT_HOUR)])
    print("Interactions for hour", CURRENT_HOUR, " are:", interactions_this_hour)
    HOUR_INTERVAL = int(3600 / interactions_this_hour)
    print("Customer interval for this hour is:", HOUR_INTERVAL, "seconds.")
    return HOUR_INTERVAL

def set_interactions_today():
    global INTERACTIONS_TODAY

    INTERACTIONS_TODAY = int(
        np.random.normal(INTERACTIONS_MEAN, INTERACTIONS_STDEV, 1))

def customer(env, name, call_center, wait_time=0):
    """
    Represents a customer interaction

    wait_time: int representing the number of seconds the customer has been
        waiting.
    """
    global CUSTOMERS_HANDLED, CUSTOMERS_WAITING

    # print("Current day: ", get_day(env))

    wait_start = (env.now - wait_time)
    print(f"Customer {name} enters waiting queue at {wait_start:.2f}!")
    # adding a customer to the list waiting
    # 2d array that holds the cust name, their wait time if they are still in
    #    the waiting queue

    CUSTOMERS_WAITING.append([name, SIM_TIME - wait_start])

    with call_center.staff.request() as request:
        yield request

        #dividing the env.now time by 60 so that minutes are shown
        print(f"Customer {name} enterscall at {env.now/60:.2f}")
        yield env.process(call_center.support(name))

        wait_end = env.now
        CUSTOMERS_WAITING.pop(0)
        print(f"Customer {name} left call at {env.now/60:.2f}")

        speed_to_respond = wait_end - wait_start
        WAIT_TIMES.append(speed_to_respond)
        print("Speed to respond: ", speed_to_respond / 60)
        CUSTOMERS_HANDLED +=1

def run_sim(env, num_employees, handle_time, customer_interval, waiting=2):
    """
    Runs the simulation, simulates one hour per execution.
    """
    global CUSTOMERS_WAITING
    # showing the customers waiting
    print("Customers waiting:", len(CUSTOMERS_WAITING))

    call_center = CallCenter(env, num_employees, handle_time)

    # the range is the number of customers that are already waiting
    # for 5 waiting, you would do range(1,6)
    if len(CUSTOMERS_WAITING) == 0:
        for i in range (1, 2):
            env.process(customer(env, num_employees, call_center))
    else:

        for i in range (1, len(CUSTOMERS_WAITING)):
            env.process(customer(env, num_employees, call_center, CUSTOMERS_WAITING[i-1][1]))

    while True:
        yield env.timeout(random.randint(customer_interval - 1, customer_interval + 1))
        try:
            i += 1
        except: i = 1
        this_customer = customer(env, i, call_center)
        env.process(this_customer)
        # if env.now() == SIM_TIME:

def simulate_day():
    """runs the sim for 24 hours, tracking the necessary variables"""
    global CURRENT_HOUR

    for i in range(0, 24):
        CURRENT_HOUR = i
        set_interactions_today()
        set_agents_working()
        my_env = simpy.Environment()
        interval = hour_customer_interval(CURRENT_HOUR)
        agent_count = get_agents_working_count()
        my_env.process(run_sim(my_env, agent_count, HANDLE_TIME, interval))
        my_env.run(until=SIM_TIME)

        # logging and displaying data
        print("Customers handled: " + str(CUSTOMERS_HANDLED))

        my_df = vars_to_df()
        asr = get_asr()
        print(my_df.head())

        log_data(my_df)

def max_output_possible():
    """
    Computes the number of interactions that could have been handled during
        the simulation.
    """
    return AGENT_STARTS * SIM_TIME / HANDLE_TIME

def get_utilization():
    """
    Computes the actual utilization
    """
    # full capacity is the amount of customers that entered, minus the ones that entered during
    #   one unit of handle time.

    return CUSTOMERS_HANDLED / max_output_possible()

def get_asr():
    """
    Computes Average Speed to Respond
    Must be called after the sim has completed.
    Return: float
    """

    return sum(WAIT_TIMES) / len(WAIT_TIMES) / 60

def vars_to_df():
    """
    Creates dataframe with the inputs and outputs of each sim run
        Pass in the number of customers (interactions) handled
    """
    columns = [
        "Timestamp", "Current Hour", "Agents Working", "Avg Handle Time",
        "ASR", "Customer Interval", "Interactions Handled",
        "Utilization"
    ]

    df = pd.DataFrame(columns=columns, index=[0])

    df["Timestamp"] = datetime.datetime.now().strftime(
        'X%m/X%d/%Y X%H:X%M:X%S').replace('X0','X').replace('X','')
    df["Current Hour"] = round(CURRENT_HOUR)
    df["Agents Working"] = get_agents_working_count()
    df["Avg Handle Time"] = round(HANDLE_TIME / 60, 2)
    df["ASR"] = round(get_asr(), 2)
    df["Interactions Handled"] = CUSTOMERS_HANDLED
    df["Customer Interval"] = round(HOUR_INTERVAL / 60, 2)
    df["Utilization"] = round(get_utilization(), 2)

    return df

def log_data(df):
    """
    logs the inputs and outputs from the simulation in the "log.csv" file, for later analysis
    """
    df.to_csv('log.csv', mode='a', index=False, header=False)

def main():
    # running the sim
    print("Starting Call Center Simulation")
    simulate_day()

if __name__ == "__main__":
    main()