import cv2
import matplotlib.pyplot as plt
import dlib
from imutils import face_utils
from joblib import load, dump

#https://stackoverflow.com/questions/39308030/how-do-i-increase-the-contrast-of-an-image-in-python-opencv
def contrastLab(img_in_bgr):
    img_lab= cv2.cvtColor(img_in_bgr, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(img_lab)
    clahe_stuff = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
    cl = clahe_stuff.apply(l)

    limg = cv2.merge((cl,a,b))

    contrasted_lab_img = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)

    return contrasted_lab_img
# kod sa prvog izazova
def enhance_gray(img_bgr):
    img_contrast = contrastLab(img_bgr)
    img = increase_brightness(img_contrast)

    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    img_bgr = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
    img_gs = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(img_gs, (5, 5), 0)
    img_gs = 0-blur
    return img_gs

def enhance_gray_gender(img_bgr):
    img_contrast = contrastLab(img_bgr)
    img = increase_brightness(img_contrast)

    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    img_bgr = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
    img_gs = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
    img_gs = 0-img_gs
    return img_gs

# https://stackoverflow.com/questions/32609098/how-to-fast-change-image-brightness-with-python-opencv
def increase_brightness(img, value=45):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(hsv)

    lim = 255 - value
    v[v > lim] = 255
    v[v <= lim] += value

    final_hsv = cv2.merge((h, s, v))
    img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
    return img

# kod sa vezbi
def detect_face(img_path):
    # inicijalizaclija dlib detektora (HOG)
    detector = dlib.get_frontal_face_detector()
    # ucitavanje pretreniranog modela za prepoznavanje karakteristicnih tacaka
    predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

    img_bgr = cv2.imread(img_path)
    image = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
    image2 = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

    # detekcija svih lica na grayscale slici

    rects = detector(image2, 1)

    # iteriramo kroz sve detekcije korak 1.
    for (i, rect) in enumerate(rects):
        # determine the facial landmarks for the face region, then
        # convert the facial landmark (x, y)-coordinates to a NumPy
        # array
        # odredjivanje kljucnih tacaka - korak 2
        shape = predictor(image2, rect)
        # shape predstavlja 68 koordinata

        shape = face_utils.shape_to_np(shape)  # konverzija u NumPy niz
        # print("Dimenzije prediktor matrice: {0}".format(shape.shape))  # 68 tacaka (x,y)
        # print("Prva 3 elementa matrice")
        # print(shape[:3])

        j = 0
        for s in shape[:68]:
            if s[0] < 0:
                shape[j] = [0, s[1]]

            j = j + 1

        # konvertovanje pravougaonika u bounding box koorinate
        (x, y, w, h) = face_utils.rect_to_bb(rect)
        # crtanje pravougaonika oko detektovanog lica
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)

        rectangle_coords = [x, y, x + w, y + h]

        if x < 0:
            rectangle_coords[0] = 0
        if y < 0:
            rectangle_coords[1] = 0

        if x + w > image.shape[1]:
            rectangle_coords[2] = image.shape[1]

        if y + h > image.shape[0]:
            rectangle_coords[3] = image.shape[0]

        # crtanje kljucnih tacaka
        for (x, y) in shape:
            cv2.circle(image, (x, y), 1, (0, 0, 255), -1)

        for (x, y) in shape:
            if coords_dont_exist_in_rect(rectangle_coords, x, y):
                rectangle_coords = expand_or_shrink_rectangle(rectangle_coords, x, y)

        cv2.rectangle(image2, (rectangle_coords[0], rectangle_coords[1]), (rectangle_coords[2], rectangle_coords[3]),
                      (0, 255, 0), 2)

        face = image2[rectangle_coords[1] + 2:rectangle_coords[3] - 2, rectangle_coords[0] + 2: rectangle_coords[2] - 2]

        return face

def coords_dont_exist_in_rect(rectangle_coords, x, y):
    if x >= rectangle_coords[0] and x <= rectangle_coords[2]:
        if y >= rectangle_coords[1] and y <= rectangle_coords[3]:
            return False
    return True

def expand_or_shrink_rectangle(rectangle_coords, x, y):
    if x < rectangle_coords[0]:
        rectangle_coords[0] = x

    if x > rectangle_coords[2]:
        rectangle_coords[2] = x

    if y < rectangle_coords[1]:
        rectangle_coords[1] = y

    if y > rectangle_coords[3]:
        rectangle_coords[3] = y

    return rectangle_coords

def resize_img(img):
    return cv2.resize(img, (200, 200), interpolation=cv2.INTER_NEAREST)


import pandas as pd
import numpy as np
import argparse
from pathlib import Path
from components.dataset_generation.utilities import utils, deltaPes_utils
from pqdm.processes import pqdm
from typing import Union
from termcolor import colored

# define some complex types for typehints
FilterFileInfo = dict[[str, bool], [str, str], dict[str, str], [str, str]]
DeltaPesList = list[list[float, str, str]]

class TripletGenerator:
    """
    This class generates triplets from every patient-day in the dataset

    Attributes:
        _batch_files_directory (Path): directory where batch-processed patient-day files are stored
        _filter_file_info (FilterFileInfo): dict that contains information about the filter file (if it is included), including
                                what columns to filter over
        _triplet_export_directory (Path): path to directory where triplets will be stored
        _statics_directory (Path): path to directory where statics files will be stored

    """

    def __init__(self, batch_files_directory: Union[str, Path], filter_file_info: FilterFileInfo = None) -> None:
        """
        Sets initial class attributes

        Args:
            batch_files_directory (Union[str, Path]): Path object that stores path to the batch files directory, where outputs from
                                   the batch processor are stored
            filter_file_info (FilterFileInfo): dict that contains information about the filter file,
                                               including which columns to filter over

        Returns:
            None:
        """

        # save attributes
        self._batch_files_directory = batch_files_directory
        self._filter_file_info = filter_file_info
        # we will modify these later
        self._statics_directory = None
        self._triplet_export_directory = None

    @staticmethod
    def _setup_directories(batch_files_directory: Union[str, Path]) -> tuple[Path, Path, Path]:
        """
        Sets up triplet_export_directory and statics_directory to store outputs from batch_files_directory path

        Args:
            batch_files_directory (Union[str, Path]): Path to the directory where batch files are located

        Returns:
            batch_files_directory (Path): Path to the directory where batch files are located
            triplet_export_directory (Path): Path to directory where triplets will be saved
            statics_directory (Path): Path to directory where statics files will be saved

        """
        # define paths
        batch_files_directory = Path(batch_files_directory)

        # put triplets directory in directory where batch files directory is
        batch_files_parent_directory = batch_files_directory.parents[0]
        triplet_export_directory = Path(batch_files_parent_directory, 'triplets')

        # create directory for statics files
        statics_directory = Path(batch_files_parent_directory, 'statics')

        # create export directory
        triplet_export_directory.mkdir(parents=True, exist_ok=True)
        statics_directory.mkdir(parents=True, exist_ok=True)

        return batch_files_directory.resolve(), triplet_export_directory.resolve(), statics_directory.resolve()

    def _get_patient_day(self, subdir_name: str) -> tuple[str, str, Path, Path, pd.DataFrame]:
        """
        Gets the patient-day from a subdirectory name

        Args:
            subdir_name (str): string of subdirectory name (patient-day subdirectory)

        Returns:
            patient_id (str): Patient ID of the triplet
            day_id (str): Day ID of the riplet
            patient_day_dir (Path): Directory where triplets for this patient-day is located
            patient_day_output_dir (Path): Directory where spectral triplets for this patient-day will be saved
            patient_day (pd.DataFrame): DataFrame with statics information about this patient-day
        """

        # get the patient and day id
        patient_id = utils.get_patient_id(subdir_name)
        day_id = utils.get_day_id(subdir_name)

        # get paths for patient day folder
        patient_day_dir = Path(self._batch_files_directory, subdir_name)
        patient_day_output_dir = Path(self._triplet_export_directory, subdir_name)

        patient_day_output_dir.mkdir(parents=True, exist_ok=True)

        # get all csv files in patient day folder
        csv_files = [x.name for x in patient_day_dir.iterdir()]

        # Find which file is the TriggersAndArtifacts file we care about
        ta_file_index = np.where([utils.TA_CSV_SUFFIX in csv for csv in csv_files])[0][0]

        patient_day = pd.read_csv(Path(patient_day_dir, csv_files[ta_file_index]), low_memory=False)

        return patient_id, day_id, patient_day_dir, patient_day_output_dir, patient_day

    @staticmethod
    def _create_dyssynchrony_mask(patient_day: pd.DataFrame) -> pd.DataFrame:
        """
        Creates a mask to only select rows to search for dyssynchronies
        i.e. Only look for dyssynchronies between inspiration and expiration

        Args:
            patient_day (pd.Dataframe): DataFrame with waveform information, inspiration and expiration triggers,
                                        and dyssynchrony information about the patient-day

        Returns:
           patient_day (pd.DataFrame): Patient-day dataframe with added dyssynchrony_mask column

        """

        # Create a mask to select only rows in which I should search for dyssynchronies
        # By creating column where rows between inspiration trigger (1) and expiration trigger (-1)
        # are equal to 1, which will be used for filtering
        patient_day['dyssynchrony_mask'] = patient_day[utils.DELINEATION_COLUMN].cumsum()

        # If the first trigger in the patient day is an expiration (-1) then the cumsum will
        # need 1 added to the entire dyssynchrony_mask column
        if patient_day['dyssynchrony_mask'].min() == -1:
            patient_day['dyssynchrony_mask'] += 1

        return patient_day

    @staticmethod
    def _create_breath_ids(patient_day: pd.DataFrame) -> pd.DataFrame:
        """
        Creates breath id's by looking at where expiration triggers occur, every time one occurs increase breath id by 1

        Args:
            patient_day (pd.DataFrame): DataFrame with waveform information, inspiration and expiration triggers,
                                        and dyssynchrony information about the patient-day

        Returns:
            patient_day (pd.DataFrame): Patient-day dataframe with breath_ids included

        """

        # Create breath ids by replacing expiration triggers
        patient_day['breath_id'] = patient_day[utils.DELINEATION_COLUMN].replace(-1, 0)
        patient_day['breath_id'] = patient_day['breath_id'].cumsum()

        return patient_day

    @staticmethod
    def _build_statics_file(patient_day: pd.DataFrame, patient_id: str, day_id: str, subdir_name: str) -> pd.DataFrame:
        """
        Builds a statics DataFrame from a patient_day dataframe and information about that patient-day

        Args:
            patient_day (pd.DataFrame): DataFrame with waveform information, inspiration and expiration triggers,
                                        and dyssynchrony information about the patient-day
            patient_id (str): string of the patient day id
            day_id (str): string of the day id
            subdir_name (str): string of subdirectory name (patient-day subdirectory)

        Returns:
            patient_day_statics (pd.DataFrame): Built statics file with summary information about the patient-day.
                                                Has columns with info about dyssynchrony counts in this patient-day

        """

        # Build the statics file, creating ID columns for the patient and day based on the filename
        patient_day_statics = utils.create_breath_statics(patient_day)

        patient_day_statics['patient_id'] = patient_id
        patient_day_statics['day_id'] = day_id
        patient_day_statics['original_subdirectory'] = subdir_name

        return patient_day_statics

    @staticmethod
    def _build_triplet(patient_day: pd.DataFrame, patient_day_output_dir: Path, breath_id: int, one_hot_dyssynchronies: pd.DataFrame) -> tuple[pd.DataFrame, Path]:
        """
        Builds a triplet DataFrame from a patient-day by taking the breaths surrounding the current breath_id,
         including the current breath

        Args:
            patient_day (pd.DataFrame): DataFrame with waveform information, inspiration and expiration triggers,
                                        and dyssynchrony information about the patient-day
            patient_day_output_dir (Path): Path to output directory for this patient-day triplet
            breath_id (int): string of breath id for this triplet
            one_hot_dyssynchronies (pd.DataFrame): Dataframe with dyssynchronies one-hot-encoded in columns

        Returns:
            triplet (pd.DataFrame): DataFrame of Triplet
            triplet_csv_filename (Path): Path to where triplet csv should be saved
        """

        # Take the breath before, current breath and breath after as a triplet
        triplet = patient_day[patient_day['breath_id'].isin([breath_id - 1,
                                                             breath_id,
                                                             breath_id + 1])].reset_index()

        # Create a triplet ID, which will just be the ID of the middle breath
        triplet['triplet_id'] = breath_id

        # Merge so that the one hot columns span the entire triplet
        triplet = triplet.merge(one_hot_dyssynchronies[one_hot_dyssynchronies.index == breath_id],
                                left_on='triplet_id',
                                right_index=True,
                                how='left')

        triplet = triplet[utils.KEEP_COLUMNS]

        triplet_csv_filename = Path(patient_day_output_dir,
                                    'breath_{}.csv'.format(breath_id))

        return triplet, triplet_csv_filename

    @staticmethod
    def _calculate_deltaPes(triplet: pd.DataFrame, breath_id: int, subdir: str,
                            deltaPes_list: DeltaPesList) -> tuple[pd.DataFrame, DeltaPesList]:
        """
        Calculates deltaPes of the central breath of the triplet and saves that information for later

        Args:
            triplet (pd.DataFrame): DataFrame with information about the triplet
            breath_id (int): string of breath id of this triplet
            subdir (str): string of subdirectory where triplet should be stored
            deltaPes_list (DeltaPesList): list of breaths that have deltaPes recorded, and information about those breaths

        Returns:
            triplet (pd.DataFrame): DataFrame of triplet with deltaPes information added
            deltaPes_list (DeltaPesList): list of breaths with deltaPes calculated
        """

        # # try to calculate deltaPes for the central breath in this triplet
        try:
            deltaPes = deltaPes_utils.calculate_deltaPes(triplet)
        # if there is a problem calculating deltaPES, save deltaPES as NaN
        except Exception as e:
            print(e)
            deltaPes = np.nan

        deltaPes_list.append([deltaPes, subdir, breath_id])

        # get binary truth for deltaPES, 0 if <20, 1 if >= 20
        deltaPes_binary = int(deltaPes >= 20)

        # save deltaPes information in the triplet dataframe
        triplet['deltaPes'] = deltaPes
        triplet['deltaPes_binary'] = deltaPes_binary
        triplet['deltaPes_clipped'] = triplet['deltaPes'].clip(upper=40)

        return triplet, deltaPes_list

    @staticmethod
    def _add_deltaPes_to_statics(all_patient_day_statics: pd.DataFrame, deltaPes_list: DeltaPesList) -> pd.DataFrame:
        """
        Adds deltaPes information to the statics file

        Args:
            all_patient_day_statics (pd.DataFrame): DataFrame with summary information (statics) about all patient-days
                                                    in the dataset
            deltaPes_list (DeltaPesList): list of all triplets in the dataset that have deltaPes calculated

        Returns:
            all_patient_day_statics (pd.DataFrame): Dataframe of all_patient_days_statics with deltaPes information added

        """

        merge_columns = ['original_subdirectory', 'breath_id']

        # Save this to restore the index after the merge
        statics_index = all_patient_day_statics.index.names

        # merge deltaPes into statics
        deltaPes_df = pd.DataFrame(deltaPes_list, columns=['deltaPes'] + merge_columns)

        all_patient_day_statics = all_patient_day_statics.reset_index().merge(deltaPes_df,
                                                                              how='left',
                                                                              on=merge_columns).set_index(statics_index)

        # threshold deltaPes, values above 40 will be clipped to 40
        all_patient_day_statics['deltaPes_clipped'] = all_patient_day_statics['deltaPes'].clip(upper=40)

        # binarize deltaPES in the statics file
        # 0 if deltaPes is <20, 1 if deltaPes >=20
        all_patient_day_statics['deltaPes_binary'] = (all_patient_day_statics['deltaPes'] >= 20).astype(int)

        return all_patient_day_statics

    def _create_final_statics(self, patient_day_statics_list: list[pd.DataFrame], deltaPes_list: DeltaPesList) -> None:
        """
        Concantenates all the patient_day statics DataFrames into one large dataframe for all patient-days in the dataset

        Args:
            patient_day_statics_list (list[pd.DataFrame]): list of all triplets that have a statics file made for them
            deltaPes_list (DeltaPesList): list of all triplets in the dataset that have deltaPes calculated

        Returns:
            None:
        """

        all_patient_day_statics = pd.concat(patient_day_statics_list)
        all_patient_day_statics = all_patient_day_statics.set_index(['patient_id', 'day_id', 'breath_id'])

        # Now we'll produce the h0_with_adjacent_h1 column and merge
        all_patient_day_statics['h0_with_adjacent_h1'] = utils.find_h0s_with_adjacent_h1(
            statics=all_patient_day_statics,
            truth_col='Double Trigger',
            num_breaths=utils.NUM_BREATHS,
            patient_id_col='patient_id',
            day_id_col='day_id')

        # For multiclass classification of the underlying double trigger dyssynchronies,
        # we need an indicator of where there is a double trigger without an underlying dyssynchrony
        # to filter these out in split_train_val_test.py.

        # This next line searches for any column that starts with "Double Trigger" (including "Double Trigger" itself)
        # and if only one column (which would have to be "Double Trigger", since the others are derived from it)
        # is present (== 1) then we know there is a Double Trigger with no cooccurring dyssynchrony
        all_patient_day_statics['no_cooccurrence_double_trigger'] = (
                all_patient_day_statics.filter(regex='^Double Trigger *', axis=1).sum(axis=1) == 1).astype(int)

        # put deltaPes information into statics file
        all_patient_day_statics = TripletGenerator._add_deltaPes_to_statics(all_patient_day_statics, deltaPes_list)

        # save out statics files to triplets export directory
        all_patient_day_statics.to_hdf(Path(self._triplet_export_directory, 'statics.hdf'), key='statics')
        all_patient_day_statics.to_csv(Path(self._triplet_export_directory, 'statics.csv'))

        # save out statics files to statics directory
        all_patient_day_statics.to_hdf(Path(self._statics_directory, 'statics.hdf'), key='statics')
        all_patient_day_statics.to_csv(Path(self._statics_directory, 'statics.csv'))

    def _loop_through_triplets(self, subdir_name: str) -> tuple[pd.DataFrame, DeltaPesList]:
        """
        Loops through all triplets in a patient-day, saves them, and then returns information
        about deltaPes and statics for inclusion in the final statics

        Args:
            subdir_name (str): string of subdirectory where triplet should be stored

        Returns:
            patient_day_statics (pd.DataFrame): DataFrame with statics information about this patient-day
            deltaPes_list (DeltaPesList): list of all triplets in the dataset that have deltaPes calculated
        """
        # a temporary list of deltaPes calculations per breath
        deltaPes_list = []

        # get the patient day and set up patient_day directories
        patient_id, day_id, patient_day_dir, patient_day_output_dir, patient_day = self._get_patient_day(subdir_name)

        # create dyssynchrony mask
        patient_day = TripletGenerator._create_dyssynchrony_mask(patient_day)

        # create breath ids
        patient_day = TripletGenerator._create_breath_ids(patient_day)

        # Convert the time column to datetime format (be explicit with the format or it's slooowwwww)
        patient_day['TimeRel'] = pd.to_datetime(patient_day['TimeRel'], format='%Y-%m-%d %H:%M:%S.%f')

        # setup statics file
        patient_day_statics = TripletGenerator._build_statics_file(
            patient_day,
            patient_id,
            day_id,
            subdir_name
        )

        # then get blacklist of breaths that are too long/short so we don't do anything with them
        breath_length_blacklist = utils.get_breath_length_blacklist(patient_day)

        breath_id_blacklist = np.unique(breath_length_blacklist)

        # Get dyssynchrony columns, so that the one hot columns span the entire triplet,
        # we will merge within the next loop
        one_hot_dyssynchronies = utils.one_hot_dyssynchronies(patient_day)

        # Loop through each breath
        for breath_id in range(1, patient_day['breath_id'].max()):

            if breath_id not in breath_id_blacklist:
                # create triplet
                triplet, triplet_csv_filename = TripletGenerator._build_triplet(patient_day,
                                                                    patient_day_output_dir,
                                                                    breath_id,
                                                                    one_hot_dyssynchronies)

                # calculate deltaPes
                triplet, deltaPes_list = TripletGenerator._calculate_deltaPes(triplet,
                                                                  breath_id,
                                                                  subdir_name,
                                                                  deltaPes_list
                                                                  )
                # save triplet to csv
                triplet.to_csv(triplet_csv_filename, index=False)

        # return first element in these lists, with multithreading there will only be one item per list
        return patient_day_statics, deltaPes_list

    def generate_triplets(self, multiprocessing: bool = False) -> tuple[Path, Path]:
        """
        Creates triplets in parallel (if flag is true), then saves them out and creates a statics file for them.

        Args:
            multiprocessing (bool): Boolean flag whether to multiprocess or not

        Returns:
            self.triplet_export_directory (Path): returns path to the directory where we saved all our triplets
            triplet_export_directory (Path): Path to the directory where we saved all our triplets
            statics_csv_output (Path): Path to the directory where we saved our statics
        """

        # setup directories
        self._batch_files_directory, \
            self._triplet_export_directory, \
            self._statics_directory = TripletGenerator._setup_directories(self._batch_files_directory)

        # Grab the triplet folders from their directories
        p = Path(self._batch_files_directory)
        subdir_names = [subdir.name for subdir in p.iterdir() if subdir.name not in utils.ERROR_DIRS]

        print(
            colored(
                f'Creating triplets from {len(subdir_names)} subdirectories of patient-days...',
                'cyan'
                    ))

        # find the number of workers to use
        n_workers, available_cores = utils.calc_num_workers(len(subdir_names), multiprocessing)
        # print(f'{n_workers} cores of {available_cores} available cores assigned to generating Triplets')

        # multiprocessing requires a list to loop over, a function object, and number of workers
        # for each subdir name in subdir names, get the results from each function call and append them to a list called results
        results = pqdm(subdir_names, self._loop_through_triplets, n_jobs=n_workers, desc='Patient-Days of Triplets Generated')

        # put together results
        patient_day_statics_list = [result[0] for result in results]
        # nested list comprehension (does .extend instead of .append essentially)
        # [variable_to_extend_with for OUTER LOOP (item in list) for INNER LOOP (variable_to_extend_with in list)]
        deltaPes_list = [deltaPes_result for result in results for deltaPes_result in result[1]]

        # make sure patient_day_statics_list has something, aka. there is a patient day with triplets. if not interrupt and raise exception
        assert len(patient_day_statics_list) > 0, f'There must be patients in the dataset with triplets. None found, exiting'

        # after looping through all triplet, create final statics file
        self._create_final_statics(patient_day_statics_list, deltaPes_list)

        triplet_export_directory = self._triplet_export_directory
        statics_csv_output = self._statics_directory

        return triplet_export_directory, statics_csv_output

# if running this file directly, only do triplet generation
if __name__ == "__main__":

    # Command Line Arguments
    p = argparse.ArgumentParser()
    p.add_argument('--input_directory', type=str, default=None, help='Directory with batch files')
    args = vars(p.parse_args())

    # define args
    input_directory = args['input_directory']

    # instantiate triplet generator class
    triplet_generator = TripletGenerator(input_directory)

    # run triplet generator
    export_directory, statics_csv_output = triplet_generator.generate_triplets()

    print(f'Triplets generated at {export_directory}')
    print(f"Statics file generated at {statics_csv_output}")


import sys
from collections import deque

input = lambda: sys.stdin.readline().rstrip()

m, n, h = map(int, input().split())
boxes = []
queue = deque()
remain_tomato = 0
max_day = 0

for k in range(h):
    box = []

    for i in range(n):
        line = list(map(int, input().split()))
        box.append(line)

        for j in range(m):
            # -2: 빈칸, -1: 안익은 토마토, 0: 0일차에 익은 토마토
            box[i][j] -= 1

            # 0일차에 안익은 토마토 갯수 세고 익은 토마토 큐에 넣기
            if box[i][j] == -1:
                remain_tomato += 1
            elif box[i][j] == 0:
                queue.append((k, i, j))

    boxes.append(box)

tomato_offset = [
    [0, -1, 0],  # 위
    [0, 1, 0],  # 아래
    [0, 0, -1],  # 왼쪽
    [0, 0, 1],  # 오른쪽
    [-1, 0, 0],  # 앞
    [1, 0, 0]  # 뒤
]

while queue:
    k, i, j = queue.popleft()
    cell = boxes[k][i][j]

    if cell == -2:
        continue

    for dk, di, dj in tomato_offset:
        nk = k + dk
        ni = i + di
        nj = j + dj

        if 0 <= nk < h \
                and 0 <= ni < n \
                and 0 <= nj < m \
                and boxes[nk][ni][nj] == -1:
            # 토마토가 하나 더 익었다!
            queue.append((nk, ni, nj))
            boxes[nk][ni][nj] = cell + 1
            remain_tomato -= 1

            # 토마토가 익는데 최대 며칠 걸리는지 업데이트
            if boxes[nk][ni][nj] > max_day:
                max_day = boxes[nk][ni][nj]

if remain_tomato == 0:
    # 다 익었으면 며칠 걸렸는지
    print(max_day)
else:
    # 안익은 토마토가 있는 경우
    print(-1)


import inspect
from typing import Callable

from torch.utils.data import DataLoader, SubsetRandomSampler

def to_markdown(string: str) -> str:
    """
    Formats linebreaks and indentation from python format to Markdown format.
    Replaces " " with "  ".
    Replaces "\n" with "
".
    """
    if string is not None:
        formatted_string = string.replace(" ", "  ").replace("\n", "
")
    else:
        formatted_string = "None"
    return formatted_string

def from_markdown(string: str) -> str:
    """
    Formats linebreaks and indentation from Markdown format to python format.
    Replaces "  "  with " ".
    Replaces "
" with "\n".
    """
    if string is not None:
        formatted_string = string.replace("  ", " ").replace("
", "\n")
    else:
        formatted_string = "None"
    return formatted_string

def get_state_as_string(obj):
    """
    Returns a string representation of the given object with its state dict.
    """
    # Check if object has a state dict, if so parse it
    state_dict = getattr(obj, "state_dict", None)
    if callable(state_dict):
        # Start with the object name
        text = f"{str(obj.__class__.__name__)}("
        # Add every key, value pair
        for key, value in obj.state_dict().items():
            # Exclude private parameters of the state dict
            if not key.startswith("_"):
                text += f"\n  {key}: {value},"
        # Close the text
        text += "\n)"
    else:
        # Only use the object name
        text = f"{str(obj.__class__.__name__)}()"
    return text

def get_method_as_string(method: Callable):
    """
    Returns a string representation of the given method object.
    """
    return inspect.getsource(method)

def get_trainer_as_string(trainer):
    """
    Returns the basic parameters of this trainer as string. Used for cleaner logging to tensorboard.
    """
    return f"{trainer.__class__.__name__}(\n" \
           f"  num_epochs: {trainer.num_epochs}\n" \
           f"  seed: {trainer.seed}\n" \
           f"  device: {trainer.device}\n" \
           f")"

def get_data_loader_as_string(data_loader: DataLoader):
    """

    Parameters
    ----------
    data_loader

    Returns
    -------

    """
    formatted_data_set = str(data_loader.dataset).replace("\n", "\n  ")

    result = f"{data_loader.__class__.__name__}(\n" \
             f"  data_set: {formatted_data_set}\n" \
             f"  batch_size: {data_loader.batch_size}\n"

    if isinstance(data_loader.sampler, SubsetRandomSampler):
        result += f"  indices: {data_loader.sampler.indices}\n"
        result += f"  generator: {data_loader.sampler.generator}\n"
    elif isinstance(data_loader.sampler, list):
        result += f"  indices: {data_loader.sampler}\n"

    result += ")"

    return result


import cv2 as cv
import os
from tqdm.auto import tqdm

from geometry import Geometry

def main():
    image = Geometry()
    news_num = 1600
    with open(r"C:\Users\User\Downloads\train_sq.csv", 'w') as solution:
        path_fold = r'C:\Users\User\Downloads\train_data'
        for i in tqdm(range(news_num)):
            path = os.path.join(path_fold, str(3001 + i) + '.png')
            img, label = image.add_geom()
            cv.imwrite(path, img)
            solution.write(f'train_data/{3001 + i}.png,{label}' + '\n')

if __name__ == "__main__":
    main()


from mpl_toolkits.mplot3d import Axes3D  # ajuda do Welton Vaz: https://github.com/weltonvaz

import matplotlib.pyplot as plt
import numpy as np

n_angles = 72
n_radii = 4

radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint = True)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)

x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())

z = np.sin(-x*(y**2))+np.cos((x**2)*-y)

fig = plt.figure()
ax = fig.gca(projection='3d')

path = '/home/renansantos/Área de Trabalho/GA Testes/'
# file = path + 'MOEAD_R2_2.txt'
file = path + 'moead_3d.txt'

def get_data_from_txt(file,index):
	file = open(file)
	f = []
	for line in file:
		f.append(float(line.replace('[','').replace(']','').replace('\n','').replace(',','').split()[index]))
	return f

f1 = get_data_from_txt(file, 0)
f2 = get_data_from_txt(file, 1)
f3 = get_data_from_txt(file, 2)
f = []

for number in f3:
	f.append(number/6000)

f3 = f
print(f1)
print(f2)
print(f3)

# plt.scatter(f2, f1,color='green',marker='o',label='MOEAD')

# ax.plot_trisurf(x, y, z, cmap='Oranges', linewidth=0.1)

# ax.scatter(f1, f2, f3, c=c, marker=m) #,color='green',marker='o',label='MOEAD'
ax.scatter(f1, f2, f3, color='green',marker='o',label='MOEAD')
ax.set_xlabel('Objective Function 1')
ax.set_ylabel('Objective Function 2')
ax.set_zlabel('Objective Function 3')

plt.legend(loc='upper right')

plt.show()


import argparse
import logging

import torch
from torch import nn

from paoding.argument_parser import ArgumentParser
from paoding.nn_utils import masked_max, masked_mean, masked_softmax, weighted_sum

logger = logging.getLogger(__name__)

POOLING_MODES = {"avg", "max", "first", "last", "attn", "attn_k", "attn_v", "attn_kv"}

class Pooler(nn.Module):
    def __init__(self, hparams: argparse.Namespace, hidden_dim: int = None):
        assert hparams.pooling_mode in POOLING_MODES

        super().__init__()
        self.hparams = hparams
        if self.hparams.pooling_mode in {"attn", "attn_k", "attn_v", "attn_kv"}:
            assert hidden_dim is not None
            self.q = nn.Linear(hidden_dim, 1, bias=False)
            if self.hparams.pooling_mode in {"attn_k", "attn_kv"}:
                self.K = nn.Linear(hidden_dim, hidden_dim)
            if self.hparams.pooling_mode in {"attn_v", "attn_kv"}:
                self.V = nn.Linear(hidden_dim, hidden_dim)

    def forward(self, tensor: torch.Tensor, mask: torch.Tensor = None):
        """
        Input:
            tensor: (bsz, seq_len, hidden_dim)
            mask: (bsz, seq_len)

        output:
            pooled_tensor: (bsz, hidden_dim)
        """
        if self.hparams.pooling_mode == "avg":
            if mask is None:
                return tensor.mean(1)
            else:
                return masked_mean(tensor, mask.unsqueeze(-1), 1)
        elif self.hparams.pooling_mode == "max":
            if mask is None:
                return tensor.max(1)
            else:
                return masked_max(tensor, mask.unsqueeze(-1), 1)
        elif self.hparams.pooling_mode == "first":
            assert mask[:, 0].all()
            return tensor[:, 0, :]
        elif self.hparams.pooling_mode == "last":
            if mask is None:
                return tensor[:, -1, :]
            if mask is not None:
                last_pos = mask.sum(-1) - 1  # (bsz,)
                return tensor[torch.arange(tensor.shape[0]), last_pos]
        elif self.hparams.pooling_mode in {"attn", "attn_k", "attn_v", "attn_kv"}:
            k = v = tensor  # (bsz, seq_len, hidden_dim)
            if self.hparams.pooling_mode in {"attn_k", "attn_kv"}:
                k = self.K(k)
            if self.hparams.pooling_mode in {"attn_v", "attn_kv"}:
                v = self.V(v)
            scores = self.q(k).squeeze(-1)  # (bsz, seq_len)
            # masked_softmax handles None mask automatically, and makes sure that paddings are 0
            weights = masked_softmax(scores, mask, dim=-1)  # (bsz, seq_len)
            return weighted_sum(v, weights)
        else:
            raise KeyError("Unsupported pooling mode")

    @staticmethod
    def add_args(parser: ArgumentParser):
        parser.add_argument(
            "--pooling_mode",
            default=None,
            type=str,
            required=True,
            choices=POOLING_MODES,
        )

        return parser


import torch
import torch.nn as nn
import torch.nn.functional as f
from functools import partial
from utils.registry import register
from . import Linear

registry = {}
register = partial(register, registry=registry)

@register('simple')
class Fusion(nn.Module):
    def __init__(self, args, input_size):
        super().__init__()
        self.fusion = Linear(input_size * 2, args.hidden_size, activations=True)

    def forward(self, x, align):
        return self.fusion(torch.cat([x, align], dim=-1))

@register('full')
class FullFusion(nn.Module):
    def __init__(self, args, input_size):
        super().__init__()
        self.dropout = args.dropout
        self.fusion1 = Linear(input_size * 2, args.hidden_size, activations=True)
        self.fusion2 = Linear(input_size * 2, args.hidden_size, activations=True)
        self.fusion3 = Linear(input_size * 2, args.hidden_size, activations=True)
        self.fusion = Linear(args.hidden_size * 3, args.hidden_size, activations=True)

    def forward(self, x, align):
        x1 = self.fusion1(torch.cat([x, align], dim=-1))
        x2 = self.fusion2(torch.cat([x, x - align], dim=-1))
        x3 = self.fusion3(torch.cat([x, x * align], dim=-1))
        x = torch.cat([x1, x2, x3], dim=-1)
        x = f.dropout(x, self.dropout, self.training)
        return self.fusion(x)


from bgem.polygons import polygons as poly
import numpy as np
import scipy.linalg as la

class FracPlane:
    """
    Fracture plane decomposition and references from the segments of decomposition to the fractures.

    """

    def __init__(self, fracture):
        """
        :param vertices: vertices of the fracture as numpy array 3x4
        :param x_loc: normalized vector of local x-axis as numpy array 3x1
        :param y_loc: normalized vector of local y-axis as numpy array 3x1
        :param area: as area of the surface
        """

        self.fracture = fracture
        self.surface = fracture.rx * fracture.ry
        self.pd = poly.PolygonDecomposition()

        #sg_a, = self.pd.add_line(np.array([0,0]), np.array([1,0]))
        #sg_b, = self.pd.add_line(np.array([1,0]), np.array([1,1]))
        #sg_c, = self.pd.add_line(np.array([1,1]), np.array([0,1]))
        #sg_d, = self.pd.add_line(np.array([0,1]), np.array([0,0]))
#       # in_wire = sg_a.wire[left_side]

    #@staticmethod
    def _add_intersection_point(self, isp):
        self.isecs[self.nisecs].append(isp)

    def _initialize_new_intersection(self):
        self.nisecs += 1
        self.isecs.append([])

    def _check_duplicity(self,coor,tol,duplicity_with):

        if duplicity_with != -1:
            for i in range(0, 4):
                vertex_coor = self.vertices[i, :].transpose()
                rel_tol = 2 * la.norm(vertex_coor - coor) / la.norm(vertex_coor + coor)
                if rel_tol >= tol:
                    duplicity_with = self.vert_id[i]
                    break

        return duplicity_with


import numpy as np
import h5py
import pickle
import yaml
import os
from collision_predictor_mpc import COLPREDMPC_DATA_DIR, COLPREDMPC_WEIGHT_DIR, COLPREDMPC_LOG_DIR
from collision_predictor_mpc.colpred.depth_state_check import Colpred
from collision_predictor_mpc.colpred.collision_checker import ColChecker
import torch
import time
import matplotlib.pyplot as plt
import sklearn.metrics
from torchinfo import summary
import argparse

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Simulate system with nmpc.')
    parser.add_argument('--cpu', dest='cpu', action='store_true', default=False, help='Force cpu.')
    args = parser.parse_args()

    dataset = 'half_clutter'  # cluttered or half_clutter or walls
    safe_ball_size = 0.1

    with open(os.path.join(COLPREDMPC_DATA_DIR, dataset) + '.yaml','r') as f:
        metadata = yaml.full_load(f)
    depth_max = metadata['depth_max']
    hfov = metadata['hfov']
    vfov = metadata['vfov']
    shape_imgs = metadata['shape_imgs']
    nb_imgs = metadata['nb_imgs_test']
    h5file = h5py.File(os.path.join(COLPREDMPC_DATA_DIR, metadata['hdf5_test']), 'r')
    data_input = h5file['raw']
    data_collision = h5file['collision']

    colcheck = ColChecker(depth_max, hfov, vfov, safe_ball_size)
    # colpred = Colpred(size_latent=128, filename=COLPREDMPC_WEIGHT_DIR+'/inflated_retry')
    colpred = Colpred(size_latent=128, filename=COLPREDMPC_WEIGHT_DIR+'/inflated')
    # colpred = Colpred(size_latent=128)
    if args.cpu: colpred.device = 'cpu'
    print('using', colpred.device)
    summary(colpred, input_size=[(1,3),(1, shape_imgs[0], shape_imgs[1], shape_imgs[2])], device=colpred.device)
    colpred.load_weights()
    # colpred.linear.load_weights()
    # colpred.encoder.load_weights()

    metrics = np.zeros((nb_imgs, 8))
    idx = list(range(nb_imgs))
    # np.random.shuffle(idx)
    for i in idx:
        depth_img = np.expand_dims(data_input[i,:,:,:], 0)
        collision_img = np.expand_dims(data_collision[i,:,:,:], 0)
        # fig, ax = plt.subplots(1, 2)
        # ax[0].imshow(depth_img[0,0,:,:])
        # ax[1].imshow(collision_img[0,0,:,:])
        # plt.show()

        with torch.no_grad():
            latent = colpred.encoder.encode(torch.tensor(depth_img, device=colpred.device))

        nb_points = 4000000

        ## COMPUTE CLASSIFICATION METRICS
        grid, grid_normalized = colcheck.sample_pos_in_frustrum(nb_points)
        # grid, grid_normalized = c olcheck.sample_pos_in_safeball(nb_points)
        gt = colcheck.check_image_points(collision_img[0,0,:,:], grid)
        grid_normalized = torch.tensor(grid_normalized, device=colpred.device, requires_grad=True)
        pred = colpred.linear(grid_normalized, latent.repeat(int(grid.shape[0]/latent.shape[0]), 1))
        binary_pred = np.where(pred.detach().cpu().numpy() > 0.5, 1, 0)

        # report = metrics.classification_report(gt, binary_pred, target_names=['free', 'collision'])
        ratio_col_gt = np.count_nonzero(gt) / gt.shape[0]
        ratio_col_pred = np.count_nonzero(binary_pred) / binary_pred.shape[0]
        accuracy = sklearn.metrics.accuracy_score(gt, binary_pred)
        precision = sklearn.metrics.precision_score(gt, binary_pred, zero_division=1.0)  # zero_div handles cases where no collision appears in image, hence precision/recall becomes undefined, the correct thing would be to ignore those images but too lazy
        recall = sklearn.metrics.recall_score(gt, binary_pred, zero_division=1.0)
        # grad = torch.autograd.grad(pred, grid_normalized, grad_outputs=torch.ones_like(pred), retain_graph=True, create_graph=True)[0]
        # norm_grad = torch.linalg.vector_norm(grad, dim=1).detach().cpu().numpy()
        # grad_ratio = np.count_nonzero(norm_grad>2.)/norm_grad.shape[0]
        # metrics[i,:] = [ratio_col_gt, ratio_col_pred, accuracy, precision, recall, norm_grad.mean(), norm_grad.max(), grad_ratio]
        metrics[i,:] = [ratio_col_gt, ratio_col_pred, accuracy, precision, recall,0,0,0]
        # print(i, ratio_col_gt, ratio_col_pred, accuracy, precision, recall, norm_grad.mean(), norm_grad.max(), grad_ratio)
        print(i, ratio_col_gt, ratio_col_pred, accuracy, precision, recall)

        ## DISPLAYS 2D CLASSFICIATION RESULTS (slices in the depth image)
        # angles = [20,10,0,-10,-20]
        # nb_pix_v = shape_imgs[1]
        # fig = plt.figure(str(i))
        # ax = fig.subplots(nrows=len(angles), ncols=5)
        # for i, bearing_v_deg in enumerate(angles):
        #     grid, grid_normalized = colcheck.grid_frustrum_slice(nb_points, bearing_v_deg, True)
        #     pixel_v = np.clip(-bearing_v_deg*np.pi/180/vfov * nb_pix_v/2 + nb_pix_v/2, 0, nb_pix_v-1)
        #
        #     # gt = colcheck.check_image_points(depth_img[0,0,:,:], grid)
        #     gt = colcheck.check_image_points(collision_img[0,0,:,:], grid)
        #     # gt = colcheck.check_image_points_np(collision_img[0,0,:,:], grid)
        #
        #     with torch.no_grad():
        #         pred = colpred.linear(torch.tensor(grid_normalized, device=colpred.device), latent.repeat(int(grid.shape[0]/latent.shape[0]), 1))
        #     pred = pred.cpu().numpy()
        #     binary_pred = np.where(pred > 0.5, 1, 0)
        #
        #     x = grid[:,0].reshape((int(grid.shape[0]**(1/2)),int(grid.shape[0]**(1/2)))).transpose()
        #     y = grid[:,1].reshape((int(grid.shape[0]**(1/2)),int(grid.shape[0]**(1/2)))).transpose()
        #
        #     ax[i,0].imshow(depth_img[0,0,:,:], vmin=0., vmax=1.)
        #     ax[i,0].hlines(pixel_v, 0, 480, color='red')
        #     ax[i,0].axis('off')
        #
        #     ax[i,1].imshow(collision_img[0,0,:,:], vmin=0., vmax=1.)
        #     ax[i,1].hlines(pixel_v, 0, 480, color='red')
        #     ax[i,1].axis('off')
        #
        #     ax[i,2].set_aspect('equal')
        #     ax[i,2].invert_xaxis()
        #     ax[i,2].contourf(y, x, gt.reshape((int(grid.shape[0]**(1/2)),int(grid.shape[0]**(1/2)))).transpose(), vmin=0., vmax=1.)
        #
        #     ax[i,3].set_aspect('equal')
        #     ax[i,3].invert_xaxis()
        #     ax[i,3].contourf(y, x, pred.reshape((int(grid.shape[0]**(1/2)),int(grid.shape[0]**(1/2)))).transpose(), vmin=0., vmax=1.)
        #
        #     ax[i,4].set_aspect('equal')
        #     ax[i,4].invert_xaxis()
        #     ax[i,4].contourf(y, x, (2*gt-binary_pred.flatten()).reshape((int(grid.shape[0]**(1/2)),int(grid.shape[0]**(1/2)))).transpose(), vmin=-1., vmax=2., cmap='bwr')
        #
        # plt.show()

        ## DISPLAYS 3D CLASSFICIATION RESULTS (sampled point cloud)
        # # grid, grid_normalized = colcheck.sample_pos_in_safeball(nb_points, True)
        # grid, grid_normalized = colcheck.sample_pos_in_frustrum(nb_points, False)
        # # grid, grid_normalized = colcheck.grid_frustrum(nb_points)
        # # grid, grid_normalized = colcheck.grid_frustrum_slice(nb_points, 0)
        #
        # gt = colcheck.check_image_points(depth_img[0,0,:,:], grid)
        # with torch.no_grad():
        #     pred = colpred.linear(torch.tensor(grid_normalized, device=colpred.device), latent.repeat(int(grid.shape[0]/latent.shape[0]), 1))
        # binary_pred = np.where(pred.cpu().numpy() > 0.5, 1, 0)
        #
        # grid_ones_gt = np.array([grid[i,:] for i in range(grid.shape[0]) if gt[i]])
        # gris_ones_pred = np.array([grid[i,:] for i in range(grid.shape[0]) if 1])#binary_pred[i]])
        #
        # fig = plt.figure()
        # ax1 = fig.add_subplot(131)
        # ax1.imshow(depth_img[0,0,:,:], vmin=0., vmax=1.)
        # ax1.axis('off')
        # ax2 = fig.add_subplot(132, projection='3d')
        # ax2.scatter(grid_ones_gt[:,0], grid_ones_gt[:,1], grid_ones_gt[:,2], color='k')
        # ax2.scatter(0,0,0, marker='s')
        # ax3 = fig.add_subplot(133, projection='3d')
        # ax3.scatter(gris_ones_pred[:,0], gris_ones_pred[:,1], gris_ones_pred[:,2], color='k')
        # ax3.scatter(0,0,0, marker='s')
        #
        # plt.show()

    print('ratio_col_gt', np.mean(metrics[:,0]), np.std(metrics[:,0]))
    print('ratio_col_pred', np.mean(metrics[:,1]), np.std(metrics[:,1]))
    print('accuracy', np.mean(metrics[:,2]), np.std(metrics[:,2]))
    print('precision', np.mean(metrics[:,3]), np.std(metrics[:,3]))
    print('recall', np.mean(metrics[:,4]), np.std(metrics[:,4]))
    print('avg gradient', np.mean(metrics[:,5]), np.std(metrics[:,5]))
    print('max gradient', np.mean(metrics[:,6]), np.std(metrics[:,6]))
    print('% grad > th', np.mean(metrics[:,7]), np.std(metrics[:,7]))

    outfile = os.path.join(COLPREDMPC_LOGS_DIR, 'metrics_test_inflated.pkl')
    with open(outfile, 'wb') as f:
        pickle.dump(metrics, f)


import subprocess
import plotly
try:
    import cadquery as cq
except:
    print('Not able to import cadquery')
import numpy as np
from stl import mesh  # pip install numpy-stl
import plotly.graph_objects as go
import streamlit as st
from PIL import Image
import os
import time
import base64 # to download from html link
from math import sqrt
hor_tolerance= 0.4
vert_tolerance= 0.8
chamfer_multi = 1

def create_download_link(val, filename):
    b64 = base64.b64encode(val)
    return f'Download mesh'

def stl2mesh3d(stl_mesh):
    # stl_mesh is read by nympy-stl from an stl file; it is  an array of faces/triangles (i.e. three 3d points)
    # This function extracts the unique vertices and the lists I, J, K to define a Plotly mesh3d
    p, q, r = stl_mesh.vectors.shape #(p, 3, 3)
    # the array stl_mesh.vectors.reshape(p*q, r) can contain multiple copies of the same vertex;
    # extract unique vertices from all mesh triangles
    vertices, ixr = np.unique(stl_mesh.vectors.reshape(p*q, r), return_inverse=True, axis=0)
    I = np.take(ixr, [3*k for k in range(p)])
    J = np.take(ixr, [3*k+1 for k in range(p)])
    K = np.take(ixr, [3*k+2 for k in range(p)])
    return vertices, I, J, K

def figure_mesh(filename):
  my_mesh = mesh.Mesh.from_file(filename)
  vertices, I, J, K = stl2mesh3d(my_mesh)
  x, y, z = vertices.T
  colorscale= [[0, '#e5dee5'], [1, '#e5dee5']]
  mesh3D = go.Mesh3d(
              x=x,
              y=y,
              z=z,
              i=I,
              j=J,
              k=K,
              name='mesh',
              showscale=False,
              colorscale=colorscale,
              intensity=z,
              flatshading=True,)
  title = "mesh"
  layout = go.Layout(
              paper_bgcolor='rgb(1,1,1)',
              title_text=None,# title_x=0.5, font_color='white',
              width=800,
              height=800,
              scene_camera=dict(eye=dict(x=1.25, y=-1.25, z=1)),
              scene_xaxis_visible=True,
              scene_yaxis_visible=True,
              scene_zaxis_visible=False)
  fig = go.Figure(data=[mesh3D], layout=layout)

  fig.data[0].update(lighting=dict(ambient= 0.18,
                                   diffuse= 1,
                                   fresnel=  .1,
                                   specular= 1,
                                   roughness= .1,
                                   facenormalsepsilon=0))
  fig.data[0].update(lightposition=dict(x=3000,
                                        y=3000,
                                        z=10000));
  fig.update_scenes(aspectmode='data')
  fig.write_html("file_stl.html")
  return fig

def cut_image(h, res):
    chamfer = h['h_break']*chamfer_multi
    # cut image
    cut_im =  cq.Workplane('XY').box(h['h_break'], h['h_break_len'],height,centered=(1,1,0)).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    # chamfer
    chamfer_top = cq.Workplane('XY').box(chamfer, h['h_break_len'],chamfer).rotate([0,0,0], [0,1,0], 45).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    chamfer_bot = cq.Workplane('XY').box(chamfer, h['h_break_len'],chamfer).rotate([0,0,0], [0,1,0], 45).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],height])
    res -= cut_im + chamfer_top + chamfer_bot
    return res

def normal_hinge(h, res):
    ### Diff part
    chamfer = h['h_break']*chamfer_multi
    pin_diam = (h['h_diam']-vert_tolerance)/3
    x_hinge = -h['h_break']/2-pin_diam/2
    res = cut_image(h, res)
    # hinge hole
    hole_h_im_x = (h['h_diam'] + pin_diam)/2 + hor_tolerance
    hole_im = cq.Workplane('XY').box(hole_h_im_x, h['h_thick']+hor_tolerance*2,height,centered=(1,1,0)).translate([-hole_h_im_x/2-h['h_break']/2,0,0]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    res -= hole_im
    ### Uni part
    hole_diam = pin_diam + vert_tolerance
    # hinge corner
    # hinge_corn = cq.Workplane('XZ').box(cq.Workplane('XY').box(hole_h_im_x, h['h_thick']+hor_tolerance*2,height,centered=(1,1,0)).translate([-hole_h_im_x/2-h['h_break']/2,0,0]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])/2 + pin_diam/2+chamfer*sqrt(2),h['h_diam'], h['h_thick'], centered=(0,0,1)).translate([-h['h_break']/2 -pin_diam/2,0,height/2-h['h_diam']/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    hinge_corn = cq.Workplane('XZ').box(hole_h_im_x/2+chamfer*sqrt(2),h['h_diam'], h['h_thick'], centered=(0,0,1)).translate([-h['h_break']/2 -pin_diam/2,0,height/2-h['h_diam']/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    # External hinge
    hinge_ext =  cq.Workplane('XZ').cylinder(h['h_thick'], h['h_diam']/2, centered=(1,0,1)).translate([x_hinge,0,height/2-h['h_diam']/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    hinge_hole =  cq.Workplane('XZ').cylinder(h['h_thick'], hole_diam/2, centered=(1,0,1)).translate([x_hinge,0,height/2-hole_diam/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    hinge_pin =  cq.Workplane('XZ').cylinder(h['h_thick']+hor_tolerance*2, pin_diam/2, centered=(1,0,1)).translate([x_hinge,0,height/2-pin_diam/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    res += hinge_corn + hinge_ext - hinge_hole + hinge_pin
    return res

def ball_joint(h, res):
    res = cut_image(h, res)
    hole_diam = h['h_diam']+vert_tolerance
    ### Diff part
    hole_im1 = cq.Workplane('XY').sphere(hole_diam/2).translate([-h['h_break']/2-hole_diam/2,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    hole_im2 = cq.Workplane('XY').sphere(hole_diam/2).translate([+h['h_break']/2+hole_diam/2,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    if h['h_expose']:
        hole_join = cq.Workplane('XY').box(h['h_break']+(h['h_diam']/2+hor_tolerance)*2, h['h_diam']/2+hor_tolerance,height,centered=(1,1,0)).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    else:
        hole_join = cq.Workplane('YZ').cylinder(h['h_break']+h['h_diam'], h['h_diam']/4+hor_tolerance).translate([0,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    res -= hole_im1 + hole_im2 + hole_join
    ### Uni part
    ball1 = cq.Workplane('XY').sphere(h['h_diam']/2).translate([-h['h_break']/2-hole_diam/2,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    ball2 = cq.Workplane('XY').sphere(h['h_diam']/2).translate([+h['h_break']/2+hole_diam/2,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    join = cq.Workplane('YZ').cylinder(h['h_break']+h['h_diam'], h['h_diam']/4).translate([0,0,height/2]).rotate([0,0,0], [0,0,1], h['h_rot']).translate([h['h_tran'][0],h['h_tran'][1],0])
    res += ball1 + ball2 + join
    return res

def build_preview(hinges, template):
    union = str()
    difference = str()
    for ind, h in hinges.items():
        if h['type'] == 'normal':
            union = union + f"""
    color("{color[ind-1]}")
    translate([{h['h_tran'][0]},{h['h_tran'][1]},0])
    rotate([0,0,{h['h_rot']}])
    uni_hinge({height}, hinge_diam={h['h_diam']}, hinge_h_thick={h['h_thick']}, break={h['h_break']});"""
        elif h['type'] == 'ball':
            union = union + f"""
color("{color[ind-1]}")
translate([{h['h_tran'][0]},{h['h_tran'][1]},0])
rotate([0,0,{h['h_rot']}])
uni_ball({height}, ball_diam={h['h_diam']}, break={h['h_break']});"""
        difference = difference + f"""
translate([{h['h_tran'][0]},{h['h_tran'][1]},0])
rotate([0,0,{h['h_rot']}])
diff({height}, break={h['h_break']}, break_len={h['h_break_len']});"""
    return template + difference + '};\n' + union

svg_to_dxf = """
translate(v=[{X_TRAN},{Y_TRAN},0])
  rotate(a=[0,0,{Z_DEG}])
    scale([{X_SCALE},{Y_SCALE},1])
      import(file = "file.svg", center = true);
"""

preview_template = """
$fn=10;

module diff(height, break=4, break_len=200){{
// line break
linear_extrude(height)
square([break, break_len], center=true);
}};

module uni_hinge(height, hinge_diam=5, hinge_h_thick=5, vert_tolerance=0.8, break=4, chamfer_multi=1){{
chamfer = break*chamfer_multi;
pin_diam = (hinge_diam-vert_tolerance)/3;
// external hinge
translate([-break/2-pin_diam/2,hinge_h_thick/2,height/2])
rotate([90,0,0])
linear_extrude(hinge_h_thick)
// external circle
circle(d=hinge_diam);
// squared cornern hing
linear_extrude(hinge_diam)
translate([-(chamfer*sqrt(2)/2-break/2)/2,0,0])
square([break/2+chamfer*sqrt(2)/2, hinge_h_thick], center=true);
}};

module uni_ball(height, ball_diam=5, tolerance=0.4, break=3){{
// internal ball left
translate([-ball_diam/2-break/2, 0, height/2]) sphere(r=ball_diam/2);
// internal ball right
translate([ball_diam/2+break/2, 0, height/2]) sphere(r=ball_diam/2);
// connection cylinder
translate([0, 0,height/2])
rotate([90, 0, 90])
cylinder(h=ball_diam+break,r=ball_diam/4, center=true);
}};

difference(){{
  linear_extrude(height = {HEIGHT})
    translate(v=[{X_TRAN},{Y_TRAN},0])
      rotate(a=[0,0,{Z_DEG}])
        scale([{X_SCALE},{Y_SCALE},1])
          import(file = "file.svg", center = true);
"""

color = ['red', 'navy', 'green', 'purple', 'silver', 'orange', 'indigo', 'teal', 'darkslategray',
    'yellowgreen', 'cyan', 'cornflowerblue', 'magenta', 'tan', 'darkred', 'deeppink', 'olive', 'lightsalmon', 'mocassin', 'rosybrown']

if __name__ == "__main__":
    for key in ('xlen', 'ylen', 'xmin', 'xmax', 'ymin', 'ymax'):
        if key not in st.session_state:
            st.session_state[key] = 0
    if 'hinges' not in st.session_state:
        st.session_state['hinges'] = dict()
    hinges = st.session_state['hinges']
    if 'image_value' not in st.session_state:
        st.session_state['image_value'] = [0]
    if hinges and max(list(hinges)) > len(color)-2:
        n_colors = len(hinges)//len(color)
        color = color * (n_colors+2)

    # clean memory
    for file in ['file.stl', 'preview.png']:
        if file in os.listdir():
            os.remove(file)

    st.title('Flexifier: make it flexi')
    st.write("Generate flexi 3D models from images! If you like the project put a like on [Printables](https://www.printables.com/it/model/505713-flexifier-make-it-flexi) or [support me with a coffee](https://www.paypal.com/donate/?hosted_button_id=V4LJ3Z3B3KXRY)! On Printables you can find more info about the project.", unsafe_allow_html=True)

    col1, col2, col3 = st.columns(3)
    # Input type
    with col1:
        filetype = st.selectbox('Input file type', ['png', 'jpg', 'svg', 'jpeg'])
    with col2:
        out = st.selectbox('Output file type', ['stl', 'step'])
    with col3:
        interface = st.selectbox('Interface', ['slider', 'number'])
    numb = False
    if interface == 'number':
        numb = True
    # Input file
    uploaded_file = st.file_uploader("Upload the file:", type=[filetype])
    if uploaded_file is not None:
        # To read file as bytes:
        bytes_data = uploaded_file.getvalue()
        with open(f'file.{filetype}', 'wb') as f:
            f.write(bytes_data)
        image_value = [bytes_data]

        # calculate the svg if the imgage is different from the previous one
        if image_value[0] != st.session_state['image_value'][0]:
            # avoid transparency in PNG, replace it with white
            if filetype == 'png':
                subprocess.run(f'convert file.{filetype} -background white -alpha remove -alpha off file.{filetype}', shell = True)
            # convert the img to svg
            if filetype != 'svg':
                subprocess.run(f'convert file.{filetype} file.pnm', shell=True)
                subprocess.run(f'potrace -s -o file.svg file.pnm', shell=True)

        # MODIFY IMAGE
        col1, col2, col3 = st.columns(3)
        with col1:
            scale = st.checkbox('Rescale image size')
        with col2:
            translate = st.checkbox('Translate the image')
        with col3:
            rotate = st.checkbox('Rotate the image')

        # SCALE
        scales = [0.4, 0.4]
        if scale:
            col1, col2, col3 = st.columns(3)
            with col1:
                if numb: scales[0] = scales[0] * st.number_input('X scale %', min_value=0.0, value=100.0) / 100
                else: scales[0] = scales[0] * st.slider('X scale %', 0.0, 500.0, step=0.1, value=100.0) / 100
            with col2:
                if numb: scales[1] = scales[1] * st.number_input('Y scale %', min_value=0.0, value=100.0) / 100
                else: scales[1] = scales[1] * st.slider('Y scale %', 0.0, 500.0, step=0.1, value=100.0) / 100
        image_value.append(scale)

        # TRANSLATE
        tran = [0.0, 0.0]
        if translate:
            col1, col2, col3 = st.columns(3)
            with col1:
                if numb: tran[0] = st.number_input('Move X', value=0.0)
                else: tran[0] = st.slider('Move X', 0.0, 200.0, step=0.1, value=0.0)
            with col2:
                if numb: tran[1] = st.number_input('Move Y', value=0.0)
                else: tran[1] = st.slider('Move Y', 0.0, 200.0, step=0.1, value=0.0)
        image_value.append(tran)

        # ROTATE
        rot = 0
        if rotate:
            col1, col2, col3 = st.columns(3)
            with col1:
                if numb: rot = st.number_input('Rotation Angle', value=0.0)
                else: rot = st.slider('Rotation Angle', 0.0, 360.0, step=0.1, value=0.0)
        image_value.append(rot)

        if numb: height = st.number_input('Model height (mm)', 0.0, 100.0 , 10.0)
        else: height = st.slider('Model height (mm)', 0.0, 100.0 , 10.0)

        # calculate bounding box only if it's a different image
        if image_value != st.session_state['image_value']:
            try:
                print('CALCOLO DXF')
                # CREATE DXF AND CALCULATE THE BOUNDING BOX
                with open("svg_to_dxf.scad", 'w') as f:
                    f.write(svg_to_dxf.format(X_TRAN=tran[0], Y_TRAN=tran[1], X_SCALE=scales[0], Y_SCALE=scales[1], Z_DEG=rot))
                subprocess.run(f'openscad svg_to_dxf.scad -o file.dxf', shell = True)
                result = (cq.importers.importDXF("file.dxf").wires().toPending().extrude(height))
                b_box = result.combine().objects[0].BoundingBox()
                st.session_state['xlen'] = b_box.xlen
                st.session_state['ylen'] = b_box.ylen
                st.session_state['xmin'] = b_box.xmin
                st.session_state['xmax'] = b_box.xmax
                st.session_state['ymin'] = b_box.ymin
                st.session_state['ymax'] = b_box.ymax
            except:
                st.warning('Not able to calculate the bounding box', icon="тЪая╕П")
                st.session_state['xlen'] = 200.0
                st.session_state['ylen'] = 200.0
                st.session_state['xmin'] = -100.0
                st.session_state['xmax'] = 100.0
                st.session_state['ymin'] = -100.0
                st.session_state['ymax'] = 100.0
        st.session_state['image_value'] = image_value
        xlen = st.session_state['xlen']
        ylen = st.session_state['ylen']
        xmin = st.session_state['xmin']
        xmax = st.session_state['xmax']
        ymin = st.session_state['ymin']
        ymax = st.session_state['ymax']

        def_values = {'h_tran': [0.0, 0.0], 'h_rot': 0.0, 'h_break': 3.0, 'h_break_len': ylen*2,
                      'h_diam': height, 'h_thick': 5.0, 'h_expose': True}

        col1, col2, col3, col4 = st.columns(4)
        with col1:
            ref = st.selectbox('Select Hinge', sorted(list(hinges), reverse=True))
        with col2:
            hinge_type = st.selectbox('Add hinge type:', ['normal', 'ball'])
        with col3:
            st.write('Add hinge')
            if st.button('Add'):
                if not hinges: #always start with at least one hinge
                    ind = 1
                    hinges[ind] = dict()
                    hinges[ind]['type'] = hinge_type
                    hinges[ind]['h_tran'] = [0.0, 0.0]
                    hinges[ind]['h_rot'] = 0.0
                    hinges[ind]['h_break'] = 3.0
                    hinges[ind]['h_break_len'] = 100.0
                    hinges[ind]['h_diam'] = height
                    hinges[ind]['h_thick'] = 5.0
                    hinges[ind]['h_expose'] = True
                    st.session_state['hinges'].update(hinges)
                    st.experimental_rerun()
                else:
                    ind = max(list(hinges)) + 1
                    hinges[ind] = dict()
                    hinges[ind].update(hinges[ref])
                    if hinges[ref]['type'] == 'normal' and hinge_type == 'ball':
                        hinges[ind]['type'] = hinge_type
                    if hinges[ref]['type'] == 'ball' and hinge_type == 'normal':
                        hinges[ind]['type'] = hinge_type
                        hinges[ind]['h_thick'] = 5.0
                    st.session_state['hinges'].update(hinges)
                    #st.experimental_rerun()
        with col4:
            st.write('Remove hinge')
            if st.button('Remove'):
                if ref not in hinges:
                    st.warning(f'Hinge {ref} not found. No hinge removed.', icon="тЪая╕П")
                else:
                    st.session_state['hinges'].pop(ref)
                    if st.session_state['hinges']:
                        ref = sorted(list(st.session_state['hinges']), reverse=True)[0]
                    else:
                        st.experimental_rerun()

        if hinges:
            col1, col2, col3 = st.columns(3)
            h_tran = [0.0, 0.0]
            with col1:
                if numb: h_tran[0] = st.number_input('Move X', value=def_values['h_tran'][0])
                else: h_tran[0] = st.slider('Move X', xmin, xmax, step=0.1, value=def_values['h_tran'][0])
            with col2:
                if numb: h_tran[1] = st.number_input('Move Y', value=def_values['h_tran'][1])
                else: h_tran[1] = st.slider('Move Y', ymin, ymax, step=0.1, value=def_values['h_tran'][1])
            with col3:
                if numb: h_rot = st.number_input('Rotate', value=def_values['h_rot'])
                else: h_rot = st.slider('Rotate', 0.0, 360.0, step=0.1, value=def_values['h_rot'])

            col1, col2, col3, col4 = st.columns(4)
            with col1:
                if hinges[ref]['type'] == 'normal':
                    h_expose = False
                    if numb: h_thick = st.number_input('Hinge thickness', value=def_values['h_thick'])
                    else: h_thick = st.slider('Hinge thickness', 0.1, 20.0, step=0.1, value=def_values['h_thick'])
                else:
                    h_thick = def_values['h_thick']
                    h_expose = st.checkbox('Expose ball joint', value=True)
            with col2:
                if numb: h_diam = st.number_input('Joint external diameter', value=def_values['h_diam'])
                else: h_diam = st.slider('Joint external diameter', 0.1, height, step=0.1, value=def_values['h_diam'])
            with col3:
                if numb: h_break = st.number_input('Image cut thickness', value=def_values['h_break'])
                else: h_break = st.slider('Image cut thickness',  0.1, max([ylen, xlen]), step=0.1, value=def_values['h_break'])
            with col4:
                if numb: h_break_len = st.number_input('Image cut length', value=def_values['h_break_len'])
                else: h_break_len = st.slider('Image cut length', h_thick, sqrt(ylen**2+xlen**2)*2, step=0.1, value=def_values['h_break_len'])

            hinges[ref]['h_tran'] = h_tran
            hinges[ref]['h_rot'] = h_rot
            hinges[ref]['h_thick'] = h_thick
            hinges[ref]['h_expose'] = h_expose
            hinges[ref]['h_diam'] = h_diam
            hinges[ref]['h_break'] = h_break
            hinges[ref]['h_break_len'] = h_break_len
            st.session_state['hinges'].update(hinges)
        #PREPARE FILES
        preview = False
        if not st.button('Render'):
            preview = True
        if preview:
            height_model = height/2
            openscad_template = preview_template
            # resize the scale of the svg
            templ = openscad_template.format(HEIGHT=height_model, X_TRAN=tran[0], Y_TRAN=tran[1], X_SCALE=scales[0], Y_SCALE=scales[1], Z_DEG=rot)
            run = build_preview(st.session_state['hinges'], templ)
            with open('run.scad', 'w') as f:
                f.write(run)
            if preview:
                subprocess.run('xvfb-run -a openscad -o preview.png --camera 0,0,0,0,0,0,0 --autocenter --viewall --view axes,scales  --projection=ortho run.scad', shell = True)
        else:
            print('Rendering')
            start = time.time()
            # run openscad
            with st.spinner('Rendering in progress...'):
                res = cq.importers.importDXF("file.dxf").wires().toPending().extrude(height)
                for h in hinges.values():
                    if h['type'] == 'normal':
                        res = normal_hinge(h, res)
                    else:
                        res = ball_joint(h, res)
                cq.exporters.export(res, f'file.{out}')
            end = time.time()
            st.success(f'Rendered in {int(end-start)} seconds', icon="тЬЕ")

        if preview:
            if 'preview.png' not in os.listdir():
                st.error('OpenScad was not able to generate the preview', icon="ЁЯЪи")
                st.stop()
            colors_text = 'Quick preview:'
            for index in st.session_state['hinges']:
                colors_text = colors_text + f' Hinge {index},'
            st.markdown(colors_text, unsafe_allow_html=True)
            image = Image.open('preview.png')
            st.image(image, caption='Openscad preview')
            image.close()
        else:
            if f'file.{out}' not in os.listdir():
                st.error('The program was not ot able to generate the mesh', icon="ЁЯЪи")
                st.stop()
            with open(f'file.{out}', "rb") as file:
                btn = st.download_button(
                        label=f"Download {out}",
                        data=file,
                        file_name=f'flexi.{out}',
                        mime=f"model/{out}"
                    )
            #html = create_download_link(file.read(), "model")
            #st.markdown(html, unsafe_allow_html=True)
            #if out=='stl':
                #st.write('Interactive mesh preview:')
                #st.plotly_chart(figure_mesh(f'file.stl'), use_container_width=True)
        st.markdown("Please, put a like [on Printables](https://www.printables.com/it/model/505713-flexifier-make-it-flexi) to support the project!", unsafe_allow_html=True)
        st.markdown("I am a student who enjoys 3D printing and programming. If you want to support me with a coffee, just [click here!](https://www.paypal.com/donate/?hosted_button_id=V4LJ3Z3B3KXRY)", unsafe_allow_html=True)


import itertools
import re

from bpython._py3compat import PythonLexer
from bpython.formatter import Parenthesis
from bpython import str_util
from pygments.token import Token

from six.moves import xrange

class ReplParser(object):
    def __init__(self, repl):
        self.repl = repl

    @property
    def buffer(self):
        return self.repl.buffer

    @property
    def cpos(self):
        return self.repl.cpos

    @property
    def s(self):
        return self.repl.s

    @property
    def highlighted_paren(self):
        return self.repl.highlighted_paren

    def reprint_line(self, *args):
        return self.repl.reprint_line(*args)

    def tokenize(self, s, newline=False):
        """Tokenize a line of code."""

        source = '\n'.join(self.buffer + [s])
        cursor = len(source) - self.cpos
        if self.cpos:
            cursor += 1
        stack = list()
        if len(self.buffer) == 0 and self.repl.interp.is_commandline(s):
            lines = s.split(' ')
            ftoken = lines[0]
            source = source[len(ftoken):]
            all_tokens = [(Token.Command, ftoken)]
            all_tokens += list(PythonLexer().get_tokens(source))
        else:
            all_tokens = list(PythonLexer().get_tokens(source))
        # Unfortunately, Pygments adds a trailing newline and strings with
        # no size, so strip them
        while not all_tokens[-1][1]:
            all_tokens.pop()
        all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip('\n'))
        line = pos = 0
        parens = dict(zip('{([', '})]'))
        line_tokens = list()
        saved_tokens = list()
        search_for_paren = True
        for (token, value) in self._split_lines(all_tokens):
            pos += len(value)
            if token is Token.Text and value == '\n':
                line += 1
                # Remove trailing newline
                line_tokens = list()
                saved_tokens = list()
                continue
            line_tokens.append((token, value))
            saved_tokens.append((token, value))
            if not search_for_paren:
                continue
            under_cursor = (pos == cursor)
            if token is Token.Punctuation:
                if value in parens:
                    if under_cursor:
                        line_tokens[-1] = (Parenthesis.UnderCursor, value)
                        # Push marker on the stack
                        stack.append((Parenthesis, value))
                    else:
                        stack.append((line, len(line_tokens) - 1,
                                      line_tokens, value))
                elif value in parens.values():
                    saved_stack = list(stack)
                    try:
                        while True:
                            opening = stack.pop()
                            if parens[opening[-1]] == value:
                                break
                    except IndexError:
                        # SyntaxError.. more closed parentheses than
                        # opened or a wrong closing paren
                        opening = None
                        if not saved_stack:
                            search_for_paren = False
                        else:
                            stack = saved_stack
                    if opening and opening[0] is Parenthesis:
                        # Marker found
                        line_tokens[-1] = (Parenthesis, value)
                        search_for_paren = False
                    elif opening and under_cursor and not newline:
                        if self.cpos:
                            line_tokens[-1] = (Parenthesis.UnderCursor, value)
                        else:
                            # The cursor is at the end of line and next to
                            # the paren, so it doesn't reverse the paren.
                            # Therefore, we insert the Parenthesis token
                            # here instead of the Parenthesis.UnderCursor
                            # token.
                            line_tokens[-1] = (Parenthesis, value)
                        (lineno, i, tokens, opening) = opening
                        if lineno == len(self.buffer):
                            self.repl.highlighted_paren = (lineno, saved_tokens)
                            line_tokens[i] = (Parenthesis, opening)
                        else:
                            self.repl.highlighted_paren = (lineno, list(tokens))
                            # We need to redraw a line
                            tokens[i] = (Parenthesis, opening)
                            self.reprint_line(lineno, tokens)
                        search_for_paren = False
                elif under_cursor:
                    search_for_paren = False
        if line != len(self.buffer):
            return list()
        return line_tokens

    def is_first_word(self):
        line = self.get_current_left_line()
        return str_util.is_only_word(line)

    def is_only_word(self):
        line = self.get_current_left_line().rstrip()
        return str_util.is_only_word(line)

    def is_assignment_statement(self):
        return str_util.is_assignment_statement(self.s)

    def get_current_left_line(self):
        if self.cpos:
            line = self.s[:-self.cpos]
        else:
            line = self.s
        return line

    def get_current_word(self):
        line = self.get_current_left_line()
        return str_util.get_rclosure_word(line)

    def get_current_sbracket(self):
        return str_util.get_rsbracket(self.s)

    def get_current_string(self):
        tokens = self.tokenize(self.s)
        string_tokens = list(itertools.takewhile(self._token_is_any_of([Token.String,
                                                        Token.Text]),
                                       reversed(tokens)))
        if not string_tokens:
            return ''
        opening = string_tokens.pop()[1]
        string = list()
        for (token, value) in reversed(string_tokens):
            if token is Token.Text:
                continue
            elif opening is None:
                opening = value
            elif token is Token.String.Doc:
                string.append(value[3:-3])
                opening = None
            elif value == opening:
                opening = None
                string = list()
            else:
                string.append(value)

        if opening is None:
            return ''
        return ''.join(string)

    def get_current_func(self):
        func, args = str_util.get_rfunc(self.s)
        if args:
            return func, len(args) - 1
        else:
            return func, 0

    def _split_lines(self, tokens):
        for (token, value) in tokens:
            if not value:
                continue
            while value:
                head, newline, value = value.partition('\n')
                yield (token, head)
                if newline:
                    yield (Token.Text, newline)

    def _token_is(self, token_type):
        """Return a callable object that returns whether a token is of the
        given type `token_type`."""

        def token_is_type(token):
            """Return whether a token is of a certain type or not."""
            token = token[0]
            while token is not token_type and token.parent:
                token = token.parent
            return token is token_type

        return token_is_type

    def _token_is_any_of(self, token_types):
        """Return a callable object that returns whether a token is any of the
        given types `token_types`."""
        is_token_types = map(self._token_is, token_types)

        def __token_is_any_of(token):
            return any(check(token) for check in is_token_types)

        return __token_is_any_of


#先下载psutil库:pip install psutil
import psutil
import os,datetime,time,platform

def get_cpu_use(): # cpu使用率不超过70%
    data = psutil.virtual_memory()
    cpu_use = int(psutil.cpu_percent(interval=1))

    flag = True
    if cpu_use > 70:
        flag = False

    return flag

def get_mem_use(): # mem使用率不超过80%
    data = psutil.virtual_memory()
    memory_use = int(round(data.percent))

    flag = True
    if memory_use > 80:
        flag = False

    return flag

def get_avg_load(): # 系统平均负载率
    flag = True
    if platform.system().lower() == 'linux':
        la1, la2, la3 = os.getloadavg()
        if la1 > 3 or la2 > 3 or la3 > 3:
            flag = False

    return flag

def total_load():  # 总负载情况
    if get_cpu_use() and get_mem_use() and get_avg_load():
        return True
    else:
        return False

def main():

    if total_load():
        print("True")
    else:
        print("False")

if __name__=="__main__":
    main()


import click

from autoeis.julia_helpers import install

@click.group("autoeis")
@click.pass_context
def autoeis_installer(context):
    ctx = context

@autoeis_installer.command("install", help="Install Julia dependencies for AutoEIS.")
@click.option(
    "-p",
    "julia_project",
    "--project",
    default=None,
    type=str,
    help="Install in a specific Julia project (e.g., a local copy of EquivalentProject.jl).",
    metavar="PROJECT_DIRECTORY",
)
@click.option("-q", "--quiet", is_flag=True, default=False, help="Disable logging.")
@click.option(
    "--precompile",
    "precompile",
    flag_value=True,
    default=None,
    help="Force precompilation of Julia libraries.",
)
@click.option(
    "--no-precompile",
    "precompile",
    flag_value=False,
    default=None,
    help="Disable precompilation.",
)
def install_cli(julia_project, quiet, precompile):
    install(julia_project=julia_project, quiet=quiet, precompile=precompile)


import os
import random
from datetime import datetime

import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from scrapy_splash import SplashRequest
from utils import LANGUAGE_MAPPER, GLOBAL_URLS, COUNTRY_MAPPER

settings = get_project_settings()

class RuSpider(scrapy.Spider):
    name = 'ru'
    start_urls = GLOBAL_URLS

    # start_urls = settings['RU_NEWSPAPERS_URLS']

    def start_requests(self):
        for url in self.start_urls:
            usr_ag = random.choice(settings['USER_AGENT_LIST'])
            yield SplashRequest(
                url=url,
                callback=self.parse,
                errback=self.errback_httpbin,
                headers={
                    'user-agent': usr_ag
                },
                splash_headers={
                    'user-agent': usr_ag,
                },
                args={
                    'wait': 0.5,
                }
            )

    def parse(self, response, **kwargs):
        try:
            result = [
                (value.css('*::text').get(),
                 value.css('::attr(href)').get())
                for value in response.css('a')
            ]
        except TypeError as e:
            self.logger.error(repr(e))
        else:
            for title, href in result:
                # print(result)
                item = {
                    "url": response.url,
                    "href": href,
                    "time": datetime.now(),
                    "lang": LANGUAGE_MAPPER.get(response.url),
                    "country": COUNTRY_MAPPER.get(response.url),
                    "title": title
                }
                yield item

    def errback_httpbin(self, failure):
        self.logger.error(repr(failure))

process = CrawlerProcess(settings=settings)
if int(os.environ.get("CRAWL")):
    process.crawl(RuSpider)
    process.start()


import torch
import os
import shutil

def load_checkpoint_and_params(CFG, rank, load_weights_by_name, scaler, net, optimizer):
    start_e = 0
    best_metric = [1, 0]
    epoch_history = []
    IoU_history_val = []
    disp_history_val = []
    loss_history_val = []
    IoU_history_train = []
    disp_history_train = []
    loss_history_train = []

    if CFG.f16:
        import apex
    if CFG.load_weights:
        map_location = {'cuda:%d' % 0: 'cuda:%d' % rank}
        if CFG.load_weights.rsplit('.', 1)[-1] == 'tar':
            checkpoint = torch.load(CFG.load_weights, map_location=map_location if CFG.nodes else 'cpu')

            state_dict = checkpoint['state_dict']
            # del state_dict["module.resnet_features.resnet_features.classifier.weight"]
            # del state_dict["module.resnet_features.resnet_features.classifier.bias"]
            # model_dict = list(net.state_dict().keys())
            # state_dict = changeWeightName(state_dict, model_dict)

            if load_weights_by_name:
                print('loading weights by name')
                own_state = net.state_dict()
                for name, param in state_dict.items():
                    if name == "module.Conv2DownUp11.1.ct2d.weight":
                        if "module.convOutput.ct2d.weight" in own_state:
                            print('found')
                            param = param.data
                            own_state["module.convOutput.ct2d.weight"].copy_(param)
                    if name not in own_state or name == 'module.segNet.Conv2DownUp2.1.ct2d.weight' or \
                            name == 'module.Conv2DownUp11.1.ct2d.weight':
                        continue
                    # if isinstance(param, Parameter):
                    # backwards compatibility for serialized parameters
                    param = param.data
                    own_state[name].copy_(param)
                net.load_state_dict(own_state)
            else:
                net.load_state_dict(state_dict)

            if CFG.f16 and 'amp' in checkpoint.keys():
                apex.amp.load_state_dict(checkpoint['amp'])
                print('amp dict loaded')
            else:
                print('no amp dict')

            if CFG.torch_amp and 'amp' in checkpoint.keys():
                scaler.load_state_dict(checkpoint['amp'])
                print('torch amp dict loaded')
            else:
                print('no cuda amp')

            start_e = checkpoint['epoch']
            if rank == 0:
                print('loading weights')
                print('training will start from epoch: ', start_e)

            if not load_weights_by_name:
                optimizer_dict = checkpoint['optimizer']
                optimizer.load_state_dict(optimizer_dict)

            if 'IoU_history_val' in checkpoint.keys():
                best_metric = checkpoint['best_metric']
                epoch_history = checkpoint['epoch_history']
                IoU_history_val = checkpoint['IoU_history_val']
                disp_history_val = checkpoint['disp_history_val']
                loss_history_val = checkpoint['loss_history_val']
                IoU_history_train = checkpoint['IoU_history_train']
                disp_history_train = checkpoint['disp_history_train']
                loss_history_train = checkpoint['loss_history_train']
        else:
            # from collections import OrderedDict
            # new_state_dict = OrderedDict()
            # for k, v in torch.load(CFG.load_weights).items():
            #     name = "module."+k#k.replace(".module", '') # remove 'module.' of dataparallel
            #     new_state_dict[name]=v

            # net.load_state_dict(new_state_dict)
            if load_weights_by_name:
                print('loading weights by name')
                own_state = net.state_dict()
                for name, param in torch.load(CFG.load_weights).items():
                    if name == "module.Conv2DownUp11.1.ct2d.weight":
                        if "module.convOutput.ct2d.weight" in own_state:
                            print('found')
                            param = param.data
                            own_state["module.convOutput.ct2d.weight"].copy_(param)
                    if name not in own_state:
                        continue
                    # else:
                    #     print('Freezing {}'.format(name))
                    #     param.requires_grad_(False)
                    # if isinstance(param, Parameter):
                    # backwards compatibility for serialized parameters
                    param = param.data
                    own_state[name].copy_(param)
                net.load_state_dict(own_state)
            else:
                net.load_state_dict(torch.load(CFG.load_weights))
            start_e = 0
    else:
        start_e = 0

    print(start_e, 'best metric', best_metric)
    return start_e, best_metric, epoch_history, IoU_history_val, disp_history_val, loss_history_val, \
        IoU_history_train, disp_history_train, loss_history_train


'''
Module responsible for GUI.
I know it's awaful....
'''
import sys
import time
import itertools
import numpy as np
import matplotlib.ticker as ticker
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
if is_pyqt5():
    from matplotlib.backends.backend_qt5agg import (
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
    from matplotlib.backends.backend_qt4agg import (
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from DoubleSlider import *

class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self,pupil,luminance_range):
        super(ApplicationWindow, self).__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        self.resize(800,1000)
        self.layout = QtWidgets.QVBoxLayout(self._main)
        self.pupil = pupil
        self.luminance_range=luminance_range
        static_canvas = FigureCanvas(Figure(figsize=(5.5, 7)))
        self.layout.addWidget(static_canvas)
        ''' Field Diameter Slider'''
        self.field_diameter_layout = QtWidgets.QHBoxLayout()
        self.field_diameter_slider = DoubleSlider(Qt.Horizontal)
        self.field_diameter_slider.setMaximum(60)
        self.field_diameter_slider.setMinimum(0.1)
        self.field_diameter_slider.setValue(10)
        self.field_diameter_slider.setTickPosition(QSlider.TicksBelow)
        self.field_diameter_slider.setTickInterval(1)
        self.field_diameter_label = QLabel()
        self.field_diameter_label.setText("Field diameter (deg)")
        self.field_diameter_label.setAlignment(Qt.AlignCenter)
        self.field_diameter_value_label = QLabel()
        text = "{:0.2f}".format(self.field_diameter_slider.value())
        self.field_diameter_value_label.setText(text)#   str(self.field_diameter_slider.value()))
        self.field_diameter_value_label.setAlignment(Qt.AlignCenter)
        self.field_diameter_layout.addWidget(self.field_diameter_label)
        self.field_diameter_layout.addWidget(self.field_diameter_slider)
        self.field_diameter_layout.addWidget(self.field_diameter_value_label)
        self.layout.addLayout(self.field_diameter_layout)
        self.field_diameter_slider.sliderReleased.connect(self.field_diameter_OnSliderReleased)
        self.field_diameter_slider.valueChanged.connect(self.field_diameter_OnValueChanged)

        '''Age Slider '''
        self.age_layout = QtWidgets.QHBoxLayout()
        self.age_slider = QSlider(Qt.Horizontal)
        self.age_slider.setMaximum(83)
        self.age_slider.setMinimum(20)
        self.age_slider.setValue(self.pupil.y)
        self.age_slider.setTickPosition(QSlider.TicksBelow)
        self.age_slider.setTickInterval(1)
        self.age_label = QLabel()
        self.age_label.setText("Age (years)")
        self.age_label.setAlignment(Qt.AlignCenter)
        self.age_value_label = QLabel()
        self.age_value_label.setText(str(self.age_slider.value()))
        self.age_value_label.setAlignment(Qt.AlignCenter)
        self.age_layout.addWidget(self.age_label)
        self.age_layout.addWidget(self.age_slider)
        self.age_layout.addWidget(self.age_value_label)
        self.layout.addLayout(self.age_layout)
        self.age_slider.valueChanged.connect(self.age_onValueChanged)
        self.age_slider.sliderReleased.connect(self.age_onSliderReleased)
        '''Radio button eyes'''
        self.eyes_layout = QtWidgets.QHBoxLayout()
        self.eyes_layout.setAlignment(Qt.AlignCenter)
        self.eyes_label = QLabel()
        self.eyes_label.setText("Eyes")
        self.eyes_one_radio_button = QRadioButton("1")
        self.eyes_two_radio_button = QRadioButton("2")
        self.eyes_two_radio_button.setChecked(True)
        self.eyes_layout.addWidget(self.eyes_label)
        self.eyes_layout.addWidget(self.eyes_one_radio_button)
        self.eyes_layout.addWidget(self.eyes_two_radio_button)
        self.layout.addLayout(self.eyes_layout)
        self.eyes_one_radio_button.toggled.connect(lambda:self.eyes_OnValueChanged(self.eyes_one_radio_button))
        self.eyes_two_radio_button.toggled.connect(lambda:self.eyes_OnValueChanged(self.eyes_two_radio_button))
        ''' Range of luminance
        self.range_luminance_layout = QFormLayout()
        self.range_luminance_cb_min = QSpinBox()
        self.range_luminance_cb_min.setMinimum(-6)
        self.range_luminance_cb_min.setMaximum(-1)
        self.range_luminance_cb_min.setValue(-6)
        self.range_luminance_cb_max = QSpinBox()
        self.range_luminance_cb_max.setMinimum(1)
        self.range_luminance_cb_max.setMaximum(10)
        self.range_luminance_cb_max.setValue(1)
    #    self.pupil.L =np.arange(10**self.range_luminance_cb_min.value(),np.power(10,self.range_luminance_cb_max.value()),0.01)
        self.range_luminance_layout.addRow((QLabel("Minimum (log cd m-2)")),self.range_luminance_cb_min)
        self.range_luminance_layout.addRow((QLabel("Maximum (log cd m-2)")),self.range_luminance_cb_max)
        self.layout.addLayout(self.range_luminance_layout)
        self.range_luminance_cb_max.valueChanged.connect(self.range_luminance_OnValueChanged)
        self.range_luminance_cb_min.valueChanged.connect(self.range_luminance_OnValueChanged)
    	'''
        self.algorithm_layout_grid = QHBoxLayout()#QGridLayout()
        ''' Checkboxes for algorithm'''
        self.algorith_checkboxes =[QCheckBox("Holladay",self),
                              QCheckBox("MoonSpancer",self),
                              QCheckBox("DeGroot Gebhard",self),
                              QCheckBox("Stanley Davies",self),
                              QCheckBox("Barten",self),
                              QCheckBox("Blackie Howland",self),
                              QCheckBox("Unified",self),
                              QCheckBox("Crawford",self)
        #                      QCheckBox("Winn",self)
                             #      QCheckBox("Winn Age Slope",self)
                             ]
        for i in range(0,len(self.algorith_checkboxes)):
            self.algorithm_layout_grid.addWidget(self.algorith_checkboxes[i])
            self.algorith_checkboxes[i].stateChanged.connect(lambda checked,i=i:self.updatePlot())
        self.layout.addLayout(self.algorithm_layout_grid)

        self.addToolBar(NavigationToolbar(static_canvas, self))
        self._static_ax = static_canvas.figure.subplots()
        self.updatePlot()

    def field_diameter_OnSliderReleased(self):
        self.pupil.a=self.field_diameter_slider.value()
        self.updatePlot()
    def field_diameter_OnValueChanged(self):
        text = "{:0.2f}".format(self.field_diameter_slider.value())
        self.field_diameter_value_label.setText(text)
    def age_onValueChanged(self):
        self.age_value_label.setText(str(self.age_slider.value()))

    def age_onSliderReleased(self):
        self.pupil.y=self.age_slider.value()
        self.updatePlot()

    def eyes_OnValueChanged(self,button):
        if button.text() == "1" and button.isChecked() == True:
            self.pupil.e = 1
        elif button.text() == "2" and button.isChecked() == True:
            self.pupil.e = 2
        self.updatePlot()
    '''def range_luminance_OnValueChanged(self):
        #self.pupil.L =np.arange(10**self.range_luminance_cb_min.value(),np.power(10,self.range_luminance_cb_max.value()),0.001)
        self.updatePlot()
    '''
    def updatePlot(self):
        self._static_ax.cla()

        if self.algorith_checkboxes[0].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.holladay(self.luminance_range),color=(1,0,0),label="Holladay")
        if self.algorith_checkboxes[1].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.moon_spancer(self.luminance_range),color=(0,1,0),label="MoonSpencer")
        if self.algorith_checkboxes[2].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.deGroot_gebhard(self.luminance_range),color=(0,0,1),label="DeGroot Gebhard")
        if self.algorith_checkboxes[3].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.stanley_davies(self.luminance_range,self.pupil.a),color=(1,0.51,0),label="Stanley Davies")
        if self.algorith_checkboxes[4].isChecked():
           self._static_ax.plot(self.luminance_range,self.pupil.barten(self.luminance_range),color=(0,1,1),label="Barten")
        if self.algorith_checkboxes[5].isChecked():
           self._static_ax.plot(self.luminance_range,self.pupil.blackie_howland(self.luminance_range),color=(0.5,0.5,0),label="Blackie Howland")
        if self.algorith_checkboxes[6].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.unified_formula(self.luminance_range),color=(0,0,0),linestyle='--',label="Unified")
        if self.algorith_checkboxes[7].isChecked():
            self._static_ax.plot(self.luminance_range,self.pupil.crawford(self.luminance_range),color=(0.5,0.7,0.3),label="Crawford")

        def flip(items, ncol):
            return itertools.chain(*[items[i::ncol] for i in range(ncol)])

        handles, labels = self._static_ax.get_legend_handles_labels()
        handles = np.concatenate((handles[::2],handles[1::2]),axis=0)
        labels = np.concatenate((labels[::2],labels[1::2]),axis=0)
        self._static_ax.legend(flip(handles, 2), flip(labels, 2), loc=9, ncol=2)
        self._static_ax.set_xscale('log')
        self._static_ax.xaxis.set_major_locator(ticker.LogLocator(base=10,numticks=5))
        self._static_ax.yaxis.set_major_locator(ticker.MultipleLocator(base=1.0))
        self._static_ax.set_ylim(top=10,bottom=0)
        self._static_ax.set_xlim(right=10**4,left=10**-4)
        self._static_ax.set_ylabel('Diameter (mm)')
        self._static_ax.set_xlabel(r'Luminance (cd $m^{-2}$)')
        self._static_ax.grid()
        #self._static_ax.figure.tight_layout();
        self._static_ax.figure.canvas.draw()

def show_GUI(pupil,luminance_range):
    qapp = QtWidgets.QApplication(sys.argv)
    app = ApplicationWindow(pupil,luminance_range)
    app.show()
    qapp.exec_()